1 /* $NetBSD: bozohttpd.c,v 1.66 2015/07/16 12:19:23 shm Exp $ */
3 /* $eterna: bozohttpd.c,v 1.178 2011/11/18 09:21:15 mrg Exp $ */
6 * Copyright (c) 1997-2015 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 * RFC 2616 (HTTP/1.1):
60 * - 14.11: content-encoding handling. [1]
62 * - 14.13: 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.28: if-unmodified-since handling. if-modified-since is
68 * done since, shouldn't be too hard for this one.
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: only support simple ranges: %d- and %d-%d
96 * would be nice to support more.
98 * - 14.9: we aren't a cache.
100 * - 14.15: content-md5 would be nice.
102 * - 14.24/14.26/14.27: if-match, if-none-match, if-range. be
103 * nice to support this.
105 * - 14.44: Vary: seems unneeded. ignore it for now.
109 #define INDEX_HTML "index.html"
111 #ifndef SERVER_SOFTWARE
112 #define SERVER_SOFTWARE "bozohttpd/20150501"
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"
124 #define PUBLIC_HTML "public_html"
128 #define USE_ARG(x) /*LINTED*/(void)&(x)
132 * And so it begins ..
135 #include <sys/param.h>
136 #include <sys/socket.h>
137 #include <sys/time.h>
138 #include <sys/mman.h>
140 #include <arpa/inet.h>
157 #include "bozohttpd.h"
159 #ifndef MAX_WAIT_TIME
160 #define MAX_WAIT_TIME 60 /* hang around for 60 seconds max */
163 /* variables and functions */
165 #define LOG_FTP LOG_DAEMON
168 volatile sig_atomic_t alarmhit
;
171 * check there's enough space in the prefs and names arrays.
174 size_arrays(bozoprefs_t
*bozoprefs
, unsigned needed
)
178 if (bozoprefs
->size
== 0) {
179 /* only get here first time around */
180 bozoprefs
->size
= needed
;
181 if ((bozoprefs
->name
= calloc(sizeof(char *), needed
)) == NULL
) {
182 (void) fprintf(stderr
, "size_arrays: bad alloc\n");
185 if ((bozoprefs
->value
= calloc(sizeof(char *), needed
)) == NULL
) {
186 free(bozoprefs
->name
);
187 (void) fprintf(stderr
, "size_arrays: bad alloc\n");
190 } else if (bozoprefs
->c
== bozoprefs
->size
) {
191 /* only uses 'needed' when filled array */
192 bozoprefs
->size
+= needed
;
193 temp
= realloc(bozoprefs
->name
, sizeof(char *) * needed
);
195 (void) fprintf(stderr
, "size_arrays: bad alloc\n");
198 bozoprefs
->name
= temp
;
199 temp
= realloc(bozoprefs
->value
, sizeof(char *) * needed
);
201 (void) fprintf(stderr
, "size_arrays: bad alloc\n");
204 bozoprefs
->value
= temp
;
210 findvar(bozoprefs_t
*bozoprefs
, const char *name
)
214 for (i
= 0 ; i
< bozoprefs
->c
&& strcmp(bozoprefs
->name
[i
], name
) != 0; i
++)
216 return (i
== bozoprefs
->c
) ? -1 : (int)i
;
220 bozo_set_pref(bozoprefs_t
*bozoprefs
, const char *name
, const char *value
)
224 if ((i
= findvar(bozoprefs
, name
)) < 0) {
225 /* add the element to the array */
226 if (size_arrays(bozoprefs
, bozoprefs
->size
+ 15)) {
227 bozoprefs
->name
[i
= bozoprefs
->c
++] = strdup(name
);
230 /* replace the element in the array */
231 if (bozoprefs
->value
[i
]) {
232 free(bozoprefs
->value
[i
]);
233 bozoprefs
->value
[i
] = NULL
;
236 /* sanity checks for range of values go here */
237 bozoprefs
->value
[i
] = strdup(value
);
242 * get a variable's value, or NULL
245 bozo_get_pref(bozoprefs_t
*bozoprefs
, const char *name
)
249 return ((i
= findvar(bozoprefs
, name
)) < 0) ? NULL
:
254 bozo_http_date(char *date
, size_t datelen
)
259 /* Sun, 06 Nov 1994 08:49:37 GMT */
261 tm
= gmtime(&now
); /* HTTP/1.1 spec rev 06 sez GMT only */
262 strftime(date
, datelen
, "%a, %d %b %Y %H:%M:%S GMT", tm
);
267 * convert "in" into the three parts of a request (first line).
268 * we allocate into file and query, but return pointers into
269 * "in" for proto and method.
272 parse_request(bozohttpd_t
*httpd
, char *in
, char **method
, char **file
,
273 char **query
, char **proto
)
279 debug((httpd
, DEBUG_EXPLODING
, "parse in: %s", in
));
280 *method
= *file
= *query
= *proto
= NULL
;
282 len
= (ssize_t
)strlen(in
);
283 val
= bozostrnsep(&in
, " \t\n\r", &len
);
284 if (len
< 1 || val
== NULL
)
288 while (*in
== ' ' || *in
== '\t')
290 val
= bozostrnsep(&in
, " \t\n\r", &len
);
299 *query
= strchr(*file
, '?');
304 while (*in
&& (*in
== ' ' || *in
== '\t'))
311 /* allocate private copies */
312 *file
= bozostrdup(httpd
, *file
);
314 *query
= bozostrdup(httpd
, *query
);
316 debug((httpd
, DEBUG_FAT
,
317 "url: method: \"%s\" file: \"%s\" query: \"%s\" proto: \"%s\"",
318 *method
, *file
, *query
, *proto
));
322 * cleanup a bozo_httpreq_t after use
325 bozo_clean_request(bozo_httpreq_t
*request
)
327 struct bozoheaders
*hdr
, *ohdr
= NULL
;
332 /* If SSL enabled cleanup SSL structure. */
333 bozo_ssl_destroy(request
->hr_httpd
);
335 /* clean up request */
336 free(request
->hr_remotehost
);
337 free(request
->hr_remoteaddr
);
338 free(request
->hr_serverport
);
339 free(request
->hr_virthostname
);
340 free(request
->hr_file
);
341 free(request
->hr_oldfile
);
342 free(request
->hr_query
);
343 free(request
->hr_host
);
344 bozo_auth_cleanup(request
);
345 for (hdr
= SIMPLEQ_FIRST(&request
->hr_headers
); hdr
;
346 hdr
= SIMPLEQ_NEXT(hdr
, h_next
)) {
358 * send a HTTP/1.1 408 response if we timeout.
368 * add or merge this header (val: str) into the requests list
370 static bozoheaders_t
*
371 addmerge_header(bozo_httpreq_t
*request
, char *val
,
372 char *str
, ssize_t len
)
374 struct bozoheaders
*hdr
;
377 /* do we exist already? */
378 SIMPLEQ_FOREACH(hdr
, &request
->hr_headers
, h_next
) {
379 if (strcasecmp(val
, hdr
->h_header
) == 0)
384 /* yup, merge it in */
387 if (asprintf(&nval
, "%s, %s", hdr
->h_value
, str
) == -1) {
388 (void)bozo_http_error(request
->hr_httpd
, 500, NULL
,
389 "memory allocation failure");
395 /* nope, create a new one */
397 hdr
= bozomalloc(request
->hr_httpd
, sizeof *hdr
);
398 hdr
->h_header
= bozostrdup(request
->hr_httpd
, val
);
400 hdr
->h_value
= bozostrdup(request
->hr_httpd
, str
);
402 hdr
->h_value
= bozostrdup(request
->hr_httpd
, " ");
404 SIMPLEQ_INSERT_TAIL(&request
->hr_headers
, hdr
, h_next
);
405 request
->hr_nheaders
++;
412 * as the prototype string is not constant (eg, "HTTP/1.1" is equivalent
413 * to "HTTP/001.01"), we MUST parse this.
416 process_proto(bozo_httpreq_t
*request
, const char *proto
)
418 char majorstr
[16], *minorstr
;
419 int majorint
, minorint
;
423 request
->hr_proto
= request
->hr_httpd
->consts
.http_09
;
424 debug((request
->hr_httpd
, DEBUG_FAT
, "request %s is http/0.9",
429 if (strncasecmp(proto
, "HTTP/", 5) != 0)
431 strncpy(majorstr
, proto
+ 5, sizeof majorstr
);
432 majorstr
[sizeof(majorstr
)-1] = 0;
433 minorstr
= strchr(majorstr
, '.');
434 if (minorstr
== NULL
)
438 majorint
= atoi(majorstr
);
439 minorint
= atoi(minorstr
);
448 request
->hr_proto
= request
->hr_httpd
->consts
.http_10
;
449 else if (minorint
== 1)
450 request
->hr_proto
= request
->hr_httpd
->consts
.http_11
;
454 debug((request
->hr_httpd
, DEBUG_FAT
, "request %s is %s",
455 request
->hr_file
, request
->hr_proto
));
456 SIMPLEQ_INIT(&request
->hr_headers
);
457 request
->hr_nheaders
= 0;
461 return bozo_http_error(request
->hr_httpd
, 404, NULL
, "unknown prototype");
465 * process each type of HTTP method, setting this HTTP requests
468 static struct method_map
{
472 { "GET", HTTP_GET
, },
473 { "POST", HTTP_POST
, },
474 { "HEAD", HTTP_HEAD
, },
475 #if 0 /* other non-required http/1.1 methods */
476 { "OPTIONS", HTTP_OPTIONS
, },
477 { "PUT", HTTP_PUT
, },
478 { "DELETE", HTTP_DELETE
, },
479 { "TRACE", HTTP_TRACE
, },
480 { "CONNECT", HTTP_CONNECT
, },
486 process_method(bozo_httpreq_t
*request
, const char *method
)
488 struct method_map
*mmp
;
490 if (request
->hr_proto
== request
->hr_httpd
->consts
.http_11
)
491 request
->hr_allow
= "GET, HEAD, POST";
493 for (mmp
= method_map
; mmp
->name
; mmp
++)
494 if (strcasecmp(method
, mmp
->name
) == 0) {
495 request
->hr_method
= mmp
->type
;
496 request
->hr_methodstr
= mmp
->name
;
500 return bozo_http_error(request
->hr_httpd
, 404, request
, "unknown method");
504 * This function reads a http request from stdin, returning a pointer to a
505 * bozo_httpreq_t structure, describing the request.
508 bozo_read_request(bozohttpd_t
*httpd
)
511 char *str
, *val
, *method
, *file
, *proto
, *query
;
512 char *host
, *addr
, *port
;
514 char hbuf
[NI_MAXHOST
], abuf
[NI_MAXHOST
];
515 struct sockaddr_storage ss
;
519 bozo_httpreq_t
*request
;
522 * if we're in daemon mode, bozo_daemon_fork() will return here twice
523 * for each call. once in the child, returning 0, and once in the
524 * parent, returning 1. for each child, then we can setup SSL, and
525 * the parent can signal the caller there was no request to process
526 * and it will wait for another.
528 if (bozo_daemon_fork(httpd
))
530 if (bozo_ssl_accept(httpd
))
533 request
= bozomalloc(httpd
, sizeof(*request
));
534 memset(request
, 0, sizeof(*request
));
535 request
->hr_httpd
= httpd
;
536 request
->hr_allow
= request
->hr_host
= NULL
;
537 request
->hr_content_type
= request
->hr_content_length
= NULL
;
538 request
->hr_range
= NULL
;
539 request
->hr_last_byte_pos
= -1;
540 request
->hr_if_modified_since
= NULL
;
541 request
->hr_virthostname
= NULL
;
542 request
->hr_file
= NULL
;
543 request
->hr_oldfile
= NULL
;
544 bozo_auth_init(request
);
547 if (getpeername(0, (struct sockaddr
*)(void *)&ss
, &slen
) < 0)
550 if (getnameinfo((struct sockaddr
*)(void *)&ss
, slen
,
551 abuf
, sizeof abuf
, NULL
, 0, NI_NUMERICHOST
) == 0)
555 if (httpd
->numeric
== 0 &&
556 getnameinfo((struct sockaddr
*)(void *)&ss
, slen
,
557 hbuf
, sizeof hbuf
, NULL
, 0, 0) == 0)
563 request
->hr_remotehost
= bozostrdup(request
->hr_httpd
, host
);
565 request
->hr_remoteaddr
= bozostrdup(request
->hr_httpd
, addr
);
569 * Override the bound port from the request value, so it works even
570 * if passed through a proxy that doesn't rewrite the port.
572 if (httpd
->bindport
) {
573 if (strcmp(httpd
->bindport
, "80") != 0)
574 port
= httpd
->bindport
;
578 if (getsockname(0, (struct sockaddr
*)(void *)&ss
, &slen
) < 0)
581 if (getnameinfo((struct sockaddr
*)(void *)&ss
, slen
, NULL
, 0,
582 bufport
, sizeof bufport
, NI_NUMERICSERV
) == 0)
589 request
->hr_serverport
= bozostrdup(request
->hr_httpd
, port
);
592 * setup a timer to make sure the request is not hung
594 sa
.sa_handler
= alarmer
;
595 sigemptyset(&sa
.sa_mask
);
596 sigaddset(&sa
.sa_mask
, SIGALRM
);
598 sigaction(SIGALRM
, &sa
, NULL
); /* XXX */
600 alarm(MAX_WAIT_TIME
);
601 while ((str
= bozodgetln(httpd
, STDIN_FILENO
, &len
, bozo_read
)) != NULL
) {
604 (void)bozo_http_error(httpd
, 408, NULL
,
605 "request timed out");
613 (void)bozo_http_error(httpd
, 404, NULL
,
618 bozo_warn(httpd
, "got request ``%s'' from host %s to port %s",
620 host
? host
: addr
? addr
: "<local>",
621 port
? port
: "<stdin>");
623 /* we allocate return space in file and query only */
624 parse_request(httpd
, str
, &method
, &file
, &query
, &proto
);
625 request
->hr_file
= file
;
626 request
->hr_query
= query
;
627 if (method
== NULL
) {
628 (void)bozo_http_error(httpd
, 404, NULL
,
633 (void)bozo_http_error(httpd
, 404, NULL
,
639 * note that we parse the proto first, so that we
640 * can more properly parse the method and the url.
643 if (process_proto(request
, proto
) ||
644 process_method(request
, method
)) {
648 debug((httpd
, DEBUG_FAT
, "got file \"%s\" query \"%s\"",
650 request
->hr_query
? request
->hr_query
: "<none>"));
652 /* http/0.9 has no header processing */
653 if (request
->hr_proto
== httpd
->consts
.http_09
)
655 } else { /* incoming headers */
661 val
= bozostrnsep(&str
, ":", &len
);
662 debug((httpd
, DEBUG_EXPLODING
,
663 "read_req2: after bozostrnsep: str ``%s'' val ``%s''",
665 if (val
== NULL
|| len
== -1) {
666 (void)bozo_http_error(httpd
, 404, request
,
670 while (*str
== ' ' || *str
== '\t')
672 while (*val
== ' ' || *val
== '\t')
675 if (bozo_auth_check_headers(request
, val
, str
, len
))
678 hdr
= addmerge_header(request
, val
, str
, len
);
680 if (strcasecmp(hdr
->h_header
, "content-type") == 0)
681 request
->hr_content_type
= hdr
->h_value
;
682 else if (strcasecmp(hdr
->h_header
, "content-length") == 0)
683 request
->hr_content_length
= hdr
->h_value
;
684 else if (strcasecmp(hdr
->h_header
, "host") == 0)
685 request
->hr_host
= bozostrdup(httpd
, hdr
->h_value
);
686 /* RFC 2616 (HTTP/1.1): 14.20 */
687 else if (strcasecmp(hdr
->h_header
, "expect") == 0) {
688 (void)bozo_http_error(httpd
, 417, request
,
689 "we don't support Expect:");
692 else if (strcasecmp(hdr
->h_header
, "referrer") == 0 ||
693 strcasecmp(hdr
->h_header
, "referer") == 0)
694 request
->hr_referrer
= hdr
->h_value
;
695 else if (strcasecmp(hdr
->h_header
, "range") == 0)
696 request
->hr_range
= hdr
->h_value
;
697 else if (strcasecmp(hdr
->h_header
,
698 "if-modified-since") == 0)
699 request
->hr_if_modified_since
= hdr
->h_value
;
700 else if (strcasecmp(hdr
->h_header
,
701 "accept-encoding") == 0)
702 request
->hr_accept_encoding
= hdr
->h_value
;
704 debug((httpd
, DEBUG_FAT
, "adding header %s: %s",
705 hdr
->h_header
, hdr
->h_value
));
708 alarm(MAX_WAIT_TIME
);
711 /* now, clear it all out */
713 signal(SIGALRM
, SIG_DFL
);
716 if (request
->hr_method
== HTTP_POST
&&
717 request
->hr_content_length
== NULL
) {
718 (void)bozo_http_error(httpd
, 400, request
,
719 "missing content length");
723 /* RFC 2616 (HTTP/1.1), 14.23 & 19.6.1.1 */
724 if (request
->hr_proto
== httpd
->consts
.http_11
&&
725 /*(strncasecmp(request->hr_file, "http://", 7) != 0) &&*/
726 request
->hr_host
== NULL
) {
727 (void)bozo_http_error(httpd
, 400, request
,
728 "missing Host header");
732 if (request
->hr_range
!= NULL
) {
733 debug((httpd
, DEBUG_FAT
, "hr_range: %s", request
->hr_range
));
734 /* support only simple ranges %d- and %d-%d */
735 if (strchr(request
->hr_range
, ',') == NULL
) {
736 const char *rstart
, *dash
;
738 rstart
= strchr(request
->hr_range
, '=');
739 if (rstart
!= NULL
) {
741 dash
= strchr(rstart
, '-');
742 if (dash
!= NULL
&& dash
!= rstart
) {
744 request
->hr_have_range
= 1;
745 request
->hr_first_byte_pos
=
746 strtoll(rstart
, NULL
, 10);
747 if (request
->hr_first_byte_pos
< 0)
748 request
->hr_first_byte_pos
= 0;
750 request
->hr_last_byte_pos
=
751 strtoll(dash
, NULL
, 10);
752 if (request
->hr_last_byte_pos
< 0)
753 request
->hr_last_byte_pos
= -1;
760 debug((httpd
, DEBUG_FAT
, "bozo_read_request returns url %s in request",
765 bozo_clean_request(request
);
771 mmap_and_write_part(bozohttpd_t
*httpd
, int fd
, off_t first_byte_pos
, size_t sz
)
773 size_t mappedsz
, wroffset
;
779 * we need to ensure that both the size *and* offset arguments to
780 * mmap() are page-aligned. our formala for this is:
782 * input offset: first_byte_pos
785 * mapped offset = page align truncate (input offset)
787 * page align extend (input offset - mapped offset + input size)
788 * write offset = input offset - mapped offset
790 * we use the write offset in all writes
792 mappedoffset
= first_byte_pos
& ~(httpd
->page_size
- 1);
794 (first_byte_pos
- mappedoffset
+ sz
+ httpd
->page_size
- 1) &
795 ~(httpd
->page_size
- 1);
796 wroffset
= (size_t)(first_byte_pos
- mappedoffset
);
798 addr
= mmap(0, mappedsz
, PROT_READ
, MAP_SHARED
, fd
, mappedoffset
);
799 if (addr
== (char *)-1) {
800 bozo_warn(httpd
, "mmap failed: %s", strerror(errno
));
805 #ifdef MADV_SEQUENTIAL
807 (void)madvise(addr
, sz
, MADV_SEQUENTIAL
);
808 #endif /* !__minix */
810 while (sz
> BOZO_WRSZ
) {
811 if (bozo_write(httpd
, STDOUT_FILENO
, addr
+ wroffset
,
812 BOZO_WRSZ
) != BOZO_WRSZ
) {
813 bozo_warn(httpd
, "write failed: %s", strerror(errno
));
816 debug((httpd
, DEBUG_OBESE
, "wrote %d bytes", BOZO_WRSZ
));
820 if (sz
&& (size_t)bozo_write(httpd
, STDOUT_FILENO
, addr
+ wroffset
,
822 bozo_warn(httpd
, "final write failed: %s", strerror(errno
));
825 debug((httpd
, DEBUG_OBESE
, "wrote %d bytes", (int)sz
));
827 if (munmap(mappedaddr
, mappedsz
) < 0) {
828 bozo_warn(httpd
, "munmap failed");
836 parse_http_date(const char *val
, time_t *timestamp
)
841 if ((remainder
= strptime(val
, "%a, %d %b %Y %T GMT", &tm
)) == NULL
&&
842 (remainder
= strptime(val
, "%a, %d-%b-%y %T GMT", &tm
)) == NULL
&&
843 (remainder
= strptime(val
, "%a %b %d %T %Y", &tm
)) == NULL
)
844 return 0; /* Invalid HTTP date format */
847 return 0; /* No trailing garbage */
849 *timestamp
= timegm(&tm
);
854 * given an url, encode it ala rfc 3986. ie, escape ? and friends.
855 * note that this function returns a static buffer, and thus needs
856 * to be updated for any sort of parallel processing.
859 bozo_escape_rfc3986(bozohttpd_t
*httpd
, const char *url
)
862 static size_t buflen
= 0;
868 if (buflen
< len
* 3 + 1) {
869 buflen
= len
* 3 + 1;
870 buf
= bozorealloc(httpd
, buf
, buflen
);
878 for (len
= 0, s
= url
, d
= buf
; *s
;) {
905 snprintf(d
, 4, "%%%02X", *s
++);
921 * checks to see if this request has a valid .bzdirect file. returns
922 * 0 on failure and 1 on success.
925 check_direct_access(bozo_httpreq_t
*request
)
929 char dir
[MAXPATHLEN
], dirfile
[MAXPATHLEN
], *basename
;
931 snprintf(dir
, sizeof(dir
), "%s", request
->hr_file
+ 1);
932 debug((request
->hr_httpd
, DEBUG_FAT
, "check_direct_access: dir %s", dir
));
933 basename
= strrchr(dir
, '/');
935 if ((!basename
|| basename
[1] != '\0') &&
936 lstat(dir
, &sb
) == 0 && S_ISDIR(sb
.st_mode
))
938 else if (basename
== NULL
)
942 bozo_check_special_files(request
, basename
);
945 if ((size_t)snprintf(dirfile
, sizeof(dirfile
), "%s/%s", dir
,
946 DIRECT_ACCESS_FILE
) >= sizeof(dirfile
)) {
947 bozo_http_error(request
->hr_httpd
, 404, request
,
948 "directfile path too long");
951 if (stat(dirfile
, &sb
) < 0 ||
952 (fp
= fopen(dirfile
, "r")) == NULL
)
959 * do automatic redirection -- if there are query parameters for the URL
960 * we will tack these on to the new (redirected) URL.
963 handle_redirect(bozo_httpreq_t
*request
,
964 const char *url
, int absolute
)
966 bozohttpd_t
*httpd
= request
->hr_httpd
;
969 const char *hostname
= BOZOHOST(httpd
, request
);
973 if (asprintf(&urlbuf
, "/%s/", request
->hr_file
) < 0)
974 bozo_err(httpd
, 1, "asprintf");
978 url
= bozo_escape_rfc3986(request
->hr_httpd
, url
);
980 if (request
->hr_query
&& strlen(request
->hr_query
))
983 if (request
->hr_serverport
&& strcmp(request
->hr_serverport
, "80") != 0)
984 snprintf(portbuf
, sizeof(portbuf
), ":%s",
985 request
->hr_serverport
);
989 bozo_warn(httpd
, "redirecting %s", url
);
991 bozo_warn(httpd
, "redirecting %s%s%s", hostname
, portbuf
, url
);
992 debug((httpd
, DEBUG_FAT
, "redirecting %s", url
));
993 bozo_printf(httpd
, "%s 301 Document Moved\r\n", request
->hr_proto
);
994 if (request
->hr_proto
!= httpd
->consts
.http_09
)
995 bozo_print_header(request
, NULL
, "text/html", NULL
);
996 if (request
->hr_proto
!= httpd
->consts
.http_09
) {
997 bozo_printf(httpd
, "Location: http://");
999 bozo_printf(httpd
, "%s%s", hostname
, portbuf
);
1001 bozo_printf(httpd
, "%s?%s\r\n", url
, request
->hr_query
);
1003 bozo_printf(httpd
, "%s\r\n", url
);
1006 bozo_printf(httpd
, "\r\n");
1007 if (request
->hr_method
== HTTP_HEAD
)
1009 bozo_printf(httpd
, "<html><head><title>Document Moved</title></head>\n");
1010 bozo_printf(httpd
, "<body><h1>Document Moved</h1>\n");
1011 bozo_printf(httpd
, "This document had moved <a href=\"http://");
1014 bozo_printf(httpd
, "%s?%s", url
, request
->hr_query
);
1016 bozo_printf(httpd
, "%s%s%s?%s", hostname
,
1017 portbuf
, url
, request
->hr_query
);
1020 bozo_printf(httpd
, "%s", url
);
1022 bozo_printf(httpd
, "%s%s%s", hostname
,
1025 bozo_printf(httpd
, "\">here</a>\n");
1026 bozo_printf(httpd
, "</body></html>\n");
1028 bozo_flush(httpd
, stdout
);
1033 * deal with virtual host names; we do this:
1034 * if we have a virtual path root (httpd->virtbase), and we are given a
1035 * virtual host spec (Host: ho.st or http://ho.st/), see if this
1036 * directory exists under httpd->virtbase. if it does, use this as the
1040 check_virtual(bozo_httpreq_t
*request
)
1042 bozohttpd_t
*httpd
= request
->hr_httpd
;
1043 char *file
= request
->hr_file
, *s
;
1046 if (!httpd
->virtbase
)
1050 * convert http://virtual.host/ to request->hr_host
1052 debug((httpd
, DEBUG_OBESE
, "checking for http:// virtual host in ``%s''",
1054 if (strncasecmp(file
, "http://", 7) == 0) {
1055 /* we would do virtual hosting here? */
1057 /* RFC 2616 (HTTP/1.1), 5.2: URI takes precedence over Host: */
1058 free(request
->hr_host
);
1059 request
->hr_host
= bozostrdup(request
->hr_httpd
, file
);
1060 if ((s
= strchr(request
->hr_host
, '/')) != NULL
)
1062 s
= strchr(file
, '/');
1063 free(request
->hr_file
);
1064 request
->hr_file
= bozostrdup(request
->hr_httpd
, s
? s
: "/");
1065 debug((httpd
, DEBUG_OBESE
, "got host ``%s'' file is now ``%s''",
1066 request
->hr_host
, request
->hr_file
));
1067 } else if (!request
->hr_host
)
1071 * canonicalise hr_host - that is, remove any :80.
1073 len
= strlen(request
->hr_host
);
1074 if (len
> 3 && strcmp(request
->hr_host
+ len
- 3, ":80") == 0) {
1075 request
->hr_host
[len
- 3] = '\0';
1076 len
= strlen(request
->hr_host
);
1080 * ok, we have a virtual host, use opendir(3) to find a case
1081 * insensitive match for the virtual host we are asked for.
1082 * note that if the virtual host is the same as the master,
1083 * we don't need to do anything special.
1085 debug((httpd
, DEBUG_OBESE
,
1086 "check_virtual: checking host `%s' under httpd->virtbase `%s' "
1088 request
->hr_host
, httpd
->virtbase
, request
->hr_file
));
1089 if (strncasecmp(httpd
->virthostname
, request
->hr_host
, len
) != 0) {
1094 if ((dirp
= opendir(httpd
->virtbase
)) != NULL
) {
1095 while ((d
= readdir(dirp
)) != NULL
) {
1096 if (strcmp(d
->d_name
, ".") == 0 ||
1097 strcmp(d
->d_name
, "..") == 0) {
1100 debug((httpd
, DEBUG_OBESE
, "looking at dir``%s''",
1102 if (strcmp(d
->d_name
, request
->hr_host
) == 0) {
1103 /* found it, punch it */
1104 debug((httpd
, DEBUG_OBESE
, "found it punch it"));
1105 request
->hr_virthostname
=
1106 bozostrdup(httpd
, d
->d_name
);
1107 if (asprintf(&s
, "%s/%s", httpd
->virtbase
,
1108 request
->hr_virthostname
) < 0)
1109 bozo_err(httpd
, 1, "asprintf");
1116 debug((httpd
, DEBUG_FAT
, "opendir %s failed: %s",
1117 httpd
->virtbase
, strerror(errno
)));
1120 if (httpd
->unknown_slash
)
1122 return bozo_http_error(httpd
, 404, request
,
1127 s
= httpd
->slashdir
;
1130 * ok, nailed the correct slashdir, chdir to it
1133 return bozo_http_error(httpd
, 404, request
,
1134 "can't chdir to slashdir");
1139 * checks to see if this request has a valid .bzredirect file. returns
1140 * 0 when no redirection happend, or 1 when handle_redirect() has been
1141 * called, -1 on error.
1144 check_bzredirect(bozo_httpreq_t
*request
)
1147 char dir
[MAXPATHLEN
], redir
[MAXPATHLEN
], redirpath
[MAXPATHLEN
+ 1],
1149 char *basename
, *finalredir
;
1153 * if this pathname is really a directory, but doesn't end in /,
1154 * use it as the directory to look for the redir file.
1156 if((size_t)snprintf(dir
, sizeof(dir
), "%s", request
->hr_file
+ 1) >=
1158 bozo_http_error(request
->hr_httpd
, 404, request
,
1159 "file path too long");
1162 debug((request
->hr_httpd
, DEBUG_FAT
, "check_bzredirect: dir %s", dir
));
1163 basename
= strrchr(dir
, '/');
1165 if ((!basename
|| basename
[1] != '\0') &&
1166 lstat(dir
, &sb
) == 0 && S_ISDIR(sb
.st_mode
))
1168 else if (basename
== NULL
)
1172 bozo_check_special_files(request
, basename
);
1175 if ((size_t)snprintf(redir
, sizeof(redir
), "%s/%s", dir
,
1176 REDIRECT_FILE
) >= sizeof(redir
)) {
1177 bozo_http_error(request
->hr_httpd
, 404, request
,
1178 "redirectfile path too long");
1181 if (lstat(redir
, &sb
) == 0) {
1182 if (!S_ISLNK(sb
.st_mode
))
1186 if((size_t)snprintf(redir
, sizeof(redir
), "%s/%s", dir
,
1187 ABSREDIRECT_FILE
) >= sizeof(redir
)) {
1188 bozo_http_error(request
->hr_httpd
, 404, request
,
1189 "redirectfile path too long");
1192 if (lstat(redir
, &sb
) < 0 || !S_ISLNK(sb
.st_mode
))
1196 debug((request
->hr_httpd
, DEBUG_FAT
,
1197 "check_bzredirect: calling readlink"));
1198 rv
= readlink(redir
, redirpath
, sizeof redirpath
- 1);
1199 if (rv
== -1 || rv
== 0) {
1200 debug((request
->hr_httpd
, DEBUG_FAT
, "readlink failed"));
1203 redirpath
[rv
] = '\0';
1204 debug((request
->hr_httpd
, DEBUG_FAT
,
1205 "readlink returned \"%s\"", redirpath
));
1207 /* check if we need authentication */
1208 snprintf(path
, sizeof(path
), "%s/", dir
);
1209 if (bozo_auth_check(request
, path
))
1212 /* now we have the link pointer, redirect to the real place */
1214 finalredir
= redirpath
;
1216 if ((size_t)snprintf(finalredir
= redir
, sizeof(redir
), "/%s/%s",
1217 dir
, redirpath
) >= sizeof(redir
)) {
1218 bozo_http_error(request
->hr_httpd
, 404, request
,
1219 "redirect path too long");
1224 debug((request
->hr_httpd
, DEBUG_FAT
,
1225 "check_bzredirect: new redir %s", finalredir
));
1226 handle_redirect(request
, finalredir
, absolute
);
1230 /* this fixes the %HH hack that RFC2396 requires. */
1232 fix_url_percent(bozo_httpreq_t
*request
)
1234 bozohttpd_t
*httpd
= request
->hr_httpd
;
1235 char *s
, *t
, buf
[3], *url
;
1236 char *end
; /* if end is not-zero, we don't translate beyond that */
1238 url
= request
->hr_file
;
1240 end
= url
+ strlen(url
);
1242 /* fast forward to the first % */
1243 if ((s
= strchr(url
, '%')) == NULL
)
1248 if (end
&& s
>= end
) {
1249 debug((httpd
, DEBUG_EXPLODING
,
1250 "fu_%%: past end, filling out.."));
1255 debug((httpd
, DEBUG_EXPLODING
,
1256 "fu_%%: got s == %%, s[1]s[2] == %c%c",
1258 if (s
[1] == '\0' || s
[2] == '\0') {
1259 (void)bozo_http_error(httpd
, 400, request
,
1260 "percent hack missing two chars afterwards");
1263 if (s
[1] == '0' && s
[2] == '0') {
1264 (void)bozo_http_error(httpd
, 404, request
,
1265 "percent hack was %00");
1268 if (s
[1] == '2' && s
[2] == 'f') {
1269 (void)bozo_http_error(httpd
, 404, request
,
1270 "percent hack was %2f (/)");
1278 *t
= (char)strtol(buf
, NULL
, 16);
1279 debug((httpd
, DEBUG_EXPLODING
,
1280 "fu_%%: strtol put '%02x' into *t", *t
));
1282 (void)bozo_http_error(httpd
, 400, request
,
1283 "percent hack got a 0 back");
1287 while (*s
&& *s
!= '%') {
1288 if (end
&& s
>= end
)
1295 debug((httpd
, DEBUG_FAT
, "fix_url_percent returns %s in url",
1302 * transform_request does this:
1303 * - ``expand'' %20 crapola
1304 * - punt if it doesn't start with /
1305 * - check httpd->untrustedref / referrer
1306 * - look for "http://myname/" and deal with it.
1307 * - maybe call bozo_process_cgi()
1308 * - check for ~user and call bozo_user_transform() if so
1309 * - if the length > 1, check for trailing slash. if so,
1310 * add the index.html file
1311 * - if the length is 1, return the index.html file
1312 * - disallow anything ending up with a file starting
1313 * at "/" or having ".." in it.
1314 * - anything else is a really weird internal error
1315 * - returns malloced file to serve, if unhandled
1318 transform_request(bozo_httpreq_t
*request
, int *isindex
)
1320 bozohttpd_t
*httpd
= request
->hr_httpd
;
1321 char *file
, *newfile
= NULL
;
1323 const char *hostname
= BOZOHOST(httpd
, request
);
1327 debug((httpd
, DEBUG_FAT
, "tf_req: file %s", request
->hr_file
));
1328 if (fix_url_percent(request
)) {
1331 if (check_virtual(request
)) {
1334 file
= request
->hr_file
;
1336 if (file
[0] != '/') {
1337 (void)bozo_http_error(httpd
, 404, request
, "unknown URL");
1341 /* omit additional slashes at the beginning */
1342 while (file
[1] == '/')
1345 switch(check_bzredirect(request
)) {
1352 if (httpd
->untrustedref
) {
1353 int to_indexhtml
= 0;
1355 #define TOP_PAGE(x) (strcmp((x), "/") == 0 || \
1356 strcmp((x) + 1, httpd->index_html) == 0 || \
1357 strcmp((x) + 1, "favicon.ico") == 0)
1359 debug((httpd
, DEBUG_EXPLODING
, "checking httpd->untrustedref"));
1361 * first check that this path isn't allowed via .bzdirect file,
1362 * and then check referrer; make sure that people come via the
1363 * real name... otherwise if we aren't looking at / or
1364 * /index.html, redirect... we also special case favicon.ico.
1366 if (check_direct_access(request
))
1368 else if (request
->hr_referrer
) {
1369 const char *r
= request
->hr_referrer
;
1371 debug((httpd
, DEBUG_FAT
,
1372 "checking referrer \"%s\" vs virthostname %s",
1374 if (strncmp(r
, "http://", 7) != 0 ||
1375 (strncasecmp(r
+ 7, hostname
,
1376 strlen(hostname
)) != 0 &&
1380 const char *h
= request
->hr_host
;
1382 debug((httpd
, DEBUG_FAT
, "url has no referrer at all"));
1383 /* if there's no referrer, let / or /index.html past */
1384 if (!TOP_PAGE(file
) ||
1385 (h
&& strncasecmp(h
, hostname
,
1386 strlen(hostname
)) != 0))
1391 char *slashindexhtml
;
1393 if (asprintf(&slashindexhtml
, "/%s",
1394 httpd
->index_html
) < 0)
1395 bozo_err(httpd
, 1, "asprintf");
1396 debug((httpd
, DEBUG_FAT
,
1397 "httpd->untrustedref: redirecting %s to %s",
1398 file
, slashindexhtml
));
1399 handle_redirect(request
, slashindexhtml
, 0);
1400 free(slashindexhtml
);
1406 if (/*CONSTCOND*/0) {
1407 #ifndef NO_USER_SUPPORT
1408 } else if (len
> 1 && httpd
->enable_users
&& file
[1] == '~') {
1409 if (file
[2] == '\0') {
1410 (void)bozo_http_error(httpd
, 404, request
,
1411 "missing username");
1414 if (strchr(file
+ 2, '/') == NULL
) {
1415 handle_redirect(request
, NULL
, 0);
1418 debug((httpd
, DEBUG_FAT
, "calling bozo_user_transform"));
1420 return bozo_user_transform(request
, isindex
);
1421 #endif /* NO_USER_SUPPORT */
1422 } else if (len
> 1) {
1423 debug((httpd
, DEBUG_FAT
, "file[len-1] == %c", file
[len
-1]));
1424 if (file
[len
-1] == '/') { /* append index.html */
1426 debug((httpd
, DEBUG_FAT
, "appending index.html"));
1427 newfile
= bozomalloc(httpd
,
1428 len
+ strlen(httpd
->index_html
) + 1);
1429 strcpy(newfile
, file
+ 1);
1430 strcat(newfile
, httpd
->index_html
);
1432 newfile
= bozostrdup(request
->hr_httpd
, file
+ 1);
1433 } else if (len
== 1) {
1434 debug((httpd
, DEBUG_EXPLODING
, "tf_req: len == 1"));
1435 newfile
= bozostrdup(request
->hr_httpd
, httpd
->index_html
);
1437 } else { /* len == 0 ? */
1438 (void)bozo_http_error(httpd
, 500, request
,
1439 "request->hr_file is nul?");
1443 if (newfile
== NULL
) {
1444 (void)bozo_http_error(httpd
, 500, request
, "internal failure");
1449 * look for "http://myname/" and deal with it as necessary.
1453 * stop traversing outside our domain
1455 * XXX true security only comes from our parent using chroot(2)
1456 * before execve(2)'ing us. or our own built in chroot(2) support.
1458 if (*newfile
== '/' || strcmp(newfile
, "..") == 0 ||
1459 strstr(newfile
, "/..") || strstr(newfile
, "../")) {
1460 (void)bozo_http_error(httpd
, 403, request
, "illegal request");
1464 if (bozo_auth_check(request
, newfile
))
1467 if (strlen(newfile
)) {
1468 request
->hr_oldfile
= request
->hr_file
;
1469 request
->hr_file
= newfile
;
1472 if (bozo_process_cgi(request
))
1475 if (bozo_process_lua(request
))
1478 debug((httpd
, DEBUG_FAT
, "transform_request set: %s", newfile
));
1481 debug((httpd
, DEBUG_FAT
, "transform_request returning: 0"));
1487 * can_gzip checks if the request supports and prefers gzip encoding.
1489 * XXX: we do not consider the associated q with gzip in making our
1490 * decision which is broken.
1494 can_gzip(bozo_httpreq_t
*request
)
1500 /* First we decide if the request can be gzipped at all. */
1502 /* not if we already are encoded... */
1503 tmp
= bozo_content_encoding(request
, request
->hr_file
);
1507 /* not if we are not asking for the whole file... */
1508 if (request
->hr_last_byte_pos
!= -1 || request
->hr_have_range
)
1511 /* Then we determine if gzip is on the cards. */
1513 for (pos
= request
->hr_accept_encoding
; pos
&& *pos
; pos
+= len
) {
1517 len
= strcspn(pos
, ";,");
1519 if ((len
== 4 && strncasecmp("gzip", pos
, 4) == 0) ||
1520 (len
== 6 && strncasecmp("x-gzip", pos
, 6) == 0))
1523 if (pos
[len
] == ';')
1524 len
+= strcspn(&pos
[len
], ",");
1534 * bozo_process_request does the following:
1535 * - check the request is valid
1536 * - process cgi-bin if necessary
1537 * - transform a filename if necesarry
1538 * - return the HTTP request
1541 bozo_process_request(bozo_httpreq_t
*request
)
1543 bozohttpd_t
*httpd
= request
->hr_httpd
;
1547 const char *type
, *encoding
;
1551 * note that transform_request chdir()'s if required. also note
1552 * that cgi is handed here. if transform_request() returns 0
1553 * then the request has been handled already.
1555 if (transform_request(request
, &isindex
) == 0)
1560 if (can_gzip(request
)) {
1561 asprintf(&file
, "%s.gz", request
->hr_file
);
1562 fd
= open(file
, O_RDONLY
);
1568 file
= request
->hr_file
;
1571 fd
= open(file
, O_RDONLY
);
1574 debug((httpd
, DEBUG_FAT
, "open failed: %s", strerror(errno
)));
1578 (void)bozo_http_error(httpd
, 403, request
,
1579 "no permission to open file");
1584 if (!bozo_dir_index(request
, file
, isindex
))
1585 (void)bozo_http_error(httpd
, 404, request
,
1589 (void)bozo_http_error(httpd
, 500, request
, "open file");
1593 if (fstat(fd
, &sb
) < 0) {
1594 (void)bozo_http_error(httpd
, 500, request
, "can't fstat");
1597 if (S_ISDIR(sb
.st_mode
)) {
1598 handle_redirect(request
, NULL
, 0);
1602 if (request
->hr_if_modified_since
&&
1603 parse_http_date(request
->hr_if_modified_since
, ×tamp
) &&
1604 timestamp
>= sb
.st_mtime
) {
1605 /* XXX ignore subsecond of timestamp */
1606 bozo_printf(httpd
, "%s 304 Not Modified\r\n",
1608 bozo_printf(httpd
, "\r\n");
1609 bozo_flush(httpd
, stdout
);
1613 /* validate requested range */
1614 if (request
->hr_last_byte_pos
== -1 ||
1615 request
->hr_last_byte_pos
>= sb
.st_size
)
1616 request
->hr_last_byte_pos
= sb
.st_size
- 1;
1617 if (request
->hr_have_range
&&
1618 request
->hr_first_byte_pos
> request
->hr_last_byte_pos
) {
1619 request
->hr_have_range
= 0; /* punt */
1620 request
->hr_first_byte_pos
= 0;
1621 request
->hr_last_byte_pos
= sb
.st_size
- 1;
1623 debug((httpd
, DEBUG_FAT
, "have_range %d first_pos %lld last_pos %lld",
1624 request
->hr_have_range
,
1625 (long long)request
->hr_first_byte_pos
,
1626 (long long)request
->hr_last_byte_pos
));
1627 if (request
->hr_have_range
)
1628 bozo_printf(httpd
, "%s 206 Partial Content\r\n",
1631 bozo_printf(httpd
, "%s 200 OK\r\n", request
->hr_proto
);
1633 if (request
->hr_proto
!= httpd
->consts
.http_09
) {
1634 type
= bozo_content_type(request
, file
);
1636 encoding
= bozo_content_encoding(request
, file
);
1638 bozo_print_header(request
, &sb
, type
, encoding
);
1639 bozo_printf(httpd
, "\r\n");
1641 bozo_flush(httpd
, stdout
);
1643 if (request
->hr_method
!= HTTP_HEAD
) {
1644 off_t szleft
, cur_byte_pos
;
1647 request
->hr_last_byte_pos
- request
->hr_first_byte_pos
+ 1;
1648 cur_byte_pos
= request
->hr_first_byte_pos
;
1654 /* This should take care of the first unaligned chunk */
1655 if ((cur_byte_pos
& (httpd
->page_size
- 1)) != 0)
1656 sz
= (size_t)(cur_byte_pos
& ~httpd
->page_size
);
1657 if ((off_t
)httpd
->mmapsz
< szleft
)
1660 sz
= (size_t)szleft
;
1661 if (mmap_and_write_part(httpd
, fd
, cur_byte_pos
, sz
)) {
1662 if (errno
== ENOMEM
) {
1664 if (httpd
->mmapsz
>= httpd
->page_size
)
1676 close(STDIN_FILENO
);
1677 close(STDOUT_FILENO
);
1678 /*close(STDERR_FILENO);*/
1681 /* make sure we're not trying to access special files */
1683 bozo_check_special_files(bozo_httpreq_t
*request
, const char *name
)
1685 bozohttpd_t
*httpd
= request
->hr_httpd
;
1687 /* ensure basename(name) != special files */
1688 if (strcmp(name
, DIRECT_ACCESS_FILE
) == 0)
1689 return bozo_http_error(httpd
, 403, request
,
1690 "no permission to open direct access file");
1691 if (strcmp(name
, REDIRECT_FILE
) == 0)
1692 return bozo_http_error(httpd
, 403, request
,
1693 "no permission to open redirect file");
1694 if (strcmp(name
, ABSREDIRECT_FILE
) == 0)
1695 return bozo_http_error(httpd
, 403, request
,
1696 "no permission to open redirect file");
1697 return bozo_auth_check_special_files(request
, name
);
1700 /* generic header printing routine */
1702 bozo_print_header(bozo_httpreq_t
*request
,
1703 struct stat
*sbp
, const char *type
, const char *encoding
)
1705 bozohttpd_t
*httpd
= request
->hr_httpd
;
1709 bozo_printf(httpd
, "Date: %s\r\n", bozo_http_date(date
, sizeof(date
)));
1710 bozo_printf(httpd
, "Server: %s\r\n", httpd
->server_software
);
1711 bozo_printf(httpd
, "Accept-Ranges: bytes\r\n");
1716 tm
= gmtime(&sbp
->st_mtime
);
1717 strftime(filedate
, sizeof filedate
,
1718 "%a, %d %b %Y %H:%M:%S GMT", tm
);
1719 bozo_printf(httpd
, "Last-Modified: %s\r\n", filedate
);
1722 bozo_printf(httpd
, "Content-Type: %s\r\n", type
);
1723 if (encoding
&& *encoding
)
1724 bozo_printf(httpd
, "Content-Encoding: %s\r\n", encoding
);
1726 if (request
->hr_have_range
) {
1727 len
= request
->hr_last_byte_pos
-
1728 request
->hr_first_byte_pos
+1;
1730 "Content-Range: bytes %qd-%qd/%qd\r\n",
1731 (long long) request
->hr_first_byte_pos
,
1732 (long long) request
->hr_last_byte_pos
,
1733 (long long) sbp
->st_size
);
1736 bozo_printf(httpd
, "Content-Length: %qd\r\n", (long long)len
);
1738 if (request
&& request
->hr_proto
== httpd
->consts
.http_11
)
1739 bozo_printf(httpd
, "Connection: close\r\n");
1740 bozo_flush(httpd
, stdout
);
1745 debug__(bozohttpd_t
*httpd
, int level
, const char *fmt
, ...)
1750 /* only log if the level is low enough */
1751 if (httpd
->debug
< level
)
1756 if (httpd
->logstderr
) {
1757 vfprintf(stderr
, fmt
, ap
);
1758 fputs("\n", stderr
);
1760 vsyslog(LOG_DEBUG
, fmt
, ap
);
1764 #endif /* NO_DEBUG */
1766 /* these are like warn() and err(), except for syslog not stderr */
1768 bozo_warn(bozohttpd_t
*httpd
, const char *fmt
, ...)
1773 if (httpd
->logstderr
|| isatty(STDERR_FILENO
)) {
1774 //fputs("warning: ", stderr);
1775 vfprintf(stderr
, fmt
, ap
);
1776 fputs("\n", stderr
);
1778 vsyslog(LOG_INFO
, fmt
, ap
);
1783 bozo_err(bozohttpd_t
*httpd
, int code
, const char *fmt
, ...)
1788 if (httpd
->logstderr
|| isatty(STDERR_FILENO
)) {
1789 //fputs("error: ", stderr);
1790 vfprintf(stderr
, fmt
, ap
);
1791 fputs("\n", stderr
);
1793 vsyslog(LOG_ERR
, fmt
, ap
);
1799 * this escapes HTML tags. returns allocated escaped
1800 * string if needed, or NULL on allocation failure or
1801 * lack of escape need.
1802 * call with NULL httpd in error paths, to avoid recursive
1803 * malloc failure. call with valid httpd in normal paths
1804 * to get automatic allocation failure handling.
1807 bozo_escape_html(bozohttpd_t
*httpd
, const char *url
)
1813 for (i
= 0, j
= 0; url
[i
]; i
++) {
1829 * we need to handle being called from different
1832 len
= strlen(url
) + j
;
1834 tmp
= bozomalloc(httpd
, len
);
1835 else if ((tmp
= malloc(len
)) == 0)
1838 for (i
= 0, j
= 0; url
[i
]; i
++) {
1841 memcpy(tmp
+ j
, "<", 4);
1845 memcpy(tmp
+ j
, ">", 4);
1849 memcpy(tmp
+ j
, "&", 5);
1861 /* short map between error code, and short/long messages */
1862 static struct errors_map
{
1863 int code
; /* HTTP return code */
1864 const char *shortmsg
; /* short version of message */
1865 const char *longmsg
; /* long version of message */
1867 { 400, "400 Bad Request", "The request was not valid", },
1868 { 401, "401 Unauthorized", "No authorization", },
1869 { 403, "403 Forbidden", "Access to this item has been denied",},
1870 { 404, "404 Not Found", "This item has not been found", },
1871 { 408, "408 Request Timeout", "This request took too long", },
1872 { 417, "417 Expectation Failed","Expectations not available", },
1873 { 420, "420 Enhance Your Calm","Chill, Winston", },
1874 { 500, "500 Internal Error", "An error occured on the server", },
1875 { 501, "501 Not Implemented", "This request is not available", },
1879 static const char *help
= "DANGER! WILL ROBINSON! DANGER!";
1882 http_errors_short(int code
)
1884 struct errors_map
*ep
;
1886 for (ep
= errors_map
; ep
->code
; ep
++)
1887 if (ep
->code
== code
)
1888 return (ep
->shortmsg
);
1893 http_errors_long(int code
)
1895 struct errors_map
*ep
;
1897 for (ep
= errors_map
; ep
->code
; ep
++)
1898 if (ep
->code
== code
)
1899 return (ep
->longmsg
);
1903 /* the follow functions and variables are used in handling HTTP errors */
1906 bozo_http_error(bozohttpd_t
*httpd
, int code
, bozo_httpreq_t
*request
,
1910 const char *header
= http_errors_short(code
);
1911 const char *reason
= http_errors_long(code
);
1912 const char *proto
= (request
&& request
->hr_proto
) ?
1913 request
->hr_proto
: httpd
->consts
.http_11
;
1916 debug((httpd
, DEBUG_FAT
, "bozo_http_error %d: %s", code
, msg
));
1917 if (header
== NULL
|| reason
== NULL
) {
1919 "bozo_http_error() failed (short = %p, long = %p)",
1924 if (request
&& request
->hr_serverport
&&
1925 strcmp(request
->hr_serverport
, "80") != 0)
1926 snprintf(portbuf
, sizeof(portbuf
), ":%s",
1927 request
->hr_serverport
);
1931 if (request
&& request
->hr_file
) {
1933 const char *hostname
= BOZOHOST(httpd
, request
);
1935 /* bozo_escape_html() failure here is just too bad. */
1936 file
= bozo_escape_html(NULL
, request
->hr_file
);
1938 file
= request
->hr_file
;
1939 size
= snprintf(httpd
->errorbuf
, BUFSIZ
,
1940 "<html><head><title>%s</title></head>\n"
1941 "<body><h1>%s</h1>\n"
1942 "%s: <pre>%s</pre>\n"
1943 "<hr><address><a href=\"http://%s%s/\">%s%s</a></address>\n"
1945 header
, header
, file
, reason
,
1946 hostname
, portbuf
, hostname
, portbuf
);
1947 if (size
>= (int)BUFSIZ
) {
1949 "bozo_http_error buffer too small, truncated");
1955 bozo_printf(httpd
, "%s %s\r\n", proto
, header
);
1957 bozo_auth_check_401(request
, code
);
1959 bozo_printf(httpd
, "Content-Type: text/html\r\n");
1960 bozo_printf(httpd
, "Content-Length: %d\r\n", size
);
1961 bozo_printf(httpd
, "Server: %s\r\n", httpd
->server_software
);
1962 if (request
&& request
->hr_allow
)
1963 bozo_printf(httpd
, "Allow: %s\r\n", request
->hr_allow
);
1964 bozo_printf(httpd
, "\r\n");
1965 /* According to the RFC 2616 sec. 9.4 HEAD method MUST NOT return a
1966 * message-body in the response */
1967 if (size
&& request
&& request
->hr_method
!= HTTP_HEAD
)
1968 bozo_printf(httpd
, "%s", httpd
->errorbuf
);
1969 bozo_flush(httpd
, stdout
);
1974 /* Below are various modified libc functions */
1977 * returns -1 in lenp if the string ran out before finding a delimiter,
1978 * but is otherwise the same as strsep. Note that the length must be
1979 * correctly passed in.
1982 bozostrnsep(char **strp
, const char *delim
, ssize_t
*lenp
)
1989 if ((s
= *strp
) == NULL
)
1992 if (lenp
&& --(*lenp
) == -1)
1997 if ((sc
= *spanp
++) == c
) {
2011 * inspired by fgetln(3), but works for fd's. should work identically
2012 * except it, however, does *not* return the newline, and it does nul
2013 * terminate the string.
2016 bozodgetln(bozohttpd_t
*httpd
, int fd
, ssize_t
*lenp
,
2017 ssize_t (*readfn
)(bozohttpd_t
*, int, void *, size_t))
2024 if (httpd
->getln_buflen
== 0) {
2025 /* should be plenty for most requests */
2026 httpd
->getln_buflen
= 128;
2027 httpd
->getln_buffer
= malloc((size_t)httpd
->getln_buflen
);
2028 if (httpd
->getln_buffer
== NULL
) {
2029 httpd
->getln_buflen
= 0;
2036 * we *have* to read one byte at a time, to not break cgi
2037 * programs (for we pass stdin off to them). could fix this
2038 * by becoming a fd-passing program instead of just exec'ing
2041 * the above is no longer true, we are the fd-passing
2044 for (; readfn(httpd
, fd
, &c
, 1) == 1; ) {
2045 debug((httpd
, DEBUG_EXPLODING
, "bozodgetln read %c", c
));
2047 if (len
>= httpd
->getln_buflen
- 1) {
2048 httpd
->getln_buflen
*= 2;
2049 debug((httpd
, DEBUG_EXPLODING
, "bozodgetln: "
2050 "reallocating buffer to buflen %zu",
2051 httpd
->getln_buflen
));
2052 nbuffer
= bozorealloc(httpd
, httpd
->getln_buffer
,
2053 (size_t)httpd
->getln_buflen
);
2054 httpd
->getln_buffer
= nbuffer
;
2057 httpd
->getln_buffer
[len
++] = c
;
2061 } else if (c
== '\n') {
2063 * HTTP/1.1 spec says to ignore CR and treat
2064 * LF as the real line terminator. even though
2065 * the same spec defines CRLF as the line
2066 * terminator, it is recommended in section 19.3
2067 * to do the LF trick for tolerance.
2077 httpd
->getln_buffer
[len
] = '\0';
2078 debug((httpd
, DEBUG_OBESE
, "bozodgetln returns: ``%s'' with len %zd",
2079 httpd
->getln_buffer
, len
));
2081 return httpd
->getln_buffer
;
2085 bozorealloc(bozohttpd_t
*httpd
, void *ptr
, size_t size
)
2089 p
= realloc(ptr
, size
);
2091 (void)bozo_http_error(httpd
, 500, NULL
,
2092 "memory allocation failure");
2099 bozomalloc(bozohttpd_t
*httpd
, size_t size
)
2105 (void)bozo_http_error(httpd
, 500, NULL
,
2106 "memory allocation failure");
2113 bozostrdup(bozohttpd_t
*httpd
, const char *str
)
2119 (void)bozo_http_error(httpd
, 500, NULL
,
2120 "memory allocation failure");
2126 /* set default values in bozohttpd_t struct */
2128 bozo_init_httpd(bozohttpd_t
*httpd
)
2130 /* make sure everything is clean */
2131 (void) memset(httpd
, 0x0, sizeof(*httpd
));
2134 httpd
->consts
.http_09
= "HTTP/0.9";
2135 httpd
->consts
.http_10
= "HTTP/1.0";
2136 httpd
->consts
.http_11
= "HTTP/1.1";
2137 httpd
->consts
.text_plain
= "text/plain";
2139 /* mmap region size */
2140 httpd
->mmapsz
= BOZO_MMAPSZ
;
2142 /* error buffer for bozo_http_error() */
2143 if ((httpd
->errorbuf
= malloc(BUFSIZ
)) == NULL
) {
2144 (void) fprintf(stderr
,
2145 "bozohttpd: memory_allocation failure\n");
2148 #ifndef NO_LUA_SUPPORT
2149 SIMPLEQ_INIT(&httpd
->lua_states
);
2154 /* set default values in bozoprefs_t struct */
2156 bozo_init_prefs(bozoprefs_t
*prefs
)
2158 /* make sure everything is clean */
2159 (void) memset(prefs
, 0x0, sizeof(*prefs
));
2161 /* set up default values */
2162 bozo_set_pref(prefs
, "server software", SERVER_SOFTWARE
);
2163 bozo_set_pref(prefs
, "index.html", INDEX_HTML
);
2164 bozo_set_pref(prefs
, "public_html", PUBLIC_HTML
);
2169 /* set default values */
2171 bozo_set_defaults(bozohttpd_t
*httpd
, bozoprefs_t
*prefs
)
2173 return bozo_init_httpd(httpd
) && bozo_init_prefs(prefs
);
2176 /* set the virtual host name, port and root */
2178 bozo_setup(bozohttpd_t
*httpd
, bozoprefs_t
*prefs
, const char *vhost
,
2182 extern char **environ
;
2183 static char *cleanenv
[1] = { NULL
};
2193 if (vhost
== NULL
) {
2194 httpd
->virthostname
= bozomalloc(httpd
, MAXHOSTNAMELEN
+1);
2195 /* XXX we do not check for FQDN here */
2196 if (gethostname(httpd
->virthostname
, MAXHOSTNAMELEN
+1) < 0)
2197 bozo_err(httpd
, 1, "gethostname");
2198 httpd
->virthostname
[MAXHOSTNAMELEN
] = '\0';
2200 httpd
->virthostname
= strdup(vhost
);
2202 httpd
->slashdir
= strdup(root
);
2203 if ((portnum
= bozo_get_pref(prefs
, "port number")) != NULL
) {
2204 httpd
->bindport
= strdup(portnum
);
2207 /* go over preferences now */
2208 if ((cp
= bozo_get_pref(prefs
, "numeric")) != NULL
&&
2209 strcmp(cp
, "true") == 0) {
2212 if ((cp
= bozo_get_pref(prefs
, "trusted referal")) != NULL
&&
2213 strcmp(cp
, "true") == 0) {
2214 httpd
->untrustedref
= 1;
2216 if ((cp
= bozo_get_pref(prefs
, "log to stderr")) != NULL
&&
2217 strcmp(cp
, "true") == 0) {
2218 httpd
->logstderr
= 1;
2220 if ((cp
= bozo_get_pref(prefs
, "bind address")) != NULL
) {
2221 httpd
->bindaddress
= strdup(cp
);
2223 if ((cp
= bozo_get_pref(prefs
, "background")) != NULL
) {
2224 httpd
->background
= atoi(cp
);
2226 if ((cp
= bozo_get_pref(prefs
, "foreground")) != NULL
&&
2227 strcmp(cp
, "true") == 0) {
2228 httpd
->foreground
= 1;
2230 if ((cp
= bozo_get_pref(prefs
, "pid file")) != NULL
) {
2231 httpd
->pidfile
= strdup(cp
);
2233 if ((cp
= bozo_get_pref(prefs
, "unknown slash")) != NULL
&&
2234 strcmp(cp
, "true") == 0) {
2235 httpd
->unknown_slash
= 1;
2237 if ((cp
= bozo_get_pref(prefs
, "virtual base")) != NULL
) {
2238 httpd
->virtbase
= strdup(cp
);
2240 if ((cp
= bozo_get_pref(prefs
, "enable users")) != NULL
&&
2241 strcmp(cp
, "true") == 0) {
2242 httpd
->enable_users
= 1;
2244 if ((cp
= bozo_get_pref(prefs
, "dirty environment")) != NULL
&&
2245 strcmp(cp
, "true") == 0) {
2248 if ((cp
= bozo_get_pref(prefs
, "hide dots")) != NULL
&&
2249 strcmp(cp
, "true") == 0) {
2250 httpd
->hide_dots
= 1;
2252 if ((cp
= bozo_get_pref(prefs
, "directory indexing")) != NULL
&&
2253 strcmp(cp
, "true") == 0) {
2254 httpd
->dir_indexing
= 1;
2256 if ((cp
= bozo_get_pref(prefs
, "public_html")) != NULL
) {
2257 httpd
->public_html
= strdup(cp
);
2259 httpd
->server_software
=
2260 strdup(bozo_get_pref(prefs
, "server software"));
2261 httpd
->index_html
= strdup(bozo_get_pref(prefs
, "index.html"));
2264 * initialise ssl and daemon mode if necessary.
2266 bozo_ssl_init(httpd
);
2267 bozo_daemon_init(httpd
);
2269 if ((username
= bozo_get_pref(prefs
, "username")) == NULL
) {
2270 if ((pw
= getpwuid(uid
= 0)) == NULL
)
2271 bozo_err(httpd
, 1, "getpwuid(0): %s", strerror(errno
));
2272 httpd
->username
= strdup(pw
->pw_name
);
2274 httpd
->username
= strdup(username
);
2275 if ((pw
= getpwnam(httpd
->username
)) == NULL
)
2276 bozo_err(httpd
, 1, "getpwnam(%s): %s", httpd
->username
,
2278 if (initgroups(pw
->pw_name
, pw
->pw_gid
) == -1)
2279 bozo_err(httpd
, 1, "initgroups: %s", strerror(errno
));
2280 if (setgid(pw
->pw_gid
) == -1)
2281 bozo_err(httpd
, 1, "setgid(%u): %s", pw
->pw_gid
,
2288 if ((chrootdir
= bozo_get_pref(prefs
, "chroot dir")) != NULL
) {
2289 httpd
->rootdir
= strdup(chrootdir
);
2290 if (chdir(httpd
->rootdir
) == -1)
2291 bozo_err(httpd
, 1, "chdir(%s): %s", httpd
->rootdir
,
2293 if (chroot(httpd
->rootdir
) == -1)
2294 bozo_err(httpd
, 1, "chroot(%s): %s", httpd
->rootdir
,
2298 if (username
!= NULL
)
2299 if (setuid(uid
) == -1)
2300 bozo_err(httpd
, 1, "setuid(%d): %s", uid
,
2304 * prevent info leakage between different compartments.
2305 * some PATH values in the environment would be invalided
2306 * by chroot. cross-user settings might result in undesirable
2309 if ((chrootdir
!= NULL
|| username
!= NULL
) && !dirtyenv
)
2313 httpd
->page_size
= (long)sysconf(_SC_PAGESIZE
);
2315 httpd
->page_size
= 4096;
2317 debug((httpd
, DEBUG_OBESE
, "myname is %s, slashdir is %s",
2318 httpd
->virthostname
, httpd
->slashdir
));