Annotation of src/libexec/httpd/cgi-bozo.c, Revision 1.24
1.24 ! mrg 1: /* $NetBSD: cgi-bozo.c,v 1.23 2013/10/12 18:46:12 mbalmer Exp $ */
1.4 tls 2:
1.20 mrg 3: /* $eterna: cgi-bozo.c,v 1.40 2011/11/18 09:21:15 mrg Exp $ */
1.1 tls 4:
5: /*
1.24 ! mrg 6: * Copyright (c) 1997-2014 Matthew R. Green
1.1 tls 7: * All rights reserved.
8: *
9: * Redistribution and use in source and binary forms, with or without
10: * modification, are permitted provided that the following conditions
11: * are met:
12: * 1. Redistributions of source code must retain the above copyright
13: * notice, this list of conditions and the following disclaimer.
14: * 2. Redistributions in binary form must reproduce the above copyright
15: * notice, this list of conditions and the following disclaimer and
16: * dedication in the documentation and/or other materials provided
17: * with the distribution.
18: *
19: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22: * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24: * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26: * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29: * SUCH DAMAGE.
30: *
31: */
32:
33: /* this code implements CGI/1.2 for bozohttpd */
34:
35: #ifndef NO_CGIBIN_SUPPORT
36:
37: #include <sys/param.h>
38: #include <sys/socket.h>
39:
40: #include <ctype.h>
41: #include <errno.h>
42: #include <paths.h>
43: #include <signal.h>
44: #include <stdlib.h>
45: #include <string.h>
1.13 mrg 46: #include <syslog.h>
1.1 tls 47: #include <unistd.h>
48:
49: #include <netinet/in.h>
50:
51: #include "bozohttpd.h"
52:
53: #define CGIBIN_PREFIX "cgi-bin/"
54: #define CGIBIN_PREFIX_LEN (sizeof(CGIBIN_PREFIX)-1)
55:
1.15 mrg 56: #ifndef USE_ARG
57: #define USE_ARG(x) /*LINTED*/(void)&(x)
58: #endif
59:
60: /*
61: * given the file name, return a CGI interpreter
62: */
63: static const char *
64: content_cgihandler(bozohttpd_t *httpd, bozo_httpreq_t *request,
65: const char *file)
66: {
67: bozo_content_map_t *map;
68:
69: USE_ARG(request);
70: debug((httpd, DEBUG_FAT, "content_cgihandler: trying file %s", file));
71: map = bozo_match_content_map(httpd, file, 0);
72: if (map)
73: return map->cgihandler;
74: return NULL;
75: }
76:
77: static int
78: parse_header(bozohttpd_t *httpd, const char *str, ssize_t len, char **hdr_str,
79: char **hdr_val)
80: {
81: char *name, *value;
82:
83: /* if the string passed is zero-length bail out */
84: if (*str == '\0')
85: return -1;
86:
87: value = bozostrdup(httpd, str);
88:
89: /* locate the ':' separator in the header/value */
90: name = bozostrnsep(&value, ":", &len);
91:
92: if (NULL == name || -1 == len) {
93: free(name);
94: return -1;
95: }
1.1 tls 96:
1.15 mrg 97: /* skip leading space/tab */
98: while (*value == ' ' || *value == '\t')
99: len--, value++;
100:
101: *hdr_str = name;
102: *hdr_val = value;
103:
104: return 0;
105: }
106:
107: /*
108: * handle parsing a CGI header output, transposing a Status: header
109: * into the HTTP reply (ie, instead of "200 OK").
110: */
111: static void
112: finish_cgi_output(bozohttpd_t *httpd, bozo_httpreq_t *request, int in, int nph)
113: {
114: char buf[BOZO_WRSZ];
115: char *str;
116: ssize_t len;
117: ssize_t rbytes;
118: SIMPLEQ_HEAD(, bozoheaders) headers;
119: bozoheaders_t *hdr, *nhdr;
120: int write_header, nheaders = 0;
121:
122: /* much of this code is like bozo_read_request()'s header loop. */
123: SIMPLEQ_INIT(&headers);
124: write_header = nph == 0;
125: /* was read(2) here - XXX - agc */
126: while (nph == 0 &&
127: (str = bozodgetln(httpd, in, &len, bozo_read)) != NULL) {
128: char *hdr_name, *hdr_value;
129:
130: if (parse_header(httpd, str, len, &hdr_name, &hdr_value))
131: break;
132:
133: /*
134: * The CGI 1.{1,2} spec both say that if the cgi program
135: * returns a `Status:' header field then the server MUST
136: * return it in the response. If the cgi program does
137: * not return any `Status:' header then the server should
138: * respond with 200 OK.
139: * XXX The CGI 1.1 and 1.2 specification differ slightly on
140: * this in that v1.2 says that the script MUST NOT return a
141: * `Status:' header if it is returning a `Location:' header.
142: * For compatibility we are going with the CGI 1.1 behavior.
143: */
144: if (strcasecmp(hdr_name, "status") == 0) {
145: debug((httpd, DEBUG_OBESE,
146: "bozo_process_cgi: writing HTTP header "
147: "from status %s ..", hdr_value));
148: bozo_printf(httpd, "%s %s\r\n", request->hr_proto,
149: hdr_value);
150: bozo_flush(httpd, stdout);
151: write_header = 0;
152: free(hdr_name);
153: break;
154: }
155:
156: hdr = bozomalloc(httpd, sizeof *hdr);
157: hdr->h_header = hdr_name;
158: hdr->h_value = hdr_value;
159: SIMPLEQ_INSERT_TAIL(&headers, hdr, h_next);
160: nheaders++;
161: }
162:
163: if (write_header) {
164: debug((httpd, DEBUG_OBESE,
165: "bozo_process_cgi: writing HTTP header .."));
166: bozo_printf(httpd,
167: "%s 200 OK\r\n", request->hr_proto);
168: bozo_flush(httpd, stdout);
169: }
170:
171: if (nheaders) {
172: debug((httpd, DEBUG_OBESE,
173: "bozo_process_cgi: writing delayed HTTP headers .."));
174: SIMPLEQ_FOREACH_SAFE(hdr, &headers, h_next, nhdr) {
175: bozo_printf(httpd, "%s: %s\r\n", hdr->h_header,
176: hdr->h_value);
177: free(hdr->h_header);
178: free(hdr);
179: }
180: bozo_printf(httpd, "\r\n");
181: bozo_flush(httpd, stdout);
182: }
183:
184: /* XXX we should have some goo that times us out
185: */
186: while ((rbytes = read(in, buf, sizeof buf)) > 0) {
187: ssize_t wbytes;
188: char *bp = buf;
189:
190: while (rbytes) {
191: wbytes = bozo_write(httpd, STDOUT_FILENO, buf,
192: (size_t)rbytes);
193: if (wbytes > 0) {
194: rbytes -= wbytes;
195: bp += wbytes;
196: } else
197: bozo_err(httpd, 1,
198: "cgi output write failed: %s",
199: strerror(errno));
200: }
201: }
202: }
203:
204: static void
205: append_index_html(bozohttpd_t *httpd, char **url)
206: {
207: *url = bozorealloc(httpd, *url,
208: strlen(*url) + strlen(httpd->index_html) + 1);
209: strcat(*url, httpd->index_html);
210: debug((httpd, DEBUG_NORMAL,
211: "append_index_html: url adjusted to `%s'", *url));
212: }
1.1 tls 213:
214: void
1.15 mrg 215: bozo_cgi_setbin(bozohttpd_t *httpd, const char *path)
1.1 tls 216: {
1.15 mrg 217: httpd->cgibin = strdup(path);
218: debug((httpd, DEBUG_OBESE, "cgibin (cgi-bin directory) is %s",
219: httpd->cgibin));
1.1 tls 220: }
221:
222: /* help build up the environ pointer */
223: void
1.15 mrg 224: bozo_setenv(bozohttpd_t *httpd, const char *env, const char *val,
225: char **envp)
1.1 tls 226: {
1.15 mrg 227: char *s1 = bozomalloc(httpd, strlen(env) + strlen(val) + 2);
1.1 tls 228:
229: strcpy(s1, env);
230: strcat(s1, "=");
231: strcat(s1, val);
1.15 mrg 232: debug((httpd, DEBUG_OBESE, "bozo_setenv: %s", s1));
1.1 tls 233: *envp = s1;
234: }
235:
236: /*
237: * Checks if the request has asked for a cgi-bin. Should only be called if
238: * cgibin is set. If it starts CGIBIN_PREFIX or has a ncontent handler,
1.12 mrg 239: * process the cgi, otherwise just return. Returns 0 if it did not handle
240: * the request.
1.1 tls 241: */
1.12 mrg 242: int
1.15 mrg 243: bozo_process_cgi(bozo_httpreq_t *request)
1.1 tls 244: {
1.15 mrg 245: bozohttpd_t *httpd = request->hr_httpd;
246: char buf[BOZO_WRSZ];
247: char date[40];
248: bozoheaders_t *headp;
1.1 tls 249: const char *type, *clen, *info, *cgihandler;
1.9 tls 250: char *query, *s, *t, *path, *env, *command, *file, *url;
1.1 tls 251: char **envp, **curenvp, *argv[4];
1.17 mrg 252: char *uri;
1.1 tls 253: size_t len;
254: ssize_t rbytes;
255: pid_t pid;
256: int envpsize, ix, nph;
257: int sv[2];
258:
1.15 mrg 259: if (!httpd->cgibin && !httpd->process_cgi)
1.12 mrg 260: return 0;
1.1 tls 261:
1.17 mrg 262: uri = request->hr_oldfile ? request->hr_oldfile : request->hr_file;
263: if (uri[0] == '/')
264: file = bozostrdup(httpd, uri);
265: else
266: asprintf(&file, "/%s", uri);
1.12 mrg 267: if (file == NULL)
268: return 0;
1.17 mrg 269:
1.12 mrg 270: if (request->hr_query && strlen(request->hr_query))
1.15 mrg 271: query = bozostrdup(httpd, request->hr_query);
1.12 mrg 272: else
273: query = NULL;
274:
275: asprintf(&url, "%s%s%s", file, query ? "?" : "", query ? query : "");
276: if (url == NULL)
277: goto out;
1.15 mrg 278: debug((httpd, DEBUG_NORMAL, "bozo_process_cgi: url `%s'", url));
1.1 tls 279:
280: path = NULL;
281: envp = NULL;
282: cgihandler = NULL;
283: command = NULL;
284: info = NULL;
1.13 mrg 285:
1.1 tls 286: len = strlen(url);
287:
1.16 mrg 288: if (bozo_auth_check(request, url + 1))
1.12 mrg 289: goto out;
290:
1.15 mrg 291: if (!httpd->cgibin ||
292: strncmp(url + 1, CGIBIN_PREFIX, CGIBIN_PREFIX_LEN) != 0) {
293: cgihandler = content_cgihandler(httpd, request, file + 1);
1.1 tls 294: if (cgihandler == NULL) {
1.15 mrg 295: debug((httpd, DEBUG_FAT,
296: "bozo_process_cgi: no handler, returning"));
1.12 mrg 297: goto out;
1.1 tls 298: }
1.9 tls 299: if (len == 0 || file[len - 1] == '/')
1.15 mrg 300: append_index_html(httpd, &file);
301: debug((httpd, DEBUG_NORMAL, "bozo_process_cgi: cgihandler `%s'",
1.1 tls 302: cgihandler));
1.8 mrg 303: } else if (len - 1 == CGIBIN_PREFIX_LEN) /* url is "/cgi-bin/" */
1.15 mrg 304: append_index_html(httpd, &file);
1.12 mrg 305:
1.1 tls 306: ix = 0;
307: if (cgihandler) {
1.9 tls 308: command = file + 1;
1.15 mrg 309: path = bozostrdup(httpd, cgihandler);
1.1 tls 310: argv[ix++] = path;
311: /* argv[] = [ path, command, query, NULL ] */
312: } else {
1.9 tls 313: command = file + CGIBIN_PREFIX_LEN + 1;
1.1 tls 314: if ((s = strchr(command, '/')) != NULL) {
1.15 mrg 315: info = bozostrdup(httpd, s);
1.1 tls 316: *s = '\0';
317: }
1.15 mrg 318: path = bozomalloc(httpd,
319: strlen(httpd->cgibin) + 1 + strlen(command) + 1);
320: strcpy(path, httpd->cgibin);
1.1 tls 321: strcat(path, "/");
322: strcat(path, command);
323: /* argv[] = [ command, query, NULL ] */
324: }
325: argv[ix++] = command;
326: argv[ix++] = query;
327: argv[ix++] = NULL;
328:
329: nph = strncmp(command, "nph-", 4) == 0;
330:
331: type = request->hr_content_type;
332: clen = request->hr_content_length;
333:
334: envpsize = 13 + request->hr_nheaders +
335: (info && *info ? 1 : 0) +
336: (query && *query ? 1 : 0) +
337: (type && *type ? 1 : 0) +
338: (clen && *clen ? 1 : 0) +
339: (request->hr_remotehost && *request->hr_remotehost ? 1 : 0) +
340: (request->hr_remoteaddr && *request->hr_remoteaddr ? 1 : 0) +
1.15 mrg 341: bozo_auth_cgi_count(request) +
1.1 tls 342: (request->hr_serverport && *request->hr_serverport ? 1 : 0);
343:
1.15 mrg 344: debug((httpd, DEBUG_FAT,
345: "bozo_process_cgi: path `%s', cmd `%s', info `%s', "
346: "query `%s', nph `%d', envpsize `%d'",
347: path, command, strornull(info),
348: strornull(query), nph, envpsize));
1.14 mrg 349:
1.15 mrg 350: envp = bozomalloc(httpd, sizeof(*envp) * envpsize);
1.1 tls 351: for (ix = 0; ix < envpsize; ix++)
352: envp[ix] = NULL;
353: curenvp = envp;
1.3 tls 354:
355: SIMPLEQ_FOREACH(headp, &request->hr_headers, h_next) {
1.1 tls 356: const char *s2;
1.15 mrg 357: env = bozomalloc(httpd, 6 + strlen(headp->h_header) + 1 +
1.1 tls 358: strlen(headp->h_value));
359:
360: t = env;
361: strcpy(t, "HTTP_");
362: t += strlen(t);
363: for (s2 = headp->h_header; *s2; t++, s2++)
364: if (islower((u_int)*s2))
365: *t = toupper((u_int)*s2);
366: else if (*s2 == '-')
367: *t = '_';
368: else
369: *t = *s2;
370: *t = '\0';
1.15 mrg 371: debug((httpd, DEBUG_OBESE, "setting header %s as %s = %s",
1.1 tls 372: headp->h_header, env, headp->h_value));
1.15 mrg 373: bozo_setenv(httpd, env, headp->h_value, curenvp++);
1.1 tls 374: free(env);
375: }
1.14 mrg 376:
1.1 tls 377: #ifndef _PATH_DEFPATH
378: #define _PATH_DEFPATH "/usr/bin:/bin"
379: #endif
380:
1.15 mrg 381: bozo_setenv(httpd, "PATH", _PATH_DEFPATH, curenvp++);
382: bozo_setenv(httpd, "IFS", " \t\n", curenvp++);
1.21 martin 383: bozo_setenv(httpd, "SERVER_NAME", BOZOHOST(httpd,request), curenvp++);
1.15 mrg 384: bozo_setenv(httpd, "GATEWAY_INTERFACE", "CGI/1.1", curenvp++);
385: bozo_setenv(httpd, "SERVER_PROTOCOL", request->hr_proto, curenvp++);
386: bozo_setenv(httpd, "REQUEST_METHOD", request->hr_methodstr, curenvp++);
387: bozo_setenv(httpd, "SCRIPT_NAME", file, curenvp++);
388: bozo_setenv(httpd, "SCRIPT_FILENAME", file + 1, curenvp++);
389: bozo_setenv(httpd, "SERVER_SOFTWARE", httpd->server_software,
390: curenvp++);
1.17 mrg 391: bozo_setenv(httpd, "REQUEST_URI", uri, curenvp++);
1.15 mrg 392: bozo_setenv(httpd, "DATE_GMT", bozo_http_date(date, sizeof(date)),
393: curenvp++);
1.1 tls 394: if (query && *query)
1.15 mrg 395: bozo_setenv(httpd, "QUERY_STRING", query, curenvp++);
1.1 tls 396: if (info && *info)
1.15 mrg 397: bozo_setenv(httpd, "PATH_INFO", info, curenvp++);
1.1 tls 398: if (type && *type)
1.15 mrg 399: bozo_setenv(httpd, "CONTENT_TYPE", type, curenvp++);
1.1 tls 400: if (clen && *clen)
1.15 mrg 401: bozo_setenv(httpd, "CONTENT_LENGTH", clen, curenvp++);
1.1 tls 402: if (request->hr_serverport && *request->hr_serverport)
1.15 mrg 403: bozo_setenv(httpd, "SERVER_PORT", request->hr_serverport,
404: curenvp++);
1.1 tls 405: if (request->hr_remotehost && *request->hr_remotehost)
1.15 mrg 406: bozo_setenv(httpd, "REMOTE_HOST", request->hr_remotehost,
407: curenvp++);
1.1 tls 408: if (request->hr_remoteaddr && *request->hr_remoteaddr)
1.15 mrg 409: bozo_setenv(httpd, "REMOTE_ADDR", request->hr_remoteaddr,
410: curenvp++);
1.19 tls 411: /*
412: * XXX Apache does this when invoking content handlers, and PHP
413: * XXX 5.3 requires it as a "security" measure.
414: */
415: if (cgihandler)
416: bozo_setenv(httpd, "REDIRECT_STATUS", "200", curenvp++);
1.16 mrg 417: bozo_auth_cgi_setenv(request, &curenvp);
1.1 tls 418:
1.10 tls 419: free(file);
420: free(url);
1.1 tls 421:
1.15 mrg 422: debug((httpd, DEBUG_FAT, "bozo_process_cgi: going exec %s, %s %s %s",
1.12 mrg 423: path, argv[0], strornull(argv[1]), strornull(argv[2])));
424:
1.15 mrg 425: if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sv) == -1)
426: bozo_err(httpd, 1, "child socketpair failed: %s",
427: strerror(errno));
1.1 tls 428:
429: /*
430: * We create 2 procs: one to become the CGI, one read from
431: * the CGI and output to the network, and this parent will
432: * continue reading from the network and writing to the
433: * CGI procsss.
434: */
435: switch (fork()) {
436: case -1: /* eep, failure */
1.15 mrg 437: bozo_err(httpd, 1, "child fork failed: %s", strerror(errno));
438: /*NOTREACHED*/
1.1 tls 439: case 0:
440: close(sv[0]);
441: dup2(sv[1], STDIN_FILENO);
442: dup2(sv[1], STDOUT_FILENO);
1.13 mrg 443: close(2);
444: close(sv[1]);
445: closelog();
1.15 mrg 446: bozo_daemon_closefds(httpd);
1.10 tls 447:
1.1 tls 448: if (-1 == execve(path, argv, envp))
1.15 mrg 449: bozo_err(httpd, 1, "child exec failed: %s: %s",
1.13 mrg 450: path, strerror(errno));
1.1 tls 451: /* NOT REACHED */
1.15 mrg 452: bozo_err(httpd, 1, "child execve returned?!");
1.1 tls 453: }
454:
455: close(sv[1]);
456:
1.15 mrg 457: /* parent: read from stdin (bozo_read()) write to sv[0] */
458: /* child: read from sv[0] (bozo_write()) write to stdout */
1.1 tls 459: pid = fork();
460: if (pid == -1)
1.15 mrg 461: bozo_err(httpd, 1, "io child fork failed: %s", strerror(errno));
1.1 tls 462: else if (pid == 0) {
463: /* child reader/writer */
464: close(STDIN_FILENO);
1.15 mrg 465: finish_cgi_output(httpd, request, sv[0], nph);
1.1 tls 466: /* if we're done output, our parent is useless... */
467: kill(getppid(), SIGKILL);
1.15 mrg 468: debug((httpd, DEBUG_FAT, "done processing cgi output"));
1.1 tls 469: _exit(0);
470: }
471: close(STDOUT_FILENO);
472:
473: /* XXX we should have some goo that times us out
474: */
1.15 mrg 475: while ((rbytes = bozo_read(httpd, STDIN_FILENO, buf, sizeof buf)) > 0) {
1.1 tls 476: ssize_t wbytes;
477: char *bp = buf;
478:
479: while (rbytes) {
1.15 mrg 480: wbytes = write(sv[0], buf, (size_t)rbytes);
1.1 tls 481: if (wbytes > 0) {
482: rbytes -= wbytes;
483: bp += wbytes;
484: } else
1.15 mrg 485: bozo_err(httpd, 1, "write failed: %s",
486: strerror(errno));
1.1 tls 487: }
488: }
1.15 mrg 489: debug((httpd, DEBUG_FAT, "done processing cgi input"));
1.1 tls 490: exit(0);
1.12 mrg 491:
492: out:
1.23 mbalmer 493: free(query);
494: free(file);
495: free(url);
1.12 mrg 496: return 0;
1.1 tls 497: }
498:
499: #ifndef NO_DYNAMIC_CONTENT
500: /* cgi maps are simple ".postfix /path/to/prog" */
501: void
1.15 mrg 502: bozo_add_content_map_cgi(bozohttpd_t *httpd, const char *arg, const char *cgihandler)
1.1 tls 503: {
1.15 mrg 504: bozo_content_map_t *map;
1.1 tls 505:
1.15 mrg 506: debug((httpd, DEBUG_NORMAL, "bozo_add_content_map_cgi: name %s cgi %s",
507: arg, cgihandler));
1.1 tls 508:
1.15 mrg 509: httpd->process_cgi = 1;
1.1 tls 510:
1.15 mrg 511: map = bozo_get_content_map(httpd, arg);
1.1 tls 512: map->name = arg;
1.18 mrg 513: map->namelen = strlen(map->name);
1.1 tls 514: map->type = map->encoding = map->encoding11 = NULL;
515: map->cgihandler = cgihandler;
516: }
517: #endif /* NO_DYNAMIC_CONTENT */
518:
519: #endif /* NO_CGIBIN_SUPPORT */
CVSweb <webmaster@jp.NetBSD.org>