1 /* $NetBSD: bozohttpd.c,v 1.14 2009/05/23 02:26:03 mrg Exp $ */
3 /* $eterna: bozohttpd.c,v 1.159 2009/05/23 02:14:30 mrg Exp $ */
6 * Copyright (c) 1997-2009 Matthew R. Green
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
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.
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 /* this program is dedicated to the Great God of Processed Cheese */
36 * bozohttpd.c: minimal httpd; provides only these features:
37 * - HTTP/0.9 (by virtue of ..)
40 * - CGI/1.1 this will only be provided for "system" scripts
41 * - automatic "missing trailing slash" redirections
42 * - configurable translation of /~user/ to ~user/public_html,
43 * however, this does not include cgi-bin support
44 * - access lists via libwrap via inetd/tcpd
46 * - not that we do not even pretend to understand MIME, but
47 * rely only on the HTTP specification
49 * - automatic `index.html' generation
50 * - configurable server name
51 * - directory index generation
52 * - daemon mode (lacks libwrap support)
57 * requirements for minimal http/1.1 (at least, as documented in
58 * <draft-ietf-http-v11-spec-rev-06> which expired may 18, 1999):
60 * - 14.15: content-encoding handling. [1]
62 * - 14.16: content-length handling. this is only a SHOULD header
63 * thus we could just not send it ever. [1]
65 * - 14.17: content-type handling. [1]
67 * - 14.25/28: if-{,un}modified-since handling. maybe do this, but
68 * i really don't want to have to parse 3 differnet date formats
70 * [1] need to revisit to ensure proper behaviour
72 * and the following is a list of features that we do not need
73 * to have due to other limits, or are too lazy. there are more
74 * of these than are listed, but these are of particular note,
75 * and could perhaps be implemented.
77 * - 3.5/3.6: content/transfer codings. probably can ignore
78 * this? we "SHOULD"n't. but 4.4 says we should ignore a
79 * `content-length' header upon reciept of a `transfer-encoding'
82 * - 5.1.1: request methods. only MUST support GET and HEAD,
83 * but there are new ones besides POST that are currently
84 * supported: OPTIONS PUT DELETE TRACE and CONNECT, plus
85 * extensions not yet known?
87 * - 10.1: we can ignore informational status codes
89 * - 10.3.3/10.3.4/10.3.8: just use '302' codes always.
91 * - 14.1/14.2/14.3/14.27: we do not support Accept: headers..
92 * just ignore them and send the request anyway. they are
95 * - 14.5/14.16/14.35: we don't do ranges. from section 14.35.2
96 * `A server MAY ignore the Range header'. but it might be nice.
97 * since 20080301 we support simple range headers.
99 * - 14.9: we aren't a cache.
101 * - 14.15: content-md5 would be nice...
103 * - 14.24/14.26/14.27: be nice to support this...
105 * - 14.44: not sure about this Vary: header. ignore it for now.
109 #define INDEX_HTML "index.html"
111 #ifndef SERVER_SOFTWARE
112 #define SERVER_SOFTWARE "bozohttpd/20090522"
114 #ifndef DIRECT_ACCESS_FILE
115 #define DIRECT_ACCESS_FILE ".bzdirect"
117 #ifndef REDIRECT_FILE
118 #define REDIRECT_FILE ".bzredirect"
120 #ifndef ABSREDIRECT_FILE
121 #define ABSREDIRECT_FILE ".bzabsredirect"
125 * And so it begins ..
128 #include <sys/param.h>
129 #include <sys/socket.h>
130 #include <sys/time.h>
131 #include <sys/mman.h>
133 #include <arpa/inet.h>
150 #ifndef __attribute__
151 #define __attribute__(x)
152 #endif /* __attribute__ */
154 #include "bozohttpd.h"
156 #ifndef MAX_WAIT_TIME
157 #define MAX_WAIT_TIME 60 /* hang around for 60 seconds max */
160 /* variables and functions */
162 int bflag
; /* background; drop into daemon mode */
163 int fflag
; /* keep daemon mode in foreground */
164 static int eflag
; /* don't clean environ; -t/-U only */
165 const char *Iflag
= "http";/* bind port; default "http" */
167 int dflag
= 0; /* debugging level */
168 char *myname
; /* my name */
171 #define LOG_FTP LOG_DAEMON
174 static char *tflag
; /* root directory */
175 static char *Uflag
; /* user name to switch to */
176 static int Vflag
; /* unknown vhosts go to normal slashdir */
177 static int nflag
; /* avoid gethostby*() */
178 static int rflag
; /* make sure referrer = me unless url = / */
179 static int sflag
; /* log to stderr even if it is not a TTY */
180 static char *vpath
; /* virtual directory base */
182 size_t page_size
; /* page size */
183 char *slashdir
; /* www slash directory */
185 const char *server_software
= SERVER_SOFTWARE
;
186 const char *index_html
= INDEX_HTML
;
187 const char http_09
[] = "HTTP/0.9";
188 const char http_10
[] = "HTTP/1.0";
189 const char http_11
[] = "HTTP/1.1";
190 const char text_plain
[] = "text/plain";
192 static void usage(void);
193 static void alarmer(int);
194 volatile sig_atomic_t alarmhit
;
196 static void parse_request(char *, char **, char **, char **, char **);
197 static void clean_request(http_req
*request
);
198 static http_req
*read_request(void);
199 static struct headers
*addmerge_header(http_req
*, char *, char *, ssize_t
);
200 static int mmap_and_write_part(int, off_t
, size_t);
201 static void process_request(http_req
*);
202 static int check_direct_access(http_req
*request
);
203 static int transform_request(http_req
*, int *);
204 static void handle_redirect(http_req
*, const char *, int);
206 static int check_virtual(http_req
*);
207 static void check_bzredirect(http_req
*);
208 static void fix_url_percent(http_req
*);
209 static int process_proto(http_req
*, const char *);
210 static int process_method(http_req
*, const char *);
211 static void escape_html(http_req
*);
213 static const char *http_errors_short(int);
214 static const char *http_errors_long(int);
218 int (*bozoprintf
)(const char *, ...) = printf
;
219 ssize_t (*bozoread
)(int, void *, size_t) = read
;
220 ssize_t (*bozowrite
)(int, const void *, size_t) = write
;
221 int (*bozoflush
)(FILE *) = fflush
;
225 int main(int, char **);
230 warning("usage: %s [options] slashdir [myname]", progname
);
233 warning(" -d\t\t\tenable debug support");
235 warning(" -s\t\t\talways log to stderr");
236 #ifndef NO_USER_SUPPORT
237 warning(" -u\t\t\tenable ~user/public_html support");
238 warning(" -p dir\t\tchange `public_html' directory name]");
240 #ifndef NO_DYNAMIC_CONTENT
241 warning(" -M arg t c c11\tadd this mime extenstion");
243 #ifndef NO_CGIBIN_SUPPORT
244 #ifndef NO_DYNAMIC_CONTENT
245 warning(" -C arg prog\t\tadd this CGI handler");
247 warning(" -c cgibin\t\tenable cgi-bin support in this directory");
249 #ifndef NO_DAEMON_MODE
250 warning(" -b\t\t\tbackground and go into daemon mode");
251 warning(" -f\t\t\tkeep daemon mode in the foreground");
252 warning(" -i address\t\tbind on this address (daemon mode only)");
253 warning(" -I port\t\tbind on this port (daemon mode only)");
255 warning(" -S version\t\tset server version string");
256 warning(" -t dir\t\tchroot to `dir'");
257 warning(" -U username\t\tchange user to `user'");
258 warning(" -e\t\t\tdon't clean the environment (-t and -U only)");
259 warning(" -v virtualroot\tenable virtual host support in this directory");
260 warning(" -r\t\t\tmake sure sub-pages come from this host via referrer");
261 #ifndef NO_DIRINDEX_SUPPORT
262 warning(" -X\t\t\tenable automatic directory index support");
263 warning(" -H\t\t\thide files starting with a period (.) in index mode");
265 warning(" -x index\t\tchange default `index.html' file name");
266 #ifndef NO_SSL_SUPPORT
267 warning(" -Z cert privkey\tspecify path to server certificate and private key file\n"
268 "\t\t\tin pem format and enable bozohttpd in SSL mode");
269 #endif /* NO_SSL_SUPPORT */
270 error(1, "%s failed to start", progname
);
274 main(int argc
, char **argv
)
277 extern char **environ
;
282 uid
= 0; /* XXX gcc */
284 if ((progname
= strrchr(argv
[0], '/')) != NULL
)
289 openlog(progname
, LOG_PID
|LOG_NDELAY
, LOG_FTP
);
291 while ((c
= getopt(argc
, argv
,
292 "C:HI:M:S:U:VXZ:bc:defhi:np:rst:uv:x:z:")) != -1) {
296 #ifndef NO_DYNAMIC_CONTENT
297 /* make sure there's four arguments */
298 if (argc
- optind
< 3)
300 add_content_map_mime(optarg
, argv
[optind
],
301 argv
[optind
+1], argv
[optind
+2]);
305 error(1, "dynmic mime content support is not enabled");
307 #endif /* NO_DYNAMIC_CONTENT */
322 server_software
= optarg
;
325 #ifndef NO_SSL_SUPPORT
326 /* make sure there's two arguments */
327 if (argc
- optind
< 1)
329 ssl_set_opts(optarg
, argv
[optind
++]);
332 error(1, "ssl support is not enabled");
334 #endif /* NO_SSL_SUPPORT */
351 #ifndef NO_DAEMON_MODE
354 * test suite support - undocumented
355 * bflag == 2 (aka, -b -b) means to
356 * only process 1 per kid
380 #else /* NO_DAEMON_MODE */
386 error(1, "Daemon mode is not enabled");
388 #endif /* NO_DAEMON_MODE */
390 #ifndef NO_CGIBIN_SUPPORT
396 #ifndef NO_DYNAMIC_CONTENT
397 /* make sure there's two arguments */
398 if (argc
- optind
< 1)
400 add_content_map_cgi(optarg
, argv
[optind
++]);
403 error(1, "dynmic CGI handler support is not enabled");
405 #endif /* NO_DYNAMIC_CONTENT */
410 error(1, "CGI is not enabled");
412 #endif /* NO_CGIBIN_SUPPORT */
418 warning("Debugging is not enabled");
422 #ifndef NO_USER_SUPPORT
424 public_html
= optarg
;
438 error(1, "User support is not enabled");
440 #endif /* NO_USER_SUPPORT */
442 #ifndef NO_DIRINDEX_SUPPORT
454 error(1, "directory indexing is not enabled");
456 #endif /* NO_DIRINDEX_SUPPORT */
467 myname
= bozomalloc(MAXHOSTNAMELEN
+1);
468 /* XXX we do not check for FQDN here */
469 if (gethostname(myname
, MAXHOSTNAMELEN
+1) < 0)
470 error(1, "gethostname");
471 myname
[MAXHOSTNAMELEN
] = '\0';
472 } else if (argc
== 2)
478 debug((DEBUG_OBESE
, "myname is %s, slashdir is %s", myname
, slashdir
));
481 page_size
= (long)sysconf(_SC_PAGESIZE
);
487 * initialise ssl and daemon mode if necessary.
493 * prevent info leakage between different compartments.
494 * some PATH values in the environment would be invalided
495 * by chroot. cross-user settings might result in undesirable
498 if ((tflag
!= NULL
|| Uflag
!= NULL
) && !eflag
) {
504 * look up user/group information.
509 if ((pw
= getpwnam(Uflag
)) == NULL
)
510 error(1, "getpwnam(%s): %s", Uflag
, strerror(errno
));
511 if (initgroups(pw
->pw_name
, pw
->pw_gid
) == -1)
512 error(1, "initgroups: %s", strerror(errno
));
513 if (setgid(pw
->pw_gid
) == -1)
514 error(1, "setgid(%u): %s", pw
->pw_gid
, strerror(errno
));
522 if (chdir(tflag
) == -1)
523 error(1, "chdir(%s): %s", tflag
, strerror(errno
));
524 if (chroot(tflag
) == -1)
525 error(1, "chroot(%s): %s", tflag
, strerror(errno
));
529 if (setuid(uid
) == -1)
530 error(1, "setuid(%d): %s", uid
, strerror(errno
));
533 * read and process the HTTP request.
536 request
= read_request();
538 process_request(request
);
539 clean_request(request
);
549 static char date
[40];
553 /* Sun, 06 Nov 1994 08:49:37 GMT */
555 tm
= gmtime(&now
); /* HTTP/1.1 spec rev 06 sez GMT only */
556 strftime(date
, sizeof date
, "%a, %d %b %Y %H:%M:%S GMT", tm
);
561 * convert "in" into the three parts of a request (first line).
562 * we allocate into file and query, but return pointers into
563 * "in" for proto and method.
566 parse_request(char *in
, char **method
, char **file
, char **query
, char **proto
)
571 debug((DEBUG_EXPLODING
, "parse in: %s", in
));
572 *method
= *file
= *query
= *proto
= NULL
;
574 len
= (ssize_t
)strlen(in
);
575 val
= bozostrnsep(&in
, " \t\n\r", &len
);
576 if (len
< 1 || val
== NULL
)
580 while (*in
== ' ' || *in
== '\t')
582 val
= bozostrnsep(&in
, " \t\n\r", &len
);
592 *query
= strchr(*file
, '?');
597 while (*in
&& (*in
== ' ' || *in
== '\t'))
603 /* allocate private copies */
604 *file
= strdup(*file
);
606 *query
= strdup(*query
);
608 debug((DEBUG_FAT
, "url: method: \"%s\" file: \"%s\" query: \"%s\" proto: \"%s\"",
609 *method
, *file
, *query
, *proto
));
613 * send a HTTP/1.1 408 response if we timeout.
623 * cleanup a http_req after use
626 clean_request(http_req
*request
)
628 struct headers
*hdr
, *ohdr
= NULL
;
633 /* clean up request */
634 #define MF(x) if (request->x) free(request->x)
641 auth_cleanup(request
);
642 for (hdr
= SIMPLEQ_FIRST(&request
->hr_headers
); hdr
;
643 hdr
= SIMPLEQ_NEXT(hdr
, h_next
)) {
657 * This function reads a http request from stdin, returning a pointer to a
658 * http_req structure, describing the request.
664 char *str
, *val
, *method
, *file
, *proto
, *query
;
665 char *host
, *addr
, *port
;
667 char hbuf
[NI_MAXHOST
], abuf
[NI_MAXHOST
];
668 struct sockaddr_storage ss
;
675 * if we're in daemon mode, daemon_fork() will return here once
676 * for each child, then we can setup SSL.
681 request
= bozomalloc(sizeof *request
);
682 memset(request
, 0, sizeof *request
);
683 request
->hr_allow
= request
->hr_host
= NULL
;
684 request
->hr_content_type
= request
->hr_content_length
= NULL
;
685 request
->hr_range
= NULL
;
686 request
->hr_last_byte_pos
= -1;
687 request
->hr_if_modified_since
= NULL
;
688 request
->hr_file
= NULL
;
691 if (getpeername(0, (struct sockaddr
*)&ss
, &slen
) < 0)
694 if (getnameinfo((struct sockaddr
*)&ss
, slen
,
695 abuf
, sizeof abuf
, NULL
, 0, NI_NUMERICHOST
) == 0)
699 if (nflag
== 0 && getnameinfo((struct sockaddr
*)&ss
, slen
,
700 hbuf
, sizeof hbuf
, NULL
, 0, 0) == 0)
706 request
->hr_remotehost
= bozostrdup(host
);
708 request
->hr_remoteaddr
= bozostrdup(addr
);
710 if (getsockname(0, (struct sockaddr
*)&ss
, &slen
) < 0)
713 if (getnameinfo((struct sockaddr
*)&ss
, slen
, NULL
, 0,
714 bufport
, sizeof bufport
, NI_NUMERICSERV
) == 0)
720 request
->hr_serverport
= bozostrdup(port
);
723 * setup a timer to make sure the request is not hung
725 sa
.sa_handler
= alarmer
;
726 sigemptyset(&sa
.sa_mask
);
727 sigaddset(&sa
.sa_mask
, SIGALRM
);
729 sigaction(SIGALRM
, &sa
, NULL
); /* XXX */
731 alarm(MAX_WAIT_TIME
);
732 while ((str
= bozodgetln(STDIN_FILENO
, &len
, bozoread
)) != NULL
) {
735 (void)http_error(408, NULL
, "request timed out");
743 (void)http_error(404, NULL
, "null method");
747 warning("got request ``%s'' from host %s to port %s",
749 host
? host
: addr
? addr
: "<local>",
750 port
? port
: "<stdin>");
752 debug((DEBUG_FAT
, "read_req, getting request: ``%s''",
756 /* we allocate return space in file and query only */
757 parse_request(str
, &method
, &file
, &query
, &proto
);
758 request
->hr_file
= file
;
759 request
->hr_query
= query
;
760 if (method
== NULL
) {
761 (void)http_error(404, NULL
, "null method");
765 (void)http_error(404, NULL
, "null file");
770 * note that we parse the proto first, so that we
771 * can more properly parse the method and the url.
774 if (process_proto(request
, proto
) ||
775 process_method(request
, method
)) {
779 debug((DEBUG_FAT
, "got file \"%s\" query \"%s\"",
781 request
->hr_query
? request
->hr_query
: "<none>"));
783 /* http/0.9 has no header processing */
784 if (request
->hr_proto
== http_09
)
786 } else { /* incoming headers */
792 val
= bozostrnsep(&str
, ":", &len
);
793 debug((DEBUG_EXPLODING
,
794 "read_req2: after bozostrnsep: str ``%s'' val ``%s''",
796 if (val
== NULL
|| len
== -1) {
797 (void)http_error(404, request
, "no header");
800 while (*str
== ' ' || *str
== '\t')
802 while (*val
== ' ' || *val
== '\t')
805 if (auth_check_headers(request
, val
, str
, len
))
808 hdr
= addmerge_header(request
, val
, str
, len
);
810 if (strcasecmp(hdr
->h_header
, "content-type") == 0)
811 request
->hr_content_type
= hdr
->h_value
;
812 else if (strcasecmp(hdr
->h_header
, "content-length") == 0)
813 request
->hr_content_length
= hdr
->h_value
;
814 else if (strcasecmp(hdr
->h_header
, "host") == 0)
815 request
->hr_host
= hdr
->h_value
;
816 /* HTTP/1.1 rev06 draft spec: 14.20 */
817 else if (strcasecmp(hdr
->h_header
, "expect") == 0) {
818 (void)http_error(417, request
, "we don't support Expect:");
821 else if (strcasecmp(hdr
->h_header
, "referrer") == 0 ||
822 strcasecmp(hdr
->h_header
, "referer") == 0)
823 request
->hr_referrer
= hdr
->h_value
;
824 else if (strcasecmp(hdr
->h_header
, "range") == 0)
825 request
->hr_range
= hdr
->h_value
;
826 else if (strcasecmp(hdr
->h_header
, "if-modified-since") == 0)
827 request
->hr_if_modified_since
= hdr
->h_value
;
829 debug((DEBUG_FAT
, "adding header %s: %s",
830 hdr
->h_header
, hdr
->h_value
));
833 alarm(MAX_WAIT_TIME
);
836 /* now, clear it all out */
838 signal(SIGALRM
, SIG_DFL
);
841 if (request
->hr_method
== HTTP_POST
&& request
->hr_content_length
== NULL
) {
842 (void)http_error(400, request
, "missing content length");
846 /* HTTP/1.1 draft rev-06, 14.23 & 19.6.1.1 */
847 if (request
->hr_proto
== http_11
&& request
->hr_host
== NULL
) {
848 (void)http_error(400, request
, "missing Host header");
852 if (request
->hr_range
!= NULL
) {
853 debug((DEBUG_FAT
, "hr_range: %s", request
->hr_range
));
854 /* support only simple ranges %d- and %d-%d */
855 if (strchr(request
->hr_range
, ',') == NULL
) {
856 const char *rstart
, *dash
;
858 rstart
= strchr(request
->hr_range
, '=');
859 if (rstart
!= NULL
) {
861 dash
= strchr(rstart
, '-');
862 if (dash
!= NULL
&& dash
!= rstart
) {
864 request
->hr_have_range
= 1;
865 request
->hr_first_byte_pos
=
866 strtoll(rstart
, NULL
, 10);
867 if (request
->hr_first_byte_pos
< 0)
868 request
->hr_first_byte_pos
= 0;
870 request
->hr_last_byte_pos
=
871 strtoll(dash
, NULL
, 10);
872 if (request
->hr_last_byte_pos
< 0)
873 request
->hr_last_byte_pos
= -1;
880 debug((DEBUG_FAT
, "read_request returns url %s in request",
885 clean_request(request
);
887 /* If SSL enabled cleanup SSL structure. */
894 * add or merge this header (val: str) into the requests list
896 static struct headers
*
897 addmerge_header(http_req
*request
, char *val
, char *str
, ssize_t len
)
901 /* do we exist already? */
902 SIMPLEQ_FOREACH(hdr
, &request
->hr_headers
, h_next
) {
903 if (strcasecmp(val
, hdr
->h_header
) == 0)
908 /* yup, merge it in */
911 if (asprintf(&nval
, "%s, %s", hdr
->h_value
, str
) == -1) {
912 (void)http_error(500, NULL
,
913 "memory allocation failure");
919 /* nope, create a new one */
921 hdr
= bozomalloc(sizeof *hdr
);
922 hdr
->h_header
= bozostrdup(val
);
924 hdr
->h_value
= bozostrdup(str
);
926 hdr
->h_value
= bozostrdup(" ");
928 SIMPLEQ_INSERT_TAIL(&request
->hr_headers
, hdr
, h_next
);
929 request
->hr_nheaders
++;
936 mmap_and_write_part(int fd
, off_t first_byte_pos
, size_t sz
)
938 size_t mappedsz
, wroffset
;
944 * we need to ensure that both the size *and* offset arguments to
945 * mmap() are page-aligned. our formala for this is:
947 * input offset: first_byte_pos
950 * mapped offset = page align truncate (input offset)
952 * page align extend (input offset - mapped offset + input size)
953 * write offset = input offset - mapped offset
955 * we use the write offset in all writes
957 mappedoffset
= first_byte_pos
& ~(page_size
- 1);
958 mappedsz
= (first_byte_pos
- mappedoffset
+ sz
+ page_size
- 1) &
960 wroffset
= first_byte_pos
- mappedoffset
;
962 addr
= mmap(0, mappedsz
, PROT_READ
, MAP_SHARED
, fd
, mappedoffset
);
963 if (addr
== (char *)-1) {
964 warning("mmap failed: %s", strerror(errno
));
969 #ifdef MADV_SEQUENTIAL
970 (void)madvise(addr
, sz
, MADV_SEQUENTIAL
);
973 if (bozowrite(STDOUT_FILENO
, addr
+ wroffset
, WRSZ
) != WRSZ
) {
974 warning("write failed: %s", strerror(errno
));
977 debug((DEBUG_OBESE
, "wrote %d bytes", WRSZ
));
981 if (sz
&& (size_t)bozowrite(STDOUT_FILENO
, addr
+ wroffset
, sz
) != sz
) {
982 warning("final write failed: %s", strerror(errno
));
985 debug((DEBUG_OBESE
, "wrote %d bytes", (int)sz
));
987 if (munmap(mappedaddr
, mappedsz
) < 0) {
988 warning("munmap failed");
996 parse_http_date(const char *val
, time_t *timestamp
)
1001 if ((remainder
= strptime(val
, "%a, %d %b %Y %T GMT", &tm
)) == NULL
&&
1002 (remainder
= strptime(val
, "%a, %d-%b-%y %T GMT", &tm
)) == NULL
&&
1003 (remainder
= strptime(val
, "%a %b %d %T %Y", &tm
)) == NULL
)
1004 return 0; /* Invalid HTTP date format */
1007 return 0; /* No trailing garbage */
1009 *timestamp
= timegm(&tm
);
1014 * process_request does the following:
1015 * - check the request is valid
1016 * - process cgi-bin if necessary
1017 * - transform a filename if necesarry
1018 * - return the HTTP request
1021 process_request(http_req
*request
)
1026 const char *type
, *encoding
;
1030 * note that transform_request chdir()'s if required. also note
1031 * that cgi is handed here. if transform_request() returns 0
1032 * then the request has been handled already.
1034 if (transform_request(request
, &isindex
) == 0)
1037 file
= request
->hr_file
;
1039 fd
= open(file
, O_RDONLY
);
1041 debug((DEBUG_FAT
, "open failed: %s", strerror(errno
)));
1043 (void)http_error(403, request
, "no permission to open file");
1044 else if (errno
== ENOENT
) {
1045 if (directory_index(request
, file
, isindex
))
1048 (void)http_error(404, request
, "no file");
1050 (void)http_error(500, request
, "open file");
1053 if (fstat(fd
, &sb
) < 0) {
1054 (void)http_error(500, request
, "can't fstat");
1057 if (S_ISDIR(sb
.st_mode
)) {
1058 handle_redirect(request
, NULL
, 0);
1062 if (request
->hr_if_modified_since
&&
1063 parse_http_date(request
->hr_if_modified_since
, ×tamp
) &&
1064 timestamp
>= sb
.st_mtime
) {
1065 /* XXX ignore subsecond of timestamp */
1066 bozoprintf("%s 304 Not Modified\r\n", request
->hr_proto
);
1072 /* validate requested range */
1073 if (request
->hr_last_byte_pos
== -1 ||
1074 request
->hr_last_byte_pos
>= sb
.st_size
)
1075 request
->hr_last_byte_pos
= sb
.st_size
- 1;
1076 if (request
->hr_have_range
&&
1077 request
->hr_first_byte_pos
> request
->hr_last_byte_pos
) {
1078 request
->hr_have_range
= 0; /* punt */
1079 request
->hr_first_byte_pos
= 0;
1080 request
->hr_last_byte_pos
= sb
.st_size
- 1;
1082 debug((DEBUG_FAT
, "have_range %d first_pos %qd last_pos %qd",
1083 request
->hr_have_range
,
1084 request
->hr_first_byte_pos
, request
->hr_last_byte_pos
));
1085 if (request
->hr_have_range
)
1086 bozoprintf("%s 206 Partial Content\r\n", request
->hr_proto
);
1088 bozoprintf("%s 200 OK\r\n", request
->hr_proto
);
1090 if (request
->hr_proto
!= http_09
) {
1091 type
= content_type(request
, file
);
1092 encoding
= content_encoding(request
, file
);
1094 print_header(request
, &sb
, type
, encoding
);
1099 if (request
->hr_method
!= HTTP_HEAD
) {
1100 off_t szleft
, cur_byte_pos
;
1101 static size_t mmapsz
= MMAPSZ
;
1104 request
->hr_last_byte_pos
- request
->hr_first_byte_pos
+ 1;
1105 cur_byte_pos
= request
->hr_first_byte_pos
;
1111 /* This should take care of the first unaligned chunk */
1112 if ((cur_byte_pos
& (page_size
- 1)) != 0)
1113 sz
= cur_byte_pos
& ~page_size
;
1114 if ((off_t
)mmapsz
< szleft
)
1118 if (mmap_and_write_part(fd
, cur_byte_pos
, sz
)) {
1119 if (errno
== ENOMEM
) {
1121 if (mmapsz
>= page_size
)
1133 close(STDIN_FILENO
);
1134 close(STDOUT_FILENO
);
1135 /*close(STDERR_FILENO);*/
1139 * deal with virtual host names; we do this:
1140 * if we have a virtual path root (vpath), and we are given a
1141 * virtual host spec (Host: ho.st or http://ho.st/), see if this
1142 * directory exists under vpath. if it does, use this as the
1146 check_virtual(http_req
*request
)
1148 char *file
= request
->hr_file
, *s
;
1149 struct dirent
**list
;
1157 * convert http://virtual.host/ to request->hr_host
1159 debug((DEBUG_OBESE
, "checking for http:// virtual host in ``%s''", file
));
1160 if (strncasecmp(file
, "http://", 7) == 0) {
1161 /* we would do virtual hosting here? */
1163 s
= strchr(file
, '/');
1164 /* HTTP/1.1 draft rev-06, 5.2: URI takes precedence over Host: */
1165 request
->hr_host
= file
;
1166 request
->hr_file
= bozostrdup(s
? s
: "/");
1167 debug((DEBUG_OBESE
, "got host ``%s'' file is now ``%s''",
1168 request
->hr_host
, request
->hr_file
));
1169 } else if (!request
->hr_host
)
1174 * ok, we have a virtual host, use scandir(3) to find a case
1175 * insensitive match for the virtual host we are asked for.
1176 * note that if the virtual host is the same as the master,
1177 * we don't need to do anything special.
1179 len
= strlen(request
->hr_host
);
1181 "check_virtual: checking host `%s' under vpath `%s' for file `%s'",
1182 request
->hr_host
, vpath
, request
->hr_file
));
1183 if (strncasecmp(myname
, request
->hr_host
, len
) != 0) {
1185 for (i
= scandir(vpath
, &list
, 0, 0); i
--; list
++) {
1186 debug((DEBUG_OBESE
, "looking at dir``%s''",
1188 if (strncasecmp((*list
)->d_name
, request
->hr_host
,
1190 /* found it, punch it */
1191 myname
= (*list
)->d_name
;
1192 if (asprintf(&s
, "%s/%s", vpath
, myname
) < 0)
1193 error(1, "asprintf");
1200 return http_error(404, request
, "unknown URL");
1207 * ok, nailed the correct slashdir, chdir to it
1210 return http_error(404, request
, "can't chdir to slashdir");
1214 /* make sure we're not trying to access special files */
1216 check_special_files(http_req
*request
, const char *name
)
1218 /* ensure basename(name) != special files */
1219 if (strcmp(name
, DIRECT_ACCESS_FILE
) == 0)
1220 return http_error(403, request
,
1221 "no permission to open direct access file");
1222 if (strcmp(name
, REDIRECT_FILE
) == 0)
1223 return http_error(403, request
,
1224 "no permission to open redirect file");
1225 if (strcmp(name
, ABSREDIRECT_FILE
) == 0)
1226 return http_error(403, request
,
1227 "no permission to open redirect file");
1228 return auth_check_special_files(request
, name
);
1232 * checks to see if this request has a valid .bzredirect file. returns
1233 * 0 on failure and 1 on success.
1236 check_bzredirect(http_req
*request
)
1239 char dir
[MAXPATHLEN
], redir
[MAXPATHLEN
], redirpath
[MAXPATHLEN
+ 1];
1240 char *basename
, *finalredir
;
1244 * if this pathname is really a directory, but doesn't end in /,
1245 * use it as the directory to look for the redir file.
1247 snprintf(dir
, sizeof(dir
), "%s", request
->hr_file
+ 1);
1248 debug((DEBUG_FAT
, "check_bzredirect: dir %s", dir
));
1249 basename
= strrchr(dir
, '/');
1251 if ((!basename
|| basename
[1] != '\0') &&
1252 lstat(dir
, &sb
) == 0 && S_ISDIR(sb
.st_mode
))
1254 else if (basename
== NULL
)
1258 check_special_files(request
, basename
);
1261 snprintf(redir
, sizeof(redir
), "%s/%s", dir
, REDIRECT_FILE
);
1262 if (lstat(redir
, &sb
) == 0) {
1263 if (S_ISLNK(sb
.st_mode
) == 0)
1267 snprintf(redir
, sizeof(redir
), "%s/%s", dir
, ABSREDIRECT_FILE
);
1268 if (lstat(redir
, &sb
) < 0 || S_ISLNK(sb
.st_mode
) == 0)
1272 debug((DEBUG_FAT
, "check_bzredirect: calling readlink"));
1273 rv
= readlink(redir
, redirpath
, sizeof redirpath
- 1);
1274 if (rv
== -1 || rv
== 0) {
1275 debug((DEBUG_FAT
, "readlink failed"));
1278 redirpath
[rv
] = '\0';
1279 debug((DEBUG_FAT
, "readlink returned \"%s\"", redirpath
));
1281 /* now we have the link pointer, redirect to the real place */
1283 finalredir
= redirpath
;
1285 snprintf(finalredir
= redir
, sizeof(redir
), "/%s/%s", dir
,
1288 debug((DEBUG_FAT
, "check_bzredirect: new redir %s", finalredir
));
1289 handle_redirect(request
, finalredir
, absolute
);
1293 * checks to see if this request has a valid .bzdirect file. returns
1294 * 0 on failure and 1 on success.
1297 check_direct_access(http_req
*request
)
1301 char dir
[MAXPATHLEN
], dirfile
[MAXPATHLEN
], *basename
;
1303 snprintf(dir
, sizeof(dir
), "%s", request
->hr_file
+ 1);
1304 debug((DEBUG_FAT
, "check_bzredirect: dir %s", dir
));
1305 basename
= strrchr(dir
, '/');
1307 if ((!basename
|| basename
[1] != '\0') &&
1308 lstat(dir
, &sb
) == 0 && S_ISDIR(sb
.st_mode
))
1310 else if (basename
== NULL
)
1314 check_special_files(request
, basename
);
1317 snprintf(dirfile
, sizeof(dirfile
), "%s/%s", dir
, DIRECT_ACCESS_FILE
);
1318 if (stat(dirfile
, &sb
) < 0 ||
1319 (fp
= fopen(dirfile
, "r")) == NULL
)
1326 * transform_request does this:
1327 * - ``expand'' %20 crapola
1328 * - punt if it doesn't start with /
1329 * - check rflag / referrer
1330 * - look for "http://myname/" and deal with it.
1331 * - maybe call process_cgi()
1332 * - check for ~user and call user_transform() if so
1333 * - if the length > 1, check for trailing slash. if so,
1334 * add the index.html file
1335 * - if the length is 1, return the index.html file
1336 * - disallow anything ending up with a file starting
1337 * at "/" or having ".." in it.
1338 * - anything else is a really weird internal error
1339 * - returns malloced file to serve, if unhandled
1342 transform_request(http_req
*request
, int *isindex
)
1344 char *file
, *newfile
= NULL
;
1349 debug((DEBUG_FAT
, "tf_req: file %s", request
->hr_file
));
1350 fix_url_percent(request
);
1351 if (check_virtual(request
)) {
1354 file
= request
->hr_file
;
1356 if (file
[0] != '/') {
1357 (void)http_error(404, request
, "unknown URL");
1361 check_bzredirect(request
);
1364 int to_indexhtml
= 0;
1366 #define TOP_PAGE(x) (strcmp((x), "/") == 0 || \
1367 strcmp((x) + 1, index_html) == 0 || \
1368 strcmp((x) + 1, "favicon.ico") == 0)
1370 debug((DEBUG_EXPLODING
, "checking rflag"));
1372 * first check that this path isn't allowed via .bzdirect file,
1373 * and then check referrer; make sure that people come via the
1374 * real name... otherwise if we aren't looking at / or
1375 * /index.html, redirect... we also special case favicon.ico.
1377 if (check_direct_access(request
))
1379 else if (request
->hr_referrer
) {
1380 const char *r
= request
->hr_referrer
;
1383 "checking referrer \"%s\" vs myname %s", r
, myname
));
1384 if (strncmp(r
, "http://", 7) != 0 ||
1385 (strncasecmp(r
+ 7, myname
, strlen(myname
)) != 0 &&
1389 const char *h
= request
->hr_host
;
1391 debug((DEBUG_FAT
, "url has no referrer at all"));
1392 /* if there's no referrer, let / or /index.html past */
1393 if (!TOP_PAGE(file
) ||
1394 (h
&& strncasecmp(h
, myname
, strlen(myname
)) != 0))
1399 char *slashindexhtml
;
1401 if (asprintf(&slashindexhtml
, "/%s", index_html
) < 0)
1402 error(1, "asprintf");
1403 debug((DEBUG_FAT
, "rflag: redirecting %s to %s", file
, slashindexhtml
));
1404 handle_redirect(request
, slashindexhtml
, 0);
1405 free(slashindexhtml
);
1412 #ifndef NO_USER_SUPPORT
1413 } else if (len
> 1 && uflag
&& file
[1] == '~') {
1414 if (file
[2] == '\0') {
1415 (void)http_error(404, request
, "missing username");
1418 if (strchr(file
+ 2, '/') == NULL
) {
1419 handle_redirect(request
, NULL
, 0);
1422 debug((DEBUG_FAT
, "calling user_transform"));
1424 return (user_transform(request
, isindex
));
1425 #endif /* NO_USER_SUPPORT */
1426 } else if (len
> 1) {
1427 debug((DEBUG_FAT
, "file[len-1] == %c", file
[len
-1]));
1428 if (file
[len
-1] == '/') { /* append index.html */
1430 debug((DEBUG_FAT
, "appending index.html"));
1431 newfile
= bozomalloc(len
+ strlen(index_html
) + 1);
1432 strcpy(newfile
, file
+ 1);
1433 strcat(newfile
, index_html
);
1435 newfile
= bozostrdup(file
+ 1);
1436 } else if (len
== 1) {
1437 debug((DEBUG_EXPLODING
, "tf_req: len == 1"));
1438 newfile
= bozostrdup(index_html
);
1440 } else { /* len == 0 ? */
1441 (void)http_error(500, request
, "request->hr_file is nul?");
1445 if (newfile
== NULL
) {
1446 (void)http_error(500, request
, "internal failure");
1451 * look for "http://myname/" and deal with it as necessary.
1455 * stop traversing outside our domain
1457 * XXX true security only comes from our parent using chroot(2)
1458 * before execve(2)'ing us. or our own built in chroot(2) support.
1460 if (*newfile
== '/' || strcmp(newfile
, "..") == 0 ||
1461 strstr(newfile
, "/..") || strstr(newfile
, "../")) {
1462 (void)http_error(403, request
, "illegal request");
1466 if (auth_check(request
, newfile
))
1469 if (strlen(newfile
)) {
1470 free(request
->hr_file
);
1471 request
->hr_file
= newfile
;
1474 if (process_cgi(request
))
1477 debug((DEBUG_FAT
, "transform_request set: %s", newfile
));
1480 debug((DEBUG_FAT
, "transform_request returning: 0"));
1487 * do automatic redirection -- if there are query parameters for the URL
1488 * we will tack these on to the new (redirected) URL.
1491 handle_redirect(http_req
*request
, const char *url
, int absolute
)
1498 if (asprintf(&urlbuf
, "/%s/", request
->hr_file
) < 0)
1499 error(1, "asprintf");
1504 if (request
->hr_query
&& strlen(request
->hr_query
)) {
1508 if (request
->hr_serverport
&& strcmp(request
->hr_serverport
, "80") != 0)
1509 snprintf(portbuf
, sizeof(portbuf
), ":%s",
1510 request
->hr_serverport
);
1513 warning("redirecting %s%s%s", myname
, portbuf
, url
);
1514 debug((DEBUG_FAT
, "redirecting %s", url
));
1515 bozoprintf("%s 301 Document Moved\r\n", request
->hr_proto
);
1516 if (request
->hr_proto
!= http_09
)
1517 print_header(request
, NULL
, "text/html", NULL
);
1518 if (request
->hr_proto
!= http_09
) {
1519 bozoprintf("Location: http://");
1521 bozoprintf("%s%s", myname
, portbuf
);
1523 bozoprintf("%s?%s\r\n", url
, request
->hr_query
);
1525 bozoprintf("%s\r\n", url
);
1529 if (request
->hr_method
== HTTP_HEAD
)
1531 bozoprintf("<html><head><title>Document Moved</title></head>\n");
1532 bozoprintf("<body><h1>Document Moved</h1>\n");
1533 bozoprintf("This document had moved <a href=\"http://");
1536 bozoprintf("%s?%s", url
, request
->hr_query
);
1538 bozoprintf("%s%s%s?%s", myname
, portbuf
, url
, request
->hr_query
);
1541 bozoprintf("%s", url
);
1543 bozoprintf("%s%s%s", myname
, portbuf
, url
);
1545 bozoprintf("\">here</a>\n");
1546 bozoprintf("</body></html>\n");
1553 /* generic header printing routine */
1555 print_header(http_req
*request
, struct stat
*sbp
, const char *type
,
1556 const char *encoding
)
1560 bozoprintf("Date: %s\r\n", http_date());
1561 bozoprintf("Server: %s\r\n", server_software
);
1562 bozoprintf("Accept-Ranges: bytes\r\n");
1567 tm
= gmtime(&sbp
->st_mtime
);
1568 strftime(filedate
, sizeof filedate
,
1569 "%a, %d %b %Y %H:%M:%S GMT", tm
);
1570 bozoprintf("Last-Modified: %s\r\n", filedate
);
1573 bozoprintf("Content-Type: %s\r\n", type
);
1574 if (encoding
&& *encoding
)
1575 bozoprintf("Content-Encoding: %s\r\n", encoding
);
1577 if (request
->hr_have_range
) {
1578 len
= request
->hr_last_byte_pos
- request
->hr_first_byte_pos
+1;
1579 bozoprintf("Content-Range: bytes %qd-%qd/%qd\r\n",
1580 (long long) request
->hr_first_byte_pos
,
1581 (long long) request
->hr_last_byte_pos
,
1582 (long long) sbp
->st_size
);
1586 bozoprintf("Content-Length: %qd\r\n", (long long)len
);
1588 if (request
&& request
->hr_proto
== http_11
)
1589 bozoprintf("Connection: close\r\n");
1593 /* this escape HTML tags */
1595 escape_html(http_req
*request
)
1598 char *url
= request
->hr_file
, *tmp
;
1600 for (i
= 0, j
= 0; url
[i
]; i
++) {
1615 if ((tmp
= (char *) malloc(strlen(url
) + j
)) == 0)
1617 * ouch, but we are only called from an error context, and
1618 * most paths here come from malloc(3) failures anyway...
1619 * we could completely punt and just exit, but isn't returning
1620 * an not-quite-correct error better than nothing at all?
1624 for (i
= 0, j
= 0; url
[i
]; i
++) {
1627 memcpy(tmp
+ j
, "<", 4);
1631 memcpy(tmp
+ j
, ">", 4);
1635 memcpy(tmp
+ j
, "&", 5);
1644 free(request
->hr_file
);
1645 request
->hr_file
= tmp
;
1648 /* this fixes the %HH hack that RFC2396 requires. */
1650 fix_url_percent(http_req
*request
)
1652 char *s
, *t
, buf
[3], *url
;
1653 char *end
; /* if end is not-zero, we don't translate beyond that */
1655 url
= request
->hr_file
;
1657 end
= url
+ strlen(url
);
1659 /* fast forward to the first % */
1660 if ((s
= strchr(url
, '%')) == NULL
)
1665 if (end
&& s
>= end
) {
1666 debug((DEBUG_EXPLODING
, "fu_%%: past end, filling out.."));
1671 debug((DEBUG_EXPLODING
, "fu_%%: got s == %%, s[1]s[2] == %c%c",
1673 if (s
[1] == '\0' || s
[2] == '\0') {
1674 (void)http_error(400, request
,
1675 "percent hack missing two chars afterwards");
1678 if (s
[1] == '0' && s
[2] == '0') {
1679 (void)http_error(404, request
, "percent hack was %00");
1682 if (s
[1] == '2' && s
[2] == 'f') {
1683 (void)http_error(404, request
, "percent hack was %2f (/)");
1691 *t
= (char)strtol(buf
, NULL
, 16);
1692 debug((DEBUG_EXPLODING
, "fu_%%: strtol put '%02x' into *t", *t
));
1694 (void)http_error(400, request
, "percent hack got a 0 back");
1698 while (*s
&& *s
!= '%') {
1699 if (end
&& s
>= end
)
1711 debug((DEBUG_FAT
, "fix_url_percent returns %s in url", request
->hr_file
));
1715 * process each type of HTTP method, setting this HTTP requests
1718 static struct method_map
{
1722 { "GET", HTTP_GET
, },
1723 { "POST", HTTP_POST
, },
1724 { "HEAD", HTTP_HEAD
, },
1725 #if 0 /* other non-required http/1.1 methods */
1726 { "OPTIONS", HTTP_OPTIONS
, },
1727 { "PUT", HTTP_PUT
, },
1728 { "DELETE", HTTP_DELETE
, },
1729 { "TRACE", HTTP_TRACE
, },
1730 { "CONNECT", HTTP_CONNECT
, },
1736 process_method(http_req
*request
, const char *method
)
1738 struct method_map
*mmp
;
1740 if (request
->hr_proto
== http_11
)
1741 request
->hr_allow
= "GET, HEAD, POST";
1743 for (mmp
= method_map
; mmp
->name
; mmp
++)
1744 if (strcasecmp(method
, mmp
->name
) == 0) {
1745 request
->hr_method
= mmp
->type
;
1746 request
->hr_methodstr
= mmp
->name
;
1750 return http_error(404, request
, "unknown method");
1754 * as the prototype string is not constant (eg, "HTTP/1.1" is equivalent
1755 * to "HTTP/001.01"), we MUST parse this.
1758 process_proto(http_req
*request
, const char *proto
)
1760 char majorstr
[16], *minorstr
;
1761 int majorint
, minorint
;
1763 if (proto
== NULL
) {
1765 request
->hr_proto
= http_09
;
1766 debug((DEBUG_FAT
, "request %s is http/0.9", request
->hr_file
));
1770 if (strncasecmp(proto
, "HTTP/", 5) != 0)
1772 strncpy(majorstr
, proto
+ 5, sizeof majorstr
);
1773 majorstr
[sizeof(majorstr
)-1] = 0;
1774 minorstr
= strchr(majorstr
, '.');
1775 if (minorstr
== NULL
)
1779 majorint
= atoi(majorstr
);
1780 minorint
= atoi(minorstr
);
1789 request
->hr_proto
= http_10
;
1790 else if (minorint
== 1)
1791 request
->hr_proto
= http_11
;
1795 debug((DEBUG_FAT
, "request %s is %s", request
->hr_file
,
1796 request
->hr_proto
));
1797 SIMPLEQ_INIT(&request
->hr_headers
);
1798 request
->hr_nheaders
= 0;
1802 return http_error(404, NULL
, "unknown prototype");
1807 debug__(int level
, const char *fmt
, ...)
1812 /* only log if the level is low enough */
1819 vfprintf(stderr
, fmt
, ap
);
1820 fputs("\n", stderr
);
1822 vsyslog(LOG_DEBUG
, fmt
, ap
);
1828 /* these are like warn() and err(), except for syslog not stderr */
1830 warning(const char *fmt
, ...)
1835 if (sflag
|| isatty(STDERR_FILENO
)) {
1836 //fputs("warning: ", stderr);
1837 vfprintf(stderr
, fmt
, ap
);
1838 fputs("\n", stderr
);
1840 vsyslog(LOG_INFO
, fmt
, ap
);
1845 error(int code
, const char *fmt
, ...)
1850 if (sflag
|| isatty(STDERR_FILENO
)) {
1851 //fputs("error: ", stderr);
1852 vfprintf(stderr
, fmt
, ap
);
1853 fputs("\n", stderr
);
1855 vsyslog(LOG_ERR
, fmt
, ap
);
1860 /* the follow functions and variables are used in handling HTTP errors */
1863 http_error(int code
, http_req
*request
, const char *msg
)
1865 static char buf
[BUFSIZ
];
1867 const char *header
= http_errors_short(code
);
1868 const char *reason
= http_errors_long(code
);
1869 const char *proto
= (request
&& request
->hr_proto
) ? request
->hr_proto
: http_11
;
1872 debug((DEBUG_FAT
, "http_error %d: %s", code
, msg
));
1873 if (header
== NULL
|| reason
== NULL
) {
1874 error(1, "http_error() failed (short = %p, long = %p)",
1879 if (request
&& request
->hr_serverport
&&
1880 strcmp(request
->hr_serverport
, "80") != 0)
1881 snprintf(portbuf
, sizeof(portbuf
), ":%s", request
->hr_serverport
);
1885 if (request
&& request
->hr_file
) {
1886 escape_html(request
);
1887 size
= snprintf(buf
, sizeof buf
,
1888 "<html><head><title>%s</title></head>\n"
1889 "<body><h1>%s</h1>\n"
1890 "%s: <pre>%s</pre>\n"
1891 "<hr><address><a href=\"http://%s%s/\">%s%s</a></address>\n"
1893 header
, header
, request
->hr_file
, reason
,
1894 myname
, portbuf
, myname
, portbuf
);
1895 if (size
>= (int)sizeof buf
) {
1896 warning("http_error buffer too small, truncated");
1897 size
= (int)sizeof buf
;
1902 bozoprintf("%s %s\r\n", proto
, header
);
1903 auth_check_401(request
, code
);
1905 bozoprintf("Content-Type: text/html\r\n");
1906 bozoprintf("Content-Length: %d\r\n", size
);
1907 bozoprintf("Server: %s\r\n", server_software
);
1908 if (request
&& request
->hr_allow
)
1909 bozoprintf("Allow: %s\r\n", request
->hr_allow
);
1912 bozoprintf("%s", buf
);
1918 /* short map between error code, and short/long messages */
1919 static struct errors_map
{
1920 int code
; /* HTTP return code */
1921 const char *shortmsg
; /* short version of message */
1922 const char *longmsg
; /* long version of message */
1924 { 400, "400 Bad Request", "The request was not valid", },
1925 { 401, "401 Unauthorized", "No authorization", },
1926 { 403, "403 Forbidden", "Access to this item has been denied",},
1927 { 404, "404 Not Found", "This item has not been found", },
1928 { 408, "408 Request Timeout", "This request took too long", },
1929 { 417, "417 Expectation Failed","Expectations not available", },
1930 { 500, "500 Internal Error", "An error occured on the server", },
1931 { 501, "501 Not Implemented", "This request is not available", },
1935 static const char *help
= "DANGER! WILL ROBINSON! DANGER!";
1938 http_errors_short(int code
)
1940 struct errors_map
*ep
;
1942 for (ep
= errors_map
; ep
->code
; ep
++)
1943 if (ep
->code
== code
)
1944 return (ep
->shortmsg
);
1949 http_errors_long(int code
)
1951 struct errors_map
*ep
;
1953 for (ep
= errors_map
; ep
->code
; ep
++)
1954 if (ep
->code
== code
)
1955 return (ep
->longmsg
);
1959 /* Below are various modified libc functions */
1962 * returns -1 in lenp if the string ran out before finding a delimiter,
1963 * but is otherwise the same as strsep. Note that the length must be
1964 * correctly passed in.
1967 bozostrnsep(char **strp
, const char *delim
, ssize_t
*lenp
)
1974 if ((s
= *strp
) == NULL
)
1977 if (lenp
&& --(*lenp
) == -1)
1982 if ((sc
= *spanp
++) == c
) {
1996 * inspired by fgetln(3), but works for fd's. should work identically
1997 * except it, however, does *not* return the newline, and it does nul
1998 * terminate the string.
2001 bozodgetln(int fd
, ssize_t
*lenp
, ssize_t (*readfn
)(int, void *, size_t))
2003 static char *buffer
;
2004 static ssize_t buflen
= 0;
2011 buflen
= 128; /* should be plenty for most requests */
2012 buffer
= malloc(buflen
);
2013 if (buffer
== NULL
) {
2021 * we *have* to read one byte at a time, to not break cgi
2022 * programs (for we pass stdin off to them). could fix this
2023 * by becoming a fd-passing program instead of just exec'ing
2026 for (; readfn(fd
, &c
, 1) == 1; ) {
2027 debug((DEBUG_EXPLODING
, "bozodgetln read %c", c
));
2029 if (len
>= buflen
- 1) {
2031 debug((DEBUG_EXPLODING
, "bozodgetln: "
2032 "reallocating buffer to buflen %zu", buflen
));
2033 nbuffer
= bozorealloc(buffer
, buflen
);
2041 } else if (c
== '\n') {
2043 * HTTP/1.1 spec says to ignore CR and treat
2044 * LF as the real line terminator. even though
2045 * the same spec defines CRLF as the line
2046 * terminator, it is recommended in section 19.3
2047 * to do the LF trick for tolerance.
2058 debug((DEBUG_OBESE
, "bozodgetln returns: ``%s'' with len %d",
2065 bozorealloc(void *ptr
, size_t size
)
2069 p
= realloc(ptr
, size
);
2071 (void)http_error(500, NULL
, "memory allocation failure");
2078 bozomalloc(size_t size
)
2084 (void)http_error(500, NULL
, "memory allocation failure");
2091 bozostrdup(const char *str
)
2097 (void)http_error(500, NULL
, "memory allocation failure");