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