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