xref: /netbsd-src/libexec/httpd/bozohttpd.c (revision 78e0157a414e6b12bb99c06f69ae8701237308fd)
1*78e0157aSrillig /*	$NetBSD: bozohttpd.c,v 1.148 2024/10/04 08:45:46 rillig Exp $	*/
218c80b65Stls 
341f9e942Smrg /*	$eterna: bozohttpd.c,v 1.178 2011/11/18 09:21:15 mrg Exp $	*/
460dbe745Stls 
560dbe745Stls /*
6ab3f0bd6Smrg  * Copyright (c) 1997-2024 Matthew R. Green
760dbe745Stls  * All rights reserved.
860dbe745Stls  *
960dbe745Stls  * Redistribution and use in source and binary forms, with or without
1060dbe745Stls  * modification, are permitted provided that the following conditions
1160dbe745Stls  * are met:
1260dbe745Stls  * 1. Redistributions of source code must retain the above copyright
1360dbe745Stls  *    notice, this list of conditions and the following disclaimer.
1460dbe745Stls  * 2. Redistributions in binary form must reproduce the above copyright
1560dbe745Stls  *    notice, this list of conditions and the following disclaimer and
1660dbe745Stls  *    dedication in the documentation and/or other materials provided
1760dbe745Stls  *    with the distribution.
1860dbe745Stls  *
1960dbe745Stls  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
2060dbe745Stls  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
2160dbe745Stls  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
2260dbe745Stls  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
2360dbe745Stls  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
2460dbe745Stls  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
2560dbe745Stls  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
2660dbe745Stls  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
2760dbe745Stls  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2860dbe745Stls  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2960dbe745Stls  * SUCH DAMAGE.
3060dbe745Stls  *
3160dbe745Stls  */
3260dbe745Stls 
3360dbe745Stls /* this program is dedicated to the Great God of Processed Cheese */
3460dbe745Stls 
3560dbe745Stls /*
3660dbe745Stls  * bozohttpd.c:  minimal httpd; provides only these features:
3760dbe745Stls  *	- HTTP/0.9 (by virtue of ..)
3860dbe745Stls  *	- HTTP/1.0
3960dbe745Stls  *	- HTTP/1.1
4060dbe745Stls  *	- CGI/1.1 this will only be provided for "system" scripts
4160dbe745Stls  *	- automatic "missing trailing slash" redirections
4260dbe745Stls  *	- configurable translation of /~user/ to ~user/public_html,
4360dbe745Stls  *	- access lists via libwrap via inetd/tcpd
4460dbe745Stls  *	- virtual hosting
4560dbe745Stls  *	- not that we do not even pretend to understand MIME, but
4660dbe745Stls  *	  rely only on the HTTP specification
4760dbe745Stls  *	- ipv6 support
4860dbe745Stls  *	- automatic `index.html' generation
4960dbe745Stls  *	- configurable server name
5060dbe745Stls  *	- directory index generation
5160dbe745Stls  *	- daemon mode (lacks libwrap support)
5260dbe745Stls  *	- .htpasswd support
5360dbe745Stls  */
5460dbe745Stls 
5560dbe745Stls /*
5660dbe745Stls  * requirements for minimal http/1.1 (at least, as documented in
57ca768e99Smrg  * RFC 2616 (HTTP/1.1):
5860dbe745Stls  *
59ca768e99Smrg  *	- 14.11: content-encoding handling. [1]
6060dbe745Stls  *
61ca768e99Smrg  *	- 14.13: content-length handling.  this is only a SHOULD header
6260dbe745Stls  *	  thus we could just not send it ever.  [1]
6360dbe745Stls  *
6460dbe745Stls  *	- 14.17: content-type handling. [1]
6560dbe745Stls  *
66ca768e99Smrg  *	- 14.28: if-unmodified-since handling.  if-modified-since is
67ca768e99Smrg  *	  done since, shouldn't be too hard for this one.
6860dbe745Stls  *
6960dbe745Stls  * [1] need to revisit to ensure proper behaviour
7060dbe745Stls  *
7160dbe745Stls  * and the following is a list of features that we do not need
7260dbe745Stls  * to have due to other limits, or are too lazy.  there are more
7360dbe745Stls  * of these than are listed, but these are of particular note,
7460dbe745Stls  * and could perhaps be implemented.
7560dbe745Stls  *
7660dbe745Stls  *	- 3.5/3.6: content/transfer codings.  probably can ignore
7760dbe745Stls  *	  this?  we "SHOULD"n't.  but 4.4 says we should ignore a
7875d2abaeSandvar  *	  `content-length' header upon receipt of a `transfer-encoding'
7960dbe745Stls  *	  header.
8060dbe745Stls  *
8160dbe745Stls  *	- 5.1.1: request methods.  only MUST support GET and HEAD,
8260dbe745Stls  *	  but there are new ones besides POST that are currently
8360dbe745Stls  *	  supported: OPTIONS PUT DELETE TRACE and CONNECT, plus
8460dbe745Stls  *	  extensions not yet known?
8560dbe745Stls  *
8660dbe745Stls  * 	- 10.1: we can ignore informational status codes
8760dbe745Stls  *
8860dbe745Stls  *	- 10.3.3/10.3.4/10.3.8:  just use '302' codes always.
8960dbe745Stls  *
90ca768e99Smrg  *	- 14.1/14.2/14.3/14.27: we do not support Accept: headers.
9160dbe745Stls  *	  just ignore them and send the request anyway.  they are
9260dbe745Stls  *	  only SHOULD.
9360dbe745Stls  *
94ca768e99Smrg  *	- 14.5/14.16/14.35: only support simple ranges: %d- and %d-%d
95ca768e99Smrg  *	  would be nice to support more.
9660dbe745Stls  *
9760dbe745Stls  *	- 14.9: we aren't a cache.
9860dbe745Stls  *
99ca768e99Smrg  *	- 14.15: content-md5 would be nice.
10060dbe745Stls  *
101ca768e99Smrg  *	- 14.24/14.26/14.27: if-match, if-none-match, if-range.  be
102ca768e99Smrg  *	  nice to support this.
10360dbe745Stls  *
104ca768e99Smrg  *	- 14.44: Vary: seems unneeded.  ignore it for now.
10560dbe745Stls  */
10660dbe745Stls 
10760dbe745Stls #ifndef INDEX_HTML
10860dbe745Stls #define INDEX_HTML		"index.html"
10960dbe745Stls #endif
11060dbe745Stls #ifndef SERVER_SOFTWARE
1113d0aa1daSmaya #define SERVER_SOFTWARE		"bozohttpd/20240428"
11260dbe745Stls #endif
113ce206308Smrg #ifndef PUBLIC_HTML
114ce206308Smrg #define PUBLIC_HTML		"public_html"
115ce206308Smrg #endif
116ce206308Smrg 
117ce206308Smrg #ifndef USE_ARG
118ce206308Smrg #define USE_ARG(x)	/*LINTED*/(void)&(x)
119ce206308Smrg #endif
12060dbe745Stls 
12160dbe745Stls /*
12260dbe745Stls  * And so it begins ..
12360dbe745Stls  */
12460dbe745Stls 
12560dbe745Stls #include <sys/param.h>
12660dbe745Stls #include <sys/socket.h>
12760dbe745Stls #include <sys/time.h>
12860dbe745Stls #include <sys/mman.h>
12960dbe745Stls 
13060dbe745Stls #include <arpa/inet.h>
13160dbe745Stls 
13260dbe745Stls #include <ctype.h>
13360dbe745Stls #include <dirent.h>
13460dbe745Stls #include <errno.h>
13560dbe745Stls #include <fcntl.h>
13660dbe745Stls #include <netdb.h>
13760dbe745Stls #include <pwd.h>
13860dbe745Stls #include <grp.h>
13960dbe745Stls #include <stdarg.h>
14060dbe745Stls #include <stdlib.h>
141ddeab5eaSmrg #include <stdint.h>
142d6e51063Smaya #include <strings.h>
14360dbe745Stls #include <string.h>
14460dbe745Stls #include <syslog.h>
14560dbe745Stls #include <time.h>
14660dbe745Stls #include <unistd.h>
14760dbe745Stls 
14860dbe745Stls #include "bozohttpd.h"
14960dbe745Stls 
15008dbfa23Smrg #ifndef SSL_TIMEOUT
1516bfcf4a0Smrg #define	SSL_TIMEOUT		"30"	/* ssl handshake: 30 seconds timeout */
15208dbfa23Smrg #endif
1533230a9a3Smrg #ifndef INITIAL_TIMEOUT
1543230a9a3Smrg #define	INITIAL_TIMEOUT		"30"	/* wait for 30 seconds initially */
1553230a9a3Smrg #endif
1563230a9a3Smrg #ifndef HEADER_WAIT_TIME
1573230a9a3Smrg #define	HEADER_WAIT_TIME	"10"	/* need more headers every 10 seconds */
1583230a9a3Smrg #endif
1593230a9a3Smrg #ifndef TOTAL_MAX_REQ_TIME
1603230a9a3Smrg #define	TOTAL_MAX_REQ_TIME	"600"	/* must have total request in 600 */
1613230a9a3Smrg #endif					/* seconds */
1623230a9a3Smrg 
1633230a9a3Smrg /* if monotonic time is not available try real time. */
1643230a9a3Smrg #ifndef CLOCK_MONOTONIC
1653230a9a3Smrg #define CLOCK_MONOTONIC CLOCK_REALTIME
16660dbe745Stls #endif
16760dbe745Stls 
16860dbe745Stls /* variables and functions */
16960dbe745Stls #ifndef LOG_FTP
17060dbe745Stls #define LOG_FTP LOG_DAEMON
17160dbe745Stls #endif
17260dbe745Stls 
1734cfb2183Smrg /*
1744cfb2183Smrg  * List of special file that we should never serve.
1754cfb2183Smrg  */
1764cfb2183Smrg struct {
1774cfb2183Smrg 	const char *file;
1784cfb2183Smrg 	const char *name;
1794cfb2183Smrg } specials[] = {
1804cfb2183Smrg 	{ REDIRECT_FILE,      "rejected redirect request" },
1814cfb2183Smrg 	{ ABSREDIRECT_FILE,   "rejected absredirect request" },
1824cfb2183Smrg 	{ REMAP_FILE,         "rejected remap request" },
1834cfb2183Smrg 	{ AUTH_FILE,          "rejected authfile request" },
1844cfb2183Smrg 	{ NULL,               NULL },
1854cfb2183Smrg };
1864cfb2183Smrg 
18708dbfa23Smrg volatile sig_atomic_t	bozo_timeout_hit;
18860dbe745Stls 
189ce206308Smrg /*
190ce206308Smrg  * check there's enough space in the prefs and names arrays.
191ce206308Smrg  */
192ce206308Smrg static int
1938f49d6e2Smrg size_arrays(bozohttpd_t *httpd, bozoprefs_t *bozoprefs, size_t needed)
19460dbe745Stls {
1958f49d6e2Smrg 	size_t	len = sizeof(char *) * needed;
196ce206308Smrg 
197ce206308Smrg 	if (bozoprefs->size == 0) {
198ce206308Smrg 		/* only get here first time around */
1998f49d6e2Smrg 		bozoprefs->name = bozomalloc(httpd, len);
2008f49d6e2Smrg 		bozoprefs->value = bozomalloc(httpd, len);
201cff2d956Smrg 	} else if (bozoprefs->count == bozoprefs->size) {
202ce206308Smrg 		/* only uses 'needed' when filled array */
2038f49d6e2Smrg 		bozoprefs->name = bozorealloc(httpd, bozoprefs->name, len);
2048f49d6e2Smrg 		bozoprefs->value = bozorealloc(httpd, bozoprefs->value, len);
205ce206308Smrg 	}
2068f49d6e2Smrg 
2078f49d6e2Smrg 	bozoprefs->size = needed;
208ce206308Smrg 	return 1;
209ce206308Smrg }
210ce206308Smrg 
211cff2d956Smrg static ssize_t
212ce206308Smrg findvar(bozoprefs_t *bozoprefs, const char *name)
213ce206308Smrg {
214cff2d956Smrg 	size_t	i;
215ce206308Smrg 
216cff2d956Smrg 	for (i = 0; i < bozoprefs->count; i++)
217cff2d956Smrg 		if (strcmp(bozoprefs->name[i], name) == 0)
218cff2d956Smrg 			return (ssize_t)i;
219cff2d956Smrg 	return -1;
22060dbe745Stls }
22160dbe745Stls 
22260dbe745Stls int
223cff2d956Smrg bozo_set_pref(bozohttpd_t *httpd, bozoprefs_t *bozoprefs,
224cff2d956Smrg 	      const char *name, const char *value)
22560dbe745Stls {
226cff2d956Smrg 	ssize_t	i;
22760dbe745Stls 
228ce206308Smrg 	if ((i = findvar(bozoprefs, name)) < 0) {
229ce206308Smrg 		/* add the element to the array */
2308f49d6e2Smrg 		if (!size_arrays(httpd, bozoprefs, bozoprefs->size + 15))
231cff2d956Smrg 			return 0;
232cff2d956Smrg 		i = bozoprefs->count++;
233cff2d956Smrg 		bozoprefs->name[i] = bozostrdup(httpd, NULL, name);
234ce206308Smrg 	} else {
235ce206308Smrg 		/* replace the element in the array */
236ce206308Smrg 		free(bozoprefs->value[i]);
23760dbe745Stls 	}
238cff2d956Smrg 	bozoprefs->value[i] = bozostrdup(httpd, NULL, value);
239ce206308Smrg 	return 1;
24060dbe745Stls }
24160dbe745Stls 
242b0f74aaaSmrg static void
243080a4ce9Smrg bozo_clear_prefs(bozoprefs_t *prefs)
244b0f74aaaSmrg {
245b0f74aaaSmrg 	size_t	i;
246b0f74aaaSmrg 
247b0f74aaaSmrg 	for (i = 0; i < prefs->count; i++) {
248b0f74aaaSmrg 		free(prefs->name[i]);
249b0f74aaaSmrg 		free(prefs->value[i]);
250b0f74aaaSmrg 	}
251b0f74aaaSmrg 
252b0f74aaaSmrg 	free(prefs->name);
253b0f74aaaSmrg 	free(prefs->value);
254b0f74aaaSmrg }
255b0f74aaaSmrg 
25660dbe745Stls /*
257ce206308Smrg  * get a variable's value, or NULL
25860dbe745Stls  */
259ce206308Smrg char *
260ce206308Smrg bozo_get_pref(bozoprefs_t *bozoprefs, const char *name)
261ce206308Smrg {
262cff2d956Smrg 	ssize_t	i;
26360dbe745Stls 
264cff2d956Smrg 	i = findvar(bozoprefs, name);
265cff2d956Smrg 	return i < 0 ? NULL : bozoprefs->value[i];
26660dbe745Stls }
26760dbe745Stls 
26860dbe745Stls char *
269ce206308Smrg bozo_http_date(char *date, size_t datelen)
27060dbe745Stls {
27160dbe745Stls 	struct	tm *tm;
27260dbe745Stls 	time_t	now;
27360dbe745Stls 
27460dbe745Stls 	/* Sun, 06 Nov 1994 08:49:37 GMT */
27560dbe745Stls 	now = time(NULL);
27660dbe745Stls 	tm = gmtime(&now);	/* HTTP/1.1 spec rev 06 sez GMT only */
277ce206308Smrg 	strftime(date, datelen, "%a, %d %b %Y %H:%M:%S GMT", tm);
27860dbe745Stls 	return date;
27960dbe745Stls }
28060dbe745Stls 
28160dbe745Stls /*
28203387632Smrg  * convert "in" into the three parts of a request (first line).
28303387632Smrg  * we allocate into file and query, but return pointers into
28403387632Smrg  * "in" for proto and method.
28560dbe745Stls  */
28660dbe745Stls static void
287ce206308Smrg parse_request(bozohttpd_t *httpd, char *in, char **method, char **file,
288ce206308Smrg 		char **query, char **proto)
28960dbe745Stls {
29060dbe745Stls 	ssize_t	len;
29160dbe745Stls 	char	*val;
29260dbe745Stls 
293ce206308Smrg 	USE_ARG(httpd);
294ce206308Smrg 	debug((httpd, DEBUG_EXPLODING, "parse in: %s", in));
29503387632Smrg 	*method = *file = *query = *proto = NULL;
29660dbe745Stls 
29760dbe745Stls 	len = (ssize_t)strlen(in);
298707281a2Smrg 	val = bozostrnsep(&in, " \t\n\r", &len);
29953359366Smrg 	if (len < 1 || val == NULL || in == NULL)
30060dbe745Stls 		return;
30160dbe745Stls 	*method = val;
30203387632Smrg 
30360dbe745Stls 	while (*in == ' ' || *in == '\t')
30460dbe745Stls 		in++;
305707281a2Smrg 	val = bozostrnsep(&in, " \t\n\r", &len);
30660dbe745Stls 	if (len < 1) {
30760dbe745Stls 		if (len == 0)
308f0f7a44fStls 			*file = val;
30960dbe745Stls 		else
310f0f7a44fStls 			*file = in;
311ce206308Smrg 	} else {
312f0f7a44fStls 		*file = val;
31303387632Smrg 
314f0f7a44fStls 		*query = strchr(*file, '?');
31503387632Smrg 		if (*query)
31603387632Smrg 			*(*query)++ = '\0';
317f0f7a44fStls 
31860dbe745Stls 		if (in) {
31960dbe745Stls 			while (*in && (*in == ' ' || *in == '\t'))
32060dbe745Stls 				in++;
32160dbe745Stls 			if (*in)
32260dbe745Stls 				*proto = in;
32360dbe745Stls 		}
324ce206308Smrg 	}
325f0f7a44fStls 
32603387632Smrg 	/* allocate private copies */
327cff2d956Smrg 	*file = bozostrdup(httpd, NULL, *file);
32803387632Smrg 	if (*query)
329cff2d956Smrg 		*query = bozostrdup(httpd, NULL, *query);
33003387632Smrg 
331ce206308Smrg 	debug((httpd, DEBUG_FAT,
332ce206308Smrg 		"url: method: \"%s\" file: \"%s\" query: \"%s\" proto: \"%s\"",
333ea8f81f3Smrg 		*method, *file, *query ? *query : "", *proto ? *proto : ""));
33460dbe745Stls }
33560dbe745Stls 
33660dbe745Stls /*
337ce206308Smrg  * cleanup a bozo_httpreq_t after use
33860dbe745Stls  */
339ce206308Smrg void
340ce206308Smrg bozo_clean_request(bozo_httpreq_t *request)
34160dbe745Stls {
342ce206308Smrg 	struct bozoheaders *hdr, *ohdr = NULL;
34303387632Smrg 
34403387632Smrg 	if (request == NULL)
34503387632Smrg 		return;
34603387632Smrg 
34730539536Smrg 	/* If SSL enabled cleanup SSL structure. */
348ee1ac2b9Smrg 	bozo_ssl_destroy(request->hr_httpd);
34930539536Smrg 
35003387632Smrg 	/* clean up request */
351ca5b33a5Sshm 	free(request->hr_remotehost);
352ca5b33a5Sshm 	free(request->hr_remoteaddr);
353ca5b33a5Sshm 	free(request->hr_serverport);
354ca5b33a5Sshm 	free(request->hr_virthostname);
35512d8621dSmrg 	free(request->hr_file_free);
356b0f74aaaSmrg 	/* XXX this is gross */
35712d8621dSmrg 	if (request->hr_file_free != request->hr_oldfile)
358ca5b33a5Sshm 		free(request->hr_oldfile);
359b0f74aaaSmrg 	else
360b0f74aaaSmrg 		free(request->hr_file);
361ca5b33a5Sshm 	free(request->hr_query);
362ca5b33a5Sshm 	free(request->hr_host);
363c4fe1facSshm 	bozo_user_free(request->hr_user);
364ce206308Smrg 	bozo_auth_cleanup(request);
36503387632Smrg 	for (hdr = SIMPLEQ_FIRST(&request->hr_headers); hdr;
36603387632Smrg 	    hdr = SIMPLEQ_NEXT(hdr, h_next)) {
36703387632Smrg 		free(hdr->h_value);
36803387632Smrg 		free(hdr->h_header);
36903387632Smrg 		free(ohdr);
37003387632Smrg 		ohdr = hdr;
37103387632Smrg 	}
372591b978bSelric 	free(ohdr);
373591b978bSelric 	ohdr = NULL;
374afe55bf8Selric 	for (hdr = SIMPLEQ_FIRST(&request->hr_replheaders); hdr;
375afe55bf8Selric 	    hdr = SIMPLEQ_NEXT(hdr, h_next)) {
376afe55bf8Selric 		free(hdr->h_value);
377afe55bf8Selric 		free(hdr->h_header);
378afe55bf8Selric 		free(ohdr);
379afe55bf8Selric 		ohdr = hdr;
380afe55bf8Selric 	}
38103387632Smrg 	free(ohdr);
38203387632Smrg 
38303387632Smrg 	free(request);
38403387632Smrg }
38503387632Smrg 
38603387632Smrg /*
387ce206308Smrg  * send a HTTP/1.1 408 response if we timeout.
38860dbe745Stls  */
389ce206308Smrg /* ARGSUSED */
390ce206308Smrg static void
391ce206308Smrg alarmer(int sig)
392ce206308Smrg {
3934864410bSmrg 	USE_ARG(sig);
39408dbfa23Smrg 	bozo_timeout_hit = 1;
3953230a9a3Smrg }
3963230a9a3Smrg 
3973230a9a3Smrg 
3983230a9a3Smrg /*
39908dbfa23Smrg  * set a timeout for "ssl", "initial", "header", or "request".
4003230a9a3Smrg  */
4013230a9a3Smrg int
4023230a9a3Smrg bozo_set_timeout(bozohttpd_t *httpd, bozoprefs_t *prefs,
403a49dff0cSmrg 		 const char *target, const char *val)
4043230a9a3Smrg {
4059644d25eSleot 	const char **cur, *timeouts[] = {
40608dbfa23Smrg 		"ssl timeout",
4073230a9a3Smrg 		"initial timeout",
4083230a9a3Smrg 		"header timeout",
4093230a9a3Smrg 		"request timeout",
4103230a9a3Smrg 		NULL,
4113230a9a3Smrg 	};
4123230a9a3Smrg 	/* adjust minlen if more timeouts appear with conflicting names */
4133230a9a3Smrg 	const size_t minlen = 1;
4143230a9a3Smrg 	size_t len = strlen(target);
4153230a9a3Smrg 
4169644d25eSleot 	for (cur = timeouts; len >= minlen && *cur; cur++) {
4179644d25eSleot 		if (strncmp(target, *cur, len) == 0) {
4189644d25eSleot 			bozo_set_pref(httpd, prefs, *cur, val);
4193230a9a3Smrg 			return 0;
4203230a9a3Smrg 		}
4213230a9a3Smrg 	}
4223230a9a3Smrg 	return 1;
423ce206308Smrg }
424ce206308Smrg 
425ce206308Smrg /*
426afe55bf8Selric  * a list of header quirks: currently, a list of headers that
427afe55bf8Selric  * can't be folded into a single line.
428afe55bf8Selric  */
429afe55bf8Selric const char *header_quirks[] = { "WWW-Authenticate", NULL };
430afe55bf8Selric 
431afe55bf8Selric /*
432ce206308Smrg  * add or merge this header (val: str) into the requests list
433ce206308Smrg  */
434ce206308Smrg static bozoheaders_t *
435afe55bf8Selric addmerge_header(bozo_httpreq_t *request, struct qheaders *headers,
436afe55bf8Selric 		const char *val, const char *str, ssize_t len)
437ce206308Smrg {
438cff2d956Smrg 	struct	bozohttpd_t *httpd = request->hr_httpd;
439afe55bf8Selric 	struct bozoheaders	 *hdr = NULL;
440afe55bf8Selric 	const char		**quirk;
441ce206308Smrg 
442ce206308Smrg 	USE_ARG(len);
443afe55bf8Selric 	for (quirk = header_quirks; *quirk; quirk++)
444afe55bf8Selric 		if (strcasecmp(*quirk, val) == 0)
445afe55bf8Selric 			break;
446afe55bf8Selric 
447afe55bf8Selric 	if (*quirk == NULL) {
448ce206308Smrg 		/* do we exist already? */
449afe55bf8Selric 		SIMPLEQ_FOREACH(hdr, headers, h_next) {
450ce206308Smrg 			if (strcasecmp(val, hdr->h_header) == 0)
451ce206308Smrg 				break;
452ce206308Smrg 		}
453afe55bf8Selric 	}
454ce206308Smrg 
455ce206308Smrg 	if (hdr) {
456ce206308Smrg 		/* yup, merge it in */
457ce206308Smrg 		char *nval;
458ce206308Smrg 
459c2e98309Smrg 		bozoasprintf(httpd, &nval, "%s, %s", hdr->h_value, str);
460ce206308Smrg 		free(hdr->h_value);
461ce206308Smrg 		hdr->h_value = nval;
462ce206308Smrg 	} else {
463ce206308Smrg 		/* nope, create a new one */
464ce206308Smrg 
465cff2d956Smrg 		hdr = bozomalloc(httpd, sizeof *hdr);
466cff2d956Smrg 		hdr->h_header = bozostrdup(httpd, request, val);
467ce206308Smrg 		if (str && *str)
468cff2d956Smrg 			hdr->h_value = bozostrdup(httpd, request, str);
469ce206308Smrg 		else
470cff2d956Smrg 			hdr->h_value = bozostrdup(httpd, request, " ");
471ce206308Smrg 
472afe55bf8Selric 		SIMPLEQ_INSERT_TAIL(headers, hdr, h_next);
473ce206308Smrg 		request->hr_nheaders++;
474ce206308Smrg 	}
475ce206308Smrg 
476ce206308Smrg 	return hdr;
477ce206308Smrg }
478ce206308Smrg 
479afe55bf8Selric bozoheaders_t *
480afe55bf8Selric addmerge_reqheader(bozo_httpreq_t *request, const char *val, const char *str,
481afe55bf8Selric 		   ssize_t len)
482afe55bf8Selric {
483afe55bf8Selric 
484afe55bf8Selric 	return addmerge_header(request, &request->hr_headers, val, str, len);
485afe55bf8Selric }
486afe55bf8Selric 
487afe55bf8Selric bozoheaders_t *
488afe55bf8Selric addmerge_replheader(bozo_httpreq_t *request, const char *val, const char *str,
489afe55bf8Selric 		    ssize_t len)
490afe55bf8Selric {
491afe55bf8Selric 
492afe55bf8Selric 	return addmerge_header(request, &request->hr_replheaders,
493afe55bf8Selric 	    val, str, len);
494afe55bf8Selric }
495afe55bf8Selric 
496ce206308Smrg /*
497ce206308Smrg  * as the prototype string is not constant (eg, "HTTP/1.1" is equivalent
498ce206308Smrg  * to "HTTP/001.01"), we MUST parse this.
499ce206308Smrg  */
500ce206308Smrg static int
501ce206308Smrg process_proto(bozo_httpreq_t *request, const char *proto)
502ce206308Smrg {
503cff2d956Smrg 	struct	bozohttpd_t *httpd = request->hr_httpd;
504ce206308Smrg 	char	majorstr[16], *minorstr;
505ce206308Smrg 	int	majorint, minorint;
506ce206308Smrg 
507ce206308Smrg 	if (proto == NULL) {
508ce206308Smrg got_proto_09:
509cff2d956Smrg 		request->hr_proto = httpd->consts.http_09;
510cff2d956Smrg 		debug((httpd, DEBUG_FAT, "request %s is http/0.9",
511ce206308Smrg 			request->hr_file));
512ce206308Smrg 		return 0;
513ce206308Smrg 	}
514ce206308Smrg 
515ce206308Smrg 	if (strncasecmp(proto, "HTTP/", 5) != 0)
516ce206308Smrg 		goto bad;
517fe9ca5aaSfox 	strncpy(majorstr, proto + 5, sizeof(majorstr)-1);
518ce206308Smrg 	majorstr[sizeof(majorstr)-1] = 0;
519ce206308Smrg 	minorstr = strchr(majorstr, '.');
520ce206308Smrg 	if (minorstr == NULL)
521ce206308Smrg 		goto bad;
522ce206308Smrg 	*minorstr++ = 0;
523ce206308Smrg 
524ce206308Smrg 	majorint = atoi(majorstr);
525ce206308Smrg 	minorint = atoi(minorstr);
526ce206308Smrg 
527ce206308Smrg 	switch (majorint) {
528ce206308Smrg 	case 0:
529ce206308Smrg 		if (minorint != 9)
530ce206308Smrg 			break;
531ce206308Smrg 		goto got_proto_09;
532ce206308Smrg 	case 1:
533ce206308Smrg 		if (minorint == 0)
534cff2d956Smrg 			request->hr_proto = httpd->consts.http_10;
535ce206308Smrg 		else if (minorint == 1)
536cff2d956Smrg 			request->hr_proto = httpd->consts.http_11;
537ce206308Smrg 		else
538ce206308Smrg 			break;
539ce206308Smrg 
540cff2d956Smrg 		debug((httpd, DEBUG_FAT, "request %s is %s",
541ce206308Smrg 		    request->hr_file, request->hr_proto));
542ce206308Smrg 		SIMPLEQ_INIT(&request->hr_headers);
543ce206308Smrg 		request->hr_nheaders = 0;
544ce206308Smrg 		return 0;
545ce206308Smrg 	}
546ce206308Smrg bad:
547cff2d956Smrg 	return bozo_http_error(httpd, 404, NULL, "unknown prototype");
548ce206308Smrg }
549ce206308Smrg 
550ce206308Smrg /*
551ce206308Smrg  * process each type of HTTP method, setting this HTTP requests
552cff2d956Smrg  * method type.
553ce206308Smrg  */
554ce206308Smrg static struct method_map {
555ce206308Smrg 	const char *name;
556ce206308Smrg 	int	type;
557ce206308Smrg } method_map[] = {
558ce206308Smrg 	{ "GET", 	HTTP_GET, },
559ce206308Smrg 	{ "POST",	HTTP_POST, },
560ce206308Smrg 	{ "HEAD",	HTTP_HEAD, },
561ce206308Smrg #if 0	/* other non-required http/1.1 methods */
562ce206308Smrg 	{ "OPTIONS",	HTTP_OPTIONS, },
563ce206308Smrg 	{ "PUT",	HTTP_PUT, },
564ce206308Smrg 	{ "DELETE",	HTTP_DELETE, },
565ce206308Smrg 	{ "TRACE",	HTTP_TRACE, },
566ce206308Smrg 	{ "CONNECT",	HTTP_CONNECT, },
567ce206308Smrg #endif
568ce206308Smrg 	{ NULL,		0, },
569ce206308Smrg };
570ce206308Smrg 
571ce206308Smrg static int
572ce206308Smrg process_method(bozo_httpreq_t *request, const char *method)
573ce206308Smrg {
574cff2d956Smrg 	struct	bozohttpd_t *httpd = request->hr_httpd;
575ce206308Smrg 	struct	method_map *mmp;
576ce206308Smrg 
577cff2d956Smrg 	if (request->hr_proto == httpd->consts.http_11)
578ce206308Smrg 		request->hr_allow = "GET, HEAD, POST";
579ce206308Smrg 
580ce206308Smrg 	for (mmp = method_map; mmp->name; mmp++)
581ce206308Smrg 		if (strcasecmp(method, mmp->name) == 0) {
582ce206308Smrg 			request->hr_method = mmp->type;
583ce206308Smrg 			request->hr_methodstr = mmp->name;
584ce206308Smrg 			return 0;
585ce206308Smrg 		}
586ce206308Smrg 
587cff2d956Smrg 	return bozo_http_error(httpd, 404, request, "unknown method");
588ce206308Smrg }
589ce206308Smrg 
5900ccc27dcSmrg /* check header byte count */
5910ccc27dcSmrg static int
5920ccc27dcSmrg bozo_got_header_length(bozo_httpreq_t *request, size_t len)
5930ccc27dcSmrg {
5940ccc27dcSmrg 
59569e8cec1Smaya 	if (len > BOZO_HEADERS_MAX_SIZE - request->hr_header_bytes)
5960ccc27dcSmrg 		return bozo_http_error(request->hr_httpd, 413, request,
5970ccc27dcSmrg 			"too many headers");
59869e8cec1Smaya 
59969e8cec1Smaya 	request->hr_header_bytes += len;
60069e8cec1Smaya 
60169e8cec1Smaya 	return 0;
6020ccc27dcSmrg }
6030ccc27dcSmrg 
604ce206308Smrg /*
605ce206308Smrg  * This function reads a http request from stdin, returning a pointer to a
606ce206308Smrg  * bozo_httpreq_t structure, describing the request.
607ce206308Smrg  */
608ce206308Smrg bozo_httpreq_t *
609ce206308Smrg bozo_read_request(bozohttpd_t *httpd)
61060dbe745Stls {
61160dbe745Stls 	struct	sigaction	sa;
612f0f7a44fStls 	char	*str, *val, *method, *file, *proto, *query;
61360dbe745Stls 	char	*host, *addr, *port;
61460dbe745Stls 	char	bufport[10];
61560dbe745Stls 	char	hbuf[NI_MAXHOST], abuf[NI_MAXHOST];
61660dbe745Stls 	struct	sockaddr_storage ss;
61760dbe745Stls 	ssize_t	len;
61860dbe745Stls 	int	line = 0;
61960dbe745Stls 	socklen_t slen;
620ce206308Smrg 	bozo_httpreq_t *request;
6213230a9a3Smrg 	struct timespec ots, ts;
62260dbe745Stls 
62360dbe745Stls 	/*
624aeb27ed4Smrg 	 * if we're in daemon mode, bozo_daemon_fork() will return here twice
625aeb27ed4Smrg 	 * for each call.  once in the child, returning 0, and once in the
62608dbfa23Smrg 	 * parent, returning 1 for each child.
62760dbe745Stls 	 */
628aeb27ed4Smrg 	if (bozo_daemon_fork(httpd))
629aeb27ed4Smrg 		return NULL;
63060dbe745Stls 
631ce206308Smrg 	request = bozomalloc(httpd, sizeof(*request));
632ce206308Smrg 	memset(request, 0, sizeof(*request));
633ce206308Smrg 	request->hr_httpd = httpd;
63460dbe745Stls 	request->hr_allow = request->hr_host = NULL;
63560dbe745Stls 	request->hr_content_type = request->hr_content_length = NULL;
636707281a2Smrg 	request->hr_range = NULL;
637707281a2Smrg 	request->hr_last_byte_pos = -1;
63803387632Smrg 	request->hr_if_modified_since = NULL;
639407204a7Smartin 	request->hr_virthostname = NULL;
64012d8621dSmrg 	request->hr_file_free = NULL;
64103387632Smrg 	request->hr_file = NULL;
642aeb27ed4Smrg 	request->hr_oldfile = NULL;
643afe55bf8Selric 	SIMPLEQ_INIT(&request->hr_replheaders);
64460a08788Sshm 	bozo_auth_init(request);
64560dbe745Stls 
64660dbe745Stls 	slen = sizeof(ss);
647ce206308Smrg 	if (getpeername(0, (struct sockaddr *)(void *)&ss, &slen) < 0)
64860dbe745Stls 		host = addr = NULL;
64960dbe745Stls 	else {
650ce206308Smrg 		if (getnameinfo((struct sockaddr *)(void *)&ss, slen,
65160dbe745Stls 		    abuf, sizeof abuf, NULL, 0, NI_NUMERICHOST) == 0)
65260dbe745Stls 			addr = abuf;
65360dbe745Stls 		else
65460dbe745Stls 			addr = NULL;
655ce206308Smrg 		if (httpd->numeric == 0 &&
656ce206308Smrg 		    getnameinfo((struct sockaddr *)(void *)&ss, slen,
65760dbe745Stls 				hbuf, sizeof hbuf, NULL, 0, 0) == 0)
65860dbe745Stls 			host = hbuf;
65960dbe745Stls 		else
66060dbe745Stls 			host = NULL;
66160dbe745Stls 	}
66260dbe745Stls 	if (host != NULL)
663cff2d956Smrg 		request->hr_remotehost = bozostrdup(httpd, request, host);
66460dbe745Stls 	if (addr != NULL)
665cff2d956Smrg 		request->hr_remoteaddr = bozostrdup(httpd, request, addr);
66660dbe745Stls 	slen = sizeof(ss);
667d0ddf3e7Smrg 
668d0ddf3e7Smrg 	/*
669d0ddf3e7Smrg 	 * Override the bound port from the request value, so it works even
670d0ddf3e7Smrg 	 * if passed through a proxy that doesn't rewrite the port.
671d0ddf3e7Smrg 	 */
6726bfcf4a0Smrg 	port = NULL;
673d0ddf3e7Smrg 	if (httpd->bindport) {
674bf53dc23Smrg 		if (strcmp(httpd->bindport, BOZO_HTTP_PORT) != 0)
675d0ddf3e7Smrg 			port = httpd->bindport;
6766bfcf4a0Smrg 	} else if (getsockname(0, (struct sockaddr *)(void *)&ss, &slen) == 0 &&
6776bfcf4a0Smrg 		   getnameinfo((struct sockaddr *)(void *)&ss, slen, NULL, 0,
6786bfcf4a0Smrg 			       bufport, sizeof bufport, NI_NUMERICSERV) == 0)
67960dbe745Stls 		port = bufport;
68060dbe745Stls 	if (port != NULL)
681cff2d956Smrg 		request->hr_serverport = bozostrdup(httpd, request, port);
68260dbe745Stls 
68360dbe745Stls 	/*
68460dbe745Stls 	 * setup a timer to make sure the request is not hung
68560dbe745Stls 	 */
68660dbe745Stls 	sa.sa_handler = alarmer;
68760dbe745Stls 	sigemptyset(&sa.sa_mask);
68860dbe745Stls 	sigaddset(&sa.sa_mask, SIGALRM);
68960dbe745Stls 	sa.sa_flags = 0;
690881b8188Smrg 	sigaction(SIGALRM, &sa, NULL);
69160dbe745Stls 
6923230a9a3Smrg 	if (clock_gettime(CLOCK_MONOTONIC, &ots) != 0) {
6934cfb2183Smrg 		bozo_http_error(httpd, 500, NULL, "clock_gettime failed");
6943230a9a3Smrg 		goto cleanup;
6953230a9a3Smrg 	}
6963230a9a3Smrg 
69708dbfa23Smrg 	/*
69808dbfa23Smrg 	 * now to try to setup SSL, and upon failure parent can signal the
69908dbfa23Smrg 	 * caller there was no request to process and it will wait for
70008dbfa23Smrg 	 * another.
70108dbfa23Smrg 	 */
70208dbfa23Smrg 	if (bozo_ssl_accept(httpd))
70308dbfa23Smrg 		return NULL;
70408dbfa23Smrg 
7053230a9a3Smrg 	alarm(httpd->initial_timeout);
706ce206308Smrg 	while ((str = bozodgetln(httpd, STDIN_FILENO, &len, bozo_read)) != NULL) {
70760dbe745Stls 		alarm(0);
7083230a9a3Smrg 
7093230a9a3Smrg 		if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
7104cfb2183Smrg 			bozo_http_error(httpd, 500, NULL, "clock_gettime failed");
7113230a9a3Smrg 			goto cleanup;
7123230a9a3Smrg 		}
7133230a9a3Smrg 		/*
7143230a9a3Smrg 		 * don't timeout if old tv_sec is not more than current
7153230a9a3Smrg 		 * tv_sec, or if current tv_sec is less than the request
7163230a9a3Smrg 		 * timeout (these shouldn't happen, but the first could
7173230a9a3Smrg 		 * if monotonic time is not available.)
7183230a9a3Smrg 		 *
7193230a9a3Smrg 		 * the other timeout and header size checks should ensure
7203230a9a3Smrg 		 * that even if time it set backwards or forwards a very
7213230a9a3Smrg 		 * long way, timeout will eventually happen, even if this
7223230a9a3Smrg 		 * one fails.
7233230a9a3Smrg 		 */
7243230a9a3Smrg 		if (ts.tv_sec > ots.tv_sec &&
7253230a9a3Smrg 		    ts.tv_sec > httpd->request_timeout &&
7263230a9a3Smrg 		    ts.tv_sec - httpd->request_timeout > ots.tv_sec)
72708dbfa23Smrg 			bozo_timeout_hit = 1;
7283230a9a3Smrg 
72908dbfa23Smrg 		if (bozo_timeout_hit) {
7304cfb2183Smrg 			bozo_http_error(httpd, 408, NULL, "request timed out");
73103387632Smrg 			goto cleanup;
73203387632Smrg 		}
73360dbe745Stls 		line++;
73460dbe745Stls 
73560dbe745Stls 		if (line == 1) {
73603387632Smrg 			if (len < 1) {
7374cfb2183Smrg 				bozo_http_error(httpd, 404, NULL, "null method");
73803387632Smrg 				goto cleanup;
73903387632Smrg 			}
740881b8188Smrg 			bozowarn(httpd,
741cff2d956Smrg 				  "got request ``%s'' from host %s to port %s",
74260dbe745Stls 				  str,
74360dbe745Stls 				  host ? host : addr ? addr : "<local>",
74460dbe745Stls 				  port ? port : "<stdin>");
74560dbe745Stls 
74603387632Smrg 			/* we allocate return space in file and query only */
747ce206308Smrg 			parse_request(httpd, str, &method, &file, &query, &proto);
74812d8621dSmrg 			request->hr_file_free = request->hr_file = file;
74903387632Smrg 			request->hr_query = query;
75003387632Smrg 			if (method == NULL) {
7514cfb2183Smrg 				bozo_http_error(httpd, 404, NULL, "null method");
75203387632Smrg 				goto cleanup;
75303387632Smrg 			}
75403387632Smrg 			if (file == NULL) {
7554cfb2183Smrg 				bozo_http_error(httpd, 404, NULL, "null file");
75603387632Smrg 				goto cleanup;
75703387632Smrg 			}
75860dbe745Stls 
75960dbe745Stls 			/*
76060dbe745Stls 			 * note that we parse the proto first, so that we
76160dbe745Stls 			 * can more properly parse the method and the url.
76260dbe745Stls 			 */
763f0f7a44fStls 
76403387632Smrg 			if (process_proto(request, proto) ||
76503387632Smrg 			    process_method(request, method)) {
76603387632Smrg 				goto cleanup;
76703387632Smrg 			}
76803387632Smrg 
769ce206308Smrg 			debug((httpd, DEBUG_FAT, "got file \"%s\" query \"%s\"",
77003387632Smrg 			    request->hr_file,
77103387632Smrg 			    request->hr_query ? request->hr_query : "<none>"));
77260dbe745Stls 
77360dbe745Stls 			/* http/0.9 has no header processing */
774ce206308Smrg 			if (request->hr_proto == httpd->consts.http_09)
77560dbe745Stls 				break;
77660dbe745Stls 		} else {		/* incoming headers */
777ce206308Smrg 			bozoheaders_t *hdr;
77860dbe745Stls 
77960dbe745Stls 			if (*str == '\0')
78060dbe745Stls 				break;
78160dbe745Stls 
782707281a2Smrg 			val = bozostrnsep(&str, ":", &len);
7834cfb2183Smrg 			debug((httpd, DEBUG_EXPLODING, "read_req2: after "
78412d8621dSmrg 			    "bozostrnsep: str `%s' val `%s'",
78512d8621dSmrg 			    str ? str : "<null>", val ? val : "<null>"));
78603387632Smrg 			if (val == NULL || len == -1) {
7874cfb2183Smrg 				bozo_http_error(httpd, 404, request, "no header");
78803387632Smrg 				goto cleanup;
78903387632Smrg 			}
79012d8621dSmrg 			if (str == NULL) {
79112d8621dSmrg 				bozo_http_error(httpd, 404, request,
79212d8621dSmrg 				    "malformed header");
79312d8621dSmrg 				goto cleanup;
79412d8621dSmrg 			}
79560dbe745Stls 			while (*str == ' ' || *str == '\t')
79660dbe745Stls 				len--, str++;
797707281a2Smrg 			while (*val == ' ' || *val == '\t')
798707281a2Smrg 				val++;
79960dbe745Stls 
8000ccc27dcSmrg 			if (bozo_got_header_length(request, len))
8010ccc27dcSmrg 				goto cleanup;
8020ccc27dcSmrg 
803a07e0db3Smrg 			if (bozo_auth_check_headers(request, val, str, len))
80460dbe745Stls 				goto next_header;
80560dbe745Stls 
806afe55bf8Selric 			hdr = addmerge_reqheader(request, val, str, len);
80760dbe745Stls 
80860dbe745Stls 			if (strcasecmp(hdr->h_header, "content-type") == 0)
80960dbe745Stls 				request->hr_content_type = hdr->h_value;
81060dbe745Stls 			else if (strcasecmp(hdr->h_header, "content-length") == 0)
81160dbe745Stls 				request->hr_content_length = hdr->h_value;
8123230a9a3Smrg 			else if (strcasecmp(hdr->h_header, "host") == 0) {
8133230a9a3Smrg 				if (request->hr_host) {
8143230a9a3Smrg 					/* RFC 7230 (HTTP/1.1): 5.4 */
8154cfb2183Smrg 					bozo_http_error(httpd, 400, request,
8163230a9a3Smrg 						"Only allow one Host: header");
8173230a9a3Smrg 					goto cleanup;
8183230a9a3Smrg 				}
819cff2d956Smrg 				request->hr_host = bozostrdup(httpd, request,
820cff2d956Smrg 							      hdr->h_value);
8213230a9a3Smrg 			}
822ca768e99Smrg 			/* RFC 2616 (HTTP/1.1): 14.20 */
82303387632Smrg 			else if (strcasecmp(hdr->h_header, "expect") == 0) {
8244cfb2183Smrg 				bozo_http_error(httpd, 417, request,
825ce206308Smrg 						"we don't support Expect:");
82603387632Smrg 				goto cleanup;
82703387632Smrg 			}
82860dbe745Stls 			else if (strcasecmp(hdr->h_header, "referrer") == 0 ||
82960dbe745Stls 			         strcasecmp(hdr->h_header, "referer") == 0)
83060dbe745Stls 				request->hr_referrer = hdr->h_value;
831707281a2Smrg 			else if (strcasecmp(hdr->h_header, "range") == 0)
832707281a2Smrg 				request->hr_range = hdr->h_value;
833ce206308Smrg 			else if (strcasecmp(hdr->h_header,
834ce206308Smrg 					"if-modified-since") == 0)
8357925dff4Sjoerg 				request->hr_if_modified_since = hdr->h_value;
836f082d14aSelric 			else if (strcasecmp(hdr->h_header,
837f082d14aSelric 					"accept-encoding") == 0)
838f082d14aSelric 				request->hr_accept_encoding = hdr->h_value;
83960dbe745Stls 
840ce206308Smrg 			debug((httpd, DEBUG_FAT, "adding header %s: %s",
84160dbe745Stls 			    hdr->h_header, hdr->h_value));
84260dbe745Stls 		}
84360dbe745Stls next_header:
8443230a9a3Smrg 		alarm(httpd->header_timeout);
84560dbe745Stls 	}
84653df5022Smrg 	if (str == NULL) {
84753df5022Smrg 		bozo_http_error(httpd, 413, request, "request too large");
84853df5022Smrg 		goto cleanup;
84953df5022Smrg 	}
85060dbe745Stls 
85160dbe745Stls 	/* now, clear it all out */
85260dbe745Stls 	alarm(0);
85360dbe745Stls 	signal(SIGALRM, SIG_DFL);
85460dbe745Stls 
85560dbe745Stls 	/* RFC1945, 8.3 */
856ce206308Smrg 	if (request->hr_method == HTTP_POST &&
857ce206308Smrg 	    request->hr_content_length == NULL) {
8584cfb2183Smrg 		bozo_http_error(httpd, 400, request, "missing content length");
85903387632Smrg 		goto cleanup;
86003387632Smrg 	}
86160dbe745Stls 
862ca768e99Smrg 	/* RFC 2616 (HTTP/1.1), 14.23 & 19.6.1.1 */
863ce206308Smrg 	if (request->hr_proto == httpd->consts.http_11 &&
864ca768e99Smrg 	    /*(strncasecmp(request->hr_file, "http://", 7) != 0) &&*/
865ce206308Smrg 	    request->hr_host == NULL) {
8664cfb2183Smrg 		bozo_http_error(httpd, 400, request, "missing Host header");
86703387632Smrg 		goto cleanup;
86803387632Smrg 	}
86903387632Smrg 
87003387632Smrg 	if (request->hr_range != NULL) {
871ce206308Smrg 		debug((httpd, DEBUG_FAT, "hr_range: %s", request->hr_range));
87203387632Smrg 		/* support only simple ranges %d- and %d-%d */
87303387632Smrg 		if (strchr(request->hr_range, ',') == NULL) {
87403387632Smrg 			const char *rstart, *dash;
87503387632Smrg 
87603387632Smrg 			rstart = strchr(request->hr_range, '=');
87703387632Smrg 			if (rstart != NULL) {
87803387632Smrg 				rstart++;
87903387632Smrg 				dash = strchr(rstart, '-');
88003387632Smrg 				if (dash != NULL && dash != rstart) {
88103387632Smrg 					dash++;
88203387632Smrg 					request->hr_have_range = 1;
88303387632Smrg 					request->hr_first_byte_pos =
88403387632Smrg 					    strtoll(rstart, NULL, 10);
88503387632Smrg 					if (request->hr_first_byte_pos < 0)
88603387632Smrg 						request->hr_first_byte_pos = 0;
88703387632Smrg 					if (*dash != '\0') {
88803387632Smrg 						request->hr_last_byte_pos =
88903387632Smrg 						    strtoll(dash, NULL, 10);
89003387632Smrg 						if (request->hr_last_byte_pos < 0)
89103387632Smrg 							request->hr_last_byte_pos = -1;
89203387632Smrg 					}
89303387632Smrg 				}
89403387632Smrg 			}
89503387632Smrg 		}
89603387632Smrg 	}
89760dbe745Stls 
898ce206308Smrg 	debug((httpd, DEBUG_FAT, "bozo_read_request returns url %s in request",
899f0f7a44fStls 	       request->hr_file));
900ce206308Smrg 	return request;
90103387632Smrg 
90203387632Smrg cleanup:
903ce206308Smrg 	bozo_clean_request(request);
90403387632Smrg 
90503387632Smrg 	return NULL;
90660dbe745Stls }
90760dbe745Stls 
9087925dff4Sjoerg static int
909ce206308Smrg mmap_and_write_part(bozohttpd_t *httpd, int fd, off_t first_byte_pos, size_t sz)
91003387632Smrg {
911c6e75af2Smrg 	size_t mappedsz, wroffset;
912c6e75af2Smrg 	off_t mappedoffset;
91303387632Smrg 	char *addr;
914c6e75af2Smrg 	void *mappedaddr;
91503387632Smrg 
916c6e75af2Smrg 	/*
917c6e75af2Smrg 	 * we need to ensure that both the size *and* offset arguments to
918c6e75af2Smrg 	 * mmap() are page-aligned.  our formala for this is:
919c6e75af2Smrg 	 *
920c6e75af2Smrg 	 *    input offset: first_byte_pos
921c6e75af2Smrg 	 *    input size: sz
922c6e75af2Smrg 	 *
923c6e75af2Smrg 	 *    mapped offset = page align truncate (input offset)
924c6e75af2Smrg 	 *    mapped size   =
925c6e75af2Smrg 	 *        page align extend (input offset - mapped offset + input size)
926c6e75af2Smrg 	 *    write offset  = input offset - mapped offset
927c6e75af2Smrg 	 *
928c6e75af2Smrg 	 * we use the write offset in all writes
929c6e75af2Smrg 	 */
930cf205aa3Shannken 	mappedoffset = first_byte_pos & ~((off_t)httpd->page_size - 1);
931ce206308Smrg 	mappedsz = (size_t)
932ce206308Smrg 		(first_byte_pos - mappedoffset + sz + httpd->page_size - 1) &
933ce206308Smrg 		~(httpd->page_size - 1);
934ce206308Smrg 	wroffset = (size_t)(first_byte_pos - mappedoffset);
935c6e75af2Smrg 
936c6e75af2Smrg 	addr = mmap(0, mappedsz, PROT_READ, MAP_SHARED, fd, mappedoffset);
93703704058Smrg 	if (addr == MAP_FAILED) {
938881b8188Smrg 		bozowarn(httpd, "mmap failed: %s", strerror(errno));
93903387632Smrg 		return -1;
94003387632Smrg 	}
941c6e75af2Smrg 	mappedaddr = addr;
94203387632Smrg 
94303387632Smrg #ifdef MADV_SEQUENTIAL
94403387632Smrg 	(void)madvise(addr, sz, MADV_SEQUENTIAL);
94503387632Smrg #endif
946ce206308Smrg 	while (sz > BOZO_WRSZ) {
947ce206308Smrg 		if (bozo_write(httpd, STDOUT_FILENO, addr + wroffset,
948ce206308Smrg 				BOZO_WRSZ) != BOZO_WRSZ) {
949881b8188Smrg 			bozowarn(httpd, "write failed: %s", strerror(errno));
95003387632Smrg 			goto out;
95103387632Smrg 		}
952ce206308Smrg 		debug((httpd, DEBUG_OBESE, "wrote %d bytes", BOZO_WRSZ));
953ce206308Smrg 		sz -= BOZO_WRSZ;
954ce206308Smrg 		addr += BOZO_WRSZ;
95503387632Smrg 	}
956ce206308Smrg 	if (sz && (size_t)bozo_write(httpd, STDOUT_FILENO, addr + wroffset,
957ce206308Smrg 				sz) != sz) {
958881b8188Smrg 		bozowarn(httpd, "final write failed: %s", strerror(errno));
95903387632Smrg 		goto out;
96003387632Smrg 	}
961ce206308Smrg 	debug((httpd, DEBUG_OBESE, "wrote %d bytes", (int)sz));
96203387632Smrg  out:
963c6e75af2Smrg 	if (munmap(mappedaddr, mappedsz) < 0) {
964881b8188Smrg 		bozowarn(httpd, "munmap failed");
96503387632Smrg 		return -1;
96603387632Smrg 	}
96703387632Smrg 
96803387632Smrg 	return 0;
96903387632Smrg }
97003387632Smrg 
97103387632Smrg static int
9727925dff4Sjoerg parse_http_date(const char *val, time_t *timestamp)
9737925dff4Sjoerg {
9747925dff4Sjoerg 	char *remainder;
9757925dff4Sjoerg 	struct tm tm;
9767925dff4Sjoerg 
9777925dff4Sjoerg 	if ((remainder = strptime(val, "%a, %d %b %Y %T GMT", &tm)) == NULL &&
9787925dff4Sjoerg 	    (remainder = strptime(val, "%a, %d-%b-%y %T GMT", &tm)) == NULL &&
9797925dff4Sjoerg 	    (remainder = strptime(val, "%a %b %d %T %Y", &tm)) == NULL)
9807925dff4Sjoerg 		return 0; /* Invalid HTTP date format */
9817925dff4Sjoerg 
9827925dff4Sjoerg 	if (*remainder)
9837925dff4Sjoerg 		return 0; /* No trailing garbage */
9847925dff4Sjoerg 
9857925dff4Sjoerg 	*timestamp = timegm(&tm);
9867925dff4Sjoerg 	return 1;
9877925dff4Sjoerg }
9887925dff4Sjoerg 
98960dbe745Stls /*
9901be97454Smrg  * given an url, encode it ala rfc 3986.  ie, escape ? and friends.
9911be97454Smrg  * note that this function returns a static buffer, and thus needs
992c4fe1facSshm  * to be updated for any sort of parallel processing. escape only
993c4fe1facSshm  * chosen characters for absolute redirects
9941be97454Smrg  */
9951be97454Smrg char *
996c4fe1facSshm bozo_escape_rfc3986(bozohttpd_t *httpd, const char *url, int absolute)
9971be97454Smrg {
9981be97454Smrg 	static char *buf;
9991be97454Smrg 	static size_t buflen = 0;
10001be97454Smrg 	size_t len;
10011be97454Smrg 	const char *s;
10021be97454Smrg 	char *d;
10031be97454Smrg 
10041be97454Smrg 	len = strlen(url);
10051be97454Smrg 	if (buflen < len * 3 + 1) {
10061be97454Smrg 		buflen = len * 3 + 1;
10071be97454Smrg 		buf = bozorealloc(httpd, buf, buflen);
10081be97454Smrg 	}
10091be97454Smrg 
101053359366Smrg 	for (s = url, d = buf; *s;) {
10111be97454Smrg 		if (*s & 0x80)
10121be97454Smrg 			goto encode_it;
10131be97454Smrg 		switch (*s) {
10141be97454Smrg 		case ':':
10151be97454Smrg 		case '?':
10161be97454Smrg 		case '#':
10171be97454Smrg 		case '[':
10181be97454Smrg 		case ']':
10191be97454Smrg 		case '@':
10201be97454Smrg 		case '!':
10211be97454Smrg 		case '$':
10221be97454Smrg 		case '&':
10231be97454Smrg 		case '\'':
10241be97454Smrg 		case '(':
10251be97454Smrg 		case ')':
10261be97454Smrg 		case '*':
10271be97454Smrg 		case '+':
10281be97454Smrg 		case ',':
10291be97454Smrg 		case ';':
10301be97454Smrg 		case '=':
10315dc860cdSmrg 		case '%':
1032c4fe1facSshm 		case '"':
1033c4fe1facSshm 			if (absolute)
1034c4fe1facSshm 				goto leave_it;
1035201b0ce7Schristos 			/*FALLTHROUGH*/
103651b65afaSshm 		case '\n':
103751b65afaSshm 		case '\r':
103851b65afaSshm 		case ' ':
10391be97454Smrg 		encode_it:
10403d201ca7Smrg 			snprintf(d, 4, "%%%02X", (unsigned char)*s++);
10411be97454Smrg 			d += 3;
10425dc860cdSmrg 			break;
10431be97454Smrg 		default:
1044201b0ce7Schristos 		leave_it:
10451be97454Smrg 			*d++ = *s++;
10465dc860cdSmrg 			break;
10471be97454Smrg 		}
10481be97454Smrg 	}
104953359366Smrg 	*d = 0;
10501be97454Smrg 
10511be97454Smrg 	return buf;
10521be97454Smrg }
10531be97454Smrg 
10541be97454Smrg /*
1055c4fe1facSshm  * do automatic redirection -- if there are query parameters or userdir for
1056c4fe1facSshm  * the URL we will tack these on to the new (redirected) URL.
1057ce206308Smrg  */
1058ce206308Smrg static void
1059881b8188Smrg handle_redirect(bozo_httpreq_t *request, const char *url, int absolute)
1060ce206308Smrg {
1061ce206308Smrg 	bozohttpd_t *httpd = request->hr_httpd;
1062c4fe1facSshm 	char *finalurl, *urlbuf;
1063c4fe1facSshm #ifndef NO_USER_SUPPORT
1064c4fe1facSshm 	char *userbuf;
1065c4fe1facSshm #endif /* !NO_USER_SUPPORT */
1066ce206308Smrg 	char portbuf[20];
106784411b58Smrg 	const char *scheme, *query, *quest;
1068407204a7Smartin 	const char *hostname = BOZOHOST(httpd, request);
106984411b58Smrg 	int absproto = 0; /* absolute redirect provides own schema */
1070ce206308Smrg 
1071ce206308Smrg 	if (url == NULL) {
1072881b8188Smrg 		bozoasprintf(httpd, &urlbuf, "/%s/", request->hr_file);
1073ce206308Smrg 		url = urlbuf;
1074ce206308Smrg 	} else
1075ce206308Smrg 		urlbuf = NULL;
1076c4fe1facSshm 
1077c4fe1facSshm #ifndef NO_USER_SUPPORT
1078c4fe1facSshm 	if (request->hr_user && !absolute) {
1079881b8188Smrg 		bozoasprintf(httpd, &userbuf, "/~%s%s", request->hr_user, url);
1080c4fe1facSshm 		url = userbuf;
1081c4fe1facSshm 	} else
1082c4fe1facSshm 		userbuf = NULL;
1083c4fe1facSshm #endif /* !NO_USER_SUPPORT */
1084c4fe1facSshm 
1085c4fe1facSshm 	if (absolute) {
1086*78e0157aSrillig 		const char *sep = NULL;
1087c4fe1facSshm 		const char *s;
1088c4fe1facSshm 
1089c4fe1facSshm 		/*
1090c2e98309Smrg 		 * absolute redirect may specify own protocol i.e. to redirect
1091c2e98309Smrg 		 * to another schema like https:// or ftp://.
1092c2e98309Smrg 		 * Details: RFC 3986, section 3.
1093c4fe1facSshm 		 */
1094c4fe1facSshm 
1095c4fe1facSshm 		/* 1. check if url contains :// */
1096c4fe1facSshm 		sep = strstr(url, "://");
1097c4fe1facSshm 
1098c4fe1facSshm 		/*
1099c4fe1facSshm 		 * RFC 3986, section 3.1:
1100c4fe1facSshm 		 * scheme      = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
1101c4fe1facSshm 		 */
1102cff2d956Smrg 		if (sep) {
1103c4fe1facSshm 			for (s = url; s != sep;) {
1104dc0342a2Srillig 				if (!isalnum((unsigned char)*s) &&
1105c2e98309Smrg 				    *s != '+' && *s != '-' && *s != '.')
1106c4fe1facSshm 					break;
1107c4fe1facSshm 				if (++s == sep) {
1108c4fe1facSshm 					absproto = 1;
1109c4fe1facSshm 				}
1110c4fe1facSshm 			}
1111c4fe1facSshm 		}
1112c4fe1facSshm 	}
1113c4fe1facSshm 
111484411b58Smrg 	/* construct final redirection url */
1115ce206308Smrg 
111684411b58Smrg 	scheme = absproto ? "" : httpd->sslinfo ? "https://" : "http://";
1117ce206308Smrg 
111884411b58Smrg 	if (absolute) {
111984411b58Smrg 		hostname = "";
112084411b58Smrg 		portbuf[0] = '\0';
112184411b58Smrg 	} else {
1122bf53dc23Smrg 		const char *defport = httpd->sslinfo ? BOZO_HTTPS_PORT : BOZO_HTTP_PORT;
112384411b58Smrg 
112484411b58Smrg 		if (request->hr_serverport &&
112584411b58Smrg 		    strcmp(request->hr_serverport, defport) != 0)
1126ce206308Smrg 			snprintf(portbuf, sizeof(portbuf), ":%s",
1127ce206308Smrg 			    request->hr_serverport);
1128ce206308Smrg 		else
1129ce206308Smrg 			portbuf[0] = '\0';
113084411b58Smrg 	}
1131c4fe1facSshm 
113284411b58Smrg 	url = bozo_escape_rfc3986(httpd, url, absolute);
113384411b58Smrg 
113484411b58Smrg 	if (request->hr_query && strlen(request->hr_query)) {
113584411b58Smrg 		query = request->hr_query;
113684411b58Smrg 		quest = "?";
113784411b58Smrg 	} else {
113884411b58Smrg 		query = quest = "";
1139c4fe1facSshm 	}
114084411b58Smrg 
114184411b58Smrg 	bozoasprintf(httpd, &finalurl, "%s%s%s%s%s%s",
114284411b58Smrg 		     scheme, hostname, portbuf, url, quest, query);
1143c4fe1facSshm 
1144881b8188Smrg 	bozowarn(httpd, "redirecting %s", finalurl);
1145c4fe1facSshm 	debug((httpd, DEBUG_FAT, "redirecting %s", finalurl));
1146c4fe1facSshm 
1147ce206308Smrg 	bozo_printf(httpd, "%s 301 Document Moved\r\n", request->hr_proto);
1148ce206308Smrg 	if (request->hr_proto != httpd->consts.http_09)
1149ce206308Smrg 		bozo_print_header(request, NULL, "text/html", NULL);
1150c4fe1facSshm 	if (request->hr_proto != httpd->consts.http_09)
1151c4fe1facSshm 		bozo_printf(httpd, "Location: %s\r\n", finalurl);
1152ce206308Smrg 	bozo_printf(httpd, "\r\n");
1153ce206308Smrg 	if (request->hr_method == HTTP_HEAD)
1154ce206308Smrg 		goto head;
1155ce206308Smrg 	bozo_printf(httpd, "<html><head><title>Document Moved</title></head>\n");
1156ce206308Smrg 	bozo_printf(httpd, "<body><h1>Document Moved</h1>\n");
1157c4fe1facSshm 	bozo_printf(httpd, "This document had moved <a href=\"%s\">here</a>\n",
1158c4fe1facSshm 	  finalurl);
1159ce206308Smrg 	bozo_printf(httpd, "</body></html>\n");
1160ce206308Smrg head:
1161ce206308Smrg 	bozo_flush(httpd, stdout);
1162ce206308Smrg 	free(urlbuf);
1163c4fe1facSshm 	free(finalurl);
1164c4fe1facSshm #ifndef NO_USER_SUPPORT
1165c4fe1facSshm 	free(userbuf);
1166c4fe1facSshm #endif /* !NO_USER_SUPPORT */
1167ce206308Smrg }
1168ce206308Smrg 
1169ce206308Smrg /*
1170bf2f242dSmartin  * Like strncmp(), but s_esc may contain characters escaped by \.
1171bf2f242dSmartin  * The len argument does not include the backslashes used for escaping,
1172bf2f242dSmartin  * that is: it gives the raw len, after unescaping the string.
1173bf2f242dSmartin  */
1174bf2f242dSmartin static int
1175bf2f242dSmartin esccmp(const char *s_plain, const char *s_esc, size_t len)
1176bf2f242dSmartin {
1177bf2f242dSmartin 	bool esc = false;
1178bf2f242dSmartin 
1179bf2f242dSmartin 	while (len) {
1180bf2f242dSmartin 		if (!esc && *s_esc == '\\') {
1181bf2f242dSmartin 			esc = true;
1182bf2f242dSmartin 			s_esc++;
1183bf2f242dSmartin 			continue;
1184bf2f242dSmartin 		}
1185bf2f242dSmartin 		esc = false;
1186bf2f242dSmartin 		if (*s_plain == 0 || *s_esc == 0 || *s_plain != *s_esc)
1187bf2f242dSmartin 			return *s_esc - *s_plain;
1188bf2f242dSmartin 		s_esc++;
1189bf2f242dSmartin 		s_plain++;
1190bf2f242dSmartin 		len--;
1191bf2f242dSmartin 	}
1192bf2f242dSmartin 	return 0;
1193bf2f242dSmartin }
1194bf2f242dSmartin 
1195bf2f242dSmartin /*
1196bf2f242dSmartin  * Check if the request refers to a uri that is mapped via a .bzremap.
1197bf2f242dSmartin  * We have  /requested/path:/re/mapped/to/this.html lines in there,
1198bf2f242dSmartin  * and the : separator may be use in the left hand side escaped with
1199bf2f242dSmartin  * \ to encode a path containig a : character.
1200bf2f242dSmartin  */
1201bf2f242dSmartin static void
12024cfb2183Smrg check_remap(bozo_httpreq_t *request)
1203bf2f242dSmartin {
1204bf2f242dSmartin 	bozohttpd_t *httpd = request->hr_httpd;
1205bf2f242dSmartin 	char *file = request->hr_file, *newfile;
1206bf2f242dSmartin 	void *fmap;
120753359366Smrg 	const char *replace = NULL, *map_to = NULL, *p;
1208bf2f242dSmartin 	struct stat st;
1209bf2f242dSmartin 	int mapfile;
1210bf2f242dSmartin 	size_t avail, len, rlen, reqlen, num_esc = 0;
1211bf2f242dSmartin 	bool escaped = false;
1212bf2f242dSmartin 
1213bf2f242dSmartin 	mapfile = open(REMAP_FILE, O_RDONLY, 0);
1214bf2f242dSmartin 	if (mapfile == -1)
1215bf2f242dSmartin 		return;
1216bf2f242dSmartin 	debug((httpd, DEBUG_FAT, "remap file found"));
1217bf2f242dSmartin 	if (fstat(mapfile, &st) == -1) {
1218bf2f242dSmartin 		bozowarn(httpd, "could not stat " REMAP_FILE ", errno: %d",
1219bf2f242dSmartin 		    errno);
12204cfb2183Smrg 		goto out;
1221bf2f242dSmartin 	}
1222bf2f242dSmartin 
122332fa179bSmrg 	fmap = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, mapfile, 0);
122403704058Smrg 	if (fmap == MAP_FAILED) {
1225bf2f242dSmartin 		bozowarn(httpd, "could not mmap " REMAP_FILE ", error %d",
1226bf2f242dSmartin 		    errno);
12274cfb2183Smrg 		goto out;
1228bf2f242dSmartin 	}
1229bf2f242dSmartin 	reqlen = strlen(file);
1230bf2f242dSmartin 	for (p = fmap, avail = st.st_size; avail; ) {
1231bf2f242dSmartin 		/*
1232bf2f242dSmartin 		 * We have lines like:
1233bf2f242dSmartin 		 *   /this/url:/replacement/that/url
1234bf2f242dSmartin 		 * If we find a matching left hand side, replace will point
1235bf2f242dSmartin 		 * to it and len will be its length. map_to will point to
1236bf2f242dSmartin 		 * the right hand side and rlen wil be its length.
1237bf2f242dSmartin 		 * If we have no match, both pointers will be NULL.
1238bf2f242dSmartin 		 */
1239bf2f242dSmartin 
1240bf2f242dSmartin 		/* skip empty lines */
1241bf2f242dSmartin 		while ((*p == '\r' || *p == '\n') && avail) {
1242bf2f242dSmartin 			p++;
1243bf2f242dSmartin 			avail--;
1244bf2f242dSmartin 		}
1245bf2f242dSmartin 		replace = p;
1246bf2f242dSmartin 		escaped = false;
1247bf2f242dSmartin 		while (avail) {
1248bf2f242dSmartin 			if (*p == '\r' || *p == '\n')
1249bf2f242dSmartin 				break;
1250bf2f242dSmartin 			if (!escaped && *p == ':')
1251bf2f242dSmartin 				break;
1252bf2f242dSmartin 			if (escaped) {
1253bf2f242dSmartin 				escaped = false;
1254bf2f242dSmartin 				num_esc++;
1255bf2f242dSmartin 			} else if (*p == '\\') {
1256bf2f242dSmartin 				escaped = true;
1257bf2f242dSmartin 			}
1258bf2f242dSmartin 			p++;
1259bf2f242dSmartin 			avail--;
1260bf2f242dSmartin 		}
1261bf2f242dSmartin 		if (!avail || *p != ':') {
1262bf2f242dSmartin 			replace = NULL;
1263bf2f242dSmartin 			map_to = NULL;
1264bf2f242dSmartin 			break;
1265bf2f242dSmartin 		}
1266bf2f242dSmartin 		len = p - replace - num_esc;
1267bf2f242dSmartin 		/*
1268bf2f242dSmartin 		 * reqlen < len: the left hand side is too long, can't be a
1269bf2f242dSmartin 		 *   match
1270bf2f242dSmartin 		 * reqlen == len: full string has to match
1271bf2f242dSmartin 		 * reqlen > len: make sure there is a path separator at 'len'
1272bf2f242dSmartin 		 * avail < 2: we are at eof, missing right hand side
1273bf2f242dSmartin 		 */
1274bf2f242dSmartin 		if (avail < 2 || reqlen < len ||
1275bf2f242dSmartin 		    (reqlen == len && esccmp(file, replace, len) != 0) ||
1276bf2f242dSmartin 		    (reqlen > len && (file[len] != '/' ||
1277bf2f242dSmartin 					esccmp(file, replace, len) != 0))) {
1278bf2f242dSmartin 
1279bf2f242dSmartin 			/* non-match, skip to end of line and continue */
1280bf2f242dSmartin 			while (*p != '\r' && *p != '\n' && avail) {
1281bf2f242dSmartin 				p++;
1282bf2f242dSmartin 				avail--;
1283bf2f242dSmartin 			}
1284bf2f242dSmartin 			replace = NULL;
1285bf2f242dSmartin 			map_to = NULL;
1286bf2f242dSmartin 			continue;
1287bf2f242dSmartin 		}
1288bf2f242dSmartin 		p++;
1289bf2f242dSmartin 		avail--;
1290bf2f242dSmartin 
1291bf2f242dSmartin 		/* found a match, parse the target */
1292bf2f242dSmartin 		map_to = p;
1293bf2f242dSmartin 		while (*p != '\r' && *p != '\n' && avail) {
1294bf2f242dSmartin 			p++;
1295bf2f242dSmartin 			avail--;
1296bf2f242dSmartin 		}
1297bf2f242dSmartin 		rlen = p - map_to;
1298bf2f242dSmartin 		break;
1299bf2f242dSmartin 	}
1300bf2f242dSmartin 
1301bf2f242dSmartin 	if (replace && map_to) {
1302bf2f242dSmartin 		newfile = bozomalloc(httpd, strlen(file) + rlen - len + 1);
1303bf2f242dSmartin 		memcpy(newfile, map_to, rlen);
1304bf2f242dSmartin 		strcpy(newfile+rlen, file + len);
13054cfb2183Smrg 		debug((httpd, DEBUG_NORMAL, "remapping found '%s'",
1306bf2f242dSmartin 		    newfile));
130712d8621dSmrg 		free(request->hr_file_free);
130812d8621dSmrg 		request->hr_file_free = request->hr_file = newfile;
1309bf2f242dSmartin 	}
1310bf2f242dSmartin 
1311bf2f242dSmartin 	munmap(fmap, st.st_size);
13124cfb2183Smrg out:
1313bf2f242dSmartin 	close(mapfile);
1314bf2f242dSmartin }
1315bf2f242dSmartin 
1316bf2f242dSmartin /*
1317ce206308Smrg  * deal with virtual host names; we do this:
1318ce206308Smrg  *	if we have a virtual path root (httpd->virtbase), and we are given a
1319ce206308Smrg  *	virtual host spec (Host: ho.st or http://ho.st/), see if this
1320ce206308Smrg  *	directory exists under httpd->virtbase.  if it does, use this as the
1321ce206308Smrg  #	new slashdir.
1322ce206308Smrg  */
1323ce206308Smrg static int
1324ce206308Smrg check_virtual(bozo_httpreq_t *request)
1325ce206308Smrg {
1326ce206308Smrg 	bozohttpd_t *httpd = request->hr_httpd;
1327ce206308Smrg 	char *file = request->hr_file, *s;
1328ce206308Smrg 	size_t len;
1329ce206308Smrg 
1330ce206308Smrg 	/*
1331ce206308Smrg 	 * convert http://virtual.host/ to request->hr_host
1332ce206308Smrg 	 */
13334cfb2183Smrg 	debug((httpd, DEBUG_OBESE,
13344cfb2183Smrg 	       "checking for http:// virtual host in '%s'", file));
1335ce206308Smrg 	if (strncasecmp(file, "http://", 7) == 0) {
1336ce206308Smrg 		/* we would do virtual hosting here? */
1337ce206308Smrg 		file += 7;
1338ca768e99Smrg 		/* RFC 2616 (HTTP/1.1), 5.2: URI takes precedence over Host: */
1339ca768e99Smrg 		free(request->hr_host);
1340cff2d956Smrg 		request->hr_host = bozostrdup(httpd, request, file);
1341ca768e99Smrg 		if ((s = strchr(request->hr_host, '/')) != NULL)
1342ca768e99Smrg 			*s = '\0';
1343ce206308Smrg 		s = strchr(file, '/');
134412d8621dSmrg 		free(request->hr_file_free);
134512d8621dSmrg 		request->hr_file_free = request->hr_file =
134612d8621dSmrg 		    bozostrdup(httpd, request, s ? s : "/");
13474cfb2183Smrg 		debug((httpd, DEBUG_OBESE, "got host '%s' file is now '%s'",
1348ce206308Smrg 		    request->hr_host, request->hr_file));
1349ce206308Smrg 	} else if (!request->hr_host)
1350ce206308Smrg 		goto use_slashdir;
1351ce206308Smrg 
1352ce206308Smrg 	/*
13537db440feSmrg 	 * canonicalise hr_host - that is, remove any :80.
13547db440feSmrg 	 */
13557db440feSmrg 	len = strlen(request->hr_host);
1356bf53dc23Smrg 	if (len > 3 &&
1357bf53dc23Smrg 	    strcmp(request->hr_host + len - 3, ":" BOZO_HTTP_PORT) == 0) {
13587db440feSmrg 		request->hr_host[len - 3] = '\0';
13597db440feSmrg 		len = strlen(request->hr_host);
13607db440feSmrg 	}
13617db440feSmrg 
1362c4fe1facSshm 	if (!httpd->virtbase) {
1363c4fe1facSshm 		/*
1364c4fe1facSshm 		 * if we don't use vhost support, then set virthostname if
1365c4fe1facSshm 		 * user supplied Host header. It will be used for possible
1366c4fe1facSshm 		 * redirections
1367c4fe1facSshm 		 */
1368c4fe1facSshm 		if (request->hr_host) {
1369c4fe1facSshm 			s = strrchr(request->hr_host, ':');
1370c4fe1facSshm 			if (s != NULL)
137153359366Smrg 				/*
137253359366Smrg 				 * truncate Host: as we want to copy it
137353359366Smrg 				 * without port part
137453359366Smrg 				 */
1375c4fe1facSshm 				*s = '\0';
1376cff2d956Smrg 			request->hr_virthostname = bozostrdup(httpd, request,
1377c4fe1facSshm 			  request->hr_host);
1378c4fe1facSshm 			if (s != NULL)
1379c4fe1facSshm 				/* fix Host: again, if we truncated it */
1380c4fe1facSshm 				*s = ':';
1381c4fe1facSshm 		}
1382c4fe1facSshm 		goto use_slashdir;
1383c4fe1facSshm 	}
1384c4fe1facSshm 
13857db440feSmrg 	/*
13867db440feSmrg 	 * ok, we have a virtual host, use opendir(3) to find a case
1387ce206308Smrg 	 * insensitive match for the virtual host we are asked for.
1388ce206308Smrg 	 * note that if the virtual host is the same as the master,
1389ce206308Smrg 	 * we don't need to do anything special.
1390ce206308Smrg 	 */
1391ce206308Smrg 	debug((httpd, DEBUG_OBESE,
1392ce206308Smrg 	    "check_virtual: checking host `%s' under httpd->virtbase `%s' "
1393ce206308Smrg 	    "for file `%s'",
1394ce206308Smrg 	    request->hr_host, httpd->virtbase, request->hr_file));
1395ce206308Smrg 	if (strncasecmp(httpd->virthostname, request->hr_host, len) != 0) {
13964cfb2183Smrg 		s = NULL;
139744128f48Smrg 		DIR *dirp;
139844128f48Smrg 		struct dirent *d;
139944128f48Smrg 
1400109d4102Smrg 		if ((dirp = opendir(httpd->virtbase)) != NULL) {
1401109d4102Smrg 			while ((d = readdir(dirp)) != NULL) {
1402109d4102Smrg 				if (strcmp(d->d_name, ".") == 0 ||
1403109d4102Smrg 				    strcmp(d->d_name, "..") == 0) {
1404109d4102Smrg 					continue;
1405109d4102Smrg 				}
14064cfb2183Smrg 				debug((httpd, DEBUG_OBESE, "looking at dir '%s'",
1407109d4102Smrg 			 	   d->d_name));
14085f066e93Sshm 				if (strcmp(d->d_name, request->hr_host) == 0) {
1409ce206308Smrg 					/* found it, punch it */
1410109d4102Smrg 					debug((httpd, DEBUG_OBESE, "found it punch it"));
1411407204a7Smartin 					request->hr_virthostname =
1412cff2d956Smrg 					    bozostrdup(httpd, request, d->d_name);
1413881b8188Smrg 					bozoasprintf(httpd, &s, "%s/%s",
1414f47ab3a3Schristos 					    httpd->virtbase,
1415f47ab3a3Schristos 					    request->hr_virthostname);
1416ce206308Smrg 					break;
1417ce206308Smrg 				}
1418ce206308Smrg 			}
1419109d4102Smrg 			closedir(dirp);
1420109d4102Smrg 		}
1421109d4102Smrg 		else {
1422109d4102Smrg 			debug((httpd, DEBUG_FAT, "opendir %s failed: %s",
1423109d4102Smrg 			    httpd->virtbase, strerror(errno)));
1424109d4102Smrg 		}
1425ce206308Smrg 		if (s == 0) {
1426ce206308Smrg 			if (httpd->unknown_slash)
1427ce206308Smrg 				goto use_slashdir;
1428ce206308Smrg 			return bozo_http_error(httpd, 404, request,
1429ce206308Smrg 						"unknown URL");
1430ce206308Smrg 		}
1431ce206308Smrg 	} else
1432ce206308Smrg use_slashdir:
1433ce206308Smrg 		s = httpd->slashdir;
1434ce206308Smrg 
1435ce206308Smrg 	/*
1436ce206308Smrg 	 * ok, nailed the correct slashdir, chdir to it
1437ce206308Smrg 	 */
1438ce206308Smrg 	if (chdir(s) < 0)
1439ce206308Smrg 		return bozo_http_error(httpd, 404, request,
1440ce206308Smrg 					"can't chdir to slashdir");
1441bf2f242dSmartin 
1442bf2f242dSmartin 	/*
1443bf2f242dSmartin 	 * is there a mapping for this request?
1444bf2f242dSmartin 	 */
14454cfb2183Smrg 	check_remap(request);
1446bf2f242dSmartin 
1447ce206308Smrg 	return 0;
1448ce206308Smrg }
1449ce206308Smrg 
1450ce206308Smrg /*
1451ce206308Smrg  * checks to see if this request has a valid .bzredirect file.  returns
14528324be4cSandvar  * 0 when no redirection happened, or 1 when handle_redirect() has been
1453a212be1eSmrg  * called, -1 on error.
1454ce206308Smrg  */
14552b3a4643Smartin static int
1456ce206308Smrg check_bzredirect(bozo_httpreq_t *request)
1457ce206308Smrg {
1458cff2d956Smrg 	bozohttpd_t *httpd = request->hr_httpd;
1459ce206308Smrg 	struct stat sb;
14608d76df80Smartin 	char dir[MAXPATHLEN], redir[MAXPATHLEN], redirpath[MAXPATHLEN + 1],
146134ece249Smrg 	    path[MAXPATHLEN + 1];
1462ce206308Smrg 	char *basename, *finalredir;
1463ce206308Smrg 	int rv, absolute;
1464ce206308Smrg 
1465ce206308Smrg 	/*
1466ce206308Smrg 	 * if this pathname is really a directory, but doesn't end in /,
1467ce206308Smrg 	 * use it as the directory to look for the redir file.
1468ce206308Smrg 	 */
1469a212be1eSmrg 	if ((size_t)snprintf(dir, sizeof(dir), "%s", request->hr_file + 1) >=
14704cfb2183Smrg 	    sizeof(dir)) {
14714cfb2183Smrg 		bozo_http_error(httpd, 404, request, "file path too long");
14724cfb2183Smrg 		return -1;
14734cfb2183Smrg 	}
1474cff2d956Smrg 	debug((httpd, DEBUG_FAT, "check_bzredirect: dir %s", dir));
1475ce206308Smrg 	basename = strrchr(dir, '/');
1476ce206308Smrg 
1477ce206308Smrg 	if ((!basename || basename[1] != '\0') &&
1478c4fe1facSshm 	    lstat(dir, &sb) == 0 && S_ISDIR(sb.st_mode)) {
1479c4fe1facSshm 		strcpy(path, dir);
1480a3912675Smrg 		basename = dir;
1481c4fe1facSshm 	} else if (basename == NULL) {
1482c4fe1facSshm 		strcpy(path, ".");
1483c4fe1facSshm 		strcpy(dir, "");
14846c220236Sleot 		basename = request->hr_file + 1;
1485c4fe1facSshm 	} else {
1486ce206308Smrg 		*basename++ = '\0';
1487c4fe1facSshm 		strcpy(path, dir);
1488ce206308Smrg 	}
148900e064adSmrg 	if (bozo_check_special_files(request, basename, true))
1490a3912675Smrg 		return -1;
1491ce206308Smrg 
1492cff2d956Smrg 	debug((httpd, DEBUG_FAT, "check_bzredirect: path %s", path));
1493c4fe1facSshm 
1494c4fe1facSshm 	if ((size_t)snprintf(redir, sizeof(redir), "%s/%s", path,
1495a212be1eSmrg 			     REDIRECT_FILE) >= sizeof(redir)) {
1496a3912675Smrg 		return bozo_http_error(httpd, 404, request,
1497a212be1eSmrg 		    "redirectfile path too long");
1498a212be1eSmrg 	}
1499ce206308Smrg 	if (lstat(redir, &sb) == 0) {
1500ce206308Smrg 		if (!S_ISLNK(sb.st_mode))
15012b3a4643Smartin 			return 0;
1502ce206308Smrg 		absolute = 0;
1503ce206308Smrg 	} else {
1504c4fe1facSshm 		if ((size_t)snprintf(redir, sizeof(redir), "%s/%s", path,
1505a212be1eSmrg 				     ABSREDIRECT_FILE) >= sizeof(redir)) {
15064cfb2183Smrg 			bozo_http_error(httpd, 404, request,
1507a212be1eSmrg 					"redirectfile path too long");
15084cfb2183Smrg 			return -1;
1509a212be1eSmrg 		}
1510ce206308Smrg 		if (lstat(redir, &sb) < 0 || !S_ISLNK(sb.st_mode))
15112b3a4643Smartin 			return 0;
1512ce206308Smrg 		absolute = 1;
1513ce206308Smrg 	}
1514cff2d956Smrg 	debug((httpd, DEBUG_FAT, "check_bzredirect: calling readlink"));
1515ce206308Smrg 	rv = readlink(redir, redirpath, sizeof redirpath - 1);
1516ce206308Smrg 	if (rv == -1 || rv == 0) {
1517cff2d956Smrg 		debug((httpd, DEBUG_FAT, "readlink failed"));
15182b3a4643Smartin 		return 0;
1519ce206308Smrg 	}
1520ce206308Smrg 	redirpath[rv] = '\0';
1521cff2d956Smrg 	debug((httpd, DEBUG_FAT, "readlink returned \"%s\"", redirpath));
1522ce206308Smrg 
15238d76df80Smartin 	/* check if we need authentication */
15248d76df80Smartin 	snprintf(path, sizeof(path), "%s/", dir);
15258d76df80Smartin 	if (bozo_auth_check(request, path))
15268d76df80Smartin 		return 1;
15278d76df80Smartin 
1528ce206308Smrg 	/* now we have the link pointer, redirect to the real place */
1529c4fe1facSshm 	if (!absolute && redirpath[0] != '/') {
1530c4fe1facSshm 		if ((size_t)snprintf(finalredir = redir, sizeof(redir), "%s%s/%s",
1531c4fe1facSshm 		  (strlen(dir) > 0 ? "/" : ""), dir, redirpath) >= sizeof(redir)) {
15324cfb2183Smrg 			bozo_http_error(httpd, 404, request,
1533a212be1eSmrg 					"redirect path too long");
15344cfb2183Smrg 			return -1;
1535a212be1eSmrg 		}
1536c4fe1facSshm 	} else
1537c4fe1facSshm 		finalredir = redirpath;
1538ce206308Smrg 
1539cff2d956Smrg 	debug((httpd, DEBUG_FAT, "check_bzredirect: new redir %s", finalredir));
1540ce206308Smrg 	handle_redirect(request, finalredir, absolute);
15412b3a4643Smartin 	return 1;
1542ce206308Smrg }
1543ce206308Smrg 
1544ce206308Smrg /* this fixes the %HH hack that RFC2396 requires.  */
154527da98ffSmrg int
154627da98ffSmrg bozo_decode_url_percent(bozo_httpreq_t *request, char *str)
1547ce206308Smrg {
1548ce206308Smrg 	bozohttpd_t *httpd = request->hr_httpd;
154927da98ffSmrg 	char	*s, *t, buf[3];
1550ce206308Smrg 	char	*end;	/* if end is not-zero, we don't translate beyond that */
1551ce206308Smrg 
155227da98ffSmrg 	end = str + strlen(str);
1553ce206308Smrg 
1554ce206308Smrg 	/* fast forward to the first % */
155527da98ffSmrg 	if ((s = strchr(str, '%')) == NULL)
1556ca5b33a5Sshm 		return 0;
1557ce206308Smrg 
1558ce206308Smrg 	t = s;
1559ce206308Smrg 	do {
1560ce206308Smrg 		if (end && s >= end) {
1561ce206308Smrg 			debug((httpd, DEBUG_EXPLODING,
1562ce206308Smrg 				"fu_%%: past end, filling out.."));
1563ce206308Smrg 			while (*s)
1564ce206308Smrg 				*t++ = *s++;
1565ce206308Smrg 			break;
1566ce206308Smrg 		}
15679c7b529aSshm 		if (&s[2] < end)
1568ce206308Smrg 			debug((httpd, DEBUG_EXPLODING,
1569ce206308Smrg 				"fu_%%: got s == %%, s[1]s[2] == %c%c",
1570ce206308Smrg 				s[1], s[2]));
15719c7b529aSshm 		else
15729c7b529aSshm 			debug((httpd, DEBUG_EXPLODING,
15739c7b529aSshm 			    "fu_%%: got s == %%, s[1] == %c s[2] is not set",
15749c7b529aSshm 				s[1]));
1575a3912675Smrg 		if (s[1] == '\0' || s[2] == '\0')
1576a3912675Smrg 			return bozo_http_error(httpd, 400, request,
1577ce206308Smrg 			    "percent hack missing two chars afterwards");
1578a3912675Smrg 		if (s[1] == '0' && s[2] == '0')
1579a3912675Smrg 			return bozo_http_error(httpd, 404, request,
1580ce206308Smrg 			    "percent hack was %00");
1581bf53dc23Smrg 		if (s[1] == '2' && (s[2] == 'f' || s[2] == 'F'))
1582a3912675Smrg 			return bozo_http_error(httpd, 404, request,
1583ce206308Smrg 			    "percent hack was %2f (/)");
1584ce206308Smrg 
1585ce206308Smrg 		buf[0] = *++s;
1586ce206308Smrg 		buf[1] = *++s;
1587ce206308Smrg 		buf[2] = '\0';
1588ce206308Smrg 		s++;
1589ce206308Smrg 		*t = (char)strtol(buf, NULL, 16);
1590ce206308Smrg 		debug((httpd, DEBUG_EXPLODING,
1591ce206308Smrg 				"fu_%%: strtol put '%02x' into *t", *t));
1592a3912675Smrg 		if (*t++ == '\0')
1593a3912675Smrg 			return bozo_http_error(httpd, 400, request,
1594ce206308Smrg 			    "percent hack got a 0 back");
1595ce206308Smrg 
1596ce206308Smrg 		while (*s && *s != '%') {
1597ce206308Smrg 			if (end && s >= end)
1598ce206308Smrg 				break;
1599ce206308Smrg 			*t++ = *s++;
1600ce206308Smrg 		}
1601ce206308Smrg 	} while (*s);
1602ce206308Smrg 	*t = '\0';
1603ca5b33a5Sshm 
160427da98ffSmrg 	debug((httpd, DEBUG_FAT, "bozo_decode_url_percent returns `%s'",
1605ce206308Smrg 			request->hr_file));
1606ca5b33a5Sshm 
1607ca5b33a5Sshm 	return 0;
1608ce206308Smrg }
1609ce206308Smrg 
1610ce206308Smrg /*
1611ce206308Smrg  * transform_request does this:
1612ce206308Smrg  *	- ``expand'' %20 crapola
1613ce206308Smrg  *	- punt if it doesn't start with /
1614ce206308Smrg  *	- look for "http://myname/" and deal with it.
1615ce206308Smrg  *	- maybe call bozo_process_cgi()
1616ce206308Smrg  *	- check for ~user and call bozo_user_transform() if so
1617ce206308Smrg  *	- if the length > 1, check for trailing slash.  if so,
1618ce206308Smrg  *	  add the index.html file
1619ce206308Smrg  *	- if the length is 1, return the index.html file
1620ce206308Smrg  *	- disallow anything ending up with a file starting
1621ce206308Smrg  *	  at "/" or having ".." in it.
1622ce206308Smrg  *	- anything else is a really weird internal error
1623ce206308Smrg  *	- returns malloced file to serve, if unhandled
1624ce206308Smrg  */
1625ce206308Smrg static int
1626ce206308Smrg transform_request(bozo_httpreq_t *request, int *isindex)
1627ce206308Smrg {
1628ce206308Smrg 	bozohttpd_t *httpd = request->hr_httpd;
1629ce206308Smrg 	char	*file, *newfile = NULL;
1630ce206308Smrg 	size_t	len;
1631ce206308Smrg 
1632ce206308Smrg 	file = NULL;
1633ce206308Smrg 	*isindex = 0;
1634ce206308Smrg 	debug((httpd, DEBUG_FAT, "tf_req: file %s", request->hr_file));
16354cfb2183Smrg 
16364cfb2183Smrg 	if (bozo_decode_url_percent(request, request->hr_file) ||
16374cfb2183Smrg 	    check_virtual(request))
1638ca5b33a5Sshm 		goto bad_done;
16394cfb2183Smrg 
1640ce206308Smrg 	file = request->hr_file;
1641ce206308Smrg 
1642ce206308Smrg 	if (file[0] != '/') {
16434cfb2183Smrg 		bozo_http_error(httpd, 404, request, "unknown URL");
1644ce206308Smrg 		goto bad_done;
1645ce206308Smrg 	}
1646ce206308Smrg 
164751b65afaSshm 	/* omit additional slashes at the beginning */
164851b65afaSshm 	while (file[1] == '/')
164951b65afaSshm 		file++;
165051b65afaSshm 
1651c4fe1facSshm 	/* fix file provided by user as it's used in other handlers */
1652c4fe1facSshm 	request->hr_file = file;
1653c4fe1facSshm 
1654c4fe1facSshm 	len = strlen(file);
1655c4fe1facSshm 
1656c4fe1facSshm #ifndef NO_USER_SUPPORT
1657c4fe1facSshm 	/* first of all expand user path */
1658c4fe1facSshm 	if (len > 1 && httpd->enable_users && file[1] == '~') {
1659c4fe1facSshm 		if (file[2] == '\0') {
16604cfb2183Smrg 			bozo_http_error(httpd, 404, request,
1661c4fe1facSshm 					"missing username");
1662c4fe1facSshm 			goto bad_done;
1663c4fe1facSshm 		}
1664c4fe1facSshm 		if (strchr(file + 2, '/') == NULL) {
1665c4fe1facSshm 			char *userredirecturl;
16664cfb2183Smrg 
1667881b8188Smrg 			bozoasprintf(httpd, &userredirecturl, "%s/", file);
1668c4fe1facSshm 			handle_redirect(request, userredirecturl, 0);
1669c4fe1facSshm 			free(userredirecturl);
1670c4fe1facSshm 			return 0;
1671c4fe1facSshm 		}
1672c4fe1facSshm 		debug((httpd, DEBUG_FAT, "calling bozo_user_transform"));
1673c4fe1facSshm 
1674c4fe1facSshm 		if (!bozo_user_transform(request))
1675c4fe1facSshm 			return 0;
1676c4fe1facSshm 
1677c4fe1facSshm 		file = request->hr_file;
1678c4fe1facSshm 		len = strlen(file);
1679c4fe1facSshm 	}
1680c4fe1facSshm #endif /* NO_USER_SUPPORT */
1681c4fe1facSshm 
1682c4fe1facSshm 
1683a212be1eSmrg 	switch (check_bzredirect(request)) {
1684a212be1eSmrg 	case -1:
1685a212be1eSmrg 		goto bad_done;
1686a3912675Smrg 	case 0:
1687a3912675Smrg 		break;
1688a3912675Smrg 	default:
16892b3a4643Smartin 		return 0;
1690a212be1eSmrg 	}
1691ce206308Smrg 
1692c4fe1facSshm 	if (len > 1) {
1693ce206308Smrg 		debug((httpd, DEBUG_FAT, "file[len-1] == %c", file[len-1]));
1694ce206308Smrg 		if (file[len-1] == '/') {	/* append index.html */
1695ce206308Smrg 			*isindex = 1;
1696ce206308Smrg 			debug((httpd, DEBUG_FAT, "appending index.html"));
1697ce206308Smrg 			newfile = bozomalloc(httpd,
1698ce206308Smrg 					len + strlen(httpd->index_html) + 1);
1699ce206308Smrg 			strcpy(newfile, file + 1);
1700ce206308Smrg 			strcat(newfile, httpd->index_html);
1701ce206308Smrg 		} else
1702cff2d956Smrg 			newfile = bozostrdup(httpd, request, file + 1);
1703ce206308Smrg 	} else if (len == 1) {
1704ce206308Smrg 		debug((httpd, DEBUG_EXPLODING, "tf_req: len == 1"));
1705cff2d956Smrg 		newfile = bozostrdup(httpd, request, httpd->index_html);
1706ce206308Smrg 		*isindex = 1;
1707ce206308Smrg 	} else {	/* len == 0 ? */
17084cfb2183Smrg 		bozo_http_error(httpd, 500, request, "request->hr_file is nul");
1709ce206308Smrg 		goto bad_done;
1710ce206308Smrg 	}
1711ce206308Smrg 
1712ce206308Smrg 	if (newfile == NULL) {
17134cfb2183Smrg 		bozo_http_error(httpd, 500, request, "internal failure");
1714ce206308Smrg 		goto bad_done;
1715ce206308Smrg 	}
1716ce206308Smrg 
1717ce206308Smrg 	/*
1718ce206308Smrg 	 * stop traversing outside our domain
1719ce206308Smrg 	 *
1720ce206308Smrg 	 * XXX true security only comes from our parent using chroot(2)
1721ce206308Smrg 	 * before execve(2)'ing us.  or our own built in chroot(2) support.
1722ce206308Smrg 	 */
1723c4fe1facSshm 
1724c4fe1facSshm 	debug((httpd, DEBUG_FAT, "newfile: %s", newfile));
1725c4fe1facSshm 
1726ce206308Smrg 	if (*newfile == '/' || strcmp(newfile, "..") == 0 ||
1727ce206308Smrg 	    strstr(newfile, "/..") || strstr(newfile, "../")) {
17284cfb2183Smrg 		bozo_http_error(httpd, 403, request, "illegal request");
1729ce206308Smrg 		goto bad_done;
1730ce206308Smrg 	}
1731ce206308Smrg 
1732a07e0db3Smrg 	if (bozo_auth_check(request, newfile))
1733ce206308Smrg 		goto bad_done;
1734ce206308Smrg 
1735ce206308Smrg 	if (strlen(newfile)) {
173612d8621dSmrg 		request->hr_oldfile = request->hr_file_free;
1737ce206308Smrg 		request->hr_file = newfile;
1738ce206308Smrg 	}
1739ce206308Smrg 
17404cfb2183Smrg 	if (bozo_process_cgi(request) ||
17414cfb2183Smrg 	    bozo_process_lua(request))
1742cb23152cSmbalmer 		return 0;
1743cb23152cSmbalmer 
1744ce206308Smrg 	debug((httpd, DEBUG_FAT, "transform_request set: %s", newfile));
1745ce206308Smrg 	return 1;
17464cfb2183Smrg 
1747ce206308Smrg bad_done:
1748ce206308Smrg 	debug((httpd, DEBUG_FAT, "transform_request returning: 0"));
1749ce206308Smrg 	free(newfile);
1750ce206308Smrg 	return 0;
1751ce206308Smrg }
1752ce206308Smrg 
1753ce206308Smrg /*
1754f082d14aSelric  * can_gzip checks if the request supports and prefers gzip encoding.
1755f082d14aSelric  *
1756f082d14aSelric  * XXX: we do not consider the associated q with gzip in making our
1757f082d14aSelric  *      decision which is broken.
1758f082d14aSelric  */
1759f082d14aSelric 
1760f082d14aSelric static int
1761f082d14aSelric can_gzip(bozo_httpreq_t *request)
1762f082d14aSelric {
1763f082d14aSelric 	const char	*pos;
1764f082d14aSelric 	const char	*tmp;
1765f082d14aSelric 	size_t		 len;
1766f082d14aSelric 
1767f082d14aSelric 	/* First we decide if the request can be gzipped at all. */
1768f082d14aSelric 
1769f082d14aSelric 	/* not if we already are encoded... */
1770f082d14aSelric 	tmp = bozo_content_encoding(request, request->hr_file);
1771f082d14aSelric 	if (tmp && *tmp)
1772f082d14aSelric 		return 0;
1773f082d14aSelric 
1774f082d14aSelric 	/* not if we are not asking for the whole file... */
1775f082d14aSelric 	if (request->hr_last_byte_pos != -1 || request->hr_have_range)
1776f082d14aSelric 		return 0;
1777f082d14aSelric 
1778f082d14aSelric 	/* Then we determine if gzip is on the cards. */
1779f082d14aSelric 
1780f082d14aSelric 	for (pos = request->hr_accept_encoding; pos && *pos; pos += len) {
1781f082d14aSelric 		while (*pos == ' ')
1782f082d14aSelric 			pos++;
1783f082d14aSelric 
1784f082d14aSelric 		len = strcspn(pos, ";,");
1785f082d14aSelric 
1786f082d14aSelric 		if ((len == 4 && strncasecmp("gzip", pos, 4) == 0) ||
1787f082d14aSelric 		    (len == 6 && strncasecmp("x-gzip", pos, 6) == 0))
1788f082d14aSelric 			return 1;
1789f082d14aSelric 
1790f082d14aSelric 		if (pos[len] == ';')
1791f082d14aSelric 			len += strcspn(&pos[len], ",");
1792f082d14aSelric 
1793f082d14aSelric 		if (pos[len])
1794f082d14aSelric 			len++;
1795f082d14aSelric 	}
1796f082d14aSelric 
1797f082d14aSelric 	return 0;
1798f082d14aSelric }
1799f082d14aSelric 
1800f082d14aSelric /*
1801ce206308Smrg  * bozo_process_request does the following:
180260dbe745Stls  *	- check the request is valid
180360dbe745Stls  *	- process cgi-bin if necessary
180460dbe745Stls  *	- transform a filename if necesarry
180560dbe745Stls  *	- return the HTTP request
180660dbe745Stls  */
1807ce206308Smrg void
1808ce206308Smrg bozo_process_request(bozo_httpreq_t *request)
180960dbe745Stls {
1810ce206308Smrg 	bozohttpd_t *httpd = request->hr_httpd;
181160dbe745Stls 	struct	stat sb;
18127925dff4Sjoerg 	time_t timestamp;
181360dbe745Stls 	char	*file;
181460dbe745Stls 	const char *type, *encoding;
181560dbe745Stls 	int	fd, isindex;
181660dbe745Stls 
181760dbe745Stls 	/*
181860dbe745Stls 	 * note that transform_request chdir()'s if required.  also note
181903387632Smrg 	 * that cgi is handed here.  if transform_request() returns 0
182003387632Smrg 	 * then the request has been handled already.
182160dbe745Stls 	 */
182203387632Smrg 	if (transform_request(request, &isindex) == 0)
182303387632Smrg 		return;
182403387632Smrg 
1825f082d14aSelric 	fd = -1;
1826f082d14aSelric 	encoding = NULL;
1827f082d14aSelric 	if (can_gzip(request)) {
1828881b8188Smrg 		bozoasprintf(httpd, &file, "%s.gz", request->hr_file);
1829f082d14aSelric 		fd = open(file, O_RDONLY);
1830f082d14aSelric 		if (fd >= 0)
1831f082d14aSelric 			encoding = "gzip";
1832f082d14aSelric 		free(file);
1833f082d14aSelric 	}
1834f082d14aSelric 
183503387632Smrg 	file = request->hr_file;
183660dbe745Stls 
1837f082d14aSelric 	if (fd < 0)
183860dbe745Stls 		fd = open(file, O_RDONLY);
1839f082d14aSelric 
184060dbe745Stls 	if (fd < 0) {
1841ce206308Smrg 		debug((httpd, DEBUG_FAT, "open failed: %s", strerror(errno)));
1842d1ed37e4Sshm 		switch (errno) {
1843d1ed37e4Sshm 		case EPERM:
1844411a393dSsnj 		case EACCES:
18454cfb2183Smrg 			bozo_http_error(httpd, 403, request,
1846ce206308Smrg 					"no permission to open file");
1847d1ed37e4Sshm 			break;
1848d1ed37e4Sshm 		case ENAMETOOLONG:
1849d1ed37e4Sshm 			/*FALLTHROUGH*/
1850d1ed37e4Sshm 		case ENOENT:
1851ce206308Smrg 			if (!bozo_dir_index(request, file, isindex))
18524cfb2183Smrg 				bozo_http_error(httpd, 404, request, "no file");
1853d1ed37e4Sshm 			break;
1854d1ed37e4Sshm 		default:
18554cfb2183Smrg 			bozo_http_error(httpd, 500, request, "open file");
1856d1ed37e4Sshm 		}
1857c6e75af2Smrg 		goto cleanup_nofd;
185860dbe745Stls 	}
185903387632Smrg 	if (fstat(fd, &sb) < 0) {
18604cfb2183Smrg 		bozo_http_error(httpd, 500, request, "can't fstat");
186103387632Smrg 		goto cleanup;
186203387632Smrg 	}
186303387632Smrg 	if (S_ISDIR(sb.st_mode)) {
186460dbe745Stls 		handle_redirect(request, NULL, 0);
186503387632Smrg 		goto cleanup;
186603387632Smrg 	}
186703387632Smrg 
18687925dff4Sjoerg 	if (request->hr_if_modified_since &&
18697925dff4Sjoerg 	    parse_http_date(request->hr_if_modified_since, &timestamp) &&
18707925dff4Sjoerg 	    timestamp >= sb.st_mtime) {
18717925dff4Sjoerg 		/* XXX ignore subsecond of timestamp */
1872ce206308Smrg 		bozo_printf(httpd, "%s 304 Not Modified\r\n",
1873ce206308Smrg 				request->hr_proto);
1874ce206308Smrg 		bozo_printf(httpd, "\r\n");
1875ce206308Smrg 		bozo_flush(httpd, stdout);
187603387632Smrg 		goto cleanup;
18777925dff4Sjoerg 	}
187860dbe745Stls 
1879707281a2Smrg 	/* validate requested range */
1880707281a2Smrg 	if (request->hr_last_byte_pos == -1 ||
1881707281a2Smrg 	    request->hr_last_byte_pos >= sb.st_size)
1882707281a2Smrg 		request->hr_last_byte_pos = sb.st_size - 1;
1883707281a2Smrg 	if (request->hr_have_range &&
1884707281a2Smrg 	    request->hr_first_byte_pos > request->hr_last_byte_pos) {
1885707281a2Smrg 		request->hr_have_range = 0;	/* punt */
1886707281a2Smrg 		request->hr_first_byte_pos = 0;
1887707281a2Smrg 		request->hr_last_byte_pos = sb.st_size - 1;
1888707281a2Smrg 	}
188943d06469Sjoerg 	debug((httpd, DEBUG_FAT, "have_range %d first_pos %lld last_pos %lld",
1890707281a2Smrg 	    request->hr_have_range,
189143d06469Sjoerg 	    (long long)request->hr_first_byte_pos,
189243d06469Sjoerg 	    (long long)request->hr_last_byte_pos));
1893707281a2Smrg 	if (request->hr_have_range)
1894ce206308Smrg 		bozo_printf(httpd, "%s 206 Partial Content\r\n",
1895ce206308Smrg 				request->hr_proto);
1896707281a2Smrg 	else
1897ce206308Smrg 		bozo_printf(httpd, "%s 200 OK\r\n", request->hr_proto);
189860dbe745Stls 
1899ce206308Smrg 	if (request->hr_proto != httpd->consts.http_09) {
1900ce206308Smrg 		type = bozo_content_type(request, file);
1901f082d14aSelric 		if (!encoding)
1902ce206308Smrg 			encoding = bozo_content_encoding(request, file);
190360dbe745Stls 
1904ce206308Smrg 		bozo_print_header(request, &sb, type, encoding);
1905ce206308Smrg 		bozo_printf(httpd, "\r\n");
190660dbe745Stls 	}
1907ce206308Smrg 	bozo_flush(httpd, stdout);
190860dbe745Stls 
190960dbe745Stls 	if (request->hr_method != HTTP_HEAD) {
191003387632Smrg 		off_t szleft, cur_byte_pos;
191103387632Smrg 
191203387632Smrg 		szleft =
191303387632Smrg 		     request->hr_last_byte_pos - request->hr_first_byte_pos + 1;
191403387632Smrg 		cur_byte_pos = request->hr_first_byte_pos;
191503387632Smrg 
1916c6e75af2Smrg  retry:
191703387632Smrg 		while (szleft) {
1918707281a2Smrg 			size_t sz;
191960dbe745Stls 
1920ce206308Smrg 			if ((off_t)httpd->mmapsz < szleft)
1921ce206308Smrg 				sz = httpd->mmapsz;
192203387632Smrg 			else
1923ce206308Smrg 				sz = (size_t)szleft;
1924ce206308Smrg 			if (mmap_and_write_part(httpd, fd, cur_byte_pos, sz)) {
1925c6e75af2Smrg 				if (errno == ENOMEM) {
1926ce206308Smrg 					httpd->mmapsz /= 2;
1927ce206308Smrg 					if (httpd->mmapsz >= httpd->page_size)
1928c6e75af2Smrg 						goto retry;
1929c6e75af2Smrg 				}
193003387632Smrg 				goto cleanup;
1931c6e75af2Smrg 			}
193203387632Smrg 			cur_byte_pos += sz;
193303387632Smrg 			szleft -= sz;
193460dbe745Stls 		}
193560dbe745Stls 	}
193603387632Smrg  cleanup:
193760dbe745Stls 	close(fd);
1938c6e75af2Smrg  cleanup_nofd:
19390acfa6caSspz 	/* If SSL enabled send close_notify. */
19400acfa6caSspz 	bozo_ssl_shutdown(request->hr_httpd);
1941c6e75af2Smrg 	close(STDIN_FILENO);
1942c6e75af2Smrg 	close(STDOUT_FILENO);
1943c6e75af2Smrg 	/*close(STDERR_FILENO);*/
194460dbe745Stls }
194560dbe745Stls 
194660dbe745Stls /* make sure we're not trying to access special files */
194703387632Smrg int
194800e064adSmrg bozo_check_special_files(bozo_httpreq_t *request, const char *name, bool doerror)
194960dbe745Stls {
1950ce206308Smrg 	bozohttpd_t *httpd = request->hr_httpd;
1951ac815567Smrg 	size_t i;
195200e064adSmrg 	int error = 0;
1953ce206308Smrg 
195400e064adSmrg 	for (i = 0; specials[i].file; i++) {
195500e064adSmrg 		if (strcmp(name, specials[i].file) == 0) {
195600e064adSmrg 			if (doerror) {
195700e064adSmrg 				error = bozo_http_error(httpd, 403, request,
19584cfb2183Smrg 					       specials[i].name);
195900e064adSmrg 			} else {
196000e064adSmrg 				error = -1;
196100e064adSmrg 			}
196200e064adSmrg 		}
196300e064adSmrg 	}
1964a3912675Smrg 
196500e064adSmrg 	return error;
196660dbe745Stls }
196760dbe745Stls 
196860dbe745Stls /* generic header printing routine */
196960dbe745Stls void
1970ce206308Smrg bozo_print_header(bozo_httpreq_t *request,
1971ce206308Smrg 		struct stat *sbp, const char *type, const char *encoding)
197260dbe745Stls {
1973ce206308Smrg 	bozohttpd_t *httpd = request->hr_httpd;
1974707281a2Smrg 	off_t len;
1975ce206308Smrg 	char	date[40];
1976afe55bf8Selric 	bozoheaders_t *hdr;
1977afe55bf8Selric 
1978afe55bf8Selric 	SIMPLEQ_FOREACH(hdr, &request->hr_replheaders, h_next) {
1979afe55bf8Selric 		bozo_printf(httpd, "%s: %s\r\n", hdr->h_header,
1980afe55bf8Selric 				hdr->h_value);
1981afe55bf8Selric 	}
1982707281a2Smrg 
1983ce206308Smrg 	bozo_printf(httpd, "Date: %s\r\n", bozo_http_date(date, sizeof(date)));
1984ce206308Smrg 	bozo_printf(httpd, "Server: %s\r\n", httpd->server_software);
1985ce206308Smrg 	bozo_printf(httpd, "Accept-Ranges: bytes\r\n");
198660dbe745Stls 	if (sbp) {
198760dbe745Stls 		char filedate[40];
198860dbe745Stls 		struct	tm *tm;
198960dbe745Stls 
199060dbe745Stls 		tm = gmtime(&sbp->st_mtime);
199160dbe745Stls 		strftime(filedate, sizeof filedate,
199260dbe745Stls 		    "%a, %d %b %Y %H:%M:%S GMT", tm);
1993ce206308Smrg 		bozo_printf(httpd, "Last-Modified: %s\r\n", filedate);
199460dbe745Stls 	}
199560dbe745Stls 	if (type && *type)
1996ce206308Smrg 		bozo_printf(httpd, "Content-Type: %s\r\n", type);
199760dbe745Stls 	if (encoding && *encoding)
1998ce206308Smrg 		bozo_printf(httpd, "Content-Encoding: %s\r\n", encoding);
1999707281a2Smrg 	if (sbp) {
2000707281a2Smrg 		if (request->hr_have_range) {
2001ce206308Smrg 			len = request->hr_last_byte_pos -
2002ce206308Smrg 					request->hr_first_byte_pos +1;
2003ce206308Smrg 			bozo_printf(httpd,
2004ce206308Smrg 				"Content-Range: bytes %qd-%qd/%qd\r\n",
2005707281a2Smrg 				(long long) request->hr_first_byte_pos,
2006707281a2Smrg 				(long long) request->hr_last_byte_pos,
2007707281a2Smrg 				(long long) sbp->st_size);
2008ce206308Smrg 		} else
2009707281a2Smrg 			len = sbp->st_size;
2010ce206308Smrg 		bozo_printf(httpd, "Content-Length: %qd\r\n", (long long)len);
2011707281a2Smrg 	}
2012c4fe1facSshm 	if (request->hr_proto == httpd->consts.http_11)
2013ce206308Smrg 		bozo_printf(httpd, "Connection: close\r\n");
2014ce206308Smrg 	bozo_flush(httpd, stdout);
2015ce206308Smrg }
2016ce206308Smrg 
2017df5be573Smrg #ifndef NO_DEBUG
2018ce206308Smrg void
2019ce206308Smrg debug__(bozohttpd_t *httpd, int level, const char *fmt, ...)
2020ce206308Smrg {
2021ce206308Smrg 	va_list	ap;
2022ce206308Smrg 	int savederrno;
2023ce206308Smrg 
2024ce206308Smrg 	/* only log if the level is low enough */
2025ce206308Smrg 	if (httpd->debug < level)
2026ce206308Smrg 		return;
2027ce206308Smrg 
2028ce206308Smrg 	savederrno = errno;
2029ce206308Smrg 	va_start(ap, fmt);
20303e94b887Smartin 	if (!httpd->nolog) {
2031ce206308Smrg 		if (httpd->logstderr) {
2032ce206308Smrg 			vfprintf(stderr, fmt, ap);
2033ce206308Smrg 			fputs("\n", stderr);
2034ce206308Smrg 		} else
2035ce206308Smrg 			vsyslog(LOG_DEBUG, fmt, ap);
20363e94b887Smartin 	}
2037ce206308Smrg 	va_end(ap);
2038ce206308Smrg 	errno = savederrno;
2039ce206308Smrg }
2040df5be573Smrg #endif /* NO_DEBUG */
2041ce206308Smrg 
2042ce206308Smrg /* these are like warn() and err(), except for syslog not stderr */
2043ce206308Smrg void
2044881b8188Smrg bozowarn(bozohttpd_t *httpd, const char *fmt, ...)
2045ce206308Smrg {
2046ce206308Smrg 	va_list ap;
2047ce206308Smrg 
2048ce206308Smrg 	va_start(ap, fmt);
20493e94b887Smartin 	if (!httpd->nolog) {
2050ce206308Smrg 		if (httpd->logstderr || isatty(STDERR_FILENO)) {
2051ce206308Smrg 			//fputs("warning: ", stderr);
2052ce206308Smrg 			vfprintf(stderr, fmt, ap);
2053ce206308Smrg 			fputs("\n", stderr);
2054ce206308Smrg 		} else
2055ce206308Smrg 			vsyslog(LOG_INFO, fmt, ap);
20563e94b887Smartin 	}
2057ce206308Smrg 	va_end(ap);
2058ce206308Smrg }
2059ce206308Smrg 
2060ce206308Smrg void
2061881b8188Smrg bozoerr(bozohttpd_t *httpd, int code, const char *fmt, ...)
2062ce206308Smrg {
2063ce206308Smrg 	va_list ap;
2064ce206308Smrg 
2065ce206308Smrg 	va_start(ap, fmt);
20663e94b887Smartin 	if (!httpd->nolog) {
2067ce206308Smrg 		if (httpd->logstderr || isatty(STDERR_FILENO)) {
2068ce206308Smrg 			//fputs("error: ", stderr);
2069ce206308Smrg 			vfprintf(stderr, fmt, ap);
2070ce206308Smrg 			fputs("\n", stderr);
2071ce206308Smrg 		} else
2072ce206308Smrg 			vsyslog(LOG_ERR, fmt, ap);
20733e94b887Smartin 	}
2074ce206308Smrg 	va_end(ap);
2075ce206308Smrg 	exit(code);
207660dbe745Stls }
207760dbe745Stls 
2078f47ab3a3Schristos void
2079881b8188Smrg bozoasprintf(bozohttpd_t *httpd, char **str, const char *fmt, ...)
2080f47ab3a3Schristos {
2081f47ab3a3Schristos 	va_list ap;
2082f47ab3a3Schristos 	int e;
2083f47ab3a3Schristos 
2084f47ab3a3Schristos 	va_start(ap, fmt);
2085f47ab3a3Schristos 	e = vasprintf(str, fmt, ap);
2086f47ab3a3Schristos 	va_end(ap);
2087f47ab3a3Schristos 
2088f47ab3a3Schristos 	if (e < 0)
2089881b8188Smrg 		bozoerr(httpd, EXIT_FAILURE, "asprintf");
2090f47ab3a3Schristos }
2091f47ab3a3Schristos 
2092a4b84ca0Smrg /*
2093a4b84ca0Smrg  * this escapes HTML tags.  returns allocated escaped
2094a4b84ca0Smrg  * string if needed, or NULL on allocation failure or
2095a4b84ca0Smrg  * lack of escape need.
2096a4b84ca0Smrg  * call with NULL httpd in error paths, to avoid recursive
2097a4b84ca0Smrg  * malloc failure.  call with valid httpd in normal paths
2098a4b84ca0Smrg  * to get automatic allocation failure handling.
2099a4b84ca0Smrg  */
2100a4b84ca0Smrg char *
2101a4b84ca0Smrg bozo_escape_html(bozohttpd_t *httpd, const char *url)
210260dbe745Stls {
210360dbe745Stls 	int	i, j;
2104a4b84ca0Smrg 	char	*tmp;
2105a4b84ca0Smrg 	size_t	len;
210660dbe745Stls 
210760dbe745Stls 	for (i = 0, j = 0; url[i]; i++) {
210860dbe745Stls 		switch (url[i]) {
210960dbe745Stls 		case '<':
211060dbe745Stls 		case '>':
211160dbe745Stls 			j += 4;
211260dbe745Stls 			break;
211360dbe745Stls 		case '&':
211460dbe745Stls 			j += 5;
211560dbe745Stls 			break;
211634ece249Smrg 		case '"':
211734ece249Smrg 			j += 6;
211834ece249Smrg 			break;
211960dbe745Stls 		}
212060dbe745Stls 	}
212160dbe745Stls 
212260dbe745Stls 	if (j == 0)
2123a4b84ca0Smrg 		return NULL;
212460dbe745Stls 
212560dbe745Stls 	/*
2126a4b84ca0Smrg 	 * we need to handle being called from different
2127a4b84ca0Smrg 	 * pathnames.
212860dbe745Stls 	 */
2129a4b84ca0Smrg 	len = strlen(url) + j;
2130a4b84ca0Smrg 	if (httpd)
2131a4b84ca0Smrg 		tmp = bozomalloc(httpd, len);
2132a4b84ca0Smrg 	else if ((tmp = malloc(len)) == 0)
2133a4b84ca0Smrg 		return NULL;
213460dbe745Stls 
213560dbe745Stls 	for (i = 0, j = 0; url[i]; i++) {
213660dbe745Stls 		switch (url[i]) {
213760dbe745Stls 		case '<':
213860dbe745Stls 			memcpy(tmp + j, "&lt;", 4);
213960dbe745Stls 			j += 4;
214060dbe745Stls 			break;
214160dbe745Stls 		case '>':
214260dbe745Stls 			memcpy(tmp + j, "&gt;", 4);
214360dbe745Stls 			j += 4;
214460dbe745Stls 			break;
214560dbe745Stls 		case '&':
214660dbe745Stls 			memcpy(tmp + j, "&amp;", 5);
214760dbe745Stls 			j += 5;
214860dbe745Stls 			break;
214934ece249Smrg 		case '"':
215034ece249Smrg 			memcpy(tmp + j, "&quot;", 6);
215134ece249Smrg 			j += 6;
215234ece249Smrg 			break;
215360dbe745Stls 		default:
215460dbe745Stls 			tmp[j++] = url[i];
215560dbe745Stls 		}
215660dbe745Stls 	}
215760dbe745Stls 	tmp[j] = 0;
215860dbe745Stls 
2159a4b84ca0Smrg 	return tmp;
216060dbe745Stls }
216160dbe745Stls 
216260dbe745Stls /* short map between error code, and short/long messages */
216360dbe745Stls static struct errors_map {
216460dbe745Stls 	int	code;			/* HTTP return code */
216560dbe745Stls 	const char *shortmsg;		/* short version of message */
216660dbe745Stls 	const char *longmsg;		/* long version of message */
216760dbe745Stls } errors_map[] = {
2168c0b4b2d2Sjruoho 	{ 200,	"200 OK",		"The request was valid", },
216960dbe745Stls 	{ 400,	"400 Bad Request",	"The request was not valid", },
217060dbe745Stls 	{ 401,	"401 Unauthorized",	"No authorization", },
217160dbe745Stls 	{ 403,	"403 Forbidden",	"Access to this item has been denied",},
217260dbe745Stls 	{ 404, 	"404 Not Found",	"This item has not been found", },
217360dbe745Stls 	{ 408, 	"408 Request Timeout",	"This request took too long", },
21740ccc27dcSmrg 	{ 413, 	"413 Payload Too Large", "Use smaller requests", },
217560dbe745Stls 	{ 417,	"417 Expectation Failed","Expectations not available", },
217656ba1ad0Smrg 	{ 420,	"420 Enhance Your Calm","Chill, Winston", },
2177cbf5c65aSandvar 	{ 500,	"500 Internal Error",	"An error occurred on the server", },
217860dbe745Stls 	{ 501,	"501 Not Implemented",	"This request is not available", },
217960dbe745Stls 	{ 0,	NULL,			NULL, },
218060dbe745Stls };
218160dbe745Stls 
218260dbe745Stls static const char *help = "DANGER! WILL ROBINSON! DANGER!";
218360dbe745Stls 
218460dbe745Stls static const char *
218560dbe745Stls http_errors_short(int code)
218660dbe745Stls {
218760dbe745Stls 	struct errors_map *ep;
218860dbe745Stls 
218960dbe745Stls 	for (ep = errors_map; ep->code; ep++)
219060dbe745Stls 		if (ep->code == code)
219160dbe745Stls 			return (ep->shortmsg);
219260dbe745Stls 	return (help);
219360dbe745Stls }
219460dbe745Stls 
219560dbe745Stls static const char *
219660dbe745Stls http_errors_long(int code)
219760dbe745Stls {
219860dbe745Stls 	struct errors_map *ep;
219960dbe745Stls 
220060dbe745Stls 	for (ep = errors_map; ep->code; ep++)
220160dbe745Stls 		if (ep->code == code)
220260dbe745Stls 			return (ep->longmsg);
220360dbe745Stls 	return (help);
220460dbe745Stls }
220560dbe745Stls 
2206c0b4b2d2Sjruoho #ifndef NO_BLOCKLIST_SUPPORT
2207c0b4b2d2Sjruoho static struct blocklist *blstate;
2208c0b4b2d2Sjruoho 
2209c0b4b2d2Sjruoho void
2210c0b4b2d2Sjruoho pfilter_notify(const int what, const int code)
2211c0b4b2d2Sjruoho {
2212c0b4b2d2Sjruoho 
2213c0b4b2d2Sjruoho 	if (blstate == NULL)
2214c0b4b2d2Sjruoho 		blstate = blocklist_open();
2215c0b4b2d2Sjruoho 
2216c0b4b2d2Sjruoho 	if (blstate == NULL)
2217c0b4b2d2Sjruoho 		return;
2218c0b4b2d2Sjruoho 
2219c0b4b2d2Sjruoho 	(void)blocklist_r(blstate, what, 0, http_errors_short(code));
2220c0b4b2d2Sjruoho }
2221c0b4b2d2Sjruoho #endif /* !NO_BLOCKLIST_SUPPORT */
2222c0b4b2d2Sjruoho 
2223ce206308Smrg /* the follow functions and variables are used in handling HTTP errors */
2224ce206308Smrg int
2225ce206308Smrg bozo_http_error(bozohttpd_t *httpd, int code, bozo_httpreq_t *request,
2226ce206308Smrg 		const char *msg)
2227ce206308Smrg {
2228ce206308Smrg 	char portbuf[20];
2229ce206308Smrg 	const char *header = http_errors_short(code);
2230ce206308Smrg 	const char *reason = http_errors_long(code);
2231ce206308Smrg 	const char *proto = (request && request->hr_proto) ?
2232ce206308Smrg 				request->hr_proto : httpd->consts.http_11;
2233ce206308Smrg 	int	size;
2234afe55bf8Selric 	bozoheaders_t *hdr;
2235ce206308Smrg 
22364864410bSmrg 	USE_ARG(msg);
22374864410bSmrg 
2238ce206308Smrg 	debug((httpd, DEBUG_FAT, "bozo_http_error %d: %s", code, msg));
2239ce206308Smrg 	if (header == NULL || reason == NULL) {
2240881b8188Smrg 		bozoerr(httpd, 1,
2241ce206308Smrg 			"bozo_http_error() failed (short = %p, long = %p)",
2242ce206308Smrg 			header, reason);
2243ce206308Smrg 		return code;
2244ce206308Smrg 	}
2245ce206308Smrg 
2246ce206308Smrg 	if (request && request->hr_serverport &&
2247bf53dc23Smrg 	    strcmp(request->hr_serverport, BOZO_HTTP_PORT) != 0)
2248ce206308Smrg 		snprintf(portbuf, sizeof(portbuf), ":%s",
2249ce206308Smrg 				request->hr_serverport);
2250ce206308Smrg 	else
2251ce206308Smrg 		portbuf[0] = '\0';
2252ce206308Smrg 
2253ce206308Smrg 	if (request && request->hr_file) {
22543230a9a3Smrg 		char *file = NULL, *user = NULL;
22551932f694Schristos 		int file_alloc = 0;
225695e8de77Smrg 		const char *hostname = BOZOHOST(httpd, request);
2257a4b84ca0Smrg 
2258a4b84ca0Smrg 		/* bozo_escape_html() failure here is just too bad. */
2259a4b84ca0Smrg 		file = bozo_escape_html(NULL, request->hr_file);
2260a4b84ca0Smrg 		if (file == NULL)
2261a4b84ca0Smrg 			file = request->hr_file;
2262c4fe1facSshm 		else
2263c4fe1facSshm 			file_alloc = 1;
2264c4fe1facSshm 
2265c4fe1facSshm #ifndef NO_USER_SUPPORT
2266c4fe1facSshm 		if (request->hr_user != NULL) {
22673230a9a3Smrg 			char *user_escaped;
22683230a9a3Smrg 
2269c4fe1facSshm 			user_escaped = bozo_escape_html(NULL, request->hr_user);
2270c4fe1facSshm 			if (user_escaped == NULL)
2271c4fe1facSshm 				user_escaped = request->hr_user;
2272c4fe1facSshm 			/* expand username to ~user/ */
2273881b8188Smrg 			bozoasprintf(httpd, &user, "~%s/", user_escaped);
22741932f694Schristos 			if (user_escaped != request->hr_user)
2275c4fe1facSshm 				free(user_escaped);
2276c4fe1facSshm 		}
2277c4fe1facSshm #endif /* !NO_USER_SUPPORT */
2278c4fe1facSshm 
22793a698d51Smrg 		size = snprintf(httpd->errorbuf, BOZO_MINBUFSIZE,
2280ce206308Smrg 		    "<html><head><title>%s</title></head>\n"
2281ce206308Smrg 		    "<body><h1>%s</h1>\n"
2282c4fe1facSshm 		    "%s%s: <pre>%s</pre>\n"
2283501cede8Smaya  		    "<hr><address><a href=\"//%s%s/\">%s%s</a></address>\n"
2284ce206308Smrg 		    "</body></html>\n",
2285c4fe1facSshm 		    header, header,
2286c4fe1facSshm 		    user ? user : "", file,
2287c4fe1facSshm 		    reason, hostname, portbuf, hostname, portbuf);
22881932f694Schristos 		free(user);
22893a698d51Smrg 		if (size >= (int)BOZO_MINBUFSIZE) {
2290881b8188Smrg 			bozowarn(httpd,
2291ce206308Smrg 				"bozo_http_error buffer too small, truncated");
22923a698d51Smrg 			size = (int)BOZO_MINBUFSIZE;
2293ce206308Smrg 		}
2294c4fe1facSshm 
2295c4fe1facSshm 		if (file_alloc)
2296c4fe1facSshm 			free(file);
2297ce206308Smrg 	} else
2298ce206308Smrg 		size = 0;
2299ce206308Smrg 
2300ce206308Smrg 	bozo_printf(httpd, "%s %s\r\n", proto, header);
2301afe55bf8Selric 
2302afe55bf8Selric 	if (request) {
2303a07e0db3Smrg 		bozo_auth_check_401(request, code);
2304afe55bf8Selric 		SIMPLEQ_FOREACH(hdr, &request->hr_replheaders, h_next) {
2305afe55bf8Selric 			bozo_printf(httpd, "%s: %s\r\n", hdr->h_header,
2306afe55bf8Selric 					hdr->h_value);
2307afe55bf8Selric 		}
2308afe55bf8Selric 	}
2309ce206308Smrg 
2310ce206308Smrg 	bozo_printf(httpd, "Content-Type: text/html\r\n");
2311ce206308Smrg 	bozo_printf(httpd, "Content-Length: %d\r\n", size);
2312ce206308Smrg 	bozo_printf(httpd, "Server: %s\r\n", httpd->server_software);
2313ce206308Smrg 	if (request && request->hr_allow)
2314ce206308Smrg 		bozo_printf(httpd, "Allow: %s\r\n", request->hr_allow);
23153230a9a3Smrg 	/* RFC 7231 (HTTP/1.1) 6.5.7 */
23161f52a22cSleot 	if (code == 408 && request &&
23171f52a22cSleot 	    request->hr_proto == httpd->consts.http_11)
23183230a9a3Smrg 		bozo_printf(httpd, "Connection: close\r\n");
2319ce206308Smrg 	bozo_printf(httpd, "\r\n");
2320ca5b33a5Sshm 	/* According to the RFC 2616 sec. 9.4 HEAD method MUST NOT return a
2321ca5b33a5Sshm 	 * message-body in the response */
2322ca5b33a5Sshm 	if (size && request && request->hr_method != HTTP_HEAD)
2323ce206308Smrg 		bozo_printf(httpd, "%s", httpd->errorbuf);
2324ce206308Smrg 	bozo_flush(httpd, stdout);
2325ce206308Smrg 
2326c0b4b2d2Sjruoho #ifndef NO_BLOCKLIST_SUPPORT
2327c0b4b2d2Sjruoho 	switch(code) {
2328c0b4b2d2Sjruoho 
2329c0b4b2d2Sjruoho 	case 401:
2330c0b4b2d2Sjruoho 		pfilter_notify(BLOCKLIST_AUTH_FAIL, code);
2331c0b4b2d2Sjruoho 		break;
2332c0b4b2d2Sjruoho 
2333830b8c52Sjruoho 	case 403:
2334c0b4b2d2Sjruoho 		pfilter_notify(BLOCKLIST_ABUSIVE_BEHAVIOR, code);
2335c0b4b2d2Sjruoho 		break;
2336c0b4b2d2Sjruoho 	}
2337c0b4b2d2Sjruoho #endif /* !NO_BLOCKLIST_SUPPORT */
2338c0b4b2d2Sjruoho 
2339ce206308Smrg 	return code;
2340ce206308Smrg }
2341ce206308Smrg 
234260dbe745Stls /* Below are various modified libc functions */
234360dbe745Stls 
234460dbe745Stls /*
234560dbe745Stls  * returns -1 in lenp if the string ran out before finding a delimiter,
234660dbe745Stls  * but is otherwise the same as strsep.  Note that the length must be
234760dbe745Stls  * correctly passed in.
234860dbe745Stls  */
234960dbe745Stls char *
2350707281a2Smrg bozostrnsep(char **strp, const char *delim, ssize_t	*lenp)
235160dbe745Stls {
235260dbe745Stls 	char	*s;
235360dbe745Stls 	const	char *spanp;
235460dbe745Stls 	int	c, sc;
235560dbe745Stls 	char	*tok;
235660dbe745Stls 
235760dbe745Stls 	if ((s = *strp) == NULL)
235860dbe745Stls 		return (NULL);
235960dbe745Stls 	for (tok = s;;) {
236060dbe745Stls 		if (lenp && --(*lenp) == -1)
236160dbe745Stls 			return (NULL);
236260dbe745Stls 		c = *s++;
236360dbe745Stls 		spanp = delim;
236460dbe745Stls 		do {
236560dbe745Stls 			if ((sc = *spanp++) == c) {
236660dbe745Stls 				if (c == 0)
236760dbe745Stls 					s = NULL;
236860dbe745Stls 				else
236960dbe745Stls 					s[-1] = '\0';
237060dbe745Stls 				*strp = s;
237160dbe745Stls 				return (tok);
237260dbe745Stls 			}
237360dbe745Stls 		} while (sc != 0);
237460dbe745Stls 	}
237560dbe745Stls 	/* NOTREACHED */
237660dbe745Stls }
237760dbe745Stls 
237860dbe745Stls /*
237960dbe745Stls  * inspired by fgetln(3), but works for fd's.  should work identically
238060dbe745Stls  * except it, however, does *not* return the newline, and it does nul
238160dbe745Stls  * terminate the string.
238253df5022Smrg  *
238353df5022Smrg  * returns NULL if the line grows too large.  empty lines will be
238453df5022Smrg  * returned with *lenp set to 0.
238560dbe745Stls  */
238660dbe745Stls char *
2387ce206308Smrg bozodgetln(bozohttpd_t *httpd, int fd, ssize_t *lenp,
2388ce206308Smrg 	ssize_t (*readfn)(bozohttpd_t *, int, void *, size_t))
238960dbe745Stls {
239060dbe745Stls 	ssize_t	len;
239160dbe745Stls 	int	got_cr = 0;
239260dbe745Stls 	char	c, *nbuffer;
239360dbe745Stls 
239460dbe745Stls 	/* initialise */
2395ce206308Smrg 	if (httpd->getln_buflen == 0) {
2396ce206308Smrg 		/* should be plenty for most requests */
2397ce206308Smrg 		httpd->getln_buflen = 128;
239853df5022Smrg 		httpd->getln_buffer =
239953df5022Smrg 		    bozomalloc(httpd, (size_t)httpd->getln_buflen);
240060dbe745Stls 	}
240160dbe745Stls 	len = 0;
240260dbe745Stls 
240360dbe745Stls 	/*
240460dbe745Stls 	 * we *have* to read one byte at a time, to not break cgi
240560dbe745Stls 	 * programs (for we pass stdin off to them).  could fix this
240660dbe745Stls 	 * by becoming a fd-passing program instead of just exec'ing
240760dbe745Stls 	 * the program
2408a07e0db3Smrg 	 *
2409a07e0db3Smrg 	 * the above is no longer true, we are the fd-passing
2410a07e0db3Smrg 	 * program already.
241160dbe745Stls 	 */
2412ce206308Smrg 	for (; readfn(httpd, fd, &c, 1) == 1; ) {
2413ce206308Smrg 		debug((httpd, DEBUG_EXPLODING, "bozodgetln read %c", c));
241460dbe745Stls 
241553df5022Smrg 		if (httpd->getln_buflen > BOZO_HEADERS_MAX_SIZE)
241653df5022Smrg 			return NULL;
241753df5022Smrg 
2418ce206308Smrg 		if (len >= httpd->getln_buflen - 1) {
2419ce206308Smrg 			httpd->getln_buflen *= 2;
2420ce206308Smrg 			debug((httpd, DEBUG_EXPLODING, "bozodgetln: "
2421ce206308Smrg 				"reallocating buffer to buflen %zu",
2422ce206308Smrg 				httpd->getln_buflen));
2423ce206308Smrg 			nbuffer = bozorealloc(httpd, httpd->getln_buffer,
2424ce206308Smrg 				(size_t)httpd->getln_buflen);
2425ce206308Smrg 			httpd->getln_buffer = nbuffer;
242660dbe745Stls 		}
242760dbe745Stls 
2428ce206308Smrg 		httpd->getln_buffer[len++] = c;
242960dbe745Stls 		if (c == '\r') {
243060dbe745Stls 			got_cr = 1;
243160dbe745Stls 			continue;
243260dbe745Stls 		} else if (c == '\n') {
243360dbe745Stls 			/*
243460dbe745Stls 			 * HTTP/1.1 spec says to ignore CR and treat
243560dbe745Stls 			 * LF as the real line terminator.  even though
243660dbe745Stls 			 * the same spec defines CRLF as the line
243760dbe745Stls 			 * terminator, it is recommended in section 19.3
243860dbe745Stls 			 * to do the LF trick for tolerance.
243960dbe745Stls 			 */
244060dbe745Stls 			if (got_cr)
244160dbe745Stls 				len -= 2;
244260dbe745Stls 			else
244360dbe745Stls 				len -= 1;
244460dbe745Stls 			break;
244560dbe745Stls 		}
244660dbe745Stls 
244760dbe745Stls 	}
2448ce206308Smrg 	httpd->getln_buffer[len] = '\0';
24494cfb2183Smrg 	debug((httpd, DEBUG_OBESE, "bozodgetln returns: '%s' with len %zd",
2450ce206308Smrg 	       httpd->getln_buffer, len));
245160dbe745Stls 	*lenp = len;
2452ce206308Smrg 	return httpd->getln_buffer;
245360dbe745Stls }
245460dbe745Stls 
245512d8621dSmrg /*
245612d8621dSmrg  * allocation frontends with error handling.
245712d8621dSmrg  *
245812d8621dSmrg  * note that these may access members of the httpd and/or request.
245912d8621dSmrg  */
246060dbe745Stls void *
2461ce206308Smrg bozorealloc(bozohttpd_t *httpd, void *ptr, size_t size)
246260dbe745Stls {
246360dbe745Stls 	void	*p;
246460dbe745Stls 
246560dbe745Stls 	p = realloc(ptr, size);
2466cff2d956Smrg 	if (p)
2467cff2d956Smrg 		return p;
2468cff2d956Smrg 
24694cfb2183Smrg 	bozo_http_error(httpd, 500, NULL, "memory allocation failure");
2470cff2d956Smrg 	exit(EXIT_FAILURE);
247160dbe745Stls }
247260dbe745Stls 
247360dbe745Stls void *
2474ce206308Smrg bozomalloc(bozohttpd_t *httpd, size_t size)
247560dbe745Stls {
247660dbe745Stls 	void	*p;
247760dbe745Stls 
247860dbe745Stls 	p = malloc(size);
2479cff2d956Smrg 	if (p)
2480cff2d956Smrg 		return p;
2481cff2d956Smrg 
24824cfb2183Smrg 	bozo_http_error(httpd, 500, NULL, "memory allocation failure");
2483f47ab3a3Schristos 	exit(EXIT_FAILURE);
248403387632Smrg }
248560dbe745Stls 
248660dbe745Stls char *
2487cff2d956Smrg bozostrdup(bozohttpd_t *httpd, bozo_httpreq_t *request, const char *str)
248860dbe745Stls {
248960dbe745Stls 	char	*p;
249060dbe745Stls 
249160dbe745Stls 	p = strdup(str);
2492cff2d956Smrg 	if (p)
2493cff2d956Smrg 		return p;
2494cff2d956Smrg 
2495cff2d956Smrg 	if (!request)
2496881b8188Smrg 		bozoerr(httpd, EXIT_FAILURE, "strdup");
2497cff2d956Smrg 
24984cfb2183Smrg 	bozo_http_error(httpd, 500, request, "memory allocation failure");
2499f47ab3a3Schristos 	exit(EXIT_FAILURE);
250003387632Smrg }
2501ce206308Smrg 
2502ce206308Smrg /* set default values in bozohttpd_t struct */
2503ce206308Smrg int
2504ce206308Smrg bozo_init_httpd(bozohttpd_t *httpd)
2505ce206308Smrg {
2506ce206308Smrg 	/* make sure everything is clean */
2507ce206308Smrg 	(void) memset(httpd, 0x0, sizeof(*httpd));
2508ce206308Smrg 
2509ce206308Smrg 	/* constants */
2510ce206308Smrg 	httpd->consts.http_09 = "HTTP/0.9";
2511ce206308Smrg 	httpd->consts.http_10 = "HTTP/1.0";
2512ce206308Smrg 	httpd->consts.http_11 = "HTTP/1.1";
2513ce206308Smrg 	httpd->consts.text_plain = "text/plain";
2514ce206308Smrg 
2515ce206308Smrg 	/* mmap region size */
2516ce206308Smrg 	httpd->mmapsz = BOZO_MMAPSZ;
2517ce206308Smrg 
2518ce206308Smrg 	/* error buffer for bozo_http_error() */
25193a698d51Smrg 	if ((httpd->errorbuf = malloc(BOZO_MINBUFSIZE)) == NULL) {
25204cfb2183Smrg 		fprintf(stderr,
2521ce206308Smrg 			"bozohttpd: memory_allocation failure\n");
2522ce206308Smrg 		return 0;
2523ce206308Smrg 	}
2524cb23152cSmbalmer #ifndef NO_LUA_SUPPORT
2525cb23152cSmbalmer 	SIMPLEQ_INIT(&httpd->lua_states);
2526cb23152cSmbalmer #endif
2527ce206308Smrg 	return 1;
2528ce206308Smrg }
2529ce206308Smrg 
2530ce206308Smrg /* set default values in bozoprefs_t struct */
2531ce206308Smrg int
2532cff2d956Smrg bozo_init_prefs(bozohttpd_t *httpd, bozoprefs_t *prefs)
2533ce206308Smrg {
25349f988576Smrg 	int rv = 1;
25353230a9a3Smrg 
2536ce206308Smrg 	/* make sure everything is clean */
2537ce206308Smrg 	(void) memset(prefs, 0x0, sizeof(*prefs));
2538ce206308Smrg 
2539ce206308Smrg 	/* set up default values */
25403230a9a3Smrg 	if (!bozo_set_pref(httpd, prefs, "server software", SERVER_SOFTWARE))
25419f988576Smrg 		rv = 0;
25423230a9a3Smrg 	if (!bozo_set_pref(httpd, prefs, "index.html", INDEX_HTML))
25439f988576Smrg 		rv = 0;
25443230a9a3Smrg 	if (!bozo_set_pref(httpd, prefs, "public_html", PUBLIC_HTML))
25459f988576Smrg 		rv = 0;
254608dbfa23Smrg 	if (!bozo_set_pref(httpd, prefs, "ssl timeout", SSL_TIMEOUT))
25479f988576Smrg 		rv = 0;
25483230a9a3Smrg 	if (!bozo_set_pref(httpd, prefs, "initial timeout", INITIAL_TIMEOUT))
25499f988576Smrg 		rv = 0;
25503230a9a3Smrg 	if (!bozo_set_pref(httpd, prefs, "header timeout", HEADER_WAIT_TIME))
25519f988576Smrg 		rv = 0;
25523230a9a3Smrg 	if (!bozo_set_pref(httpd, prefs, "request timeout", TOTAL_MAX_REQ_TIME))
25539f988576Smrg 		rv = 0;
2554ce206308Smrg 
25553230a9a3Smrg 	return rv;
2556ce206308Smrg }
2557ce206308Smrg 
2558ce206308Smrg /* set default values */
2559ce206308Smrg int
2560ce206308Smrg bozo_set_defaults(bozohttpd_t *httpd, bozoprefs_t *prefs)
2561ce206308Smrg {
2562cff2d956Smrg 	return bozo_init_httpd(httpd) && bozo_init_prefs(httpd, prefs);
2563ce206308Smrg }
2564ce206308Smrg 
2565ce206308Smrg /* set the virtual host name, port and root */
2566ce206308Smrg int
2567ce206308Smrg bozo_setup(bozohttpd_t *httpd, bozoprefs_t *prefs, const char *vhost,
2568ce206308Smrg 		const char *root)
2569ce206308Smrg {
2570ce206308Smrg 	struct passwd	 *pw;
2571ce206308Smrg 	extern char	**environ;
257230539536Smrg 	static char	 *cleanenv[1] = { NULL };
2573ce206308Smrg 	uid_t		  uid;
25749d18868aSmrg 	int		  uidset = 0;
2575ce206308Smrg 	char		 *chrootdir;
2576ce206308Smrg 	char		 *username;
2577ce206308Smrg 	char		 *portnum;
2578ce206308Smrg 	char		 *cp;
2579ce206308Smrg 	int		  dirtyenv;
2580ce206308Smrg 
2581ce206308Smrg 	dirtyenv = 0;
2582ce206308Smrg 
2583ce206308Smrg 	if (vhost == NULL) {
2584ce206308Smrg 		httpd->virthostname = bozomalloc(httpd, MAXHOSTNAMELEN+1);
2585ce206308Smrg 		if (gethostname(httpd->virthostname, MAXHOSTNAMELEN+1) < 0)
2586881b8188Smrg 			bozoerr(httpd, 1, "gethostname");
2587ce206308Smrg 		httpd->virthostname[MAXHOSTNAMELEN] = '\0';
2588ce206308Smrg 	} else {
2589cff2d956Smrg 		httpd->virthostname = bozostrdup(httpd, NULL, vhost);
2590ce206308Smrg 	}
2591cff2d956Smrg 	httpd->slashdir = bozostrdup(httpd, NULL, root);
2592ce206308Smrg 	if ((portnum = bozo_get_pref(prefs, "port number")) != NULL) {
2593cff2d956Smrg 		httpd->bindport = bozostrdup(httpd, NULL, portnum);
2594ce206308Smrg 	}
2595ce206308Smrg 
2596ce206308Smrg 	/* go over preferences now */
2597ce206308Smrg 	if ((cp = bozo_get_pref(prefs, "numeric")) != NULL &&
2598ce206308Smrg 	    strcmp(cp, "true") == 0) {
2599ce206308Smrg 		httpd->numeric = 1;
2600ce206308Smrg 	}
2601ce206308Smrg 	if ((cp = bozo_get_pref(prefs, "log to stderr")) != NULL &&
2602ce206308Smrg 	    strcmp(cp, "true") == 0) {
2603ce206308Smrg 		httpd->logstderr = 1;
2604ce206308Smrg 	}
26053e94b887Smartin 	if ((cp = bozo_get_pref(prefs, "no log")) != NULL &&
26063e94b887Smartin 	    strcmp(cp, "true") == 0) {
26073e94b887Smartin 		httpd->nolog = 1;
26083e94b887Smartin 	}
2609ce206308Smrg 	if ((cp = bozo_get_pref(prefs, "bind address")) != NULL) {
2610cff2d956Smrg 		httpd->bindaddress = bozostrdup(httpd, NULL, cp);
2611ce206308Smrg 	}
2612ce206308Smrg 	if ((cp = bozo_get_pref(prefs, "background")) != NULL) {
2613ce206308Smrg 		httpd->background = atoi(cp);
2614ce206308Smrg 	}
2615ce206308Smrg 	if ((cp = bozo_get_pref(prefs, "foreground")) != NULL &&
2616ce206308Smrg 	    strcmp(cp, "true") == 0) {
2617ce206308Smrg 		httpd->foreground = 1;
2618ce206308Smrg 	}
261983bb4389Sjmmv 	if ((cp = bozo_get_pref(prefs, "pid file")) != NULL) {
2620cff2d956Smrg 		httpd->pidfile = bozostrdup(httpd, NULL, cp);
262183bb4389Sjmmv 	}
2622ce206308Smrg 	if ((cp = bozo_get_pref(prefs, "unknown slash")) != NULL &&
2623ce206308Smrg 	    strcmp(cp, "true") == 0) {
2624ce206308Smrg 		httpd->unknown_slash = 1;
2625ce206308Smrg 	}
2626ce206308Smrg 	if ((cp = bozo_get_pref(prefs, "virtual base")) != NULL) {
2627cff2d956Smrg 		httpd->virtbase = bozostrdup(httpd, NULL, cp);
2628ce206308Smrg 	}
2629ce206308Smrg 	if ((cp = bozo_get_pref(prefs, "enable users")) != NULL &&
2630ce206308Smrg 	    strcmp(cp, "true") == 0) {
2631ce206308Smrg 		httpd->enable_users = 1;
2632ce206308Smrg 	}
2633c4fe1facSshm 	if ((cp = bozo_get_pref(prefs, "enable user cgibin")) != NULL &&
2634c4fe1facSshm 	    strcmp(cp, "true") == 0) {
2635c4fe1facSshm 		httpd->enable_cgi_users = 1;
2636c4fe1facSshm 	}
2637ce206308Smrg 	if ((cp = bozo_get_pref(prefs, "dirty environment")) != NULL &&
2638ce206308Smrg 	    strcmp(cp, "true") == 0) {
2639ce206308Smrg 		dirtyenv = 1;
2640ce206308Smrg 	}
2641ce206308Smrg 	if ((cp = bozo_get_pref(prefs, "hide dots")) != NULL &&
2642ce206308Smrg 	    strcmp(cp, "true") == 0) {
2643ce206308Smrg 		httpd->hide_dots = 1;
2644ce206308Smrg 	}
2645ce206308Smrg 	if ((cp = bozo_get_pref(prefs, "directory indexing")) != NULL &&
2646ce206308Smrg 	    strcmp(cp, "true") == 0) {
2647ce206308Smrg 		httpd->dir_indexing = 1;
2648ce206308Smrg 	}
2649026e4ac0Sjmcneill 	if ((cp = bozo_get_pref(prefs, "directory index readme")) != NULL) {
2650026e4ac0Sjmcneill 		httpd->dir_readme = bozostrdup(httpd, NULL, cp);
2651026e4ac0Sjmcneill 	}
2652aeb27ed4Smrg 	if ((cp = bozo_get_pref(prefs, "public_html")) != NULL) {
2653cff2d956Smrg 		httpd->public_html = bozostrdup(httpd, NULL, cp);
2654aeb27ed4Smrg 	}
265508dbfa23Smrg 	if ((cp = bozo_get_pref(prefs, "ssl timeout")) != NULL) {
265608dbfa23Smrg 		httpd->ssl_timeout = atoi(cp);
265708dbfa23Smrg 	}
26583230a9a3Smrg 	if ((cp = bozo_get_pref(prefs, "initial timeout")) != NULL) {
26593230a9a3Smrg 		httpd->initial_timeout = atoi(cp);
26603230a9a3Smrg 	}
26613230a9a3Smrg 	if ((cp = bozo_get_pref(prefs, "header timeout")) != NULL) {
26623230a9a3Smrg 		httpd->header_timeout = atoi(cp);
26633230a9a3Smrg 	}
26643230a9a3Smrg 	if ((cp = bozo_get_pref(prefs, "request timeout")) != NULL) {
26653230a9a3Smrg 		httpd->request_timeout = atoi(cp);
26663230a9a3Smrg 	}
2667ce206308Smrg 	httpd->server_software =
2668cff2d956Smrg 	    bozostrdup(httpd, NULL, bozo_get_pref(prefs, "server software"));
2669f47ab3a3Schristos 	httpd->index_html =
2670cff2d956Smrg 	    bozostrdup(httpd, NULL, bozo_get_pref(prefs, "index.html"));
2671ce206308Smrg 
2672ce206308Smrg 	/*
2673ce206308Smrg 	 * initialise ssl and daemon mode if necessary.
2674ce206308Smrg 	 */
2675ce206308Smrg 	bozo_ssl_init(httpd);
2676ce206308Smrg 	bozo_daemon_init(httpd);
2677ce206308Smrg 
2678c2e98309Smrg 	username = bozo_get_pref(prefs, "username");
2679c2e98309Smrg 	if (username != NULL) {
2680c2e98309Smrg 		if ((pw = getpwnam(username)) == NULL)
2681c2e98309Smrg 			bozoerr(httpd, 1, "getpwnam(%s): %s", username,
2682ce206308Smrg 				strerror(errno));
2683ce206308Smrg 		if (initgroups(pw->pw_name, pw->pw_gid) == -1)
2684881b8188Smrg 			bozoerr(httpd, 1, "initgroups: %s", strerror(errno));
2685ce206308Smrg 		if (setgid(pw->pw_gid) == -1)
2686881b8188Smrg 			bozoerr(httpd, 1, "setgid(%u): %s", pw->pw_gid,
2687ce206308Smrg 				strerror(errno));
2688ce206308Smrg 		uid = pw->pw_uid;
26899d18868aSmrg 		uidset = 1;
2690ce206308Smrg 	}
2691ce206308Smrg 	/*
2692ce206308Smrg 	 * handle chroot.
2693ce206308Smrg 	 */
2694ce206308Smrg 	if ((chrootdir = bozo_get_pref(prefs, "chroot dir")) != NULL) {
2695cff2d956Smrg 		httpd->rootdir = bozostrdup(httpd, NULL, chrootdir);
2696ce206308Smrg 		if (chdir(httpd->rootdir) == -1)
2697881b8188Smrg 			bozoerr(httpd, 1, "chdir(%s): %s", httpd->rootdir,
2698ce206308Smrg 				strerror(errno));
2699ce206308Smrg 		if (chroot(httpd->rootdir) == -1)
2700881b8188Smrg 			bozoerr(httpd, 1, "chroot(%s): %s", httpd->rootdir,
2701ce206308Smrg 				strerror(errno));
2702ce206308Smrg 	}
2703ce206308Smrg 
27049d18868aSmrg 	if (uidset && setuid(uid) == -1)
2705c2e98309Smrg 		bozoerr(httpd, 1, "setuid(%d): %s", uid, strerror(errno));
2706ce206308Smrg 
2707ce206308Smrg 	/*
2708ce206308Smrg 	 * prevent info leakage between different compartments.
2709ce206308Smrg 	 * some PATH values in the environment would be invalided
2710ce206308Smrg 	 * by chroot. cross-user settings might result in undesirable
2711ce206308Smrg 	 * effects.
2712ce206308Smrg 	 */
271330539536Smrg 	if ((chrootdir != NULL || username != NULL) && !dirtyenv)
2714ce206308Smrg 		environ = cleanenv;
271530539536Smrg 
2716ce206308Smrg #ifdef _SC_PAGESIZE
2717ce206308Smrg 	httpd->page_size = (long)sysconf(_SC_PAGESIZE);
2718ce206308Smrg #else
2719ce206308Smrg 	httpd->page_size = 4096;
2720ce206308Smrg #endif
2721ce206308Smrg 	debug((httpd, DEBUG_OBESE, "myname is %s, slashdir is %s",
2722ce206308Smrg 			httpd->virthostname, httpd->slashdir));
2723ce206308Smrg 
2724ce206308Smrg 	return 1;
2725ce206308Smrg }
272620563328Sagc 
2727b0f74aaaSmrg void
2728b0f74aaaSmrg bozo_cleanup(bozohttpd_t *httpd, bozoprefs_t *prefs)
2729b0f74aaaSmrg {
2730080a4ce9Smrg 	bozo_clear_prefs(prefs);
2731b0f74aaaSmrg 
2732b0f74aaaSmrg 	free(httpd->virthostname);
2733b0f74aaaSmrg 	free(httpd->errorbuf);
2734b0f74aaaSmrg 	free(httpd->getln_buffer);
2735b0f74aaaSmrg 	free(httpd->slashdir);
27367c063803Sshm 	free(httpd->bindport);
27377c063803Sshm 	free(httpd->pidfile);
27387c063803Sshm 	free(httpd->cgibin);
27397c063803Sshm 	free(httpd->virtbase);
27407c063803Sshm 	free(httpd->dynamic_content_map);
2741b0f74aaaSmrg #define bozo_unconst(x) ((void *)(uintptr_t)x)
2742b0f74aaaSmrg 	free(bozo_unconst(httpd->server_software));
2743b0f74aaaSmrg 	free(bozo_unconst(httpd->index_html));
2744b0f74aaaSmrg 	free(bozo_unconst(httpd->dir_readme));
2745b0f74aaaSmrg 	free(bozo_unconst(httpd->public_html));
2746b0f74aaaSmrg #undef bozo_unconst
2747b0f74aaaSmrg }
2748b0f74aaaSmrg 
274920563328Sagc int
275020563328Sagc bozo_get_version(char *buf, size_t size)
275120563328Sagc {
275220563328Sagc 	return snprintf(buf, size, "%s", SERVER_SOFTWARE);
275320563328Sagc }
2754