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