1 /* $NetBSD: bozohttpd.c,v 1.143 2023/06/07 20:12:31 mrg Exp $ */ 2 3 /* $eterna: bozohttpd.c,v 1.178 2011/11/18 09:21:15 mrg Exp $ */ 4 5 /* 6 * Copyright (c) 1997-2023 Matthew R. Green 7 * All rights reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer and 16 * dedication in the documentation and/or other materials provided 17 * with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 26 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 * 31 */ 32 33 /* this program is dedicated to the Great God of Processed Cheese */ 34 35 /* 36 * bozohttpd.c: minimal httpd; provides only these features: 37 * - HTTP/0.9 (by virtue of ..) 38 * - HTTP/1.0 39 * - HTTP/1.1 40 * - CGI/1.1 this will only be provided for "system" scripts 41 * - automatic "missing trailing slash" redirections 42 * - configurable translation of /~user/ to ~user/public_html, 43 * - access lists via libwrap via inetd/tcpd 44 * - virtual hosting 45 * - not that we do not even pretend to understand MIME, but 46 * rely only on the HTTP specification 47 * - ipv6 support 48 * - automatic `index.html' generation 49 * - configurable server name 50 * - directory index generation 51 * - daemon mode (lacks libwrap support) 52 * - .htpasswd support 53 */ 54 55 /* 56 * requirements for minimal http/1.1 (at least, as documented in 57 * RFC 2616 (HTTP/1.1): 58 * 59 * - 14.11: content-encoding handling. [1] 60 * 61 * - 14.13: content-length handling. this is only a SHOULD header 62 * thus we could just not send it ever. [1] 63 * 64 * - 14.17: content-type handling. [1] 65 * 66 * - 14.28: if-unmodified-since handling. if-modified-since is 67 * done since, shouldn't be too hard for this one. 68 * 69 * [1] need to revisit to ensure proper behaviour 70 * 71 * and the following is a list of features that we do not need 72 * to have due to other limits, or are too lazy. there are more 73 * of these than are listed, but these are of particular note, 74 * and could perhaps be implemented. 75 * 76 * - 3.5/3.6: content/transfer codings. probably can ignore 77 * this? we "SHOULD"n't. but 4.4 says we should ignore a 78 * `content-length' header upon receipt of a `transfer-encoding' 79 * header. 80 * 81 * - 5.1.1: request methods. only MUST support GET and HEAD, 82 * but there are new ones besides POST that are currently 83 * supported: OPTIONS PUT DELETE TRACE and CONNECT, plus 84 * extensions not yet known? 85 * 86 * - 10.1: we can ignore informational status codes 87 * 88 * - 10.3.3/10.3.4/10.3.8: just use '302' codes always. 89 * 90 * - 14.1/14.2/14.3/14.27: we do not support Accept: headers. 91 * just ignore them and send the request anyway. they are 92 * only SHOULD. 93 * 94 * - 14.5/14.16/14.35: only support simple ranges: %d- and %d-%d 95 * would be nice to support more. 96 * 97 * - 14.9: we aren't a cache. 98 * 99 * - 14.15: content-md5 would be nice. 100 * 101 * - 14.24/14.26/14.27: if-match, if-none-match, if-range. be 102 * nice to support this. 103 * 104 * - 14.44: Vary: seems unneeded. ignore it for now. 105 */ 106 107 #ifndef INDEX_HTML 108 #define INDEX_HTML "index.html" 109 #endif 110 #ifndef SERVER_SOFTWARE 111 #define SERVER_SOFTWARE "bozohttpd/20230602" 112 #endif 113 #ifndef PUBLIC_HTML 114 #define PUBLIC_HTML "public_html" 115 #endif 116 117 #ifndef USE_ARG 118 #define USE_ARG(x) /*LINTED*/(void)&(x) 119 #endif 120 121 /* 122 * And so it begins .. 123 */ 124 125 #include <sys/param.h> 126 #include <sys/socket.h> 127 #include <sys/time.h> 128 #include <sys/mman.h> 129 130 #include <arpa/inet.h> 131 132 #include <ctype.h> 133 #include <dirent.h> 134 #include <errno.h> 135 #include <fcntl.h> 136 #include <netdb.h> 137 #include <pwd.h> 138 #include <grp.h> 139 #include <stdarg.h> 140 #include <stdlib.h> 141 #include <stdint.h> 142 #include <strings.h> 143 #include <string.h> 144 #include <syslog.h> 145 #include <time.h> 146 #include <unistd.h> 147 148 #include "bozohttpd.h" 149 150 #ifndef SSL_TIMEOUT 151 #define SSL_TIMEOUT "30" /* ssl handshake: 30 seconds timeout */ 152 #endif 153 #ifndef INITIAL_TIMEOUT 154 #define INITIAL_TIMEOUT "30" /* wait for 30 seconds initially */ 155 #endif 156 #ifndef HEADER_WAIT_TIME 157 #define HEADER_WAIT_TIME "10" /* need more headers every 10 seconds */ 158 #endif 159 #ifndef TOTAL_MAX_REQ_TIME 160 #define TOTAL_MAX_REQ_TIME "600" /* must have total request in 600 */ 161 #endif /* seconds */ 162 163 /* if monotonic time is not available try real time. */ 164 #ifndef CLOCK_MONOTONIC 165 #define CLOCK_MONOTONIC CLOCK_REALTIME 166 #endif 167 168 /* variables and functions */ 169 #ifndef LOG_FTP 170 #define LOG_FTP LOG_DAEMON 171 #endif 172 173 /* 174 * List of special file that we should never serve. 175 */ 176 struct { 177 const char *file; 178 const char *name; 179 } specials[] = { 180 { REDIRECT_FILE, "rejected redirect request" }, 181 { ABSREDIRECT_FILE, "rejected absredirect request" }, 182 { REMAP_FILE, "rejected remap request" }, 183 { AUTH_FILE, "rejected authfile request" }, 184 { NULL, NULL }, 185 }; 186 187 volatile sig_atomic_t bozo_timeout_hit; 188 189 /* 190 * check there's enough space in the prefs and names arrays. 191 */ 192 static int 193 size_arrays(bozohttpd_t *httpd, bozoprefs_t *bozoprefs, size_t needed) 194 { 195 size_t len = sizeof(char *) * needed; 196 197 if (bozoprefs->size == 0) { 198 /* only get here first time around */ 199 bozoprefs->name = bozomalloc(httpd, len); 200 bozoprefs->value = bozomalloc(httpd, len); 201 } else if (bozoprefs->count == bozoprefs->size) { 202 /* only uses 'needed' when filled array */ 203 bozoprefs->name = bozorealloc(httpd, bozoprefs->name, len); 204 bozoprefs->value = bozorealloc(httpd, bozoprefs->value, len); 205 } 206 207 bozoprefs->size = needed; 208 return 1; 209 } 210 211 static ssize_t 212 findvar(bozoprefs_t *bozoprefs, const char *name) 213 { 214 size_t i; 215 216 for (i = 0; i < bozoprefs->count; i++) 217 if (strcmp(bozoprefs->name[i], name) == 0) 218 return (ssize_t)i; 219 return -1; 220 } 221 222 int 223 bozo_set_pref(bozohttpd_t *httpd, bozoprefs_t *bozoprefs, 224 const char *name, const char *value) 225 { 226 ssize_t i; 227 228 if ((i = findvar(bozoprefs, name)) < 0) { 229 /* add the element to the array */ 230 if (!size_arrays(httpd, bozoprefs, bozoprefs->size + 15)) 231 return 0; 232 i = bozoprefs->count++; 233 bozoprefs->name[i] = bozostrdup(httpd, NULL, name); 234 } else { 235 /* replace the element in the array */ 236 free(bozoprefs->value[i]); 237 } 238 bozoprefs->value[i] = bozostrdup(httpd, NULL, value); 239 return 1; 240 } 241 242 static void 243 bozo_clear_prefs(bozoprefs_t *prefs) 244 { 245 size_t i; 246 247 for (i = 0; i < prefs->count; i++) { 248 free(prefs->name[i]); 249 free(prefs->value[i]); 250 } 251 252 free(prefs->name); 253 free(prefs->value); 254 } 255 256 /* 257 * get a variable's value, or NULL 258 */ 259 char * 260 bozo_get_pref(bozoprefs_t *bozoprefs, const char *name) 261 { 262 ssize_t i; 263 264 i = findvar(bozoprefs, name); 265 return i < 0 ? NULL : bozoprefs->value[i]; 266 } 267 268 char * 269 bozo_http_date(char *date, size_t datelen) 270 { 271 struct tm *tm; 272 time_t now; 273 274 /* Sun, 06 Nov 1994 08:49:37 GMT */ 275 now = time(NULL); 276 tm = gmtime(&now); /* HTTP/1.1 spec rev 06 sez GMT only */ 277 strftime(date, datelen, "%a, %d %b %Y %H:%M:%S GMT", tm); 278 return date; 279 } 280 281 /* 282 * convert "in" into the three parts of a request (first line). 283 * we allocate into file and query, but return pointers into 284 * "in" for proto and method. 285 */ 286 static void 287 parse_request(bozohttpd_t *httpd, char *in, char **method, char **file, 288 char **query, char **proto) 289 { 290 ssize_t len; 291 char *val; 292 293 USE_ARG(httpd); 294 debug((httpd, DEBUG_EXPLODING, "parse in: %s", in)); 295 *method = *file = *query = *proto = NULL; 296 297 len = (ssize_t)strlen(in); 298 val = bozostrnsep(&in, " \t\n\r", &len); 299 if (len < 1 || val == NULL || in == NULL) 300 return; 301 *method = val; 302 303 while (*in == ' ' || *in == '\t') 304 in++; 305 val = bozostrnsep(&in, " \t\n\r", &len); 306 if (len < 1) { 307 if (len == 0) 308 *file = val; 309 else 310 *file = in; 311 } else { 312 *file = val; 313 314 *query = strchr(*file, '?'); 315 if (*query) 316 *(*query)++ = '\0'; 317 318 if (in) { 319 while (*in && (*in == ' ' || *in == '\t')) 320 in++; 321 if (*in) 322 *proto = in; 323 } 324 } 325 326 /* allocate private copies */ 327 *file = bozostrdup(httpd, NULL, *file); 328 if (*query) 329 *query = bozostrdup(httpd, NULL, *query); 330 331 debug((httpd, DEBUG_FAT, 332 "url: method: \"%s\" file: \"%s\" query: \"%s\" proto: \"%s\"", 333 *method, *file, *query ? *query : "", *proto ? *proto : "")); 334 } 335 336 /* 337 * cleanup a bozo_httpreq_t after use 338 */ 339 void 340 bozo_clean_request(bozo_httpreq_t *request) 341 { 342 struct bozoheaders *hdr, *ohdr = NULL; 343 344 if (request == NULL) 345 return; 346 347 /* If SSL enabled cleanup SSL structure. */ 348 bozo_ssl_destroy(request->hr_httpd); 349 350 /* clean up request */ 351 free(request->hr_remotehost); 352 free(request->hr_remoteaddr); 353 free(request->hr_serverport); 354 free(request->hr_virthostname); 355 free(request->hr_file_free); 356 /* XXX this is gross */ 357 if (request->hr_file_free != request->hr_oldfile) 358 free(request->hr_oldfile); 359 else 360 free(request->hr_file); 361 free(request->hr_query); 362 free(request->hr_host); 363 bozo_user_free(request->hr_user); 364 bozo_auth_cleanup(request); 365 for (hdr = SIMPLEQ_FIRST(&request->hr_headers); hdr; 366 hdr = SIMPLEQ_NEXT(hdr, h_next)) { 367 free(hdr->h_value); 368 free(hdr->h_header); 369 free(ohdr); 370 ohdr = hdr; 371 } 372 free(ohdr); 373 ohdr = NULL; 374 for (hdr = SIMPLEQ_FIRST(&request->hr_replheaders); hdr; 375 hdr = SIMPLEQ_NEXT(hdr, h_next)) { 376 free(hdr->h_value); 377 free(hdr->h_header); 378 free(ohdr); 379 ohdr = hdr; 380 } 381 free(ohdr); 382 383 free(request); 384 } 385 386 /* 387 * send a HTTP/1.1 408 response if we timeout. 388 */ 389 /* ARGSUSED */ 390 static void 391 alarmer(int sig) 392 { 393 USE_ARG(sig); 394 bozo_timeout_hit = 1; 395 } 396 397 398 /* 399 * set a timeout for "ssl", "initial", "header", or "request". 400 */ 401 int 402 bozo_set_timeout(bozohttpd_t *httpd, bozoprefs_t *prefs, 403 const char *target, const char *val) 404 { 405 const char **cur, *timeouts[] = { 406 "ssl timeout", 407 "initial timeout", 408 "header timeout", 409 "request timeout", 410 NULL, 411 }; 412 /* adjust minlen if more timeouts appear with conflicting names */ 413 const size_t minlen = 1; 414 size_t len = strlen(target); 415 416 for (cur = timeouts; len >= minlen && *cur; cur++) { 417 if (strncmp(target, *cur, len) == 0) { 418 bozo_set_pref(httpd, prefs, *cur, val); 419 return 0; 420 } 421 } 422 return 1; 423 } 424 425 /* 426 * a list of header quirks: currently, a list of headers that 427 * can't be folded into a single line. 428 */ 429 const char *header_quirks[] = { "WWW-Authenticate", NULL }; 430 431 /* 432 * add or merge this header (val: str) into the requests list 433 */ 434 static bozoheaders_t * 435 addmerge_header(bozo_httpreq_t *request, struct qheaders *headers, 436 const char *val, const char *str, ssize_t len) 437 { 438 struct bozohttpd_t *httpd = request->hr_httpd; 439 struct bozoheaders *hdr = NULL; 440 const char **quirk; 441 442 USE_ARG(len); 443 for (quirk = header_quirks; *quirk; quirk++) 444 if (strcasecmp(*quirk, val) == 0) 445 break; 446 447 if (*quirk == NULL) { 448 /* do we exist already? */ 449 SIMPLEQ_FOREACH(hdr, headers, h_next) { 450 if (strcasecmp(val, hdr->h_header) == 0) 451 break; 452 } 453 } 454 455 if (hdr) { 456 /* yup, merge it in */ 457 char *nval; 458 459 bozoasprintf(httpd, &nval, "%s, %s", hdr->h_value, str); 460 free(hdr->h_value); 461 hdr->h_value = nval; 462 } else { 463 /* nope, create a new one */ 464 465 hdr = bozomalloc(httpd, sizeof *hdr); 466 hdr->h_header = bozostrdup(httpd, request, val); 467 if (str && *str) 468 hdr->h_value = bozostrdup(httpd, request, str); 469 else 470 hdr->h_value = bozostrdup(httpd, request, " "); 471 472 SIMPLEQ_INSERT_TAIL(headers, hdr, h_next); 473 request->hr_nheaders++; 474 } 475 476 return hdr; 477 } 478 479 bozoheaders_t * 480 addmerge_reqheader(bozo_httpreq_t *request, const char *val, const char *str, 481 ssize_t len) 482 { 483 484 return addmerge_header(request, &request->hr_headers, val, str, len); 485 } 486 487 bozoheaders_t * 488 addmerge_replheader(bozo_httpreq_t *request, const char *val, const char *str, 489 ssize_t len) 490 { 491 492 return addmerge_header(request, &request->hr_replheaders, 493 val, str, len); 494 } 495 496 /* 497 * as the prototype string is not constant (eg, "HTTP/1.1" is equivalent 498 * to "HTTP/001.01"), we MUST parse this. 499 */ 500 static int 501 process_proto(bozo_httpreq_t *request, const char *proto) 502 { 503 struct bozohttpd_t *httpd = request->hr_httpd; 504 char majorstr[16], *minorstr; 505 int majorint, minorint; 506 507 if (proto == NULL) { 508 got_proto_09: 509 request->hr_proto = httpd->consts.http_09; 510 debug((httpd, DEBUG_FAT, "request %s is http/0.9", 511 request->hr_file)); 512 return 0; 513 } 514 515 if (strncasecmp(proto, "HTTP/", 5) != 0) 516 goto bad; 517 strncpy(majorstr, proto + 5, sizeof(majorstr)-1); 518 majorstr[sizeof(majorstr)-1] = 0; 519 minorstr = strchr(majorstr, '.'); 520 if (minorstr == NULL) 521 goto bad; 522 *minorstr++ = 0; 523 524 majorint = atoi(majorstr); 525 minorint = atoi(minorstr); 526 527 switch (majorint) { 528 case 0: 529 if (minorint != 9) 530 break; 531 goto got_proto_09; 532 case 1: 533 if (minorint == 0) 534 request->hr_proto = httpd->consts.http_10; 535 else if (minorint == 1) 536 request->hr_proto = httpd->consts.http_11; 537 else 538 break; 539 540 debug((httpd, DEBUG_FAT, "request %s is %s", 541 request->hr_file, request->hr_proto)); 542 SIMPLEQ_INIT(&request->hr_headers); 543 request->hr_nheaders = 0; 544 return 0; 545 } 546 bad: 547 return bozo_http_error(httpd, 404, NULL, "unknown prototype"); 548 } 549 550 /* 551 * process each type of HTTP method, setting this HTTP requests 552 * method type. 553 */ 554 static struct method_map { 555 const char *name; 556 int type; 557 } method_map[] = { 558 { "GET", HTTP_GET, }, 559 { "POST", HTTP_POST, }, 560 { "HEAD", HTTP_HEAD, }, 561 #if 0 /* other non-required http/1.1 methods */ 562 { "OPTIONS", HTTP_OPTIONS, }, 563 { "PUT", HTTP_PUT, }, 564 { "DELETE", HTTP_DELETE, }, 565 { "TRACE", HTTP_TRACE, }, 566 { "CONNECT", HTTP_CONNECT, }, 567 #endif 568 { NULL, 0, }, 569 }; 570 571 static int 572 process_method(bozo_httpreq_t *request, const char *method) 573 { 574 struct bozohttpd_t *httpd = request->hr_httpd; 575 struct method_map *mmp; 576 577 if (request->hr_proto == httpd->consts.http_11) 578 request->hr_allow = "GET, HEAD, POST"; 579 580 for (mmp = method_map; mmp->name; mmp++) 581 if (strcasecmp(method, mmp->name) == 0) { 582 request->hr_method = mmp->type; 583 request->hr_methodstr = mmp->name; 584 return 0; 585 } 586 587 return bozo_http_error(httpd, 404, request, "unknown method"); 588 } 589 590 /* check header byte count */ 591 static int 592 bozo_got_header_length(bozo_httpreq_t *request, size_t len) 593 { 594 595 if (len > BOZO_HEADERS_MAX_SIZE - request->hr_header_bytes) 596 return bozo_http_error(request->hr_httpd, 413, request, 597 "too many headers"); 598 599 request->hr_header_bytes += len; 600 601 return 0; 602 } 603 604 /* 605 * This function reads a http request from stdin, returning a pointer to a 606 * bozo_httpreq_t structure, describing the request. 607 */ 608 bozo_httpreq_t * 609 bozo_read_request(bozohttpd_t *httpd) 610 { 611 struct sigaction sa; 612 char *str, *val, *method, *file, *proto, *query; 613 char *host, *addr, *port; 614 char bufport[10]; 615 char hbuf[NI_MAXHOST], abuf[NI_MAXHOST]; 616 struct sockaddr_storage ss; 617 ssize_t len; 618 int line = 0; 619 socklen_t slen; 620 bozo_httpreq_t *request; 621 struct timespec ots, ts; 622 623 /* 624 * if we're in daemon mode, bozo_daemon_fork() will return here twice 625 * for each call. once in the child, returning 0, and once in the 626 * parent, returning 1 for each child. 627 */ 628 if (bozo_daemon_fork(httpd)) 629 return NULL; 630 631 request = bozomalloc(httpd, sizeof(*request)); 632 memset(request, 0, sizeof(*request)); 633 request->hr_httpd = httpd; 634 request->hr_allow = request->hr_host = NULL; 635 request->hr_content_type = request->hr_content_length = NULL; 636 request->hr_range = NULL; 637 request->hr_last_byte_pos = -1; 638 request->hr_if_modified_since = NULL; 639 request->hr_virthostname = NULL; 640 request->hr_file_free = NULL; 641 request->hr_file = NULL; 642 request->hr_oldfile = NULL; 643 SIMPLEQ_INIT(&request->hr_replheaders); 644 bozo_auth_init(request); 645 646 slen = sizeof(ss); 647 if (getpeername(0, (struct sockaddr *)(void *)&ss, &slen) < 0) 648 host = addr = NULL; 649 else { 650 if (getnameinfo((struct sockaddr *)(void *)&ss, slen, 651 abuf, sizeof abuf, NULL, 0, NI_NUMERICHOST) == 0) 652 addr = abuf; 653 else 654 addr = NULL; 655 if (httpd->numeric == 0 && 656 getnameinfo((struct sockaddr *)(void *)&ss, slen, 657 hbuf, sizeof hbuf, NULL, 0, 0) == 0) 658 host = hbuf; 659 else 660 host = NULL; 661 } 662 if (host != NULL) 663 request->hr_remotehost = bozostrdup(httpd, request, host); 664 if (addr != NULL) 665 request->hr_remoteaddr = bozostrdup(httpd, request, addr); 666 slen = sizeof(ss); 667 668 /* 669 * Override the bound port from the request value, so it works even 670 * if passed through a proxy that doesn't rewrite the port. 671 */ 672 port = NULL; 673 if (httpd->bindport) { 674 if (strcmp(httpd->bindport, BOZO_HTTP_PORT) != 0) 675 port = httpd->bindport; 676 } else if (getsockname(0, (struct sockaddr *)(void *)&ss, &slen) == 0 && 677 getnameinfo((struct sockaddr *)(void *)&ss, slen, NULL, 0, 678 bufport, sizeof bufport, NI_NUMERICSERV) == 0) 679 port = bufport; 680 if (port != NULL) 681 request->hr_serverport = bozostrdup(httpd, request, port); 682 683 /* 684 * setup a timer to make sure the request is not hung 685 */ 686 sa.sa_handler = alarmer; 687 sigemptyset(&sa.sa_mask); 688 sigaddset(&sa.sa_mask, SIGALRM); 689 sa.sa_flags = 0; 690 sigaction(SIGALRM, &sa, NULL); 691 692 if (clock_gettime(CLOCK_MONOTONIC, &ots) != 0) { 693 bozo_http_error(httpd, 500, NULL, "clock_gettime failed"); 694 goto cleanup; 695 } 696 697 /* 698 * now to try to setup SSL, and upon failure parent can signal the 699 * caller there was no request to process and it will wait for 700 * another. 701 */ 702 if (bozo_ssl_accept(httpd)) 703 return NULL; 704 705 alarm(httpd->initial_timeout); 706 while ((str = bozodgetln(httpd, STDIN_FILENO, &len, bozo_read)) != NULL) { 707 alarm(0); 708 709 if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) { 710 bozo_http_error(httpd, 500, NULL, "clock_gettime failed"); 711 goto cleanup; 712 } 713 /* 714 * don't timeout if old tv_sec is not more than current 715 * tv_sec, or if current tv_sec is less than the request 716 * timeout (these shouldn't happen, but the first could 717 * if monotonic time is not available.) 718 * 719 * the other timeout and header size checks should ensure 720 * that even if time it set backwards or forwards a very 721 * long way, timeout will eventually happen, even if this 722 * one fails. 723 */ 724 if (ts.tv_sec > ots.tv_sec && 725 ts.tv_sec > httpd->request_timeout && 726 ts.tv_sec - httpd->request_timeout > ots.tv_sec) 727 bozo_timeout_hit = 1; 728 729 if (bozo_timeout_hit) { 730 bozo_http_error(httpd, 408, NULL, "request timed out"); 731 goto cleanup; 732 } 733 line++; 734 735 if (line == 1) { 736 if (len < 1) { 737 bozo_http_error(httpd, 404, NULL, "null method"); 738 goto cleanup; 739 } 740 bozowarn(httpd, 741 "got request ``%s'' from host %s to port %s", 742 str, 743 host ? host : addr ? addr : "<local>", 744 port ? port : "<stdin>"); 745 746 /* we allocate return space in file and query only */ 747 parse_request(httpd, str, &method, &file, &query, &proto); 748 request->hr_file_free = request->hr_file = file; 749 request->hr_query = query; 750 if (method == NULL) { 751 bozo_http_error(httpd, 404, NULL, "null method"); 752 goto cleanup; 753 } 754 if (file == NULL) { 755 bozo_http_error(httpd, 404, NULL, "null file"); 756 goto cleanup; 757 } 758 759 /* 760 * note that we parse the proto first, so that we 761 * can more properly parse the method and the url. 762 */ 763 764 if (process_proto(request, proto) || 765 process_method(request, method)) { 766 goto cleanup; 767 } 768 769 debug((httpd, DEBUG_FAT, "got file \"%s\" query \"%s\"", 770 request->hr_file, 771 request->hr_query ? request->hr_query : "<none>")); 772 773 /* http/0.9 has no header processing */ 774 if (request->hr_proto == httpd->consts.http_09) 775 break; 776 } else { /* incoming headers */ 777 bozoheaders_t *hdr; 778 779 if (*str == '\0') 780 break; 781 782 val = bozostrnsep(&str, ":", &len); 783 debug((httpd, DEBUG_EXPLODING, "read_req2: after " 784 "bozostrnsep: str `%s' val `%s'", 785 str ? str : "<null>", val ? val : "<null>")); 786 if (val == NULL || len == -1) { 787 bozo_http_error(httpd, 404, request, "no header"); 788 goto cleanup; 789 } 790 if (str == NULL) { 791 bozo_http_error(httpd, 404, request, 792 "malformed header"); 793 goto cleanup; 794 } 795 while (*str == ' ' || *str == '\t') 796 len--, str++; 797 while (*val == ' ' || *val == '\t') 798 val++; 799 800 if (bozo_got_header_length(request, len)) 801 goto cleanup; 802 803 if (bozo_auth_check_headers(request, val, str, len)) 804 goto next_header; 805 806 hdr = addmerge_reqheader(request, val, str, len); 807 808 if (strcasecmp(hdr->h_header, "content-type") == 0) 809 request->hr_content_type = hdr->h_value; 810 else if (strcasecmp(hdr->h_header, "content-length") == 0) 811 request->hr_content_length = hdr->h_value; 812 else if (strcasecmp(hdr->h_header, "host") == 0) { 813 if (request->hr_host) { 814 /* RFC 7230 (HTTP/1.1): 5.4 */ 815 bozo_http_error(httpd, 400, request, 816 "Only allow one Host: header"); 817 goto cleanup; 818 } 819 request->hr_host = bozostrdup(httpd, request, 820 hdr->h_value); 821 } 822 /* RFC 2616 (HTTP/1.1): 14.20 */ 823 else if (strcasecmp(hdr->h_header, "expect") == 0) { 824 bozo_http_error(httpd, 417, request, 825 "we don't support Expect:"); 826 goto cleanup; 827 } 828 else if (strcasecmp(hdr->h_header, "referrer") == 0 || 829 strcasecmp(hdr->h_header, "referer") == 0) 830 request->hr_referrer = hdr->h_value; 831 else if (strcasecmp(hdr->h_header, "range") == 0) 832 request->hr_range = hdr->h_value; 833 else if (strcasecmp(hdr->h_header, 834 "if-modified-since") == 0) 835 request->hr_if_modified_since = hdr->h_value; 836 else if (strcasecmp(hdr->h_header, 837 "accept-encoding") == 0) 838 request->hr_accept_encoding = hdr->h_value; 839 840 debug((httpd, DEBUG_FAT, "adding header %s: %s", 841 hdr->h_header, hdr->h_value)); 842 } 843 next_header: 844 alarm(httpd->header_timeout); 845 } 846 if (str == NULL) { 847 bozo_http_error(httpd, 413, request, "request too large"); 848 goto cleanup; 849 } 850 851 /* now, clear it all out */ 852 alarm(0); 853 signal(SIGALRM, SIG_DFL); 854 855 /* RFC1945, 8.3 */ 856 if (request->hr_method == HTTP_POST && 857 request->hr_content_length == NULL) { 858 bozo_http_error(httpd, 400, request, "missing content length"); 859 goto cleanup; 860 } 861 862 /* RFC 2616 (HTTP/1.1), 14.23 & 19.6.1.1 */ 863 if (request->hr_proto == httpd->consts.http_11 && 864 /*(strncasecmp(request->hr_file, "http://", 7) != 0) &&*/ 865 request->hr_host == NULL) { 866 bozo_http_error(httpd, 400, request, "missing Host header"); 867 goto cleanup; 868 } 869 870 if (request->hr_range != NULL) { 871 debug((httpd, DEBUG_FAT, "hr_range: %s", request->hr_range)); 872 /* support only simple ranges %d- and %d-%d */ 873 if (strchr(request->hr_range, ',') == NULL) { 874 const char *rstart, *dash; 875 876 rstart = strchr(request->hr_range, '='); 877 if (rstart != NULL) { 878 rstart++; 879 dash = strchr(rstart, '-'); 880 if (dash != NULL && dash != rstart) { 881 dash++; 882 request->hr_have_range = 1; 883 request->hr_first_byte_pos = 884 strtoll(rstart, NULL, 10); 885 if (request->hr_first_byte_pos < 0) 886 request->hr_first_byte_pos = 0; 887 if (*dash != '\0') { 888 request->hr_last_byte_pos = 889 strtoll(dash, NULL, 10); 890 if (request->hr_last_byte_pos < 0) 891 request->hr_last_byte_pos = -1; 892 } 893 } 894 } 895 } 896 } 897 898 debug((httpd, DEBUG_FAT, "bozo_read_request returns url %s in request", 899 request->hr_file)); 900 return request; 901 902 cleanup: 903 bozo_clean_request(request); 904 905 return NULL; 906 } 907 908 static int 909 mmap_and_write_part(bozohttpd_t *httpd, int fd, off_t first_byte_pos, size_t sz) 910 { 911 size_t mappedsz, wroffset; 912 off_t mappedoffset; 913 char *addr; 914 void *mappedaddr; 915 916 /* 917 * we need to ensure that both the size *and* offset arguments to 918 * mmap() are page-aligned. our formala for this is: 919 * 920 * input offset: first_byte_pos 921 * input size: sz 922 * 923 * mapped offset = page align truncate (input offset) 924 * mapped size = 925 * page align extend (input offset - mapped offset + input size) 926 * write offset = input offset - mapped offset 927 * 928 * we use the write offset in all writes 929 */ 930 mappedoffset = first_byte_pos & ~((off_t)httpd->page_size - 1); 931 mappedsz = (size_t) 932 (first_byte_pos - mappedoffset + sz + httpd->page_size - 1) & 933 ~(httpd->page_size - 1); 934 wroffset = (size_t)(first_byte_pos - mappedoffset); 935 936 addr = mmap(0, mappedsz, PROT_READ, MAP_SHARED, fd, mappedoffset); 937 if (addr == MAP_FAILED) { 938 bozowarn(httpd, "mmap failed: %s", strerror(errno)); 939 return -1; 940 } 941 mappedaddr = addr; 942 943 #ifdef MADV_SEQUENTIAL 944 (void)madvise(addr, sz, MADV_SEQUENTIAL); 945 #endif 946 while (sz > BOZO_WRSZ) { 947 if (bozo_write(httpd, STDOUT_FILENO, addr + wroffset, 948 BOZO_WRSZ) != BOZO_WRSZ) { 949 bozowarn(httpd, "write failed: %s", strerror(errno)); 950 goto out; 951 } 952 debug((httpd, DEBUG_OBESE, "wrote %d bytes", BOZO_WRSZ)); 953 sz -= BOZO_WRSZ; 954 addr += BOZO_WRSZ; 955 } 956 if (sz && (size_t)bozo_write(httpd, STDOUT_FILENO, addr + wroffset, 957 sz) != sz) { 958 bozowarn(httpd, "final write failed: %s", strerror(errno)); 959 goto out; 960 } 961 debug((httpd, DEBUG_OBESE, "wrote %d bytes", (int)sz)); 962 out: 963 if (munmap(mappedaddr, mappedsz) < 0) { 964 bozowarn(httpd, "munmap failed"); 965 return -1; 966 } 967 968 return 0; 969 } 970 971 static int 972 parse_http_date(const char *val, time_t *timestamp) 973 { 974 char *remainder; 975 struct tm tm; 976 977 if ((remainder = strptime(val, "%a, %d %b %Y %T GMT", &tm)) == NULL && 978 (remainder = strptime(val, "%a, %d-%b-%y %T GMT", &tm)) == NULL && 979 (remainder = strptime(val, "%a %b %d %T %Y", &tm)) == NULL) 980 return 0; /* Invalid HTTP date format */ 981 982 if (*remainder) 983 return 0; /* No trailing garbage */ 984 985 *timestamp = timegm(&tm); 986 return 1; 987 } 988 989 /* 990 * given an url, encode it ala rfc 3986. ie, escape ? and friends. 991 * note that this function returns a static buffer, and thus needs 992 * to be updated for any sort of parallel processing. escape only 993 * chosen characters for absolute redirects 994 */ 995 char * 996 bozo_escape_rfc3986(bozohttpd_t *httpd, const char *url, int absolute) 997 { 998 static char *buf; 999 static size_t buflen = 0; 1000 size_t len; 1001 const char *s; 1002 char *d; 1003 1004 len = strlen(url); 1005 if (buflen < len * 3 + 1) { 1006 buflen = len * 3 + 1; 1007 buf = bozorealloc(httpd, buf, buflen); 1008 } 1009 1010 for (s = url, d = buf; *s;) { 1011 if (*s & 0x80) 1012 goto encode_it; 1013 switch (*s) { 1014 case ':': 1015 case '?': 1016 case '#': 1017 case '[': 1018 case ']': 1019 case '@': 1020 case '!': 1021 case '$': 1022 case '&': 1023 case '\'': 1024 case '(': 1025 case ')': 1026 case '*': 1027 case '+': 1028 case ',': 1029 case ';': 1030 case '=': 1031 case '%': 1032 case '"': 1033 if (absolute) 1034 goto leave_it; 1035 /*FALLTHROUGH*/ 1036 case '\n': 1037 case '\r': 1038 case ' ': 1039 encode_it: 1040 snprintf(d, 4, "%%%02X", (unsigned char)*s++); 1041 d += 3; 1042 break; 1043 default: 1044 leave_it: 1045 *d++ = *s++; 1046 break; 1047 } 1048 } 1049 *d = 0; 1050 1051 return buf; 1052 } 1053 1054 /* 1055 * do automatic redirection -- if there are query parameters or userdir for 1056 * the URL we will tack these on to the new (redirected) URL. 1057 */ 1058 static void 1059 handle_redirect(bozo_httpreq_t *request, const char *url, int absolute) 1060 { 1061 bozohttpd_t *httpd = request->hr_httpd; 1062 char *finalurl, *urlbuf; 1063 #ifndef NO_USER_SUPPORT 1064 char *userbuf; 1065 #endif /* !NO_USER_SUPPORT */ 1066 char portbuf[20]; 1067 const char *scheme, *query, *quest; 1068 const char *hostname = BOZOHOST(httpd, request); 1069 int absproto = 0; /* absolute redirect provides own schema */ 1070 1071 if (url == NULL) { 1072 bozoasprintf(httpd, &urlbuf, "/%s/", request->hr_file); 1073 url = urlbuf; 1074 } else 1075 urlbuf = NULL; 1076 1077 #ifndef NO_USER_SUPPORT 1078 if (request->hr_user && !absolute) { 1079 bozoasprintf(httpd, &userbuf, "/~%s%s", request->hr_user, url); 1080 url = userbuf; 1081 } else 1082 userbuf = NULL; 1083 #endif /* !NO_USER_SUPPORT */ 1084 1085 if (absolute) { 1086 char *sep = NULL; 1087 const char *s; 1088 1089 /* 1090 * absolute redirect may specify own protocol i.e. to redirect 1091 * to another schema like https:// or ftp://. 1092 * Details: RFC 3986, section 3. 1093 */ 1094 1095 /* 1. check if url contains :// */ 1096 sep = strstr(url, "://"); 1097 1098 /* 1099 * RFC 3986, section 3.1: 1100 * scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) 1101 */ 1102 if (sep) { 1103 for (s = url; s != sep;) { 1104 if (!isalnum((unsigned char)*s) && 1105 *s != '+' && *s != '-' && *s != '.') 1106 break; 1107 if (++s == sep) { 1108 absproto = 1; 1109 } 1110 } 1111 } 1112 } 1113 1114 /* construct final redirection url */ 1115 1116 scheme = absproto ? "" : httpd->sslinfo ? "https://" : "http://"; 1117 1118 if (absolute) { 1119 hostname = ""; 1120 portbuf[0] = '\0'; 1121 } else { 1122 const char *defport = httpd->sslinfo ? BOZO_HTTPS_PORT : BOZO_HTTP_PORT; 1123 1124 if (request->hr_serverport && 1125 strcmp(request->hr_serverport, defport) != 0) 1126 snprintf(portbuf, sizeof(portbuf), ":%s", 1127 request->hr_serverport); 1128 else 1129 portbuf[0] = '\0'; 1130 } 1131 1132 url = bozo_escape_rfc3986(httpd, url, absolute); 1133 1134 if (request->hr_query && strlen(request->hr_query)) { 1135 query = request->hr_query; 1136 quest = "?"; 1137 } else { 1138 query = quest = ""; 1139 } 1140 1141 bozoasprintf(httpd, &finalurl, "%s%s%s%s%s%s", 1142 scheme, hostname, portbuf, url, quest, query); 1143 1144 bozowarn(httpd, "redirecting %s", finalurl); 1145 debug((httpd, DEBUG_FAT, "redirecting %s", finalurl)); 1146 1147 bozo_printf(httpd, "%s 301 Document Moved\r\n", request->hr_proto); 1148 if (request->hr_proto != httpd->consts.http_09) 1149 bozo_print_header(request, NULL, "text/html", NULL); 1150 if (request->hr_proto != httpd->consts.http_09) 1151 bozo_printf(httpd, "Location: %s\r\n", finalurl); 1152 bozo_printf(httpd, "\r\n"); 1153 if (request->hr_method == HTTP_HEAD) 1154 goto head; 1155 bozo_printf(httpd, "<html><head><title>Document Moved</title></head>\n"); 1156 bozo_printf(httpd, "<body><h1>Document Moved</h1>\n"); 1157 bozo_printf(httpd, "This document had moved <a href=\"%s\">here</a>\n", 1158 finalurl); 1159 bozo_printf(httpd, "</body></html>\n"); 1160 head: 1161 bozo_flush(httpd, stdout); 1162 free(urlbuf); 1163 free(finalurl); 1164 #ifndef NO_USER_SUPPORT 1165 free(userbuf); 1166 #endif /* !NO_USER_SUPPORT */ 1167 } 1168 1169 /* 1170 * Like strncmp(), but s_esc may contain characters escaped by \. 1171 * The len argument does not include the backslashes used for escaping, 1172 * that is: it gives the raw len, after unescaping the string. 1173 */ 1174 static int 1175 esccmp(const char *s_plain, const char *s_esc, size_t len) 1176 { 1177 bool esc = false; 1178 1179 while (len) { 1180 if (!esc && *s_esc == '\\') { 1181 esc = true; 1182 s_esc++; 1183 continue; 1184 } 1185 esc = false; 1186 if (*s_plain == 0 || *s_esc == 0 || *s_plain != *s_esc) 1187 return *s_esc - *s_plain; 1188 s_esc++; 1189 s_plain++; 1190 len--; 1191 } 1192 return 0; 1193 } 1194 1195 /* 1196 * Check if the request refers to a uri that is mapped via a .bzremap. 1197 * We have /requested/path:/re/mapped/to/this.html lines in there, 1198 * and the : separator may be use in the left hand side escaped with 1199 * \ to encode a path containig a : character. 1200 */ 1201 static void 1202 check_remap(bozo_httpreq_t *request) 1203 { 1204 bozohttpd_t *httpd = request->hr_httpd; 1205 char *file = request->hr_file, *newfile; 1206 void *fmap; 1207 const char *replace = NULL, *map_to = NULL, *p; 1208 struct stat st; 1209 int mapfile; 1210 size_t avail, len, rlen, reqlen, num_esc = 0; 1211 bool escaped = false; 1212 1213 mapfile = open(REMAP_FILE, O_RDONLY, 0); 1214 if (mapfile == -1) 1215 return; 1216 debug((httpd, DEBUG_FAT, "remap file found")); 1217 if (fstat(mapfile, &st) == -1) { 1218 bozowarn(httpd, "could not stat " REMAP_FILE ", errno: %d", 1219 errno); 1220 goto out; 1221 } 1222 1223 fmap = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, mapfile, 0); 1224 if (fmap == MAP_FAILED) { 1225 bozowarn(httpd, "could not mmap " REMAP_FILE ", error %d", 1226 errno); 1227 goto out; 1228 } 1229 reqlen = strlen(file); 1230 for (p = fmap, avail = st.st_size; avail; ) { 1231 /* 1232 * We have lines like: 1233 * /this/url:/replacement/that/url 1234 * If we find a matching left hand side, replace will point 1235 * to it and len will be its length. map_to will point to 1236 * the right hand side and rlen wil be its length. 1237 * If we have no match, both pointers will be NULL. 1238 */ 1239 1240 /* skip empty lines */ 1241 while ((*p == '\r' || *p == '\n') && avail) { 1242 p++; 1243 avail--; 1244 } 1245 replace = p; 1246 escaped = false; 1247 while (avail) { 1248 if (*p == '\r' || *p == '\n') 1249 break; 1250 if (!escaped && *p == ':') 1251 break; 1252 if (escaped) { 1253 escaped = false; 1254 num_esc++; 1255 } else if (*p == '\\') { 1256 escaped = true; 1257 } 1258 p++; 1259 avail--; 1260 } 1261 if (!avail || *p != ':') { 1262 replace = NULL; 1263 map_to = NULL; 1264 break; 1265 } 1266 len = p - replace - num_esc; 1267 /* 1268 * reqlen < len: the left hand side is too long, can't be a 1269 * match 1270 * reqlen == len: full string has to match 1271 * reqlen > len: make sure there is a path separator at 'len' 1272 * avail < 2: we are at eof, missing right hand side 1273 */ 1274 if (avail < 2 || reqlen < len || 1275 (reqlen == len && esccmp(file, replace, len) != 0) || 1276 (reqlen > len && (file[len] != '/' || 1277 esccmp(file, replace, len) != 0))) { 1278 1279 /* non-match, skip to end of line and continue */ 1280 while (*p != '\r' && *p != '\n' && avail) { 1281 p++; 1282 avail--; 1283 } 1284 replace = NULL; 1285 map_to = NULL; 1286 continue; 1287 } 1288 p++; 1289 avail--; 1290 1291 /* found a match, parse the target */ 1292 map_to = p; 1293 while (*p != '\r' && *p != '\n' && avail) { 1294 p++; 1295 avail--; 1296 } 1297 rlen = p - map_to; 1298 break; 1299 } 1300 1301 if (replace && map_to) { 1302 newfile = bozomalloc(httpd, strlen(file) + rlen - len + 1); 1303 memcpy(newfile, map_to, rlen); 1304 strcpy(newfile+rlen, file + len); 1305 debug((httpd, DEBUG_NORMAL, "remapping found '%s'", 1306 newfile)); 1307 free(request->hr_file_free); 1308 request->hr_file_free = request->hr_file = newfile; 1309 } 1310 1311 munmap(fmap, st.st_size); 1312 out: 1313 close(mapfile); 1314 } 1315 1316 /* 1317 * deal with virtual host names; we do this: 1318 * if we have a virtual path root (httpd->virtbase), and we are given a 1319 * virtual host spec (Host: ho.st or http://ho.st/), see if this 1320 * directory exists under httpd->virtbase. if it does, use this as the 1321 # new slashdir. 1322 */ 1323 static int 1324 check_virtual(bozo_httpreq_t *request) 1325 { 1326 bozohttpd_t *httpd = request->hr_httpd; 1327 char *file = request->hr_file, *s; 1328 size_t len; 1329 1330 /* 1331 * convert http://virtual.host/ to request->hr_host 1332 */ 1333 debug((httpd, DEBUG_OBESE, 1334 "checking for http:// virtual host in '%s'", file)); 1335 if (strncasecmp(file, "http://", 7) == 0) { 1336 /* we would do virtual hosting here? */ 1337 file += 7; 1338 /* RFC 2616 (HTTP/1.1), 5.2: URI takes precedence over Host: */ 1339 free(request->hr_host); 1340 request->hr_host = bozostrdup(httpd, request, file); 1341 if ((s = strchr(request->hr_host, '/')) != NULL) 1342 *s = '\0'; 1343 s = strchr(file, '/'); 1344 free(request->hr_file_free); 1345 request->hr_file_free = request->hr_file = 1346 bozostrdup(httpd, request, s ? s : "/"); 1347 debug((httpd, DEBUG_OBESE, "got host '%s' file is now '%s'", 1348 request->hr_host, request->hr_file)); 1349 } else if (!request->hr_host) 1350 goto use_slashdir; 1351 1352 /* 1353 * canonicalise hr_host - that is, remove any :80. 1354 */ 1355 len = strlen(request->hr_host); 1356 if (len > 3 && 1357 strcmp(request->hr_host + len - 3, ":" BOZO_HTTP_PORT) == 0) { 1358 request->hr_host[len - 3] = '\0'; 1359 len = strlen(request->hr_host); 1360 } 1361 1362 if (!httpd->virtbase) { 1363 /* 1364 * if we don't use vhost support, then set virthostname if 1365 * user supplied Host header. It will be used for possible 1366 * redirections 1367 */ 1368 if (request->hr_host) { 1369 s = strrchr(request->hr_host, ':'); 1370 if (s != NULL) 1371 /* 1372 * truncate Host: as we want to copy it 1373 * without port part 1374 */ 1375 *s = '\0'; 1376 request->hr_virthostname = bozostrdup(httpd, request, 1377 request->hr_host); 1378 if (s != NULL) 1379 /* fix Host: again, if we truncated it */ 1380 *s = ':'; 1381 } 1382 goto use_slashdir; 1383 } 1384 1385 /* 1386 * ok, we have a virtual host, use opendir(3) to find a case 1387 * insensitive match for the virtual host we are asked for. 1388 * note that if the virtual host is the same as the master, 1389 * we don't need to do anything special. 1390 */ 1391 debug((httpd, DEBUG_OBESE, 1392 "check_virtual: checking host `%s' under httpd->virtbase `%s' " 1393 "for file `%s'", 1394 request->hr_host, httpd->virtbase, request->hr_file)); 1395 if (strncasecmp(httpd->virthostname, request->hr_host, len) != 0) { 1396 s = NULL; 1397 DIR *dirp; 1398 struct dirent *d; 1399 1400 if ((dirp = opendir(httpd->virtbase)) != NULL) { 1401 while ((d = readdir(dirp)) != NULL) { 1402 if (strcmp(d->d_name, ".") == 0 || 1403 strcmp(d->d_name, "..") == 0) { 1404 continue; 1405 } 1406 debug((httpd, DEBUG_OBESE, "looking at dir '%s'", 1407 d->d_name)); 1408 if (strcmp(d->d_name, request->hr_host) == 0) { 1409 /* found it, punch it */ 1410 debug((httpd, DEBUG_OBESE, "found it punch it")); 1411 request->hr_virthostname = 1412 bozostrdup(httpd, request, d->d_name); 1413 bozoasprintf(httpd, &s, "%s/%s", 1414 httpd->virtbase, 1415 request->hr_virthostname); 1416 break; 1417 } 1418 } 1419 closedir(dirp); 1420 } 1421 else { 1422 debug((httpd, DEBUG_FAT, "opendir %s failed: %s", 1423 httpd->virtbase, strerror(errno))); 1424 } 1425 if (s == 0) { 1426 if (httpd->unknown_slash) 1427 goto use_slashdir; 1428 return bozo_http_error(httpd, 404, request, 1429 "unknown URL"); 1430 } 1431 } else 1432 use_slashdir: 1433 s = httpd->slashdir; 1434 1435 /* 1436 * ok, nailed the correct slashdir, chdir to it 1437 */ 1438 if (chdir(s) < 0) 1439 return bozo_http_error(httpd, 404, request, 1440 "can't chdir to slashdir"); 1441 1442 /* 1443 * is there a mapping for this request? 1444 */ 1445 check_remap(request); 1446 1447 return 0; 1448 } 1449 1450 /* 1451 * checks to see if this request has a valid .bzredirect file. returns 1452 * 0 when no redirection happened, or 1 when handle_redirect() has been 1453 * called, -1 on error. 1454 */ 1455 static int 1456 check_bzredirect(bozo_httpreq_t *request) 1457 { 1458 bozohttpd_t *httpd = request->hr_httpd; 1459 struct stat sb; 1460 char dir[MAXPATHLEN], redir[MAXPATHLEN], redirpath[MAXPATHLEN + 1], 1461 path[MAXPATHLEN + 1]; 1462 char *basename, *finalredir; 1463 int rv, absolute; 1464 1465 /* 1466 * if this pathname is really a directory, but doesn't end in /, 1467 * use it as the directory to look for the redir file. 1468 */ 1469 if ((size_t)snprintf(dir, sizeof(dir), "%s", request->hr_file + 1) >= 1470 sizeof(dir)) { 1471 bozo_http_error(httpd, 404, request, "file path too long"); 1472 return -1; 1473 } 1474 debug((httpd, DEBUG_FAT, "check_bzredirect: dir %s", dir)); 1475 basename = strrchr(dir, '/'); 1476 1477 if ((!basename || basename[1] != '\0') && 1478 lstat(dir, &sb) == 0 && S_ISDIR(sb.st_mode)) { 1479 strcpy(path, dir); 1480 basename = dir; 1481 } else if (basename == NULL) { 1482 strcpy(path, "."); 1483 strcpy(dir, ""); 1484 basename = request->hr_file + 1; 1485 } else { 1486 *basename++ = '\0'; 1487 strcpy(path, dir); 1488 } 1489 if (bozo_check_special_files(request, basename, true)) 1490 return -1; 1491 1492 debug((httpd, DEBUG_FAT, "check_bzredirect: path %s", path)); 1493 1494 if ((size_t)snprintf(redir, sizeof(redir), "%s/%s", path, 1495 REDIRECT_FILE) >= sizeof(redir)) { 1496 return bozo_http_error(httpd, 404, request, 1497 "redirectfile path too long"); 1498 } 1499 if (lstat(redir, &sb) == 0) { 1500 if (!S_ISLNK(sb.st_mode)) 1501 return 0; 1502 absolute = 0; 1503 } else { 1504 if ((size_t)snprintf(redir, sizeof(redir), "%s/%s", path, 1505 ABSREDIRECT_FILE) >= sizeof(redir)) { 1506 bozo_http_error(httpd, 404, request, 1507 "redirectfile path too long"); 1508 return -1; 1509 } 1510 if (lstat(redir, &sb) < 0 || !S_ISLNK(sb.st_mode)) 1511 return 0; 1512 absolute = 1; 1513 } 1514 debug((httpd, DEBUG_FAT, "check_bzredirect: calling readlink")); 1515 rv = readlink(redir, redirpath, sizeof redirpath - 1); 1516 if (rv == -1 || rv == 0) { 1517 debug((httpd, DEBUG_FAT, "readlink failed")); 1518 return 0; 1519 } 1520 redirpath[rv] = '\0'; 1521 debug((httpd, DEBUG_FAT, "readlink returned \"%s\"", redirpath)); 1522 1523 /* check if we need authentication */ 1524 snprintf(path, sizeof(path), "%s/", dir); 1525 if (bozo_auth_check(request, path)) 1526 return 1; 1527 1528 /* now we have the link pointer, redirect to the real place */ 1529 if (!absolute && redirpath[0] != '/') { 1530 if ((size_t)snprintf(finalredir = redir, sizeof(redir), "%s%s/%s", 1531 (strlen(dir) > 0 ? "/" : ""), dir, redirpath) >= sizeof(redir)) { 1532 bozo_http_error(httpd, 404, request, 1533 "redirect path too long"); 1534 return -1; 1535 } 1536 } else 1537 finalredir = redirpath; 1538 1539 debug((httpd, DEBUG_FAT, "check_bzredirect: new redir %s", finalredir)); 1540 handle_redirect(request, finalredir, absolute); 1541 return 1; 1542 } 1543 1544 /* this fixes the %HH hack that RFC2396 requires. */ 1545 int 1546 bozo_decode_url_percent(bozo_httpreq_t *request, char *str) 1547 { 1548 bozohttpd_t *httpd = request->hr_httpd; 1549 char *s, *t, buf[3]; 1550 char *end; /* if end is not-zero, we don't translate beyond that */ 1551 1552 end = str + strlen(str); 1553 1554 /* fast forward to the first % */ 1555 if ((s = strchr(str, '%')) == NULL) 1556 return 0; 1557 1558 t = s; 1559 do { 1560 if (end && s >= end) { 1561 debug((httpd, DEBUG_EXPLODING, 1562 "fu_%%: past end, filling out..")); 1563 while (*s) 1564 *t++ = *s++; 1565 break; 1566 } 1567 debug((httpd, DEBUG_EXPLODING, 1568 "fu_%%: got s == %%, s[1]s[2] == %c%c", 1569 s[1], s[2])); 1570 if (s[1] == '\0' || s[2] == '\0') 1571 return bozo_http_error(httpd, 400, request, 1572 "percent hack missing two chars afterwards"); 1573 if (s[1] == '0' && s[2] == '0') 1574 return bozo_http_error(httpd, 404, request, 1575 "percent hack was %00"); 1576 if (s[1] == '2' && (s[2] == 'f' || s[2] == 'F')) 1577 return bozo_http_error(httpd, 404, request, 1578 "percent hack was %2f (/)"); 1579 1580 buf[0] = *++s; 1581 buf[1] = *++s; 1582 buf[2] = '\0'; 1583 s++; 1584 *t = (char)strtol(buf, NULL, 16); 1585 debug((httpd, DEBUG_EXPLODING, 1586 "fu_%%: strtol put '%02x' into *t", *t)); 1587 if (*t++ == '\0') 1588 return bozo_http_error(httpd, 400, request, 1589 "percent hack got a 0 back"); 1590 1591 while (*s && *s != '%') { 1592 if (end && s >= end) 1593 break; 1594 *t++ = *s++; 1595 } 1596 } while (*s); 1597 *t = '\0'; 1598 1599 debug((httpd, DEBUG_FAT, "bozo_decode_url_percent returns `%s'", 1600 request->hr_file)); 1601 1602 return 0; 1603 } 1604 1605 /* 1606 * transform_request does this: 1607 * - ``expand'' %20 crapola 1608 * - punt if it doesn't start with / 1609 * - look for "http://myname/" and deal with it. 1610 * - maybe call bozo_process_cgi() 1611 * - check for ~user and call bozo_user_transform() if so 1612 * - if the length > 1, check for trailing slash. if so, 1613 * add the index.html file 1614 * - if the length is 1, return the index.html file 1615 * - disallow anything ending up with a file starting 1616 * at "/" or having ".." in it. 1617 * - anything else is a really weird internal error 1618 * - returns malloced file to serve, if unhandled 1619 */ 1620 static int 1621 transform_request(bozo_httpreq_t *request, int *isindex) 1622 { 1623 bozohttpd_t *httpd = request->hr_httpd; 1624 char *file, *newfile = NULL; 1625 size_t len; 1626 1627 file = NULL; 1628 *isindex = 0; 1629 debug((httpd, DEBUG_FAT, "tf_req: file %s", request->hr_file)); 1630 1631 if (bozo_decode_url_percent(request, request->hr_file) || 1632 check_virtual(request)) 1633 goto bad_done; 1634 1635 file = request->hr_file; 1636 1637 if (file[0] != '/') { 1638 bozo_http_error(httpd, 404, request, "unknown URL"); 1639 goto bad_done; 1640 } 1641 1642 /* omit additional slashes at the beginning */ 1643 while (file[1] == '/') 1644 file++; 1645 1646 /* fix file provided by user as it's used in other handlers */ 1647 request->hr_file = file; 1648 1649 len = strlen(file); 1650 1651 #ifndef NO_USER_SUPPORT 1652 /* first of all expand user path */ 1653 if (len > 1 && httpd->enable_users && file[1] == '~') { 1654 if (file[2] == '\0') { 1655 bozo_http_error(httpd, 404, request, 1656 "missing username"); 1657 goto bad_done; 1658 } 1659 if (strchr(file + 2, '/') == NULL) { 1660 char *userredirecturl; 1661 1662 bozoasprintf(httpd, &userredirecturl, "%s/", file); 1663 handle_redirect(request, userredirecturl, 0); 1664 free(userredirecturl); 1665 return 0; 1666 } 1667 debug((httpd, DEBUG_FAT, "calling bozo_user_transform")); 1668 1669 if (!bozo_user_transform(request)) 1670 return 0; 1671 1672 file = request->hr_file; 1673 len = strlen(file); 1674 } 1675 #endif /* NO_USER_SUPPORT */ 1676 1677 1678 switch (check_bzredirect(request)) { 1679 case -1: 1680 goto bad_done; 1681 case 0: 1682 break; 1683 default: 1684 return 0; 1685 } 1686 1687 if (len > 1) { 1688 debug((httpd, DEBUG_FAT, "file[len-1] == %c", file[len-1])); 1689 if (file[len-1] == '/') { /* append index.html */ 1690 *isindex = 1; 1691 debug((httpd, DEBUG_FAT, "appending index.html")); 1692 newfile = bozomalloc(httpd, 1693 len + strlen(httpd->index_html) + 1); 1694 strcpy(newfile, file + 1); 1695 strcat(newfile, httpd->index_html); 1696 } else 1697 newfile = bozostrdup(httpd, request, file + 1); 1698 } else if (len == 1) { 1699 debug((httpd, DEBUG_EXPLODING, "tf_req: len == 1")); 1700 newfile = bozostrdup(httpd, request, httpd->index_html); 1701 *isindex = 1; 1702 } else { /* len == 0 ? */ 1703 bozo_http_error(httpd, 500, request, "request->hr_file is nul"); 1704 goto bad_done; 1705 } 1706 1707 if (newfile == NULL) { 1708 bozo_http_error(httpd, 500, request, "internal failure"); 1709 goto bad_done; 1710 } 1711 1712 /* 1713 * stop traversing outside our domain 1714 * 1715 * XXX true security only comes from our parent using chroot(2) 1716 * before execve(2)'ing us. or our own built in chroot(2) support. 1717 */ 1718 1719 debug((httpd, DEBUG_FAT, "newfile: %s", newfile)); 1720 1721 if (*newfile == '/' || strcmp(newfile, "..") == 0 || 1722 strstr(newfile, "/..") || strstr(newfile, "../")) { 1723 bozo_http_error(httpd, 403, request, "illegal request"); 1724 goto bad_done; 1725 } 1726 1727 if (bozo_auth_check(request, newfile)) 1728 goto bad_done; 1729 1730 if (strlen(newfile)) { 1731 request->hr_oldfile = request->hr_file_free; 1732 request->hr_file = newfile; 1733 } 1734 1735 if (bozo_process_cgi(request) || 1736 bozo_process_lua(request)) 1737 return 0; 1738 1739 debug((httpd, DEBUG_FAT, "transform_request set: %s", newfile)); 1740 return 1; 1741 1742 bad_done: 1743 debug((httpd, DEBUG_FAT, "transform_request returning: 0")); 1744 free(newfile); 1745 return 0; 1746 } 1747 1748 /* 1749 * can_gzip checks if the request supports and prefers gzip encoding. 1750 * 1751 * XXX: we do not consider the associated q with gzip in making our 1752 * decision which is broken. 1753 */ 1754 1755 static int 1756 can_gzip(bozo_httpreq_t *request) 1757 { 1758 const char *pos; 1759 const char *tmp; 1760 size_t len; 1761 1762 /* First we decide if the request can be gzipped at all. */ 1763 1764 /* not if we already are encoded... */ 1765 tmp = bozo_content_encoding(request, request->hr_file); 1766 if (tmp && *tmp) 1767 return 0; 1768 1769 /* not if we are not asking for the whole file... */ 1770 if (request->hr_last_byte_pos != -1 || request->hr_have_range) 1771 return 0; 1772 1773 /* Then we determine if gzip is on the cards. */ 1774 1775 for (pos = request->hr_accept_encoding; pos && *pos; pos += len) { 1776 while (*pos == ' ') 1777 pos++; 1778 1779 len = strcspn(pos, ";,"); 1780 1781 if ((len == 4 && strncasecmp("gzip", pos, 4) == 0) || 1782 (len == 6 && strncasecmp("x-gzip", pos, 6) == 0)) 1783 return 1; 1784 1785 if (pos[len] == ';') 1786 len += strcspn(&pos[len], ","); 1787 1788 if (pos[len]) 1789 len++; 1790 } 1791 1792 return 0; 1793 } 1794 1795 /* 1796 * bozo_process_request does the following: 1797 * - check the request is valid 1798 * - process cgi-bin if necessary 1799 * - transform a filename if necesarry 1800 * - return the HTTP request 1801 */ 1802 void 1803 bozo_process_request(bozo_httpreq_t *request) 1804 { 1805 bozohttpd_t *httpd = request->hr_httpd; 1806 struct stat sb; 1807 time_t timestamp; 1808 char *file; 1809 const char *type, *encoding; 1810 int fd, isindex; 1811 1812 /* 1813 * note that transform_request chdir()'s if required. also note 1814 * that cgi is handed here. if transform_request() returns 0 1815 * then the request has been handled already. 1816 */ 1817 if (transform_request(request, &isindex) == 0) 1818 return; 1819 1820 fd = -1; 1821 encoding = NULL; 1822 if (can_gzip(request)) { 1823 bozoasprintf(httpd, &file, "%s.gz", request->hr_file); 1824 fd = open(file, O_RDONLY); 1825 if (fd >= 0) 1826 encoding = "gzip"; 1827 free(file); 1828 } 1829 1830 file = request->hr_file; 1831 1832 if (fd < 0) 1833 fd = open(file, O_RDONLY); 1834 1835 if (fd < 0) { 1836 debug((httpd, DEBUG_FAT, "open failed: %s", strerror(errno))); 1837 switch (errno) { 1838 case EPERM: 1839 case EACCES: 1840 bozo_http_error(httpd, 403, request, 1841 "no permission to open file"); 1842 break; 1843 case ENAMETOOLONG: 1844 /*FALLTHROUGH*/ 1845 case ENOENT: 1846 if (!bozo_dir_index(request, file, isindex)) 1847 bozo_http_error(httpd, 404, request, "no file"); 1848 break; 1849 default: 1850 bozo_http_error(httpd, 500, request, "open file"); 1851 } 1852 goto cleanup_nofd; 1853 } 1854 if (fstat(fd, &sb) < 0) { 1855 bozo_http_error(httpd, 500, request, "can't fstat"); 1856 goto cleanup; 1857 } 1858 if (S_ISDIR(sb.st_mode)) { 1859 handle_redirect(request, NULL, 0); 1860 goto cleanup; 1861 } 1862 1863 if (request->hr_if_modified_since && 1864 parse_http_date(request->hr_if_modified_since, ×tamp) && 1865 timestamp >= sb.st_mtime) { 1866 /* XXX ignore subsecond of timestamp */ 1867 bozo_printf(httpd, "%s 304 Not Modified\r\n", 1868 request->hr_proto); 1869 bozo_printf(httpd, "\r\n"); 1870 bozo_flush(httpd, stdout); 1871 goto cleanup; 1872 } 1873 1874 /* validate requested range */ 1875 if (request->hr_last_byte_pos == -1 || 1876 request->hr_last_byte_pos >= sb.st_size) 1877 request->hr_last_byte_pos = sb.st_size - 1; 1878 if (request->hr_have_range && 1879 request->hr_first_byte_pos > request->hr_last_byte_pos) { 1880 request->hr_have_range = 0; /* punt */ 1881 request->hr_first_byte_pos = 0; 1882 request->hr_last_byte_pos = sb.st_size - 1; 1883 } 1884 debug((httpd, DEBUG_FAT, "have_range %d first_pos %lld last_pos %lld", 1885 request->hr_have_range, 1886 (long long)request->hr_first_byte_pos, 1887 (long long)request->hr_last_byte_pos)); 1888 if (request->hr_have_range) 1889 bozo_printf(httpd, "%s 206 Partial Content\r\n", 1890 request->hr_proto); 1891 else 1892 bozo_printf(httpd, "%s 200 OK\r\n", request->hr_proto); 1893 1894 if (request->hr_proto != httpd->consts.http_09) { 1895 type = bozo_content_type(request, file); 1896 if (!encoding) 1897 encoding = bozo_content_encoding(request, file); 1898 1899 bozo_print_header(request, &sb, type, encoding); 1900 bozo_printf(httpd, "\r\n"); 1901 } 1902 bozo_flush(httpd, stdout); 1903 1904 if (request->hr_method != HTTP_HEAD) { 1905 off_t szleft, cur_byte_pos; 1906 1907 szleft = 1908 request->hr_last_byte_pos - request->hr_first_byte_pos + 1; 1909 cur_byte_pos = request->hr_first_byte_pos; 1910 1911 retry: 1912 while (szleft) { 1913 size_t sz; 1914 1915 if ((off_t)httpd->mmapsz < szleft) 1916 sz = httpd->mmapsz; 1917 else 1918 sz = (size_t)szleft; 1919 if (mmap_and_write_part(httpd, fd, cur_byte_pos, sz)) { 1920 if (errno == ENOMEM) { 1921 httpd->mmapsz /= 2; 1922 if (httpd->mmapsz >= httpd->page_size) 1923 goto retry; 1924 } 1925 goto cleanup; 1926 } 1927 cur_byte_pos += sz; 1928 szleft -= sz; 1929 } 1930 } 1931 cleanup: 1932 close(fd); 1933 cleanup_nofd: 1934 /* If SSL enabled send close_notify. */ 1935 bozo_ssl_shutdown(request->hr_httpd); 1936 close(STDIN_FILENO); 1937 close(STDOUT_FILENO); 1938 /*close(STDERR_FILENO);*/ 1939 } 1940 1941 /* make sure we're not trying to access special files */ 1942 int 1943 bozo_check_special_files(bozo_httpreq_t *request, const char *name, bool doerror) 1944 { 1945 bozohttpd_t *httpd = request->hr_httpd; 1946 size_t i; 1947 int error = 0; 1948 1949 for (i = 0; specials[i].file; i++) { 1950 if (strcmp(name, specials[i].file) == 0) { 1951 if (doerror) { 1952 error = bozo_http_error(httpd, 403, request, 1953 specials[i].name); 1954 } else { 1955 error = -1; 1956 } 1957 } 1958 } 1959 1960 return error; 1961 } 1962 1963 /* generic header printing routine */ 1964 void 1965 bozo_print_header(bozo_httpreq_t *request, 1966 struct stat *sbp, const char *type, const char *encoding) 1967 { 1968 bozohttpd_t *httpd = request->hr_httpd; 1969 off_t len; 1970 char date[40]; 1971 bozoheaders_t *hdr; 1972 1973 SIMPLEQ_FOREACH(hdr, &request->hr_replheaders, h_next) { 1974 bozo_printf(httpd, "%s: %s\r\n", hdr->h_header, 1975 hdr->h_value); 1976 } 1977 1978 bozo_printf(httpd, "Date: %s\r\n", bozo_http_date(date, sizeof(date))); 1979 bozo_printf(httpd, "Server: %s\r\n", httpd->server_software); 1980 bozo_printf(httpd, "Accept-Ranges: bytes\r\n"); 1981 if (sbp) { 1982 char filedate[40]; 1983 struct tm *tm; 1984 1985 tm = gmtime(&sbp->st_mtime); 1986 strftime(filedate, sizeof filedate, 1987 "%a, %d %b %Y %H:%M:%S GMT", tm); 1988 bozo_printf(httpd, "Last-Modified: %s\r\n", filedate); 1989 } 1990 if (type && *type) 1991 bozo_printf(httpd, "Content-Type: %s\r\n", type); 1992 if (encoding && *encoding) 1993 bozo_printf(httpd, "Content-Encoding: %s\r\n", encoding); 1994 if (sbp) { 1995 if (request->hr_have_range) { 1996 len = request->hr_last_byte_pos - 1997 request->hr_first_byte_pos +1; 1998 bozo_printf(httpd, 1999 "Content-Range: bytes %qd-%qd/%qd\r\n", 2000 (long long) request->hr_first_byte_pos, 2001 (long long) request->hr_last_byte_pos, 2002 (long long) sbp->st_size); 2003 } else 2004 len = sbp->st_size; 2005 bozo_printf(httpd, "Content-Length: %qd\r\n", (long long)len); 2006 } 2007 if (request->hr_proto == httpd->consts.http_11) 2008 bozo_printf(httpd, "Connection: close\r\n"); 2009 bozo_flush(httpd, stdout); 2010 } 2011 2012 #ifndef NO_DEBUG 2013 void 2014 debug__(bozohttpd_t *httpd, int level, const char *fmt, ...) 2015 { 2016 va_list ap; 2017 int savederrno; 2018 2019 /* only log if the level is low enough */ 2020 if (httpd->debug < level) 2021 return; 2022 2023 savederrno = errno; 2024 va_start(ap, fmt); 2025 if (!httpd->nolog) { 2026 if (httpd->logstderr) { 2027 vfprintf(stderr, fmt, ap); 2028 fputs("\n", stderr); 2029 } else 2030 vsyslog(LOG_DEBUG, fmt, ap); 2031 } 2032 va_end(ap); 2033 errno = savederrno; 2034 } 2035 #endif /* NO_DEBUG */ 2036 2037 /* these are like warn() and err(), except for syslog not stderr */ 2038 void 2039 bozowarn(bozohttpd_t *httpd, const char *fmt, ...) 2040 { 2041 va_list ap; 2042 2043 va_start(ap, fmt); 2044 if (!httpd->nolog) { 2045 if (httpd->logstderr || isatty(STDERR_FILENO)) { 2046 //fputs("warning: ", stderr); 2047 vfprintf(stderr, fmt, ap); 2048 fputs("\n", stderr); 2049 } else 2050 vsyslog(LOG_INFO, fmt, ap); 2051 } 2052 va_end(ap); 2053 } 2054 2055 void 2056 bozoerr(bozohttpd_t *httpd, int code, const char *fmt, ...) 2057 { 2058 va_list ap; 2059 2060 va_start(ap, fmt); 2061 if (!httpd->nolog) { 2062 if (httpd->logstderr || isatty(STDERR_FILENO)) { 2063 //fputs("error: ", stderr); 2064 vfprintf(stderr, fmt, ap); 2065 fputs("\n", stderr); 2066 } else 2067 vsyslog(LOG_ERR, fmt, ap); 2068 } 2069 va_end(ap); 2070 exit(code); 2071 } 2072 2073 void 2074 bozoasprintf(bozohttpd_t *httpd, char **str, const char *fmt, ...) 2075 { 2076 va_list ap; 2077 int e; 2078 2079 va_start(ap, fmt); 2080 e = vasprintf(str, fmt, ap); 2081 va_end(ap); 2082 2083 if (e < 0) 2084 bozoerr(httpd, EXIT_FAILURE, "asprintf"); 2085 } 2086 2087 /* 2088 * this escapes HTML tags. returns allocated escaped 2089 * string if needed, or NULL on allocation failure or 2090 * lack of escape need. 2091 * call with NULL httpd in error paths, to avoid recursive 2092 * malloc failure. call with valid httpd in normal paths 2093 * to get automatic allocation failure handling. 2094 */ 2095 char * 2096 bozo_escape_html(bozohttpd_t *httpd, const char *url) 2097 { 2098 int i, j; 2099 char *tmp; 2100 size_t len; 2101 2102 for (i = 0, j = 0; url[i]; i++) { 2103 switch (url[i]) { 2104 case '<': 2105 case '>': 2106 j += 4; 2107 break; 2108 case '&': 2109 j += 5; 2110 break; 2111 case '"': 2112 j += 6; 2113 break; 2114 } 2115 } 2116 2117 if (j == 0) 2118 return NULL; 2119 2120 /* 2121 * we need to handle being called from different 2122 * pathnames. 2123 */ 2124 len = strlen(url) + j; 2125 if (httpd) 2126 tmp = bozomalloc(httpd, len); 2127 else if ((tmp = malloc(len)) == 0) 2128 return NULL; 2129 2130 for (i = 0, j = 0; url[i]; i++) { 2131 switch (url[i]) { 2132 case '<': 2133 memcpy(tmp + j, "<", 4); 2134 j += 4; 2135 break; 2136 case '>': 2137 memcpy(tmp + j, ">", 4); 2138 j += 4; 2139 break; 2140 case '&': 2141 memcpy(tmp + j, "&", 5); 2142 j += 5; 2143 break; 2144 case '"': 2145 memcpy(tmp + j, """, 6); 2146 j += 6; 2147 break; 2148 default: 2149 tmp[j++] = url[i]; 2150 } 2151 } 2152 tmp[j] = 0; 2153 2154 return tmp; 2155 } 2156 2157 /* short map between error code, and short/long messages */ 2158 static struct errors_map { 2159 int code; /* HTTP return code */ 2160 const char *shortmsg; /* short version of message */ 2161 const char *longmsg; /* long version of message */ 2162 } errors_map[] = { 2163 { 200, "200 OK", "The request was valid", }, 2164 { 400, "400 Bad Request", "The request was not valid", }, 2165 { 401, "401 Unauthorized", "No authorization", }, 2166 { 403, "403 Forbidden", "Access to this item has been denied",}, 2167 { 404, "404 Not Found", "This item has not been found", }, 2168 { 408, "408 Request Timeout", "This request took too long", }, 2169 { 413, "413 Payload Too Large", "Use smaller requests", }, 2170 { 417, "417 Expectation Failed","Expectations not available", }, 2171 { 420, "420 Enhance Your Calm","Chill, Winston", }, 2172 { 500, "500 Internal Error", "An error occurred on the server", }, 2173 { 501, "501 Not Implemented", "This request is not available", }, 2174 { 0, NULL, NULL, }, 2175 }; 2176 2177 static const char *help = "DANGER! WILL ROBINSON! DANGER!"; 2178 2179 static const char * 2180 http_errors_short(int code) 2181 { 2182 struct errors_map *ep; 2183 2184 for (ep = errors_map; ep->code; ep++) 2185 if (ep->code == code) 2186 return (ep->shortmsg); 2187 return (help); 2188 } 2189 2190 static const char * 2191 http_errors_long(int code) 2192 { 2193 struct errors_map *ep; 2194 2195 for (ep = errors_map; ep->code; ep++) 2196 if (ep->code == code) 2197 return (ep->longmsg); 2198 return (help); 2199 } 2200 2201 #ifndef NO_BLOCKLIST_SUPPORT 2202 static struct blocklist *blstate; 2203 2204 void 2205 pfilter_notify(const int what, const int code) 2206 { 2207 2208 if (blstate == NULL) 2209 blstate = blocklist_open(); 2210 2211 if (blstate == NULL) 2212 return; 2213 2214 (void)blocklist_r(blstate, what, 0, http_errors_short(code)); 2215 } 2216 #endif /* !NO_BLOCKLIST_SUPPORT */ 2217 2218 /* the follow functions and variables are used in handling HTTP errors */ 2219 int 2220 bozo_http_error(bozohttpd_t *httpd, int code, bozo_httpreq_t *request, 2221 const char *msg) 2222 { 2223 char portbuf[20]; 2224 const char *header = http_errors_short(code); 2225 const char *reason = http_errors_long(code); 2226 const char *proto = (request && request->hr_proto) ? 2227 request->hr_proto : httpd->consts.http_11; 2228 int size; 2229 bozoheaders_t *hdr; 2230 2231 USE_ARG(msg); 2232 2233 debug((httpd, DEBUG_FAT, "bozo_http_error %d: %s", code, msg)); 2234 if (header == NULL || reason == NULL) { 2235 bozoerr(httpd, 1, 2236 "bozo_http_error() failed (short = %p, long = %p)", 2237 header, reason); 2238 return code; 2239 } 2240 2241 if (request && request->hr_serverport && 2242 strcmp(request->hr_serverport, BOZO_HTTP_PORT) != 0) 2243 snprintf(portbuf, sizeof(portbuf), ":%s", 2244 request->hr_serverport); 2245 else 2246 portbuf[0] = '\0'; 2247 2248 if (request && request->hr_file) { 2249 char *file = NULL, *user = NULL; 2250 int file_alloc = 0; 2251 const char *hostname = BOZOHOST(httpd, request); 2252 2253 /* bozo_escape_html() failure here is just too bad. */ 2254 file = bozo_escape_html(NULL, request->hr_file); 2255 if (file == NULL) 2256 file = request->hr_file; 2257 else 2258 file_alloc = 1; 2259 2260 #ifndef NO_USER_SUPPORT 2261 if (request->hr_user != NULL) { 2262 char *user_escaped; 2263 2264 user_escaped = bozo_escape_html(NULL, request->hr_user); 2265 if (user_escaped == NULL) 2266 user_escaped = request->hr_user; 2267 /* expand username to ~user/ */ 2268 bozoasprintf(httpd, &user, "~%s/", user_escaped); 2269 if (user_escaped != request->hr_user) 2270 free(user_escaped); 2271 } 2272 #endif /* !NO_USER_SUPPORT */ 2273 2274 size = snprintf(httpd->errorbuf, BOZO_MINBUFSIZE, 2275 "<html><head><title>%s</title></head>\n" 2276 "<body><h1>%s</h1>\n" 2277 "%s%s: <pre>%s</pre>\n" 2278 "<hr><address><a href=\"//%s%s/\">%s%s</a></address>\n" 2279 "</body></html>\n", 2280 header, header, 2281 user ? user : "", file, 2282 reason, hostname, portbuf, hostname, portbuf); 2283 free(user); 2284 if (size >= (int)BOZO_MINBUFSIZE) { 2285 bozowarn(httpd, 2286 "bozo_http_error buffer too small, truncated"); 2287 size = (int)BOZO_MINBUFSIZE; 2288 } 2289 2290 if (file_alloc) 2291 free(file); 2292 } else 2293 size = 0; 2294 2295 bozo_printf(httpd, "%s %s\r\n", proto, header); 2296 2297 if (request) { 2298 bozo_auth_check_401(request, code); 2299 SIMPLEQ_FOREACH(hdr, &request->hr_replheaders, h_next) { 2300 bozo_printf(httpd, "%s: %s\r\n", hdr->h_header, 2301 hdr->h_value); 2302 } 2303 } 2304 2305 bozo_printf(httpd, "Content-Type: text/html\r\n"); 2306 bozo_printf(httpd, "Content-Length: %d\r\n", size); 2307 bozo_printf(httpd, "Server: %s\r\n", httpd->server_software); 2308 if (request && request->hr_allow) 2309 bozo_printf(httpd, "Allow: %s\r\n", request->hr_allow); 2310 /* RFC 7231 (HTTP/1.1) 6.5.7 */ 2311 if (code == 408 && request && 2312 request->hr_proto == httpd->consts.http_11) 2313 bozo_printf(httpd, "Connection: close\r\n"); 2314 bozo_printf(httpd, "\r\n"); 2315 /* According to the RFC 2616 sec. 9.4 HEAD method MUST NOT return a 2316 * message-body in the response */ 2317 if (size && request && request->hr_method != HTTP_HEAD) 2318 bozo_printf(httpd, "%s", httpd->errorbuf); 2319 bozo_flush(httpd, stdout); 2320 2321 #ifndef NO_BLOCKLIST_SUPPORT 2322 switch(code) { 2323 2324 case 401: 2325 pfilter_notify(BLOCKLIST_AUTH_FAIL, code); 2326 break; 2327 2328 case 403: 2329 pfilter_notify(BLOCKLIST_ABUSIVE_BEHAVIOR, code); 2330 break; 2331 } 2332 #endif /* !NO_BLOCKLIST_SUPPORT */ 2333 2334 return code; 2335 } 2336 2337 /* Below are various modified libc functions */ 2338 2339 /* 2340 * returns -1 in lenp if the string ran out before finding a delimiter, 2341 * but is otherwise the same as strsep. Note that the length must be 2342 * correctly passed in. 2343 */ 2344 char * 2345 bozostrnsep(char **strp, const char *delim, ssize_t *lenp) 2346 { 2347 char *s; 2348 const char *spanp; 2349 int c, sc; 2350 char *tok; 2351 2352 if ((s = *strp) == NULL) 2353 return (NULL); 2354 for (tok = s;;) { 2355 if (lenp && --(*lenp) == -1) 2356 return (NULL); 2357 c = *s++; 2358 spanp = delim; 2359 do { 2360 if ((sc = *spanp++) == c) { 2361 if (c == 0) 2362 s = NULL; 2363 else 2364 s[-1] = '\0'; 2365 *strp = s; 2366 return (tok); 2367 } 2368 } while (sc != 0); 2369 } 2370 /* NOTREACHED */ 2371 } 2372 2373 /* 2374 * inspired by fgetln(3), but works for fd's. should work identically 2375 * except it, however, does *not* return the newline, and it does nul 2376 * terminate the string. 2377 * 2378 * returns NULL if the line grows too large. empty lines will be 2379 * returned with *lenp set to 0. 2380 */ 2381 char * 2382 bozodgetln(bozohttpd_t *httpd, int fd, ssize_t *lenp, 2383 ssize_t (*readfn)(bozohttpd_t *, int, void *, size_t)) 2384 { 2385 ssize_t len; 2386 int got_cr = 0; 2387 char c, *nbuffer; 2388 2389 /* initialise */ 2390 if (httpd->getln_buflen == 0) { 2391 /* should be plenty for most requests */ 2392 httpd->getln_buflen = 128; 2393 httpd->getln_buffer = 2394 bozomalloc(httpd, (size_t)httpd->getln_buflen); 2395 } 2396 len = 0; 2397 2398 /* 2399 * we *have* to read one byte at a time, to not break cgi 2400 * programs (for we pass stdin off to them). could fix this 2401 * by becoming a fd-passing program instead of just exec'ing 2402 * the program 2403 * 2404 * the above is no longer true, we are the fd-passing 2405 * program already. 2406 */ 2407 for (; readfn(httpd, fd, &c, 1) == 1; ) { 2408 debug((httpd, DEBUG_EXPLODING, "bozodgetln read %c", c)); 2409 2410 if (httpd->getln_buflen > BOZO_HEADERS_MAX_SIZE) 2411 return NULL; 2412 2413 if (len >= httpd->getln_buflen - 1) { 2414 httpd->getln_buflen *= 2; 2415 debug((httpd, DEBUG_EXPLODING, "bozodgetln: " 2416 "reallocating buffer to buflen %zu", 2417 httpd->getln_buflen)); 2418 nbuffer = bozorealloc(httpd, httpd->getln_buffer, 2419 (size_t)httpd->getln_buflen); 2420 httpd->getln_buffer = nbuffer; 2421 } 2422 2423 httpd->getln_buffer[len++] = c; 2424 if (c == '\r') { 2425 got_cr = 1; 2426 continue; 2427 } else if (c == '\n') { 2428 /* 2429 * HTTP/1.1 spec says to ignore CR and treat 2430 * LF as the real line terminator. even though 2431 * the same spec defines CRLF as the line 2432 * terminator, it is recommended in section 19.3 2433 * to do the LF trick for tolerance. 2434 */ 2435 if (got_cr) 2436 len -= 2; 2437 else 2438 len -= 1; 2439 break; 2440 } 2441 2442 } 2443 httpd->getln_buffer[len] = '\0'; 2444 debug((httpd, DEBUG_OBESE, "bozodgetln returns: '%s' with len %zd", 2445 httpd->getln_buffer, len)); 2446 *lenp = len; 2447 return httpd->getln_buffer; 2448 } 2449 2450 /* 2451 * allocation frontends with error handling. 2452 * 2453 * note that these may access members of the httpd and/or request. 2454 */ 2455 void * 2456 bozorealloc(bozohttpd_t *httpd, void *ptr, size_t size) 2457 { 2458 void *p; 2459 2460 p = realloc(ptr, size); 2461 if (p) 2462 return p; 2463 2464 bozo_http_error(httpd, 500, NULL, "memory allocation failure"); 2465 exit(EXIT_FAILURE); 2466 } 2467 2468 void * 2469 bozomalloc(bozohttpd_t *httpd, size_t size) 2470 { 2471 void *p; 2472 2473 p = malloc(size); 2474 if (p) 2475 return p; 2476 2477 bozo_http_error(httpd, 500, NULL, "memory allocation failure"); 2478 exit(EXIT_FAILURE); 2479 } 2480 2481 char * 2482 bozostrdup(bozohttpd_t *httpd, bozo_httpreq_t *request, const char *str) 2483 { 2484 char *p; 2485 2486 p = strdup(str); 2487 if (p) 2488 return p; 2489 2490 if (!request) 2491 bozoerr(httpd, EXIT_FAILURE, "strdup"); 2492 2493 bozo_http_error(httpd, 500, request, "memory allocation failure"); 2494 exit(EXIT_FAILURE); 2495 } 2496 2497 /* set default values in bozohttpd_t struct */ 2498 int 2499 bozo_init_httpd(bozohttpd_t *httpd) 2500 { 2501 /* make sure everything is clean */ 2502 (void) memset(httpd, 0x0, sizeof(*httpd)); 2503 2504 /* constants */ 2505 httpd->consts.http_09 = "HTTP/0.9"; 2506 httpd->consts.http_10 = "HTTP/1.0"; 2507 httpd->consts.http_11 = "HTTP/1.1"; 2508 httpd->consts.text_plain = "text/plain"; 2509 2510 /* mmap region size */ 2511 httpd->mmapsz = BOZO_MMAPSZ; 2512 2513 /* error buffer for bozo_http_error() */ 2514 if ((httpd->errorbuf = malloc(BOZO_MINBUFSIZE)) == NULL) { 2515 fprintf(stderr, 2516 "bozohttpd: memory_allocation failure\n"); 2517 return 0; 2518 } 2519 #ifndef NO_LUA_SUPPORT 2520 SIMPLEQ_INIT(&httpd->lua_states); 2521 #endif 2522 return 1; 2523 } 2524 2525 /* set default values in bozoprefs_t struct */ 2526 int 2527 bozo_init_prefs(bozohttpd_t *httpd, bozoprefs_t *prefs) 2528 { 2529 int rv = 1; 2530 2531 /* make sure everything is clean */ 2532 (void) memset(prefs, 0x0, sizeof(*prefs)); 2533 2534 /* set up default values */ 2535 if (!bozo_set_pref(httpd, prefs, "server software", SERVER_SOFTWARE)) 2536 rv = 0; 2537 if (!bozo_set_pref(httpd, prefs, "index.html", INDEX_HTML)) 2538 rv = 0; 2539 if (!bozo_set_pref(httpd, prefs, "public_html", PUBLIC_HTML)) 2540 rv = 0; 2541 if (!bozo_set_pref(httpd, prefs, "ssl timeout", SSL_TIMEOUT)) 2542 rv = 0; 2543 if (!bozo_set_pref(httpd, prefs, "initial timeout", INITIAL_TIMEOUT)) 2544 rv = 0; 2545 if (!bozo_set_pref(httpd, prefs, "header timeout", HEADER_WAIT_TIME)) 2546 rv = 0; 2547 if (!bozo_set_pref(httpd, prefs, "request timeout", TOTAL_MAX_REQ_TIME)) 2548 rv = 0; 2549 2550 return rv; 2551 } 2552 2553 /* set default values */ 2554 int 2555 bozo_set_defaults(bozohttpd_t *httpd, bozoprefs_t *prefs) 2556 { 2557 return bozo_init_httpd(httpd) && bozo_init_prefs(httpd, prefs); 2558 } 2559 2560 /* set the virtual host name, port and root */ 2561 int 2562 bozo_setup(bozohttpd_t *httpd, bozoprefs_t *prefs, const char *vhost, 2563 const char *root) 2564 { 2565 struct passwd *pw; 2566 extern char **environ; 2567 static char *cleanenv[1] = { NULL }; 2568 uid_t uid; 2569 int uidset = 0; 2570 char *chrootdir; 2571 char *username; 2572 char *portnum; 2573 char *cp; 2574 int dirtyenv; 2575 2576 dirtyenv = 0; 2577 2578 if (vhost == NULL) { 2579 httpd->virthostname = bozomalloc(httpd, MAXHOSTNAMELEN+1); 2580 if (gethostname(httpd->virthostname, MAXHOSTNAMELEN+1) < 0) 2581 bozoerr(httpd, 1, "gethostname"); 2582 httpd->virthostname[MAXHOSTNAMELEN] = '\0'; 2583 } else { 2584 httpd->virthostname = bozostrdup(httpd, NULL, vhost); 2585 } 2586 httpd->slashdir = bozostrdup(httpd, NULL, root); 2587 if ((portnum = bozo_get_pref(prefs, "port number")) != NULL) { 2588 httpd->bindport = bozostrdup(httpd, NULL, portnum); 2589 } 2590 2591 /* go over preferences now */ 2592 if ((cp = bozo_get_pref(prefs, "numeric")) != NULL && 2593 strcmp(cp, "true") == 0) { 2594 httpd->numeric = 1; 2595 } 2596 if ((cp = bozo_get_pref(prefs, "log to stderr")) != NULL && 2597 strcmp(cp, "true") == 0) { 2598 httpd->logstderr = 1; 2599 } 2600 if ((cp = bozo_get_pref(prefs, "no log")) != NULL && 2601 strcmp(cp, "true") == 0) { 2602 httpd->nolog = 1; 2603 } 2604 if ((cp = bozo_get_pref(prefs, "bind address")) != NULL) { 2605 httpd->bindaddress = bozostrdup(httpd, NULL, cp); 2606 } 2607 if ((cp = bozo_get_pref(prefs, "background")) != NULL) { 2608 httpd->background = atoi(cp); 2609 } 2610 if ((cp = bozo_get_pref(prefs, "foreground")) != NULL && 2611 strcmp(cp, "true") == 0) { 2612 httpd->foreground = 1; 2613 } 2614 if ((cp = bozo_get_pref(prefs, "pid file")) != NULL) { 2615 httpd->pidfile = bozostrdup(httpd, NULL, cp); 2616 } 2617 if ((cp = bozo_get_pref(prefs, "unknown slash")) != NULL && 2618 strcmp(cp, "true") == 0) { 2619 httpd->unknown_slash = 1; 2620 } 2621 if ((cp = bozo_get_pref(prefs, "virtual base")) != NULL) { 2622 httpd->virtbase = bozostrdup(httpd, NULL, cp); 2623 } 2624 if ((cp = bozo_get_pref(prefs, "enable users")) != NULL && 2625 strcmp(cp, "true") == 0) { 2626 httpd->enable_users = 1; 2627 } 2628 if ((cp = bozo_get_pref(prefs, "enable user cgibin")) != NULL && 2629 strcmp(cp, "true") == 0) { 2630 httpd->enable_cgi_users = 1; 2631 } 2632 if ((cp = bozo_get_pref(prefs, "dirty environment")) != NULL && 2633 strcmp(cp, "true") == 0) { 2634 dirtyenv = 1; 2635 } 2636 if ((cp = bozo_get_pref(prefs, "hide dots")) != NULL && 2637 strcmp(cp, "true") == 0) { 2638 httpd->hide_dots = 1; 2639 } 2640 if ((cp = bozo_get_pref(prefs, "directory indexing")) != NULL && 2641 strcmp(cp, "true") == 0) { 2642 httpd->dir_indexing = 1; 2643 } 2644 if ((cp = bozo_get_pref(prefs, "directory index readme")) != NULL) { 2645 httpd->dir_readme = bozostrdup(httpd, NULL, cp); 2646 } 2647 if ((cp = bozo_get_pref(prefs, "public_html")) != NULL) { 2648 httpd->public_html = bozostrdup(httpd, NULL, cp); 2649 } 2650 if ((cp = bozo_get_pref(prefs, "ssl timeout")) != NULL) { 2651 httpd->ssl_timeout = atoi(cp); 2652 } 2653 if ((cp = bozo_get_pref(prefs, "initial timeout")) != NULL) { 2654 httpd->initial_timeout = atoi(cp); 2655 } 2656 if ((cp = bozo_get_pref(prefs, "header timeout")) != NULL) { 2657 httpd->header_timeout = atoi(cp); 2658 } 2659 if ((cp = bozo_get_pref(prefs, "request timeout")) != NULL) { 2660 httpd->request_timeout = atoi(cp); 2661 } 2662 httpd->server_software = 2663 bozostrdup(httpd, NULL, bozo_get_pref(prefs, "server software")); 2664 httpd->index_html = 2665 bozostrdup(httpd, NULL, bozo_get_pref(prefs, "index.html")); 2666 2667 /* 2668 * initialise ssl and daemon mode if necessary. 2669 */ 2670 bozo_ssl_init(httpd); 2671 bozo_daemon_init(httpd); 2672 2673 username = bozo_get_pref(prefs, "username"); 2674 if (username != NULL) { 2675 if ((pw = getpwnam(username)) == NULL) 2676 bozoerr(httpd, 1, "getpwnam(%s): %s", username, 2677 strerror(errno)); 2678 if (initgroups(pw->pw_name, pw->pw_gid) == -1) 2679 bozoerr(httpd, 1, "initgroups: %s", strerror(errno)); 2680 if (setgid(pw->pw_gid) == -1) 2681 bozoerr(httpd, 1, "setgid(%u): %s", pw->pw_gid, 2682 strerror(errno)); 2683 uid = pw->pw_uid; 2684 uidset = 1; 2685 } 2686 /* 2687 * handle chroot. 2688 */ 2689 if ((chrootdir = bozo_get_pref(prefs, "chroot dir")) != NULL) { 2690 httpd->rootdir = bozostrdup(httpd, NULL, chrootdir); 2691 if (chdir(httpd->rootdir) == -1) 2692 bozoerr(httpd, 1, "chdir(%s): %s", httpd->rootdir, 2693 strerror(errno)); 2694 if (chroot(httpd->rootdir) == -1) 2695 bozoerr(httpd, 1, "chroot(%s): %s", httpd->rootdir, 2696 strerror(errno)); 2697 } 2698 2699 if (uidset && setuid(uid) == -1) 2700 bozoerr(httpd, 1, "setuid(%d): %s", uid, strerror(errno)); 2701 2702 /* 2703 * prevent info leakage between different compartments. 2704 * some PATH values in the environment would be invalided 2705 * by chroot. cross-user settings might result in undesirable 2706 * effects. 2707 */ 2708 if ((chrootdir != NULL || username != NULL) && !dirtyenv) 2709 environ = cleanenv; 2710 2711 #ifdef _SC_PAGESIZE 2712 httpd->page_size = (long)sysconf(_SC_PAGESIZE); 2713 #else 2714 httpd->page_size = 4096; 2715 #endif 2716 debug((httpd, DEBUG_OBESE, "myname is %s, slashdir is %s", 2717 httpd->virthostname, httpd->slashdir)); 2718 2719 return 1; 2720 } 2721 2722 void 2723 bozo_cleanup(bozohttpd_t *httpd, bozoprefs_t *prefs) 2724 { 2725 bozo_clear_prefs(prefs); 2726 2727 free(httpd->virthostname); 2728 free(httpd->errorbuf); 2729 free(httpd->getln_buffer); 2730 free(httpd->slashdir); 2731 #define bozo_unconst(x) ((void *)(uintptr_t)x) 2732 free(bozo_unconst(httpd->server_software)); 2733 free(bozo_unconst(httpd->index_html)); 2734 free(bozo_unconst(httpd->dir_readme)); 2735 free(bozo_unconst(httpd->public_html)); 2736 #undef bozo_unconst 2737 } 2738 2739 int 2740 bozo_get_version(char *buf, size_t size) 2741 { 2742 return snprintf(buf, size, "%s", SERVER_SOFTWARE); 2743 } 2744