Remove building with NOCRYPTO option
[minix.git] / libexec / httpd / cgi-bozo.c
blobe0bcc8ca70dbeeb62c0f7b088a50cab12ba3d61f
1 /* $NetBSD: cgi-bozo.c,v 1.27 2015/05/02 11:35:48 mrg Exp $ */
3 /* $eterna: cgi-bozo.c,v 1.40 2011/11/18 09:21:15 mrg Exp $ */
5 /*
6 * Copyright (c) 1997-2015 Matthew R. Green
7 * All rights reserved.
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.
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.
33 /* this code implements CGI/1.2 for bozohttpd */
35 #ifndef NO_CGIBIN_SUPPORT
37 #include <sys/param.h>
38 #include <sys/socket.h>
40 #include <ctype.h>
41 #include <errno.h>
42 #include <paths.h>
43 #include <signal.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <syslog.h>
47 #include <unistd.h>
49 #include <netinet/in.h>
51 #include "bozohttpd.h"
53 #define CGIBIN_PREFIX "cgi-bin/"
54 #define CGIBIN_PREFIX_LEN (sizeof(CGIBIN_PREFIX)-1)
56 #ifndef USE_ARG
57 #define USE_ARG(x) /*LINTED*/(void)&(x)
58 #endif
61 * given the file name, return a CGI interpreter
63 static const char *
64 content_cgihandler(bozohttpd_t *httpd, bozo_httpreq_t *request,
65 const char *file)
67 bozo_content_map_t *map;
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;
77 static int
78 parse_header(bozohttpd_t *httpd, const char *str, ssize_t len, char **hdr_str,
79 char **hdr_val)
81 char *name, *value;
83 /* if the string passed is zero-length bail out */
84 if (*str == '\0')
85 return -1;
87 value = bozostrdup(httpd, str);
89 /* locate the ':' separator in the header/value */
90 name = bozostrnsep(&value, ":", &len);
92 if (NULL == name || -1 == len) {
93 free(name);
94 return -1;
97 /* skip leading space/tab */
98 while (*value == ' ' || *value == '\t')
99 len--, value++;
101 *hdr_str = name;
102 *hdr_val = value;
104 return 0;
108 * handle parsing a CGI header output, transposing a Status: header
109 * into the HTTP reply (ie, instead of "200 OK").
111 static void
112 finish_cgi_output(bozohttpd_t *httpd, bozo_httpreq_t *request, int in, int nph)
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;
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;
130 if (parse_header(httpd, str, len, &hdr_name, &hdr_value))
131 break;
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.
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;
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++;
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);
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);
180 bozo_printf(httpd, "\r\n");
181 bozo_flush(httpd, stdout);
184 /* XXX we should have some goo that times us out
186 while ((rbytes = read(in, buf, sizeof buf)) > 0) {
187 ssize_t wbytes;
188 char *bp = buf;
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));
204 static void
205 append_index_html(bozohttpd_t *httpd, char **url)
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));
214 void
215 bozo_cgi_setbin(bozohttpd_t *httpd, const char *path)
217 httpd->cgibin = strdup(path);
218 debug((httpd, DEBUG_OBESE, "cgibin (cgi-bin directory) is %s",
219 httpd->cgibin));
222 /* help build up the environ pointer */
223 void
224 bozo_setenv(bozohttpd_t *httpd, const char *env, const char *val,
225 char **envp)
227 char *s1 = bozomalloc(httpd, strlen(env) + strlen(val) + 2);
229 strcpy(s1, env);
230 strcat(s1, "=");
231 strcat(s1, val);
232 debug((httpd, DEBUG_OBESE, "bozo_setenv: %s", s1));
233 *envp = s1;
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,
239 * process the cgi, otherwise just return. Returns 0 if it did not handle
240 * the request.
243 bozo_process_cgi(bozo_httpreq_t *request)
245 bozohttpd_t *httpd = request->hr_httpd;
246 char buf[BOZO_WRSZ];
247 char date[40];
248 bozoheaders_t *headp;
249 const char *type, *clen, *info, *cgihandler;
250 char *query, *s, *t, *path, *env, *file, *url;
251 char command[MAXPATHLEN];
252 char **envp, **curenvp, *argv[4];
253 char *uri;
254 size_t len;
255 ssize_t rbytes;
256 pid_t pid;
257 int envpsize, ix, nph;
258 int sv[2];
260 if (!httpd->cgibin && !httpd->process_cgi)
261 return 0;
263 if (request->hr_oldfile && strcmp(request->hr_oldfile, "/") != 0)
264 uri = request->hr_oldfile;
265 else
266 uri = request->hr_file;
268 if (uri[0] == '/')
269 file = bozostrdup(httpd, uri);
270 else
271 asprintf(&file, "/%s", uri);
272 if (file == NULL)
273 return 0;
275 if (request->hr_query && strlen(request->hr_query))
276 query = bozostrdup(httpd, request->hr_query);
277 else
278 query = NULL;
280 asprintf(&url, "%s%s%s", file, query ? "?" : "", query ? query : "");
281 if (url == NULL)
282 goto out;
283 debug((httpd, DEBUG_NORMAL, "bozo_process_cgi: url `%s'", url));
285 path = NULL;
286 envp = NULL;
287 cgihandler = NULL;
288 info = NULL;
290 len = strlen(url);
292 if (bozo_auth_check(request, url + 1))
293 goto out;
295 if (!httpd->cgibin ||
296 strncmp(url + 1, CGIBIN_PREFIX, CGIBIN_PREFIX_LEN) != 0) {
297 cgihandler = content_cgihandler(httpd, request, file + 1);
298 if (cgihandler == NULL) {
299 debug((httpd, DEBUG_FAT,
300 "bozo_process_cgi: no handler, returning"));
301 goto out;
303 if (len == 0 || file[len - 1] == '/')
304 append_index_html(httpd, &file);
305 debug((httpd, DEBUG_NORMAL, "bozo_process_cgi: cgihandler `%s'",
306 cgihandler));
307 } else if (len - 1 == CGIBIN_PREFIX_LEN) /* url is "/cgi-bin/" */
308 append_index_html(httpd, &file);
310 ix = 0;
311 if (cgihandler) {
312 snprintf(command, sizeof(command), "%s", file + 1);
313 path = bozostrdup(httpd, cgihandler);
314 argv[ix++] = path;
315 /* argv[] = [ path, command, query, NULL ] */
316 } else {
317 snprintf(command, sizeof(command), "%s",
318 file + CGIBIN_PREFIX_LEN + 1);
319 if ((s = strchr(command, '/')) != NULL) {
320 info = bozostrdup(httpd, s);
321 *s = '\0';
323 path = bozomalloc(httpd,
324 strlen(httpd->cgibin) + 1 + strlen(command) + 1);
325 strcpy(path, httpd->cgibin);
326 strcat(path, "/");
327 strcat(path, command);
328 /* argv[] = [ command, query, NULL ] */
330 argv[ix++] = command;
331 argv[ix++] = query;
332 argv[ix++] = NULL;
334 nph = strncmp(command, "nph-", 4) == 0;
336 type = request->hr_content_type;
337 clen = request->hr_content_length;
339 envpsize = 13 + request->hr_nheaders +
340 (info && *info ? 1 : 0) +
341 (query && *query ? 1 : 0) +
342 (type && *type ? 1 : 0) +
343 (clen && *clen ? 1 : 0) +
344 (request->hr_remotehost && *request->hr_remotehost ? 1 : 0) +
345 (request->hr_remoteaddr && *request->hr_remoteaddr ? 1 : 0) +
346 bozo_auth_cgi_count(request) +
347 (request->hr_serverport && *request->hr_serverport ? 1 : 0);
349 debug((httpd, DEBUG_FAT,
350 "bozo_process_cgi: path `%s', cmd `%s', info `%s', "
351 "query `%s', nph `%d', envpsize `%d'",
352 path, command, strornull(info),
353 strornull(query), nph, envpsize));
355 envp = bozomalloc(httpd, sizeof(*envp) * envpsize);
356 for (ix = 0; ix < envpsize; ix++)
357 envp[ix] = NULL;
358 curenvp = envp;
360 SIMPLEQ_FOREACH(headp, &request->hr_headers, h_next) {
361 const char *s2;
362 env = bozomalloc(httpd, 6 + strlen(headp->h_header) + 1 +
363 strlen(headp->h_value));
365 t = env;
366 strcpy(t, "HTTP_");
367 t += strlen(t);
368 for (s2 = headp->h_header; *s2; t++, s2++)
369 if (islower((u_int)*s2))
370 *t = toupper((u_int)*s2);
371 else if (*s2 == '-')
372 *t = '_';
373 else
374 *t = *s2;
375 *t = '\0';
376 debug((httpd, DEBUG_OBESE, "setting header %s as %s = %s",
377 headp->h_header, env, headp->h_value));
378 bozo_setenv(httpd, env, headp->h_value, curenvp++);
379 free(env);
382 #ifndef _PATH_DEFPATH
383 #define _PATH_DEFPATH "/usr/bin:/bin"
384 #endif
386 bozo_setenv(httpd, "PATH", _PATH_DEFPATH, curenvp++);
387 bozo_setenv(httpd, "IFS", " \t\n", curenvp++);
388 bozo_setenv(httpd, "SERVER_NAME", BOZOHOST(httpd,request), curenvp++);
389 bozo_setenv(httpd, "GATEWAY_INTERFACE", "CGI/1.1", curenvp++);
390 bozo_setenv(httpd, "SERVER_PROTOCOL", request->hr_proto, curenvp++);
391 bozo_setenv(httpd, "REQUEST_METHOD", request->hr_methodstr, curenvp++);
392 bozo_setenv(httpd, "SCRIPT_NAME", file, curenvp++);
393 bozo_setenv(httpd, "SCRIPT_FILENAME", file + 1, curenvp++);
394 bozo_setenv(httpd, "SERVER_SOFTWARE", httpd->server_software,
395 curenvp++);
396 bozo_setenv(httpd, "REQUEST_URI", uri, curenvp++);
397 bozo_setenv(httpd, "DATE_GMT", bozo_http_date(date, sizeof(date)),
398 curenvp++);
399 if (query && *query)
400 bozo_setenv(httpd, "QUERY_STRING", query, curenvp++);
401 if (info && *info)
402 bozo_setenv(httpd, "PATH_INFO", info, curenvp++);
403 if (type && *type)
404 bozo_setenv(httpd, "CONTENT_TYPE", type, curenvp++);
405 if (clen && *clen)
406 bozo_setenv(httpd, "CONTENT_LENGTH", clen, curenvp++);
407 if (request->hr_serverport && *request->hr_serverport)
408 bozo_setenv(httpd, "SERVER_PORT", request->hr_serverport,
409 curenvp++);
410 if (request->hr_remotehost && *request->hr_remotehost)
411 bozo_setenv(httpd, "REMOTE_HOST", request->hr_remotehost,
412 curenvp++);
413 if (request->hr_remoteaddr && *request->hr_remoteaddr)
414 bozo_setenv(httpd, "REMOTE_ADDR", request->hr_remoteaddr,
415 curenvp++);
417 * XXX Apache does this when invoking content handlers, and PHP
418 * XXX 5.3 requires it as a "security" measure.
420 if (cgihandler)
421 bozo_setenv(httpd, "REDIRECT_STATUS", "200", curenvp++);
422 bozo_auth_cgi_setenv(request, &curenvp);
424 free(file);
425 free(url);
427 debug((httpd, DEBUG_FAT, "bozo_process_cgi: going exec %s, %s %s %s",
428 path, argv[0], strornull(argv[1]), strornull(argv[2])));
430 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sv) == -1)
431 bozo_err(httpd, 1, "child socketpair failed: %s",
432 strerror(errno));
435 * We create 2 procs: one to become the CGI, one read from
436 * the CGI and output to the network, and this parent will
437 * continue reading from the network and writing to the
438 * CGI procsss.
440 switch (fork()) {
441 case -1: /* eep, failure */
442 bozo_err(httpd, 1, "child fork failed: %s", strerror(errno));
443 /*NOTREACHED*/
444 case 0:
445 close(sv[0]);
446 dup2(sv[1], STDIN_FILENO);
447 dup2(sv[1], STDOUT_FILENO);
448 close(2);
449 close(sv[1]);
450 closelog();
451 bozo_daemon_closefds(httpd);
453 if (-1 == execve(path, argv, envp))
454 bozo_err(httpd, 1, "child exec failed: %s: %s",
455 path, strerror(errno));
456 /* NOT REACHED */
457 bozo_err(httpd, 1, "child execve returned?!");
460 close(sv[1]);
462 /* parent: read from stdin (bozo_read()) write to sv[0] */
463 /* child: read from sv[0] (bozo_write()) write to stdout */
464 pid = fork();
465 if (pid == -1)
466 bozo_err(httpd, 1, "io child fork failed: %s", strerror(errno));
467 else if (pid == 0) {
468 /* child reader/writer */
469 close(STDIN_FILENO);
470 finish_cgi_output(httpd, request, sv[0], nph);
471 /* if we're done output, our parent is useless... */
472 kill(getppid(), SIGKILL);
473 debug((httpd, DEBUG_FAT, "done processing cgi output"));
474 _exit(0);
476 close(STDOUT_FILENO);
478 /* XXX we should have some goo that times us out
480 while ((rbytes = bozo_read(httpd, STDIN_FILENO, buf, sizeof buf)) > 0) {
481 ssize_t wbytes;
482 char *bp = buf;
484 while (rbytes) {
485 wbytes = write(sv[0], buf, (size_t)rbytes);
486 if (wbytes > 0) {
487 rbytes -= wbytes;
488 bp += wbytes;
489 } else
490 bozo_err(httpd, 1, "write failed: %s",
491 strerror(errno));
494 debug((httpd, DEBUG_FAT, "done processing cgi input"));
495 exit(0);
497 out:
498 free(query);
499 free(file);
500 free(url);
501 return 0;
504 #ifndef NO_DYNAMIC_CONTENT
505 /* cgi maps are simple ".postfix /path/to/prog" */
506 void
507 bozo_add_content_map_cgi(bozohttpd_t *httpd, const char *arg, const char *cgihandler)
509 bozo_content_map_t *map;
511 debug((httpd, DEBUG_NORMAL, "bozo_add_content_map_cgi: name %s cgi %s",
512 arg, cgihandler));
514 httpd->process_cgi = 1;
516 map = bozo_get_content_map(httpd, arg);
517 map->name = arg;
518 map->type = map->encoding = map->encoding11 = NULL;
519 map->cgihandler = cgihandler;
521 #endif /* NO_DYNAMIC_CONTENT */
523 #endif /* NO_CGIBIN_SUPPORT */