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 $ */
6 * Copyright (c) 1997-2015 Matthew R. Green
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
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
33 /* this code implements CGI/1.2 for bozohttpd */
35 #ifndef NO_CGIBIN_SUPPORT
37 #include <sys/param.h>
38 #include <sys/socket.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)
57 #define USE_ARG(x) /*LINTED*/(void)&(x)
61 * given the file name, return a CGI interpreter
64 content_cgihandler(bozohttpd_t
*httpd
, bozo_httpreq_t
*request
,
67 bozo_content_map_t
*map
;
70 debug((httpd
, DEBUG_FAT
, "content_cgihandler: trying file %s", file
));
71 map
= bozo_match_content_map(httpd
, file
, 0);
73 return map
->cgihandler
;
78 parse_header(bozohttpd_t
*httpd
, const char *str
, ssize_t len
, char **hdr_str
,
83 /* if the string passed is zero-length bail out */
87 value
= bozostrdup(httpd
, str
);
89 /* locate the ':' separator in the header/value */
90 name
= bozostrnsep(&value
, ":", &len
);
92 if (NULL
== name
|| -1 == len
) {
97 /* skip leading space/tab */
98 while (*value
== ' ' || *value
== '\t')
108 * handle parsing a CGI header output, transposing a Status: header
109 * into the HTTP reply (ie, instead of "200 OK").
112 finish_cgi_output(bozohttpd_t
*httpd
, bozo_httpreq_t
*request
, int in
, int nph
)
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 */
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
))
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
,
150 bozo_flush(httpd
, stdout
);
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
);
164 debug((httpd
, DEBUG_OBESE
,
165 "bozo_process_cgi: writing HTTP header .."));
167 "%s 200 OK\r\n", request
->hr_proto
);
168 bozo_flush(httpd
, stdout
);
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
,
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) {
191 wbytes
= bozo_write(httpd
, STDOUT_FILENO
, buf
,
198 "cgi output write failed: %s",
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
));
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",
222 /* help build up the environ pointer */
224 bozo_setenv(bozohttpd_t
*httpd
, const char *env
, const char *val
,
227 char *s1
= bozomalloc(httpd
, strlen(env
) + strlen(val
) + 2);
232 debug((httpd
, DEBUG_OBESE
, "bozo_setenv: %s", 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
243 bozo_process_cgi(bozo_httpreq_t
*request
)
245 bozohttpd_t
*httpd
= request
->hr_httpd
;
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];
257 int envpsize
, ix
, nph
;
260 if (!httpd
->cgibin
&& !httpd
->process_cgi
)
263 if (request
->hr_oldfile
&& strcmp(request
->hr_oldfile
, "/") != 0)
264 uri
= request
->hr_oldfile
;
266 uri
= request
->hr_file
;
269 file
= bozostrdup(httpd
, uri
);
271 asprintf(&file
, "/%s", uri
);
275 if (request
->hr_query
&& strlen(request
->hr_query
))
276 query
= bozostrdup(httpd
, request
->hr_query
);
280 asprintf(&url
, "%s%s%s", file
, query
? "?" : "", query
? query
: "");
283 debug((httpd
, DEBUG_NORMAL
, "bozo_process_cgi: url `%s'", url
));
292 if (bozo_auth_check(request
, url
+ 1))
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"));
303 if (len
== 0 || file
[len
- 1] == '/')
304 append_index_html(httpd
, &file
);
305 debug((httpd
, DEBUG_NORMAL
, "bozo_process_cgi: cgihandler `%s'",
307 } else if (len
- 1 == CGIBIN_PREFIX_LEN
) /* url is "/cgi-bin/" */
308 append_index_html(httpd
, &file
);
312 snprintf(command
, sizeof(command
), "%s", file
+ 1);
313 path
= bozostrdup(httpd
, cgihandler
);
315 /* argv[] = [ path, command, query, NULL ] */
317 snprintf(command
, sizeof(command
), "%s",
318 file
+ CGIBIN_PREFIX_LEN
+ 1);
319 if ((s
= strchr(command
, '/')) != NULL
) {
320 info
= bozostrdup(httpd
, s
);
323 path
= bozomalloc(httpd
,
324 strlen(httpd
->cgibin
) + 1 + strlen(command
) + 1);
325 strcpy(path
, httpd
->cgibin
);
327 strcat(path
, command
);
328 /* argv[] = [ command, query, NULL ] */
330 argv
[ix
++] = command
;
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
++)
360 SIMPLEQ_FOREACH(headp
, &request
->hr_headers
, h_next
) {
362 env
= bozomalloc(httpd
, 6 + strlen(headp
->h_header
) + 1 +
363 strlen(headp
->h_value
));
368 for (s2
= headp
->h_header
; *s2
; t
++, s2
++)
369 if (islower((u_int
)*s2
))
370 *t
= toupper((u_int
)*s2
);
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
++);
382 #ifndef _PATH_DEFPATH
383 #define _PATH_DEFPATH "/usr/bin:/bin"
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
,
396 bozo_setenv(httpd
, "REQUEST_URI", uri
, curenvp
++);
397 bozo_setenv(httpd
, "DATE_GMT", bozo_http_date(date
, sizeof(date
)),
400 bozo_setenv(httpd
, "QUERY_STRING", query
, curenvp
++);
402 bozo_setenv(httpd
, "PATH_INFO", info
, curenvp
++);
404 bozo_setenv(httpd
, "CONTENT_TYPE", type
, curenvp
++);
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
,
410 if (request
->hr_remotehost
&& *request
->hr_remotehost
)
411 bozo_setenv(httpd
, "REMOTE_HOST", request
->hr_remotehost
,
413 if (request
->hr_remoteaddr
&& *request
->hr_remoteaddr
)
414 bozo_setenv(httpd
, "REMOTE_ADDR", request
->hr_remoteaddr
,
417 * XXX Apache does this when invoking content handlers, and PHP
418 * XXX 5.3 requires it as a "security" measure.
421 bozo_setenv(httpd
, "REDIRECT_STATUS", "200", curenvp
++);
422 bozo_auth_cgi_setenv(request
, &curenvp
);
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",
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
441 case -1: /* eep, failure */
442 bozo_err(httpd
, 1, "child fork failed: %s", strerror(errno
));
446 dup2(sv
[1], STDIN_FILENO
);
447 dup2(sv
[1], STDOUT_FILENO
);
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
));
457 bozo_err(httpd
, 1, "child execve returned?!");
462 /* parent: read from stdin (bozo_read()) write to sv[0] */
463 /* child: read from sv[0] (bozo_write()) write to stdout */
466 bozo_err(httpd
, 1, "io child fork failed: %s", strerror(errno
));
468 /* child reader/writer */
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"));
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) {
485 wbytes
= write(sv
[0], buf
, (size_t)rbytes
);
490 bozo_err(httpd
, 1, "write failed: %s",
494 debug((httpd
, DEBUG_FAT
, "done processing cgi input"));
504 #ifndef NO_DYNAMIC_CONTENT
505 /* cgi maps are simple ".postfix /path/to/prog" */
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",
514 httpd
->process_cgi
= 1;
516 map
= bozo_get_content_map(httpd
, arg
);
518 map
->type
= map
->encoding
= map
->encoding11
= NULL
;
519 map
->cgihandler
= cgihandler
;
521 #endif /* NO_DYNAMIC_CONTENT */
523 #endif /* NO_CGIBIN_SUPPORT */