1 /* $NetBSD: bozohttpd.c,v 1.4 2007/12/15 16:32:05 perry Exp $ */ 2 3 /* $eterna: bozohttpd.c,v 1.137 2006/05/17 08:37:36 mrg Exp $ */ 4 5 /* 6 * Copyright (c) 1997-2006 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 * 100 * - 14.9: we aren't a cache. 101 * 102 * - 14.15: content-md5 would be nice... 103 * 104 * - 14.24/14.26/14.27: be nice to support this... 105 * 106 * - 14.44: not sure about this Vary: header. ignore it for now. 107 */ 108 109 #ifndef INDEX_HTML 110 #define INDEX_HTML "index.html" 111 #endif 112 #ifndef SERVER_SOFTWARE 113 #define SERVER_SOFTWARE "bozohttpd/20060517" 114 #endif 115 #ifndef DIRECT_ACCESS_FILE 116 #define DIRECT_ACCESS_FILE ".bzdirect" 117 #endif 118 #ifndef REDIRECT_FILE 119 #define REDIRECT_FILE ".bzredirect" 120 #endif 121 #ifndef ABSREDIRECT_FILE 122 #define ABSREDIRECT_FILE ".bzabsredirect" 123 #endif 124 125 /* 126 * And so it begins .. 127 */ 128 129 #include <sys/cdefs.h> 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 **); 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 'i': 379 case 'I': 380 error(1, "Daemon mode is not enabled"); 381 /* NOTREACHED */ 382 #endif /* NO_DAEMON_MODE */ 383 384 #ifndef NO_CGIBIN_SUPPORT 385 case 'c': 386 set_cgibin(optarg); 387 break; 388 389 case 'C': 390 #ifndef NO_DYNAMIC_CONTENT 391 /* make sure there's two arguments */ 392 if (argc - optind < 1) 393 usage(); 394 add_content_map_cgi(optarg, argv[optind++]); 395 break; 396 #else 397 error(1, "dynmic CGI handler support is not enabled"); 398 /* NOTREACHED */ 399 #endif /* NO_DYNAMIC_CONTENT */ 400 401 #else 402 case 'c': 403 case 'C': 404 error(1, "CGI is not enabled"); 405 /* NOTREACHED */ 406 #endif /* NO_CGIBIN_SUPPORT */ 407 408 case 'd': 409 dflag++; 410 #ifndef DEBUG 411 if (dflag == 1) 412 warning("Debugging is not enabled"); 413 #endif /* !DEBUG */ 414 break; 415 416 #ifndef NO_USER_SUPPORT 417 case 'p': 418 public_html = optarg; 419 break; 420 421 case 't': 422 tflag = optarg; 423 break; 424 425 case 'u': 426 uflag = 1; 427 break; 428 #else 429 case 'u': 430 case 'p': 431 error(1, "User support is not enabled"); 432 /* NOTREACHED */ 433 #endif /* NO_USER_SUPPORT */ 434 435 #ifndef NO_DIRINDEX_SUPPORT 436 case 'H': 437 Hflag = 1; 438 break; 439 440 case 'X': 441 Xflag = 1; 442 break; 443 444 #else 445 case 'H': 446 case 'X': 447 error(1, "directory indexing is not enabled"); 448 /* NOTREACHED */ 449 #endif /* NO_DIRINDEX_SUPPORT */ 450 451 default: 452 usage(); 453 /* NOTREACHED */ 454 } 455 } 456 argc -= optind; 457 argv += optind; 458 459 if (argc == 1) { 460 myname = bozomalloc(MAXHOSTNAMELEN+1); 461 /* XXX we do not check for FQDN here */ 462 if (gethostname(myname, MAXHOSTNAMELEN+1) < 0) 463 error(1, "gethostname"); 464 myname[MAXHOSTNAMELEN] = '\0'; 465 } else if (argc == 2) 466 myname = argv[1]; 467 else 468 usage(); 469 470 slashdir = argv[0]; 471 debug((DEBUG_OBESE, "myname is %s, slashdir is %s", myname, slashdir)); 472 473 /* 474 * initialise ssl and daemon mode if necessary. 475 */ 476 ssl_init(); 477 daemon_init(); 478 479 /* 480 * prevent info leakage between different compartments. 481 * some PATH values in the environment would be invalided 482 * by chroot. cross-user settings might result in undesirable 483 * effects. 484 */ 485 if ((tflag != NULL || Uflag != NULL) && !eflag) { 486 cleanenv[0] = NULL; 487 environ = cleanenv; 488 } 489 490 /* 491 * look up user/group information. 492 */ 493 if (Uflag != NULL) { 494 struct passwd *pw; 495 496 if ((pw = getpwnam(Uflag)) == NULL) 497 error(1, "getpwnam(%s): %s", Uflag, strerror(errno)); 498 if (initgroups(pw->pw_name, pw->pw_gid) == -1) 499 error(1, "initgroups: %s", strerror(errno)); 500 if (setgid(pw->pw_gid) == -1) 501 error(1, "setgid(%u): %s", pw->pw_gid, strerror(errno)); 502 uid = pw->pw_uid; 503 } 504 505 /* 506 * handle chroot. 507 */ 508 if (tflag != NULL) { 509 if (chdir(tflag) == -1) 510 error(1, "chdir(%s): %s", tflag, strerror(errno)); 511 if (chroot(tflag) == -1) 512 error(1, "chroot(%s): %s", tflag, strerror(errno)); 513 } 514 515 if (Uflag != NULL) 516 if (setuid(uid) == -1) 517 error(1, "setuid(%d): %s", uid, strerror(errno)); 518 519 /* 520 * be sane, don't start serving up files from a 521 * hierarchy we don't have permission to get to. 522 */ 523 if (tflag != NULL) 524 if (chdir("/") == -1) 525 error(1, "chdir /: %s", strerror(errno)); 526 527 /* 528 * read and process the HTTP request. 529 */ 530 do { 531 http_request = read_request(); 532 if (http_request) { 533 process_request(http_request); 534 return (0); 535 } 536 } while (bflag); 537 538 return (0); 539 } 540 541 char * 542 http_date(void) 543 { 544 static char date[40]; 545 struct tm *tm; 546 time_t now; 547 548 /* Sun, 06 Nov 1994 08:49:37 GMT */ 549 now = time(NULL); 550 tm = gmtime(&now); /* HTTP/1.1 spec rev 06 sez GMT only */ 551 strftime(date, sizeof date, "%a, %d %b %Y %H:%M:%S GMT", tm); 552 return date; 553 } 554 555 /* 556 * convert "in" into the three parts of a request (first line) 557 */ 558 static void 559 parse_request(char *in, char **method, char **url, char **proto) 560 { 561 ssize_t len; 562 char *val; 563 564 *method = *url = *proto = NULL; /* set them up */ 565 566 len = (ssize_t)strlen(in); 567 val = strnsep(&in, " \t\n\r", &len); 568 if (len < 1 || val == NULL) 569 return; 570 *method = val; 571 while (*in == ' ' || *in == '\t') 572 in++; 573 val = strnsep(&in, " \t\n\r", &len); 574 if (len < 1) { 575 if (len == 0) 576 *url = val; 577 else 578 *url = in; 579 return; 580 } 581 *url = val; 582 if (in) { 583 while (*in && (*in == ' ' || *in == '\t')) 584 in++; 585 if (*in) 586 *proto = in; 587 } 588 } 589 590 /* 591 * send a HTTP/1.1 408 response if we timeout. 592 */ 593 /* ARGSUSED */ 594 static void 595 alarmer(int sig) 596 { 597 alarmhit = 1; 598 } 599 600 /* 601 * This function reads a http request from stdin, returning a pointer to a 602 * http_req structure, describing the request. 603 */ 604 static http_req * 605 read_request(void) 606 { 607 struct sigaction sa; 608 char *str, *val, *method, *url, *proto; 609 char *host, *addr, *port; 610 char bufport[10]; 611 char hbuf[NI_MAXHOST], abuf[NI_MAXHOST]; 612 struct sockaddr_storage ss; 613 ssize_t len; 614 int line = 0; 615 socklen_t slen; 616 http_req *request; 617 618 /* 619 * if we're in daemon mode, daemon_fork() will return here once 620 * for each child, then we can setup SSL. 621 */ 622 daemon_fork(); 623 ssl_accept(); 624 625 request = bozomalloc(sizeof *request); 626 memset(request, 0, sizeof *request); 627 request->hr_allow = request->hr_host = NULL; 628 request->hr_content_type = request->hr_content_length = NULL; 629 630 slen = sizeof(ss); 631 if (getpeername(0, (struct sockaddr *)&ss, &slen) < 0) 632 host = addr = NULL; 633 else { 634 if (getnameinfo((struct sockaddr *)&ss, slen, 635 abuf, sizeof abuf, NULL, 0, NI_NUMERICHOST) == 0) 636 addr = abuf; 637 else 638 addr = NULL; 639 if (nflag == 0 && getnameinfo((struct sockaddr *)&ss, slen, 640 hbuf, sizeof hbuf, NULL, 0, 0) == 0) 641 host = hbuf; 642 else 643 host = NULL; 644 } 645 if (host != NULL) 646 request->hr_remotehost = bozostrdup(host); 647 if (addr != NULL) 648 request->hr_remoteaddr = bozostrdup(addr); 649 slen = sizeof(ss); 650 if (getsockname(0, (struct sockaddr *)&ss, &slen) < 0) 651 port = NULL; 652 else { 653 if (getnameinfo((struct sockaddr *)&ss, slen, NULL, 0, 654 bufport, sizeof bufport, NI_NUMERICSERV) == 0) 655 port = bufport; 656 else 657 port = NULL; 658 } 659 if (port != NULL) 660 request->hr_serverport = bozostrdup(port); 661 662 /* 663 * setup a timer to make sure the request is not hung 664 */ 665 sa.sa_handler = alarmer; 666 sigemptyset(&sa.sa_mask); 667 sigaddset(&sa.sa_mask, SIGALRM); 668 sa.sa_flags = 0; 669 sigaction(SIGALRM, &sa, NULL); /* XXX */ 670 671 alarm(MAX_WAIT_TIME); 672 while ((str = dgetln(STDIN_FILENO, &len, bozoread)) != NULL) { 673 alarm(0); 674 if (alarmhit) 675 http_error(408, NULL, "request timed out"); 676 line++; 677 678 if (line == 1) { 679 str = bozostrdup(str); /* we use this copy */ 680 681 if (len < 1) 682 http_error(404, NULL, "null method"); 683 warning("got request ``%s'' from host %s to port %s", 684 str, 685 host ? host : addr ? addr : "<local>", 686 port ? port : "<stdin>"); 687 debug((DEBUG_FAT, "read_req, getting request: ``%s''", 688 str)); 689 690 parse_request(str, &method, &url, &proto); 691 if (method == NULL) 692 http_error(404, NULL, "null method"); 693 if (url == NULL) 694 http_error(404, NULL, "null url"); 695 696 /* 697 * note that we parse the proto first, so that we 698 * can more properly parse the method and the url. 699 */ 700 request->hr_url = url; 701 process_proto(request, proto); 702 process_method(request, method); 703 704 /* http/0.9 has no header processing */ 705 if (request->hr_proto == http_09) 706 break; 707 } else { /* incoming headers */ 708 struct headers *hdr; 709 710 if (*str == '\0') 711 break; 712 713 val = strnsep(&str, ":", &len); 714 debug((DEBUG_EXPLODING, 715 "read_req2: after strnsep: str ``%s'' val ``%s''", 716 str, val)); 717 if (val == NULL || len == -1) 718 http_error(404, request, "no header"); 719 while (*str == ' ' || *str == '\t') 720 len--, str++; 721 722 if (auth_check_headers(request, val, str, len)) 723 goto next_header; 724 725 hdr = addmerge_header(request, val, str, len); 726 727 if (strcasecmp(hdr->h_header, "content-type") == 0) 728 request->hr_content_type = hdr->h_value; 729 else if (strcasecmp(hdr->h_header, "content-length") == 0) 730 request->hr_content_length = hdr->h_value; 731 else if (strcasecmp(hdr->h_header, "host") == 0) 732 request->hr_host = hdr->h_value; 733 /* HTTP/1.1 rev06 draft spec: 14.20 */ 734 else if (strcasecmp(hdr->h_header, "expect") == 0) 735 http_error(417, request, "we don't support Expect:"); 736 else if (strcasecmp(hdr->h_header, "referrer") == 0 || 737 strcasecmp(hdr->h_header, "referer") == 0) 738 request->hr_referrer = hdr->h_value; 739 740 debug((DEBUG_FAT, "adding header %s: %s", 741 hdr->h_header, hdr->h_value)); 742 } 743 next_header: 744 alarm(MAX_WAIT_TIME); 745 } 746 747 /* now, clear it all out */ 748 alarm(0); 749 signal(SIGALRM, SIG_DFL); 750 751 /* RFC1945, 8.3 */ 752 if (request->hr_method == HTTP_POST && request->hr_content_length == NULL) 753 http_error(400, request, "missing content length"); 754 755 /* HTTP/1.1 draft rev-06, 14.23 & 19.6.1.1 */ 756 if (request->hr_proto == http_11 && request->hr_host == NULL) 757 http_error(400, request, "missing Host header"); 758 759 debug((DEBUG_FAT, "read_request returns url %s in request", request->hr_url)); 760 return (request); 761 } 762 763 /* 764 * add or merge this header (val: str) into the requests list 765 */ 766 static struct headers * 767 addmerge_header(http_req *request, char *val, char *str, ssize_t len) 768 { 769 struct headers *hdr; 770 static char space[2] = { ' ', 0 }; 771 772 /* do we exist already? */ 773 SIMPLEQ_FOREACH(hdr, &request->hr_headers, h_next) { 774 if (strcasecmp(val, hdr->h_header) == 0) 775 break; 776 } 777 778 if (hdr) { 779 /* yup, merge it in */ 780 if (hdr->h_value == space) 781 hdr->h_value = bozostrdup(str); 782 else { 783 char *nval; 784 785 if (asprintf(&nval, "%s, %s", hdr->h_value, str) == -1) 786 http_error(500, NULL, 787 "memory allocation failure"); 788 free(hdr->h_value); 789 hdr->h_value = nval; 790 } 791 } else { 792 /* nope, create a new one */ 793 794 hdr = bozomalloc(sizeof *hdr); 795 hdr->h_header = bozostrdup(val); 796 if (str && *str) 797 hdr->h_value = bozostrdup(str); 798 else 799 hdr->h_value = space; 800 801 SIMPLEQ_INSERT_TAIL(&request->hr_headers, hdr, h_next); 802 request->hr_nheaders++; 803 } 804 805 return hdr; 806 } 807 808 /* 809 * process_request does the following: 810 * - check the request is valid 811 * - process cgi-bin if necessary 812 * - transform a filename if necesarry 813 * - return the HTTP request 814 */ 815 static void 816 process_request(http_req *request) 817 { 818 struct stat sb; 819 char *file; 820 const char *type, *encoding; 821 int fd, isindex; 822 823 /* 824 * note that transform_request chdir()'s if required. also note 825 * that cgi is handed here, and a cgi request will never return 826 * back here. 827 */ 828 file = transform_request(request, &isindex); 829 if (file == NULL) 830 http_error(404, request, "empty file after transform"); 831 832 fd = open(file, O_RDONLY); 833 if (fd < 0) { 834 debug((DEBUG_FAT, "open failed: %s", strerror(errno))); 835 if (errno == EPERM) 836 http_error(403, request, "no permission to open file"); 837 else if (errno == ENOENT) { 838 if (directory_index(request, file, isindex)) 839 return; 840 http_error(404, request, "no file"); 841 } else 842 http_error(500, request, "open file"); 843 } 844 if (fstat(fd, &sb) < 0) 845 http_error(500, request, "can't fstat"); 846 if (S_ISDIR(sb.st_mode)) 847 handle_redirect(request, NULL, 0); 848 /* NOTREACHED */ 849 /* XXX RFC1945 10.9 If-Modified-Since (http code 304) */ 850 851 bozoprintf("%s 200 OK\r\n", request->hr_proto); 852 853 if (request->hr_proto != http_09) { 854 type = content_type(request, file); 855 encoding = content_encoding(request, file); 856 857 print_header(request, &sb, type, encoding); 858 bozoprintf("\r\n"); 859 } 860 bozoflush(stdout); 861 862 if (request->hr_method != HTTP_HEAD) { 863 char *addr; 864 void *oaddr; 865 off_t sz = sb.st_size; 866 867 oaddr = addr = mmap(0, (size_t)sz, PROT_READ, 868 MAP_SHARED, fd, 0); 869 if (addr == (char *)-1) 870 error(1, "mmap failed: %s", strerror(errno)); 871 872 #ifdef MADV_SEQUENTIAL 873 madvise(addr, sz, MADV_SEQUENTIAL); 874 #endif 875 while (sz > WRSZ) { 876 if (bozowrite(STDOUT_FILENO, addr, WRSZ) != WRSZ) 877 error(1, "write failed: %s", strerror(errno)); 878 sz -= WRSZ; 879 addr += WRSZ; 880 } 881 if (sz && bozowrite(STDOUT_FILENO, addr, sz) != sz) 882 error(1, "final write failed: %s", strerror(errno)); 883 if (munmap(oaddr, (size_t)sb.st_size) < 0) 884 warning("munmap failed"); 885 } 886 /* If SSL enabled cleanup SSL structure. */ 887 ssl_destroy(); 888 close(fd); 889 free(file); 890 } 891 892 /* 893 * deal with virtual host names; we do this: 894 * if we have a virtual path root (vpath), and we are given a 895 * virtual host spec (Host: ho.st or http://ho.st/), see if this 896 * directory exists under vpath. if it does, use this as the 897 # new slashdir. 898 */ 899 static void 900 check_virtual(http_req *request) 901 { 902 char *url = request->hr_url, *s; 903 struct dirent **list; 904 size_t len; 905 int i; 906 907 if (!vpath) 908 goto use_slashdir; 909 910 /* 911 * convert http://virtual.host/ to request->hr_host 912 */ 913 debug((DEBUG_OBESE, "checking for http:// virtual host in ``%s''", url)); 914 if (strncasecmp(url, "http://", 7) == 0) { 915 /* we would do virtual hosting here? */ 916 url += 7; 917 s = strchr(url, '/'); 918 /* HTTP/1.1 draft rev-06, 5.2: URI takes precedence over Host: */ 919 request->hr_host = url; 920 request->hr_url = bozostrdup(s ? s : "/"); 921 debug((DEBUG_OBESE, "got host ``%s'' url is now ``%s''", 922 request->hr_host, request->hr_url)); 923 } else if (!request->hr_host) 924 goto use_slashdir; 925 926 927 /* 928 * ok, we have a virtual host, use scandir(3) to find a case 929 * insensitive match for the virtual host we are asked for. 930 * note that if the virtual host is the same as the master, 931 * we don't need to do anything special. 932 */ 933 len = strlen(request->hr_host); 934 debug((DEBUG_OBESE, 935 "check_virtual: checking host `%s' under vpath `%s' for url `%s'", 936 request->hr_host, vpath, request->hr_url)); 937 if (strncasecmp(myname, request->hr_host, len) != 0) { 938 s = 0; 939 for (i = scandir(vpath, &list, 0, 0); i--; list++) { 940 debug((DEBUG_OBESE, "looking at dir``%s''", 941 (*list)->d_name)); 942 if (strncasecmp((*list)->d_name, request->hr_host, 943 len) == 0) { 944 /* found it, punch it */ 945 myname = (*list)->d_name; 946 if (asprintf(&s, "%s/%s", vpath, myname) < 0) 947 error(1, "asprintf"); 948 break; 949 } 950 } 951 if (s == 0) { 952 if (Vflag) 953 goto use_slashdir; 954 http_error(404, request, "unknown URL"); 955 } 956 } else 957 use_slashdir: 958 s = slashdir; 959 960 /* 961 * ok, nailed the correct slashdir, chdir to it 962 */ 963 if (chdir(s) < 0) 964 error(1, "can't chdir %s: %s", s, strerror(errno)); 965 } 966 967 /* make sure we're not trying to access special files */ 968 void 969 check_special_files(http_req *request, const char *name) 970 { 971 /* ensure basename(name) != special files */ 972 if (strcmp(name, DIRECT_ACCESS_FILE) == 0) 973 http_error(403, request, 974 "no permission to open direct access file"); 975 if (strcmp(name, REDIRECT_FILE) == 0) 976 http_error(403, request, 977 "no permission to open redirect file"); 978 if (strcmp(name, ABSREDIRECT_FILE) == 0) 979 http_error(403, request, 980 "no permission to open redirect file"); 981 auth_check_special_files(request, name); 982 } 983 984 /* 985 * checks to see if this request has a valid .bzredirect file. returns 986 * 0 on failure and 1 on success. 987 */ 988 static void 989 check_bzredirect(http_req *request) 990 { 991 struct stat sb; 992 char dir[MAXPATHLEN], redir[MAXPATHLEN], redirpath[MAXPATHLEN + 1]; 993 char *basename, *finalredir; 994 int rv, absolute; 995 996 /* 997 * if this pathname is really a directory, but doesn't end in /, 998 * use it as the directory to look for the redir file. 999 */ 1000 snprintf(dir, sizeof(dir), "%s", request->hr_url + 1); 1001 debug((DEBUG_FAT, "check_bzredirect: dir %s", dir)); 1002 basename = strrchr(dir, '/'); 1003 1004 if ((!basename || basename[1] != '\0') && 1005 lstat(dir, &sb) == 0 && S_ISDIR(sb.st_mode)) 1006 /* nothing */; 1007 else if (basename == NULL) 1008 strcpy(dir, "."); 1009 else { 1010 *basename++ = '\0'; 1011 check_special_files(request, basename); 1012 } 1013 1014 snprintf(redir, sizeof(redir), "%s/%s", dir, REDIRECT_FILE); 1015 if (lstat(redir, &sb) == 0) { 1016 if (S_ISLNK(sb.st_mode) == 0) 1017 return; 1018 absolute = 0; 1019 } else { 1020 snprintf(redir, sizeof(redir), "%s/%s", dir, ABSREDIRECT_FILE); 1021 if (lstat(redir, &sb) < 0 || S_ISLNK(sb.st_mode) == 0) 1022 return; 1023 absolute = 1; 1024 } 1025 debug((DEBUG_FAT, "check_bzredirect: calling readlink")); 1026 rv = readlink(redir, redirpath, sizeof redirpath - 1); 1027 if (rv == -1 || rv == 0) { 1028 debug((DEBUG_FAT, "readlink failed")); 1029 return; 1030 } 1031 redirpath[rv] = '\0'; 1032 debug((DEBUG_FAT, "readlink returned \"%s\"", redirpath)); 1033 1034 /* now we have the link pointer, redirect to the real place */ 1035 if (absolute) 1036 finalredir = redirpath; 1037 else 1038 snprintf(finalredir = redir, sizeof(redir), "/%s/%s", dir, 1039 redirpath); 1040 1041 debug((DEBUG_FAT, "check_bzredirect: new redir %s", finalredir)); 1042 handle_redirect(request, finalredir, absolute); 1043 } 1044 1045 /* 1046 * checks to see if this request has a valid .bzdirect file. returns 1047 * 0 on failure and 1 on success. 1048 */ 1049 static int 1050 check_direct_access(http_req *request) 1051 { 1052 FILE *fp; 1053 struct stat sb; 1054 char dir[MAXPATHLEN], dirfile[MAXPATHLEN], *basename; 1055 1056 snprintf(dir, sizeof(dir), "%s", request->hr_url + 1); 1057 debug((DEBUG_FAT, "check_bzredirect: dir %s", dir)); 1058 basename = strrchr(dir, '/'); 1059 1060 if ((!basename || basename[1] != '\0') && 1061 lstat(dir, &sb) == 0 && S_ISDIR(sb.st_mode)) 1062 /* nothing */; 1063 else if (basename == NULL) 1064 strcpy(dir, "."); 1065 else { 1066 *basename++ = '\0'; 1067 check_special_files(request, basename); 1068 } 1069 1070 snprintf(dirfile, sizeof(dirfile), "%s/%s", dir, DIRECT_ACCESS_FILE); 1071 if (stat(dirfile, &sb) < 0 || 1072 (fp = fopen(dirfile, "r")) == NULL) 1073 return 0; 1074 fclose(fp); 1075 return 1; 1076 } 1077 1078 /* 1079 * transform_request does this: 1080 * - ``expand'' %20 crapola 1081 * - punt if it doesn't start with / 1082 * - check rflag / referrer 1083 * - look for "http://myname/" and deal with it. 1084 * - maybe call process_cgi() 1085 * - check for ~user and call user_transform() if so 1086 * - if the length > 1, check for trailing slash. if so, 1087 * add the index.html file 1088 * - if the length is 1, return the index.html file 1089 * - disallow anything ending up with a file starting 1090 * at "/" or having ".." in it. 1091 * - anything else is a really weird internal error 1092 */ 1093 static char * 1094 transform_request(http_req *request, int *isindex) 1095 { 1096 char *file; 1097 char *url; 1098 size_t len; 1099 1100 file = NULL; 1101 *isindex = 0; 1102 debug((DEBUG_FAT, "tf_req: url %s", request->hr_url)); 1103 fix_url_percent(request); 1104 check_virtual(request); 1105 url = request->hr_url; 1106 1107 if (url[0] != '/') 1108 http_error(404, request, "unknown URL"); 1109 1110 check_bzredirect(request); 1111 1112 if (rflag) { 1113 int to_indexhtml = 0; 1114 1115 #define TOP_PAGE(x) (strcmp((x), "/") == 0 || \ 1116 strcmp((x) + 1, index_html) == 0 || \ 1117 strcmp((x) + 1, "favicon.ico") == 0) 1118 1119 debug((DEBUG_EXPLODING, "checking rflag")); 1120 /* 1121 * first check that this path isn't allowed via .bzdirect file, 1122 * and then check referrer; make sure that people come via the 1123 * real name... otherwise if we aren't looking at / or 1124 * /index.html, redirect... we also special case favicon.ico. 1125 */ 1126 if (check_direct_access(request)) 1127 /* nothing */; 1128 else if (request->hr_referrer) { 1129 const char *r = request->hr_referrer; 1130 1131 debug((DEBUG_FAT, 1132 "checking referrer \"%s\" vs myname %s", r, myname)); 1133 if (strncmp(r, "http://", 7) != 0 || 1134 (strncasecmp(r + 7, myname, strlen(myname)) != 0 && 1135 !TOP_PAGE(url))) 1136 to_indexhtml = 1; 1137 } else { 1138 const char *h = request->hr_host; 1139 1140 debug((DEBUG_FAT, "url has no referrer at all")); 1141 /* if there's no referrer, let / or /index.html past */ 1142 if (!TOP_PAGE(url) || 1143 (h && strncasecmp(h, myname, strlen(myname)) != 0)) 1144 to_indexhtml = 1; 1145 } 1146 1147 if (to_indexhtml) { 1148 char *slashindexhtml; 1149 1150 if (asprintf(&slashindexhtml, "/%s", index_html) < 0) 1151 error(1, "asprintf"); 1152 debug((DEBUG_FAT, "rflag: redirecting %s to %s", url, slashindexhtml)); 1153 handle_redirect(request, slashindexhtml, 0); 1154 /* NOTREACHED */ 1155 } 1156 } 1157 1158 process_cgi(request); 1159 1160 len = strlen(url); 1161 if (0) { 1162 #ifndef NO_USER_SUPPORT 1163 } else if (len > 1 && uflag && url[1] == '~') { 1164 if (url[2] == '\0') 1165 http_error(404, request, "missing username"); 1166 if (strchr(url + 2, '/') == NULL) 1167 handle_redirect(request, NULL, 0); 1168 /* NOTREACHED */ 1169 debug((DEBUG_FAT, "calling user_transform")); 1170 return (user_transform(request, isindex)); 1171 #endif /* NO_USER_SUPPORT */ 1172 } else if (len > 1) { 1173 debug((DEBUG_FAT, "url[len-1] == %c", url[len-1])); 1174 if (url[len-1] == '/') { /* append index.html */ 1175 *isindex = 1; 1176 debug((DEBUG_FAT, "appending index.html")); 1177 file = bozomalloc(len + strlen(index_html) + 1); 1178 strcpy(file, url + 1); 1179 strcat(file, index_html); 1180 } else 1181 file = bozostrdup(url + 1); 1182 } else if (len == 1) { 1183 debug((DEBUG_EXPLODING, "tf_req: len == 1")); 1184 file = bozostrdup(index_html); 1185 *isindex = 1; 1186 } else /* len == 0 ? */ 1187 http_error(500, request, "request->hr_url is nul?"); 1188 1189 if (file == NULL) 1190 http_error(500, request, "internal failure"); 1191 1192 /* 1193 * look for "http://myname/" and deal with it as necessary. 1194 */ 1195 1196 /* 1197 * stop traversing outside our domain 1198 * 1199 * XXX true security only comes from our parent using chroot(2) 1200 * before execve(2)'ing us. or our own built in chroot(2) support. 1201 */ 1202 if (*file == '/' || strcmp(file, "..") == 0 || 1203 strstr(file, "/..") || strstr(file, "../")) 1204 http_error(403, request, "illegal request"); 1205 1206 auth_check(request, file); 1207 1208 debug((DEBUG_FAT, "transform_request returned: %s", file)); 1209 return (file); 1210 } 1211 1212 /* 1213 * do automatic redirection 1214 */ 1215 static void 1216 handle_redirect(http_req *request, const char *url, int absolute) 1217 { 1218 char *urlbuf; 1219 char portbuf[20]; 1220 1221 if (url == NULL) { 1222 if (asprintf(&urlbuf, "%s/", request->hr_url) < 0) 1223 error(1, "asprintf"); 1224 url = urlbuf; 1225 } 1226 if (request->hr_serverport && strcmp(request->hr_serverport, "80") != 0) 1227 snprintf(portbuf, sizeof(portbuf), ":%s", 1228 request->hr_serverport); 1229 else 1230 portbuf[0] = '\0'; 1231 warning("redirecting %s%s%s", myname, portbuf, url); 1232 debug((DEBUG_FAT, "redirecting %s", url)); 1233 bozoprintf("%s 301 Document Moved\r\n", request->hr_proto); 1234 if (request->hr_proto != http_09) 1235 print_header(request, NULL, "text/html", NULL); 1236 if (request->hr_proto != http_09) { 1237 bozoprintf("Location: http://"); 1238 if (absolute == 0) 1239 bozoprintf("%s%s", myname, portbuf); 1240 bozoprintf("%s\r\n", url); 1241 } 1242 bozoprintf("\r\n"); 1243 if (request->hr_method == HTTP_HEAD) 1244 goto head; 1245 bozoprintf("<html><head><title>Document Moved</title></head>\n"); 1246 bozoprintf("<body><h1>Document Moved</h1>\n"); 1247 bozoprintf("This document had moved <a href=\"http://"); 1248 if (absolute) 1249 bozoprintf("%s", url); 1250 else 1251 bozoprintf("%s%s%s", myname, portbuf, url); 1252 bozoprintf("\">here</a>\n"); 1253 bozoprintf("</body></html>\n"); 1254 head: 1255 bozoflush(stdout); 1256 exit(0); 1257 } 1258 1259 /* generic header printing routine */ 1260 void 1261 print_header(http_req *request, struct stat *sbp, const char *type, 1262 const char *encoding) 1263 { 1264 bozoprintf("Date: %s\r\n", http_date()); 1265 bozoprintf("Server: %s\r\n", server_software); 1266 if (sbp) { 1267 char filedate[40]; 1268 struct tm *tm; 1269 1270 tm = gmtime(&sbp->st_mtime); 1271 strftime(filedate, sizeof filedate, 1272 "%a, %d %b %Y %H:%M:%S GMT", tm); 1273 bozoprintf("Last-Modified: %s\r\n", filedate); 1274 } 1275 if (type && *type) 1276 bozoprintf("Content-Type: %s\r\n", type); 1277 if (encoding && *encoding) 1278 bozoprintf("Content-Encoding: %s\r\n", encoding); 1279 if (sbp) 1280 bozoprintf("Content-Length: %qd\r\n", (long long)sbp->st_size); 1281 if (request && request->hr_proto == http_11) 1282 bozoprintf("Connection: close\r\n"); 1283 bozoflush(stdout); 1284 } 1285 1286 /* this escape HTML tags */ 1287 static void 1288 escape_html(http_req *request) 1289 { 1290 int i, j; 1291 char *url = request->hr_url, *tmp; 1292 1293 for (i = 0, j = 0; url[i]; i++) { 1294 switch (url[i]) { 1295 case '<': 1296 case '>': 1297 j += 4; 1298 break; 1299 case '&': 1300 j += 5; 1301 break; 1302 } 1303 } 1304 1305 if (j == 0) 1306 return; 1307 1308 if ((tmp = (char *) malloc(strlen(url) + j)) == 0) 1309 /* 1310 * ouch, but we are only called from an error context, and 1311 * most paths here come from malloc(3) failures anyway... 1312 * we could completely punt and just exit, but isn't returning 1313 * an not-quite-correct error better than nothing at all? 1314 */ 1315 return; 1316 1317 for (i = 0, j = 0; url[i]; i++) { 1318 switch (url[i]) { 1319 case '<': 1320 memcpy(tmp + j, "<", 4); 1321 j += 4; 1322 break; 1323 case '>': 1324 memcpy(tmp + j, ">", 4); 1325 j += 4; 1326 break; 1327 case '&': 1328 memcpy(tmp + j, "&", 5); 1329 j += 5; 1330 break; 1331 default: 1332 tmp[j++] = url[i]; 1333 } 1334 } 1335 tmp[j] = 0; 1336 1337 /* 1338 * XXX original "url" is a substring of an allocation, so we 1339 * can't touch it. so, ignore it and replace the request. 1340 */ 1341 request->hr_url = tmp; 1342 } 1343 1344 /* this fixes the %HH hack that RFC2396 requires. */ 1345 static void 1346 fix_url_percent(http_req *request) 1347 { 1348 char *s, *t, buf[3], *url; 1349 char *end; /* if end is not-zero, we don't translate beyond that */ 1350 1351 url = request->hr_url; 1352 1353 /* make sure we don't translate *too* much */ 1354 end = strchr(request->hr_url, '?'); 1355 1356 /* fast forward to the first % */ 1357 if ((s = strchr(url, '%')) == NULL) 1358 return; 1359 1360 t = s; 1361 do { 1362 if (end && s >= end) { 1363 debug((DEBUG_EXPLODING, "fu_%%: past end, filling out..")); 1364 while (*s) 1365 *t++ = *s++; 1366 break; 1367 } 1368 debug((DEBUG_EXPLODING, "fu_%%: got s == %%, s[1]s[2] == %c%c", 1369 s[1], s[2])); 1370 if (s[1] == '\0' || s[2] == '\0') 1371 http_error(400, request, 1372 "percent hack missing two chars afterwards"); 1373 if (s[1] == '0' && s[2] == '0') 1374 http_error(404, request, "percent hack was %00"); 1375 if (s[1] == '2' && s[2] == 'f') 1376 http_error(404, request, "percent hack was %2f (/)"); 1377 1378 buf[0] = *++s; 1379 buf[1] = *++s; 1380 buf[2] = '\0'; 1381 s++; 1382 *t = (char)strtol(buf, NULL, 16); 1383 debug((DEBUG_EXPLODING, "fu_%%: strtol put %c into *t", *t)); 1384 if (*t++ == '\0') 1385 http_error(400, request, "percent hack got a 0 back"); 1386 1387 while (*s && *s != '%') { 1388 if (s >= end) 1389 break; 1390 *t++ = *s++; 1391 } 1392 } while (*s); 1393 *t = '\0'; 1394 debug((DEBUG_FAT, "fix_url_percent returns %s in url", request->hr_url)); 1395 } 1396 1397 /* 1398 * process each type of HTTP method, setting this HTTP requests 1399 # method type. 1400 */ 1401 static struct method_map { 1402 const char *name; 1403 int type; 1404 } method_map[] = { 1405 { "GET", HTTP_GET, }, 1406 { "POST", HTTP_POST, }, 1407 { "HEAD", HTTP_HEAD, }, 1408 #if 0 /* other non-required http/1.1 methods */ 1409 { "OPTIONS", HTTP_OPTIONS, }, 1410 { "PUT", HTTP_PUT, }, 1411 { "DELETE", HTTP_DELETE, }, 1412 { "TRACE", HTTP_TRACE, }, 1413 { "CONNECT", HTTP_CONNECT, }, 1414 #endif 1415 { NULL, 0, }, 1416 }; 1417 1418 static void 1419 process_method(http_req *request, const char *method) 1420 { 1421 struct method_map *mmp; 1422 1423 for (mmp = method_map; mmp->name; mmp++) 1424 if (strcasecmp(method, mmp->name) == 0) { 1425 request->hr_method = mmp->type; 1426 request->hr_methodstr = mmp->name; 1427 return; 1428 } 1429 1430 if (request->hr_proto == http_11) 1431 request->hr_allow = "GET, HEAD, POST"; 1432 http_error(404, request, "unknown method"); 1433 } 1434 1435 /* 1436 * as the prototype string is not constant (eg, "HTTP/1.1" is equivalent 1437 * to "HTTP/001.01"), we MUST parse this. 1438 */ 1439 static void 1440 process_proto(http_req *request, const char *proto) 1441 { 1442 char majorstr[16], *minorstr; 1443 int majorint, minorint; 1444 1445 if (proto == NULL) { 1446 got_proto_09: 1447 request->hr_proto = http_09; 1448 debug((DEBUG_FAT, "request %s is http/0.9", request->hr_url)); 1449 return; 1450 } 1451 1452 if (strncasecmp(proto, "HTTP/", 5) != 0) 1453 goto bad; 1454 strncpy(majorstr, proto + 5, sizeof majorstr); 1455 majorstr[sizeof(majorstr)-1] = 0; 1456 minorstr = strchr(majorstr, '.'); 1457 if (minorstr == NULL) 1458 goto bad; 1459 *minorstr++ = 0; 1460 1461 majorint = atoi(majorstr); 1462 minorint = atoi(minorstr); 1463 1464 switch (majorint) { 1465 case 0: 1466 if (minorint != 9) 1467 break; 1468 goto got_proto_09; 1469 case 1: 1470 if (minorint == 0) 1471 request->hr_proto = http_10; 1472 else if (minorint == 1) 1473 request->hr_proto = http_11; 1474 else 1475 break; 1476 1477 debug((DEBUG_FAT, "request %s is %s", request->hr_url, 1478 request->hr_proto)); 1479 SIMPLEQ_INIT(&request->hr_headers); 1480 request->hr_nheaders = 0; 1481 return; 1482 } 1483 bad: 1484 http_error(404, NULL, "unknown prototype"); 1485 } 1486 1487 #ifdef DEBUG 1488 void 1489 debug__(int level, const char *fmt, ...) 1490 { 1491 va_list ap; 1492 int savederrno; 1493 1494 /* only log if the level is low enough */ 1495 if (dflag < level) 1496 return; 1497 1498 savederrno = errno; 1499 va_start(ap, fmt); 1500 if (sflag) { 1501 vfprintf(stderr, fmt, ap); 1502 fputs("\n", stderr); 1503 } else 1504 vsyslog(LOG_DEBUG, fmt, ap); 1505 va_end(ap); 1506 errno = savederrno; 1507 } 1508 #endif /* DEBUG */ 1509 1510 /* these are like warn() and err(), except for syslog not stderr */ 1511 void 1512 warning(const char *fmt, ...) 1513 { 1514 va_list ap; 1515 1516 va_start(ap, fmt); 1517 if (sflag || isatty(STDERR_FILENO)) { 1518 vfprintf(stderr, fmt, ap); 1519 fputs("\n", stderr); 1520 } else 1521 vsyslog(LOG_INFO, fmt, ap); 1522 va_end(ap); 1523 } 1524 1525 void 1526 error(int code, const char *fmt, ...) 1527 { 1528 va_list ap; 1529 1530 va_start(ap, fmt); 1531 if (sflag || isatty(STDERR_FILENO)) { 1532 vfprintf(stderr, fmt, ap); 1533 fputs("\n", stderr); 1534 } else 1535 vsyslog(LOG_ERR, fmt, ap); 1536 va_end(ap); 1537 exit(code); 1538 } 1539 1540 /* the follow functions and variables are used in handling HTTP errors */ 1541 /* ARGSUSED */ 1542 void 1543 http_error(int code, http_req *request, const char *msg) 1544 { 1545 static char buf[BUFSIZ]; 1546 char portbuf[20]; 1547 const char *header = http_errors_short(code); 1548 const char *reason = http_errors_long(code); 1549 const char *proto = (request && request->hr_proto) ? request->hr_proto : http_11; 1550 int size; 1551 1552 debug((DEBUG_FAT, "http_error %d: %s", code, msg)); 1553 if (header == NULL || reason == NULL) 1554 error(1, "http_error() failed (short = %p, long = %p)", 1555 header, reason); 1556 1557 if (request && request->hr_serverport && strcmp(request->hr_serverport, "80") != 0) 1558 snprintf(portbuf, sizeof(portbuf), ":%s", request->hr_serverport); 1559 else 1560 portbuf[0] = '\0'; 1561 1562 if (request && request->hr_url) { 1563 escape_html(request); 1564 size = snprintf(buf, sizeof buf, 1565 "<html><head><title>%s</title></head>\n" 1566 "<body><h1>%s</h1>\n" 1567 "%s: <pre>%s</pre>\n" 1568 "<hr><address><a href=\"http://%s%s/\">%s%s</a></address>\n" 1569 "</body></html>\n", 1570 header, header, request->hr_url, reason, 1571 myname, portbuf, myname, portbuf); 1572 if (size >= sizeof buf) 1573 warning("http_error buffer too small, truncated"); 1574 } else 1575 size = 0; 1576 1577 bozoprintf("%s %s\r\n", proto, header); 1578 auth_check_401(request, code); 1579 1580 bozoprintf("Content-Type: text/html\r\n"); 1581 bozoprintf("Content-Length: %d\r\n", size); 1582 bozoprintf("Server: %s\r\n", server_software); 1583 if (request && request->hr_allow) 1584 bozoprintf("Allow: %s\r\n", request->hr_allow); 1585 bozoprintf("\r\n"); 1586 if (size) 1587 bozoprintf("%s", buf); 1588 bozoflush(stdout); 1589 1590 exit(1); 1591 } 1592 1593 /* short map between error code, and short/long messages */ 1594 static struct errors_map { 1595 int code; /* HTTP return code */ 1596 const char *shortmsg; /* short version of message */ 1597 const char *longmsg; /* long version of message */ 1598 } errors_map[] = { 1599 { 400, "400 Bad Request", "The request was not valid", }, 1600 { 401, "401 Unauthorized", "No authorization", }, 1601 { 403, "403 Forbidden", "Access to this item has been denied", }, 1602 { 404, "404 Not Found", "This item has not been found", }, 1603 { 408, "408 Request Timeout", "This request took too long", }, 1604 { 417, "417 Expectation Failed","Expectations not available", }, 1605 { 500, "500 Internal Error", "An error occured on the server", }, 1606 { 501, "501 Not Implemented", "This request is not available", }, 1607 { 0, NULL, NULL, }, 1608 }; 1609 1610 static const char *help = "DANGER! WILL ROBINSON! DANGER!"; 1611 1612 static const char * 1613 http_errors_short(int code) 1614 { 1615 struct errors_map *ep; 1616 1617 for (ep = errors_map; ep->code; ep++) 1618 if (ep->code == code) 1619 return (ep->shortmsg); 1620 return (help); 1621 } 1622 1623 static const char * 1624 http_errors_long(int code) 1625 { 1626 struct errors_map *ep; 1627 1628 for (ep = errors_map; ep->code; ep++) 1629 if (ep->code == code) 1630 return (ep->longmsg); 1631 return (help); 1632 } 1633 1634 /* Below are various modified libc functions */ 1635 1636 /* 1637 * returns -1 in lenp if the string ran out before finding a delimiter, 1638 * but is otherwise the same as strsep. Note that the length must be 1639 * correctly passed in. 1640 */ 1641 char * 1642 strnsep(char **strp, const char *delim, ssize_t *lenp) 1643 { 1644 char *s; 1645 const char *spanp; 1646 int c, sc; 1647 char *tok; 1648 1649 if ((s = *strp) == NULL) 1650 return (NULL); 1651 for (tok = s;;) { 1652 if (lenp && --(*lenp) == -1) 1653 return (NULL); 1654 c = *s++; 1655 spanp = delim; 1656 do { 1657 if ((sc = *spanp++) == c) { 1658 if (c == 0) 1659 s = NULL; 1660 else 1661 s[-1] = '\0'; 1662 *strp = s; 1663 return (tok); 1664 } 1665 } while (sc != 0); 1666 } 1667 /* NOTREACHED */ 1668 } 1669 1670 /* 1671 * inspired by fgetln(3), but works for fd's. should work identically 1672 * except it, however, does *not* return the newline, and it does nul 1673 * terminate the string. 1674 */ 1675 char * 1676 dgetln(int fd, ssize_t *lenp, ssize_t (*readfn)(int, void *, size_t)) 1677 { 1678 static char *buffer; 1679 static ssize_t buflen = 0; 1680 ssize_t len; 1681 int got_cr = 0; 1682 char c, *nbuffer; 1683 1684 /* initialise */ 1685 if (buflen == 0) { 1686 buflen = 128; /* should be plenty for most requests */ 1687 buffer = malloc(buflen); 1688 if (buffer == NULL) { 1689 buflen = 0; 1690 return NULL; 1691 } 1692 } 1693 len = 0; 1694 1695 /* 1696 * we *have* to read one byte at a time, to not break cgi 1697 * programs (for we pass stdin off to them). could fix this 1698 * by becoming a fd-passing program instead of just exec'ing 1699 * the program 1700 */ 1701 for (; readfn(fd, &c, 1) == 1; ) { 1702 debug((DEBUG_EXPLODING, "dgetln read %c", c)); 1703 1704 if (len >= buflen - 1) { 1705 buflen *= 2; 1706 debug((DEBUG_EXPLODING, 1707 "dgetln: reallocating buffer to buflen %d", buflen)); 1708 nbuffer = realloc(buffer, buflen); 1709 if (nbuffer == NULL) { 1710 free(buffer); 1711 buflen = 0; 1712 buffer = NULL; 1713 return NULL; 1714 } 1715 buffer = nbuffer; 1716 } 1717 1718 buffer[len++] = c; 1719 if (c == '\r') { 1720 got_cr = 1; 1721 continue; 1722 } else if (c == '\n') { 1723 /* 1724 * HTTP/1.1 spec says to ignore CR and treat 1725 * LF as the real line terminator. even though 1726 * the same spec defines CRLF as the line 1727 * terminator, it is recommended in section 19.3 1728 * to do the LF trick for tolerance. 1729 */ 1730 if (got_cr) 1731 len -= 2; 1732 else 1733 len -= 1; 1734 break; 1735 } 1736 1737 } 1738 buffer[len] = '\0'; 1739 debug((DEBUG_OBESE, "dgetln returns: ``%s'' with len %d", buffer, len)); 1740 *lenp = len; 1741 return (buffer); 1742 } 1743 1744 void * 1745 bozorealloc(void *ptr, size_t size) 1746 { 1747 void *p; 1748 1749 p = realloc(ptr, size); 1750 if (p == NULL) 1751 http_error(500, NULL, "memory allocation failure"); 1752 return (p); 1753 } 1754 1755 void * 1756 bozomalloc(size_t size) 1757 { 1758 void *p; 1759 1760 p = malloc(size); 1761 if (p == NULL) 1762 http_error(500, NULL, "memory allocation failure"); 1763 return (p); 1764 } 1765 1766 char * 1767 bozostrdup(const char *str) 1768 { 1769 char *p; 1770 1771 p = strdup(str); 1772 if (p == NULL) 1773 http_error(500, NULL, "memory allocation failure"); 1774 return (p); 1775 } 1776