1 /* $NetBSD: bozohttpd.c,v 1.15 2009/05/23 08:26:26 mrg Exp $ */ 2 3 /* $eterna: bozohttpd.c,v 1.159 2009/05/23 02:14:30 mrg Exp $ */ 4 5 /* 6 * Copyright (c) 1997-2009 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 * however, this does not include cgi-bin support 44 * - access lists via libwrap via inetd/tcpd 45 * - virtual hosting 46 * - not that we do not even pretend to understand MIME, but 47 * rely only on the HTTP specification 48 * - ipv6 support 49 * - automatic `index.html' generation 50 * - configurable server name 51 * - directory index generation 52 * - daemon mode (lacks libwrap support) 53 * - .htpasswd support 54 */ 55 56 /* 57 * requirements for minimal http/1.1 (at least, as documented in 58 * <draft-ietf-http-v11-spec-rev-06> which expired may 18, 1999): 59 * 60 * - 14.15: content-encoding handling. [1] 61 * 62 * - 14.16: content-length handling. this is only a SHOULD header 63 * thus we could just not send it ever. [1] 64 * 65 * - 14.17: content-type handling. [1] 66 * 67 * - 14.25/28: if-{,un}modified-since handling. maybe do this, but 68 * i really don't want to have to parse 3 differnet date formats 69 * 70 * [1] need to revisit to ensure proper behaviour 71 * 72 * and the following is a list of features that we do not need 73 * to have due to other limits, or are too lazy. there are more 74 * of these than are listed, but these are of particular note, 75 * and could perhaps be implemented. 76 * 77 * - 3.5/3.6: content/transfer codings. probably can ignore 78 * this? we "SHOULD"n't. but 4.4 says we should ignore a 79 * `content-length' header upon reciept of a `transfer-encoding' 80 * header. 81 * 82 * - 5.1.1: request methods. only MUST support GET and HEAD, 83 * but there are new ones besides POST that are currently 84 * supported: OPTIONS PUT DELETE TRACE and CONNECT, plus 85 * extensions not yet known? 86 * 87 * - 10.1: we can ignore informational status codes 88 * 89 * - 10.3.3/10.3.4/10.3.8: just use '302' codes always. 90 * 91 * - 14.1/14.2/14.3/14.27: we do not support Accept: headers.. 92 * just ignore them and send the request anyway. they are 93 * only SHOULD. 94 * 95 * - 14.5/14.16/14.35: we don't do ranges. from section 14.35.2 96 * `A server MAY ignore the Range header'. but it might be nice. 97 * since 20080301 we support simple range headers. 98 * 99 * - 14.9: we aren't a cache. 100 * 101 * - 14.15: content-md5 would be nice... 102 * 103 * - 14.24/14.26/14.27: be nice to support this... 104 * 105 * - 14.44: not sure about this Vary: header. ignore it for now. 106 */ 107 108 #ifndef INDEX_HTML 109 #define INDEX_HTML "index.html" 110 #endif 111 #ifndef SERVER_SOFTWARE 112 #define SERVER_SOFTWARE "bozohttpd/20090522" 113 #endif 114 #ifndef DIRECT_ACCESS_FILE 115 #define DIRECT_ACCESS_FILE ".bzdirect" 116 #endif 117 #ifndef REDIRECT_FILE 118 #define REDIRECT_FILE ".bzredirect" 119 #endif 120 #ifndef ABSREDIRECT_FILE 121 #define ABSREDIRECT_FILE ".bzabsredirect" 122 #endif 123 124 /* 125 * And so it begins .. 126 */ 127 128 #include <sys/param.h> 129 #include <sys/socket.h> 130 #include <sys/time.h> 131 #include <sys/mman.h> 132 133 #include <arpa/inet.h> 134 135 #include <ctype.h> 136 #include <dirent.h> 137 #include <errno.h> 138 #include <fcntl.h> 139 #include <netdb.h> 140 #include <pwd.h> 141 #include <grp.h> 142 #include <signal.h> 143 #include <stdarg.h> 144 #include <stdlib.h> 145 #include <string.h> 146 #include <syslog.h> 147 #include <time.h> 148 #include <unistd.h> 149 150 #ifndef __attribute__ 151 #define __attribute__(x) 152 #endif /* __attribute__ */ 153 154 #include "bozohttpd.h" 155 156 #ifndef MAX_WAIT_TIME 157 #define MAX_WAIT_TIME 60 /* hang around for 60 seconds max */ 158 #endif 159 160 /* variables and functions */ 161 162 int bflag; /* background; drop into daemon mode */ 163 int fflag; /* keep daemon mode in foreground */ 164 static int eflag; /* don't clean environ; -t/-U only */ 165 const char *Iflag = "http";/* bind port; default "http" */ 166 int Iflag_set; 167 int dflag = 0; /* debugging level */ 168 char *myname; /* my name */ 169 170 #ifndef LOG_FTP 171 #define LOG_FTP LOG_DAEMON 172 #endif 173 174 static char *tflag; /* root directory */ 175 static char *Uflag; /* user name to switch to */ 176 static int Vflag; /* unknown vhosts go to normal slashdir */ 177 static int nflag; /* avoid gethostby*() */ 178 static int rflag; /* make sure referrer = me unless url = / */ 179 static int sflag; /* log to stderr even if it is not a TTY */ 180 static char *vpath; /* virtual directory base */ 181 182 size_t page_size; /* page size */ 183 char *slashdir; /* www slash directory */ 184 185 const char *server_software = SERVER_SOFTWARE; 186 const char *index_html = INDEX_HTML; 187 const char http_09[] = "HTTP/0.9"; 188 const char http_10[] = "HTTP/1.0"; 189 const char http_11[] = "HTTP/1.1"; 190 const char text_plain[] = "text/plain"; 191 192 static void usage(void); 193 static void alarmer(int); 194 volatile sig_atomic_t alarmhit; 195 196 static void parse_request(char *, char **, char **, char **, char **); 197 static void clean_request(http_req *request); 198 static http_req *read_request(void); 199 static struct headers *addmerge_header(http_req *, char *, char *, ssize_t); 200 static int mmap_and_write_part(int, off_t, size_t); 201 static void process_request(http_req *); 202 static int check_direct_access(http_req *request); 203 static int transform_request(http_req *, int *); 204 static void handle_redirect(http_req *, const char *, int); 205 206 static int check_virtual(http_req *); 207 static void check_bzredirect(http_req *); 208 static void fix_url_percent(http_req *); 209 static int process_proto(http_req *, const char *); 210 static int process_method(http_req *, const char *); 211 static void escape_html(http_req *); 212 213 static const char *http_errors_short(int); 214 static const char *http_errors_long(int); 215 216 217 /* bozotic io */ 218 int (*bozoprintf)(const char *, ...) = printf; 219 ssize_t (*bozoread)(int, void *, size_t) = read; 220 ssize_t (*bozowrite)(int, const void *, size_t) = write; 221 int (*bozoflush)(FILE *) = fflush; 222 223 char *progname; 224 225 int main(int, char **); 226 227 static void 228 usage(void) 229 { 230 warning("usage: %s [options] slashdir [myname]", progname); 231 warning("options:"); 232 #ifdef DEBUG 233 warning(" -d\t\t\tenable debug support"); 234 #endif 235 warning(" -s\t\t\talways log to stderr"); 236 #ifndef NO_USER_SUPPORT 237 warning(" -u\t\t\tenable ~user/public_html support"); 238 warning(" -p dir\t\tchange `public_html' directory name]"); 239 #endif 240 #ifndef NO_DYNAMIC_CONTENT 241 warning(" -M arg t c c11\tadd this mime extenstion"); 242 #endif 243 #ifndef NO_CGIBIN_SUPPORT 244 #ifndef NO_DYNAMIC_CONTENT 245 warning(" -C arg prog\t\tadd this CGI handler"); 246 #endif 247 warning(" -c cgibin\t\tenable cgi-bin support in this directory"); 248 #endif 249 #ifndef NO_DAEMON_MODE 250 warning(" -b\t\t\tbackground and go into daemon mode"); 251 warning(" -f\t\t\tkeep daemon mode in the foreground"); 252 warning(" -i address\t\tbind on this address (daemon mode only)"); 253 warning(" -I port\t\tbind on this port (daemon mode only)"); 254 #endif 255 warning(" -S version\t\tset server version string"); 256 warning(" -t dir\t\tchroot to `dir'"); 257 warning(" -U username\t\tchange user to `user'"); 258 warning(" -e\t\t\tdon't clean the environment (-t and -U only)"); 259 warning(" -v virtualroot\tenable virtual host support in this directory"); 260 warning(" -r\t\t\tmake sure sub-pages come from this host via referrer"); 261 #ifndef NO_DIRINDEX_SUPPORT 262 warning(" -X\t\t\tenable automatic directory index support"); 263 warning(" -H\t\t\thide files starting with a period (.) in index mode"); 264 #endif 265 warning(" -x index\t\tchange default `index.html' file name"); 266 #ifndef NO_SSL_SUPPORT 267 warning(" -Z cert privkey\tspecify path to server certificate and private key file\n" 268 "\t\t\tin pem format and enable bozohttpd in SSL mode"); 269 #endif /* NO_SSL_SUPPORT */ 270 error(1, "%s failed to start", progname); 271 } 272 273 int 274 main(int argc, char **argv) 275 { 276 http_req *request; 277 extern char **environ; 278 char *cleanenv[1]; 279 uid_t uid; 280 int c; 281 282 uid = 0; /* XXX gcc */ 283 284 if ((progname = strrchr(argv[0], '/')) != NULL) 285 progname++; 286 else 287 progname = argv[0]; 288 289 openlog(progname, LOG_PID|LOG_NDELAY, LOG_FTP); 290 291 while ((c = getopt(argc, argv, 292 "C:HI:M:S:U:VXZ:bc:defhi:np:rst:uv:x:z:")) != -1) { 293 switch(c) { 294 295 case 'M': 296 #ifndef NO_DYNAMIC_CONTENT 297 /* make sure there's four arguments */ 298 if (argc - optind < 3) 299 usage(); 300 add_content_map_mime(optarg, argv[optind], 301 argv[optind+1], argv[optind+2]); 302 optind += 3; 303 break; 304 #else 305 error(1, "dynmic mime content support is not enabled"); 306 /* NOTREACHED */ 307 #endif /* NO_DYNAMIC_CONTENT */ 308 309 case 'n': 310 nflag = 1; 311 break; 312 313 case 'r': 314 rflag = 1; 315 break; 316 317 case 's': 318 sflag = 1; 319 break; 320 321 case 'S': 322 server_software = optarg; 323 break; 324 case 'Z': 325 #ifndef NO_SSL_SUPPORT 326 /* make sure there's two arguments */ 327 if (argc - optind < 1) 328 usage(); 329 ssl_set_opts(optarg, argv[optind++]); 330 break; 331 #else 332 error(1, "ssl support is not enabled"); 333 /* NOT REACHED */ 334 #endif /* NO_SSL_SUPPORT */ 335 case 'U': 336 Uflag = optarg; 337 break; 338 339 case 'V': 340 Vflag = 1; 341 break; 342 343 case 'v': 344 vpath = optarg; 345 break; 346 347 case 'x': 348 index_html = optarg; 349 break; 350 351 #ifndef NO_DAEMON_MODE 352 case 'b': 353 /* 354 * test suite support - undocumented 355 * bflag == 2 (aka, -b -b) means to 356 * only process 1 per kid 357 */ 358 if (bflag) 359 bflag = 2; 360 else 361 bflag = 1; 362 break; 363 364 case 'e': 365 eflag = 1; 366 break; 367 368 case 'f': 369 fflag = 1; 370 break; 371 372 case 'i': 373 iflag = optarg; 374 break; 375 376 case 'I': 377 Iflag_set = 1; 378 Iflag = optarg; 379 break; 380 #else /* NO_DAEMON_MODE */ 381 case 'b': 382 case 'e': 383 case 'f': 384 case 'i': 385 case 'I': 386 error(1, "Daemon mode is not enabled"); 387 /* NOTREACHED */ 388 #endif /* NO_DAEMON_MODE */ 389 390 #ifndef NO_CGIBIN_SUPPORT 391 case 'c': 392 set_cgibin(optarg); 393 break; 394 395 case 'C': 396 #ifndef NO_DYNAMIC_CONTENT 397 /* make sure there's two arguments */ 398 if (argc - optind < 1) 399 usage(); 400 add_content_map_cgi(optarg, argv[optind++]); 401 break; 402 #else 403 error(1, "dynmic CGI handler support is not enabled"); 404 /* NOTREACHED */ 405 #endif /* NO_DYNAMIC_CONTENT */ 406 407 #else 408 case 'c': 409 case 'C': 410 error(1, "CGI is not enabled"); 411 /* NOTREACHED */ 412 #endif /* NO_CGIBIN_SUPPORT */ 413 414 case 'd': 415 dflag++; 416 #ifndef DEBUG 417 if (dflag == 1) 418 warning("Debugging is not enabled"); 419 #endif /* !DEBUG */ 420 break; 421 422 #ifndef NO_USER_SUPPORT 423 case 'p': 424 public_html = optarg; 425 break; 426 427 case 't': 428 tflag = optarg; 429 break; 430 431 case 'u': 432 uflag = 1; 433 break; 434 #else 435 case 'p': 436 case 't': 437 case 'u': 438 error(1, "User support is not enabled"); 439 /* NOTREACHED */ 440 #endif /* NO_USER_SUPPORT */ 441 442 #ifndef NO_DIRINDEX_SUPPORT 443 case 'H': 444 Hflag = 1; 445 break; 446 447 case 'X': 448 Xflag = 1; 449 break; 450 451 #else 452 case 'H': 453 case 'X': 454 error(1, "directory indexing is not enabled"); 455 /* NOTREACHED */ 456 #endif /* NO_DIRINDEX_SUPPORT */ 457 458 default: 459 usage(); 460 /* NOTREACHED */ 461 } 462 } 463 argc -= optind; 464 argv += optind; 465 466 if (argc == 1) { 467 myname = bozomalloc(MAXHOSTNAMELEN+1); 468 /* XXX we do not check for FQDN here */ 469 if (gethostname(myname, MAXHOSTNAMELEN+1) < 0) 470 error(1, "gethostname"); 471 myname[MAXHOSTNAMELEN] = '\0'; 472 } else if (argc == 2) 473 myname = argv[1]; 474 else 475 usage(); 476 477 slashdir = argv[0]; 478 debug((DEBUG_OBESE, "myname is %s, slashdir is %s", myname, slashdir)); 479 480 #ifdef _SC_PAGESIZE 481 page_size = (long)sysconf(_SC_PAGESIZE); 482 #else 483 page_size = 4096; 484 #endif 485 486 /* 487 * initialise ssl and daemon mode if necessary. 488 */ 489 ssl_init(); 490 daemon_init(); 491 492 /* 493 * prevent info leakage between different compartments. 494 * some PATH values in the environment would be invalided 495 * by chroot. cross-user settings might result in undesirable 496 * effects. 497 */ 498 if ((tflag != NULL || Uflag != NULL) && !eflag) { 499 cleanenv[0] = NULL; 500 environ = cleanenv; 501 } 502 503 /* 504 * look up user/group information. 505 */ 506 if (Uflag != NULL) { 507 struct passwd *pw; 508 509 if ((pw = getpwnam(Uflag)) == NULL) 510 error(1, "getpwnam(%s): %s", Uflag, strerror(errno)); 511 if (initgroups(pw->pw_name, pw->pw_gid) == -1) 512 error(1, "initgroups: %s", strerror(errno)); 513 if (setgid(pw->pw_gid) == -1) 514 error(1, "setgid(%u): %s", pw->pw_gid, strerror(errno)); 515 uid = pw->pw_uid; 516 } 517 518 /* 519 * handle chroot. 520 */ 521 if (tflag != NULL) { 522 if (chdir(tflag) == -1) 523 error(1, "chdir(%s): %s", tflag, strerror(errno)); 524 if (chroot(tflag) == -1) 525 error(1, "chroot(%s): %s", tflag, strerror(errno)); 526 } 527 528 if (Uflag != NULL) 529 if (setuid(uid) == -1) 530 error(1, "setuid(%d): %s", uid, strerror(errno)); 531 532 /* 533 * read and process the HTTP request. 534 */ 535 do { 536 request = read_request(); 537 if (request) { 538 process_request(request); 539 clean_request(request); 540 } 541 } while (bflag); 542 543 return (0); 544 } 545 546 char * 547 http_date(void) 548 { 549 static char date[40]; 550 struct tm *tm; 551 time_t now; 552 553 /* Sun, 06 Nov 1994 08:49:37 GMT */ 554 now = time(NULL); 555 tm = gmtime(&now); /* HTTP/1.1 spec rev 06 sez GMT only */ 556 strftime(date, sizeof date, "%a, %d %b %Y %H:%M:%S GMT", tm); 557 return date; 558 } 559 560 /* 561 * convert "in" into the three parts of a request (first line). 562 * we allocate into file and query, but return pointers into 563 * "in" for proto and method. 564 */ 565 static void 566 parse_request(char *in, char **method, char **file, char **query, char **proto) 567 { 568 ssize_t len; 569 char *val; 570 571 debug((DEBUG_EXPLODING, "parse in: %s", in)); 572 *method = *file = *query = *proto = NULL; 573 574 len = (ssize_t)strlen(in); 575 val = bozostrnsep(&in, " \t\n\r", &len); 576 if (len < 1 || val == NULL) 577 return; 578 *method = val; 579 580 while (*in == ' ' || *in == '\t') 581 in++; 582 val = bozostrnsep(&in, " \t\n\r", &len); 583 if (len < 1) { 584 if (len == 0) 585 *file = val; 586 else 587 *file = in; 588 return; 589 } 590 *file = val; 591 592 *query = strchr(*file, '?'); 593 if (*query) 594 *(*query)++ = '\0'; 595 596 if (in) { 597 while (*in && (*in == ' ' || *in == '\t')) 598 in++; 599 if (*in) 600 *proto = in; 601 } 602 603 /* allocate private copies */ 604 *file = strdup(*file); 605 if (*query) 606 *query = strdup(*query); 607 608 debug((DEBUG_FAT, "url: method: \"%s\" file: \"%s\" query: \"%s\" proto: \"%s\"", 609 *method, *file, *query, *proto)); 610 } 611 612 /* 613 * send a HTTP/1.1 408 response if we timeout. 614 */ 615 /* ARGSUSED */ 616 static void 617 alarmer(int sig) 618 { 619 alarmhit = 1; 620 } 621 622 /* 623 * cleanup a http_req after use 624 */ 625 static void 626 clean_request(http_req *request) 627 { 628 struct headers *hdr, *ohdr = NULL; 629 630 if (request == NULL) 631 return; 632 633 /* clean up request */ 634 #define MF(x) if (request->x) free(request->x) 635 MF(hr_remotehost); 636 MF(hr_remoteaddr); 637 MF(hr_serverport); 638 MF(hr_file); 639 MF(hr_query); 640 #undef MF 641 auth_cleanup(request); 642 for (hdr = SIMPLEQ_FIRST(&request->hr_headers); hdr; 643 hdr = SIMPLEQ_NEXT(hdr, h_next)) { 644 free(hdr->h_value); 645 free(hdr->h_header); 646 if (ohdr) 647 free(ohdr); 648 ohdr = hdr; 649 } 650 if (ohdr) 651 free(ohdr); 652 653 free(request); 654 } 655 656 /* 657 * This function reads a http request from stdin, returning a pointer to a 658 * http_req structure, describing the request. 659 */ 660 static http_req * 661 read_request(void) 662 { 663 struct sigaction sa; 664 char *str, *val, *method, *file, *proto, *query; 665 char *host, *addr, *port; 666 char bufport[10]; 667 char hbuf[NI_MAXHOST], abuf[NI_MAXHOST]; 668 struct sockaddr_storage ss; 669 ssize_t len; 670 int line = 0; 671 socklen_t slen; 672 http_req *request; 673 674 /* 675 * if we're in daemon mode, daemon_fork() will return here once 676 * for each child, then we can setup SSL. 677 */ 678 daemon_fork(); 679 ssl_accept(); 680 681 request = bozomalloc(sizeof *request); 682 memset(request, 0, sizeof *request); 683 request->hr_allow = request->hr_host = NULL; 684 request->hr_content_type = request->hr_content_length = NULL; 685 request->hr_range = NULL; 686 request->hr_last_byte_pos = -1; 687 request->hr_if_modified_since = NULL; 688 request->hr_file = NULL; 689 690 slen = sizeof(ss); 691 if (getpeername(0, (struct sockaddr *)&ss, &slen) < 0) 692 host = addr = NULL; 693 else { 694 if (getnameinfo((struct sockaddr *)&ss, slen, 695 abuf, sizeof abuf, NULL, 0, NI_NUMERICHOST) == 0) 696 addr = abuf; 697 else 698 addr = NULL; 699 if (nflag == 0 && getnameinfo((struct sockaddr *)&ss, slen, 700 hbuf, sizeof hbuf, NULL, 0, 0) == 0) 701 host = hbuf; 702 else 703 host = NULL; 704 } 705 if (host != NULL) 706 request->hr_remotehost = bozostrdup(host); 707 if (addr != NULL) 708 request->hr_remoteaddr = bozostrdup(addr); 709 slen = sizeof(ss); 710 if (getsockname(0, (struct sockaddr *)&ss, &slen) < 0) 711 port = NULL; 712 else { 713 if (getnameinfo((struct sockaddr *)&ss, slen, NULL, 0, 714 bufport, sizeof bufport, NI_NUMERICSERV) == 0) 715 port = bufport; 716 else 717 port = NULL; 718 } 719 if (port != NULL) 720 request->hr_serverport = bozostrdup(port); 721 722 /* 723 * setup a timer to make sure the request is not hung 724 */ 725 sa.sa_handler = alarmer; 726 sigemptyset(&sa.sa_mask); 727 sigaddset(&sa.sa_mask, SIGALRM); 728 sa.sa_flags = 0; 729 sigaction(SIGALRM, &sa, NULL); /* XXX */ 730 731 alarm(MAX_WAIT_TIME); 732 while ((str = bozodgetln(STDIN_FILENO, &len, bozoread)) != NULL) { 733 alarm(0); 734 if (alarmhit) { 735 (void)http_error(408, NULL, "request timed out"); 736 goto cleanup; 737 } 738 line++; 739 740 if (line == 1) { 741 742 if (len < 1) { 743 (void)http_error(404, NULL, "null method"); 744 goto cleanup; 745 } 746 747 warning("got request ``%s'' from host %s to port %s", 748 str, 749 host ? host : addr ? addr : "<local>", 750 port ? port : "<stdin>"); 751 #if 0 752 debug((DEBUG_FAT, "read_req, getting request: ``%s''", 753 str)); 754 #endif 755 756 /* we allocate return space in file and query only */ 757 parse_request(str, &method, &file, &query, &proto); 758 request->hr_file = file; 759 request->hr_query = query; 760 if (method == NULL) { 761 (void)http_error(404, NULL, "null method"); 762 goto cleanup; 763 } 764 if (file == NULL) { 765 (void)http_error(404, NULL, "null file"); 766 goto cleanup; 767 } 768 769 /* 770 * note that we parse the proto first, so that we 771 * can more properly parse the method and the url. 772 */ 773 774 if (process_proto(request, proto) || 775 process_method(request, method)) { 776 goto cleanup; 777 } 778 779 debug((DEBUG_FAT, "got file \"%s\" query \"%s\"", 780 request->hr_file, 781 request->hr_query ? request->hr_query : "<none>")); 782 783 /* http/0.9 has no header processing */ 784 if (request->hr_proto == http_09) 785 break; 786 } else { /* incoming headers */ 787 struct headers *hdr; 788 789 if (*str == '\0') 790 break; 791 792 val = bozostrnsep(&str, ":", &len); 793 debug((DEBUG_EXPLODING, 794 "read_req2: after bozostrnsep: str ``%s'' val ``%s''", 795 str, val)); 796 if (val == NULL || len == -1) { 797 (void)http_error(404, request, "no header"); 798 goto cleanup; 799 } 800 while (*str == ' ' || *str == '\t') 801 len--, str++; 802 while (*val == ' ' || *val == '\t') 803 val++; 804 805 if (auth_check_headers(request, val, str, len)) 806 goto next_header; 807 808 hdr = addmerge_header(request, val, str, len); 809 810 if (strcasecmp(hdr->h_header, "content-type") == 0) 811 request->hr_content_type = hdr->h_value; 812 else if (strcasecmp(hdr->h_header, "content-length") == 0) 813 request->hr_content_length = hdr->h_value; 814 else if (strcasecmp(hdr->h_header, "host") == 0) 815 request->hr_host = hdr->h_value; 816 /* HTTP/1.1 rev06 draft spec: 14.20 */ 817 else if (strcasecmp(hdr->h_header, "expect") == 0) { 818 (void)http_error(417, request, "we don't support Expect:"); 819 goto cleanup; 820 } 821 else if (strcasecmp(hdr->h_header, "referrer") == 0 || 822 strcasecmp(hdr->h_header, "referer") == 0) 823 request->hr_referrer = hdr->h_value; 824 else if (strcasecmp(hdr->h_header, "range") == 0) 825 request->hr_range = hdr->h_value; 826 else if (strcasecmp(hdr->h_header, "if-modified-since") == 0) 827 request->hr_if_modified_since = hdr->h_value; 828 829 debug((DEBUG_FAT, "adding header %s: %s", 830 hdr->h_header, hdr->h_value)); 831 } 832 next_header: 833 alarm(MAX_WAIT_TIME); 834 } 835 836 /* now, clear it all out */ 837 alarm(0); 838 signal(SIGALRM, SIG_DFL); 839 840 /* RFC1945, 8.3 */ 841 if (request->hr_method == HTTP_POST && request->hr_content_length == NULL) { 842 (void)http_error(400, request, "missing content length"); 843 goto cleanup; 844 } 845 846 /* HTTP/1.1 draft rev-06, 14.23 & 19.6.1.1 */ 847 if (request->hr_proto == http_11 && request->hr_host == NULL) { 848 (void)http_error(400, request, "missing Host header"); 849 goto cleanup; 850 } 851 852 if (request->hr_range != NULL) { 853 debug((DEBUG_FAT, "hr_range: %s", request->hr_range)); 854 /* support only simple ranges %d- and %d-%d */ 855 if (strchr(request->hr_range, ',') == NULL) { 856 const char *rstart, *dash; 857 858 rstart = strchr(request->hr_range, '='); 859 if (rstart != NULL) { 860 rstart++; 861 dash = strchr(rstart, '-'); 862 if (dash != NULL && dash != rstart) { 863 dash++; 864 request->hr_have_range = 1; 865 request->hr_first_byte_pos = 866 strtoll(rstart, NULL, 10); 867 if (request->hr_first_byte_pos < 0) 868 request->hr_first_byte_pos = 0; 869 if (*dash != '\0') { 870 request->hr_last_byte_pos = 871 strtoll(dash, NULL, 10); 872 if (request->hr_last_byte_pos < 0) 873 request->hr_last_byte_pos = -1; 874 } 875 } 876 } 877 } 878 } 879 880 debug((DEBUG_FAT, "read_request returns url %s in request", 881 request->hr_file)); 882 return (request); 883 884 cleanup: 885 clean_request(request); 886 887 /* If SSL enabled cleanup SSL structure. */ 888 ssl_destroy(); 889 890 return NULL; 891 } 892 893 /* 894 * add or merge this header (val: str) into the requests list 895 */ 896 static struct headers * 897 addmerge_header(http_req *request, char *val, char *str, ssize_t len) 898 { 899 struct headers *hdr; 900 901 /* do we exist already? */ 902 SIMPLEQ_FOREACH(hdr, &request->hr_headers, h_next) { 903 if (strcasecmp(val, hdr->h_header) == 0) 904 break; 905 } 906 907 if (hdr) { 908 /* yup, merge it in */ 909 char *nval; 910 911 if (asprintf(&nval, "%s, %s", hdr->h_value, str) == -1) { 912 (void)http_error(500, NULL, 913 "memory allocation failure"); 914 return NULL; 915 } 916 free(hdr->h_value); 917 hdr->h_value = nval; 918 } else { 919 /* nope, create a new one */ 920 921 hdr = bozomalloc(sizeof *hdr); 922 hdr->h_header = bozostrdup(val); 923 if (str && *str) 924 hdr->h_value = bozostrdup(str); 925 else 926 hdr->h_value = bozostrdup(" "); 927 928 SIMPLEQ_INSERT_TAIL(&request->hr_headers, hdr, h_next); 929 request->hr_nheaders++; 930 } 931 932 return hdr; 933 } 934 935 static int 936 mmap_and_write_part(int fd, off_t first_byte_pos, size_t sz) 937 { 938 size_t mappedsz, wroffset; 939 off_t mappedoffset; 940 char *addr; 941 void *mappedaddr; 942 943 /* 944 * we need to ensure that both the size *and* offset arguments to 945 * mmap() are page-aligned. our formala for this is: 946 * 947 * input offset: first_byte_pos 948 * input size: sz 949 * 950 * mapped offset = page align truncate (input offset) 951 * mapped size = 952 * page align extend (input offset - mapped offset + input size) 953 * write offset = input offset - mapped offset 954 * 955 * we use the write offset in all writes 956 */ 957 mappedoffset = first_byte_pos & ~(page_size - 1); 958 mappedsz = (first_byte_pos - mappedoffset + sz + page_size - 1) & 959 ~(page_size - 1); 960 wroffset = first_byte_pos - mappedoffset; 961 962 addr = mmap(0, mappedsz, PROT_READ, MAP_SHARED, fd, mappedoffset); 963 if (addr == (char *)-1) { 964 warning("mmap failed: %s", strerror(errno)); 965 return -1; 966 } 967 mappedaddr = addr; 968 969 #ifdef MADV_SEQUENTIAL 970 (void)madvise(addr, sz, MADV_SEQUENTIAL); 971 #endif 972 while (sz > WRSZ) { 973 if (bozowrite(STDOUT_FILENO, addr + wroffset, WRSZ) != WRSZ) { 974 warning("write failed: %s", strerror(errno)); 975 goto out; 976 } 977 debug((DEBUG_OBESE, "wrote %d bytes", WRSZ)); 978 sz -= WRSZ; 979 addr += WRSZ; 980 } 981 if (sz && (size_t)bozowrite(STDOUT_FILENO, addr + wroffset, sz) != sz) { 982 warning("final write failed: %s", strerror(errno)); 983 goto out; 984 } 985 debug((DEBUG_OBESE, "wrote %d bytes", (int)sz)); 986 out: 987 if (munmap(mappedaddr, mappedsz) < 0) { 988 warning("munmap failed"); 989 return -1; 990 } 991 992 return 0; 993 } 994 995 static int 996 parse_http_date(const char *val, time_t *timestamp) 997 { 998 char *remainder; 999 struct tm tm; 1000 1001 if ((remainder = strptime(val, "%a, %d %b %Y %T GMT", &tm)) == NULL && 1002 (remainder = strptime(val, "%a, %d-%b-%y %T GMT", &tm)) == NULL && 1003 (remainder = strptime(val, "%a %b %d %T %Y", &tm)) == NULL) 1004 return 0; /* Invalid HTTP date format */ 1005 1006 if (*remainder) 1007 return 0; /* No trailing garbage */ 1008 1009 *timestamp = timegm(&tm); 1010 return 1; 1011 } 1012 1013 /* 1014 * process_request does the following: 1015 * - check the request is valid 1016 * - process cgi-bin if necessary 1017 * - transform a filename if necesarry 1018 * - return the HTTP request 1019 */ 1020 static void 1021 process_request(http_req *request) 1022 { 1023 struct stat sb; 1024 time_t timestamp; 1025 char *file; 1026 const char *type, *encoding; 1027 int fd, isindex; 1028 1029 /* 1030 * note that transform_request chdir()'s if required. also note 1031 * that cgi is handed here. if transform_request() returns 0 1032 * then the request has been handled already. 1033 */ 1034 if (transform_request(request, &isindex) == 0) 1035 return; 1036 1037 file = request->hr_file; 1038 1039 fd = open(file, O_RDONLY); 1040 if (fd < 0) { 1041 debug((DEBUG_FAT, "open failed: %s", strerror(errno))); 1042 if (errno == EPERM) 1043 (void)http_error(403, request, "no permission to open file"); 1044 else if (errno == ENOENT) { 1045 if (directory_index(request, file, isindex)) 1046 ; 1047 else 1048 (void)http_error(404, request, "no file"); 1049 } else 1050 (void)http_error(500, request, "open file"); 1051 goto cleanup_nofd; 1052 } 1053 if (fstat(fd, &sb) < 0) { 1054 (void)http_error(500, request, "can't fstat"); 1055 goto cleanup; 1056 } 1057 if (S_ISDIR(sb.st_mode)) { 1058 handle_redirect(request, NULL, 0); 1059 goto cleanup; 1060 } 1061 1062 if (request->hr_if_modified_since && 1063 parse_http_date(request->hr_if_modified_since, ×tamp) && 1064 timestamp >= sb.st_mtime) { 1065 /* XXX ignore subsecond of timestamp */ 1066 bozoprintf("%s 304 Not Modified\r\n", request->hr_proto); 1067 bozoprintf("\r\n"); 1068 bozoflush(stdout); 1069 goto cleanup; 1070 } 1071 1072 /* validate requested range */ 1073 if (request->hr_last_byte_pos == -1 || 1074 request->hr_last_byte_pos >= sb.st_size) 1075 request->hr_last_byte_pos = sb.st_size - 1; 1076 if (request->hr_have_range && 1077 request->hr_first_byte_pos > request->hr_last_byte_pos) { 1078 request->hr_have_range = 0; /* punt */ 1079 request->hr_first_byte_pos = 0; 1080 request->hr_last_byte_pos = sb.st_size - 1; 1081 } 1082 debug((DEBUG_FAT, "have_range %d first_pos %qd last_pos %qd", 1083 request->hr_have_range, 1084 request->hr_first_byte_pos, request->hr_last_byte_pos)); 1085 if (request->hr_have_range) 1086 bozoprintf("%s 206 Partial Content\r\n", request->hr_proto); 1087 else 1088 bozoprintf("%s 200 OK\r\n", request->hr_proto); 1089 1090 if (request->hr_proto != http_09) { 1091 type = content_type(request, file); 1092 encoding = content_encoding(request, file); 1093 1094 print_header(request, &sb, type, encoding); 1095 bozoprintf("\r\n"); 1096 } 1097 bozoflush(stdout); 1098 1099 if (request->hr_method != HTTP_HEAD) { 1100 off_t szleft, cur_byte_pos; 1101 static size_t mmapsz = MMAPSZ; 1102 1103 szleft = 1104 request->hr_last_byte_pos - request->hr_first_byte_pos + 1; 1105 cur_byte_pos = request->hr_first_byte_pos; 1106 1107 retry: 1108 while (szleft) { 1109 size_t sz; 1110 1111 /* This should take care of the first unaligned chunk */ 1112 if ((cur_byte_pos & (page_size - 1)) != 0) 1113 sz = cur_byte_pos & ~page_size; 1114 if ((off_t)mmapsz < szleft) 1115 sz = mmapsz; 1116 else 1117 sz = szleft; 1118 if (mmap_and_write_part(fd, cur_byte_pos, sz)) { 1119 if (errno == ENOMEM) { 1120 mmapsz /= 2; 1121 if (mmapsz >= page_size) 1122 goto retry; 1123 } 1124 goto cleanup; 1125 } 1126 cur_byte_pos += sz; 1127 szleft -= sz; 1128 } 1129 } 1130 cleanup: 1131 close(fd); 1132 cleanup_nofd: 1133 close(STDIN_FILENO); 1134 close(STDOUT_FILENO); 1135 /*close(STDERR_FILENO);*/ 1136 } 1137 1138 /* 1139 * deal with virtual host names; we do this: 1140 * if we have a virtual path root (vpath), and we are given a 1141 * virtual host spec (Host: ho.st or http://ho.st/), see if this 1142 * directory exists under vpath. if it does, use this as the 1143 # new slashdir. 1144 */ 1145 static int 1146 check_virtual(http_req *request) 1147 { 1148 char *file = request->hr_file, *s; 1149 struct dirent **list; 1150 size_t len; 1151 int i; 1152 1153 if (!vpath) 1154 goto use_slashdir; 1155 1156 /* 1157 * convert http://virtual.host/ to request->hr_host 1158 */ 1159 debug((DEBUG_OBESE, "checking for http:// virtual host in ``%s''", file)); 1160 if (strncasecmp(file, "http://", 7) == 0) { 1161 /* we would do virtual hosting here? */ 1162 file += 7; 1163 s = strchr(file, '/'); 1164 /* HTTP/1.1 draft rev-06, 5.2: URI takes precedence over Host: */ 1165 request->hr_host = file; 1166 request->hr_file = bozostrdup(s ? s : "/"); 1167 debug((DEBUG_OBESE, "got host ``%s'' file is now ``%s''", 1168 request->hr_host, request->hr_file)); 1169 } else if (!request->hr_host) 1170 goto use_slashdir; 1171 1172 1173 /* 1174 * ok, we have a virtual host, use scandir(3) to find a case 1175 * insensitive match for the virtual host we are asked for. 1176 * note that if the virtual host is the same as the master, 1177 * we don't need to do anything special. 1178 */ 1179 len = strlen(request->hr_host); 1180 debug((DEBUG_OBESE, 1181 "check_virtual: checking host `%s' under vpath `%s' for file `%s'", 1182 request->hr_host, vpath, request->hr_file)); 1183 if (strncasecmp(myname, request->hr_host, len) != 0) { 1184 s = 0; 1185 for (i = scandir(vpath, &list, 0, 0); i--; list++) { 1186 debug((DEBUG_OBESE, "looking at dir``%s''", 1187 (*list)->d_name)); 1188 if (strncasecmp((*list)->d_name, request->hr_host, 1189 len) == 0) { 1190 /* found it, punch it */ 1191 myname = (*list)->d_name; 1192 if (asprintf(&s, "%s/%s", vpath, myname) < 0) 1193 error(1, "asprintf"); 1194 break; 1195 } 1196 } 1197 if (s == 0) { 1198 if (Vflag) 1199 goto use_slashdir; 1200 return http_error(404, request, "unknown URL"); 1201 } 1202 } else 1203 use_slashdir: 1204 s = slashdir; 1205 1206 /* 1207 * ok, nailed the correct slashdir, chdir to it 1208 */ 1209 if (chdir(s) < 0) 1210 return http_error(404, request, "can't chdir to slashdir"); 1211 return 0; 1212 } 1213 1214 /* make sure we're not trying to access special files */ 1215 int 1216 check_special_files(http_req *request, const char *name) 1217 { 1218 /* ensure basename(name) != special files */ 1219 if (strcmp(name, DIRECT_ACCESS_FILE) == 0) 1220 return http_error(403, request, 1221 "no permission to open direct access file"); 1222 if (strcmp(name, REDIRECT_FILE) == 0) 1223 return http_error(403, request, 1224 "no permission to open redirect file"); 1225 if (strcmp(name, ABSREDIRECT_FILE) == 0) 1226 return http_error(403, request, 1227 "no permission to open redirect file"); 1228 return auth_check_special_files(request, name); 1229 } 1230 1231 /* 1232 * checks to see if this request has a valid .bzredirect file. returns 1233 * 0 on failure and 1 on success. 1234 */ 1235 static void 1236 check_bzredirect(http_req *request) 1237 { 1238 struct stat sb; 1239 char dir[MAXPATHLEN], redir[MAXPATHLEN], redirpath[MAXPATHLEN + 1]; 1240 char *basename, *finalredir; 1241 int rv, absolute; 1242 1243 /* 1244 * if this pathname is really a directory, but doesn't end in /, 1245 * use it as the directory to look for the redir file. 1246 */ 1247 snprintf(dir, sizeof(dir), "%s", request->hr_file + 1); 1248 debug((DEBUG_FAT, "check_bzredirect: dir %s", dir)); 1249 basename = strrchr(dir, '/'); 1250 1251 if ((!basename || basename[1] != '\0') && 1252 lstat(dir, &sb) == 0 && S_ISDIR(sb.st_mode)) 1253 /* nothing */; 1254 else if (basename == NULL) 1255 strcpy(dir, "."); 1256 else { 1257 *basename++ = '\0'; 1258 check_special_files(request, basename); 1259 } 1260 1261 snprintf(redir, sizeof(redir), "%s/%s", dir, REDIRECT_FILE); 1262 if (lstat(redir, &sb) == 0) { 1263 if (S_ISLNK(sb.st_mode) == 0) 1264 return; 1265 absolute = 0; 1266 } else { 1267 snprintf(redir, sizeof(redir), "%s/%s", dir, ABSREDIRECT_FILE); 1268 if (lstat(redir, &sb) < 0 || S_ISLNK(sb.st_mode) == 0) 1269 return; 1270 absolute = 1; 1271 } 1272 debug((DEBUG_FAT, "check_bzredirect: calling readlink")); 1273 rv = readlink(redir, redirpath, sizeof redirpath - 1); 1274 if (rv == -1 || rv == 0) { 1275 debug((DEBUG_FAT, "readlink failed")); 1276 return; 1277 } 1278 redirpath[rv] = '\0'; 1279 debug((DEBUG_FAT, "readlink returned \"%s\"", redirpath)); 1280 1281 /* now we have the link pointer, redirect to the real place */ 1282 if (absolute) 1283 finalredir = redirpath; 1284 else 1285 snprintf(finalredir = redir, sizeof(redir), "/%s/%s", dir, 1286 redirpath); 1287 1288 debug((DEBUG_FAT, "check_bzredirect: new redir %s", finalredir)); 1289 handle_redirect(request, finalredir, absolute); 1290 } 1291 1292 /* 1293 * checks to see if this request has a valid .bzdirect file. returns 1294 * 0 on failure and 1 on success. 1295 */ 1296 static int 1297 check_direct_access(http_req *request) 1298 { 1299 FILE *fp; 1300 struct stat sb; 1301 char dir[MAXPATHLEN], dirfile[MAXPATHLEN], *basename; 1302 1303 snprintf(dir, sizeof(dir), "%s", request->hr_file + 1); 1304 debug((DEBUG_FAT, "check_bzredirect: dir %s", dir)); 1305 basename = strrchr(dir, '/'); 1306 1307 if ((!basename || basename[1] != '\0') && 1308 lstat(dir, &sb) == 0 && S_ISDIR(sb.st_mode)) 1309 /* nothing */; 1310 else if (basename == NULL) 1311 strcpy(dir, "."); 1312 else { 1313 *basename++ = '\0'; 1314 check_special_files(request, basename); 1315 } 1316 1317 snprintf(dirfile, sizeof(dirfile), "%s/%s", dir, DIRECT_ACCESS_FILE); 1318 if (stat(dirfile, &sb) < 0 || 1319 (fp = fopen(dirfile, "r")) == NULL) 1320 return 0; 1321 fclose(fp); 1322 return 1; 1323 } 1324 1325 /* 1326 * transform_request does this: 1327 * - ``expand'' %20 crapola 1328 * - punt if it doesn't start with / 1329 * - check rflag / referrer 1330 * - look for "http://myname/" and deal with it. 1331 * - maybe call process_cgi() 1332 * - check for ~user and call user_transform() if so 1333 * - if the length > 1, check for trailing slash. if so, 1334 * add the index.html file 1335 * - if the length is 1, return the index.html file 1336 * - disallow anything ending up with a file starting 1337 * at "/" or having ".." in it. 1338 * - anything else is a really weird internal error 1339 * - returns malloced file to serve, if unhandled 1340 */ 1341 int 1342 transform_request(http_req *request, int *isindex) 1343 { 1344 char *file, *newfile = NULL; 1345 size_t len; 1346 1347 file = NULL; 1348 *isindex = 0; 1349 debug((DEBUG_FAT, "tf_req: file %s", request->hr_file)); 1350 fix_url_percent(request); 1351 if (check_virtual(request)) { 1352 goto bad_done; 1353 } 1354 file = request->hr_file; 1355 1356 if (file[0] != '/') { 1357 (void)http_error(404, request, "unknown URL"); 1358 goto bad_done; 1359 } 1360 1361 check_bzredirect(request); 1362 1363 if (rflag) { 1364 int to_indexhtml = 0; 1365 1366 #define TOP_PAGE(x) (strcmp((x), "/") == 0 || \ 1367 strcmp((x) + 1, index_html) == 0 || \ 1368 strcmp((x) + 1, "favicon.ico") == 0) 1369 1370 debug((DEBUG_EXPLODING, "checking rflag")); 1371 /* 1372 * first check that this path isn't allowed via .bzdirect file, 1373 * and then check referrer; make sure that people come via the 1374 * real name... otherwise if we aren't looking at / or 1375 * /index.html, redirect... we also special case favicon.ico. 1376 */ 1377 if (check_direct_access(request)) 1378 /* nothing */; 1379 else if (request->hr_referrer) { 1380 const char *r = request->hr_referrer; 1381 1382 debug((DEBUG_FAT, 1383 "checking referrer \"%s\" vs myname %s", r, myname)); 1384 if (strncmp(r, "http://", 7) != 0 || 1385 (strncasecmp(r + 7, myname, strlen(myname)) != 0 && 1386 !TOP_PAGE(file))) 1387 to_indexhtml = 1; 1388 } else { 1389 const char *h = request->hr_host; 1390 1391 debug((DEBUG_FAT, "url has no referrer at all")); 1392 /* if there's no referrer, let / or /index.html past */ 1393 if (!TOP_PAGE(file) || 1394 (h && strncasecmp(h, myname, strlen(myname)) != 0)) 1395 to_indexhtml = 1; 1396 } 1397 1398 if (to_indexhtml) { 1399 char *slashindexhtml; 1400 1401 if (asprintf(&slashindexhtml, "/%s", index_html) < 0) 1402 error(1, "asprintf"); 1403 debug((DEBUG_FAT, "rflag: redirecting %s to %s", file, slashindexhtml)); 1404 handle_redirect(request, slashindexhtml, 0); 1405 free(slashindexhtml); 1406 return 0; 1407 } 1408 } 1409 1410 len = strlen(file); 1411 if (0) { 1412 #ifndef NO_USER_SUPPORT 1413 } else if (len > 1 && uflag && file[1] == '~') { 1414 if (file[2] == '\0') { 1415 (void)http_error(404, request, "missing username"); 1416 goto bad_done; 1417 } 1418 if (strchr(file + 2, '/') == NULL) { 1419 handle_redirect(request, NULL, 0); 1420 return 0; 1421 } 1422 debug((DEBUG_FAT, "calling user_transform")); 1423 1424 return (user_transform(request, isindex)); 1425 #endif /* NO_USER_SUPPORT */ 1426 } else if (len > 1) { 1427 debug((DEBUG_FAT, "file[len-1] == %c", file[len-1])); 1428 if (file[len-1] == '/') { /* append index.html */ 1429 *isindex = 1; 1430 debug((DEBUG_FAT, "appending index.html")); 1431 newfile = bozomalloc(len + strlen(index_html) + 1); 1432 strcpy(newfile, file + 1); 1433 strcat(newfile, index_html); 1434 } else 1435 newfile = bozostrdup(file + 1); 1436 } else if (len == 1) { 1437 debug((DEBUG_EXPLODING, "tf_req: len == 1")); 1438 newfile = bozostrdup(index_html); 1439 *isindex = 1; 1440 } else { /* len == 0 ? */ 1441 (void)http_error(500, request, "request->hr_file is nul?"); 1442 goto bad_done; 1443 } 1444 1445 if (newfile == NULL) { 1446 (void)http_error(500, request, "internal failure"); 1447 goto bad_done; 1448 } 1449 1450 /* 1451 * look for "http://myname/" and deal with it as necessary. 1452 */ 1453 1454 /* 1455 * stop traversing outside our domain 1456 * 1457 * XXX true security only comes from our parent using chroot(2) 1458 * before execve(2)'ing us. or our own built in chroot(2) support. 1459 */ 1460 if (*newfile == '/' || strcmp(newfile, "..") == 0 || 1461 strstr(newfile, "/..") || strstr(newfile, "../")) { 1462 (void)http_error(403, request, "illegal request"); 1463 goto bad_done; 1464 } 1465 1466 if (auth_check(request, newfile)) 1467 goto bad_done; 1468 1469 if (strlen(newfile)) { 1470 free(request->hr_file); 1471 request->hr_file = newfile; 1472 } 1473 1474 if (process_cgi(request)) 1475 return 0; 1476 1477 debug((DEBUG_FAT, "transform_request set: %s", newfile)); 1478 return 1; 1479 bad_done: 1480 debug((DEBUG_FAT, "transform_request returning: 0")); 1481 if (newfile) 1482 free(newfile); 1483 return 0; 1484 } 1485 1486 /* 1487 * do automatic redirection -- if there are query parameters for the URL 1488 * we will tack these on to the new (redirected) URL. 1489 */ 1490 static void 1491 handle_redirect(http_req *request, const char *url, int absolute) 1492 { 1493 char *urlbuf; 1494 char portbuf[20]; 1495 int query = 0; 1496 1497 if (url == NULL) { 1498 if (asprintf(&urlbuf, "/%s/", request->hr_file) < 0) 1499 error(1, "asprintf"); 1500 url = urlbuf; 1501 } else 1502 urlbuf = NULL; 1503 1504 if (request->hr_query && strlen(request->hr_query)) { 1505 query = 1; 1506 } 1507 1508 if (request->hr_serverport && strcmp(request->hr_serverport, "80") != 0) 1509 snprintf(portbuf, sizeof(portbuf), ":%s", 1510 request->hr_serverport); 1511 else 1512 portbuf[0] = '\0'; 1513 warning("redirecting %s%s%s", myname, portbuf, url); 1514 debug((DEBUG_FAT, "redirecting %s", url)); 1515 bozoprintf("%s 301 Document Moved\r\n", request->hr_proto); 1516 if (request->hr_proto != http_09) 1517 print_header(request, NULL, "text/html", NULL); 1518 if (request->hr_proto != http_09) { 1519 bozoprintf("Location: http://"); 1520 if (absolute == 0) 1521 bozoprintf("%s%s", myname, portbuf); 1522 if (query) { 1523 bozoprintf("%s?%s\r\n", url, request->hr_query); 1524 } else { 1525 bozoprintf("%s\r\n", url); 1526 } 1527 } 1528 bozoprintf("\r\n"); 1529 if (request->hr_method == HTTP_HEAD) 1530 goto head; 1531 bozoprintf("<html><head><title>Document Moved</title></head>\n"); 1532 bozoprintf("<body><h1>Document Moved</h1>\n"); 1533 bozoprintf("This document had moved <a href=\"http://"); 1534 if (query) { 1535 if (absolute) 1536 bozoprintf("%s?%s", url, request->hr_query); 1537 else 1538 bozoprintf("%s%s%s?%s", myname, portbuf, url, request->hr_query); 1539 } else { 1540 if (absolute) 1541 bozoprintf("%s", url); 1542 else 1543 bozoprintf("%s%s%s", myname, portbuf, url); 1544 } 1545 bozoprintf("\">here</a>\n"); 1546 bozoprintf("</body></html>\n"); 1547 head: 1548 bozoflush(stdout); 1549 if (urlbuf) 1550 free(urlbuf); 1551 } 1552 1553 /* generic header printing routine */ 1554 void 1555 print_header(http_req *request, struct stat *sbp, const char *type, 1556 const char *encoding) 1557 { 1558 off_t len; 1559 1560 bozoprintf("Date: %s\r\n", http_date()); 1561 bozoprintf("Server: %s\r\n", server_software); 1562 bozoprintf("Accept-Ranges: bytes\r\n"); 1563 if (sbp) { 1564 char filedate[40]; 1565 struct tm *tm; 1566 1567 tm = gmtime(&sbp->st_mtime); 1568 strftime(filedate, sizeof filedate, 1569 "%a, %d %b %Y %H:%M:%S GMT", tm); 1570 bozoprintf("Last-Modified: %s\r\n", filedate); 1571 } 1572 if (type && *type) 1573 bozoprintf("Content-Type: %s\r\n", type); 1574 if (encoding && *encoding) 1575 bozoprintf("Content-Encoding: %s\r\n", encoding); 1576 if (sbp) { 1577 if (request->hr_have_range) { 1578 len = request->hr_last_byte_pos - request->hr_first_byte_pos +1; 1579 bozoprintf("Content-Range: bytes %qd-%qd/%qd\r\n", 1580 (long long) request->hr_first_byte_pos, 1581 (long long) request->hr_last_byte_pos, 1582 (long long) sbp->st_size); 1583 } 1584 else 1585 len = sbp->st_size; 1586 bozoprintf("Content-Length: %qd\r\n", (long long)len); 1587 } 1588 if (request && request->hr_proto == http_11) 1589 bozoprintf("Connection: close\r\n"); 1590 bozoflush(stdout); 1591 } 1592 1593 /* this escape HTML tags */ 1594 static void 1595 escape_html(http_req *request) 1596 { 1597 int i, j; 1598 char *url = request->hr_file, *tmp; 1599 1600 for (i = 0, j = 0; url[i]; i++) { 1601 switch (url[i]) { 1602 case '<': 1603 case '>': 1604 j += 4; 1605 break; 1606 case '&': 1607 j += 5; 1608 break; 1609 } 1610 } 1611 1612 if (j == 0) 1613 return; 1614 1615 if ((tmp = (char *) malloc(strlen(url) + j)) == 0) 1616 /* 1617 * ouch, but we are only called from an error context, and 1618 * most paths here come from malloc(3) failures anyway... 1619 * we could completely punt and just exit, but isn't returning 1620 * an not-quite-correct error better than nothing at all? 1621 */ 1622 return; 1623 1624 for (i = 0, j = 0; url[i]; i++) { 1625 switch (url[i]) { 1626 case '<': 1627 memcpy(tmp + j, "<", 4); 1628 j += 4; 1629 break; 1630 case '>': 1631 memcpy(tmp + j, ">", 4); 1632 j += 4; 1633 break; 1634 case '&': 1635 memcpy(tmp + j, "&", 5); 1636 j += 5; 1637 break; 1638 default: 1639 tmp[j++] = url[i]; 1640 } 1641 } 1642 tmp[j] = 0; 1643 1644 free(request->hr_file); 1645 request->hr_file = tmp; 1646 } 1647 1648 /* this fixes the %HH hack that RFC2396 requires. */ 1649 static void 1650 fix_url_percent(http_req *request) 1651 { 1652 char *s, *t, buf[3], *url; 1653 char *end; /* if end is not-zero, we don't translate beyond that */ 1654 1655 url = request->hr_file; 1656 1657 end = url + strlen(url); 1658 1659 /* fast forward to the first % */ 1660 if ((s = strchr(url, '%')) == NULL) 1661 return; 1662 1663 t = s; 1664 do { 1665 if (end && s >= end) { 1666 debug((DEBUG_EXPLODING, "fu_%%: past end, filling out..")); 1667 while (*s) 1668 *t++ = *s++; 1669 break; 1670 } 1671 debug((DEBUG_EXPLODING, "fu_%%: got s == %%, s[1]s[2] == %c%c", 1672 s[1], s[2])); 1673 if (s[1] == '\0' || s[2] == '\0') { 1674 (void)http_error(400, request, 1675 "percent hack missing two chars afterwards"); 1676 goto copy_rest; 1677 } 1678 if (s[1] == '0' && s[2] == '0') { 1679 (void)http_error(404, request, "percent hack was %00"); 1680 goto copy_rest; 1681 } 1682 if (s[1] == '2' && s[2] == 'f') { 1683 (void)http_error(404, request, "percent hack was %2f (/)"); 1684 goto copy_rest; 1685 } 1686 1687 buf[0] = *++s; 1688 buf[1] = *++s; 1689 buf[2] = '\0'; 1690 s++; 1691 *t = (char)strtol(buf, NULL, 16); 1692 debug((DEBUG_EXPLODING, "fu_%%: strtol put '%02x' into *t", *t)); 1693 if (*t++ == '\0') { 1694 (void)http_error(400, request, "percent hack got a 0 back"); 1695 goto copy_rest; 1696 } 1697 1698 while (*s && *s != '%') { 1699 if (end && s >= end) 1700 break; 1701 *t++ = *s++; 1702 } 1703 } while (*s); 1704 copy_rest: 1705 while (*s) { 1706 if (s >= end) 1707 break; 1708 *t++ = *s++; 1709 } 1710 *t = '\0'; 1711 debug((DEBUG_FAT, "fix_url_percent returns %s in url", request->hr_file)); 1712 } 1713 1714 /* 1715 * process each type of HTTP method, setting this HTTP requests 1716 # method type. 1717 */ 1718 static struct method_map { 1719 const char *name; 1720 int type; 1721 } method_map[] = { 1722 { "GET", HTTP_GET, }, 1723 { "POST", HTTP_POST, }, 1724 { "HEAD", HTTP_HEAD, }, 1725 #if 0 /* other non-required http/1.1 methods */ 1726 { "OPTIONS", HTTP_OPTIONS, }, 1727 { "PUT", HTTP_PUT, }, 1728 { "DELETE", HTTP_DELETE, }, 1729 { "TRACE", HTTP_TRACE, }, 1730 { "CONNECT", HTTP_CONNECT, }, 1731 #endif 1732 { NULL, 0, }, 1733 }; 1734 1735 static int 1736 process_method(http_req *request, const char *method) 1737 { 1738 struct method_map *mmp; 1739 1740 if (request->hr_proto == http_11) 1741 request->hr_allow = "GET, HEAD, POST"; 1742 1743 for (mmp = method_map; mmp->name; mmp++) 1744 if (strcasecmp(method, mmp->name) == 0) { 1745 request->hr_method = mmp->type; 1746 request->hr_methodstr = mmp->name; 1747 return 0; 1748 } 1749 1750 return http_error(404, request, "unknown method"); 1751 } 1752 1753 /* 1754 * as the prototype string is not constant (eg, "HTTP/1.1" is equivalent 1755 * to "HTTP/001.01"), we MUST parse this. 1756 */ 1757 static int 1758 process_proto(http_req *request, const char *proto) 1759 { 1760 char majorstr[16], *minorstr; 1761 int majorint, minorint; 1762 1763 if (proto == NULL) { 1764 got_proto_09: 1765 request->hr_proto = http_09; 1766 debug((DEBUG_FAT, "request %s is http/0.9", request->hr_file)); 1767 return 0; 1768 } 1769 1770 if (strncasecmp(proto, "HTTP/", 5) != 0) 1771 goto bad; 1772 strncpy(majorstr, proto + 5, sizeof majorstr); 1773 majorstr[sizeof(majorstr)-1] = 0; 1774 minorstr = strchr(majorstr, '.'); 1775 if (minorstr == NULL) 1776 goto bad; 1777 *minorstr++ = 0; 1778 1779 majorint = atoi(majorstr); 1780 minorint = atoi(minorstr); 1781 1782 switch (majorint) { 1783 case 0: 1784 if (minorint != 9) 1785 break; 1786 goto got_proto_09; 1787 case 1: 1788 if (minorint == 0) 1789 request->hr_proto = http_10; 1790 else if (minorint == 1) 1791 request->hr_proto = http_11; 1792 else 1793 break; 1794 1795 debug((DEBUG_FAT, "request %s is %s", request->hr_file, 1796 request->hr_proto)); 1797 SIMPLEQ_INIT(&request->hr_headers); 1798 request->hr_nheaders = 0; 1799 return 0; 1800 } 1801 bad: 1802 return http_error(404, NULL, "unknown prototype"); 1803 } 1804 1805 #ifdef DEBUG 1806 void 1807 debug__(int level, const char *fmt, ...) 1808 { 1809 va_list ap; 1810 int savederrno; 1811 1812 /* only log if the level is low enough */ 1813 if (dflag < level) 1814 return; 1815 1816 savederrno = errno; 1817 va_start(ap, fmt); 1818 if (sflag) { 1819 vfprintf(stderr, fmt, ap); 1820 fputs("\n", stderr); 1821 } else 1822 vsyslog(LOG_DEBUG, fmt, ap); 1823 va_end(ap); 1824 errno = savederrno; 1825 } 1826 #endif /* DEBUG */ 1827 1828 /* these are like warn() and err(), except for syslog not stderr */ 1829 void 1830 warning(const char *fmt, ...) 1831 { 1832 va_list ap; 1833 1834 va_start(ap, fmt); 1835 if (sflag || isatty(STDERR_FILENO)) { 1836 //fputs("warning: ", stderr); 1837 vfprintf(stderr, fmt, ap); 1838 fputs("\n", stderr); 1839 } else 1840 vsyslog(LOG_INFO, fmt, ap); 1841 va_end(ap); 1842 } 1843 1844 void 1845 error(int code, const char *fmt, ...) 1846 { 1847 va_list ap; 1848 1849 va_start(ap, fmt); 1850 if (sflag || isatty(STDERR_FILENO)) { 1851 //fputs("error: ", stderr); 1852 vfprintf(stderr, fmt, ap); 1853 fputs("\n", stderr); 1854 } else 1855 vsyslog(LOG_ERR, fmt, ap); 1856 va_end(ap); 1857 exit(code); 1858 } 1859 1860 /* the follow functions and variables are used in handling HTTP errors */ 1861 /* ARGSUSED */ 1862 int 1863 http_error(int code, http_req *request, const char *msg) 1864 { 1865 static char buf[BUFSIZ]; 1866 char portbuf[20]; 1867 const char *header = http_errors_short(code); 1868 const char *reason = http_errors_long(code); 1869 const char *proto = (request && request->hr_proto) ? request->hr_proto : http_11; 1870 int size; 1871 1872 debug((DEBUG_FAT, "http_error %d: %s", code, msg)); 1873 if (header == NULL || reason == NULL) { 1874 error(1, "http_error() failed (short = %p, long = %p)", 1875 header, reason); 1876 return code; 1877 } 1878 1879 if (request && request->hr_serverport && 1880 strcmp(request->hr_serverport, "80") != 0) 1881 snprintf(portbuf, sizeof(portbuf), ":%s", request->hr_serverport); 1882 else 1883 portbuf[0] = '\0'; 1884 1885 if (request && request->hr_file) { 1886 escape_html(request); 1887 size = snprintf(buf, sizeof buf, 1888 "<html><head><title>%s</title></head>\n" 1889 "<body><h1>%s</h1>\n" 1890 "%s: <pre>%s</pre>\n" 1891 "<hr><address><a href=\"http://%s%s/\">%s%s</a></address>\n" 1892 "</body></html>\n", 1893 header, header, request->hr_file, reason, 1894 myname, portbuf, myname, portbuf); 1895 if (size >= (int)sizeof buf) { 1896 warning("http_error buffer too small, truncated"); 1897 size = (int)sizeof buf; 1898 } 1899 } else 1900 size = 0; 1901 1902 bozoprintf("%s %s\r\n", proto, header); 1903 auth_check_401(request, code); 1904 1905 bozoprintf("Content-Type: text/html\r\n"); 1906 bozoprintf("Content-Length: %d\r\n", size); 1907 bozoprintf("Server: %s\r\n", server_software); 1908 if (request && request->hr_allow) 1909 bozoprintf("Allow: %s\r\n", request->hr_allow); 1910 bozoprintf("\r\n"); 1911 if (size) 1912 bozoprintf("%s", buf); 1913 bozoflush(stdout); 1914 1915 return code; 1916 } 1917 1918 /* short map between error code, and short/long messages */ 1919 static struct errors_map { 1920 int code; /* HTTP return code */ 1921 const char *shortmsg; /* short version of message */ 1922 const char *longmsg; /* long version of message */ 1923 } errors_map[] = { 1924 { 400, "400 Bad Request", "The request was not valid", }, 1925 { 401, "401 Unauthorized", "No authorization", }, 1926 { 403, "403 Forbidden", "Access to this item has been denied",}, 1927 { 404, "404 Not Found", "This item has not been found", }, 1928 { 408, "408 Request Timeout", "This request took too long", }, 1929 { 417, "417 Expectation Failed","Expectations not available", }, 1930 { 500, "500 Internal Error", "An error occured on the server", }, 1931 { 501, "501 Not Implemented", "This request is not available", }, 1932 { 0, NULL, NULL, }, 1933 }; 1934 1935 static const char *help = "DANGER! WILL ROBINSON! DANGER!"; 1936 1937 static const char * 1938 http_errors_short(int code) 1939 { 1940 struct errors_map *ep; 1941 1942 for (ep = errors_map; ep->code; ep++) 1943 if (ep->code == code) 1944 return (ep->shortmsg); 1945 return (help); 1946 } 1947 1948 static const char * 1949 http_errors_long(int code) 1950 { 1951 struct errors_map *ep; 1952 1953 for (ep = errors_map; ep->code; ep++) 1954 if (ep->code == code) 1955 return (ep->longmsg); 1956 return (help); 1957 } 1958 1959 /* Below are various modified libc functions */ 1960 1961 /* 1962 * returns -1 in lenp if the string ran out before finding a delimiter, 1963 * but is otherwise the same as strsep. Note that the length must be 1964 * correctly passed in. 1965 */ 1966 char * 1967 bozostrnsep(char **strp, const char *delim, ssize_t *lenp) 1968 { 1969 char *s; 1970 const char *spanp; 1971 int c, sc; 1972 char *tok; 1973 1974 if ((s = *strp) == NULL) 1975 return (NULL); 1976 for (tok = s;;) { 1977 if (lenp && --(*lenp) == -1) 1978 return (NULL); 1979 c = *s++; 1980 spanp = delim; 1981 do { 1982 if ((sc = *spanp++) == c) { 1983 if (c == 0) 1984 s = NULL; 1985 else 1986 s[-1] = '\0'; 1987 *strp = s; 1988 return (tok); 1989 } 1990 } while (sc != 0); 1991 } 1992 /* NOTREACHED */ 1993 } 1994 1995 /* 1996 * inspired by fgetln(3), but works for fd's. should work identically 1997 * except it, however, does *not* return the newline, and it does nul 1998 * terminate the string. 1999 */ 2000 char * 2001 bozodgetln(int fd, ssize_t *lenp, ssize_t (*readfn)(int, void *, size_t)) 2002 { 2003 static char *buffer; 2004 static ssize_t buflen = 0; 2005 ssize_t len; 2006 int got_cr = 0; 2007 char c, *nbuffer; 2008 2009 /* initialise */ 2010 if (buflen == 0) { 2011 buflen = 128; /* should be plenty for most requests */ 2012 buffer = malloc(buflen); 2013 if (buffer == NULL) { 2014 buflen = 0; 2015 return NULL; 2016 } 2017 } 2018 len = 0; 2019 2020 /* 2021 * we *have* to read one byte at a time, to not break cgi 2022 * programs (for we pass stdin off to them). could fix this 2023 * by becoming a fd-passing program instead of just exec'ing 2024 * the program 2025 */ 2026 for (; readfn(fd, &c, 1) == 1; ) { 2027 debug((DEBUG_EXPLODING, "bozodgetln read %c", c)); 2028 2029 if (len >= buflen - 1) { 2030 buflen *= 2; 2031 debug((DEBUG_EXPLODING, "bozodgetln: " 2032 "reallocating buffer to buflen %zu", buflen)); 2033 nbuffer = bozorealloc(buffer, buflen); 2034 buffer = nbuffer; 2035 } 2036 2037 buffer[len++] = c; 2038 if (c == '\r') { 2039 got_cr = 1; 2040 continue; 2041 } else if (c == '\n') { 2042 /* 2043 * HTTP/1.1 spec says to ignore CR and treat 2044 * LF as the real line terminator. even though 2045 * the same spec defines CRLF as the line 2046 * terminator, it is recommended in section 19.3 2047 * to do the LF trick for tolerance. 2048 */ 2049 if (got_cr) 2050 len -= 2; 2051 else 2052 len -= 1; 2053 break; 2054 } 2055 2056 } 2057 buffer[len] = '\0'; 2058 debug((DEBUG_OBESE, "bozodgetln returns: ``%s'' with len %d", 2059 buffer, len)); 2060 *lenp = len; 2061 return (buffer); 2062 } 2063 2064 void * 2065 bozorealloc(void *ptr, size_t size) 2066 { 2067 void *p; 2068 2069 p = realloc(ptr, size); 2070 if (p == NULL) { 2071 (void)http_error(500, NULL, "memory allocation failure"); 2072 exit(1); 2073 } 2074 return (p); 2075 } 2076 2077 void * 2078 bozomalloc(size_t size) 2079 { 2080 void *p; 2081 2082 p = malloc(size); 2083 if (p == NULL) { 2084 (void)http_error(500, NULL, "memory allocation failure"); 2085 exit(1); 2086 } 2087 return (p); 2088 } 2089 2090 char * 2091 bozostrdup(const char *str) 2092 { 2093 char *p; 2094 2095 p = strdup(str); 2096 if (p == NULL) { 2097 (void)http_error(500, NULL, "memory allocation failure"); 2098 exit(1); 2099 } 2100 return (p); 2101 } 2102