xref: /openbsd/usr.sbin/httpd/server_http.c (revision 95d80e7a)
1*95d80e7aSjung /*	$OpenBSD: server_http.c,v 1.107 2016/05/22 19:20:03 jung Exp $	*/
2b7b6a941Sreyk 
3b7b6a941Sreyk /*
405c2c945Sreyk  * Copyright (c) 2006 - 2015 Reyk Floeter <reyk@openbsd.org>
5b7b6a941Sreyk  *
6b7b6a941Sreyk  * Permission to use, copy, modify, and distribute this software for any
7b7b6a941Sreyk  * purpose with or without fee is hereby granted, provided that the above
8b7b6a941Sreyk  * copyright notice and this permission notice appear in all copies.
9b7b6a941Sreyk  *
10b7b6a941Sreyk  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11b7b6a941Sreyk  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12b7b6a941Sreyk  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13b7b6a941Sreyk  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14b7b6a941Sreyk  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15b7b6a941Sreyk  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16b7b6a941Sreyk  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17b7b6a941Sreyk  */
18b7b6a941Sreyk 
19b7b6a941Sreyk #include <sys/types.h>
20b7b6a941Sreyk #include <sys/queue.h>
21b7b6a941Sreyk #include <sys/socket.h>
22b7b6a941Sreyk #include <sys/tree.h>
23b7b6a941Sreyk 
24b7b6a941Sreyk #include <netinet/in.h>
2586f952e4Sreyk #include <arpa/inet.h>
26b7b6a941Sreyk 
27b7b6a941Sreyk #include <errno.h>
28b7b6a941Sreyk #include <stdlib.h>
29b7b6a941Sreyk #include <string.h>
30b7b6a941Sreyk #include <unistd.h>
31b9fc9a72Sderaadt #include <limits.h>
3259355b5aSreyk #include <fnmatch.h>
33b7b6a941Sreyk #include <stdio.h>
3486f952e4Sreyk #include <time.h>
35e286121aSflorian #include <resolv.h>
36b7b6a941Sreyk #include <event.h>
3759355b5aSreyk #include <ctype.h>
38e3c03affSreyk #include <vis.h>
39b7b6a941Sreyk 
40b7b6a941Sreyk #include "httpd.h"
41b7b6a941Sreyk #include "http.h"
4259355b5aSreyk #include "patterns.h"
43b7b6a941Sreyk 
44b7b6a941Sreyk static int	 server_httpmethod_cmp(const void *, const void *);
45b7b6a941Sreyk static int	 server_httperror_cmp(const void *, const void *);
46b7b6a941Sreyk void		 server_httpdesc_free(struct http_descriptor *);
47e286121aSflorian int		 server_http_authenticate(struct server_config *,
48e286121aSflorian 		    struct client *);
49586dade4Sreyk char		*server_expand_http(struct client *, const char *,
50586dade4Sreyk 		    char *, size_t);
51b7b6a941Sreyk 
52b7b6a941Sreyk static struct httpd	*env = NULL;
53b7b6a941Sreyk 
54b7b6a941Sreyk static struct http_method	 http_methods[] = HTTP_METHODS;
55b7b6a941Sreyk static struct http_error	 http_errors[] = HTTP_ERRORS;
56b7b6a941Sreyk 
57b7b6a941Sreyk void
58b7b6a941Sreyk server_http(struct httpd *x_env)
59b7b6a941Sreyk {
60b7b6a941Sreyk 	if (x_env != NULL)
61b7b6a941Sreyk 		env = x_env;
62b7b6a941Sreyk 
63b7b6a941Sreyk 	DPRINTF("%s: sorting lookup tables, pid %d", __func__, getpid());
64b7b6a941Sreyk 
65b7b6a941Sreyk 	/* Sort the HTTP lookup arrays */
66b7b6a941Sreyk 	qsort(http_methods, sizeof(http_methods) /
67b7b6a941Sreyk 	    sizeof(http_methods[0]) - 1,
68b7b6a941Sreyk 	    sizeof(http_methods[0]), server_httpmethod_cmp);
69b7b6a941Sreyk 	qsort(http_errors, sizeof(http_errors) /
70b7b6a941Sreyk 	    sizeof(http_errors[0]) - 1,
71b7b6a941Sreyk 	    sizeof(http_errors[0]), server_httperror_cmp);
72b7b6a941Sreyk }
73b7b6a941Sreyk 
74b7b6a941Sreyk void
75b7b6a941Sreyk server_http_init(struct server *srv)
76b7b6a941Sreyk {
77b7b6a941Sreyk 	/* nothing */
78b7b6a941Sreyk }
79b7b6a941Sreyk 
80b7b6a941Sreyk int
81b7b6a941Sreyk server_httpdesc_init(struct client *clt)
82b7b6a941Sreyk {
83b7b6a941Sreyk 	struct http_descriptor	*desc;
84b7b6a941Sreyk 
85b7b6a941Sreyk 	if ((desc = calloc(1, sizeof(*desc))) == NULL)
86b7b6a941Sreyk 		return (-1);
87b7b6a941Sreyk 	RB_INIT(&desc->http_headers);
88d08e4976Sreyk 	clt->clt_descreq = desc;
89d08e4976Sreyk 
90d08e4976Sreyk 	if ((desc = calloc(1, sizeof(*desc))) == NULL) {
91d08e4976Sreyk 		/* req will be cleaned up later */
92d08e4976Sreyk 		return (-1);
93d08e4976Sreyk 	}
94d08e4976Sreyk 	RB_INIT(&desc->http_headers);
95d08e4976Sreyk 	clt->clt_descresp = desc;
96b7b6a941Sreyk 
97b7b6a941Sreyk 	return (0);
98b7b6a941Sreyk }
99b7b6a941Sreyk 
100b7b6a941Sreyk void
101b7b6a941Sreyk server_httpdesc_free(struct http_descriptor *desc)
102b7b6a941Sreyk {
103d08e4976Sreyk 	if (desc == NULL)
104d08e4976Sreyk 		return;
1053fe25232Sreyk 
106b7b6a941Sreyk 	free(desc->http_path);
107b7b6a941Sreyk 	desc->http_path = NULL;
108de6550b1Sreyk 	free(desc->http_path_alias);
109de6550b1Sreyk 	desc->http_path_alias = NULL;
110b7b6a941Sreyk 	free(desc->http_query);
111b7b6a941Sreyk 	desc->http_query = NULL;
112b7b6a941Sreyk 	free(desc->http_version);
113b7b6a941Sreyk 	desc->http_version = NULL;
11447dc2a9dSreyk 	free(desc->http_host);
11547dc2a9dSreyk 	desc->http_host = NULL;
1163fe25232Sreyk 
117b7b6a941Sreyk 	kv_purge(&desc->http_headers);
11844ed0680Sreyk 	desc->http_lastheader = NULL;
119d08e4976Sreyk 	desc->http_method = 0;
120d08e4976Sreyk 	desc->http_chunked = 0;
121b7b6a941Sreyk }
122b7b6a941Sreyk 
123e286121aSflorian int
124e286121aSflorian server_http_authenticate(struct server_config *srv_conf, struct client *clt)
125e286121aSflorian {
1267d6098fdSreyk 	char			 decoded[1024];
127e286121aSflorian 	FILE			*fp = NULL;
128e286121aSflorian 	struct http_descriptor	*desc = clt->clt_descreq;
1291413cd80Sreyk 	const struct auth	*auth = srv_conf->auth;
130e286121aSflorian 	struct kv		*ba, key;
131e286121aSflorian 	size_t			 linesize = 0;
132e286121aSflorian 	ssize_t			 linelen;
133e286121aSflorian 	int			 ret = -1;
1347d6098fdSreyk 	char			*line = NULL, *user = NULL, *pass = NULL;
1357d6098fdSreyk 	char			*clt_user = NULL, *clt_pass = NULL;
136e286121aSflorian 
137e286121aSflorian 	memset(decoded, 0, sizeof(decoded));
138e286121aSflorian 	key.kv_key = "Authorization";
139e286121aSflorian 
140e286121aSflorian 	if ((ba = kv_find(&desc->http_headers, &key)) == NULL ||
141e286121aSflorian 	    ba->kv_value == NULL)
142e286121aSflorian 		goto done;
143e286121aSflorian 
144e286121aSflorian 	if (strncmp(ba->kv_value, "Basic ", strlen("Basic ")) != 0)
145e286121aSflorian 		goto done;
146e286121aSflorian 
1474703e0faSreyk 	if (b64_pton(strchr(ba->kv_value, ' ') + 1, (uint8_t *)decoded,
148e286121aSflorian 	    sizeof(decoded)) <= 0)
149e286121aSflorian 		goto done;
150e286121aSflorian 
151e286121aSflorian 	if ((clt_pass = strchr(decoded, ':')) == NULL)
152e286121aSflorian 		goto done;
153e286121aSflorian 
154e286121aSflorian 	clt_user = decoded;
155e286121aSflorian 	*clt_pass++ = '\0';
156d22ad799Sflorian 	if ((clt->clt_remote_user = strdup(clt_user)) == NULL)
157d22ad799Sflorian 		goto done;
158e286121aSflorian 
159e286121aSflorian 	if (clt_pass == NULL)
160e286121aSflorian 		goto done;
161e286121aSflorian 
162602531d9Sreyk 	if ((fp = fopen(auth->auth_htpasswd, "r")) == NULL)
163e286121aSflorian 		goto done;
164e286121aSflorian 
165e286121aSflorian 	while ((linelen = getline(&line, &linesize, fp)) != -1) {
166e286121aSflorian 		if (line[linelen - 1] == '\n')
167e286121aSflorian 			line[linelen - 1] = '\0';
168e286121aSflorian 		user = line;
169e286121aSflorian 		pass = strchr(line, ':');
170e286121aSflorian 
171e286121aSflorian 		if (pass == NULL) {
172e286121aSflorian 			explicit_bzero(line, linelen);
173e286121aSflorian 			continue;
174e286121aSflorian 		}
175e286121aSflorian 
176e286121aSflorian 		*pass++ = '\0';
177e286121aSflorian 
178e286121aSflorian 		if (strcmp(clt_user, user) != 0) {
179e286121aSflorian 			explicit_bzero(line, linelen);
180e286121aSflorian 			continue;
181e286121aSflorian 		}
182e286121aSflorian 
183e286121aSflorian 		if (crypt_checkpass(clt_pass, pass) == 0) {
184e286121aSflorian 			explicit_bzero(line, linelen);
185e286121aSflorian 			ret = 0;
186e286121aSflorian 			break;
187e286121aSflorian 		}
188e286121aSflorian 	}
189e286121aSflorian done:
1903ca71284Ssunil 	free(line);
191e286121aSflorian 	if (fp != NULL)
192e286121aSflorian 		fclose(fp);
193e286121aSflorian 
194e286121aSflorian 	if (ba != NULL && ba->kv_value != NULL) {
195e286121aSflorian 		explicit_bzero(ba->kv_value, strlen(ba->kv_value));
196e286121aSflorian 		explicit_bzero(decoded, sizeof(decoded));
197e286121aSflorian 	}
198e286121aSflorian 
199e286121aSflorian 	return (ret);
200e286121aSflorian }
201e286121aSflorian 
202b7b6a941Sreyk void
203b7b6a941Sreyk server_read_http(struct bufferevent *bev, void *arg)
204b7b6a941Sreyk {
205b7b6a941Sreyk 	struct client		*clt = arg;
2064dd188b5Sreyk 	struct server_config	*srv_conf = clt->clt_srv_conf;
207d08e4976Sreyk 	struct http_descriptor	*desc = clt->clt_descreq;
208b7b6a941Sreyk 	struct evbuffer		*src = EVBUFFER_INPUT(bev);
209b7b6a941Sreyk 	char			*line = NULL, *key, *value;
210b7b6a941Sreyk 	const char		*errstr;
211b7b6a941Sreyk 	size_t			 size, linelen;
212b7b6a941Sreyk 	struct kv		*hdr = NULL;
213b7b6a941Sreyk 
214b7b6a941Sreyk 	getmonotime(&clt->clt_tv_last);
215b7b6a941Sreyk 
216b7b6a941Sreyk 	size = EVBUFFER_LENGTH(src);
217b7b6a941Sreyk 	DPRINTF("%s: session %d: size %lu, to read %lld",
218b7b6a941Sreyk 	    __func__, clt->clt_id, size, clt->clt_toread);
219b7b6a941Sreyk 	if (!size) {
220b7b6a941Sreyk 		clt->clt_toread = TOREAD_HTTP_HEADER;
221b7b6a941Sreyk 		goto done;
222b7b6a941Sreyk 	}
223b7b6a941Sreyk 
2248757e0ccSjsg 	while (!clt->clt_done && (line =
2258757e0ccSjsg 	    evbuffer_readln(src, NULL, EVBUFFER_EOL_CRLF_STRICT)) != NULL) {
226b7b6a941Sreyk 		linelen = strlen(line);
227b7b6a941Sreyk 
228b7b6a941Sreyk 		/*
229b7b6a941Sreyk 		 * An empty line indicates the end of the request.
230b7b6a941Sreyk 		 * libevent already stripped the \r\n for us.
231b7b6a941Sreyk 		 */
232b7b6a941Sreyk 		if (!linelen) {
233b7b6a941Sreyk 			clt->clt_done = 1;
234b7b6a941Sreyk 			free(line);
235b7b6a941Sreyk 			break;
236b7b6a941Sreyk 		}
237b7b6a941Sreyk 		key = line;
238b7b6a941Sreyk 
239b7b6a941Sreyk 		/* Limit the total header length minus \r\n */
240b7b6a941Sreyk 		clt->clt_headerlen += linelen;
241b7b6a941Sreyk 		if (clt->clt_headerlen > SERVER_MAXHEADERLENGTH) {
242b7b6a941Sreyk 			server_abort_http(clt, 413, "request too large");
2430859b34fSreyk 			goto abort;
244b7b6a941Sreyk 		}
245b7b6a941Sreyk 
246b7b6a941Sreyk 		/*
247b7b6a941Sreyk 		 * The first line is the GET/POST/PUT/... request,
248b7b6a941Sreyk 		 * subsequent lines are HTTP headers.
249b7b6a941Sreyk 		 */
250b7b6a941Sreyk 		if (++clt->clt_line == 1)
251b7b6a941Sreyk 			value = strchr(key, ' ');
252b7b6a941Sreyk 		else if (*key == ' ' || *key == '\t')
253b7b6a941Sreyk 			/* Multiline headers wrap with a space or tab */
254b7b6a941Sreyk 			value = NULL;
255b7b6a941Sreyk 		else
256b7b6a941Sreyk 			value = strchr(key, ':');
257b7b6a941Sreyk 		if (value == NULL) {
258b7b6a941Sreyk 			if (clt->clt_line == 1) {
259b7b6a941Sreyk 				server_abort_http(clt, 400, "malformed");
26003fd4389Sreyk 				goto abort;
261b7b6a941Sreyk 			}
262b7b6a941Sreyk 
263b7b6a941Sreyk 			/* Append line to the last header, if present */
264b7b6a941Sreyk 			if (kv_extend(&desc->http_headers,
26503fd4389Sreyk 			    desc->http_lastheader, line) == NULL)
266b7b6a941Sreyk 				goto fail;
267b7b6a941Sreyk 
268b7b6a941Sreyk 			free(line);
269b7b6a941Sreyk 			continue;
270b7b6a941Sreyk 		}
271b7b6a941Sreyk 		if (*value == ':') {
272b7b6a941Sreyk 			*value++ = '\0';
273b7b6a941Sreyk 			value += strspn(value, " \t\r\n");
274b7b6a941Sreyk 		} else {
275b7b6a941Sreyk 			*value++ = '\0';
276b7b6a941Sreyk 		}
277b7b6a941Sreyk 
278b7b6a941Sreyk 		DPRINTF("%s: session %d: header '%s: %s'", __func__,
279b7b6a941Sreyk 		    clt->clt_id, key, value);
280b7b6a941Sreyk 
281b7b6a941Sreyk 		/*
282b7b6a941Sreyk 		 * Identify and handle specific HTTP request methods
283b7b6a941Sreyk 		 */
284b7b6a941Sreyk 		if (clt->clt_line == 1) {
285b7b6a941Sreyk 			if ((desc->http_method = server_httpmethod_byname(key))
286fc062027Sreyk 			    == HTTP_METHOD_NONE) {
287fc062027Sreyk 				server_abort_http(clt, 400, "malformed");
288fc062027Sreyk 				goto abort;
289fc062027Sreyk 			}
290b7b6a941Sreyk 
291b7b6a941Sreyk 			/*
292b7b6a941Sreyk 			 * Decode request path and query
293b7b6a941Sreyk 			 */
294b7b6a941Sreyk 			desc->http_path = strdup(value);
29503fd4389Sreyk 			if (desc->http_path == NULL)
296b7b6a941Sreyk 				goto fail;
29703fd4389Sreyk 
298b7b6a941Sreyk 			desc->http_version = strchr(desc->http_path, ' ');
29903fd4389Sreyk 			if (desc->http_version == NULL)
3002e2bd5b6Sreyk 				goto fail;
30103fd4389Sreyk 
302b7b6a941Sreyk 			*desc->http_version++ = '\0';
303b7b6a941Sreyk 			desc->http_query = strchr(desc->http_path, '?');
304b7b6a941Sreyk 			if (desc->http_query != NULL)
305b7b6a941Sreyk 				*desc->http_query++ = '\0';
306b7b6a941Sreyk 
307b7b6a941Sreyk 			/*
308b7b6a941Sreyk 			 * Have to allocate the strings because they could
3092e2bd5b6Sreyk 			 * be changed independently by the filters later.
310b7b6a941Sreyk 			 */
3112e2bd5b6Sreyk 			if ((desc->http_version =
31203fd4389Sreyk 			    strdup(desc->http_version)) == NULL)
313b7b6a941Sreyk 				goto fail;
31403fd4389Sreyk 
315b7b6a941Sreyk 			if (desc->http_query != NULL &&
316b7b6a941Sreyk 			    (desc->http_query =
31703fd4389Sreyk 			    strdup(desc->http_query)) == NULL)
318b7b6a941Sreyk 				goto fail;
31903fd4389Sreyk 
320b7b6a941Sreyk 		} else if (desc->http_method != HTTP_METHOD_NONE &&
321b7b6a941Sreyk 		    strcasecmp("Content-Length", key) == 0) {
322b7b6a941Sreyk 			if (desc->http_method == HTTP_METHOD_TRACE ||
323b7b6a941Sreyk 			    desc->http_method == HTTP_METHOD_CONNECT) {
324b7b6a941Sreyk 				/*
325b7b6a941Sreyk 				 * These method should not have a body
326b7b6a941Sreyk 				 * and thus no Content-Length header.
327b7b6a941Sreyk 				 */
328b7b6a941Sreyk 				server_abort_http(clt, 400, "malformed");
329b7b6a941Sreyk 				goto abort;
330b7b6a941Sreyk 			}
331b7b6a941Sreyk 
332b7b6a941Sreyk 			/*
333b7b6a941Sreyk 			 * Need to read data from the client after the
334b7b6a941Sreyk 			 * HTTP header.
335b7b6a941Sreyk 			 * XXX What about non-standard clients not using
336b7b6a941Sreyk 			 * the carriage return? And some browsers seem to
337b7b6a941Sreyk 			 * include the line length in the content-length.
338b7b6a941Sreyk 			 */
339b7b6a941Sreyk 			clt->clt_toread = strtonum(value, 0, LLONG_MAX,
340b7b6a941Sreyk 			    &errstr);
341b7b6a941Sreyk 			if (errstr) {
342b7b6a941Sreyk 				server_abort_http(clt, 500, errstr);
343b7b6a941Sreyk 				goto abort;
344b7b6a941Sreyk 			}
3454dd188b5Sreyk 			if ((size_t)clt->clt_toread >
3464dd188b5Sreyk 			    srv_conf->maxrequestbody) {
3474dd188b5Sreyk 				server_abort_http(clt, 413, NULL);
3484dd188b5Sreyk 				goto abort;
3494dd188b5Sreyk 			}
350b7b6a941Sreyk 		}
351b7b6a941Sreyk 
352b7b6a941Sreyk 		if (strcasecmp("Transfer-Encoding", key) == 0 &&
353b7b6a941Sreyk 		    strcasecmp("chunked", value) == 0)
354b7b6a941Sreyk 			desc->http_chunked = 1;
355b7b6a941Sreyk 
356b7b6a941Sreyk 		if (clt->clt_line != 1) {
357b7b6a941Sreyk 			if ((hdr = kv_add(&desc->http_headers, key,
35803fd4389Sreyk 			    value)) == NULL)
359b7b6a941Sreyk 				goto fail;
36003fd4389Sreyk 
361b7b6a941Sreyk 			desc->http_lastheader = hdr;
362b7b6a941Sreyk 		}
363b7b6a941Sreyk 
364b7b6a941Sreyk 		free(line);
365b7b6a941Sreyk 	}
366b7b6a941Sreyk 	if (clt->clt_done) {
367b7b6a941Sreyk 		if (desc->http_method == HTTP_METHOD_NONE) {
368b7b6a941Sreyk 			server_abort_http(clt, 406, "no method");
369b7b6a941Sreyk 			return;
370b7b6a941Sreyk 		}
371b7b6a941Sreyk 
372b7b6a941Sreyk 		switch (desc->http_method) {
373b7b6a941Sreyk 		case HTTP_METHOD_CONNECT:
374b7b6a941Sreyk 			/* Data stream */
375b7b6a941Sreyk 			clt->clt_toread = TOREAD_UNLIMITED;
376b7b6a941Sreyk 			bev->readcb = server_read;
377b7b6a941Sreyk 			break;
378b7b6a941Sreyk 		case HTTP_METHOD_DELETE:
379b7b6a941Sreyk 		case HTTP_METHOD_GET:
380b7b6a941Sreyk 		case HTTP_METHOD_HEAD:
381b7b6a941Sreyk 		case HTTP_METHOD_OPTIONS:
382f0c872b4Sreyk 		/* WebDAV methods */
383f0c872b4Sreyk 		case HTTP_METHOD_COPY:
384ac272b40Sreyk 		case HTTP_METHOD_MOVE:
385b7b6a941Sreyk 			clt->clt_toread = 0;
386b7b6a941Sreyk 			break;
387b7b6a941Sreyk 		case HTTP_METHOD_POST:
388b7b6a941Sreyk 		case HTTP_METHOD_PUT:
389b7b6a941Sreyk 		case HTTP_METHOD_RESPONSE:
390f0c872b4Sreyk 		/* WebDAV methods */
391f0c872b4Sreyk 		case HTTP_METHOD_PROPFIND:
392f0c872b4Sreyk 		case HTTP_METHOD_PROPPATCH:
393f0c872b4Sreyk 		case HTTP_METHOD_MKCOL:
394f0c872b4Sreyk 		case HTTP_METHOD_LOCK:
395f0c872b4Sreyk 		case HTTP_METHOD_UNLOCK:
396f0c872b4Sreyk 		case HTTP_METHOD_VERSION_CONTROL:
397f0c872b4Sreyk 		case HTTP_METHOD_REPORT:
398f0c872b4Sreyk 		case HTTP_METHOD_CHECKOUT:
399f0c872b4Sreyk 		case HTTP_METHOD_CHECKIN:
400f0c872b4Sreyk 		case HTTP_METHOD_UNCHECKOUT:
401f0c872b4Sreyk 		case HTTP_METHOD_MKWORKSPACE:
402f0c872b4Sreyk 		case HTTP_METHOD_UPDATE:
403f0c872b4Sreyk 		case HTTP_METHOD_LABEL:
404f0c872b4Sreyk 		case HTTP_METHOD_MERGE:
405f0c872b4Sreyk 		case HTTP_METHOD_BASELINE_CONTROL:
406f0c872b4Sreyk 		case HTTP_METHOD_MKACTIVITY:
407f0c872b4Sreyk 		case HTTP_METHOD_ORDERPATCH:
408f0c872b4Sreyk 		case HTTP_METHOD_ACL:
409f0c872b4Sreyk 		case HTTP_METHOD_MKREDIRECTREF:
410f0c872b4Sreyk 		case HTTP_METHOD_UPDATEREDIRECTREF:
411f0c872b4Sreyk 		case HTTP_METHOD_SEARCH:
412f0c872b4Sreyk 		case HTTP_METHOD_PATCH:
413b7b6a941Sreyk 			/* HTTP request payload */
414b7b6a941Sreyk 			if (clt->clt_toread > 0)
415b7b6a941Sreyk 				bev->readcb = server_read_httpcontent;
416b7b6a941Sreyk 
417b7b6a941Sreyk 			/* Single-pass HTTP body */
418b7b6a941Sreyk 			if (clt->clt_toread < 0) {
419b7b6a941Sreyk 				clt->clt_toread = TOREAD_UNLIMITED;
420b7b6a941Sreyk 				bev->readcb = server_read;
421b7b6a941Sreyk 			}
422b7b6a941Sreyk 			break;
423b7b6a941Sreyk 		default:
424f0c872b4Sreyk 			server_abort_http(clt, 405, "method not allowed");
425f0c872b4Sreyk 			return;
426b7b6a941Sreyk 		}
427b7b6a941Sreyk 		if (desc->http_chunked) {
428b7b6a941Sreyk 			/* Chunked transfer encoding */
429b7b6a941Sreyk 			clt->clt_toread = TOREAD_HTTP_CHUNK_LENGTH;
430b7b6a941Sreyk 			bev->readcb = server_read_httpchunks;
431b7b6a941Sreyk 		}
432b7b6a941Sreyk 
433b7b6a941Sreyk  done:
434b6a65335Sflorian 		if (clt->clt_toread != 0)
435b6a65335Sflorian 			bufferevent_disable(bev, EV_READ);
43612312c27Sreyk 		server_response(env, clt);
437b7b6a941Sreyk 		return;
438b7b6a941Sreyk 	}
439b7b6a941Sreyk 	if (clt->clt_done) {
440b7b6a941Sreyk 		server_close(clt, "done");
441b7b6a941Sreyk 		return;
442b7b6a941Sreyk 	}
443b7b6a941Sreyk 	if (EVBUFFER_LENGTH(src) && bev->readcb != server_read_http)
444b7b6a941Sreyk 		bev->readcb(bev, arg);
445b7b6a941Sreyk 	bufferevent_enable(bev, EV_READ);
446b7b6a941Sreyk 	return;
447b7b6a941Sreyk  fail:
448b7b6a941Sreyk 	server_abort_http(clt, 500, strerror(errno));
449b7b6a941Sreyk  abort:
450b7b6a941Sreyk 	free(line);
451b7b6a941Sreyk }
452b7b6a941Sreyk 
453b7b6a941Sreyk void
454b7b6a941Sreyk server_read_httpcontent(struct bufferevent *bev, void *arg)
455b7b6a941Sreyk {
456b7b6a941Sreyk 	struct client		*clt = arg;
457b7b6a941Sreyk 	struct evbuffer		*src = EVBUFFER_INPUT(bev);
458b7b6a941Sreyk 	size_t			 size;
459b7b6a941Sreyk 
460b7b6a941Sreyk 	getmonotime(&clt->clt_tv_last);
461b7b6a941Sreyk 
462b7b6a941Sreyk 	size = EVBUFFER_LENGTH(src);
463b7b6a941Sreyk 	DPRINTF("%s: session %d: size %lu, to read %lld", __func__,
464b7b6a941Sreyk 	    clt->clt_id, size, clt->clt_toread);
465b7b6a941Sreyk 	if (!size)
466b7b6a941Sreyk 		return;
467b7b6a941Sreyk 
468b7b6a941Sreyk 	if (clt->clt_toread > 0) {
469b7b6a941Sreyk 		/* Read content data */
470b7b6a941Sreyk 		if ((off_t)size > clt->clt_toread) {
471b7b6a941Sreyk 			size = clt->clt_toread;
472b6a65335Sflorian 			if (fcgi_add_stdin(clt, src) == -1)
473b7b6a941Sreyk 				goto fail;
474b7b6a941Sreyk 			clt->clt_toread = 0;
475b7b6a941Sreyk 		} else {
476b6a65335Sflorian 			if (fcgi_add_stdin(clt, src) == -1)
477b7b6a941Sreyk 				goto fail;
478b7b6a941Sreyk 			clt->clt_toread -= size;
479b7b6a941Sreyk 		}
480b7b6a941Sreyk 		DPRINTF("%s: done, size %lu, to read %lld", __func__,
481b7b6a941Sreyk 		    size, clt->clt_toread);
482b7b6a941Sreyk 	}
483b7b6a941Sreyk 	if (clt->clt_toread == 0) {
484b6a65335Sflorian 		fcgi_add_stdin(clt, NULL);
485b7b6a941Sreyk 		clt->clt_toread = TOREAD_HTTP_HEADER;
486b6a65335Sflorian 		bufferevent_disable(bev, EV_READ);
487b7b6a941Sreyk 		bev->readcb = server_read_http;
488b6a65335Sflorian 		return;
489b7b6a941Sreyk 	}
490b7b6a941Sreyk 	if (clt->clt_done)
491b7b6a941Sreyk 		goto done;
492b7b6a941Sreyk 	if (bev->readcb != server_read_httpcontent)
493b7b6a941Sreyk 		bev->readcb(bev, arg);
494b6a65335Sflorian 
495b7b6a941Sreyk 	return;
496b7b6a941Sreyk  done:
497b7b6a941Sreyk 	return;
498b7b6a941Sreyk  fail:
499b7b6a941Sreyk 	server_close(clt, strerror(errno));
500b7b6a941Sreyk }
501b7b6a941Sreyk 
502b7b6a941Sreyk void
503b7b6a941Sreyk server_read_httpchunks(struct bufferevent *bev, void *arg)
504b7b6a941Sreyk {
505b7b6a941Sreyk 	struct client		*clt = arg;
506b7b6a941Sreyk 	struct evbuffer		*src = EVBUFFER_INPUT(bev);
507b7b6a941Sreyk 	char			*line;
508b7b6a941Sreyk 	long long		 llval;
509b7b6a941Sreyk 	size_t			 size;
510b7b6a941Sreyk 
511b7b6a941Sreyk 	getmonotime(&clt->clt_tv_last);
512b7b6a941Sreyk 
513b7b6a941Sreyk 	size = EVBUFFER_LENGTH(src);
514b7b6a941Sreyk 	DPRINTF("%s: session %d: size %lu, to read %lld", __func__,
515b7b6a941Sreyk 	    clt->clt_id, size, clt->clt_toread);
516b7b6a941Sreyk 	if (!size)
517b7b6a941Sreyk 		return;
518b7b6a941Sreyk 
519b7b6a941Sreyk 	if (clt->clt_toread > 0) {
520b7b6a941Sreyk 		/* Read chunk data */
521b7b6a941Sreyk 		if ((off_t)size > clt->clt_toread) {
522b7b6a941Sreyk 			size = clt->clt_toread;
523b7b6a941Sreyk 			if (server_bufferevent_write_chunk(clt, src, size)
524b7b6a941Sreyk 			    == -1)
525b7b6a941Sreyk 				goto fail;
526b7b6a941Sreyk 			clt->clt_toread = 0;
527b7b6a941Sreyk 		} else {
528b7b6a941Sreyk 			if (server_bufferevent_write_buffer(clt, src) == -1)
529b7b6a941Sreyk 				goto fail;
530b7b6a941Sreyk 			clt->clt_toread -= size;
531b7b6a941Sreyk 		}
532b7b6a941Sreyk 		DPRINTF("%s: done, size %lu, to read %lld", __func__,
533b7b6a941Sreyk 		    size, clt->clt_toread);
534b7b6a941Sreyk 	}
535b7b6a941Sreyk 	switch (clt->clt_toread) {
536b7b6a941Sreyk 	case TOREAD_HTTP_CHUNK_LENGTH:
5378757e0ccSjsg 		line = evbuffer_readln(src, NULL, EVBUFFER_EOL_CRLF_STRICT);
538b7b6a941Sreyk 		if (line == NULL) {
539b7b6a941Sreyk 			/* Ignore empty line, continue */
540b7b6a941Sreyk 			bufferevent_enable(bev, EV_READ);
541b7b6a941Sreyk 			return;
542b7b6a941Sreyk 		}
543b7b6a941Sreyk 		if (strlen(line) == 0) {
544b7b6a941Sreyk 			free(line);
545b7b6a941Sreyk 			goto next;
546b7b6a941Sreyk 		}
547b7b6a941Sreyk 
548b7b6a941Sreyk 		/*
549b7b6a941Sreyk 		 * Read prepended chunk size in hex, ignore the trailer.
550b7b6a941Sreyk 		 * The returned signed value must not be negative.
551b7b6a941Sreyk 		 */
552b7b6a941Sreyk 		if (sscanf(line, "%llx", &llval) != 1 || llval < 0) {
553b7b6a941Sreyk 			free(line);
554b7b6a941Sreyk 			server_close(clt, "invalid chunk size");
555b7b6a941Sreyk 			return;
556b7b6a941Sreyk 		}
557b7b6a941Sreyk 
558b7b6a941Sreyk 		if (server_bufferevent_print(clt, line) == -1 ||
559b7b6a941Sreyk 		    server_bufferevent_print(clt, "\r\n") == -1) {
560b7b6a941Sreyk 			free(line);
561b7b6a941Sreyk 			goto fail;
562b7b6a941Sreyk 		}
563b7b6a941Sreyk 		free(line);
564b7b6a941Sreyk 
565b7b6a941Sreyk 		if ((clt->clt_toread = llval) == 0) {
566b7b6a941Sreyk 			DPRINTF("%s: last chunk", __func__);
567b7b6a941Sreyk 			clt->clt_toread = TOREAD_HTTP_CHUNK_TRAILER;
568b7b6a941Sreyk 		}
569b7b6a941Sreyk 		break;
570b7b6a941Sreyk 	case TOREAD_HTTP_CHUNK_TRAILER:
571b7b6a941Sreyk 		/* Last chunk is 0 bytes followed by trailer and empty line */
5728757e0ccSjsg 		line = evbuffer_readln(src, NULL, EVBUFFER_EOL_CRLF_STRICT);
573b7b6a941Sreyk 		if (line == NULL) {
574b7b6a941Sreyk 			/* Ignore empty line, continue */
575b7b6a941Sreyk 			bufferevent_enable(bev, EV_READ);
576b7b6a941Sreyk 			return;
577b7b6a941Sreyk 		}
578b7b6a941Sreyk 		if (server_bufferevent_print(clt, line) == -1 ||
579b7b6a941Sreyk 		    server_bufferevent_print(clt, "\r\n") == -1) {
580b7b6a941Sreyk 			free(line);
581b7b6a941Sreyk 			goto fail;
582b7b6a941Sreyk 		}
583b7b6a941Sreyk 		if (strlen(line) == 0) {
584b7b6a941Sreyk 			/* Switch to HTTP header mode */
585b7b6a941Sreyk 			clt->clt_toread = TOREAD_HTTP_HEADER;
586b7b6a941Sreyk 			bev->readcb = server_read_http;
587b7b6a941Sreyk 		}
588b7b6a941Sreyk 		free(line);
589b7b6a941Sreyk 		break;
590b7b6a941Sreyk 	case 0:
591b7b6a941Sreyk 		/* Chunk is terminated by an empty newline */
5928757e0ccSjsg 		line = evbuffer_readln(src, NULL, EVBUFFER_EOL_CRLF_STRICT);
593b7b6a941Sreyk 		free(line);
594b7b6a941Sreyk 		if (server_bufferevent_print(clt, "\r\n") == -1)
595b7b6a941Sreyk 			goto fail;
596b7b6a941Sreyk 		clt->clt_toread = TOREAD_HTTP_CHUNK_LENGTH;
597b7b6a941Sreyk 		break;
598b7b6a941Sreyk 	}
599b7b6a941Sreyk 
600b7b6a941Sreyk  next:
601b7b6a941Sreyk 	if (clt->clt_done)
602b7b6a941Sreyk 		goto done;
603b7b6a941Sreyk 	if (EVBUFFER_LENGTH(src))
604b7b6a941Sreyk 		bev->readcb(bev, arg);
605b7b6a941Sreyk 	bufferevent_enable(bev, EV_READ);
606b7b6a941Sreyk 	return;
607b7b6a941Sreyk 
608b7b6a941Sreyk  done:
609b7b6a941Sreyk 	server_close(clt, "last http chunk read (done)");
610b7b6a941Sreyk 	return;
611b7b6a941Sreyk  fail:
612b7b6a941Sreyk 	server_close(clt, strerror(errno));
613b7b6a941Sreyk }
614b7b6a941Sreyk 
615b7b6a941Sreyk void
616b7b6a941Sreyk server_reset_http(struct client *clt)
617b7b6a941Sreyk {
618fa361cd1Sreyk 	struct server		*srv = clt->clt_srv;
619b7b6a941Sreyk 
620c145f9a8Sreyk 	server_log(clt, NULL);
621c145f9a8Sreyk 
622d08e4976Sreyk 	server_httpdesc_free(clt->clt_descreq);
623d08e4976Sreyk 	server_httpdesc_free(clt->clt_descresp);
624b7b6a941Sreyk 	clt->clt_headerlen = 0;
625b7b6a941Sreyk 	clt->clt_line = 0;
626b7b6a941Sreyk 	clt->clt_done = 0;
627ac2cdcb6Sreyk 	clt->clt_chunk = 0;
628daa1b608Sflorian 	free(clt->clt_remote_user);
629daa1b608Sflorian 	clt->clt_remote_user = NULL;
6305fa30660Sreyk 	clt->clt_bev->readcb = server_read_http;
631fa361cd1Sreyk 	clt->clt_srv_conf = &srv->srv_conf;
63259355b5aSreyk 	str_match_free(&clt->clt_srv_match);
633b7b6a941Sreyk }
634b7b6a941Sreyk 
635be5ab2e6Schrisz ssize_t
636be5ab2e6Schrisz server_http_time(time_t t, char *tmbuf, size_t len)
6379f126950Sreyk {
6389f126950Sreyk 	struct tm		 tm;
6399f126950Sreyk 
6409f126950Sreyk 	/* New HTTP/1.1 RFC 7231 prefers IMF-fixdate from RFC 5322 */
641be5ab2e6Schrisz 	if (t == -1 || gmtime_r(&t, &tm) == NULL)
642be5ab2e6Schrisz 		return (-1);
643be5ab2e6Schrisz 	else
644be5ab2e6Schrisz 		return (strftime(tmbuf, len, "%a, %d %h %Y %T %Z", &tm));
6459f126950Sreyk }
6469f126950Sreyk 
6476af43371Sreyk const char *
6486af43371Sreyk server_http_host(struct sockaddr_storage *ss, char *buf, size_t len)
6496af43371Sreyk {
650b9fc9a72Sderaadt 	char		hbuf[HOST_NAME_MAX+1];
6516af43371Sreyk 	in_port_t	port;
6526af43371Sreyk 
6536af43371Sreyk 	if (print_host(ss, buf, len) == NULL)
6546af43371Sreyk 		return (NULL);
6556af43371Sreyk 
6566af43371Sreyk 	port = ntohs(server_socket_getport(ss));
6576af43371Sreyk 	if (port == HTTP_PORT)
6586af43371Sreyk 		return (buf);
6596af43371Sreyk 
6606af43371Sreyk 	switch (ss->ss_family) {
6616af43371Sreyk 	case AF_INET:
6626af43371Sreyk 		if ((size_t)snprintf(hbuf, sizeof(hbuf),
6636af43371Sreyk 		    "%s:%u", buf, port) >= sizeof(hbuf))
6646af43371Sreyk 			return (NULL);
6656af43371Sreyk 		break;
6666af43371Sreyk 	case AF_INET6:
6676af43371Sreyk 		if ((size_t)snprintf(hbuf, sizeof(hbuf),
6686af43371Sreyk 		    "[%s]:%u", buf, port) >= sizeof(hbuf))
6696af43371Sreyk 			return (NULL);
6706af43371Sreyk 		break;
6716af43371Sreyk 	}
6726af43371Sreyk 
6736af43371Sreyk 	if (strlcpy(buf, hbuf, len) >= len)
6746af43371Sreyk 		return (NULL);
6756af43371Sreyk 
6766af43371Sreyk 	return (buf);
6776af43371Sreyk }
6786af43371Sreyk 
67977fd0032Sreyk char *
68077fd0032Sreyk server_http_parsehost(char *host, char *buf, size_t len, int *portval)
68177fd0032Sreyk {
68277fd0032Sreyk 	char		*start, *end, *port;
68377fd0032Sreyk 	const char	*errstr = NULL;
68477fd0032Sreyk 
68577fd0032Sreyk 	if (strlcpy(buf, host, len) >= len) {
68677fd0032Sreyk 		log_debug("%s: host name too long", __func__);
68777fd0032Sreyk 		return (NULL);
68877fd0032Sreyk 	}
68977fd0032Sreyk 
69077fd0032Sreyk 	start = buf;
69177fd0032Sreyk 	end = port = NULL;
69277fd0032Sreyk 
69377fd0032Sreyk 	if (*start == '[' && (end = strchr(start, ']')) != NULL) {
69477fd0032Sreyk 		/* Address enclosed in [] with port, eg. [2001:db8::1]:80 */
69577fd0032Sreyk 		start++;
69677fd0032Sreyk 		*end++ = '\0';
69777fd0032Sreyk 		if ((port = strchr(end, ':')) == NULL || *port == '\0')
69877fd0032Sreyk 			port = NULL;
69977fd0032Sreyk 		else
70077fd0032Sreyk 			port++;
70177fd0032Sreyk 		memmove(buf, start, strlen(start) + 1);
70277fd0032Sreyk 	} else if ((end = strchr(start, ':')) != NULL) {
70377fd0032Sreyk 		/* Name or address with port, eg. www.example.com:80 */
70477fd0032Sreyk 		*end++ = '\0';
70577fd0032Sreyk 		port = end;
70677fd0032Sreyk 	} else {
70777fd0032Sreyk 		/* Name or address with default port, eg. www.example.com */
70877fd0032Sreyk 		port = NULL;
70977fd0032Sreyk 	}
71077fd0032Sreyk 
71177fd0032Sreyk 	if (port != NULL) {
71277fd0032Sreyk 		/* Save the requested port */
71377fd0032Sreyk 		*portval = strtonum(port, 0, 0xffff, &errstr);
71477fd0032Sreyk 		if (errstr != NULL) {
71577fd0032Sreyk 			log_debug("%s: invalid port: %s", __func__,
71677fd0032Sreyk 			    strerror(errno));
71777fd0032Sreyk 			return (NULL);
71877fd0032Sreyk 		}
71977fd0032Sreyk 		*portval = htons(*portval);
72077fd0032Sreyk 	} else {
72177fd0032Sreyk 		/* Port not given, indicate the default port */
72277fd0032Sreyk 		*portval = -1;
72377fd0032Sreyk 	}
72477fd0032Sreyk 
72577fd0032Sreyk 	return (start);
72677fd0032Sreyk }
72777fd0032Sreyk 
728b7b6a941Sreyk void
7294703e0faSreyk server_abort_http(struct client *clt, unsigned int code, const char *msg)
730b7b6a941Sreyk {
731f5d55328Sflorian 	struct server_config	*srv_conf = clt->clt_srv_conf;
732b7b6a941Sreyk 	struct bufferevent	*bev = clt->clt_bev;
7338b4c340eSflorian 	struct http_descriptor	*desc = clt->clt_descreq;
7348b4c340eSflorian 	const char		*httperr = NULL, *style;
7358b4c340eSflorian 	char			*httpmsg, *body = NULL, *extraheader = NULL;
736f5d55328Sflorian 	char			 tmbuf[32], hbuf[128], *hstsheader = NULL;
737bf34a23eSreyk 	char			 buf[IBUF_READ_SIZE];
7380674f400Ssemarie 	char			*escapedmsg = NULL;
7398b4c340eSflorian 	int			 bodylen;
740b7b6a941Sreyk 
741f8932becSreyk 	if (code == 0) {
742f8932becSreyk 		server_close(clt, "dropped");
743f8932becSreyk 		return;
744f8932becSreyk 	}
745f8932becSreyk 
746b7b6a941Sreyk 	if ((httperr = server_httperror_byid(code)) == NULL)
747b7b6a941Sreyk 		httperr = "Unknown Error";
748b7b6a941Sreyk 
749b7b6a941Sreyk 	if (bev == NULL)
750b7b6a941Sreyk 		goto done;
751b7b6a941Sreyk 
752e9b02f4aSreyk 	if (server_log_http(clt, code, 0) == -1)
753e9b02f4aSreyk 		goto done;
754e9b02f4aSreyk 
755b7b6a941Sreyk 	/* Some system information */
7569fb8351aSreyk 	if (print_host(&srv_conf->ss, hbuf, sizeof(hbuf)) == NULL)
757b7b6a941Sreyk 		goto done;
758b7b6a941Sreyk 
759be5ab2e6Schrisz 	if (server_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0)
760be5ab2e6Schrisz 		goto done;
761b7b6a941Sreyk 
762b7b6a941Sreyk 	/* Do not send details of the Internal Server Error */
7635d9e55e4Sreyk 	switch (code) {
7645d9e55e4Sreyk 	case 301:
7655d9e55e4Sreyk 	case 302:
766586dade4Sreyk 	case 303:
767586dade4Sreyk 		if (msg == NULL)
768586dade4Sreyk 			break;
769586dade4Sreyk 		memset(buf, 0, sizeof(buf));
770bf34a23eSreyk 		if (server_expand_http(clt, msg, buf, sizeof(buf)) == NULL)
771586dade4Sreyk 			goto done;
772bf34a23eSreyk 		if (asprintf(&extraheader, "Location: %s\r\n", buf) == -1) {
7735d9e55e4Sreyk 			code = 500;
7745d9e55e4Sreyk 			extraheader = NULL;
7755d9e55e4Sreyk 		}
776bf34a23eSreyk 		msg = buf;
7775d9e55e4Sreyk 		break;
778e286121aSflorian 	case 401:
7790674f400Ssemarie 		if (stravis(&escapedmsg, msg, VIS_DQ) == -1) {
7800674f400Ssemarie 			code = 500;
7810674f400Ssemarie 			extraheader = NULL;
7820674f400Ssemarie 		} else if (asprintf(&extraheader,
7830674f400Ssemarie 		    "WWW-Authenticate: Basic realm=\"%s\"\r\n", escapedmsg)
7840674f400Ssemarie 		    == -1) {
785e286121aSflorian 			code = 500;
786e286121aSflorian 			extraheader = NULL;
787e286121aSflorian 		}
788e286121aSflorian 		break;
789b0faf28cSflorian 	case 416:
790b0faf28cSflorian 		if (asprintf(&extraheader,
791b0faf28cSflorian 		    "Content-Range: %s\r\n", msg) == -1) {
792b0faf28cSflorian 			code = 500;
793b0faf28cSflorian 			extraheader = NULL;
794b0faf28cSflorian 		}
795b0faf28cSflorian 		break;
7965d9e55e4Sreyk 	default:
797cbebb5b9Sreyk 		/*
798cbebb5b9Sreyk 		 * Do not send details of the error.  Traditionally,
799cbebb5b9Sreyk 		 * web servers responsed with the request path on 40x
800cbebb5b9Sreyk 		 * errors which could be abused to inject JavaScript etc.
801cbebb5b9Sreyk 		 * Instead of sanitizing the path here, we just don't
802cbebb5b9Sreyk 		 * reprint it.
803cbebb5b9Sreyk 		 */
8045d9e55e4Sreyk 		break;
8055d9e55e4Sreyk 	}
806b7b6a941Sreyk 
8070674f400Ssemarie 	free(escapedmsg);
8080674f400Ssemarie 
809b7b6a941Sreyk 	/* A CSS stylesheet allows minimal customization by the user */
810f687442bSreyk 	style = "body { background-color: white; color: black; font-family: "
811cbebb5b9Sreyk 	    "'Comic Sans MS', 'Chalkboard SE', 'Comic Neue', sans-serif; }\n"
812cbebb5b9Sreyk 	    "hr { border: 0; border-bottom: 1px dashed; }\n";
8138b4c340eSflorian 
8148b4c340eSflorian 	/* Generate simple HTML error document */
8158b4c340eSflorian 	if ((bodylen = asprintf(&body,
816d89214cdSreyk 	    "<!DOCTYPE html>\n"
817b7b6a941Sreyk 	    "<html>\n"
818b7b6a941Sreyk 	    "<head>\n"
819f5ec6fb6Sflorian 	    "<meta http-equiv=\"Content-Type\" content=\"text/html; "
820f5ec6fb6Sflorian 	    "charset=utf-8\"/>\n"
821b7b6a941Sreyk 	    "<title>%03d %s</title>\n"
822b7b6a941Sreyk 	    "<style type=\"text/css\"><!--\n%s\n--></style>\n"
823b7b6a941Sreyk 	    "</head>\n"
824b7b6a941Sreyk 	    "<body>\n"
825cbebb5b9Sreyk 	    "<h1>%03d %s</h1>\n"
826cbebb5b9Sreyk 	    "<hr>\n<address>%s</address>\n"
827b7b6a941Sreyk 	    "</body>\n"
828b7b6a941Sreyk 	    "</html>\n",
829*95d80e7aSjung 	    code, httperr, style, code, httperr, HTTPD_SERVERNAME)) == -1) {
830*95d80e7aSjung 		body = NULL;
8318b4c340eSflorian 		goto done;
832*95d80e7aSjung 	}
8338b4c340eSflorian 
834f5d55328Sflorian 	if (srv_conf->flags & SRVFLAG_SERVER_HSTS) {
835f5d55328Sflorian 		if (asprintf(&hstsheader, "Strict-Transport-Security: "
83652f7cd50Sreyk 		    "max-age=%d%s%s\r\n", srv_conf->hsts_max_age,
83752f7cd50Sreyk 		    srv_conf->hsts_flags & HSTSFLAG_SUBDOMAINS ?
83852f7cd50Sreyk 		    "; includeSubDomains" : "",
83952f7cd50Sreyk 		    srv_conf->hsts_flags & HSTSFLAG_PRELOAD ?
840*95d80e7aSjung 		    "; preload" : "") == -1) {
841*95d80e7aSjung 			hstsheader = NULL;
842f5d55328Sflorian 			goto done;
843f5d55328Sflorian 		}
844*95d80e7aSjung 	}
845f5d55328Sflorian 
8468b4c340eSflorian 	/* Add basic HTTP headers */
8478b4c340eSflorian 	if (asprintf(&httpmsg,
8488b4c340eSflorian 	    "HTTP/1.0 %03d %s\r\n"
8498b4c340eSflorian 	    "Date: %s\r\n"
8508b4c340eSflorian 	    "Server: %s\r\n"
8518b4c340eSflorian 	    "Connection: close\r\n"
8528b4c340eSflorian 	    "Content-Type: text/html\r\n"
8538b4c340eSflorian 	    "Content-Length: %d\r\n"
8548b4c340eSflorian 	    "%s"
855f5d55328Sflorian 	    "%s"
8568b4c340eSflorian 	    "\r\n"
8578b4c340eSflorian 	    "%s",
8588b4c340eSflorian 	    code, httperr, tmbuf, HTTPD_SERVERNAME, bodylen,
8595d9e55e4Sreyk 	    extraheader == NULL ? "" : extraheader,
860f5d55328Sflorian 	    hstsheader == NULL ? "" : hstsheader,
8618b4c340eSflorian 	    desc->http_method == HTTP_METHOD_HEAD ? "" : body) == -1)
862b7b6a941Sreyk 		goto done;
863b7b6a941Sreyk 
864b7b6a941Sreyk 	/* Dump the message without checking for success */
865b7b6a941Sreyk 	server_dump(clt, httpmsg, strlen(httpmsg));
866b7b6a941Sreyk 	free(httpmsg);
867b7b6a941Sreyk 
868b7b6a941Sreyk  done:
8698b4c340eSflorian 	free(body);
8705d9e55e4Sreyk 	free(extraheader);
871f5d55328Sflorian 	free(hstsheader);
872586dade4Sreyk 	if (msg == NULL)
873586dade4Sreyk 		msg = "\"\"";
874df9b638bSreyk 	if (asprintf(&httpmsg, "%s (%03d %s)", msg, code, httperr) == -1) {
875b7b6a941Sreyk 		server_close(clt, msg);
876df9b638bSreyk 	} else {
877b7b6a941Sreyk 		server_close(clt, httpmsg);
878b7b6a941Sreyk 		free(httpmsg);
879b7b6a941Sreyk 	}
880b7b6a941Sreyk }
881b7b6a941Sreyk 
882b7b6a941Sreyk void
883b7b6a941Sreyk server_close_http(struct client *clt)
884b7b6a941Sreyk {
885d08e4976Sreyk 	struct http_descriptor *desc;
886b7b6a941Sreyk 
887d08e4976Sreyk 	desc = clt->clt_descreq;
888b7b6a941Sreyk 	server_httpdesc_free(desc);
889b7b6a941Sreyk 	free(desc);
890d08e4976Sreyk 	clt->clt_descreq = NULL;
891d08e4976Sreyk 
892d08e4976Sreyk 	desc = clt->clt_descresp;
893d08e4976Sreyk 	server_httpdesc_free(desc);
894d08e4976Sreyk 	free(desc);
895d08e4976Sreyk 	clt->clt_descresp = NULL;
896daa1b608Sflorian 	free(clt->clt_remote_user);
897daa1b608Sflorian 	clt->clt_remote_user = NULL;
89859355b5aSreyk 
89959355b5aSreyk 	str_match_free(&clt->clt_srv_match);
900b7b6a941Sreyk }
901b7b6a941Sreyk 
902586dade4Sreyk char *
903586dade4Sreyk server_expand_http(struct client *clt, const char *val, char *buf,
904586dade4Sreyk     size_t len)
905586dade4Sreyk {
906586dade4Sreyk 	struct http_descriptor	*desc = clt->clt_descreq;
907586dade4Sreyk 	struct server_config	*srv_conf = clt->clt_srv_conf;
908bf34a23eSreyk 	char			 ibuf[128], *str, *path, *query;
90959355b5aSreyk 	const char		*errstr = NULL, *p;
91059355b5aSreyk 	size_t			 size;
91159355b5aSreyk 	int			 n, ret;
912586dade4Sreyk 
913586dade4Sreyk 	if (strlcpy(buf, val, len) >= len)
914586dade4Sreyk 		return (NULL);
915586dade4Sreyk 
91659355b5aSreyk 	/* Find previously matched substrings by index */
91759355b5aSreyk 	for (p = val; clt->clt_srv_match.sm_nmatch &&
91859355b5aSreyk 	    (p = strstr(p, "%")) != NULL; p++) {
91900f3986fSreyk 		if (!isdigit((unsigned char)*(p + 1)))
92059355b5aSreyk 			continue;
92159355b5aSreyk 
92259355b5aSreyk 		/* Copy number, leading '%' char and add trailing \0 */
92359355b5aSreyk 		size = strspn(p + 1, "0123456789") + 2;
92459355b5aSreyk 		if (size  >= sizeof(ibuf))
92559355b5aSreyk 			return (NULL);
92659355b5aSreyk 		(void)strlcpy(ibuf, p, size);
92759355b5aSreyk 		n = strtonum(ibuf + 1, 0,
92859355b5aSreyk 		    clt->clt_srv_match.sm_nmatch - 1, &errstr);
92959355b5aSreyk 		if (errstr != NULL)
93059355b5aSreyk 			return (NULL);
93159355b5aSreyk 
93259355b5aSreyk 		/* Expand variable with matched value */
9330788fdf8Ssemarie 		if ((str = url_encode(clt->clt_srv_match.sm_match[n])) == NULL)
9340788fdf8Ssemarie 			return (NULL);
9350788fdf8Ssemarie 		ret = expand_string(buf, len, ibuf, str);
9360788fdf8Ssemarie 		free(str);
9370788fdf8Ssemarie 		if (ret != 0)
93859355b5aSreyk 			return (NULL);
93959355b5aSreyk 	}
940586dade4Sreyk 	if (strstr(val, "$DOCUMENT_URI") != NULL) {
941bf34a23eSreyk 		if ((path = url_encode(desc->http_path)) == NULL)
942bf34a23eSreyk 			return (NULL);
943bf34a23eSreyk 		ret = expand_string(buf, len, "$DOCUMENT_URI", path);
944bf34a23eSreyk 		free(path);
945bf34a23eSreyk 		if (ret != 0)
946586dade4Sreyk 			return (NULL);
947586dade4Sreyk 	}
948586dade4Sreyk 	if (strstr(val, "$QUERY_STRING") != NULL) {
949bf34a23eSreyk 		if (desc->http_query == NULL) {
950bf34a23eSreyk 			ret = expand_string(buf, len, "$QUERY_STRING", "");
951bf34a23eSreyk 		} else {
952bf34a23eSreyk 			if ((query = url_encode(desc->http_query)) == NULL)
953bf34a23eSreyk 				return (NULL);
954bf34a23eSreyk 			ret = expand_string(buf, len, "$QUERY_STRING", query);
955bf34a23eSreyk 			free(query);
956bf34a23eSreyk 		}
957bf34a23eSreyk 		if (ret != 0)
958586dade4Sreyk 			return (NULL);
959586dade4Sreyk 	}
960586dade4Sreyk 	if (strstr(val, "$REMOTE_") != NULL) {
961586dade4Sreyk 		if (strstr(val, "$REMOTE_ADDR") != NULL) {
962586dade4Sreyk 			if (print_host(&clt->clt_ss,
963586dade4Sreyk 			    ibuf, sizeof(ibuf)) == NULL)
964586dade4Sreyk 				return (NULL);
965586dade4Sreyk 			if (expand_string(buf, len,
966586dade4Sreyk 			    "$REMOTE_ADDR", ibuf) != 0)
967586dade4Sreyk 				return (NULL);
968586dade4Sreyk 		}
969586dade4Sreyk 		if (strstr(val, "$REMOTE_PORT") != NULL) {
970586dade4Sreyk 			snprintf(ibuf, sizeof(ibuf),
971586dade4Sreyk 			    "%u", ntohs(clt->clt_port));
972586dade4Sreyk 			if (expand_string(buf, len,
973586dade4Sreyk 			    "$REMOTE_PORT", ibuf) != 0)
974586dade4Sreyk 				return (NULL);
975586dade4Sreyk 		}
976586dade4Sreyk 		if (strstr(val, "$REMOTE_USER") != NULL) {
977586dade4Sreyk 			if ((srv_conf->flags & SRVFLAG_AUTH) &&
978885c4aa1Sreyk 			    clt->clt_remote_user != NULL) {
979885c4aa1Sreyk 				if ((str = url_encode(clt->clt_remote_user))
980885c4aa1Sreyk 				    == NULL)
981885c4aa1Sreyk 					return (NULL);
982885c4aa1Sreyk 			} else
983885c4aa1Sreyk 				str = strdup("");
984885c4aa1Sreyk 			ret = expand_string(buf, len, "$REMOTE_USER", str);
985885c4aa1Sreyk 			free(str);
986885c4aa1Sreyk 			if (ret != 0)
987586dade4Sreyk 				return (NULL);
988586dade4Sreyk 		}
989586dade4Sreyk 	}
990586dade4Sreyk 	if (strstr(val, "$REQUEST_URI") != NULL) {
991bf34a23eSreyk 		if ((path = url_encode(desc->http_path)) == NULL)
992bf34a23eSreyk 			return (NULL);
993586dade4Sreyk 		if (desc->http_query == NULL) {
994bf34a23eSreyk 			str = path;
995bf34a23eSreyk 		} else {
996bf34a23eSreyk 			if ((query = url_encode(desc->http_query)) == NULL) {
997bf34a23eSreyk 				free(path);
998586dade4Sreyk 				return (NULL);
999586dade4Sreyk 			}
1000bf34a23eSreyk 			ret = asprintf(&str, "%s?%s", path, query);
1001bf34a23eSreyk 			free(path);
1002bf34a23eSreyk 			free(query);
1003bf34a23eSreyk 			if (ret == -1)
1004bf34a23eSreyk 				return (NULL);
1005bf34a23eSreyk 		}
1006bf34a23eSreyk 
1007bf34a23eSreyk 		ret = expand_string(buf, len, "$REQUEST_URI", str);
1008586dade4Sreyk 		free(str);
1009bf34a23eSreyk 		if (ret != 0)
1010bf34a23eSreyk 			return (NULL);
1011586dade4Sreyk 	}
1012586dade4Sreyk 	if (strstr(val, "$SERVER_") != NULL) {
1013586dade4Sreyk 		if (strstr(val, "$SERVER_ADDR") != NULL) {
1014586dade4Sreyk 			if (print_host(&srv_conf->ss,
1015586dade4Sreyk 			    ibuf, sizeof(ibuf)) == NULL)
1016586dade4Sreyk 				return (NULL);
1017586dade4Sreyk 			if (expand_string(buf, len,
1018586dade4Sreyk 			    "$SERVER_ADDR", ibuf) != 0)
1019586dade4Sreyk 				return (NULL);
1020586dade4Sreyk 		}
1021586dade4Sreyk 		if (strstr(val, "$SERVER_PORT") != NULL) {
1022586dade4Sreyk 			snprintf(ibuf, sizeof(ibuf), "%u",
1023586dade4Sreyk 			    ntohs(srv_conf->port));
1024586dade4Sreyk 			if (expand_string(buf, len,
1025586dade4Sreyk 			    "$SERVER_PORT", ibuf) != 0)
1026586dade4Sreyk 				return (NULL);
1027586dade4Sreyk 		}
1028586dade4Sreyk 		if (strstr(val, "$SERVER_NAME") != NULL) {
1029885c4aa1Sreyk 			if ((str = url_encode(srv_conf->name))
1030885c4aa1Sreyk 			     == NULL)
1031885c4aa1Sreyk 				return (NULL);
1032885c4aa1Sreyk 			ret = expand_string(buf, len, "$SERVER_NAME", str);
1033885c4aa1Sreyk 			free(str);
1034885c4aa1Sreyk 			if (ret != 0)
1035586dade4Sreyk 				return (NULL);
1036586dade4Sreyk 		}
1037586dade4Sreyk 	}
1038586dade4Sreyk 
1039586dade4Sreyk 	return (buf);
1040586dade4Sreyk }
1041586dade4Sreyk 
1042b7b6a941Sreyk int
10435fa30660Sreyk server_response(struct httpd *httpd, struct client *clt)
10445fa30660Sreyk {
1045b9fc9a72Sderaadt 	char			 path[PATH_MAX];
1046b9fc9a72Sderaadt 	char			 hostname[HOST_NAME_MAX+1];
1047d08e4976Sreyk 	struct http_descriptor	*desc = clt->clt_descreq;
1048d08e4976Sreyk 	struct http_descriptor	*resp = clt->clt_descresp;
1049d9bba0abSreyk 	struct server		*srv = clt->clt_srv;
1050de6550b1Sreyk 	struct server_config	*srv_conf = &srv->srv_conf;
10516af43371Sreyk 	struct kv		*kv, key, *host;
105259355b5aSreyk 	struct str_find		 sm;
105359355b5aSreyk 	int			 portval = -1, ret;
105477fd0032Sreyk 	char			*hostval;
105559355b5aSreyk 	const char		*errstr = NULL;
10565fa30660Sreyk 
1057c9351fd6Sreyk 	/* Canonicalize the request path */
1058c9351fd6Sreyk 	if (desc->http_path == NULL ||
1059a383dca2Sreyk 	    url_decode(desc->http_path) == NULL ||
1060c9351fd6Sreyk 	    canonicalize_path(desc->http_path, path, sizeof(path)) == NULL)
1061c9351fd6Sreyk 		goto fail;
1062c9351fd6Sreyk 	free(desc->http_path);
1063c9351fd6Sreyk 	if ((desc->http_path = strdup(path)) == NULL)
10645fa30660Sreyk 		goto fail;
10655fa30660Sreyk 
10666af43371Sreyk 	key.kv_key = "Host";
10676af43371Sreyk 	if ((host = kv_find(&desc->http_headers, &key)) != NULL &&
10686af43371Sreyk 	    host->kv_value == NULL)
10696af43371Sreyk 		host = NULL;
10706af43371Sreyk 
10715fa30660Sreyk 	if (strcmp(desc->http_version, "HTTP/1.1") == 0) {
10725fa30660Sreyk 		/* Host header is mandatory */
10736af43371Sreyk 		if (host == NULL)
10745fa30660Sreyk 			goto fail;
10755fa30660Sreyk 
10765fa30660Sreyk 		/* Is the connection persistent? */
1077091144dbSreyk 		key.kv_key = "Connection";
10785fa30660Sreyk 		if ((kv = kv_find(&desc->http_headers, &key)) != NULL &&
10795fa30660Sreyk 		    strcasecmp("close", kv->kv_value) == 0)
10805fa30660Sreyk 			clt->clt_persist = 0;
10815fa30660Sreyk 		else
1082091144dbSreyk 			clt->clt_persist++;
10835fa30660Sreyk 	} else {
1084091144dbSreyk 		/* Is the connection persistent? */
1085091144dbSreyk 		key.kv_key = "Connection";
1086091144dbSreyk 		if ((kv = kv_find(&desc->http_headers, &key)) != NULL &&
1087091144dbSreyk 		    strcasecmp("keep-alive", kv->kv_value) == 0)
1088091144dbSreyk 			clt->clt_persist++;
1089091144dbSreyk 		else
10905fa30660Sreyk 			clt->clt_persist = 0;
10915fa30660Sreyk 	}
10925fa30660Sreyk 
10937a5a4a11Sreyk 	if (clt->clt_persist >= srv_conf->maxrequests)
1094e139abd5Sreyk 		clt->clt_persist = 0;
1095e139abd5Sreyk 
1096d9bba0abSreyk 	/*
1097d9bba0abSreyk 	 * Do we have a Host header and matching configuration?
1098d9bba0abSreyk 	 * XXX the Host can also appear in the URL path.
1099d9bba0abSreyk 	 */
11006af43371Sreyk 	if (host != NULL) {
110177fd0032Sreyk 		if ((hostval = server_http_parsehost(host->kv_value,
110277fd0032Sreyk 		    hostname, sizeof(hostname), &portval)) == NULL)
110377fd0032Sreyk 			goto fail;
110477fd0032Sreyk 
1105d9bba0abSreyk 		TAILQ_FOREACH(srv_conf, &srv->srv_hosts, entry) {
1106bd1bab2fSreyk #ifdef DEBUG
1107bd1bab2fSreyk 			if ((srv_conf->flags & SRVFLAG_LOCATION) == 0) {
110877fd0032Sreyk 				DPRINTF("%s: virtual host \"%s:%u\""
110977fd0032Sreyk 				    " host \"%s\" (\"%s\")",
111077fd0032Sreyk 				    __func__, srv_conf->name,
111177fd0032Sreyk 				    ntohs(srv_conf->port), host->kv_value,
111277fd0032Sreyk 				    hostname);
1113bd1bab2fSreyk 			}
1114bd1bab2fSreyk #endif
111559355b5aSreyk 			if (srv_conf->flags & SRVFLAG_LOCATION)
111659355b5aSreyk 				continue;
111759355b5aSreyk 			else if (srv_conf->flags & SRVFLAG_SERVER_MATCH) {
111859355b5aSreyk 				str_find(hostname, srv_conf->name,
111959355b5aSreyk 				    &sm, 1, &errstr);
112059355b5aSreyk 				ret = errstr == NULL ? 0 : -1;
112159355b5aSreyk 			} else {
112259355b5aSreyk 				ret = fnmatch(srv_conf->name,
112359355b5aSreyk 				    hostname, FNM_CASEFOLD);
112459355b5aSreyk 			}
112559355b5aSreyk 			if (ret == 0 &&
112677fd0032Sreyk 			    (portval == -1 ||
112777fd0032Sreyk 			    (portval != -1 && portval == srv_conf->port))) {
1128d9bba0abSreyk 				/* Replace host configuration */
1129d9bba0abSreyk 				clt->clt_srv_conf = srv_conf;
11306af43371Sreyk 				srv_conf = NULL;
1131d9bba0abSreyk 				break;
1132d9bba0abSreyk 			}
1133d9bba0abSreyk 		}
1134d9bba0abSreyk 	}
1135d9bba0abSreyk 
11366af43371Sreyk 	if (srv_conf != NULL) {
11376af43371Sreyk 		/* Use the actual server IP address */
113847dc2a9dSreyk 		if (server_http_host(&clt->clt_srv_ss, hostname,
113947dc2a9dSreyk 		    sizeof(hostname)) == NULL)
11406af43371Sreyk 			goto fail;
11416af43371Sreyk 	} else {
11426af43371Sreyk 		/* Host header was valid and found */
114347dc2a9dSreyk 		if (strlcpy(hostname, host->kv_value, sizeof(hostname)) >=
114447dc2a9dSreyk 		    sizeof(hostname))
11456af43371Sreyk 			goto fail;
11464f194ec8Sreyk 		srv_conf = clt->clt_srv_conf;
11474f194ec8Sreyk 	}
11484f194ec8Sreyk 
114947dc2a9dSreyk 	if ((desc->http_host = strdup(hostname)) == NULL)
115047dc2a9dSreyk 		goto fail;
115147dc2a9dSreyk 
1152d08e4976Sreyk 	/* Now fill in the mandatory parts of the response descriptor */
1153d08e4976Sreyk 	resp->http_method = desc->http_method;
1154d08e4976Sreyk 	if ((resp->http_version = strdup(desc->http_version)) == NULL)
1155d08e4976Sreyk 		goto fail;
1156d08e4976Sreyk 
11574f194ec8Sreyk 	/* Now search for the location */
1158de6550b1Sreyk 	srv_conf = server_getlocation(clt, desc->http_path);
1159de6550b1Sreyk 
1160f8932becSreyk 	if (srv_conf->flags & SRVFLAG_BLOCK) {
1161f8932becSreyk 		server_abort_http(clt, srv_conf->return_code,
1162f8932becSreyk 		    srv_conf->return_uri);
1163f8932becSreyk 		return (-1);
1164f8932becSreyk 	} else if (srv_conf->flags & SRVFLAG_AUTH &&
1165e286121aSflorian 	    server_http_authenticate(srv_conf, clt) == -1) {
1166e286121aSflorian 		server_abort_http(clt, 401, srv_conf->auth_realm);
1167e286121aSflorian 		return (-1);
1168e286121aSflorian 	} else
1169de6550b1Sreyk 		return (server_file(httpd, clt));
1170de6550b1Sreyk  fail:
1171de6550b1Sreyk 	server_abort_http(clt, 400, "bad request");
1172de6550b1Sreyk 	return (-1);
1173de6550b1Sreyk }
1174de6550b1Sreyk 
11754624b10aSchrisz const char *
11764624b10aSchrisz server_root_strip(const char *path, int n)
11774624b10aSchrisz {
11784624b10aSchrisz 	const char *p;
11794624b10aSchrisz 
11804624b10aSchrisz 	/* Strip strip leading directories. Leading '/' is ignored. */
11814624b10aSchrisz 	for (; n > 0 && *path != '\0'; n--)
11824624b10aSchrisz 		if ((p = strchr(++path, '/')) == NULL)
11834624b10aSchrisz 			path = strchr(path, '\0');
11844624b10aSchrisz 		else
11854624b10aSchrisz 			path = p;
11864624b10aSchrisz 
11874624b10aSchrisz 	return (path);
11884624b10aSchrisz }
11894624b10aSchrisz 
1190de6550b1Sreyk struct server_config *
1191de6550b1Sreyk server_getlocation(struct client *clt, const char *path)
1192de6550b1Sreyk {
1193de6550b1Sreyk 	struct server		*srv = clt->clt_srv;
1194de6550b1Sreyk 	struct server_config	*srv_conf = clt->clt_srv_conf, *location;
119559355b5aSreyk 	const char		*errstr = NULL;
119659355b5aSreyk 	int			 ret;
1197de6550b1Sreyk 
1198de6550b1Sreyk 	/* Now search for the location */
11994f194ec8Sreyk 	TAILQ_FOREACH(location, &srv->srv_hosts, entry) {
1200bd1bab2fSreyk #ifdef DEBUG
1201bd1bab2fSreyk 		if (location->flags & SRVFLAG_LOCATION) {
1202bd1bab2fSreyk 			DPRINTF("%s: location \"%s\" path \"%s\"",
1203bd1bab2fSreyk 			    __func__, location->location, path);
1204bd1bab2fSreyk 		}
1205bd1bab2fSreyk #endif
12064f194ec8Sreyk 		if ((location->flags & SRVFLAG_LOCATION) &&
120759355b5aSreyk 		    location->parent_id == srv_conf->parent_id) {
120859355b5aSreyk 			errstr = NULL;
120959355b5aSreyk 			if (location->flags & SRVFLAG_LOCATION_MATCH) {
121059355b5aSreyk 				ret = str_match(path, location->location,
121159355b5aSreyk 				    &clt->clt_srv_match, &errstr);
121259355b5aSreyk 			} else {
121359355b5aSreyk 				ret = fnmatch(location->location,
121459355b5aSreyk 				    path, FNM_CASEFOLD);
121559355b5aSreyk 			}
121659355b5aSreyk 			if (ret == 0 && errstr == NULL) {
12174f194ec8Sreyk 				/* Replace host configuration */
1218435dc7cdSreyk 				clt->clt_srv_conf = srv_conf = location;
12194f194ec8Sreyk 				break;
12204f194ec8Sreyk 			}
12216af43371Sreyk 		}
122259355b5aSreyk 	}
12236af43371Sreyk 
1224de6550b1Sreyk 	return (srv_conf);
12255fa30660Sreyk }
12265fa30660Sreyk 
12275fa30660Sreyk int
12284703e0faSreyk server_response_http(struct client *clt, unsigned int code,
1229d1cfc522Skettenis     struct media_type *media, off_t size, time_t mtime)
12305fa30660Sreyk {
1231f5d55328Sflorian 	struct server_config	*srv_conf = clt->clt_srv_conf;
1232d08e4976Sreyk 	struct http_descriptor	*desc = clt->clt_descreq;
1233d08e4976Sreyk 	struct http_descriptor	*resp = clt->clt_descresp;
12345fa30660Sreyk 	const char		*error;
12355fa30660Sreyk 	struct kv		*ct, *cl;
12369f126950Sreyk 	char			 tmbuf[32];
12375fa30660Sreyk 
1238d24f6b1eSreyk 	if (desc == NULL || media == NULL ||
1239d24f6b1eSreyk 	    (error = server_httperror_byid(code)) == NULL)
12405fa30660Sreyk 		return (-1);
12415fa30660Sreyk 
1242ea62a379Sdoug 	if (server_log_http(clt, code, size) == -1)
1243ea62a379Sdoug 		return (-1);
1244ea62a379Sdoug 
12455fa30660Sreyk 	/* Add error codes */
12463323ac76Sbenno 	if (kv_setkey(&resp->http_pathquery, "%u", code) == -1 ||
1247d08e4976Sreyk 	    kv_set(&resp->http_pathquery, "%s", error) == -1)
12485fa30660Sreyk 		return (-1);
12495fa30660Sreyk 
12505fa30660Sreyk 	/* Add headers */
1251d08e4976Sreyk 	if (kv_add(&resp->http_headers, "Server", HTTPD_SERVERNAME) == NULL)
12525fa30660Sreyk 		return (-1);
12535fa30660Sreyk 
12545fa30660Sreyk 	/* Is it a persistent connection? */
12555fa30660Sreyk 	if (clt->clt_persist) {
1256d08e4976Sreyk 		if (kv_add(&resp->http_headers,
12575fa30660Sreyk 		    "Connection", "keep-alive") == NULL)
12585fa30660Sreyk 			return (-1);
1259d08e4976Sreyk 	} else if (kv_add(&resp->http_headers, "Connection", "close") == NULL)
12605fa30660Sreyk 		return (-1);
12615fa30660Sreyk 
12625fa30660Sreyk 	/* Set media type */
1263d08e4976Sreyk 	if ((ct = kv_add(&resp->http_headers, "Content-Type", NULL)) == NULL ||
1264d24f6b1eSreyk 	    kv_set(ct, "%s/%s", media->media_type, media->media_subtype) == -1)
12655fa30660Sreyk 		return (-1);
12665fa30660Sreyk 
12675fa30660Sreyk 	/* Set content length, if specified */
126841425654Sreyk 	if ((cl =
1269d08e4976Sreyk 	    kv_add(&resp->http_headers, "Content-Length", NULL)) == NULL ||
1270d1cfc522Skettenis 	    kv_set(cl, "%lld", (long long)size) == -1)
12715fa30660Sreyk 		return (-1);
12725fa30660Sreyk 
1273be5ab2e6Schrisz 	/* Set last modification time */
1274be5ab2e6Schrisz 	if (server_http_time(mtime, tmbuf, sizeof(tmbuf)) <= 0 ||
1275d08e4976Sreyk 	    kv_add(&resp->http_headers, "Last-Modified", tmbuf) == NULL)
1276be5ab2e6Schrisz 		return (-1);
1277be5ab2e6Schrisz 
1278f5d55328Sflorian 	/* HSTS header */
1279f5d55328Sflorian 	if (srv_conf->flags & SRVFLAG_SERVER_HSTS) {
1280f5d55328Sflorian 		if ((cl =
1281f5d55328Sflorian 		    kv_add(&resp->http_headers, "Strict-Transport-Security",
1282f5d55328Sflorian 		    NULL)) == NULL ||
12833323ac76Sbenno 		    kv_set(cl, "max-age=%d%s%s", srv_conf->hsts_max_age,
128452f7cd50Sreyk 		    srv_conf->hsts_flags & HSTSFLAG_SUBDOMAINS ?
128552f7cd50Sreyk 		    "; includeSubDomains" : "",
128652f7cd50Sreyk 		    srv_conf->hsts_flags & HSTSFLAG_PRELOAD ?
128752f7cd50Sreyk 		    "; preload" : "") == -1)
1288f5d55328Sflorian 			return (-1);
1289f5d55328Sflorian 	}
1290f5d55328Sflorian 
1291be5ab2e6Schrisz 	/* Date header is mandatory and should be added as late as possible */
1292be5ab2e6Schrisz 	if (server_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0 ||
1293d08e4976Sreyk 	    kv_add(&resp->http_headers, "Date", tmbuf) == NULL)
12949f126950Sreyk 		return (-1);
12959f126950Sreyk 
12965fa30660Sreyk 	/* Write completed header */
12975fa30660Sreyk 	if (server_writeresponse_http(clt) == -1 ||
12985fa30660Sreyk 	    server_bufferevent_print(clt, "\r\n") == -1 ||
1299d08e4976Sreyk 	    server_headers(clt, resp, server_writeheader_http, NULL) == -1 ||
13005fa30660Sreyk 	    server_bufferevent_print(clt, "\r\n") == -1)
13015fa30660Sreyk 		return (-1);
13025fa30660Sreyk 
1303d08e4976Sreyk 	if (size == 0 || resp->http_method == HTTP_METHOD_HEAD) {
13045fa30660Sreyk 		bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE);
13055fa30660Sreyk 		if (clt->clt_persist)
13065fa30660Sreyk 			clt->clt_toread = TOREAD_HTTP_HEADER;
13075fa30660Sreyk 		else
13085fa30660Sreyk 			clt->clt_toread = TOREAD_HTTP_NONE;
13095fa30660Sreyk 		clt->clt_done = 0;
13105fa30660Sreyk 		return (0);
13115fa30660Sreyk 	}
13125fa30660Sreyk 
13135fa30660Sreyk 	return (1);
13145fa30660Sreyk }
13155fa30660Sreyk 
13165fa30660Sreyk int
1317b7b6a941Sreyk server_writeresponse_http(struct client *clt)
1318b7b6a941Sreyk {
1319d08e4976Sreyk 	struct http_descriptor	*desc = clt->clt_descresp;
1320b7b6a941Sreyk 
1321b7b6a941Sreyk 	DPRINTF("version: %s rescode: %s resmsg: %s", desc->http_version,
1322b7b6a941Sreyk 	    desc->http_rescode, desc->http_resmesg);
1323b7b6a941Sreyk 
1324b7b6a941Sreyk 	if (server_bufferevent_print(clt, desc->http_version) == -1 ||
1325b7b6a941Sreyk 	    server_bufferevent_print(clt, " ") == -1 ||
1326b7b6a941Sreyk 	    server_bufferevent_print(clt, desc->http_rescode) == -1 ||
1327b7b6a941Sreyk 	    server_bufferevent_print(clt, " ") == -1 ||
1328b7b6a941Sreyk 	    server_bufferevent_print(clt, desc->http_resmesg) == -1)
1329b7b6a941Sreyk 		return (-1);
1330b7b6a941Sreyk 
1331b7b6a941Sreyk 	return (0);
1332b7b6a941Sreyk }
1333b7b6a941Sreyk 
1334b7b6a941Sreyk int
13354aa750c1Sreyk server_writeheader_http(struct client *clt, struct kv *hdr, void *arg)
1336b7b6a941Sreyk {
1337b7b6a941Sreyk 	char			*ptr;
1338b7b6a941Sreyk 	const char		*key;
1339b7b6a941Sreyk 
1340b7b6a941Sreyk 	if (hdr->kv_flags & KV_FLAG_INVALID)
1341b7b6a941Sreyk 		return (0);
1342b7b6a941Sreyk 
1343b7b6a941Sreyk 	/* The key might have been updated in the parent */
1344b7b6a941Sreyk 	if (hdr->kv_parent != NULL && hdr->kv_parent->kv_key != NULL)
1345b7b6a941Sreyk 		key = hdr->kv_parent->kv_key;
1346b7b6a941Sreyk 	else
1347b7b6a941Sreyk 		key = hdr->kv_key;
1348b7b6a941Sreyk 
1349b7b6a941Sreyk 	ptr = hdr->kv_value;
1350b7b6a941Sreyk 	if (server_bufferevent_print(clt, key) == -1 ||
1351b7b6a941Sreyk 	    (ptr != NULL &&
1352b7b6a941Sreyk 	    (server_bufferevent_print(clt, ": ") == -1 ||
1353b7b6a941Sreyk 	    server_bufferevent_print(clt, ptr) == -1 ||
1354b7b6a941Sreyk 	    server_bufferevent_print(clt, "\r\n") == -1)))
1355b7b6a941Sreyk 		return (-1);
1356b7b6a941Sreyk 	DPRINTF("%s: %s: %s", __func__, key,
1357b7b6a941Sreyk 	    hdr->kv_value == NULL ? "" : hdr->kv_value);
1358b7b6a941Sreyk 
1359b7b6a941Sreyk 	return (0);
1360b7b6a941Sreyk }
1361b7b6a941Sreyk 
1362b7b6a941Sreyk int
1363d08e4976Sreyk server_headers(struct client *clt, void *descp,
13644aa750c1Sreyk     int (*hdr_cb)(struct client *, struct kv *, void *), void *arg)
1365b7b6a941Sreyk {
1366b7b6a941Sreyk 	struct kv		*hdr, *kv;
1367d08e4976Sreyk 	struct http_descriptor	*desc = descp;
1368b7b6a941Sreyk 
1369b7b6a941Sreyk 	RB_FOREACH(hdr, kvtree, &desc->http_headers) {
13704aa750c1Sreyk 		if ((hdr_cb)(clt, hdr, arg) == -1)
1371b7b6a941Sreyk 			return (-1);
1372b7b6a941Sreyk 		TAILQ_FOREACH(kv, &hdr->kv_children, kv_entry) {
13734aa750c1Sreyk 			if ((hdr_cb)(clt, kv, arg) == -1)
1374b7b6a941Sreyk 				return (-1);
1375b7b6a941Sreyk 		}
1376b7b6a941Sreyk 	}
1377b7b6a941Sreyk 
1378b7b6a941Sreyk 	return (0);
1379b7b6a941Sreyk }
1380b7b6a941Sreyk 
1381b7b6a941Sreyk enum httpmethod
1382b7b6a941Sreyk server_httpmethod_byname(const char *name)
1383b7b6a941Sreyk {
1384b7b6a941Sreyk 	enum httpmethod		 id = HTTP_METHOD_NONE;
1385b7b6a941Sreyk 	struct http_method	 method, *res = NULL;
1386b7b6a941Sreyk 
1387b7b6a941Sreyk 	/* Set up key */
1388b7b6a941Sreyk 	method.method_name = name;
1389b7b6a941Sreyk 
1390b7b6a941Sreyk 	if ((res = bsearch(&method, http_methods,
1391b7b6a941Sreyk 	    sizeof(http_methods) / sizeof(http_methods[0]) - 1,
1392b7b6a941Sreyk 	    sizeof(http_methods[0]), server_httpmethod_cmp)) != NULL)
1393b7b6a941Sreyk 		id = res->method_id;
1394b7b6a941Sreyk 
1395b7b6a941Sreyk 	return (id);
1396b7b6a941Sreyk }
1397b7b6a941Sreyk 
1398b7b6a941Sreyk const char *
13994703e0faSreyk server_httpmethod_byid(unsigned int id)
1400b7b6a941Sreyk {
1401f0c872b4Sreyk 	const char	*name = "<UNKNOWN>";
1402b7b6a941Sreyk 	int		 i;
1403b7b6a941Sreyk 
1404b7b6a941Sreyk 	for (i = 0; http_methods[i].method_name != NULL; i++) {
1405b7b6a941Sreyk 		if (http_methods[i].method_id == id) {
1406b7b6a941Sreyk 			name = http_methods[i].method_name;
1407b7b6a941Sreyk 			break;
1408b7b6a941Sreyk 		}
1409b7b6a941Sreyk 	}
1410b7b6a941Sreyk 
1411b7b6a941Sreyk 	return (name);
1412b7b6a941Sreyk }
1413b7b6a941Sreyk 
1414b7b6a941Sreyk static int
1415b7b6a941Sreyk server_httpmethod_cmp(const void *a, const void *b)
1416b7b6a941Sreyk {
1417b7b6a941Sreyk 	const struct http_method *ma = a;
1418b7b6a941Sreyk 	const struct http_method *mb = b;
14191d89351eSstsp 
14201d89351eSstsp 	/*
14211d89351eSstsp 	 * RFC 2616 section 5.1.1 says that the method is case
14221d89351eSstsp 	 * sensitive so we don't do a strcasecmp here.
14231d89351eSstsp 	 */
1424b7b6a941Sreyk 	return (strcmp(ma->method_name, mb->method_name));
1425b7b6a941Sreyk }
1426b7b6a941Sreyk 
1427b7b6a941Sreyk const char *
14284703e0faSreyk server_httperror_byid(unsigned int id)
1429b7b6a941Sreyk {
14301a67c375Sreyk 	struct http_error	 error, *res;
1431b7b6a941Sreyk 
1432b7b6a941Sreyk 	/* Set up key */
1433b7b6a941Sreyk 	error.error_code = (int)id;
1434b7b6a941Sreyk 
14351a67c375Sreyk 	if ((res = bsearch(&error, http_errors,
1436b7b6a941Sreyk 	    sizeof(http_errors) / sizeof(http_errors[0]) - 1,
14371a67c375Sreyk 	    sizeof(http_errors[0]), server_httperror_cmp)) != NULL)
1438b7b6a941Sreyk 		return (res->error_name);
14391a67c375Sreyk 
14401a67c375Sreyk 	return (NULL);
1441b7b6a941Sreyk }
1442b7b6a941Sreyk 
1443b7b6a941Sreyk static int
1444b7b6a941Sreyk server_httperror_cmp(const void *a, const void *b)
1445b7b6a941Sreyk {
1446b7b6a941Sreyk 	const struct http_error *ea = a;
1447b7b6a941Sreyk 	const struct http_error *eb = b;
1448b7b6a941Sreyk 	return (ea->error_code - eb->error_code);
1449b7b6a941Sreyk }
1450ea62a379Sdoug 
1451ea62a379Sdoug int
14524703e0faSreyk server_log_http(struct client *clt, unsigned int code, size_t len)
1453ea62a379Sdoug {
1454ea62a379Sdoug 	static char		 tstamp[64];
1455ea62a379Sdoug 	static char		 ip[INET6_ADDRSTRLEN];
1456ea62a379Sdoug 	time_t			 t;
1457af3cfad1Sdoug 	struct kv		 key, *agent, *referrer;
1458ea62a379Sdoug 	struct tm		*tm;
1459ea62a379Sdoug 	struct server_config	*srv_conf;
1460ea62a379Sdoug 	struct http_descriptor	*desc;
1461e22b0c74Sreyk 	int			 ret = -1;
14626ebc1f19Ssemarie 	char			*user = NULL;
14636ebc1f19Ssemarie 	char			*path = NULL;
14646ebc1f19Ssemarie 	char			*query = NULL;
14656ebc1f19Ssemarie 	char			*version = NULL;
14666ebc1f19Ssemarie 	char			*referrer_v = NULL;
14676ebc1f19Ssemarie 	char			*agent_v = NULL;
1468ea62a379Sdoug 
1469ea62a379Sdoug 	if ((srv_conf = clt->clt_srv_conf) == NULL)
1470ea62a379Sdoug 		return (-1);
1471af3cfad1Sdoug 	if ((srv_conf->flags & SRVFLAG_LOG) == 0)
1472af3cfad1Sdoug 		return (0);
1473d08e4976Sreyk 	if ((desc = clt->clt_descreq) == NULL)
1474ea62a379Sdoug 		return (-1);
1475ea62a379Sdoug 
1476ea62a379Sdoug 	if ((t = time(NULL)) == -1)
1477ea62a379Sdoug 		return (-1);
1478ea62a379Sdoug 	if ((tm = localtime(&t)) == NULL)
1479ea62a379Sdoug 		return (-1);
1480ea62a379Sdoug 	if (strftime(tstamp, sizeof(tstamp), "%d/%b/%Y:%H:%M:%S %z", tm) == 0)
1481ea62a379Sdoug 		return (-1);
1482ea62a379Sdoug 
1483944a3fefSreyk 	if (print_host(&clt->clt_ss, ip, sizeof(ip)) == NULL)
1484944a3fefSreyk 		return (-1);
1485ea62a379Sdoug 
1486ea62a379Sdoug 	/*
1487ea62a379Sdoug 	 * For details on common log format, see:
1488ea62a379Sdoug 	 * https://httpd.apache.org/docs/current/mod/mod_log_config.html
1489af3cfad1Sdoug 	 *
1490af3cfad1Sdoug 	 * httpd's format is similar to these Apache LogFormats:
1491af3cfad1Sdoug 	 * "%v %h %l %u %t \"%r\" %>s %B"
1492af3cfad1Sdoug 	 * "%v %h %l %u %t \"%r\" %>s %B \"%{Referer}i\" \"%{User-agent}i\""
1493ea62a379Sdoug 	 */
1494ea62a379Sdoug 	switch (srv_conf->logformat) {
1495ea62a379Sdoug 	case LOG_FORMAT_COMMON:
1496e3c03affSreyk 		/* Use vis to encode input values from the header */
14976ebc1f19Ssemarie 		if (clt->clt_remote_user &&
1498ba87bb65Sreyk 		    stravis(&user, clt->clt_remote_user, HTTPD_LOGVIS) == -1)
1499e3c03affSreyk 			goto done;
1500e3c03affSreyk 		if (desc->http_version &&
1501ba87bb65Sreyk 		    stravis(&version, desc->http_version, HTTPD_LOGVIS) == -1)
15026ebc1f19Ssemarie 			goto done;
15036ebc1f19Ssemarie 
1504e3c03affSreyk 		/* The following should be URL-encoded */
15056ebc1f19Ssemarie 		if (desc->http_path &&
1506e22b0c74Sreyk 		    (path = url_encode(desc->http_path)) == NULL)
15076ebc1f19Ssemarie 			goto done;
15086ebc1f19Ssemarie 		if (desc->http_query &&
1509e22b0c74Sreyk 		    (query = url_encode(desc->http_query)) == NULL)
15106ebc1f19Ssemarie 			goto done;
15116ebc1f19Ssemarie 
15126ebc1f19Ssemarie 		ret = evbuffer_add_printf(clt->clt_log,
151341dcbd59Stim 		    "%s %s - %s [%s] \"%s %s%s%s%s%s\" %03d %zu\n",
151441dcbd59Stim 		    srv_conf->name, ip, clt->clt_remote_user == NULL ? "-" :
151541dcbd59Stim 		    user, tstamp,
1516ea62a379Sdoug 		    server_httpmethod_byid(desc->http_method),
15176ebc1f19Ssemarie 		    desc->http_path == NULL ? "" : path,
1518ea62a379Sdoug 		    desc->http_query == NULL ? "" : "?",
15196ebc1f19Ssemarie 		    desc->http_query == NULL ? "" : query,
1520ea62a379Sdoug 		    desc->http_version == NULL ? "" : " ",
15216ebc1f19Ssemarie 		    desc->http_version == NULL ? "" : version,
15226ebc1f19Ssemarie 		    code, len);
15236ebc1f19Ssemarie 
1524ea62a379Sdoug 		break;
1525ea62a379Sdoug 
1526ea62a379Sdoug 	case LOG_FORMAT_COMBINED:
1527ea62a379Sdoug 		key.kv_key = "Referer"; /* sic */
1528ea62a379Sdoug 		if ((referrer = kv_find(&desc->http_headers, &key)) != NULL &&
1529ea62a379Sdoug 		    referrer->kv_value == NULL)
1530ea62a379Sdoug 			referrer = NULL;
1531ea62a379Sdoug 
1532ea62a379Sdoug 		key.kv_key = "User-Agent";
1533ea62a379Sdoug 		if ((agent = kv_find(&desc->http_headers, &key)) != NULL &&
1534ea62a379Sdoug 		    agent->kv_value == NULL)
1535ea62a379Sdoug 			agent = NULL;
1536ea62a379Sdoug 
1537e3c03affSreyk 		/* Use vis to encode input values from the header */
15386ebc1f19Ssemarie 		if (clt->clt_remote_user &&
1539ba87bb65Sreyk 		    stravis(&user, clt->clt_remote_user, HTTPD_LOGVIS) == -1)
1540e3c03affSreyk 			goto done;
1541e3c03affSreyk 		if (desc->http_version &&
1542ba87bb65Sreyk 		    stravis(&version, desc->http_version, HTTPD_LOGVIS) == -1)
1543e3c03affSreyk 			goto done;
1544e3c03affSreyk 		if (agent &&
1545ba87bb65Sreyk 		    stravis(&agent_v, agent->kv_value, HTTPD_LOGVIS) == -1)
15466ebc1f19Ssemarie 			goto done;
15476ebc1f19Ssemarie 
1548e3c03affSreyk 		/* The following should be URL-encoded */
15496ebc1f19Ssemarie 		if (desc->http_path &&
1550e22b0c74Sreyk 		    (path = url_encode(desc->http_path)) == NULL)
15516ebc1f19Ssemarie 			goto done;
15526ebc1f19Ssemarie 		if (desc->http_query &&
1553e22b0c74Sreyk 		    (query = url_encode(desc->http_query)) == NULL)
15546ebc1f19Ssemarie 			goto done;
1555e22b0c74Sreyk 		if (referrer &&
1556e22b0c74Sreyk 		    (referrer_v = url_encode(referrer->kv_value)) == NULL)
15576ebc1f19Ssemarie 			goto done;
15586ebc1f19Ssemarie 
15596ebc1f19Ssemarie 		ret = evbuffer_add_printf(clt->clt_log,
156041dcbd59Stim 		    "%s %s - %s [%s] \"%s %s%s%s%s%s\""
15610afe0ae4Sreyk 		    " %03d %zu \"%s\" \"%s\"\n",
156241dcbd59Stim 		    srv_conf->name, ip, clt->clt_remote_user == NULL ? "-" :
156341dcbd59Stim 		    user, tstamp,
1564ea62a379Sdoug 		    server_httpmethod_byid(desc->http_method),
15656ebc1f19Ssemarie 		    desc->http_path == NULL ? "" : path,
1566ea62a379Sdoug 		    desc->http_query == NULL ? "" : "?",
15676ebc1f19Ssemarie 		    desc->http_query == NULL ? "" : query,
1568ea62a379Sdoug 		    desc->http_version == NULL ? "" : " ",
15696ebc1f19Ssemarie 		    desc->http_version == NULL ? "" : version,
1570ea62a379Sdoug 		    code, len,
15716ebc1f19Ssemarie 		    referrer == NULL ? "" : referrer_v,
15726ebc1f19Ssemarie 		    agent == NULL ? "" : agent_v);
15736ebc1f19Ssemarie 
1574ea62a379Sdoug 		break;
1575944a3fefSreyk 
1576944a3fefSreyk 	case LOG_FORMAT_CONNECTION:
1577e3c03affSreyk 		/* URL-encode the path */
15786ebc1f19Ssemarie 		if (desc->http_path &&
1579e22b0c74Sreyk 		    (path = url_encode(desc->http_path)) == NULL)
15806ebc1f19Ssemarie 			goto done;
15816ebc1f19Ssemarie 
15826ebc1f19Ssemarie 		ret = evbuffer_add_printf(clt->clt_log, " [%s]",
15836ebc1f19Ssemarie 		    desc->http_path == NULL ? "" : path);
15846ebc1f19Ssemarie 
1585944a3fefSreyk 		break;
1586ea62a379Sdoug 	}
1587ea62a379Sdoug 
15886ebc1f19Ssemarie done:
15896ebc1f19Ssemarie 	free(user);
15906ebc1f19Ssemarie 	free(path);
15916ebc1f19Ssemarie 	free(query);
15926ebc1f19Ssemarie 	free(version);
15936ebc1f19Ssemarie 	free(referrer_v);
15946ebc1f19Ssemarie 	free(agent_v);
15956ebc1f19Ssemarie 
15966ebc1f19Ssemarie 	return (ret);
1597ea62a379Sdoug }
1598