xref: /netbsd-src/libexec/httpd/cgi-bozo.c (revision 8b0f9554ff8762542c4defc4f70e1eb76fb508fa)
1 /*	$NetBSD: cgi-bozo.c,v 1.6 2007/11/04 15:20:12 rtr Exp $	*/
2 
3 /*	$eterna: cgi-bozo.c,v 1.13 2006/05/17 08:19:10 mrg Exp $	*/
4 
5 /*
6  * Copyright (c) 1997-2006 Matthew R. Green
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  * 3. The name of the author may not be used to endorse or promote products
19  *    derived from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  */
34 
35 /* this code implements CGI/1.2 for bozohttpd */
36 
37 #ifndef NO_CGIBIN_SUPPORT
38 
39 #include <sys/param.h>
40 #include <sys/socket.h>
41 
42 #include <ctype.h>
43 #include <errno.h>
44 #include <paths.h>
45 #include <signal.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <unistd.h>
49 
50 #include <netinet/in.h>
51 
52 #include "bozohttpd.h"
53 
54 #define CGIBIN_PREFIX		"cgi-bin/"
55 #define CGIBIN_PREFIX_LEN	(sizeof(CGIBIN_PREFIX)-1)
56 
57 static	char	*cgibin;	/* cgi-bin directory */
58 static	int	Cflag;		/* added a cgi handler, always process_cgi() */
59 
60 static	const char *	content_cgihandler(http_req *, const char *);
61 static	void		finish_cgi_output(http_req *request, int, int);
62 static	int		parse_header(const char *, ssize_t, char **, char **);
63 
64 void
65 set_cgibin(char *path)
66 {
67 	cgibin = path;
68 	debug((DEBUG_OBESE, "cgibin (cgi-bin directory) is %s", cgibin));
69 }
70 
71 /* help build up the environ pointer */
72 void
73 spsetenv(const char *env, const char *val, char **envp)
74 {
75 	char *s1 = bozomalloc(strlen(env) + strlen(val) + 2);
76 
77 	strcpy(s1, env);
78 	strcat(s1, "=");
79 	strcat(s1, val);
80 	debug((DEBUG_OBESE, "spsetenv: %s", s1));
81 	*envp = s1;
82 }
83 
84 /*
85  * Checks if the request has asked for a cgi-bin.  Should only be called if
86  * cgibin is set.  If it starts CGIBIN_PREFIX or has a ncontent handler,
87  * process the cgi, otherwise just return.
88  */
89 void
90 process_cgi(http_req *request)
91 {
92 	char	buf[WRSZ];
93 	struct	headers *headp;
94 	const char *type, *clen, *info, *cgihandler;
95 	char	*query, *s, *t, *path, *env, *command, *url;
96 	char	**envp, **curenvp, *argv[4];
97 	size_t	len;
98 	ssize_t rbytes;
99 	pid_t	pid;
100 	int	envpsize, ix, nph;
101 	int	sv[2];
102 
103 	if (!cgibin && !Cflag)
104 		return;
105 
106 	debug((DEBUG_NORMAL, "process_cgi: url `%s'", request->hr_url));
107 
108 	url = bozostrdup(request->hr_url);
109 	if ((s = strchr(url, '?')) != NULL) {
110 		*s++ = '\0';
111 		query = s;
112 	} else
113 		query = NULL;
114 	path = NULL;
115 	envp = NULL;
116 	cgihandler = NULL;
117 	command = NULL;
118 	info = NULL;
119 
120 	len = strlen(url);
121 	if (len == 0 || url[len - 1] == '/') {	/* append index.html */
122 		debug((DEBUG_FAT, "appending index.html"));
123 		url = bozorealloc(url, len + strlen(index_html) + 1);
124 		strcat(url, index_html);
125 		debug((DEBUG_NORMAL, "process_cgi: url adjusted to `%s'", url));
126 	}
127 
128 	auth_check(request, url + 1);
129 
130 	if (!cgibin || strncmp(url + 1, CGIBIN_PREFIX, CGIBIN_PREFIX_LEN) != 0) {
131 		cgihandler = content_cgihandler(request, url + 1);
132 		if (cgihandler == NULL) {
133 			free(url);
134 			return;
135 		}
136 		debug((DEBUG_NORMAL, "process_cgi: cgihandler `%s'",
137 		    cgihandler));
138 	}
139 
140 	ix = 0;
141 	if (cgihandler) {
142 		command = url + 1;
143 		path = bozostrdup(cgihandler);
144 		argv[ix++] = path;
145 			/* argv[] = [ path, command, query, NULL ] */
146 	} else {
147 		command = url + CGIBIN_PREFIX_LEN + 1;
148 		if ((s = strchr(command, '/')) != NULL) {
149 			info = bozostrdup(s);
150 			*s = '\0';
151 		}
152 		path = bozomalloc(strlen(cgibin) + 1 + strlen(command) + 1);
153 		strcpy(path, cgibin);
154 		strcat(path, "/");
155 		strcat(path, command);
156 			/* argv[] = [ command, query, NULL ] */
157 	}
158 	argv[ix++] = command;
159 	argv[ix++] = query;
160 	argv[ix++] = NULL;
161 
162 	nph = strncmp(command, "nph-", 4) == 0;
163 
164 	debug((DEBUG_FAT,
165 	    "process_cgi: path `%s' cmd `%s' info `%s' query `%s' nph `%d'",
166 	    path, command, strornull(info), strornull(query), nph));
167 
168 	type = request->hr_content_type;
169 	clen = request->hr_content_length;
170 
171 	envpsize = 13 + request->hr_nheaders +
172 	    (info && *info ? 1 : 0) +
173 	    (query && *query ? 1 : 0) +
174 	    (type && *type ? 1 : 0) +
175 	    (clen && *clen ? 1 : 0) +
176 	    (request->hr_remotehost && *request->hr_remotehost ? 1 : 0) +
177 	    (request->hr_remoteaddr && *request->hr_remoteaddr ? 1 : 0) +
178 	    auth_cgi_count(request) +
179 	    (request->hr_serverport && *request->hr_serverport ? 1 : 0);
180 
181 	envp = bozomalloc(sizeof(*envp) * envpsize);
182 	for (ix = 0; ix < envpsize; ix++)
183 		envp[ix] = NULL;
184 	curenvp = envp;
185 
186 	SIMPLEQ_FOREACH(headp, &request->hr_headers, h_next) {
187 		const char *s2;
188 		env = bozomalloc(6 + strlen(headp->h_header) + 1 +
189 		    strlen(headp->h_value));
190 
191 		t = env;
192 		strcpy(t, "HTTP_");
193 		t += strlen(t);
194 		for (s2 = headp->h_header; *s2; t++, s2++)
195 			if (islower((u_int)*s2))
196 				*t = toupper((u_int)*s2);
197 			else if (*s2 == '-')
198 				*t = '_';
199 			else
200 				*t = *s2;
201 		*t = '\0';
202 		debug((DEBUG_OBESE, "setting header %s as %s = %s",
203 		    headp->h_header, env, headp->h_value));
204 		spsetenv(env, headp->h_value, curenvp++);
205 		free(env);
206 	}
207 
208 #ifndef _PATH_DEFPATH
209 #define _PATH_DEFPATH "/usr/bin:/bin"
210 #endif
211 
212 	spsetenv("PATH", _PATH_DEFPATH, curenvp++);
213 	spsetenv("IFS", " \t\n", curenvp++);
214 	spsetenv("SERVER_NAME", myname, curenvp++);
215 	spsetenv("GATEWAY_INTERFACE", "CGI/1.1", curenvp++);
216 	spsetenv("SERVER_PROTOCOL", request->hr_proto, curenvp++);
217 	spsetenv("REQUEST_METHOD", request->hr_methodstr, curenvp++);
218 	spsetenv("SCRIPT_NAME", url, curenvp++);
219 	spsetenv("SCRIPT_FILENAME", url + 1, curenvp++);
220 	spsetenv("SERVER_SOFTWARE", server_software, curenvp++);
221 	spsetenv("REQUEST_URI", request->hr_url, curenvp++);
222 	spsetenv("DATE_GMT", http_date(), curenvp++);
223 	if (query && *query)
224 		spsetenv("QUERY_STRING", query, curenvp++);
225 	if (info && *info)
226 		spsetenv("PATH_INFO", info, curenvp++);
227 	if (type && *type)
228 		spsetenv("CONTENT_TYPE", type, curenvp++);
229 	if (clen && *clen)
230 		spsetenv("CONTENT_LENGTH", clen, curenvp++);
231 	if (request->hr_serverport && *request->hr_serverport)
232 		spsetenv("SERVER_PORT", request->hr_serverport, curenvp++);
233 	if (request->hr_remotehost && *request->hr_remotehost)
234 		spsetenv("REMOTE_HOST", request->hr_remotehost, curenvp++);
235 	if (request->hr_remoteaddr && *request->hr_remoteaddr)
236 		spsetenv("REMOTE_ADDR", request->hr_remoteaddr, curenvp++);
237 	auth_cgi_setenv(request, &curenvp);
238 
239 	debug((DEBUG_FAT, "process_cgi: going exec %s, %s %s %s",
240 	    path, argv[0], strornull(argv[1]), strornull(argv[2])));
241 
242 	if (-1 == socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sv))
243 		error(1, "child socketpair failed: %s", strerror(errno));
244 
245 	/*
246 	 * We create 2 procs: one to become the CGI, one read from
247 	 * the CGI and output to the network, and this parent will
248 	 * continue reading from the network and writing to the
249 	 * CGI procsss.
250 	 */
251 	switch (fork()) {
252 	case -1: /* eep, failure */
253 		error(1, "child fork failed: %s", strerror(errno));
254 	case 0:
255 		close(sv[0]);
256 		dup2(sv[1], STDIN_FILENO);
257 		dup2(sv[1], STDOUT_FILENO);
258 
259 		if (-1 == execve(path, argv, envp))
260 			error(1, "child exec failed: %s", path);
261 		/* NOT REACHED */
262 		error(1, "child execve returned?!");
263 	}
264 
265 	close(sv[1]);
266 
267 	/* parent: read from stdin (bozoread()) write to sv[0] */
268 	/* child: read from sv[0] (bozowrite()) write to stdout */
269 	pid = fork();
270 	if (pid == -1)
271 		error(1, "io child fork failed: %s", strerror(errno));
272 	else if (pid == 0) {
273 		/* child reader/writer */
274 		close(STDIN_FILENO);
275 		finish_cgi_output(request, sv[0], nph);
276 		/* if we're done output, our parent is useless... */
277 		kill(getppid(), SIGKILL);
278 		debug((DEBUG_FAT, "done processing cgi output"));
279 		_exit(0);
280 	}
281 	close(STDOUT_FILENO);
282 
283 	/* XXX we should have some goo that times us out
284 	 */
285 	while ((rbytes = bozoread(STDIN_FILENO, buf, sizeof buf)) > 0) {
286 		ssize_t wbytes;
287 		char *bp = buf;
288 
289 		while (rbytes) {
290 			wbytes = write(sv[0], buf , rbytes);
291 			if (wbytes > 0) {
292 				rbytes -= wbytes;
293 				bp += wbytes;
294 			} else
295 				error(1, "write failed: %s", strerror(errno));
296 		}
297 	}
298 	debug((DEBUG_FAT, "done processing cgi input"));
299 	exit(0);
300 }
301 
302 /*
303  * handle parsing a CGI header output, transposing a Status: header
304  * into the HTTP reply (ie, instead of "200 OK").
305  */
306 static void
307 finish_cgi_output(http_req *request, int in, int nph)
308 {
309 	char	buf[WRSZ];
310 	char	*str;
311 	size_t	len;
312 	ssize_t rbytes;
313 	SIMPLEQ_HEAD(, headers)	headers;
314 	struct	headers *hdr;
315 	int	write_header, nheaders = 0;
316 
317 	/* much of this code is like read_request()'s header loop. hmmm... */
318 	SIMPLEQ_INIT(&headers);
319 	write_header = nph == 0;
320 	while (nph == 0 && (str = dgetln(in, (ssize_t *)&len, read)) != NULL) {
321 		char * hdr_name, * hdr_value;
322 
323 		if (parse_header(str, (ssize_t)len, &hdr_name, &hdr_value))
324 			break;
325 
326 		/*
327 		 * The CGI 1.{1,2} spec both say that if the cgi program
328 		 * returns a `Status:' header field then the server MUST
329 		 * return it in the response.  If the cgi program does
330 		 * not return any `Status:' header then the server should
331 		 * respond with 200 OK.
332 		 * XXX The CGI 1.1 and 1.2 specification differ slightly on
333 		 * this in that v1.2 says that the script MUST NOT return a
334 		 * `Status:' header if it is returning a `Location:' header.
335 		 * For compatibility we are going with the CGI 1.1 behavior.
336 		 */
337 		if (strcasecmp(hdr_name, "status") == 0) {
338 			debug((DEBUG_OBESE, "process_cgi:  writing HTTP header "
339 					    "from status %s ..", hdr_value));
340 			bozoprintf("%s %s\r\n", request->hr_proto, hdr_value);
341 			bozoflush(stdout);
342 			write_header = 0;
343 			free(hdr_name);
344 			break;
345 		}
346 
347 		hdr = bozomalloc(sizeof *hdr);
348 		hdr->h_header = hdr_name;
349 		hdr->h_value = hdr_value;
350 		SIMPLEQ_INSERT_TAIL(&headers, hdr, h_next);
351 		nheaders++;
352 	}
353 
354 	if (write_header) {
355 		debug((DEBUG_OBESE, "process_cgi:  writing HTTP header .."));
356 		bozoprintf("%s 200 OK\r\n", request->hr_proto);
357 		bozoflush(stdout);
358 	}
359 
360 	if (nheaders) {
361 		debug((DEBUG_OBESE, "process_cgi:  writing delayed HTTP "
362 				    "headers .."));
363 		SIMPLEQ_FOREACH(hdr, &headers, h_next) {
364 			bozoprintf("%s: %s\r\n", hdr->h_header, hdr->h_value);
365 			free(hdr->h_header);
366 			free(hdr);
367 		}
368 		bozoflush(stdout);
369 	}
370 
371 	/* XXX we should have some goo that times us out
372 	 */
373 	while ((rbytes = read(in, buf, sizeof buf)) > 0) {
374 		ssize_t wbytes;
375 		char *bp = buf;
376 
377 		while (rbytes) {
378 			wbytes = bozowrite(STDOUT_FILENO, buf, rbytes);
379 			if (wbytes > 0) {
380 				rbytes -= wbytes;
381 				bp += wbytes;
382 			} else
383 				error(1, "cgi output write failed: %s",
384 				    strerror(errno));
385 		}
386 	}
387 }
388 
389 static int
390 parse_header(const char * str, ssize_t len, char ** hdr_str, char ** hdr_val)
391 {
392 	char * name, * value;
393 
394 	/* if the string passed is zero-length bail out */
395 	if (*str == '\0')
396 		return -1;
397 
398 	name = value = bozostrdup(str);
399 
400 	/* locate the ':' separator in the header/value */
401 	name = strnsep(&value, ":", &len);
402 
403 	if (NULL == name || -1 == len) {
404 		free(name);
405 		return -1;
406 	}
407 
408 	/* skip leading space/tab */
409 	while (*value == ' ' || *value == '\t')
410 		len--, value++;
411 
412 	*hdr_str = name;
413 	*hdr_val = value;
414 
415 	return 0;
416 }
417 
418 /*
419  * given the file name, return a CGI interpreter
420  */
421 static const char *
422 content_cgihandler(http_req *request, const char *file)
423 {
424 	struct	content_map	*map;
425 
426 	map = match_content_map(file, 0);
427 	if (map)
428 		return (map->cgihandler);
429 	return (NULL);
430 }
431 
432 #ifndef NO_DYNAMIC_CONTENT
433 /* cgi maps are simple ".postfix /path/to/prog" */
434 void
435 add_content_map_cgi(char *arg, char *cgihandler)
436 {
437 	struct content_map *map;
438 
439 	debug((DEBUG_NORMAL, "add_content_map_cgi: name %s cgi %s", arg, cgihandler));
440 
441 	Cflag = 1;
442 
443 	map = get_content_map(arg);
444 	map->name = arg;
445 	map->type = map->encoding = map->encoding11 = NULL;
446 	map->cgihandler = cgihandler;
447 }
448 #endif /* NO_DYNAMIC_CONTENT */
449 
450 #endif /* NO_CGIBIN_SUPPORT */
451