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