1*b0c10c99Sflorian /* $OpenBSD: server_http.c,v 1.155 2024/12/22 13:51:42 florian 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 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 76b7b6a941Sreyk server_http_init(struct server *srv) 77b7b6a941Sreyk { 78b7b6a941Sreyk /* nothing */ 79b7b6a941Sreyk } 80b7b6a941Sreyk 81b7b6a941Sreyk int 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 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 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 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 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 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 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 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 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 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 * 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 * 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 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 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 * 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 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 1371*b0c10c99Sflorian 1372*b0c10c99Sflorian /* Set request timeout from matching host configuration. */ 1373*b0c10c99Sflorian bufferevent_settimeout(clt->clt_bev, 1374*b0c10c99Sflorian srv_conf->requesttimeout.tv_sec, srv_conf->requesttimeout.tv_sec); 1375*b0c10c99Sflorian 1376ca8e2b3eSbenno if (clt->clt_persist >= srv_conf->maxrequests) 1377ca8e2b3eSbenno clt->clt_persist = 0; 1378ca8e2b3eSbenno 1379ca8e2b3eSbenno /* pipelining should end after the first "idempotent" method */ 1380ca8e2b3eSbenno if (clt->clt_pipelining && clt->clt_toread > 0) 1381ca8e2b3eSbenno clt->clt_persist = 0; 1382ca8e2b3eSbenno 138347dc2a9dSreyk if ((desc->http_host = strdup(hostname)) == NULL) 138447dc2a9dSreyk goto fail; 138547dc2a9dSreyk 1386d08e4976Sreyk /* Now fill in the mandatory parts of the response descriptor */ 1387d08e4976Sreyk resp->http_method = desc->http_method; 1388d08e4976Sreyk if ((resp->http_version = strdup(desc->http_version)) == NULL) 1389d08e4976Sreyk goto fail; 1390d08e4976Sreyk 13914f194ec8Sreyk /* Now search for the location */ 13928b6f8711Stb if ((srv_conf = server_getlocation(clt, desc->http_path)) == NULL) { 1393e96b74b9Sdenis server_abort_http(clt, 500, desc->http_path); 1394e96b74b9Sdenis return (-1); 1395e96b74b9Sdenis } 1396de6550b1Sreyk 139793038d14Sreyk /* Optional rewrite */ 139893038d14Sreyk if (srv_conf->flags & SRVFLAG_PATH_REWRITE) { 139993038d14Sreyk /* Expand macros */ 140093038d14Sreyk if (server_expand_http(clt, srv_conf->path, 140193038d14Sreyk path, sizeof(path)) == NULL) 140293038d14Sreyk goto fail; 140393038d14Sreyk 140493038d14Sreyk /* 140593038d14Sreyk * Reset and update the query. The updated query must already 140693038d14Sreyk * be URL encoded - either specified by the user or by using the 140793038d14Sreyk * original $QUERY_STRING. 140893038d14Sreyk */ 1409a23b6848Stb free(desc->http_query_alias); 1410a23b6848Stb desc->http_query_alias = NULL; 141193038d14Sreyk if ((query = strchr(path, '?')) != NULL) { 141293038d14Sreyk *query++ = '\0'; 1413a23b6848Stb if ((desc->http_query_alias = strdup(query)) == NULL) 141493038d14Sreyk goto fail; 141593038d14Sreyk } 141693038d14Sreyk 141793038d14Sreyk /* Canonicalize the updated request path */ 141893038d14Sreyk if (canonicalize_path(path, 141993038d14Sreyk path, sizeof(path)) == NULL) 142093038d14Sreyk goto fail; 142193038d14Sreyk 1422a23b6848Stb log_debug("%s: rewrote %s?%s -> %s?%s", __func__, 14232c98785eSflorian desc->http_path, desc->http_query ? desc->http_query : "", 14242c98785eSflorian path, query ? query : ""); 142593038d14Sreyk 1426a23b6848Stb free(desc->http_path_alias); 1427a23b6848Stb if ((desc->http_path_alias = strdup(path)) == NULL) 142893038d14Sreyk goto fail; 142993038d14Sreyk 143093038d14Sreyk /* Now search for the updated location */ 1431e96b74b9Sdenis if ((srv_conf = server_getlocation(clt, 1432e96b74b9Sdenis desc->http_path_alias)) == NULL) { 1433e96b74b9Sdenis server_abort_http(clt, 500, desc->http_path_alias); 1434e96b74b9Sdenis return (-1); 1435e96b74b9Sdenis } 143693038d14Sreyk } 143793038d14Sreyk 1438f0a27d98Sflorian if (clt->clt_toread > 0 && (size_t)clt->clt_toread > 1439f0a27d98Sflorian srv_conf->maxrequestbody) { 1440fa3025f4Sbenno server_abort_http(clt, 413, "request body too large"); 1441f0a27d98Sflorian return (-1); 1442f0a27d98Sflorian } 1443f0a27d98Sflorian 1444f8932becSreyk if (srv_conf->flags & SRVFLAG_BLOCK) { 1445f8932becSreyk server_abort_http(clt, srv_conf->return_code, 1446f8932becSreyk srv_conf->return_uri); 1447f8932becSreyk return (-1); 1448f8932becSreyk } else if (srv_conf->flags & SRVFLAG_AUTH && 1449e286121aSflorian server_http_authenticate(srv_conf, clt) == -1) { 1450e286121aSflorian server_abort_http(clt, 401, srv_conf->auth_realm); 1451e286121aSflorian return (-1); 1452e286121aSflorian } else 1453de6550b1Sreyk return (server_file(httpd, clt)); 1454de6550b1Sreyk fail: 1455de6550b1Sreyk server_abort_http(clt, 400, "bad request"); 1456de6550b1Sreyk return (-1); 1457de6550b1Sreyk } 1458de6550b1Sreyk 14594624b10aSchrisz const char * 14604624b10aSchrisz server_root_strip(const char *path, int n) 14614624b10aSchrisz { 14624624b10aSchrisz const char *p; 14634624b10aSchrisz 14644624b10aSchrisz /* Strip strip leading directories. Leading '/' is ignored. */ 14654624b10aSchrisz for (; n > 0 && *path != '\0'; n--) 14664624b10aSchrisz if ((p = strchr(++path, '/')) == NULL) 14674624b10aSchrisz path = strchr(path, '\0'); 14684624b10aSchrisz else 14694624b10aSchrisz path = p; 14704624b10aSchrisz 14714624b10aSchrisz return (path); 14724624b10aSchrisz } 14734624b10aSchrisz 1474de6550b1Sreyk struct server_config * 1475de6550b1Sreyk server_getlocation(struct client *clt, const char *path) 1476de6550b1Sreyk { 1477de6550b1Sreyk struct server *srv = clt->clt_srv; 1478de6550b1Sreyk struct server_config *srv_conf = clt->clt_srv_conf, *location; 147959355b5aSreyk const char *errstr = NULL; 148059355b5aSreyk int ret; 1481de6550b1Sreyk 1482de6550b1Sreyk /* Now search for the location */ 14834f194ec8Sreyk TAILQ_FOREACH(location, &srv->srv_hosts, entry) { 1484bd1bab2fSreyk #ifdef DEBUG 1485bd1bab2fSreyk if (location->flags & SRVFLAG_LOCATION) { 1486bd1bab2fSreyk DPRINTF("%s: location \"%s\" path \"%s\"", 1487bd1bab2fSreyk __func__, location->location, path); 1488bd1bab2fSreyk } 1489bd1bab2fSreyk #endif 14904f194ec8Sreyk if ((location->flags & SRVFLAG_LOCATION) && 149159355b5aSreyk location->parent_id == srv_conf->parent_id) { 149259355b5aSreyk errstr = NULL; 149359355b5aSreyk if (location->flags & SRVFLAG_LOCATION_MATCH) { 149459355b5aSreyk ret = str_match(path, location->location, 149559355b5aSreyk &clt->clt_srv_match, &errstr); 149659355b5aSreyk } else { 149759355b5aSreyk ret = fnmatch(location->location, 149859355b5aSreyk path, FNM_CASEFOLD); 149959355b5aSreyk } 150059355b5aSreyk if (ret == 0 && errstr == NULL) { 1501e96b74b9Sdenis if ((ret = server_locationaccesstest(location, 1502e96b74b9Sdenis path)) == -1) 1503e96b74b9Sdenis return (NULL); 1504e96b74b9Sdenis 1505e96b74b9Sdenis if (ret) 1506e96b74b9Sdenis continue; 15074f194ec8Sreyk /* Replace host configuration */ 1508435dc7cdSreyk clt->clt_srv_conf = srv_conf = location; 15094f194ec8Sreyk break; 15104f194ec8Sreyk } 15116af43371Sreyk } 151259355b5aSreyk } 15136af43371Sreyk 1514de6550b1Sreyk return (srv_conf); 15155fa30660Sreyk } 15165fa30660Sreyk 15175fa30660Sreyk int 1518e96b74b9Sdenis server_locationaccesstest(struct server_config *srv_conf, const char *path) 1519e96b74b9Sdenis { 1520e96b74b9Sdenis int rootfd, ret; 1521e96b74b9Sdenis struct stat sb; 1522e96b74b9Sdenis 1523e96b74b9Sdenis if (((SRVFLAG_LOCATION_FOUND | SRVFLAG_LOCATION_NOT_FOUND) & 1524e96b74b9Sdenis srv_conf->flags) == 0) 1525e96b74b9Sdenis return (0); 1526e96b74b9Sdenis 1527e96b74b9Sdenis if ((rootfd = open(srv_conf->root, O_RDONLY)) == -1) 1528e96b74b9Sdenis return (-1); 1529e96b74b9Sdenis 1530e96b74b9Sdenis path = server_root_strip(path, srv_conf->strip) + 1; 1531e96b74b9Sdenis if ((ret = faccessat(rootfd, path, R_OK, 0)) != -1) 1532e96b74b9Sdenis ret = fstatat(rootfd, path, &sb, 0); 1533e96b74b9Sdenis close(rootfd); 1534e96b74b9Sdenis return ((ret == -1 && SRVFLAG_LOCATION_FOUND & srv_conf->flags) || 1535e96b74b9Sdenis (ret == 0 && SRVFLAG_LOCATION_NOT_FOUND & srv_conf->flags)); 1536e96b74b9Sdenis } 1537e96b74b9Sdenis 1538e96b74b9Sdenis int 15394703e0faSreyk server_response_http(struct client *clt, unsigned int code, 1540d1cfc522Skettenis struct media_type *media, off_t size, time_t mtime) 15415fa30660Sreyk { 1542f5d55328Sflorian struct server_config *srv_conf = clt->clt_srv_conf; 1543d08e4976Sreyk struct http_descriptor *desc = clt->clt_descreq; 1544d08e4976Sreyk struct http_descriptor *resp = clt->clt_descresp; 15455fa30660Sreyk const char *error; 15465fa30660Sreyk struct kv *ct, *cl; 15479f126950Sreyk char tmbuf[32]; 15485fa30660Sreyk 1549d24f6b1eSreyk if (desc == NULL || media == NULL || 1550d24f6b1eSreyk (error = server_httperror_byid(code)) == NULL) 15515fa30660Sreyk return (-1); 15525fa30660Sreyk 1553cc526105Sreyk if (server_log_http(clt, code, size >= 0 ? size : 0) == -1) 1554ea62a379Sdoug return (-1); 1555ea62a379Sdoug 15565fa30660Sreyk /* Add error codes */ 15573323ac76Sbenno if (kv_setkey(&resp->http_pathquery, "%u", code) == -1 || 1558d08e4976Sreyk kv_set(&resp->http_pathquery, "%s", error) == -1) 15595fa30660Sreyk return (-1); 15605fa30660Sreyk 15615fa30660Sreyk /* Add headers */ 1562d08e4976Sreyk if (kv_add(&resp->http_headers, "Server", HTTPD_SERVERNAME) == NULL) 15635fa30660Sreyk return (-1); 15645fa30660Sreyk 15655fa30660Sreyk /* Is it a persistent connection? */ 15665fa30660Sreyk if (clt->clt_persist) { 1567d08e4976Sreyk if (kv_add(&resp->http_headers, 15685fa30660Sreyk "Connection", "keep-alive") == NULL) 15695fa30660Sreyk return (-1); 1570d08e4976Sreyk } else if (kv_add(&resp->http_headers, "Connection", "close") == NULL) 15715fa30660Sreyk return (-1); 15725fa30660Sreyk 15735fa30660Sreyk /* Set media type */ 1574d08e4976Sreyk if ((ct = kv_add(&resp->http_headers, "Content-Type", NULL)) == NULL || 1575d24f6b1eSreyk kv_set(ct, "%s/%s", media->media_type, media->media_subtype) == -1) 15765fa30660Sreyk return (-1); 15775fa30660Sreyk 15785fa30660Sreyk /* Set content length, if specified */ 1579cc526105Sreyk if (size >= 0 && ((cl = 1580d08e4976Sreyk kv_add(&resp->http_headers, "Content-Length", NULL)) == NULL || 1581cc526105Sreyk kv_set(cl, "%lld", (long long)size) == -1)) 15825fa30660Sreyk return (-1); 15835fa30660Sreyk 1584be5ab2e6Schrisz /* Set last modification time */ 1585be5ab2e6Schrisz if (server_http_time(mtime, tmbuf, sizeof(tmbuf)) <= 0 || 1586d08e4976Sreyk kv_add(&resp->http_headers, "Last-Modified", tmbuf) == NULL) 1587be5ab2e6Schrisz return (-1); 1588be5ab2e6Schrisz 1589f5d55328Sflorian /* HSTS header */ 1590ae30f9e2Sbentley if (srv_conf->flags & SRVFLAG_SERVER_HSTS && 1591ae30f9e2Sbentley srv_conf->flags & SRVFLAG_TLS) { 1592f5d55328Sflorian if ((cl = 1593f5d55328Sflorian kv_add(&resp->http_headers, "Strict-Transport-Security", 1594f5d55328Sflorian NULL)) == NULL || 15953323ac76Sbenno kv_set(cl, "max-age=%d%s%s", srv_conf->hsts_max_age, 159652f7cd50Sreyk srv_conf->hsts_flags & HSTSFLAG_SUBDOMAINS ? 159752f7cd50Sreyk "; includeSubDomains" : "", 159852f7cd50Sreyk srv_conf->hsts_flags & HSTSFLAG_PRELOAD ? 159952f7cd50Sreyk "; preload" : "") == -1) 1600f5d55328Sflorian return (-1); 1601f5d55328Sflorian } 1602f5d55328Sflorian 1603be5ab2e6Schrisz /* Date header is mandatory and should be added as late as possible */ 1604be5ab2e6Schrisz if (server_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0 || 1605d08e4976Sreyk kv_add(&resp->http_headers, "Date", tmbuf) == NULL) 16069f126950Sreyk return (-1); 16079f126950Sreyk 16085fa30660Sreyk /* Write completed header */ 16095fa30660Sreyk if (server_writeresponse_http(clt) == -1 || 16105fa30660Sreyk server_bufferevent_print(clt, "\r\n") == -1 || 1611d08e4976Sreyk server_headers(clt, resp, server_writeheader_http, NULL) == -1 || 16125fa30660Sreyk server_bufferevent_print(clt, "\r\n") == -1) 16135fa30660Sreyk return (-1); 16145fa30660Sreyk 1615cc526105Sreyk if (size <= 0 || resp->http_method == HTTP_METHOD_HEAD) { 16165fa30660Sreyk bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); 16175fa30660Sreyk if (clt->clt_persist) 16185fa30660Sreyk clt->clt_toread = TOREAD_HTTP_HEADER; 16195fa30660Sreyk else 16205fa30660Sreyk clt->clt_toread = TOREAD_HTTP_NONE; 16215fa30660Sreyk clt->clt_done = 0; 16225fa30660Sreyk return (0); 16235fa30660Sreyk } 16245fa30660Sreyk 16255fa30660Sreyk return (1); 16265fa30660Sreyk } 16275fa30660Sreyk 16285fa30660Sreyk int 1629b7b6a941Sreyk server_writeresponse_http(struct client *clt) 1630b7b6a941Sreyk { 1631d08e4976Sreyk struct http_descriptor *desc = clt->clt_descresp; 1632b7b6a941Sreyk 1633b7b6a941Sreyk DPRINTF("version: %s rescode: %s resmsg: %s", desc->http_version, 1634b7b6a941Sreyk desc->http_rescode, desc->http_resmesg); 1635b7b6a941Sreyk 1636b7b6a941Sreyk if (server_bufferevent_print(clt, desc->http_version) == -1 || 1637b7b6a941Sreyk server_bufferevent_print(clt, " ") == -1 || 1638b7b6a941Sreyk server_bufferevent_print(clt, desc->http_rescode) == -1 || 1639b7b6a941Sreyk server_bufferevent_print(clt, " ") == -1 || 1640b7b6a941Sreyk server_bufferevent_print(clt, desc->http_resmesg) == -1) 1641b7b6a941Sreyk return (-1); 1642b7b6a941Sreyk 1643b7b6a941Sreyk return (0); 1644b7b6a941Sreyk } 1645b7b6a941Sreyk 1646b7b6a941Sreyk int 16474aa750c1Sreyk server_writeheader_http(struct client *clt, struct kv *hdr, void *arg) 1648b7b6a941Sreyk { 1649b7b6a941Sreyk char *ptr; 1650b7b6a941Sreyk const char *key; 1651b7b6a941Sreyk 1652b7b6a941Sreyk /* The key might have been updated in the parent */ 1653b7b6a941Sreyk if (hdr->kv_parent != NULL && hdr->kv_parent->kv_key != NULL) 1654b7b6a941Sreyk key = hdr->kv_parent->kv_key; 1655b7b6a941Sreyk else 1656b7b6a941Sreyk key = hdr->kv_key; 1657b7b6a941Sreyk 1658b7b6a941Sreyk ptr = hdr->kv_value; 1659b7b6a941Sreyk if (server_bufferevent_print(clt, key) == -1 || 1660b7b6a941Sreyk (ptr != NULL && 1661b7b6a941Sreyk (server_bufferevent_print(clt, ": ") == -1 || 1662b7b6a941Sreyk server_bufferevent_print(clt, ptr) == -1 || 1663b7b6a941Sreyk server_bufferevent_print(clt, "\r\n") == -1))) 1664b7b6a941Sreyk return (-1); 1665b7b6a941Sreyk DPRINTF("%s: %s: %s", __func__, key, 1666b7b6a941Sreyk hdr->kv_value == NULL ? "" : hdr->kv_value); 1667b7b6a941Sreyk 1668b7b6a941Sreyk return (0); 1669b7b6a941Sreyk } 1670b7b6a941Sreyk 1671b7b6a941Sreyk int 1672d08e4976Sreyk server_headers(struct client *clt, void *descp, 16734aa750c1Sreyk int (*hdr_cb)(struct client *, struct kv *, void *), void *arg) 1674b7b6a941Sreyk { 1675b7b6a941Sreyk struct kv *hdr, *kv; 1676d08e4976Sreyk struct http_descriptor *desc = descp; 1677b7b6a941Sreyk 1678b7b6a941Sreyk RB_FOREACH(hdr, kvtree, &desc->http_headers) { 16794aa750c1Sreyk if ((hdr_cb)(clt, hdr, arg) == -1) 1680b7b6a941Sreyk return (-1); 1681b7b6a941Sreyk TAILQ_FOREACH(kv, &hdr->kv_children, kv_entry) { 16824aa750c1Sreyk if ((hdr_cb)(clt, kv, arg) == -1) 1683b7b6a941Sreyk return (-1); 1684b7b6a941Sreyk } 1685b7b6a941Sreyk } 1686b7b6a941Sreyk 1687b7b6a941Sreyk return (0); 1688b7b6a941Sreyk } 1689b7b6a941Sreyk 1690b7b6a941Sreyk enum httpmethod 1691b7b6a941Sreyk server_httpmethod_byname(const char *name) 1692b7b6a941Sreyk { 1693b7b6a941Sreyk enum httpmethod id = HTTP_METHOD_NONE; 1694b7b6a941Sreyk struct http_method method, *res = NULL; 1695b7b6a941Sreyk 1696b7b6a941Sreyk /* Set up key */ 1697b7b6a941Sreyk method.method_name = name; 1698b7b6a941Sreyk 1699b7b6a941Sreyk if ((res = bsearch(&method, http_methods, 1700b7b6a941Sreyk sizeof(http_methods) / sizeof(http_methods[0]) - 1, 1701b7b6a941Sreyk sizeof(http_methods[0]), server_httpmethod_cmp)) != NULL) 1702b7b6a941Sreyk id = res->method_id; 1703b7b6a941Sreyk 1704b7b6a941Sreyk return (id); 1705b7b6a941Sreyk } 1706b7b6a941Sreyk 1707b7b6a941Sreyk const char * 17084703e0faSreyk server_httpmethod_byid(unsigned int id) 1709b7b6a941Sreyk { 1710f0c872b4Sreyk const char *name = "<UNKNOWN>"; 1711b7b6a941Sreyk int i; 1712b7b6a941Sreyk 1713b7b6a941Sreyk for (i = 0; http_methods[i].method_name != NULL; i++) { 1714b7b6a941Sreyk if (http_methods[i].method_id == id) { 1715b7b6a941Sreyk name = http_methods[i].method_name; 1716b7b6a941Sreyk break; 1717b7b6a941Sreyk } 1718b7b6a941Sreyk } 1719b7b6a941Sreyk 1720b7b6a941Sreyk return (name); 1721b7b6a941Sreyk } 1722b7b6a941Sreyk 1723b7b6a941Sreyk static int 1724b7b6a941Sreyk server_httpmethod_cmp(const void *a, const void *b) 1725b7b6a941Sreyk { 1726b7b6a941Sreyk const struct http_method *ma = a; 1727b7b6a941Sreyk const struct http_method *mb = b; 17281d89351eSstsp 17291d89351eSstsp /* 17301d89351eSstsp * RFC 2616 section 5.1.1 says that the method is case 17311d89351eSstsp * sensitive so we don't do a strcasecmp here. 17321d89351eSstsp */ 1733b7b6a941Sreyk return (strcmp(ma->method_name, mb->method_name)); 1734b7b6a941Sreyk } 1735b7b6a941Sreyk 1736b7b6a941Sreyk const char * 17374703e0faSreyk server_httperror_byid(unsigned int id) 1738b7b6a941Sreyk { 17391a67c375Sreyk struct http_error error, *res; 1740b7b6a941Sreyk 1741b7b6a941Sreyk /* Set up key */ 1742b7b6a941Sreyk error.error_code = (int)id; 1743b7b6a941Sreyk 17441a67c375Sreyk if ((res = bsearch(&error, http_errors, 1745b7b6a941Sreyk sizeof(http_errors) / sizeof(http_errors[0]) - 1, 17461a67c375Sreyk sizeof(http_errors[0]), server_httperror_cmp)) != NULL) 1747b7b6a941Sreyk return (res->error_name); 17481a67c375Sreyk 17491a67c375Sreyk return (NULL); 1750b7b6a941Sreyk } 1751b7b6a941Sreyk 1752b7b6a941Sreyk static int 1753b7b6a941Sreyk server_httperror_cmp(const void *a, const void *b) 1754b7b6a941Sreyk { 1755b7b6a941Sreyk const struct http_error *ea = a; 1756b7b6a941Sreyk const struct http_error *eb = b; 1757b7b6a941Sreyk return (ea->error_code - eb->error_code); 1758b7b6a941Sreyk } 1759ea62a379Sdoug 1760cbced0bdSian /* 1761cbced0bdSian * return -1 on failure, strlen() of read file otherwise. 1762cbced0bdSian * body is NULL on failure, contents of file with trailing \0 otherwise. 1763cbced0bdSian */ 1764cbced0bdSian char * 1765cbced0bdSian read_errdoc(const char *root, const char *file) 1766cbced0bdSian { 1767cbced0bdSian struct stat sb; 1768cbced0bdSian char *path; 1769cbced0bdSian int fd; 1770b32af659Sclaudio char *ret; 1771cbced0bdSian 1772cbced0bdSian if (asprintf(&path, "%s/%s.html", root, file) == -1) 1773cbced0bdSian fatal("asprintf"); 1774cbced0bdSian if ((fd = open(path, O_RDONLY)) == -1) { 1775cbced0bdSian free(path); 1776b32af659Sclaudio if (errno != ENOENT) 1777cbced0bdSian log_warn("%s: open", __func__); 1778cbced0bdSian return (NULL); 1779cbced0bdSian } 1780cbced0bdSian free(path); 1781cbced0bdSian if (fstat(fd, &sb) < 0) { 1782cbced0bdSian log_warn("%s: stat", __func__); 1783dabb2923Sop close(fd); 1784cbced0bdSian return (NULL); 1785cbced0bdSian } 1786cbced0bdSian 1787cbced0bdSian if ((ret = calloc(1, sb.st_size + 1)) == NULL) 1788cbced0bdSian fatal("calloc"); 1789dabb2923Sop if (sb.st_size == 0) { 1790dabb2923Sop close(fd); 1791cbced0bdSian return (ret); 1792dabb2923Sop } 1793cbced0bdSian if (read(fd, ret, sb.st_size) != sb.st_size) { 1794cbced0bdSian log_warn("%s: read", __func__); 1795cbced0bdSian close(fd); 1796cbced0bdSian free(ret); 1797b32af659Sclaudio return (NULL); 1798cbced0bdSian } 1799cbced0bdSian close(fd); 1800cbced0bdSian 1801cbced0bdSian return (ret); 1802cbced0bdSian } 1803cbced0bdSian 1804cbced0bdSian char * 1805cbced0bdSian replace_var(char *str, const char *var, const char *repl) 1806cbced0bdSian { 1807cbced0bdSian char *iv, *r; 1808cbced0bdSian size_t vlen; 1809cbced0bdSian 1810cbced0bdSian vlen = strlen(var); 1811cbced0bdSian while ((iv = strstr(str, var)) != NULL) { 1812cbced0bdSian *iv = '\0'; 1813cbced0bdSian if (asprintf(&r, "%s%s%s", str, repl, &iv[vlen]) == -1) 1814cbced0bdSian fatal("asprintf"); 1815cbced0bdSian free(str); 1816cbced0bdSian str = r; 1817cbced0bdSian } 1818cbced0bdSian return (str); 1819cbced0bdSian } 1820cbced0bdSian 1821ea62a379Sdoug int 18224703e0faSreyk server_log_http(struct client *clt, unsigned int code, size_t len) 1823ea62a379Sdoug { 1824ea62a379Sdoug static char tstamp[64]; 1825ea62a379Sdoug static char ip[INET6_ADDRSTRLEN]; 1826ea62a379Sdoug time_t t; 182734ff7cffStb struct kv key, *agent, *referrer, *xff, *xfp; 1828ea62a379Sdoug struct tm *tm; 1829ea62a379Sdoug struct server_config *srv_conf; 1830ea62a379Sdoug struct http_descriptor *desc; 1831e22b0c74Sreyk int ret = -1; 18326ebc1f19Ssemarie char *user = NULL; 18336ebc1f19Ssemarie char *path = NULL; 18346ebc1f19Ssemarie char *version = NULL; 18356ebc1f19Ssemarie char *referrer_v = NULL; 18366ebc1f19Ssemarie char *agent_v = NULL; 183734ff7cffStb char *xff_v = NULL; 183834ff7cffStb char *xfp_v = NULL; 1839ea62a379Sdoug 1840ea62a379Sdoug if ((srv_conf = clt->clt_srv_conf) == NULL) 1841ea62a379Sdoug return (-1); 1842af3cfad1Sdoug if ((srv_conf->flags & SRVFLAG_LOG) == 0) 1843af3cfad1Sdoug return (0); 1844d08e4976Sreyk if ((desc = clt->clt_descreq) == NULL) 1845ea62a379Sdoug return (-1); 1846ea62a379Sdoug 1847ea62a379Sdoug if ((t = time(NULL)) == -1) 1848ea62a379Sdoug return (-1); 1849ea62a379Sdoug if ((tm = localtime(&t)) == NULL) 1850ea62a379Sdoug return (-1); 1851ea62a379Sdoug if (strftime(tstamp, sizeof(tstamp), "%d/%b/%Y:%H:%M:%S %z", tm) == 0) 1852ea62a379Sdoug return (-1); 1853ea62a379Sdoug 1854944a3fefSreyk if (print_host(&clt->clt_ss, ip, sizeof(ip)) == NULL) 1855944a3fefSreyk return (-1); 1856ea62a379Sdoug 1857ea62a379Sdoug /* 1858ea62a379Sdoug * For details on common log format, see: 1859ea62a379Sdoug * https://httpd.apache.org/docs/current/mod/mod_log_config.html 1860af3cfad1Sdoug * 1861af3cfad1Sdoug * httpd's format is similar to these Apache LogFormats: 1862af3cfad1Sdoug * "%v %h %l %u %t \"%r\" %>s %B" 1863af3cfad1Sdoug * "%v %h %l %u %t \"%r\" %>s %B \"%{Referer}i\" \"%{User-agent}i\"" 1864ea62a379Sdoug */ 1865ea62a379Sdoug switch (srv_conf->logformat) { 1866ea62a379Sdoug case LOG_FORMAT_COMMON: 1867e3c03affSreyk /* Use vis to encode input values from the header */ 18686ebc1f19Ssemarie if (clt->clt_remote_user && 1869ba87bb65Sreyk stravis(&user, clt->clt_remote_user, HTTPD_LOGVIS) == -1) 1870e3c03affSreyk goto done; 1871e3c03affSreyk if (desc->http_version && 1872ba87bb65Sreyk stravis(&version, desc->http_version, HTTPD_LOGVIS) == -1) 18736ebc1f19Ssemarie goto done; 18746ebc1f19Ssemarie 1875e3c03affSreyk /* The following should be URL-encoded */ 18766ebc1f19Ssemarie if (desc->http_path && 1877e22b0c74Sreyk (path = url_encode(desc->http_path)) == NULL) 18786ebc1f19Ssemarie goto done; 18796ebc1f19Ssemarie 18806ebc1f19Ssemarie ret = evbuffer_add_printf(clt->clt_log, 188141dcbd59Stim "%s %s - %s [%s] \"%s %s%s%s%s%s\" %03d %zu\n", 188241dcbd59Stim srv_conf->name, ip, clt->clt_remote_user == NULL ? "-" : 188341dcbd59Stim user, tstamp, 1884ea62a379Sdoug server_httpmethod_byid(desc->http_method), 18856ebc1f19Ssemarie desc->http_path == NULL ? "" : path, 1886ea62a379Sdoug desc->http_query == NULL ? "" : "?", 188758963e4fSreyk desc->http_query == NULL ? "" : desc->http_query, 1888ea62a379Sdoug desc->http_version == NULL ? "" : " ", 18896ebc1f19Ssemarie desc->http_version == NULL ? "" : version, 18906ebc1f19Ssemarie code, len); 18916ebc1f19Ssemarie 1892ea62a379Sdoug break; 1893ea62a379Sdoug 1894ea62a379Sdoug case LOG_FORMAT_COMBINED: 189534ff7cffStb case LOG_FORMAT_FORWARDED: 1896ea62a379Sdoug key.kv_key = "Referer"; /* sic */ 1897ea62a379Sdoug if ((referrer = kv_find(&desc->http_headers, &key)) != NULL && 1898ea62a379Sdoug referrer->kv_value == NULL) 1899ea62a379Sdoug referrer = NULL; 1900ea62a379Sdoug 1901ea62a379Sdoug key.kv_key = "User-Agent"; 1902ea62a379Sdoug if ((agent = kv_find(&desc->http_headers, &key)) != NULL && 1903ea62a379Sdoug agent->kv_value == NULL) 1904ea62a379Sdoug agent = NULL; 1905ea62a379Sdoug 1906e3c03affSreyk /* Use vis to encode input values from the header */ 19076ebc1f19Ssemarie if (clt->clt_remote_user && 1908ba87bb65Sreyk stravis(&user, clt->clt_remote_user, HTTPD_LOGVIS) == -1) 1909e3c03affSreyk goto done; 1910095ccd49Sbenno if (clt->clt_remote_user == NULL && 1911095ccd49Sbenno clt->clt_tls_ctx != NULL && 1912095ccd49Sbenno (srv_conf->tls_flags & TLSFLAG_CA) && 1913095ccd49Sbenno tls_peer_cert_subject(clt->clt_tls_ctx) != NULL && 1914095ccd49Sbenno stravis(&user, tls_peer_cert_subject(clt->clt_tls_ctx), 1915095ccd49Sbenno HTTPD_LOGVIS) == -1) 1916095ccd49Sbenno goto done; 1917e3c03affSreyk if (desc->http_version && 1918ba87bb65Sreyk stravis(&version, desc->http_version, HTTPD_LOGVIS) == -1) 1919e3c03affSreyk goto done; 1920e3c03affSreyk if (agent && 1921ba87bb65Sreyk stravis(&agent_v, agent->kv_value, HTTPD_LOGVIS) == -1) 19226ebc1f19Ssemarie goto done; 19236ebc1f19Ssemarie 1924e3c03affSreyk /* The following should be URL-encoded */ 19256ebc1f19Ssemarie if (desc->http_path && 1926e22b0c74Sreyk (path = url_encode(desc->http_path)) == NULL) 19276ebc1f19Ssemarie goto done; 1928e22b0c74Sreyk if (referrer && 1929e22b0c74Sreyk (referrer_v = url_encode(referrer->kv_value)) == NULL) 19306ebc1f19Ssemarie goto done; 19316ebc1f19Ssemarie 193234ff7cffStb if ((ret = evbuffer_add_printf(clt->clt_log, 193341dcbd59Stim "%s %s - %s [%s] \"%s %s%s%s%s%s\"" 193434ff7cffStb " %03d %zu \"%s\" \"%s\"", 1935095ccd49Sbenno srv_conf->name, ip, user == NULL ? "-" : 193641dcbd59Stim user, tstamp, 1937ea62a379Sdoug server_httpmethod_byid(desc->http_method), 19386ebc1f19Ssemarie desc->http_path == NULL ? "" : path, 1939ea62a379Sdoug desc->http_query == NULL ? "" : "?", 194058963e4fSreyk desc->http_query == NULL ? "" : desc->http_query, 1941ea62a379Sdoug desc->http_version == NULL ? "" : " ", 19426ebc1f19Ssemarie desc->http_version == NULL ? "" : version, 1943ea62a379Sdoug code, len, 19446ebc1f19Ssemarie referrer == NULL ? "" : referrer_v, 194534ff7cffStb agent == NULL ? "" : agent_v)) == -1) 194634ff7cffStb break; 194734ff7cffStb 194834ff7cffStb if (srv_conf->logformat == LOG_FORMAT_COMBINED) 194934ff7cffStb goto finish; 195034ff7cffStb 195134ff7cffStb xff = xfp = NULL; 195234ff7cffStb 195334ff7cffStb key.kv_key = "X-Forwarded-For"; 195434ff7cffStb if ((xff = kv_find(&desc->http_headers, &key)) != NULL 195534ff7cffStb && xff->kv_value == NULL) 195634ff7cffStb xff = NULL; 195734ff7cffStb 195834ff7cffStb if (xff && 195934ff7cffStb stravis(&xff_v, xff->kv_value, HTTPD_LOGVIS) == -1) 196034ff7cffStb goto finish; 196134ff7cffStb 196234ff7cffStb key.kv_key = "X-Forwarded-Port"; 1963e95f05c9Sreyk if ((xfp = kv_find(&desc->http_headers, &key)) != NULL && 1964e95f05c9Sreyk (xfp->kv_value == NULL)) 196534ff7cffStb xfp = NULL; 196634ff7cffStb 196734ff7cffStb if (xfp && 196834ff7cffStb stravis(&xfp_v, xfp->kv_value, HTTPD_LOGVIS) == -1) 196934ff7cffStb goto finish; 197034ff7cffStb 197134ff7cffStb if ((ret = evbuffer_add_printf(clt->clt_log, " %s %s", 197234ff7cffStb xff == NULL ? "-" : xff_v, 197334ff7cffStb xfp == NULL ? "-" : xfp_v)) == -1) 197434ff7cffStb break; 197534ff7cffStb finish: 197634ff7cffStb ret = evbuffer_add_printf(clt->clt_log, "\n"); 19776ebc1f19Ssemarie 1978ea62a379Sdoug break; 1979944a3fefSreyk 1980944a3fefSreyk case LOG_FORMAT_CONNECTION: 1981e3c03affSreyk /* URL-encode the path */ 19826ebc1f19Ssemarie if (desc->http_path && 1983e22b0c74Sreyk (path = url_encode(desc->http_path)) == NULL) 19846ebc1f19Ssemarie goto done; 19856ebc1f19Ssemarie 19866ebc1f19Ssemarie ret = evbuffer_add_printf(clt->clt_log, " [%s]", 19876ebc1f19Ssemarie desc->http_path == NULL ? "" : path); 19886ebc1f19Ssemarie 1989944a3fefSreyk break; 1990ea62a379Sdoug } 1991ea62a379Sdoug 19926ebc1f19Ssemarie done: 19936ebc1f19Ssemarie free(user); 19946ebc1f19Ssemarie free(path); 19956ebc1f19Ssemarie free(version); 19966ebc1f19Ssemarie free(referrer_v); 19976ebc1f19Ssemarie free(agent_v); 199834ff7cffStb free(xff_v); 199934ff7cffStb free(xfp_v); 20006ebc1f19Ssemarie 20016ebc1f19Ssemarie return (ret); 2002ea62a379Sdoug } 2003