1*b32af659Sclaudio /* $OpenBSD: server_http.c,v 1.154 2024/02/13 14:00:24 claudio Exp $ */
2b7b6a941Sreyk
3b7b6a941Sreyk /*
4e96b74b9Sdenis * Copyright (c) 2020 Matthias Pressfreund <mpfr@fn.de>
593038d14Sreyk * Copyright (c) 2006 - 2018 Reyk Floeter <reyk@openbsd.org>
6b7b6a941Sreyk *
7b7b6a941Sreyk * Permission to use, copy, modify, and distribute this software for any
8b7b6a941Sreyk * purpose with or without fee is hereby granted, provided that the above
9b7b6a941Sreyk * copyright notice and this permission notice appear in all copies.
10b7b6a941Sreyk *
11b7b6a941Sreyk * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12b7b6a941Sreyk * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13b7b6a941Sreyk * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14b7b6a941Sreyk * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15b7b6a941Sreyk * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16b7b6a941Sreyk * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17b7b6a941Sreyk * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18b7b6a941Sreyk */
19b7b6a941Sreyk
20b7b6a941Sreyk #include <sys/types.h>
21b7b6a941Sreyk #include <sys/queue.h>
22b7b6a941Sreyk #include <sys/socket.h>
23b7b6a941Sreyk #include <sys/tree.h>
24e96b74b9Sdenis #include <sys/stat.h>
25b7b6a941Sreyk
26b7b6a941Sreyk #include <netinet/in.h>
2786f952e4Sreyk #include <arpa/inet.h>
28b7b6a941Sreyk
29b7b6a941Sreyk #include <errno.h>
30b7b6a941Sreyk #include <stdlib.h>
31b7b6a941Sreyk #include <string.h>
32b7b6a941Sreyk #include <unistd.h>
33b9fc9a72Sderaadt #include <limits.h>
3459355b5aSreyk #include <fnmatch.h>
35b7b6a941Sreyk #include <stdio.h>
3686f952e4Sreyk #include <time.h>
37e286121aSflorian #include <resolv.h>
38b7b6a941Sreyk #include <event.h>
3959355b5aSreyk #include <ctype.h>
40e3c03affSreyk #include <vis.h>
41e96b74b9Sdenis #include <fcntl.h>
42b7b6a941Sreyk
43b7b6a941Sreyk #include "httpd.h"
44b7b6a941Sreyk #include "http.h"
4559355b5aSreyk #include "patterns.h"
46b7b6a941Sreyk
47b7b6a941Sreyk static int server_httpmethod_cmp(const void *, const void *);
48b7b6a941Sreyk static int server_httperror_cmp(const void *, const void *);
49b7b6a941Sreyk void server_httpdesc_free(struct http_descriptor *);
50e286121aSflorian int server_http_authenticate(struct server_config *,
51e286121aSflorian struct client *);
52db043ce8Sbenno static int http_version_num(char *);
53586dade4Sreyk char *server_expand_http(struct client *, const char *,
54586dade4Sreyk char *, size_t);
55cbced0bdSian char *replace_var(char *, const char *, const char *);
56cbced0bdSian char *read_errdoc(const char *, const char *);
57b7b6a941Sreyk
58b7b6a941Sreyk static struct http_method http_methods[] = HTTP_METHODS;
59b7b6a941Sreyk static struct http_error http_errors[] = HTTP_ERRORS;
60b7b6a941Sreyk
61b7b6a941Sreyk void
server_http(void)62781985a7Srzalamena server_http(void)
63b7b6a941Sreyk {
64b7b6a941Sreyk DPRINTF("%s: sorting lookup tables, pid %d", __func__, getpid());
65b7b6a941Sreyk
66b7b6a941Sreyk /* Sort the HTTP lookup arrays */
67b7b6a941Sreyk qsort(http_methods, sizeof(http_methods) /
68b7b6a941Sreyk sizeof(http_methods[0]) - 1,
69b7b6a941Sreyk sizeof(http_methods[0]), server_httpmethod_cmp);
70b7b6a941Sreyk qsort(http_errors, sizeof(http_errors) /
71b7b6a941Sreyk sizeof(http_errors[0]) - 1,
72b7b6a941Sreyk sizeof(http_errors[0]), server_httperror_cmp);
73b7b6a941Sreyk }
74b7b6a941Sreyk
75b7b6a941Sreyk void
server_http_init(struct server * srv)76b7b6a941Sreyk server_http_init(struct server *srv)
77b7b6a941Sreyk {
78b7b6a941Sreyk /* nothing */
79b7b6a941Sreyk }
80b7b6a941Sreyk
81b7b6a941Sreyk int
server_httpdesc_init(struct client * clt)82b7b6a941Sreyk server_httpdesc_init(struct client *clt)
83b7b6a941Sreyk {
84b7b6a941Sreyk struct http_descriptor *desc;
85b7b6a941Sreyk
86b7b6a941Sreyk if ((desc = calloc(1, sizeof(*desc))) == NULL)
87b7b6a941Sreyk return (-1);
88b7b6a941Sreyk RB_INIT(&desc->http_headers);
89d08e4976Sreyk clt->clt_descreq = desc;
90d08e4976Sreyk
91d08e4976Sreyk if ((desc = calloc(1, sizeof(*desc))) == NULL) {
92d08e4976Sreyk /* req will be cleaned up later */
93d08e4976Sreyk return (-1);
94d08e4976Sreyk }
95d08e4976Sreyk RB_INIT(&desc->http_headers);
96d08e4976Sreyk clt->clt_descresp = desc;
97b7b6a941Sreyk
98b7b6a941Sreyk return (0);
99b7b6a941Sreyk }
100b7b6a941Sreyk
101b7b6a941Sreyk void
server_httpdesc_free(struct http_descriptor * desc)102b7b6a941Sreyk server_httpdesc_free(struct http_descriptor *desc)
103b7b6a941Sreyk {
104d08e4976Sreyk if (desc == NULL)
105d08e4976Sreyk return;
1063fe25232Sreyk
107b7b6a941Sreyk free(desc->http_path);
108b7b6a941Sreyk desc->http_path = NULL;
10916a88448Syasuoka free(desc->http_path_orig);
11016a88448Syasuoka desc->http_path_orig = NULL;
111de6550b1Sreyk free(desc->http_path_alias);
112de6550b1Sreyk desc->http_path_alias = NULL;
113b7b6a941Sreyk free(desc->http_query);
114b7b6a941Sreyk desc->http_query = NULL;
115a23b6848Stb free(desc->http_query_alias);
116a23b6848Stb desc->http_query_alias = NULL;
117b7b6a941Sreyk free(desc->http_version);
118b7b6a941Sreyk desc->http_version = NULL;
11947dc2a9dSreyk free(desc->http_host);
12047dc2a9dSreyk desc->http_host = NULL;
1213fe25232Sreyk
122b7b6a941Sreyk kv_purge(&desc->http_headers);
12344ed0680Sreyk desc->http_lastheader = NULL;
124d08e4976Sreyk desc->http_method = 0;
125d08e4976Sreyk desc->http_chunked = 0;
126b7b6a941Sreyk }
127b7b6a941Sreyk
128e286121aSflorian int
server_http_authenticate(struct server_config * srv_conf,struct client * clt)129e286121aSflorian server_http_authenticate(struct server_config *srv_conf, struct client *clt)
130e286121aSflorian {
1317d6098fdSreyk char decoded[1024];
132e286121aSflorian FILE *fp = NULL;
133e286121aSflorian struct http_descriptor *desc = clt->clt_descreq;
1341413cd80Sreyk const struct auth *auth = srv_conf->auth;
135e286121aSflorian struct kv *ba, key;
136e286121aSflorian size_t linesize = 0;
137e286121aSflorian ssize_t linelen;
138e286121aSflorian int ret = -1;
1397d6098fdSreyk char *line = NULL, *user = NULL, *pass = NULL;
1407d6098fdSreyk char *clt_user = NULL, *clt_pass = NULL;
141e286121aSflorian
142e286121aSflorian memset(decoded, 0, sizeof(decoded));
143e286121aSflorian key.kv_key = "Authorization";
144e286121aSflorian
145e286121aSflorian if ((ba = kv_find(&desc->http_headers, &key)) == NULL ||
146e286121aSflorian ba->kv_value == NULL)
147e286121aSflorian goto done;
148e286121aSflorian
149e286121aSflorian if (strncmp(ba->kv_value, "Basic ", strlen("Basic ")) != 0)
150e286121aSflorian goto done;
151e286121aSflorian
1524703e0faSreyk if (b64_pton(strchr(ba->kv_value, ' ') + 1, (uint8_t *)decoded,
153e286121aSflorian sizeof(decoded)) <= 0)
154e286121aSflorian goto done;
155e286121aSflorian
156e286121aSflorian if ((clt_pass = strchr(decoded, ':')) == NULL)
157e286121aSflorian goto done;
158e286121aSflorian
159e286121aSflorian clt_user = decoded;
160e286121aSflorian *clt_pass++ = '\0';
161d22ad799Sflorian if ((clt->clt_remote_user = strdup(clt_user)) == NULL)
162d22ad799Sflorian goto done;
163e286121aSflorian
164602531d9Sreyk if ((fp = fopen(auth->auth_htpasswd, "r")) == NULL)
165e286121aSflorian goto done;
166e286121aSflorian
167e286121aSflorian while ((linelen = getline(&line, &linesize, fp)) != -1) {
168e286121aSflorian if (line[linelen - 1] == '\n')
169e286121aSflorian line[linelen - 1] = '\0';
170e286121aSflorian user = line;
171e286121aSflorian pass = strchr(line, ':');
172e286121aSflorian
173e286121aSflorian if (pass == NULL) {
174e286121aSflorian explicit_bzero(line, linelen);
175e286121aSflorian continue;
176e286121aSflorian }
177e286121aSflorian
178e286121aSflorian *pass++ = '\0';
179e286121aSflorian
180e286121aSflorian if (strcmp(clt_user, user) != 0) {
181e286121aSflorian explicit_bzero(line, linelen);
182e286121aSflorian continue;
183e286121aSflorian }
184e286121aSflorian
185e286121aSflorian if (crypt_checkpass(clt_pass, pass) == 0) {
186e286121aSflorian explicit_bzero(line, linelen);
187e286121aSflorian ret = 0;
188e286121aSflorian break;
189e286121aSflorian }
190e286121aSflorian }
191e286121aSflorian done:
1923ca71284Ssunil free(line);
193e286121aSflorian if (fp != NULL)
194e286121aSflorian fclose(fp);
195e286121aSflorian
196e286121aSflorian if (ba != NULL && ba->kv_value != NULL) {
197e286121aSflorian explicit_bzero(ba->kv_value, strlen(ba->kv_value));
198e286121aSflorian explicit_bzero(decoded, sizeof(decoded));
199e286121aSflorian }
200e286121aSflorian
201e286121aSflorian return (ret);
202e286121aSflorian }
203e286121aSflorian
204db043ce8Sbenno static int
http_version_num(char * version)2058f1ed696Sbenno http_version_num(char *version)
2068f1ed696Sbenno {
207db043ce8Sbenno if (strlen(version) != 8 || strncmp(version, "HTTP/", 5) != 0
208db043ce8Sbenno || !isdigit((unsigned char)version[5]) || version[6] != '.'
209db043ce8Sbenno || !isdigit((unsigned char)version[7]))
210db043ce8Sbenno return (-1);
211db043ce8Sbenno if (version[5] == '0' && version[7] == '9')
2128f1ed696Sbenno return (9);
213db043ce8Sbenno if (version[5] == '1') {
214db043ce8Sbenno if (version[7] == '0')
2158f1ed696Sbenno return (10);
216db043ce8Sbenno else
2178f1ed696Sbenno /* any other version 1.x gets downgraded to 1.1 */
2188f1ed696Sbenno return (11);
219db043ce8Sbenno }
2208f1ed696Sbenno return (0);
2218f1ed696Sbenno }
2228f1ed696Sbenno
223b7b6a941Sreyk void
server_read_http(struct bufferevent * bev,void * arg)224b7b6a941Sreyk server_read_http(struct bufferevent *bev, void *arg)
225b7b6a941Sreyk {
226b7b6a941Sreyk struct client *clt = arg;
227d08e4976Sreyk struct http_descriptor *desc = clt->clt_descreq;
228b7b6a941Sreyk struct evbuffer *src = EVBUFFER_INPUT(bev);
229b7b6a941Sreyk char *line = NULL, *key, *value;
230b7b6a941Sreyk const char *errstr;
231a89b976fSclaudio char *http_version, *query;
232b7b6a941Sreyk size_t size, linelen;
2338f1ed696Sbenno int version;
234b7b6a941Sreyk struct kv *hdr = NULL;
235b7b6a941Sreyk
236b7b6a941Sreyk getmonotime(&clt->clt_tv_last);
237b7b6a941Sreyk
238b7b6a941Sreyk size = EVBUFFER_LENGTH(src);
239b7b6a941Sreyk DPRINTF("%s: session %d: size %lu, to read %lld",
240b7b6a941Sreyk __func__, clt->clt_id, size, clt->clt_toread);
241b7b6a941Sreyk if (!size) {
242b7b6a941Sreyk clt->clt_toread = TOREAD_HTTP_HEADER;
243b7b6a941Sreyk goto done;
244b7b6a941Sreyk }
245b7b6a941Sreyk
246619eca92Sreyk while (!clt->clt_headersdone) {
247619eca92Sreyk if (!clt->clt_line) {
248619eca92Sreyk /* Peek into the buffer to see if it looks like HTTP */
249619eca92Sreyk key = EVBUFFER_DATA(src);
25074875158Sguenther if (!isalpha((unsigned char)*key)) {
251619eca92Sreyk server_abort_http(clt, 400,
252619eca92Sreyk "invalid request line");
253619eca92Sreyk goto abort;
254619eca92Sreyk }
255619eca92Sreyk }
256619eca92Sreyk
257619eca92Sreyk if ((line = evbuffer_readln(src,
258619eca92Sreyk &linelen, EVBUFFER_EOL_CRLF_STRICT)) == NULL) {
259619eca92Sreyk /* No newline found after too many bytes */
260619eca92Sreyk if (size > SERVER_MAXHEADERLENGTH) {
261619eca92Sreyk server_abort_http(clt, 413,
262619eca92Sreyk "request line too long");
263619eca92Sreyk goto abort;
264619eca92Sreyk }
265619eca92Sreyk break;
266619eca92Sreyk }
267b7b6a941Sreyk
268b7b6a941Sreyk /*
269b7b6a941Sreyk * An empty line indicates the end of the request.
270b7b6a941Sreyk * libevent already stripped the \r\n for us.
271b7b6a941Sreyk */
272b7b6a941Sreyk if (!linelen) {
273bf6a310cSreyk clt->clt_headersdone = 1;
274b7b6a941Sreyk free(line);
275b7b6a941Sreyk break;
276b7b6a941Sreyk }
277b7b6a941Sreyk key = line;
278b7b6a941Sreyk
279b7b6a941Sreyk /* Limit the total header length minus \r\n */
280b7b6a941Sreyk clt->clt_headerlen += linelen;
281b7b6a941Sreyk if (clt->clt_headerlen > SERVER_MAXHEADERLENGTH) {
282b7b6a941Sreyk server_abort_http(clt, 413, "request too large");
2830859b34fSreyk goto abort;
284b7b6a941Sreyk }
285b7b6a941Sreyk
286b7b6a941Sreyk /*
287b7b6a941Sreyk * The first line is the GET/POST/PUT/... request,
288b7b6a941Sreyk * subsequent lines are HTTP headers.
289b7b6a941Sreyk */
290b7b6a941Sreyk if (++clt->clt_line == 1)
291b7b6a941Sreyk value = strchr(key, ' ');
292b7b6a941Sreyk else if (*key == ' ' || *key == '\t')
293b7b6a941Sreyk /* Multiline headers wrap with a space or tab */
294b7b6a941Sreyk value = NULL;
2956014b236Sbenno else {
2966014b236Sbenno /* Not a multiline header, should have a : */
297b7b6a941Sreyk value = strchr(key, ':');
298b7b6a941Sreyk if (value == NULL) {
2996014b236Sbenno server_abort_http(clt, 400, "malformed");
3006014b236Sbenno goto abort;
3016014b236Sbenno }
3026014b236Sbenno }
3036014b236Sbenno if (value == NULL) {
304b7b6a941Sreyk if (clt->clt_line == 1) {
305b7b6a941Sreyk server_abort_http(clt, 400, "malformed");
30603fd4389Sreyk goto abort;
307b7b6a941Sreyk }
308b7b6a941Sreyk
309b7b6a941Sreyk /* Append line to the last header, if present */
310b7b6a941Sreyk if (kv_extend(&desc->http_headers,
31103fd4389Sreyk desc->http_lastheader, line) == NULL)
312b7b6a941Sreyk goto fail;
313b7b6a941Sreyk
314b7b6a941Sreyk free(line);
315b7b6a941Sreyk continue;
316b7b6a941Sreyk }
317b7b6a941Sreyk if (*value == ':') {
318b7b6a941Sreyk *value++ = '\0';
319b7b6a941Sreyk value += strspn(value, " \t\r\n");
320b7b6a941Sreyk } else {
321b7b6a941Sreyk *value++ = '\0';
322b7b6a941Sreyk }
323b7b6a941Sreyk
324b7b6a941Sreyk DPRINTF("%s: session %d: header '%s: %s'", __func__,
325b7b6a941Sreyk clt->clt_id, key, value);
326b7b6a941Sreyk
327b7b6a941Sreyk /*
328b7b6a941Sreyk * Identify and handle specific HTTP request methods
329b7b6a941Sreyk */
330b7b6a941Sreyk if (clt->clt_line == 1) {
331b7b6a941Sreyk if ((desc->http_method = server_httpmethod_byname(key))
332fc062027Sreyk == HTTP_METHOD_NONE) {
333fc062027Sreyk server_abort_http(clt, 400, "malformed");
334fc062027Sreyk goto abort;
335fc062027Sreyk }
336b7b6a941Sreyk
337b7b6a941Sreyk /*
338b7b6a941Sreyk * Decode request path and query
339b7b6a941Sreyk */
340b7b6a941Sreyk desc->http_path = strdup(value);
34103fd4389Sreyk if (desc->http_path == NULL)
342b7b6a941Sreyk goto fail;
34303fd4389Sreyk
3448f1ed696Sbenno http_version = strchr(desc->http_path, ' ');
3458f1ed696Sbenno if (http_version == NULL) {
34635d60353Skrw server_abort_http(clt, 400, "malformed");
34735d60353Skrw goto abort;
34835d60353Skrw }
34903fd4389Sreyk
3508f1ed696Sbenno *http_version++ = '\0';
351b7b6a941Sreyk
352b7b6a941Sreyk /*
3538f1ed696Sbenno * We have to allocate the strings because they could
3542e2bd5b6Sreyk * be changed independently by the filters later.
3558f1ed696Sbenno * Allow HTTP version 0.9 to 1.1.
3568f1ed696Sbenno * Downgrade http version > 1.1 <= 1.9 to version 1.1.
3578f1ed696Sbenno * Return HTTP Version Not Supported for anything else.
358b7b6a941Sreyk */
3598f1ed696Sbenno
3608f1ed696Sbenno version = http_version_num(http_version);
3618f1ed696Sbenno
362db043ce8Sbenno if (version == -1) {
363db043ce8Sbenno server_abort_http(clt, 400, "malformed");
364db043ce8Sbenno goto abort;
365db043ce8Sbenno } else if (version == 0) {
3668f1ed696Sbenno server_abort_http(clt, 505, "bad http version");
3678f1ed696Sbenno goto abort;
3688f1ed696Sbenno } else if (version == 11) {
3692e2bd5b6Sreyk if ((desc->http_version =
3708f1ed696Sbenno strdup("HTTP/1.1")) == NULL)
371b7b6a941Sreyk goto fail;
3728f1ed696Sbenno } else {
3738f1ed696Sbenno if ((desc->http_version =
3748f1ed696Sbenno strdup(http_version)) == NULL)
3758f1ed696Sbenno goto fail;
3768f1ed696Sbenno }
37703fd4389Sreyk
378a89b976fSclaudio query = strchr(desc->http_path, '?');
379a89b976fSclaudio if (query != NULL) {
380a89b976fSclaudio *query++ = '\0';
381a89b976fSclaudio
382a89b976fSclaudio if ((desc->http_query = strdup(query)) == NULL)
383b7b6a941Sreyk goto fail;
384a89b976fSclaudio }
38503fd4389Sreyk
386b7b6a941Sreyk } else if (desc->http_method != HTTP_METHOD_NONE &&
387b7b6a941Sreyk strcasecmp("Content-Length", key) == 0) {
388b7b6a941Sreyk if (desc->http_method == HTTP_METHOD_TRACE ||
389b7b6a941Sreyk desc->http_method == HTTP_METHOD_CONNECT) {
390b7b6a941Sreyk /*
391b7b6a941Sreyk * These method should not have a body
392b7b6a941Sreyk * and thus no Content-Length header.
393b7b6a941Sreyk */
394b7b6a941Sreyk server_abort_http(clt, 400, "malformed");
395b7b6a941Sreyk goto abort;
396b7b6a941Sreyk }
397b7b6a941Sreyk
398b7b6a941Sreyk /*
399b7b6a941Sreyk * Need to read data from the client after the
400b7b6a941Sreyk * HTTP header.
401b7b6a941Sreyk * XXX What about non-standard clients not using
402b7b6a941Sreyk * the carriage return? And some browsers seem to
403b7b6a941Sreyk * include the line length in the content-length.
404b7b6a941Sreyk */
405b7b6a941Sreyk clt->clt_toread = strtonum(value, 0, LLONG_MAX,
406b7b6a941Sreyk &errstr);
407b7b6a941Sreyk if (errstr) {
408b7b6a941Sreyk server_abort_http(clt, 500, errstr);
409b7b6a941Sreyk goto abort;
410b7b6a941Sreyk }
411b7b6a941Sreyk }
412b7b6a941Sreyk
413b7b6a941Sreyk if (strcasecmp("Transfer-Encoding", key) == 0 &&
414b7b6a941Sreyk strcasecmp("chunked", value) == 0)
415b7b6a941Sreyk desc->http_chunked = 1;
416b7b6a941Sreyk
417b7b6a941Sreyk if (clt->clt_line != 1) {
418b7b6a941Sreyk if ((hdr = kv_add(&desc->http_headers, key,
41903fd4389Sreyk value)) == NULL)
420b7b6a941Sreyk goto fail;
42103fd4389Sreyk
422b7b6a941Sreyk desc->http_lastheader = hdr;
423b7b6a941Sreyk }
424b7b6a941Sreyk
425b7b6a941Sreyk free(line);
426b7b6a941Sreyk }
427bf6a310cSreyk if (clt->clt_headersdone) {
428b7b6a941Sreyk if (desc->http_method == HTTP_METHOD_NONE) {
429b7b6a941Sreyk server_abort_http(clt, 406, "no method");
430b7b6a941Sreyk return;
431b7b6a941Sreyk }
432b7b6a941Sreyk
433b7b6a941Sreyk switch (desc->http_method) {
434b7b6a941Sreyk case HTTP_METHOD_CONNECT:
435b7b6a941Sreyk /* Data stream */
436b7b6a941Sreyk clt->clt_toread = TOREAD_UNLIMITED;
437b7b6a941Sreyk bev->readcb = server_read;
438b7b6a941Sreyk break;
439b7b6a941Sreyk case HTTP_METHOD_GET:
440b7b6a941Sreyk case HTTP_METHOD_HEAD:
441f0c872b4Sreyk /* WebDAV methods */
442f0c872b4Sreyk case HTTP_METHOD_COPY:
443ac272b40Sreyk case HTTP_METHOD_MOVE:
444b7b6a941Sreyk clt->clt_toread = 0;
445b7b6a941Sreyk break;
446a3e115cfSreyk case HTTP_METHOD_DELETE:
447986dfe8aSreyk case HTTP_METHOD_OPTIONS:
448b7b6a941Sreyk case HTTP_METHOD_POST:
449b7b6a941Sreyk case HTTP_METHOD_PUT:
450b7b6a941Sreyk case HTTP_METHOD_RESPONSE:
451f0c872b4Sreyk /* WebDAV methods */
452f0c872b4Sreyk case HTTP_METHOD_PROPFIND:
453f0c872b4Sreyk case HTTP_METHOD_PROPPATCH:
454f0c872b4Sreyk case HTTP_METHOD_MKCOL:
455f0c872b4Sreyk case HTTP_METHOD_LOCK:
456f0c872b4Sreyk case HTTP_METHOD_UNLOCK:
457f0c872b4Sreyk case HTTP_METHOD_VERSION_CONTROL:
458f0c872b4Sreyk case HTTP_METHOD_REPORT:
459f0c872b4Sreyk case HTTP_METHOD_CHECKOUT:
460f0c872b4Sreyk case HTTP_METHOD_CHECKIN:
461f0c872b4Sreyk case HTTP_METHOD_UNCHECKOUT:
462f0c872b4Sreyk case HTTP_METHOD_MKWORKSPACE:
463f0c872b4Sreyk case HTTP_METHOD_UPDATE:
464f0c872b4Sreyk case HTTP_METHOD_LABEL:
465f0c872b4Sreyk case HTTP_METHOD_MERGE:
466f0c872b4Sreyk case HTTP_METHOD_BASELINE_CONTROL:
467f0c872b4Sreyk case HTTP_METHOD_MKACTIVITY:
468f0c872b4Sreyk case HTTP_METHOD_ORDERPATCH:
469f0c872b4Sreyk case HTTP_METHOD_ACL:
470f0c872b4Sreyk case HTTP_METHOD_MKREDIRECTREF:
471f0c872b4Sreyk case HTTP_METHOD_UPDATEREDIRECTREF:
472f0c872b4Sreyk case HTTP_METHOD_SEARCH:
473f0c872b4Sreyk case HTTP_METHOD_PATCH:
474b7b6a941Sreyk /* HTTP request payload */
475b7b6a941Sreyk if (clt->clt_toread > 0)
476b7b6a941Sreyk bev->readcb = server_read_httpcontent;
4773fc7a1c7Syasuoka if (clt->clt_toread < 0 && !desc->http_chunked)
4783fc7a1c7Syasuoka /* 7. of RFC 9112 Section 6.3 */
4793fc7a1c7Syasuoka clt->clt_toread = 0;
480b7b6a941Sreyk break;
481b7b6a941Sreyk default:
482f0c872b4Sreyk server_abort_http(clt, 405, "method not allowed");
483f0c872b4Sreyk return;
484b7b6a941Sreyk }
485b7b6a941Sreyk if (desc->http_chunked) {
486b7b6a941Sreyk /* Chunked transfer encoding */
487b7b6a941Sreyk clt->clt_toread = TOREAD_HTTP_CHUNK_LENGTH;
488b7b6a941Sreyk bev->readcb = server_read_httpchunks;
489b7b6a941Sreyk }
490b7b6a941Sreyk
491b7b6a941Sreyk done:
492b6a65335Sflorian if (clt->clt_toread != 0)
493b6a65335Sflorian bufferevent_disable(bev, EV_READ);
494781985a7Srzalamena server_response(httpd_env, clt);
495b7b6a941Sreyk return;
496b7b6a941Sreyk }
497b7b6a941Sreyk if (clt->clt_done) {
498b7b6a941Sreyk server_close(clt, "done");
499b7b6a941Sreyk return;
500b7b6a941Sreyk }
501b7b6a941Sreyk if (EVBUFFER_LENGTH(src) && bev->readcb != server_read_http)
502b7b6a941Sreyk bev->readcb(bev, arg);
503b7b6a941Sreyk bufferevent_enable(bev, EV_READ);
504b7b6a941Sreyk return;
505b7b6a941Sreyk fail:
506b7b6a941Sreyk server_abort_http(clt, 500, strerror(errno));
507b7b6a941Sreyk abort:
508b7b6a941Sreyk free(line);
509b7b6a941Sreyk }
510b7b6a941Sreyk
511b7b6a941Sreyk void
server_read_httpcontent(struct bufferevent * bev,void * arg)512b7b6a941Sreyk server_read_httpcontent(struct bufferevent *bev, void *arg)
513b7b6a941Sreyk {
514b7b6a941Sreyk struct client *clt = arg;
515b7b6a941Sreyk struct evbuffer *src = EVBUFFER_INPUT(bev);
516b7b6a941Sreyk size_t size;
517b7b6a941Sreyk
518b7b6a941Sreyk getmonotime(&clt->clt_tv_last);
519b7b6a941Sreyk
520b7b6a941Sreyk size = EVBUFFER_LENGTH(src);
521b7b6a941Sreyk DPRINTF("%s: session %d: size %lu, to read %lld", __func__,
522b7b6a941Sreyk clt->clt_id, size, clt->clt_toread);
523b7b6a941Sreyk if (!size)
524b7b6a941Sreyk return;
525b7b6a941Sreyk
526b7b6a941Sreyk if (clt->clt_toread > 0) {
527b7b6a941Sreyk /* Read content data */
528b7b6a941Sreyk if ((off_t)size > clt->clt_toread) {
529b7b6a941Sreyk size = clt->clt_toread;
530b6a65335Sflorian if (fcgi_add_stdin(clt, src) == -1)
531b7b6a941Sreyk goto fail;
532b7b6a941Sreyk clt->clt_toread = 0;
533b7b6a941Sreyk } else {
534b6a65335Sflorian if (fcgi_add_stdin(clt, src) == -1)
535b7b6a941Sreyk goto fail;
536b7b6a941Sreyk clt->clt_toread -= size;
537b7b6a941Sreyk }
538b7b6a941Sreyk DPRINTF("%s: done, size %lu, to read %lld", __func__,
539b7b6a941Sreyk size, clt->clt_toread);
540b7b6a941Sreyk }
541b7b6a941Sreyk if (clt->clt_toread == 0) {
542b6a65335Sflorian fcgi_add_stdin(clt, NULL);
543b7b6a941Sreyk clt->clt_toread = TOREAD_HTTP_HEADER;
544b6a65335Sflorian bufferevent_disable(bev, EV_READ);
545b7b6a941Sreyk bev->readcb = server_read_http;
546b6a65335Sflorian return;
547b7b6a941Sreyk }
548b7b6a941Sreyk if (clt->clt_done)
549b7b6a941Sreyk goto done;
550b7b6a941Sreyk if (bev->readcb != server_read_httpcontent)
551b7b6a941Sreyk bev->readcb(bev, arg);
552b6a65335Sflorian
553b7b6a941Sreyk return;
554b7b6a941Sreyk done:
555b7b6a941Sreyk return;
556b7b6a941Sreyk fail:
557b7b6a941Sreyk server_close(clt, strerror(errno));
558b7b6a941Sreyk }
559b7b6a941Sreyk
560b7b6a941Sreyk void
server_read_httpchunks(struct bufferevent * bev,void * arg)561b7b6a941Sreyk server_read_httpchunks(struct bufferevent *bev, void *arg)
562b7b6a941Sreyk {
563b7b6a941Sreyk struct client *clt = arg;
564b7b6a941Sreyk struct evbuffer *src = EVBUFFER_INPUT(bev);
565b7b6a941Sreyk char *line;
566b7b6a941Sreyk long long llval;
567b7b6a941Sreyk size_t size;
568b7b6a941Sreyk
569b7b6a941Sreyk getmonotime(&clt->clt_tv_last);
570b7b6a941Sreyk
571b7b6a941Sreyk size = EVBUFFER_LENGTH(src);
572b7b6a941Sreyk DPRINTF("%s: session %d: size %lu, to read %lld", __func__,
573b7b6a941Sreyk clt->clt_id, size, clt->clt_toread);
574b7b6a941Sreyk if (!size)
575b7b6a941Sreyk return;
576b7b6a941Sreyk
577b7b6a941Sreyk if (clt->clt_toread > 0) {
578b7b6a941Sreyk /* Read chunk data */
579b7b6a941Sreyk if ((off_t)size > clt->clt_toread) {
580b7b6a941Sreyk size = clt->clt_toread;
581b7b6a941Sreyk if (server_bufferevent_write_chunk(clt, src, size)
582b7b6a941Sreyk == -1)
583b7b6a941Sreyk goto fail;
584b7b6a941Sreyk clt->clt_toread = 0;
585b7b6a941Sreyk } else {
586b7b6a941Sreyk if (server_bufferevent_write_buffer(clt, src) == -1)
587b7b6a941Sreyk goto fail;
588b7b6a941Sreyk clt->clt_toread -= size;
589b7b6a941Sreyk }
590b7b6a941Sreyk DPRINTF("%s: done, size %lu, to read %lld", __func__,
591b7b6a941Sreyk size, clt->clt_toread);
592b7b6a941Sreyk }
593b7b6a941Sreyk switch (clt->clt_toread) {
594b7b6a941Sreyk case TOREAD_HTTP_CHUNK_LENGTH:
5958757e0ccSjsg line = evbuffer_readln(src, NULL, EVBUFFER_EOL_CRLF_STRICT);
596b7b6a941Sreyk if (line == NULL) {
597b7b6a941Sreyk /* Ignore empty line, continue */
598b7b6a941Sreyk bufferevent_enable(bev, EV_READ);
599b7b6a941Sreyk return;
600b7b6a941Sreyk }
601b7b6a941Sreyk if (strlen(line) == 0) {
602b7b6a941Sreyk free(line);
603b7b6a941Sreyk goto next;
604b7b6a941Sreyk }
605b7b6a941Sreyk
606b7b6a941Sreyk /*
607b7b6a941Sreyk * Read prepended chunk size in hex, ignore the trailer.
608b7b6a941Sreyk * The returned signed value must not be negative.
609b7b6a941Sreyk */
610b7b6a941Sreyk if (sscanf(line, "%llx", &llval) != 1 || llval < 0) {
611b7b6a941Sreyk free(line);
612b7b6a941Sreyk server_close(clt, "invalid chunk size");
613b7b6a941Sreyk return;
614b7b6a941Sreyk }
615b7b6a941Sreyk
616b7b6a941Sreyk if (server_bufferevent_print(clt, line) == -1 ||
617b7b6a941Sreyk server_bufferevent_print(clt, "\r\n") == -1) {
618b7b6a941Sreyk free(line);
619b7b6a941Sreyk goto fail;
620b7b6a941Sreyk }
621b7b6a941Sreyk free(line);
622b7b6a941Sreyk
623b7b6a941Sreyk if ((clt->clt_toread = llval) == 0) {
624b7b6a941Sreyk DPRINTF("%s: last chunk", __func__);
625b7b6a941Sreyk clt->clt_toread = TOREAD_HTTP_CHUNK_TRAILER;
626b7b6a941Sreyk }
627b7b6a941Sreyk break;
628b7b6a941Sreyk case TOREAD_HTTP_CHUNK_TRAILER:
629b7b6a941Sreyk /* Last chunk is 0 bytes followed by trailer and empty line */
6308757e0ccSjsg line = evbuffer_readln(src, NULL, EVBUFFER_EOL_CRLF_STRICT);
631b7b6a941Sreyk if (line == NULL) {
632b7b6a941Sreyk /* Ignore empty line, continue */
633b7b6a941Sreyk bufferevent_enable(bev, EV_READ);
634b7b6a941Sreyk return;
635b7b6a941Sreyk }
636b7b6a941Sreyk if (server_bufferevent_print(clt, line) == -1 ||
637b7b6a941Sreyk server_bufferevent_print(clt, "\r\n") == -1) {
638b7b6a941Sreyk free(line);
639b7b6a941Sreyk goto fail;
640b7b6a941Sreyk }
641b7b6a941Sreyk if (strlen(line) == 0) {
642b7b6a941Sreyk /* Switch to HTTP header mode */
643b7b6a941Sreyk clt->clt_toread = TOREAD_HTTP_HEADER;
644b7b6a941Sreyk bev->readcb = server_read_http;
645b7b6a941Sreyk }
646b7b6a941Sreyk free(line);
647b7b6a941Sreyk break;
648b7b6a941Sreyk case 0:
649b7b6a941Sreyk /* Chunk is terminated by an empty newline */
6508757e0ccSjsg line = evbuffer_readln(src, NULL, EVBUFFER_EOL_CRLF_STRICT);
651b7b6a941Sreyk free(line);
652b7b6a941Sreyk if (server_bufferevent_print(clt, "\r\n") == -1)
653b7b6a941Sreyk goto fail;
654b7b6a941Sreyk clt->clt_toread = TOREAD_HTTP_CHUNK_LENGTH;
655b7b6a941Sreyk break;
656b7b6a941Sreyk }
657b7b6a941Sreyk
658b7b6a941Sreyk next:
659b7b6a941Sreyk if (clt->clt_done)
660b7b6a941Sreyk goto done;
661b7b6a941Sreyk if (EVBUFFER_LENGTH(src))
662b7b6a941Sreyk bev->readcb(bev, arg);
663b7b6a941Sreyk bufferevent_enable(bev, EV_READ);
664b7b6a941Sreyk return;
665b7b6a941Sreyk
666b7b6a941Sreyk done:
667b7b6a941Sreyk server_close(clt, "last http chunk read (done)");
668b7b6a941Sreyk return;
669b7b6a941Sreyk fail:
670b7b6a941Sreyk server_close(clt, strerror(errno));
671b7b6a941Sreyk }
672b7b6a941Sreyk
673b7b6a941Sreyk void
server_read_httprange(struct bufferevent * bev,void * arg)674142cfc82Sreyk server_read_httprange(struct bufferevent *bev, void *arg)
675142cfc82Sreyk {
676142cfc82Sreyk struct client *clt = arg;
677142cfc82Sreyk struct evbuffer *src = EVBUFFER_INPUT(bev);
678142cfc82Sreyk size_t size;
679142cfc82Sreyk struct media_type *media;
680142cfc82Sreyk struct range_data *r = &clt->clt_ranges;
681142cfc82Sreyk struct range *range;
682142cfc82Sreyk
683142cfc82Sreyk getmonotime(&clt->clt_tv_last);
684142cfc82Sreyk
685142cfc82Sreyk if (r->range_toread > 0) {
686142cfc82Sreyk size = EVBUFFER_LENGTH(src);
687142cfc82Sreyk if (!size)
688142cfc82Sreyk return;
689142cfc82Sreyk
690142cfc82Sreyk /* Read chunk data */
691142cfc82Sreyk if ((off_t)size > r->range_toread) {
692142cfc82Sreyk size = r->range_toread;
693142cfc82Sreyk if (server_bufferevent_write_chunk(clt, src, size)
694142cfc82Sreyk == -1)
695142cfc82Sreyk goto fail;
696142cfc82Sreyk r->range_toread = 0;
697142cfc82Sreyk } else {
698142cfc82Sreyk if (server_bufferevent_write_buffer(clt, src) == -1)
699142cfc82Sreyk goto fail;
700142cfc82Sreyk r->range_toread -= size;
701142cfc82Sreyk }
702142cfc82Sreyk if (r->range_toread < 1)
703142cfc82Sreyk r->range_toread = TOREAD_HTTP_RANGE;
704142cfc82Sreyk DPRINTF("%s: done, size %lu, to read %lld", __func__,
705142cfc82Sreyk size, r->range_toread);
706142cfc82Sreyk }
707142cfc82Sreyk
708142cfc82Sreyk switch (r->range_toread) {
709142cfc82Sreyk case TOREAD_HTTP_RANGE:
710142cfc82Sreyk if (r->range_index >= r->range_count) {
711142cfc82Sreyk if (r->range_count > 1) {
712142cfc82Sreyk /* Add end marker */
713142cfc82Sreyk if (server_bufferevent_printf(clt,
714142cfc82Sreyk "\r\n--%llu--\r\n",
715142cfc82Sreyk clt->clt_boundary) == -1)
716142cfc82Sreyk goto fail;
717142cfc82Sreyk }
718142cfc82Sreyk r->range_toread = TOREAD_HTTP_NONE;
719142cfc82Sreyk break;
720142cfc82Sreyk }
721142cfc82Sreyk
722142cfc82Sreyk range = &r->range[r->range_index];
723142cfc82Sreyk
724142cfc82Sreyk if (r->range_count > 1) {
725142cfc82Sreyk media = r->range_media;
726142cfc82Sreyk if (server_bufferevent_printf(clt,
727142cfc82Sreyk "\r\n--%llu\r\n"
728142cfc82Sreyk "Content-Type: %s/%s\r\n"
729142cfc82Sreyk "Content-Range: bytes %lld-%lld/%zu\r\n\r\n",
730142cfc82Sreyk clt->clt_boundary,
731142cfc82Sreyk media->media_type, media->media_subtype,
732142cfc82Sreyk range->start, range->end, r->range_total) == -1)
733142cfc82Sreyk goto fail;
734142cfc82Sreyk }
735142cfc82Sreyk r->range_toread = range->end - range->start + 1;
736142cfc82Sreyk
737142cfc82Sreyk if (lseek(clt->clt_fd, range->start, SEEK_SET) == -1)
738142cfc82Sreyk goto fail;
739142cfc82Sreyk
740142cfc82Sreyk /* Throw away bytes that are already in the input buffer */
741142cfc82Sreyk evbuffer_drain(src, EVBUFFER_LENGTH(src));
742142cfc82Sreyk
743142cfc82Sreyk /* Increment for the next part */
744142cfc82Sreyk r->range_index++;
745142cfc82Sreyk break;
746142cfc82Sreyk case TOREAD_HTTP_NONE:
74710e99d49Sflorian goto done;
748142cfc82Sreyk case 0:
749142cfc82Sreyk break;
750142cfc82Sreyk }
751142cfc82Sreyk
752142cfc82Sreyk if (clt->clt_done)
753142cfc82Sreyk goto done;
754142cfc82Sreyk
755142cfc82Sreyk if (EVBUFFER_LENGTH(EVBUFFER_OUTPUT(clt->clt_bev)) > (size_t)
756142cfc82Sreyk SERVER_MAX_PREFETCH * clt->clt_sndbufsiz) {
757142cfc82Sreyk bufferevent_disable(clt->clt_srvbev, EV_READ);
758142cfc82Sreyk clt->clt_srvbev_throttled = 1;
759142cfc82Sreyk }
760142cfc82Sreyk
761142cfc82Sreyk return;
762142cfc82Sreyk done:
763142cfc82Sreyk (*bev->errorcb)(bev, EVBUFFER_READ, bev->cbarg);
764142cfc82Sreyk return;
765142cfc82Sreyk fail:
766142cfc82Sreyk server_close(clt, strerror(errno));
767142cfc82Sreyk }
768142cfc82Sreyk
769142cfc82Sreyk void
server_reset_http(struct client * clt)770b7b6a941Sreyk server_reset_http(struct client *clt)
771b7b6a941Sreyk {
772fa361cd1Sreyk struct server *srv = clt->clt_srv;
773b7b6a941Sreyk
774c145f9a8Sreyk server_log(clt, NULL);
775c145f9a8Sreyk
776d08e4976Sreyk server_httpdesc_free(clt->clt_descreq);
777d08e4976Sreyk server_httpdesc_free(clt->clt_descresp);
778b7b6a941Sreyk clt->clt_headerlen = 0;
779bf6a310cSreyk clt->clt_headersdone = 0;
780b7b6a941Sreyk clt->clt_done = 0;
781bf6a310cSreyk clt->clt_line = 0;
782ac2cdcb6Sreyk clt->clt_chunk = 0;
783daa1b608Sflorian free(clt->clt_remote_user);
784daa1b608Sflorian clt->clt_remote_user = NULL;
7855fa30660Sreyk clt->clt_bev->readcb = server_read_http;
786fa361cd1Sreyk clt->clt_srv_conf = &srv->srv_conf;
78759355b5aSreyk str_match_free(&clt->clt_srv_match);
788b7b6a941Sreyk }
789b7b6a941Sreyk
790be5ab2e6Schrisz ssize_t
server_http_time(time_t t,char * tmbuf,size_t len)791be5ab2e6Schrisz server_http_time(time_t t, char *tmbuf, size_t len)
7929f126950Sreyk {
7939f126950Sreyk struct tm tm;
7949f126950Sreyk
7959f126950Sreyk /* New HTTP/1.1 RFC 7231 prefers IMF-fixdate from RFC 5322 */
796be5ab2e6Schrisz if (t == -1 || gmtime_r(&t, &tm) == NULL)
797be5ab2e6Schrisz return (-1);
798be5ab2e6Schrisz else
799be5ab2e6Schrisz return (strftime(tmbuf, len, "%a, %d %h %Y %T %Z", &tm));
8009f126950Sreyk }
8019f126950Sreyk
8026af43371Sreyk const char *
server_http_host(struct sockaddr_storage * ss,char * buf,size_t len)8036af43371Sreyk server_http_host(struct sockaddr_storage *ss, char *buf, size_t len)
8046af43371Sreyk {
805b9fc9a72Sderaadt char hbuf[HOST_NAME_MAX+1];
8066af43371Sreyk in_port_t port;
8076af43371Sreyk
8086af43371Sreyk if (print_host(ss, buf, len) == NULL)
8096af43371Sreyk return (NULL);
8106af43371Sreyk
8116af43371Sreyk port = ntohs(server_socket_getport(ss));
8126af43371Sreyk if (port == HTTP_PORT)
8136af43371Sreyk return (buf);
8146af43371Sreyk
8156af43371Sreyk switch (ss->ss_family) {
8166af43371Sreyk case AF_INET:
8176af43371Sreyk if ((size_t)snprintf(hbuf, sizeof(hbuf),
8186af43371Sreyk "%s:%u", buf, port) >= sizeof(hbuf))
8196af43371Sreyk return (NULL);
8206af43371Sreyk break;
8216af43371Sreyk case AF_INET6:
8226af43371Sreyk if ((size_t)snprintf(hbuf, sizeof(hbuf),
8236af43371Sreyk "[%s]:%u", buf, port) >= sizeof(hbuf))
8246af43371Sreyk return (NULL);
8256af43371Sreyk break;
8266af43371Sreyk }
8276af43371Sreyk
8286af43371Sreyk if (strlcpy(buf, hbuf, len) >= len)
8296af43371Sreyk return (NULL);
8306af43371Sreyk
8316af43371Sreyk return (buf);
8326af43371Sreyk }
8336af43371Sreyk
83477fd0032Sreyk char *
server_http_parsehost(char * host,char * buf,size_t len,int * portval)83577fd0032Sreyk server_http_parsehost(char *host, char *buf, size_t len, int *portval)
83677fd0032Sreyk {
83777fd0032Sreyk char *start, *end, *port;
83877fd0032Sreyk const char *errstr = NULL;
83977fd0032Sreyk
84077fd0032Sreyk if (strlcpy(buf, host, len) >= len) {
84177fd0032Sreyk log_debug("%s: host name too long", __func__);
84277fd0032Sreyk return (NULL);
84377fd0032Sreyk }
84477fd0032Sreyk
84577fd0032Sreyk start = buf;
84677fd0032Sreyk end = port = NULL;
84777fd0032Sreyk
84877fd0032Sreyk if (*start == '[' && (end = strchr(start, ']')) != NULL) {
84977fd0032Sreyk /* Address enclosed in [] with port, eg. [2001:db8::1]:80 */
85077fd0032Sreyk start++;
85177fd0032Sreyk *end++ = '\0';
85277fd0032Sreyk if ((port = strchr(end, ':')) == NULL || *port == '\0')
85377fd0032Sreyk port = NULL;
85477fd0032Sreyk else
85577fd0032Sreyk port++;
85677fd0032Sreyk memmove(buf, start, strlen(start) + 1);
85777fd0032Sreyk } else if ((end = strchr(start, ':')) != NULL) {
85877fd0032Sreyk /* Name or address with port, eg. www.example.com:80 */
85977fd0032Sreyk *end++ = '\0';
86077fd0032Sreyk port = end;
86177fd0032Sreyk } else {
86277fd0032Sreyk /* Name or address with default port, eg. www.example.com */
86377fd0032Sreyk port = NULL;
86477fd0032Sreyk }
86577fd0032Sreyk
86677fd0032Sreyk if (port != NULL) {
86777fd0032Sreyk /* Save the requested port */
86877fd0032Sreyk *portval = strtonum(port, 0, 0xffff, &errstr);
86977fd0032Sreyk if (errstr != NULL) {
87077fd0032Sreyk log_debug("%s: invalid port: %s", __func__,
87177fd0032Sreyk strerror(errno));
87277fd0032Sreyk return (NULL);
87377fd0032Sreyk }
87477fd0032Sreyk *portval = htons(*portval);
87577fd0032Sreyk } else {
87677fd0032Sreyk /* Port not given, indicate the default port */
87777fd0032Sreyk *portval = -1;
87877fd0032Sreyk }
87977fd0032Sreyk
88077fd0032Sreyk return (start);
88177fd0032Sreyk }
88277fd0032Sreyk
883b7b6a941Sreyk void
server_abort_http(struct client * clt,unsigned int code,const char * msg)8844703e0faSreyk server_abort_http(struct client *clt, unsigned int code, const char *msg)
885b7b6a941Sreyk {
886f5d55328Sflorian struct server_config *srv_conf = clt->clt_srv_conf;
887b7b6a941Sreyk struct bufferevent *bev = clt->clt_bev;
8888b4c340eSflorian struct http_descriptor *desc = clt->clt_descreq;
8898b4c340eSflorian const char *httperr = NULL, *style;
8908b4c340eSflorian char *httpmsg, *body = NULL, *extraheader = NULL;
891f5d55328Sflorian char tmbuf[32], hbuf[128], *hstsheader = NULL;
892b82bc353Sflorian char *clenheader = NULL;
893bf34a23eSreyk char buf[IBUF_READ_SIZE];
8940674f400Ssemarie char *escapedmsg = NULL;
895cbced0bdSian char cstr[5];
896cbced0bdSian ssize_t bodylen;
897b7b6a941Sreyk
898f8932becSreyk if (code == 0) {
899f8932becSreyk server_close(clt, "dropped");
900f8932becSreyk return;
901f8932becSreyk }
902f8932becSreyk
903b7b6a941Sreyk if ((httperr = server_httperror_byid(code)) == NULL)
904b7b6a941Sreyk httperr = "Unknown Error";
905b7b6a941Sreyk
906b7b6a941Sreyk if (bev == NULL)
907b7b6a941Sreyk goto done;
908b7b6a941Sreyk
909e9b02f4aSreyk if (server_log_http(clt, code, 0) == -1)
910e9b02f4aSreyk goto done;
911e9b02f4aSreyk
912b7b6a941Sreyk /* Some system information */
9139fb8351aSreyk if (print_host(&srv_conf->ss, hbuf, sizeof(hbuf)) == NULL)
914b7b6a941Sreyk goto done;
915b7b6a941Sreyk
916be5ab2e6Schrisz if (server_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0)
917be5ab2e6Schrisz goto done;
918b7b6a941Sreyk
919b7b6a941Sreyk /* Do not send details of the Internal Server Error */
9205d9e55e4Sreyk switch (code) {
9215d9e55e4Sreyk case 301:
9225d9e55e4Sreyk case 302:
923586dade4Sreyk case 303:
9240cc11a24Sbenno case 307:
9250cc11a24Sbenno case 308:
926586dade4Sreyk if (msg == NULL)
927586dade4Sreyk break;
928586dade4Sreyk memset(buf, 0, sizeof(buf));
929bf34a23eSreyk if (server_expand_http(clt, msg, buf, sizeof(buf)) == NULL)
930586dade4Sreyk goto done;
931bf34a23eSreyk if (asprintf(&extraheader, "Location: %s\r\n", buf) == -1) {
9325d9e55e4Sreyk code = 500;
9335d9e55e4Sreyk extraheader = NULL;
9345d9e55e4Sreyk }
935bf34a23eSreyk msg = buf;
9365d9e55e4Sreyk break;
937e286121aSflorian case 401:
93860acb58bSjsg if (msg == NULL)
93960acb58bSjsg break;
9400674f400Ssemarie if (stravis(&escapedmsg, msg, VIS_DQ) == -1) {
9410674f400Ssemarie code = 500;
9420674f400Ssemarie extraheader = NULL;
9430674f400Ssemarie } else if (asprintf(&extraheader,
9440674f400Ssemarie "WWW-Authenticate: Basic realm=\"%s\"\r\n", escapedmsg)
9450674f400Ssemarie == -1) {
946e286121aSflorian code = 500;
947e286121aSflorian extraheader = NULL;
948e286121aSflorian }
949e286121aSflorian break;
950b0faf28cSflorian case 416:
95160acb58bSjsg if (msg == NULL)
95260acb58bSjsg break;
953b0faf28cSflorian if (asprintf(&extraheader,
954b0faf28cSflorian "Content-Range: %s\r\n", msg) == -1) {
955b0faf28cSflorian code = 500;
956b0faf28cSflorian extraheader = NULL;
957b0faf28cSflorian }
958b0faf28cSflorian break;
9595d9e55e4Sreyk default:
960cbebb5b9Sreyk /*
961cbebb5b9Sreyk * Do not send details of the error. Traditionally,
962cbebb5b9Sreyk * web servers responsed with the request path on 40x
963cbebb5b9Sreyk * errors which could be abused to inject JavaScript etc.
964cbebb5b9Sreyk * Instead of sanitizing the path here, we just don't
965cbebb5b9Sreyk * reprint it.
966cbebb5b9Sreyk */
9675d9e55e4Sreyk break;
9685d9e55e4Sreyk }
969b7b6a941Sreyk
9700674f400Ssemarie free(escapedmsg);
9710674f400Ssemarie
972cbced0bdSian if ((srv_conf->flags & SRVFLAG_ERRDOCS) == 0)
973cbced0bdSian goto builtin; /* errdocs not enabled */
974cbced0bdSian if ((size_t)snprintf(cstr, sizeof(cstr), "%03u", code) >= sizeof(cstr))
975cbced0bdSian goto builtin;
976cbced0bdSian
977cbced0bdSian if ((body = read_errdoc(srv_conf->errdocroot, cstr)) == NULL &&
978cbced0bdSian (body = read_errdoc(srv_conf->errdocroot, HTTPD_ERRDOCTEMPLATE))
979cbced0bdSian == NULL)
980cbced0bdSian goto builtin;
981cbced0bdSian
982cbced0bdSian body = replace_var(body, "$HTTP_ERROR", httperr);
983cbced0bdSian body = replace_var(body, "$RESPONSE_CODE", cstr);
984cbced0bdSian body = replace_var(body, "$SERVER_SOFTWARE", HTTPD_SERVERNAME);
985cbced0bdSian bodylen = strlen(body);
986cbced0bdSian goto send;
987cbced0bdSian
988cbced0bdSian builtin:
989b7b6a941Sreyk /* A CSS stylesheet allows minimal customization by the user */
990f687442bSreyk style = "body { background-color: white; color: black; font-family: "
991cbebb5b9Sreyk "'Comic Sans MS', 'Chalkboard SE', 'Comic Neue', sans-serif; }\n"
992aa3aed54Scwen "hr { border: 0; border-bottom: 1px dashed; }\n"
993aa3aed54Scwen "@media (prefers-color-scheme: dark) {\n"
994aa3aed54Scwen "body { background-color: #1E1F21; color: #EEEFF1; }\n"
995aa3aed54Scwen "a { color: #BAD7FF; }\n}";
9968b4c340eSflorian
9978b4c340eSflorian /* Generate simple HTML error document */
9988b4c340eSflorian if ((bodylen = asprintf(&body,
999d89214cdSreyk "<!DOCTYPE html>\n"
1000b7b6a941Sreyk "<html>\n"
1001b7b6a941Sreyk "<head>\n"
10028643c0edSbentley "<meta charset=\"utf-8\">\n"
1003b7b6a941Sreyk "<title>%03d %s</title>\n"
1004b7b6a941Sreyk "<style type=\"text/css\"><!--\n%s\n--></style>\n"
1005b7b6a941Sreyk "</head>\n"
1006b7b6a941Sreyk "<body>\n"
1007cbebb5b9Sreyk "<h1>%03d %s</h1>\n"
1008cbebb5b9Sreyk "<hr>\n<address>%s</address>\n"
1009b7b6a941Sreyk "</body>\n"
1010b7b6a941Sreyk "</html>\n",
101195d80e7aSjung code, httperr, style, code, httperr, HTTPD_SERVERNAME)) == -1) {
101295d80e7aSjung body = NULL;
10138b4c340eSflorian goto done;
101495d80e7aSjung }
10158b4c340eSflorian
1016cbced0bdSian send:
1017ae30f9e2Sbentley if (srv_conf->flags & SRVFLAG_SERVER_HSTS &&
1018ae30f9e2Sbentley srv_conf->flags & SRVFLAG_TLS) {
1019f5d55328Sflorian if (asprintf(&hstsheader, "Strict-Transport-Security: "
102052f7cd50Sreyk "max-age=%d%s%s\r\n", srv_conf->hsts_max_age,
102152f7cd50Sreyk srv_conf->hsts_flags & HSTSFLAG_SUBDOMAINS ?
102252f7cd50Sreyk "; includeSubDomains" : "",
102352f7cd50Sreyk srv_conf->hsts_flags & HSTSFLAG_PRELOAD ?
102495d80e7aSjung "; preload" : "") == -1) {
102595d80e7aSjung hstsheader = NULL;
1026f5d55328Sflorian goto done;
1027f5d55328Sflorian }
102895d80e7aSjung }
1029f5d55328Sflorian
1030b82bc353Sflorian if ((code >= 100 && code < 200) || code == 204)
1031b82bc353Sflorian clenheader = NULL;
1032b82bc353Sflorian else {
1033b82bc353Sflorian if (asprintf(&clenheader,
1034cbced0bdSian "Content-Length: %zd\r\n", bodylen) == -1) {
1035b82bc353Sflorian clenheader = NULL;
1036b82bc353Sflorian goto done;
1037b82bc353Sflorian }
1038b82bc353Sflorian }
1039b82bc353Sflorian
10408b4c340eSflorian /* Add basic HTTP headers */
10418b4c340eSflorian if (asprintf(&httpmsg,
10428b4c340eSflorian "HTTP/1.0 %03d %s\r\n"
10438b4c340eSflorian "Date: %s\r\n"
10448b4c340eSflorian "Server: %s\r\n"
10458b4c340eSflorian "Connection: close\r\n"
10468b4c340eSflorian "Content-Type: text/html\r\n"
1047b82bc353Sflorian "%s"
10488b4c340eSflorian "%s"
1049f5d55328Sflorian "%s"
10508b4c340eSflorian "\r\n"
10518b4c340eSflorian "%s",
1052b82bc353Sflorian code, httperr, tmbuf, HTTPD_SERVERNAME,
1053b82bc353Sflorian clenheader == NULL ? "" : clenheader,
10545d9e55e4Sreyk extraheader == NULL ? "" : extraheader,
1055f5d55328Sflorian hstsheader == NULL ? "" : hstsheader,
1056b82bc353Sflorian desc->http_method == HTTP_METHOD_HEAD || clenheader == NULL ?
1057b82bc353Sflorian "" : body) == -1)
1058b7b6a941Sreyk goto done;
1059b7b6a941Sreyk
1060b7b6a941Sreyk /* Dump the message without checking for success */
1061b7b6a941Sreyk server_dump(clt, httpmsg, strlen(httpmsg));
1062b7b6a941Sreyk free(httpmsg);
1063b7b6a941Sreyk
1064b7b6a941Sreyk done:
10658b4c340eSflorian free(body);
10665d9e55e4Sreyk free(extraheader);
1067f5d55328Sflorian free(hstsheader);
1068b82bc353Sflorian free(clenheader);
1069586dade4Sreyk if (msg == NULL)
1070586dade4Sreyk msg = "\"\"";
1071df9b638bSreyk if (asprintf(&httpmsg, "%s (%03d %s)", msg, code, httperr) == -1) {
1072b7b6a941Sreyk server_close(clt, msg);
1073df9b638bSreyk } else {
1074b7b6a941Sreyk server_close(clt, httpmsg);
1075b7b6a941Sreyk free(httpmsg);
1076b7b6a941Sreyk }
1077b7b6a941Sreyk }
1078b7b6a941Sreyk
1079b7b6a941Sreyk void
server_close_http(struct client * clt)1080b7b6a941Sreyk server_close_http(struct client *clt)
1081b7b6a941Sreyk {
1082d08e4976Sreyk struct http_descriptor *desc;
1083b7b6a941Sreyk
1084d08e4976Sreyk desc = clt->clt_descreq;
1085b7b6a941Sreyk server_httpdesc_free(desc);
1086b7b6a941Sreyk free(desc);
1087d08e4976Sreyk clt->clt_descreq = NULL;
1088d08e4976Sreyk
1089d08e4976Sreyk desc = clt->clt_descresp;
1090d08e4976Sreyk server_httpdesc_free(desc);
1091d08e4976Sreyk free(desc);
1092d08e4976Sreyk clt->clt_descresp = NULL;
1093daa1b608Sflorian free(clt->clt_remote_user);
1094daa1b608Sflorian clt->clt_remote_user = NULL;
109559355b5aSreyk
109659355b5aSreyk str_match_free(&clt->clt_srv_match);
1097b7b6a941Sreyk }
1098b7b6a941Sreyk
1099586dade4Sreyk char *
server_expand_http(struct client * clt,const char * val,char * buf,size_t len)1100586dade4Sreyk server_expand_http(struct client *clt, const char *val, char *buf,
1101586dade4Sreyk size_t len)
1102586dade4Sreyk {
1103586dade4Sreyk struct http_descriptor *desc = clt->clt_descreq;
1104586dade4Sreyk struct server_config *srv_conf = clt->clt_srv_conf;
1105fabb812fStb char ibuf[128], *str, *path, *query;
110659355b5aSreyk const char *errstr = NULL, *p;
110759355b5aSreyk size_t size;
110859355b5aSreyk int n, ret;
1109586dade4Sreyk
1110586dade4Sreyk if (strlcpy(buf, val, len) >= len)
1111586dade4Sreyk return (NULL);
1112586dade4Sreyk
111359355b5aSreyk /* Find previously matched substrings by index */
111459355b5aSreyk for (p = val; clt->clt_srv_match.sm_nmatch &&
111559355b5aSreyk (p = strstr(p, "%")) != NULL; p++) {
111600f3986fSreyk if (!isdigit((unsigned char)*(p + 1)))
111759355b5aSreyk continue;
111859355b5aSreyk
111959355b5aSreyk /* Copy number, leading '%' char and add trailing \0 */
112059355b5aSreyk size = strspn(p + 1, "0123456789") + 2;
112159355b5aSreyk if (size >= sizeof(ibuf))
112259355b5aSreyk return (NULL);
112359355b5aSreyk (void)strlcpy(ibuf, p, size);
112459355b5aSreyk n = strtonum(ibuf + 1, 0,
112559355b5aSreyk clt->clt_srv_match.sm_nmatch - 1, &errstr);
112659355b5aSreyk if (errstr != NULL)
112759355b5aSreyk return (NULL);
112859355b5aSreyk
112959355b5aSreyk /* Expand variable with matched value */
11300788fdf8Ssemarie if ((str = url_encode(clt->clt_srv_match.sm_match[n])) == NULL)
11310788fdf8Ssemarie return (NULL);
11320788fdf8Ssemarie ret = expand_string(buf, len, ibuf, str);
11330788fdf8Ssemarie free(str);
11340788fdf8Ssemarie if (ret != 0)
113559355b5aSreyk return (NULL);
113659355b5aSreyk }
1137586dade4Sreyk if (strstr(val, "$DOCUMENT_URI") != NULL) {
1138bf34a23eSreyk if ((path = url_encode(desc->http_path)) == NULL)
1139bf34a23eSreyk return (NULL);
1140bf34a23eSreyk ret = expand_string(buf, len, "$DOCUMENT_URI", path);
1141bf34a23eSreyk free(path);
1142bf34a23eSreyk if (ret != 0)
1143586dade4Sreyk return (NULL);
1144586dade4Sreyk }
1145fabb812fStb if (strstr(val, "$QUERY_STRING_ENC") != NULL) {
1146fabb812fStb if (desc->http_query == NULL) {
1147fabb812fStb ret = expand_string(buf, len, "$QUERY_STRING_ENC", "");
1148fabb812fStb } else {
1149fabb812fStb if ((query = url_encode(desc->http_query)) == NULL)
1150fabb812fStb return (NULL);
1151fabb812fStb ret = expand_string(buf, len, "$QUERY_STRING_ENC", query);
1152fabb812fStb free(query);
1153fabb812fStb }
1154fabb812fStb if (ret != 0)
1155fabb812fStb return (NULL);
1156fabb812fStb }
1157586dade4Sreyk if (strstr(val, "$QUERY_STRING") != NULL) {
1158bf34a23eSreyk if (desc->http_query == NULL) {
1159bf34a23eSreyk ret = expand_string(buf, len, "$QUERY_STRING", "");
1160bf34a23eSreyk } else {
116158963e4fSreyk ret = expand_string(buf, len, "$QUERY_STRING",
116258963e4fSreyk desc->http_query);
1163bf34a23eSreyk }
1164bf34a23eSreyk if (ret != 0)
1165586dade4Sreyk return (NULL);
1166586dade4Sreyk }
116711d703aaSflorian if (strstr(val, "$HTTP_HOST") != NULL) {
116811d703aaSflorian if (desc->http_host == NULL)
116911d703aaSflorian return (NULL);
117011d703aaSflorian if ((str = url_encode(desc->http_host)) == NULL)
117111d703aaSflorian return (NULL);
117211d703aaSflorian expand_string(buf, len, "$HTTP_HOST", str);
117311d703aaSflorian free(str);
117411d703aaSflorian }
1175586dade4Sreyk if (strstr(val, "$REMOTE_") != NULL) {
1176586dade4Sreyk if (strstr(val, "$REMOTE_ADDR") != NULL) {
1177586dade4Sreyk if (print_host(&clt->clt_ss,
1178586dade4Sreyk ibuf, sizeof(ibuf)) == NULL)
1179586dade4Sreyk return (NULL);
1180586dade4Sreyk if (expand_string(buf, len,
1181586dade4Sreyk "$REMOTE_ADDR", ibuf) != 0)
1182586dade4Sreyk return (NULL);
1183586dade4Sreyk }
1184586dade4Sreyk if (strstr(val, "$REMOTE_PORT") != NULL) {
1185586dade4Sreyk snprintf(ibuf, sizeof(ibuf),
1186586dade4Sreyk "%u", ntohs(clt->clt_port));
1187586dade4Sreyk if (expand_string(buf, len,
1188586dade4Sreyk "$REMOTE_PORT", ibuf) != 0)
1189586dade4Sreyk return (NULL);
1190586dade4Sreyk }
1191586dade4Sreyk if (strstr(val, "$REMOTE_USER") != NULL) {
1192586dade4Sreyk if ((srv_conf->flags & SRVFLAG_AUTH) &&
1193885c4aa1Sreyk clt->clt_remote_user != NULL) {
1194885c4aa1Sreyk if ((str = url_encode(clt->clt_remote_user))
1195885c4aa1Sreyk == NULL)
1196885c4aa1Sreyk return (NULL);
1197885c4aa1Sreyk } else
1198885c4aa1Sreyk str = strdup("");
1199885c4aa1Sreyk ret = expand_string(buf, len, "$REMOTE_USER", str);
1200885c4aa1Sreyk free(str);
1201885c4aa1Sreyk if (ret != 0)
1202586dade4Sreyk return (NULL);
1203586dade4Sreyk }
1204586dade4Sreyk }
1205586dade4Sreyk if (strstr(val, "$REQUEST_URI") != NULL) {
1206bf34a23eSreyk if ((path = url_encode(desc->http_path)) == NULL)
1207bf34a23eSreyk return (NULL);
1208586dade4Sreyk if (desc->http_query == NULL) {
1209bf34a23eSreyk str = path;
1210bf34a23eSreyk } else {
121158963e4fSreyk ret = asprintf(&str, "%s?%s", path, desc->http_query);
1212bf34a23eSreyk free(path);
1213bf34a23eSreyk if (ret == -1)
1214bf34a23eSreyk return (NULL);
1215bf34a23eSreyk }
1216bf34a23eSreyk
1217bf34a23eSreyk ret = expand_string(buf, len, "$REQUEST_URI", str);
1218586dade4Sreyk free(str);
1219bf34a23eSreyk if (ret != 0)
1220bf34a23eSreyk return (NULL);
1221586dade4Sreyk }
1222603a52d7Ssthen if (strstr(val, "$REQUEST_SCHEME") != NULL) {
1223603a52d7Ssthen if (srv_conf->flags & SRVFLAG_TLS) {
1224603a52d7Ssthen ret = expand_string(buf, len, "$REQUEST_SCHEME", "https");
1225603a52d7Ssthen } else {
1226603a52d7Ssthen ret = expand_string(buf, len, "$REQUEST_SCHEME", "http");
1227603a52d7Ssthen }
1228603a52d7Ssthen if (ret != 0)
1229603a52d7Ssthen return (NULL);
1230603a52d7Ssthen }
1231586dade4Sreyk if (strstr(val, "$SERVER_") != NULL) {
1232586dade4Sreyk if (strstr(val, "$SERVER_ADDR") != NULL) {
1233586dade4Sreyk if (print_host(&srv_conf->ss,
1234586dade4Sreyk ibuf, sizeof(ibuf)) == NULL)
1235586dade4Sreyk return (NULL);
1236586dade4Sreyk if (expand_string(buf, len,
1237586dade4Sreyk "$SERVER_ADDR", ibuf) != 0)
1238586dade4Sreyk return (NULL);
1239586dade4Sreyk }
1240586dade4Sreyk if (strstr(val, "$SERVER_PORT") != NULL) {
1241586dade4Sreyk snprintf(ibuf, sizeof(ibuf), "%u",
1242586dade4Sreyk ntohs(srv_conf->port));
1243586dade4Sreyk if (expand_string(buf, len,
1244586dade4Sreyk "$SERVER_PORT", ibuf) != 0)
1245586dade4Sreyk return (NULL);
1246586dade4Sreyk }
1247586dade4Sreyk if (strstr(val, "$SERVER_NAME") != NULL) {
1248885c4aa1Sreyk if ((str = url_encode(srv_conf->name))
1249885c4aa1Sreyk == NULL)
1250885c4aa1Sreyk return (NULL);
1251885c4aa1Sreyk ret = expand_string(buf, len, "$SERVER_NAME", str);
1252885c4aa1Sreyk free(str);
1253885c4aa1Sreyk if (ret != 0)
1254586dade4Sreyk return (NULL);
1255586dade4Sreyk }
1256586dade4Sreyk }
1257586dade4Sreyk
1258586dade4Sreyk return (buf);
1259586dade4Sreyk }
1260586dade4Sreyk
1261b7b6a941Sreyk int
server_response(struct httpd * httpd,struct client * clt)12625fa30660Sreyk server_response(struct httpd *httpd, struct client *clt)
12635fa30660Sreyk {
1264b9fc9a72Sderaadt char path[PATH_MAX];
1265b9fc9a72Sderaadt char hostname[HOST_NAME_MAX+1];
1266d08e4976Sreyk struct http_descriptor *desc = clt->clt_descreq;
1267d08e4976Sreyk struct http_descriptor *resp = clt->clt_descresp;
1268d9bba0abSreyk struct server *srv = clt->clt_srv;
1269de6550b1Sreyk struct server_config *srv_conf = &srv->srv_conf;
12706af43371Sreyk struct kv *kv, key, *host;
127159355b5aSreyk struct str_find sm;
127259355b5aSreyk int portval = -1, ret;
127393038d14Sreyk char *hostval, *query;
127459355b5aSreyk const char *errstr = NULL;
12755fa30660Sreyk
127616a88448Syasuoka /* Preserve original path */
1277c9351fd6Sreyk if (desc->http_path == NULL ||
127816a88448Syasuoka (desc->http_path_orig = strdup(desc->http_path)) == NULL)
127916a88448Syasuoka goto fail;
128016a88448Syasuoka
128116a88448Syasuoka /* Decode the URL */
128216a88448Syasuoka if (url_decode(desc->http_path) == NULL)
128393038d14Sreyk goto fail;
128493038d14Sreyk
128593038d14Sreyk /* Canonicalize the request path */
128693038d14Sreyk if (canonicalize_path(desc->http_path, path, sizeof(path)) == NULL)
1287c9351fd6Sreyk goto fail;
1288c9351fd6Sreyk free(desc->http_path);
1289c9351fd6Sreyk if ((desc->http_path = strdup(path)) == NULL)
12905fa30660Sreyk goto fail;
12915fa30660Sreyk
12926af43371Sreyk key.kv_key = "Host";
12936af43371Sreyk if ((host = kv_find(&desc->http_headers, &key)) != NULL &&
12946af43371Sreyk host->kv_value == NULL)
12956af43371Sreyk host = NULL;
12966af43371Sreyk
12975fa30660Sreyk if (strcmp(desc->http_version, "HTTP/1.1") == 0) {
12985fa30660Sreyk /* Host header is mandatory */
12996af43371Sreyk if (host == NULL)
13005fa30660Sreyk goto fail;
13015fa30660Sreyk
13025fa30660Sreyk /* Is the connection persistent? */
1303091144dbSreyk key.kv_key = "Connection";
13045fa30660Sreyk if ((kv = kv_find(&desc->http_headers, &key)) != NULL &&
13055fa30660Sreyk strcasecmp("close", kv->kv_value) == 0)
13065fa30660Sreyk clt->clt_persist = 0;
13075fa30660Sreyk else
1308091144dbSreyk clt->clt_persist++;
13095fa30660Sreyk } else {
1310091144dbSreyk /* Is the connection persistent? */
1311091144dbSreyk key.kv_key = "Connection";
1312091144dbSreyk if ((kv = kv_find(&desc->http_headers, &key)) != NULL &&
1313091144dbSreyk strcasecmp("keep-alive", kv->kv_value) == 0)
1314091144dbSreyk clt->clt_persist++;
1315091144dbSreyk else
13165fa30660Sreyk clt->clt_persist = 0;
13175fa30660Sreyk }
13185fa30660Sreyk
1319d9bba0abSreyk /*
1320d9bba0abSreyk * Do we have a Host header and matching configuration?
1321d9bba0abSreyk * XXX the Host can also appear in the URL path.
1322d9bba0abSreyk */
13236af43371Sreyk if (host != NULL) {
132477fd0032Sreyk if ((hostval = server_http_parsehost(host->kv_value,
132577fd0032Sreyk hostname, sizeof(hostname), &portval)) == NULL)
132677fd0032Sreyk goto fail;
132777fd0032Sreyk
1328d9bba0abSreyk TAILQ_FOREACH(srv_conf, &srv->srv_hosts, entry) {
1329bd1bab2fSreyk #ifdef DEBUG
1330bd1bab2fSreyk if ((srv_conf->flags & SRVFLAG_LOCATION) == 0) {
133177fd0032Sreyk DPRINTF("%s: virtual host \"%s:%u\""
133277fd0032Sreyk " host \"%s\" (\"%s\")",
133377fd0032Sreyk __func__, srv_conf->name,
133477fd0032Sreyk ntohs(srv_conf->port), host->kv_value,
133577fd0032Sreyk hostname);
1336bd1bab2fSreyk }
1337bd1bab2fSreyk #endif
133859355b5aSreyk if (srv_conf->flags & SRVFLAG_LOCATION)
133959355b5aSreyk continue;
134059355b5aSreyk else if (srv_conf->flags & SRVFLAG_SERVER_MATCH) {
134159355b5aSreyk str_find(hostname, srv_conf->name,
134259355b5aSreyk &sm, 1, &errstr);
134359355b5aSreyk ret = errstr == NULL ? 0 : -1;
134459355b5aSreyk } else {
134559355b5aSreyk ret = fnmatch(srv_conf->name,
134659355b5aSreyk hostname, FNM_CASEFOLD);
134759355b5aSreyk }
134859355b5aSreyk if (ret == 0 &&
1349b19a8c1eSbenno (portval == -1 || portval == srv_conf->port)) {
1350d9bba0abSreyk /* Replace host configuration */
1351d9bba0abSreyk clt->clt_srv_conf = srv_conf;
13526af43371Sreyk srv_conf = NULL;
1353d9bba0abSreyk break;
1354d9bba0abSreyk }
1355d9bba0abSreyk }
1356d9bba0abSreyk }
1357d9bba0abSreyk
13586af43371Sreyk if (srv_conf != NULL) {
13596af43371Sreyk /* Use the actual server IP address */
136047dc2a9dSreyk if (server_http_host(&clt->clt_srv_ss, hostname,
136147dc2a9dSreyk sizeof(hostname)) == NULL)
13626af43371Sreyk goto fail;
13636af43371Sreyk } else {
13646af43371Sreyk /* Host header was valid and found */
136547dc2a9dSreyk if (strlcpy(hostname, host->kv_value, sizeof(hostname)) >=
136647dc2a9dSreyk sizeof(hostname))
13676af43371Sreyk goto fail;
13684f194ec8Sreyk srv_conf = clt->clt_srv_conf;
13694f194ec8Sreyk }
13704f194ec8Sreyk
1371ca8e2b3eSbenno if (clt->clt_persist >= srv_conf->maxrequests)
1372ca8e2b3eSbenno clt->clt_persist = 0;
1373ca8e2b3eSbenno
1374ca8e2b3eSbenno /* pipelining should end after the first "idempotent" method */
1375ca8e2b3eSbenno if (clt->clt_pipelining && clt->clt_toread > 0)
1376ca8e2b3eSbenno clt->clt_persist = 0;
1377ca8e2b3eSbenno
137847dc2a9dSreyk if ((desc->http_host = strdup(hostname)) == NULL)
137947dc2a9dSreyk goto fail;
138047dc2a9dSreyk
1381d08e4976Sreyk /* Now fill in the mandatory parts of the response descriptor */
1382d08e4976Sreyk resp->http_method = desc->http_method;
1383d08e4976Sreyk if ((resp->http_version = strdup(desc->http_version)) == NULL)
1384d08e4976Sreyk goto fail;
1385d08e4976Sreyk
13864f194ec8Sreyk /* Now search for the location */
13878b6f8711Stb if ((srv_conf = server_getlocation(clt, desc->http_path)) == NULL) {
1388e96b74b9Sdenis server_abort_http(clt, 500, desc->http_path);
1389e96b74b9Sdenis return (-1);
1390e96b74b9Sdenis }
1391de6550b1Sreyk
139293038d14Sreyk /* Optional rewrite */
139393038d14Sreyk if (srv_conf->flags & SRVFLAG_PATH_REWRITE) {
139493038d14Sreyk /* Expand macros */
139593038d14Sreyk if (server_expand_http(clt, srv_conf->path,
139693038d14Sreyk path, sizeof(path)) == NULL)
139793038d14Sreyk goto fail;
139893038d14Sreyk
139993038d14Sreyk /*
140093038d14Sreyk * Reset and update the query. The updated query must already
140193038d14Sreyk * be URL encoded - either specified by the user or by using the
140293038d14Sreyk * original $QUERY_STRING.
140393038d14Sreyk */
1404a23b6848Stb free(desc->http_query_alias);
1405a23b6848Stb desc->http_query_alias = NULL;
140693038d14Sreyk if ((query = strchr(path, '?')) != NULL) {
140793038d14Sreyk *query++ = '\0';
1408a23b6848Stb if ((desc->http_query_alias = strdup(query)) == NULL)
140993038d14Sreyk goto fail;
141093038d14Sreyk }
141193038d14Sreyk
141293038d14Sreyk /* Canonicalize the updated request path */
141393038d14Sreyk if (canonicalize_path(path,
141493038d14Sreyk path, sizeof(path)) == NULL)
141593038d14Sreyk goto fail;
141693038d14Sreyk
1417a23b6848Stb log_debug("%s: rewrote %s?%s -> %s?%s", __func__,
14182c98785eSflorian desc->http_path, desc->http_query ? desc->http_query : "",
14192c98785eSflorian path, query ? query : "");
142093038d14Sreyk
1421a23b6848Stb free(desc->http_path_alias);
1422a23b6848Stb if ((desc->http_path_alias = strdup(path)) == NULL)
142393038d14Sreyk goto fail;
142493038d14Sreyk
142593038d14Sreyk /* Now search for the updated location */
1426e96b74b9Sdenis if ((srv_conf = server_getlocation(clt,
1427e96b74b9Sdenis desc->http_path_alias)) == NULL) {
1428e96b74b9Sdenis server_abort_http(clt, 500, desc->http_path_alias);
1429e96b74b9Sdenis return (-1);
1430e96b74b9Sdenis }
143193038d14Sreyk }
143293038d14Sreyk
1433f0a27d98Sflorian if (clt->clt_toread > 0 && (size_t)clt->clt_toread >
1434f0a27d98Sflorian srv_conf->maxrequestbody) {
1435fa3025f4Sbenno server_abort_http(clt, 413, "request body too large");
1436f0a27d98Sflorian return (-1);
1437f0a27d98Sflorian }
1438f0a27d98Sflorian
1439f8932becSreyk if (srv_conf->flags & SRVFLAG_BLOCK) {
1440f8932becSreyk server_abort_http(clt, srv_conf->return_code,
1441f8932becSreyk srv_conf->return_uri);
1442f8932becSreyk return (-1);
1443f8932becSreyk } else if (srv_conf->flags & SRVFLAG_AUTH &&
1444e286121aSflorian server_http_authenticate(srv_conf, clt) == -1) {
1445e286121aSflorian server_abort_http(clt, 401, srv_conf->auth_realm);
1446e286121aSflorian return (-1);
1447e286121aSflorian } else
1448de6550b1Sreyk return (server_file(httpd, clt));
1449de6550b1Sreyk fail:
1450de6550b1Sreyk server_abort_http(clt, 400, "bad request");
1451de6550b1Sreyk return (-1);
1452de6550b1Sreyk }
1453de6550b1Sreyk
14544624b10aSchrisz const char *
server_root_strip(const char * path,int n)14554624b10aSchrisz server_root_strip(const char *path, int n)
14564624b10aSchrisz {
14574624b10aSchrisz const char *p;
14584624b10aSchrisz
14594624b10aSchrisz /* Strip strip leading directories. Leading '/' is ignored. */
14604624b10aSchrisz for (; n > 0 && *path != '\0'; n--)
14614624b10aSchrisz if ((p = strchr(++path, '/')) == NULL)
14624624b10aSchrisz path = strchr(path, '\0');
14634624b10aSchrisz else
14644624b10aSchrisz path = p;
14654624b10aSchrisz
14664624b10aSchrisz return (path);
14674624b10aSchrisz }
14684624b10aSchrisz
1469de6550b1Sreyk struct server_config *
server_getlocation(struct client * clt,const char * path)1470de6550b1Sreyk server_getlocation(struct client *clt, const char *path)
1471de6550b1Sreyk {
1472de6550b1Sreyk struct server *srv = clt->clt_srv;
1473de6550b1Sreyk struct server_config *srv_conf = clt->clt_srv_conf, *location;
147459355b5aSreyk const char *errstr = NULL;
147559355b5aSreyk int ret;
1476de6550b1Sreyk
1477de6550b1Sreyk /* Now search for the location */
14784f194ec8Sreyk TAILQ_FOREACH(location, &srv->srv_hosts, entry) {
1479bd1bab2fSreyk #ifdef DEBUG
1480bd1bab2fSreyk if (location->flags & SRVFLAG_LOCATION) {
1481bd1bab2fSreyk DPRINTF("%s: location \"%s\" path \"%s\"",
1482bd1bab2fSreyk __func__, location->location, path);
1483bd1bab2fSreyk }
1484bd1bab2fSreyk #endif
14854f194ec8Sreyk if ((location->flags & SRVFLAG_LOCATION) &&
148659355b5aSreyk location->parent_id == srv_conf->parent_id) {
148759355b5aSreyk errstr = NULL;
148859355b5aSreyk if (location->flags & SRVFLAG_LOCATION_MATCH) {
148959355b5aSreyk ret = str_match(path, location->location,
149059355b5aSreyk &clt->clt_srv_match, &errstr);
149159355b5aSreyk } else {
149259355b5aSreyk ret = fnmatch(location->location,
149359355b5aSreyk path, FNM_CASEFOLD);
149459355b5aSreyk }
149559355b5aSreyk if (ret == 0 && errstr == NULL) {
1496e96b74b9Sdenis if ((ret = server_locationaccesstest(location,
1497e96b74b9Sdenis path)) == -1)
1498e96b74b9Sdenis return (NULL);
1499e96b74b9Sdenis
1500e96b74b9Sdenis if (ret)
1501e96b74b9Sdenis continue;
15024f194ec8Sreyk /* Replace host configuration */
1503435dc7cdSreyk clt->clt_srv_conf = srv_conf = location;
15044f194ec8Sreyk break;
15054f194ec8Sreyk }
15066af43371Sreyk }
150759355b5aSreyk }
15086af43371Sreyk
1509de6550b1Sreyk return (srv_conf);
15105fa30660Sreyk }
15115fa30660Sreyk
15125fa30660Sreyk int
server_locationaccesstest(struct server_config * srv_conf,const char * path)1513e96b74b9Sdenis server_locationaccesstest(struct server_config *srv_conf, const char *path)
1514e96b74b9Sdenis {
1515e96b74b9Sdenis int rootfd, ret;
1516e96b74b9Sdenis struct stat sb;
1517e96b74b9Sdenis
1518e96b74b9Sdenis if (((SRVFLAG_LOCATION_FOUND | SRVFLAG_LOCATION_NOT_FOUND) &
1519e96b74b9Sdenis srv_conf->flags) == 0)
1520e96b74b9Sdenis return (0);
1521e96b74b9Sdenis
1522e96b74b9Sdenis if ((rootfd = open(srv_conf->root, O_RDONLY)) == -1)
1523e96b74b9Sdenis return (-1);
1524e96b74b9Sdenis
1525e96b74b9Sdenis path = server_root_strip(path, srv_conf->strip) + 1;
1526e96b74b9Sdenis if ((ret = faccessat(rootfd, path, R_OK, 0)) != -1)
1527e96b74b9Sdenis ret = fstatat(rootfd, path, &sb, 0);
1528e96b74b9Sdenis close(rootfd);
1529e96b74b9Sdenis return ((ret == -1 && SRVFLAG_LOCATION_FOUND & srv_conf->flags) ||
1530e96b74b9Sdenis (ret == 0 && SRVFLAG_LOCATION_NOT_FOUND & srv_conf->flags));
1531e96b74b9Sdenis }
1532e96b74b9Sdenis
1533e96b74b9Sdenis int
server_response_http(struct client * clt,unsigned int code,struct media_type * media,off_t size,time_t mtime)15344703e0faSreyk server_response_http(struct client *clt, unsigned int code,
1535d1cfc522Skettenis struct media_type *media, off_t size, time_t mtime)
15365fa30660Sreyk {
1537f5d55328Sflorian struct server_config *srv_conf = clt->clt_srv_conf;
1538d08e4976Sreyk struct http_descriptor *desc = clt->clt_descreq;
1539d08e4976Sreyk struct http_descriptor *resp = clt->clt_descresp;
15405fa30660Sreyk const char *error;
15415fa30660Sreyk struct kv *ct, *cl;
15429f126950Sreyk char tmbuf[32];
15435fa30660Sreyk
1544d24f6b1eSreyk if (desc == NULL || media == NULL ||
1545d24f6b1eSreyk (error = server_httperror_byid(code)) == NULL)
15465fa30660Sreyk return (-1);
15475fa30660Sreyk
1548cc526105Sreyk if (server_log_http(clt, code, size >= 0 ? size : 0) == -1)
1549ea62a379Sdoug return (-1);
1550ea62a379Sdoug
15515fa30660Sreyk /* Add error codes */
15523323ac76Sbenno if (kv_setkey(&resp->http_pathquery, "%u", code) == -1 ||
1553d08e4976Sreyk kv_set(&resp->http_pathquery, "%s", error) == -1)
15545fa30660Sreyk return (-1);
15555fa30660Sreyk
15565fa30660Sreyk /* Add headers */
1557d08e4976Sreyk if (kv_add(&resp->http_headers, "Server", HTTPD_SERVERNAME) == NULL)
15585fa30660Sreyk return (-1);
15595fa30660Sreyk
15605fa30660Sreyk /* Is it a persistent connection? */
15615fa30660Sreyk if (clt->clt_persist) {
1562d08e4976Sreyk if (kv_add(&resp->http_headers,
15635fa30660Sreyk "Connection", "keep-alive") == NULL)
15645fa30660Sreyk return (-1);
1565d08e4976Sreyk } else if (kv_add(&resp->http_headers, "Connection", "close") == NULL)
15665fa30660Sreyk return (-1);
15675fa30660Sreyk
15685fa30660Sreyk /* Set media type */
1569d08e4976Sreyk if ((ct = kv_add(&resp->http_headers, "Content-Type", NULL)) == NULL ||
1570d24f6b1eSreyk kv_set(ct, "%s/%s", media->media_type, media->media_subtype) == -1)
15715fa30660Sreyk return (-1);
15725fa30660Sreyk
15735fa30660Sreyk /* Set content length, if specified */
1574cc526105Sreyk if (size >= 0 && ((cl =
1575d08e4976Sreyk kv_add(&resp->http_headers, "Content-Length", NULL)) == NULL ||
1576cc526105Sreyk kv_set(cl, "%lld", (long long)size) == -1))
15775fa30660Sreyk return (-1);
15785fa30660Sreyk
1579be5ab2e6Schrisz /* Set last modification time */
1580be5ab2e6Schrisz if (server_http_time(mtime, tmbuf, sizeof(tmbuf)) <= 0 ||
1581d08e4976Sreyk kv_add(&resp->http_headers, "Last-Modified", tmbuf) == NULL)
1582be5ab2e6Schrisz return (-1);
1583be5ab2e6Schrisz
1584f5d55328Sflorian /* HSTS header */
1585ae30f9e2Sbentley if (srv_conf->flags & SRVFLAG_SERVER_HSTS &&
1586ae30f9e2Sbentley srv_conf->flags & SRVFLAG_TLS) {
1587f5d55328Sflorian if ((cl =
1588f5d55328Sflorian kv_add(&resp->http_headers, "Strict-Transport-Security",
1589f5d55328Sflorian NULL)) == NULL ||
15903323ac76Sbenno kv_set(cl, "max-age=%d%s%s", srv_conf->hsts_max_age,
159152f7cd50Sreyk srv_conf->hsts_flags & HSTSFLAG_SUBDOMAINS ?
159252f7cd50Sreyk "; includeSubDomains" : "",
159352f7cd50Sreyk srv_conf->hsts_flags & HSTSFLAG_PRELOAD ?
159452f7cd50Sreyk "; preload" : "") == -1)
1595f5d55328Sflorian return (-1);
1596f5d55328Sflorian }
1597f5d55328Sflorian
1598be5ab2e6Schrisz /* Date header is mandatory and should be added as late as possible */
1599be5ab2e6Schrisz if (server_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0 ||
1600d08e4976Sreyk kv_add(&resp->http_headers, "Date", tmbuf) == NULL)
16019f126950Sreyk return (-1);
16029f126950Sreyk
16035fa30660Sreyk /* Write completed header */
16045fa30660Sreyk if (server_writeresponse_http(clt) == -1 ||
16055fa30660Sreyk server_bufferevent_print(clt, "\r\n") == -1 ||
1606d08e4976Sreyk server_headers(clt, resp, server_writeheader_http, NULL) == -1 ||
16075fa30660Sreyk server_bufferevent_print(clt, "\r\n") == -1)
16085fa30660Sreyk return (-1);
16095fa30660Sreyk
1610cc526105Sreyk if (size <= 0 || resp->http_method == HTTP_METHOD_HEAD) {
16115fa30660Sreyk bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE);
16125fa30660Sreyk if (clt->clt_persist)
16135fa30660Sreyk clt->clt_toread = TOREAD_HTTP_HEADER;
16145fa30660Sreyk else
16155fa30660Sreyk clt->clt_toread = TOREAD_HTTP_NONE;
16165fa30660Sreyk clt->clt_done = 0;
16175fa30660Sreyk return (0);
16185fa30660Sreyk }
16195fa30660Sreyk
16205fa30660Sreyk return (1);
16215fa30660Sreyk }
16225fa30660Sreyk
16235fa30660Sreyk int
server_writeresponse_http(struct client * clt)1624b7b6a941Sreyk server_writeresponse_http(struct client *clt)
1625b7b6a941Sreyk {
1626d08e4976Sreyk struct http_descriptor *desc = clt->clt_descresp;
1627b7b6a941Sreyk
1628b7b6a941Sreyk DPRINTF("version: %s rescode: %s resmsg: %s", desc->http_version,
1629b7b6a941Sreyk desc->http_rescode, desc->http_resmesg);
1630b7b6a941Sreyk
1631b7b6a941Sreyk if (server_bufferevent_print(clt, desc->http_version) == -1 ||
1632b7b6a941Sreyk server_bufferevent_print(clt, " ") == -1 ||
1633b7b6a941Sreyk server_bufferevent_print(clt, desc->http_rescode) == -1 ||
1634b7b6a941Sreyk server_bufferevent_print(clt, " ") == -1 ||
1635b7b6a941Sreyk server_bufferevent_print(clt, desc->http_resmesg) == -1)
1636b7b6a941Sreyk return (-1);
1637b7b6a941Sreyk
1638b7b6a941Sreyk return (0);
1639b7b6a941Sreyk }
1640b7b6a941Sreyk
1641b7b6a941Sreyk int
server_writeheader_http(struct client * clt,struct kv * hdr,void * arg)16424aa750c1Sreyk server_writeheader_http(struct client *clt, struct kv *hdr, void *arg)
1643b7b6a941Sreyk {
1644b7b6a941Sreyk char *ptr;
1645b7b6a941Sreyk const char *key;
1646b7b6a941Sreyk
1647b7b6a941Sreyk /* The key might have been updated in the parent */
1648b7b6a941Sreyk if (hdr->kv_parent != NULL && hdr->kv_parent->kv_key != NULL)
1649b7b6a941Sreyk key = hdr->kv_parent->kv_key;
1650b7b6a941Sreyk else
1651b7b6a941Sreyk key = hdr->kv_key;
1652b7b6a941Sreyk
1653b7b6a941Sreyk ptr = hdr->kv_value;
1654b7b6a941Sreyk if (server_bufferevent_print(clt, key) == -1 ||
1655b7b6a941Sreyk (ptr != NULL &&
1656b7b6a941Sreyk (server_bufferevent_print(clt, ": ") == -1 ||
1657b7b6a941Sreyk server_bufferevent_print(clt, ptr) == -1 ||
1658b7b6a941Sreyk server_bufferevent_print(clt, "\r\n") == -1)))
1659b7b6a941Sreyk return (-1);
1660b7b6a941Sreyk DPRINTF("%s: %s: %s", __func__, key,
1661b7b6a941Sreyk hdr->kv_value == NULL ? "" : hdr->kv_value);
1662b7b6a941Sreyk
1663b7b6a941Sreyk return (0);
1664b7b6a941Sreyk }
1665b7b6a941Sreyk
1666b7b6a941Sreyk int
server_headers(struct client * clt,void * descp,int (* hdr_cb)(struct client *,struct kv *,void *),void * arg)1667d08e4976Sreyk server_headers(struct client *clt, void *descp,
16684aa750c1Sreyk int (*hdr_cb)(struct client *, struct kv *, void *), void *arg)
1669b7b6a941Sreyk {
1670b7b6a941Sreyk struct kv *hdr, *kv;
1671d08e4976Sreyk struct http_descriptor *desc = descp;
1672b7b6a941Sreyk
1673b7b6a941Sreyk RB_FOREACH(hdr, kvtree, &desc->http_headers) {
16744aa750c1Sreyk if ((hdr_cb)(clt, hdr, arg) == -1)
1675b7b6a941Sreyk return (-1);
1676b7b6a941Sreyk TAILQ_FOREACH(kv, &hdr->kv_children, kv_entry) {
16774aa750c1Sreyk if ((hdr_cb)(clt, kv, arg) == -1)
1678b7b6a941Sreyk return (-1);
1679b7b6a941Sreyk }
1680b7b6a941Sreyk }
1681b7b6a941Sreyk
1682b7b6a941Sreyk return (0);
1683b7b6a941Sreyk }
1684b7b6a941Sreyk
1685b7b6a941Sreyk enum httpmethod
server_httpmethod_byname(const char * name)1686b7b6a941Sreyk server_httpmethod_byname(const char *name)
1687b7b6a941Sreyk {
1688b7b6a941Sreyk enum httpmethod id = HTTP_METHOD_NONE;
1689b7b6a941Sreyk struct http_method method, *res = NULL;
1690b7b6a941Sreyk
1691b7b6a941Sreyk /* Set up key */
1692b7b6a941Sreyk method.method_name = name;
1693b7b6a941Sreyk
1694b7b6a941Sreyk if ((res = bsearch(&method, http_methods,
1695b7b6a941Sreyk sizeof(http_methods) / sizeof(http_methods[0]) - 1,
1696b7b6a941Sreyk sizeof(http_methods[0]), server_httpmethod_cmp)) != NULL)
1697b7b6a941Sreyk id = res->method_id;
1698b7b6a941Sreyk
1699b7b6a941Sreyk return (id);
1700b7b6a941Sreyk }
1701b7b6a941Sreyk
1702b7b6a941Sreyk const char *
server_httpmethod_byid(unsigned int id)17034703e0faSreyk server_httpmethod_byid(unsigned int id)
1704b7b6a941Sreyk {
1705f0c872b4Sreyk const char *name = "<UNKNOWN>";
1706b7b6a941Sreyk int i;
1707b7b6a941Sreyk
1708b7b6a941Sreyk for (i = 0; http_methods[i].method_name != NULL; i++) {
1709b7b6a941Sreyk if (http_methods[i].method_id == id) {
1710b7b6a941Sreyk name = http_methods[i].method_name;
1711b7b6a941Sreyk break;
1712b7b6a941Sreyk }
1713b7b6a941Sreyk }
1714b7b6a941Sreyk
1715b7b6a941Sreyk return (name);
1716b7b6a941Sreyk }
1717b7b6a941Sreyk
1718b7b6a941Sreyk static int
server_httpmethod_cmp(const void * a,const void * b)1719b7b6a941Sreyk server_httpmethod_cmp(const void *a, const void *b)
1720b7b6a941Sreyk {
1721b7b6a941Sreyk const struct http_method *ma = a;
1722b7b6a941Sreyk const struct http_method *mb = b;
17231d89351eSstsp
17241d89351eSstsp /*
17251d89351eSstsp * RFC 2616 section 5.1.1 says that the method is case
17261d89351eSstsp * sensitive so we don't do a strcasecmp here.
17271d89351eSstsp */
1728b7b6a941Sreyk return (strcmp(ma->method_name, mb->method_name));
1729b7b6a941Sreyk }
1730b7b6a941Sreyk
1731b7b6a941Sreyk const char *
server_httperror_byid(unsigned int id)17324703e0faSreyk server_httperror_byid(unsigned int id)
1733b7b6a941Sreyk {
17341a67c375Sreyk struct http_error error, *res;
1735b7b6a941Sreyk
1736b7b6a941Sreyk /* Set up key */
1737b7b6a941Sreyk error.error_code = (int)id;
1738b7b6a941Sreyk
17391a67c375Sreyk if ((res = bsearch(&error, http_errors,
1740b7b6a941Sreyk sizeof(http_errors) / sizeof(http_errors[0]) - 1,
17411a67c375Sreyk sizeof(http_errors[0]), server_httperror_cmp)) != NULL)
1742b7b6a941Sreyk return (res->error_name);
17431a67c375Sreyk
17441a67c375Sreyk return (NULL);
1745b7b6a941Sreyk }
1746b7b6a941Sreyk
1747b7b6a941Sreyk static int
server_httperror_cmp(const void * a,const void * b)1748b7b6a941Sreyk server_httperror_cmp(const void *a, const void *b)
1749b7b6a941Sreyk {
1750b7b6a941Sreyk const struct http_error *ea = a;
1751b7b6a941Sreyk const struct http_error *eb = b;
1752b7b6a941Sreyk return (ea->error_code - eb->error_code);
1753b7b6a941Sreyk }
1754ea62a379Sdoug
1755cbced0bdSian /*
1756cbced0bdSian * return -1 on failure, strlen() of read file otherwise.
1757cbced0bdSian * body is NULL on failure, contents of file with trailing \0 otherwise.
1758cbced0bdSian */
1759cbced0bdSian char *
read_errdoc(const char * root,const char * file)1760cbced0bdSian read_errdoc(const char *root, const char *file)
1761cbced0bdSian {
1762cbced0bdSian struct stat sb;
1763cbced0bdSian char *path;
1764cbced0bdSian int fd;
1765*b32af659Sclaudio char *ret;
1766cbced0bdSian
1767cbced0bdSian if (asprintf(&path, "%s/%s.html", root, file) == -1)
1768cbced0bdSian fatal("asprintf");
1769cbced0bdSian if ((fd = open(path, O_RDONLY)) == -1) {
1770cbced0bdSian free(path);
1771*b32af659Sclaudio if (errno != ENOENT)
1772cbced0bdSian log_warn("%s: open", __func__);
1773cbced0bdSian return (NULL);
1774cbced0bdSian }
1775cbced0bdSian free(path);
1776cbced0bdSian if (fstat(fd, &sb) < 0) {
1777cbced0bdSian log_warn("%s: stat", __func__);
1778dabb2923Sop close(fd);
1779cbced0bdSian return (NULL);
1780cbced0bdSian }
1781cbced0bdSian
1782cbced0bdSian if ((ret = calloc(1, sb.st_size + 1)) == NULL)
1783cbced0bdSian fatal("calloc");
1784dabb2923Sop if (sb.st_size == 0) {
1785dabb2923Sop close(fd);
1786cbced0bdSian return (ret);
1787dabb2923Sop }
1788cbced0bdSian if (read(fd, ret, sb.st_size) != sb.st_size) {
1789cbced0bdSian log_warn("%s: read", __func__);
1790cbced0bdSian close(fd);
1791cbced0bdSian free(ret);
1792*b32af659Sclaudio return (NULL);
1793cbced0bdSian }
1794cbced0bdSian close(fd);
1795cbced0bdSian
1796cbced0bdSian return (ret);
1797cbced0bdSian }
1798cbced0bdSian
1799cbced0bdSian char *
replace_var(char * str,const char * var,const char * repl)1800cbced0bdSian replace_var(char *str, const char *var, const char *repl)
1801cbced0bdSian {
1802cbced0bdSian char *iv, *r;
1803cbced0bdSian size_t vlen;
1804cbced0bdSian
1805cbced0bdSian vlen = strlen(var);
1806cbced0bdSian while ((iv = strstr(str, var)) != NULL) {
1807cbced0bdSian *iv = '\0';
1808cbced0bdSian if (asprintf(&r, "%s%s%s", str, repl, &iv[vlen]) == -1)
1809cbced0bdSian fatal("asprintf");
1810cbced0bdSian free(str);
1811cbced0bdSian str = r;
1812cbced0bdSian }
1813cbced0bdSian return (str);
1814cbced0bdSian }
1815cbced0bdSian
1816ea62a379Sdoug int
server_log_http(struct client * clt,unsigned int code,size_t len)18174703e0faSreyk server_log_http(struct client *clt, unsigned int code, size_t len)
1818ea62a379Sdoug {
1819ea62a379Sdoug static char tstamp[64];
1820ea62a379Sdoug static char ip[INET6_ADDRSTRLEN];
1821ea62a379Sdoug time_t t;
182234ff7cffStb struct kv key, *agent, *referrer, *xff, *xfp;
1823ea62a379Sdoug struct tm *tm;
1824ea62a379Sdoug struct server_config *srv_conf;
1825ea62a379Sdoug struct http_descriptor *desc;
1826e22b0c74Sreyk int ret = -1;
18276ebc1f19Ssemarie char *user = NULL;
18286ebc1f19Ssemarie char *path = NULL;
18296ebc1f19Ssemarie char *version = NULL;
18306ebc1f19Ssemarie char *referrer_v = NULL;
18316ebc1f19Ssemarie char *agent_v = NULL;
183234ff7cffStb char *xff_v = NULL;
183334ff7cffStb char *xfp_v = NULL;
1834ea62a379Sdoug
1835ea62a379Sdoug if ((srv_conf = clt->clt_srv_conf) == NULL)
1836ea62a379Sdoug return (-1);
1837af3cfad1Sdoug if ((srv_conf->flags & SRVFLAG_LOG) == 0)
1838af3cfad1Sdoug return (0);
1839d08e4976Sreyk if ((desc = clt->clt_descreq) == NULL)
1840ea62a379Sdoug return (-1);
1841ea62a379Sdoug
1842ea62a379Sdoug if ((t = time(NULL)) == -1)
1843ea62a379Sdoug return (-1);
1844ea62a379Sdoug if ((tm = localtime(&t)) == NULL)
1845ea62a379Sdoug return (-1);
1846ea62a379Sdoug if (strftime(tstamp, sizeof(tstamp), "%d/%b/%Y:%H:%M:%S %z", tm) == 0)
1847ea62a379Sdoug return (-1);
1848ea62a379Sdoug
1849944a3fefSreyk if (print_host(&clt->clt_ss, ip, sizeof(ip)) == NULL)
1850944a3fefSreyk return (-1);
1851ea62a379Sdoug
1852ea62a379Sdoug /*
1853ea62a379Sdoug * For details on common log format, see:
1854ea62a379Sdoug * https://httpd.apache.org/docs/current/mod/mod_log_config.html
1855af3cfad1Sdoug *
1856af3cfad1Sdoug * httpd's format is similar to these Apache LogFormats:
1857af3cfad1Sdoug * "%v %h %l %u %t \"%r\" %>s %B"
1858af3cfad1Sdoug * "%v %h %l %u %t \"%r\" %>s %B \"%{Referer}i\" \"%{User-agent}i\""
1859ea62a379Sdoug */
1860ea62a379Sdoug switch (srv_conf->logformat) {
1861ea62a379Sdoug case LOG_FORMAT_COMMON:
1862e3c03affSreyk /* Use vis to encode input values from the header */
18636ebc1f19Ssemarie if (clt->clt_remote_user &&
1864ba87bb65Sreyk stravis(&user, clt->clt_remote_user, HTTPD_LOGVIS) == -1)
1865e3c03affSreyk goto done;
1866e3c03affSreyk if (desc->http_version &&
1867ba87bb65Sreyk stravis(&version, desc->http_version, HTTPD_LOGVIS) == -1)
18686ebc1f19Ssemarie goto done;
18696ebc1f19Ssemarie
1870e3c03affSreyk /* The following should be URL-encoded */
18716ebc1f19Ssemarie if (desc->http_path &&
1872e22b0c74Sreyk (path = url_encode(desc->http_path)) == NULL)
18736ebc1f19Ssemarie goto done;
18746ebc1f19Ssemarie
18756ebc1f19Ssemarie ret = evbuffer_add_printf(clt->clt_log,
187641dcbd59Stim "%s %s - %s [%s] \"%s %s%s%s%s%s\" %03d %zu\n",
187741dcbd59Stim srv_conf->name, ip, clt->clt_remote_user == NULL ? "-" :
187841dcbd59Stim user, tstamp,
1879ea62a379Sdoug server_httpmethod_byid(desc->http_method),
18806ebc1f19Ssemarie desc->http_path == NULL ? "" : path,
1881ea62a379Sdoug desc->http_query == NULL ? "" : "?",
188258963e4fSreyk desc->http_query == NULL ? "" : desc->http_query,
1883ea62a379Sdoug desc->http_version == NULL ? "" : " ",
18846ebc1f19Ssemarie desc->http_version == NULL ? "" : version,
18856ebc1f19Ssemarie code, len);
18866ebc1f19Ssemarie
1887ea62a379Sdoug break;
1888ea62a379Sdoug
1889ea62a379Sdoug case LOG_FORMAT_COMBINED:
189034ff7cffStb case LOG_FORMAT_FORWARDED:
1891ea62a379Sdoug key.kv_key = "Referer"; /* sic */
1892ea62a379Sdoug if ((referrer = kv_find(&desc->http_headers, &key)) != NULL &&
1893ea62a379Sdoug referrer->kv_value == NULL)
1894ea62a379Sdoug referrer = NULL;
1895ea62a379Sdoug
1896ea62a379Sdoug key.kv_key = "User-Agent";
1897ea62a379Sdoug if ((agent = kv_find(&desc->http_headers, &key)) != NULL &&
1898ea62a379Sdoug agent->kv_value == NULL)
1899ea62a379Sdoug agent = NULL;
1900ea62a379Sdoug
1901e3c03affSreyk /* Use vis to encode input values from the header */
19026ebc1f19Ssemarie if (clt->clt_remote_user &&
1903ba87bb65Sreyk stravis(&user, clt->clt_remote_user, HTTPD_LOGVIS) == -1)
1904e3c03affSreyk goto done;
1905095ccd49Sbenno if (clt->clt_remote_user == NULL &&
1906095ccd49Sbenno clt->clt_tls_ctx != NULL &&
1907095ccd49Sbenno (srv_conf->tls_flags & TLSFLAG_CA) &&
1908095ccd49Sbenno tls_peer_cert_subject(clt->clt_tls_ctx) != NULL &&
1909095ccd49Sbenno stravis(&user, tls_peer_cert_subject(clt->clt_tls_ctx),
1910095ccd49Sbenno HTTPD_LOGVIS) == -1)
1911095ccd49Sbenno goto done;
1912e3c03affSreyk if (desc->http_version &&
1913ba87bb65Sreyk stravis(&version, desc->http_version, HTTPD_LOGVIS) == -1)
1914e3c03affSreyk goto done;
1915e3c03affSreyk if (agent &&
1916ba87bb65Sreyk stravis(&agent_v, agent->kv_value, HTTPD_LOGVIS) == -1)
19176ebc1f19Ssemarie goto done;
19186ebc1f19Ssemarie
1919e3c03affSreyk /* The following should be URL-encoded */
19206ebc1f19Ssemarie if (desc->http_path &&
1921e22b0c74Sreyk (path = url_encode(desc->http_path)) == NULL)
19226ebc1f19Ssemarie goto done;
1923e22b0c74Sreyk if (referrer &&
1924e22b0c74Sreyk (referrer_v = url_encode(referrer->kv_value)) == NULL)
19256ebc1f19Ssemarie goto done;
19266ebc1f19Ssemarie
192734ff7cffStb if ((ret = evbuffer_add_printf(clt->clt_log,
192841dcbd59Stim "%s %s - %s [%s] \"%s %s%s%s%s%s\""
192934ff7cffStb " %03d %zu \"%s\" \"%s\"",
1930095ccd49Sbenno srv_conf->name, ip, user == NULL ? "-" :
193141dcbd59Stim user, tstamp,
1932ea62a379Sdoug server_httpmethod_byid(desc->http_method),
19336ebc1f19Ssemarie desc->http_path == NULL ? "" : path,
1934ea62a379Sdoug desc->http_query == NULL ? "" : "?",
193558963e4fSreyk desc->http_query == NULL ? "" : desc->http_query,
1936ea62a379Sdoug desc->http_version == NULL ? "" : " ",
19376ebc1f19Ssemarie desc->http_version == NULL ? "" : version,
1938ea62a379Sdoug code, len,
19396ebc1f19Ssemarie referrer == NULL ? "" : referrer_v,
194034ff7cffStb agent == NULL ? "" : agent_v)) == -1)
194134ff7cffStb break;
194234ff7cffStb
194334ff7cffStb if (srv_conf->logformat == LOG_FORMAT_COMBINED)
194434ff7cffStb goto finish;
194534ff7cffStb
194634ff7cffStb xff = xfp = NULL;
194734ff7cffStb
194834ff7cffStb key.kv_key = "X-Forwarded-For";
194934ff7cffStb if ((xff = kv_find(&desc->http_headers, &key)) != NULL
195034ff7cffStb && xff->kv_value == NULL)
195134ff7cffStb xff = NULL;
195234ff7cffStb
195334ff7cffStb if (xff &&
195434ff7cffStb stravis(&xff_v, xff->kv_value, HTTPD_LOGVIS) == -1)
195534ff7cffStb goto finish;
195634ff7cffStb
195734ff7cffStb key.kv_key = "X-Forwarded-Port";
1958e95f05c9Sreyk if ((xfp = kv_find(&desc->http_headers, &key)) != NULL &&
1959e95f05c9Sreyk (xfp->kv_value == NULL))
196034ff7cffStb xfp = NULL;
196134ff7cffStb
196234ff7cffStb if (xfp &&
196334ff7cffStb stravis(&xfp_v, xfp->kv_value, HTTPD_LOGVIS) == -1)
196434ff7cffStb goto finish;
196534ff7cffStb
196634ff7cffStb if ((ret = evbuffer_add_printf(clt->clt_log, " %s %s",
196734ff7cffStb xff == NULL ? "-" : xff_v,
196834ff7cffStb xfp == NULL ? "-" : xfp_v)) == -1)
196934ff7cffStb break;
197034ff7cffStb finish:
197134ff7cffStb ret = evbuffer_add_printf(clt->clt_log, "\n");
19726ebc1f19Ssemarie
1973ea62a379Sdoug break;
1974944a3fefSreyk
1975944a3fefSreyk case LOG_FORMAT_CONNECTION:
1976e3c03affSreyk /* URL-encode the path */
19776ebc1f19Ssemarie if (desc->http_path &&
1978e22b0c74Sreyk (path = url_encode(desc->http_path)) == NULL)
19796ebc1f19Ssemarie goto done;
19806ebc1f19Ssemarie
19816ebc1f19Ssemarie ret = evbuffer_add_printf(clt->clt_log, " [%s]",
19826ebc1f19Ssemarie desc->http_path == NULL ? "" : path);
19836ebc1f19Ssemarie
1984944a3fefSreyk break;
1985ea62a379Sdoug }
1986ea62a379Sdoug
19876ebc1f19Ssemarie done:
19886ebc1f19Ssemarie free(user);
19896ebc1f19Ssemarie free(path);
19906ebc1f19Ssemarie free(version);
19916ebc1f19Ssemarie free(referrer_v);
19926ebc1f19Ssemarie free(agent_v);
199334ff7cffStb free(xff_v);
199434ff7cffStb free(xfp_v);
19956ebc1f19Ssemarie
19966ebc1f19Ssemarie return (ret);
1997ea62a379Sdoug }
1998