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