Annotation of src/libexec/httpd/bozohttpd.c, Revision 1.8
1.8 ! lukem 1: /* $NetBSD: bozohttpd.c,v 1.7 2008/03/07 18:20:20 mrg Exp $ */
1.3 tls 2:
1.6 mrg 3: /* $eterna: bozohttpd.c,v 1.142 2008/03/03 03:36:11 mrg Exp $ */
1.1 tls 4:
5: /*
1.6 mrg 6: * Copyright (c) 1997-2008 Matthew R. Green
1.1 tls 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.
1.6 mrg 99: * since 20080301 we support simple range headers.
1.1 tls 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
1.6 mrg 114: #define SERVER_SOFTWARE "bozohttpd/20080303"
1.1 tls 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':
1.6 mrg 378: case 'e':
379: case 'f':
1.1 tls 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
1.6 mrg 431: case 'p':
432: case 't':
1.1 tls 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);
1.6 mrg 570: val = bozostrnsep(&in, " \t\n\r", &len);
1.1 tls 571: if (len < 1 || val == NULL)
572: return;
573: *method = val;
574: while (*in == ' ' || *in == '\t')
575: in++;
1.6 mrg 576: val = bozostrnsep(&in, " \t\n\r", &len);
1.1 tls 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;
1.6 mrg 632: request->hr_range = NULL;
633: request->hr_last_byte_pos = -1;
1.1 tls 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);
1.6 mrg 677: while ((str = bozodgetln(STDIN_FILENO, &len, bozoread)) != NULL) {
1.1 tls 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:
1.6 mrg 718: val = bozostrnsep(&str, ":", &len);
1.1 tls 719: debug((DEBUG_EXPLODING,
1.6 mrg 720: "read_req2: after bozostrnsep: str ``%s'' val ``%s''",
1.1 tls 721: str, val));
722: if (val == NULL || len == -1)
723: http_error(404, request, "no header");
724: while (*str == ' ' || *str == '\t')
725: len--, str++;
1.6 mrg 726: while (*val == ' ' || *val == '\t')
727: val++;
1.1 tls 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;
1.6 mrg 746: else if (strcasecmp(hdr->h_header, "range") == 0)
747: request->hr_range = hdr->h_value;
1.1 tls 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:
1.6 mrg 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:
1.1 tls 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? */
1.2 tls 810: SIMPLEQ_FOREACH(hdr, &request->hr_headers, h_next) {
1.1 tls 811: if (strcasecmp(val, hdr->h_header) == 0)
812: break;
1.2 tls 813: }
1.1 tls 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:
1.6 mrg 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);
1.1 tls 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;
1.6 mrg 918: size_t mappedsz;
919: size_t sz;
1.1 tls 920:
1.6 mrg 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);
1.1 tls 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));
1.6 mrg 933: debug((DEBUG_OBESE, "wrote %d bytes", WRSZ));
1.1 tls 934: sz -= WRSZ;
935: addr += WRSZ;
936: }
1.8 ! lukem 937: if (sz && (size_t)bozowrite(STDOUT_FILENO, addr, sz) != sz)
1.1 tls 938: error(1, "final write failed: %s", strerror(errno));
1.6 mrg 939: debug((DEBUG_OBESE, "wrote %d bytes", (int)sz));
940: if (munmap(oaddr, mappedsz) < 0)
1.1 tls 941: warning("munmap failed");
942: }
943: /* If SSL enabled cleanup SSL structure. */
944: ssl_destroy();
945: close(fd);
1.2 tls 946: free(file);
1.1 tls 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: {
1.6 mrg 1321: off_t len;
1322:
1.1 tls 1323: bozoprintf("Date: %s\r\n", http_date());
1324: bozoprintf("Server: %s\r\n", server_software);
1.6 mrg 1325: bozoprintf("Accept-Ranges: bytes\r\n");
1.1 tls 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);
1.6 mrg 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: }
1.1 tls 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: /*
1.6 mrg 1408: * original "url" is a substring of an allocation, so we
1.1 tls 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);
1.7 mrg 1453: debug((DEBUG_EXPLODING, "fu_%%: strtol put '%c' into *t", *t));
1.1 tls 1454: if (*t++ == '\0')
1455: http_error(400, request, "percent hack got a 0 back");
1456:
1457: while (*s && *s != '%') {
1.7 mrg 1458: if (end && s >= end)
1.1 tls 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:
1.6 mrg 1627: if (request && request->hr_serverport &&
1628: strcmp(request->hr_serverport, "80") != 0)
1.1 tls 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);
1.8 ! lukem 1643: if (size >= (int)sizeof buf)
1.1 tls 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", },
1.6 mrg 1672: { 403, "403 Forbidden", "Access to this item has been denied",},
1.1 tls 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 *
1.6 mrg 1713: bozostrnsep(char **strp, const char *delim, ssize_t *lenp)
1.1 tls 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 *
1.6 mrg 1747: bozodgetln(int fd, ssize_t *lenp, ssize_t (*readfn)(int, void *, size_t))
1.1 tls 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; ) {
1.6 mrg 1773: debug((DEBUG_EXPLODING, "bozodgetln read %c", c));
1.1 tls 1774:
1775: if (len >= buflen - 1) {
1776: buflen *= 2;
1.6 mrg 1777: debug((DEBUG_EXPLODING, "bozodgetln: "
1778: "reallocating buffer to buflen %zu", buflen));
1.1 tls 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';
1.6 mrg 1810: debug((DEBUG_OBESE, "bozodgetln returns: ``%s'' with len %d",
1811: buffer, len));
1.1 tls 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: }
CVSweb <webmaster@jp.NetBSD.org>