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