bozohttpd.c revision 1.89 1 /* $NetBSD: bozohttpd.c,v 1.89 2018/11/19 04:12:22 mrg Exp $ */
2
3 /* $eterna: bozohttpd.c,v 1.178 2011/11/18 09:21:15 mrg Exp $ */
4
5 /*
6 * Copyright (c) 1997-2017 Matthew R. Green
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer and
16 * dedication in the documentation and/or other materials provided
17 * with the distribution.
18 *
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
29 * SUCH DAMAGE.
30 *
31 */
32
33 /* this program is dedicated to the Great God of Processed Cheese */
34
35 /*
36 * bozohttpd.c: minimal httpd; provides only these features:
37 * - HTTP/0.9 (by virtue of ..)
38 * - HTTP/1.0
39 * - HTTP/1.1
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
45 * - virtual hosting
46 * - not that we do not even pretend to understand MIME, but
47 * rely only on the HTTP specification
48 * - ipv6 support
49 * - automatic `index.html' generation
50 * - configurable server name
51 * - directory index generation
52 * - daemon mode (lacks libwrap support)
53 * - .htpasswd support
54 */
55
56 /*
57 * requirements for minimal http/1.1 (at least, as documented in
58 * RFC 2616 (HTTP/1.1):
59 *
60 * - 14.11: content-encoding handling. [1]
61 *
62 * - 14.13: content-length handling. this is only a SHOULD header
63 * thus we could just not send it ever. [1]
64 *
65 * - 14.17: content-type handling. [1]
66 *
67 * - 14.28: if-unmodified-since handling. if-modified-since is
68 * done since, shouldn't be too hard for this one.
69 *
70 * [1] need to revisit to ensure proper behaviour
71 *
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.
76 *
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'
80 * header.
81 *
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?
86 *
87 * - 10.1: we can ignore informational status codes
88 *
89 * - 10.3.3/10.3.4/10.3.8: just use '302' codes always.
90 *
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
93 * only SHOULD.
94 *
95 * - 14.5/14.16/14.35: only support simple ranges: %d- and %d-%d
96 * would be nice to support more.
97 *
98 * - 14.9: we aren't a cache.
99 *
100 * - 14.15: content-md5 would be nice.
101 *
102 * - 14.24/14.26/14.27: if-match, if-none-match, if-range. be
103 * nice to support this.
104 *
105 * - 14.44: Vary: seems unneeded. ignore it for now.
106 */
107
108 #ifndef INDEX_HTML
109 #define INDEX_HTML "index.html"
110 #endif
111 #ifndef SERVER_SOFTWARE
112 #define SERVER_SOFTWARE "bozohttpd/20181118"
113 #endif
114 #ifndef DIRECT_ACCESS_FILE
115 #define DIRECT_ACCESS_FILE ".bzdirect"
116 #endif
117 #ifndef REDIRECT_FILE
118 #define REDIRECT_FILE ".bzredirect"
119 #endif
120 #ifndef ABSREDIRECT_FILE
121 #define ABSREDIRECT_FILE ".bzabsredirect"
122 #endif
123 #ifndef REMAP_FILE
124 #define REMAP_FILE ".bzremap"
125 #endif
126
127 /*
128 * When you add some .bz* file, make sure to also check it in
129 * bozo_check_special_files()
130 */
131
132 #ifndef PUBLIC_HTML
133 #define PUBLIC_HTML "public_html"
134 #endif
135
136 #ifndef USE_ARG
137 #define USE_ARG(x) /*LINTED*/(void)&(x)
138 #endif
139
140 /*
141 * And so it begins ..
142 */
143
144 #include <sys/param.h>
145 #include <sys/socket.h>
146 #include <sys/time.h>
147 #include <sys/mman.h>
148
149 #include <arpa/inet.h>
150
151 #include <ctype.h>
152 #include <dirent.h>
153 #include <errno.h>
154 #include <fcntl.h>
155 #include <netdb.h>
156 #include <pwd.h>
157 #include <grp.h>
158 #include <signal.h>
159 #include <stdarg.h>
160 #include <stdlib.h>
161 #include <stdbool.h>
162 #include <string.h>
163 #include <syslog.h>
164 #include <time.h>
165 #include <unistd.h>
166
167 #include "bozohttpd.h"
168
169 #ifndef MAX_WAIT_TIME
170 #define MAX_WAIT_TIME 60 /* hang around for 60 seconds max */
171 #endif
172
173 /* variables and functions */
174 #ifndef LOG_FTP
175 #define LOG_FTP LOG_DAEMON
176 #endif
177
178 volatile sig_atomic_t alarmhit;
179
180 /*
181 * check there's enough space in the prefs and names arrays.
182 */
183 static int
184 size_arrays(bozoprefs_t *bozoprefs, size_t needed)
185 {
186 char **temp;
187
188 if (bozoprefs->size == 0) {
189 /* only get here first time around */
190 bozoprefs->name = calloc(sizeof(char *), needed);
191 if (bozoprefs->name == NULL)
192 return 0;
193 bozoprefs->value = calloc(sizeof(char *), needed);
194 if (bozoprefs->value == NULL) {
195 free(bozoprefs->name);
196 return 0;
197 }
198 bozoprefs->size = needed;
199 } else if (bozoprefs->count == bozoprefs->size) {
200 /* only uses 'needed' when filled array */
201 temp = realloc(bozoprefs->name, sizeof(char *) * needed);
202 if (temp == NULL)
203 return 0;
204 bozoprefs->name = temp;
205 temp = realloc(bozoprefs->value, sizeof(char *) * needed);
206 if (temp == NULL)
207 return 0;
208 bozoprefs->value = temp;
209 bozoprefs->size += needed;
210 }
211 return 1;
212 }
213
214 static ssize_t
215 findvar(bozoprefs_t *bozoprefs, const char *name)
216 {
217 size_t i;
218
219 for (i = 0; i < bozoprefs->count; i++)
220 if (strcmp(bozoprefs->name[i], name) == 0)
221 return (ssize_t)i;
222 return -1;
223 }
224
225 int
226 bozo_set_pref(bozohttpd_t *httpd, bozoprefs_t *bozoprefs,
227 const char *name, const char *value)
228 {
229 ssize_t i;
230
231 if ((i = findvar(bozoprefs, name)) < 0) {
232 /* add the element to the array */
233 if (!size_arrays(bozoprefs, bozoprefs->size + 15))
234 return 0;
235 i = bozoprefs->count++;
236 bozoprefs->name[i] = bozostrdup(httpd, NULL, name);
237 } else {
238 /* replace the element in the array */
239 if (bozoprefs->value[i]) {
240 free(bozoprefs->value[i]);
241 bozoprefs->value[i] = NULL;
242 }
243 }
244 bozoprefs->value[i] = bozostrdup(httpd, NULL, value);
245 return 1;
246 }
247
248 /*
249 * get a variable's value, or NULL
250 */
251 char *
252 bozo_get_pref(bozoprefs_t *bozoprefs, const char *name)
253 {
254 ssize_t i;
255
256 i = findvar(bozoprefs, name);
257 return i < 0 ? NULL : bozoprefs->value[i];
258 }
259
260 char *
261 bozo_http_date(char *date, size_t datelen)
262 {
263 struct tm *tm;
264 time_t now;
265
266 /* Sun, 06 Nov 1994 08:49:37 GMT */
267 now = time(NULL);
268 tm = gmtime(&now); /* HTTP/1.1 spec rev 06 sez GMT only */
269 strftime(date, datelen, "%a, %d %b %Y %H:%M:%S GMT", tm);
270 return date;
271 }
272
273 /*
274 * convert "in" into the three parts of a request (first line).
275 * we allocate into file and query, but return pointers into
276 * "in" for proto and method.
277 */
278 static void
279 parse_request(bozohttpd_t *httpd, char *in, char **method, char **file,
280 char **query, char **proto)
281 {
282 ssize_t len;
283 char *val;
284
285 USE_ARG(httpd);
286 debug((httpd, DEBUG_EXPLODING, "parse in: %s", in));
287 *method = *file = *query = *proto = NULL;
288
289 len = (ssize_t)strlen(in);
290 val = bozostrnsep(&in, " \t\n\r", &len);
291 if (len < 1 || val == NULL)
292 return;
293 *method = val;
294
295 while (*in == ' ' || *in == '\t')
296 in++;
297 val = bozostrnsep(&in, " \t\n\r", &len);
298 if (len < 1) {
299 if (len == 0)
300 *file = val;
301 else
302 *file = in;
303 } else {
304 *file = val;
305
306 *query = strchr(*file, '?');
307 if (*query)
308 *(*query)++ = '\0';
309
310 if (in) {
311 while (*in && (*in == ' ' || *in == '\t'))
312 in++;
313 if (*in)
314 *proto = in;
315 }
316 }
317
318 /* allocate private copies */
319 *file = bozostrdup(httpd, NULL, *file);
320 if (*query)
321 *query = bozostrdup(httpd, NULL, *query);
322
323 debug((httpd, DEBUG_FAT,
324 "url: method: \"%s\" file: \"%s\" query: \"%s\" proto: \"%s\"",
325 *method, *file, *query, *proto));
326 }
327
328 /*
329 * cleanup a bozo_httpreq_t after use
330 */
331 void
332 bozo_clean_request(bozo_httpreq_t *request)
333 {
334 struct bozoheaders *hdr, *ohdr = NULL;
335
336 if (request == NULL)
337 return;
338
339 /* If SSL enabled cleanup SSL structure. */
340 bozo_ssl_destroy(request->hr_httpd);
341
342 /* clean up request */
343 free(request->hr_remotehost);
344 free(request->hr_remoteaddr);
345 free(request->hr_serverport);
346 free(request->hr_virthostname);
347 free(request->hr_file);
348 free(request->hr_oldfile);
349 free(request->hr_query);
350 free(request->hr_host);
351 bozo_user_free(request->hr_user);
352 bozo_auth_cleanup(request);
353 for (hdr = SIMPLEQ_FIRST(&request->hr_headers); hdr;
354 hdr = SIMPLEQ_NEXT(hdr, h_next)) {
355 free(hdr->h_value);
356 free(hdr->h_header);
357 free(ohdr);
358 ohdr = hdr;
359 }
360 free(ohdr);
361 ohdr = NULL;
362 for (hdr = SIMPLEQ_FIRST(&request->hr_replheaders); hdr;
363 hdr = SIMPLEQ_NEXT(hdr, h_next)) {
364 free(hdr->h_value);
365 free(hdr->h_header);
366 free(ohdr);
367 ohdr = hdr;
368 }
369 free(ohdr);
370
371 free(request);
372 }
373
374 /*
375 * send a HTTP/1.1 408 response if we timeout.
376 */
377 /* ARGSUSED */
378 static void
379 alarmer(int sig)
380 {
381 alarmhit = 1;
382 }
383
384 /*
385 * a list of header quirks: currently, a list of headers that
386 * can't be folded into a single line.
387 */
388 const char *header_quirks[] = { "WWW-Authenticate", NULL };
389
390 /*
391 * add or merge this header (val: str) into the requests list
392 */
393 static bozoheaders_t *
394 addmerge_header(bozo_httpreq_t *request, struct qheaders *headers,
395 const char *val, const char *str, ssize_t len)
396 {
397 struct bozohttpd_t *httpd = request->hr_httpd;
398 struct bozoheaders *hdr = NULL;
399 const char **quirk;
400
401 USE_ARG(len);
402 for (quirk = header_quirks; *quirk; quirk++)
403 if (strcasecmp(*quirk, val) == 0)
404 break;
405
406 if (*quirk == NULL) {
407 /* do we exist already? */
408 SIMPLEQ_FOREACH(hdr, headers, h_next) {
409 if (strcasecmp(val, hdr->h_header) == 0)
410 break;
411 }
412 }
413
414 if (hdr) {
415 /* yup, merge it in */
416 char *nval;
417
418 bozoasprintf(httpd, &nval, "%s, %s", hdr->h_value, str);
419 free(hdr->h_value);
420 hdr->h_value = nval;
421 } else {
422 /* nope, create a new one */
423
424 hdr = bozomalloc(httpd, sizeof *hdr);
425 hdr->h_header = bozostrdup(httpd, request, val);
426 if (str && *str)
427 hdr->h_value = bozostrdup(httpd, request, str);
428 else
429 hdr->h_value = bozostrdup(httpd, request, " ");
430
431 SIMPLEQ_INSERT_TAIL(headers, hdr, h_next);
432 request->hr_nheaders++;
433 }
434
435 return hdr;
436 }
437
438 bozoheaders_t *
439 addmerge_reqheader(bozo_httpreq_t *request, const char *val, const char *str,
440 ssize_t len)
441 {
442
443 return addmerge_header(request, &request->hr_headers, val, str, len);
444 }
445
446 bozoheaders_t *
447 addmerge_replheader(bozo_httpreq_t *request, const char *val, const char *str,
448 ssize_t len)
449 {
450
451 return addmerge_header(request, &request->hr_replheaders,
452 val, str, len);
453 }
454
455 /*
456 * as the prototype string is not constant (eg, "HTTP/1.1" is equivalent
457 * to "HTTP/001.01"), we MUST parse this.
458 */
459 static int
460 process_proto(bozo_httpreq_t *request, const char *proto)
461 {
462 struct bozohttpd_t *httpd = request->hr_httpd;
463 char majorstr[16], *minorstr;
464 int majorint, minorint;
465
466 if (proto == NULL) {
467 got_proto_09:
468 request->hr_proto = httpd->consts.http_09;
469 debug((httpd, DEBUG_FAT, "request %s is http/0.9",
470 request->hr_file));
471 return 0;
472 }
473
474 if (strncasecmp(proto, "HTTP/", 5) != 0)
475 goto bad;
476 strncpy(majorstr, proto + 5, sizeof majorstr);
477 majorstr[sizeof(majorstr)-1] = 0;
478 minorstr = strchr(majorstr, '.');
479 if (minorstr == NULL)
480 goto bad;
481 *minorstr++ = 0;
482
483 majorint = atoi(majorstr);
484 minorint = atoi(minorstr);
485
486 switch (majorint) {
487 case 0:
488 if (minorint != 9)
489 break;
490 goto got_proto_09;
491 case 1:
492 if (minorint == 0)
493 request->hr_proto = httpd->consts.http_10;
494 else if (minorint == 1)
495 request->hr_proto = httpd->consts.http_11;
496 else
497 break;
498
499 debug((httpd, DEBUG_FAT, "request %s is %s",
500 request->hr_file, request->hr_proto));
501 SIMPLEQ_INIT(&request->hr_headers);
502 request->hr_nheaders = 0;
503 return 0;
504 }
505 bad:
506 return bozo_http_error(httpd, 404, NULL, "unknown prototype");
507 }
508
509 /*
510 * process each type of HTTP method, setting this HTTP requests
511 * method type.
512 */
513 static struct method_map {
514 const char *name;
515 int type;
516 } method_map[] = {
517 { "GET", HTTP_GET, },
518 { "POST", HTTP_POST, },
519 { "HEAD", HTTP_HEAD, },
520 #if 0 /* other non-required http/1.1 methods */
521 { "OPTIONS", HTTP_OPTIONS, },
522 { "PUT", HTTP_PUT, },
523 { "DELETE", HTTP_DELETE, },
524 { "TRACE", HTTP_TRACE, },
525 { "CONNECT", HTTP_CONNECT, },
526 #endif
527 { NULL, 0, },
528 };
529
530 static int
531 process_method(bozo_httpreq_t *request, const char *method)
532 {
533 struct bozohttpd_t *httpd = request->hr_httpd;
534 struct method_map *mmp;
535
536 if (request->hr_proto == httpd->consts.http_11)
537 request->hr_allow = "GET, HEAD, POST";
538
539 for (mmp = method_map; mmp->name; mmp++)
540 if (strcasecmp(method, mmp->name) == 0) {
541 request->hr_method = mmp->type;
542 request->hr_methodstr = mmp->name;
543 return 0;
544 }
545
546 return bozo_http_error(httpd, 404, request, "unknown method");
547 }
548
549 /* check header byte count */
550 static int
551 bozo_got_header_length(bozo_httpreq_t *request, size_t len)
552 {
553 request->hr_header_bytes += len;
554 if (request->hr_header_bytes < BOZO_HEADERS_MAX_SIZE)
555 return 0;
556
557 return bozo_http_error(request->hr_httpd, 413, request,
558 "too many headers");
559 }
560
561 /*
562 * This function reads a http request from stdin, returning a pointer to a
563 * bozo_httpreq_t structure, describing the request.
564 */
565 bozo_httpreq_t *
566 bozo_read_request(bozohttpd_t *httpd)
567 {
568 struct sigaction sa;
569 char *str, *val, *method, *file, *proto, *query;
570 char *host, *addr, *port;
571 char bufport[10];
572 char hbuf[NI_MAXHOST], abuf[NI_MAXHOST];
573 struct sockaddr_storage ss;
574 ssize_t len;
575 int line = 0;
576 socklen_t slen;
577 bozo_httpreq_t *request;
578
579 /*
580 * if we're in daemon mode, bozo_daemon_fork() will return here twice
581 * for each call. once in the child, returning 0, and once in the
582 * parent, returning 1. for each child, then we can setup SSL, and
583 * the parent can signal the caller there was no request to process
584 * and it will wait for another.
585 */
586 if (bozo_daemon_fork(httpd))
587 return NULL;
588 if (bozo_ssl_accept(httpd))
589 return NULL;
590
591 request = bozomalloc(httpd, sizeof(*request));
592 memset(request, 0, sizeof(*request));
593 request->hr_httpd = httpd;
594 request->hr_allow = request->hr_host = NULL;
595 request->hr_content_type = request->hr_content_length = NULL;
596 request->hr_range = NULL;
597 request->hr_last_byte_pos = -1;
598 request->hr_if_modified_since = NULL;
599 request->hr_virthostname = NULL;
600 request->hr_file = NULL;
601 request->hr_oldfile = NULL;
602 SIMPLEQ_INIT(&request->hr_replheaders);
603 bozo_auth_init(request);
604
605 slen = sizeof(ss);
606 if (getpeername(0, (struct sockaddr *)(void *)&ss, &slen) < 0)
607 host = addr = NULL;
608 else {
609 if (getnameinfo((struct sockaddr *)(void *)&ss, slen,
610 abuf, sizeof abuf, NULL, 0, NI_NUMERICHOST) == 0)
611 addr = abuf;
612 else
613 addr = NULL;
614 if (httpd->numeric == 0 &&
615 getnameinfo((struct sockaddr *)(void *)&ss, slen,
616 hbuf, sizeof hbuf, NULL, 0, 0) == 0)
617 host = hbuf;
618 else
619 host = NULL;
620 }
621 if (host != NULL)
622 request->hr_remotehost = bozostrdup(httpd, request, host);
623 if (addr != NULL)
624 request->hr_remoteaddr = bozostrdup(httpd, request, addr);
625 slen = sizeof(ss);
626
627 /*
628 * Override the bound port from the request value, so it works even
629 * if passed through a proxy that doesn't rewrite the port.
630 */
631 if (httpd->bindport) {
632 if (strcmp(httpd->bindport, "80") != 0)
633 port = httpd->bindport;
634 else
635 port = NULL;
636 } else {
637 if (getsockname(0, (struct sockaddr *)(void *)&ss, &slen) < 0)
638 port = NULL;
639 else {
640 if (getnameinfo((struct sockaddr *)(void *)&ss, slen,
641 NULL, 0, bufport, sizeof bufport,
642 NI_NUMERICSERV) == 0)
643 port = bufport;
644 else
645 port = NULL;
646 }
647 }
648 if (port != NULL)
649 request->hr_serverport = bozostrdup(httpd, request, port);
650
651 /*
652 * setup a timer to make sure the request is not hung
653 */
654 sa.sa_handler = alarmer;
655 sigemptyset(&sa.sa_mask);
656 sigaddset(&sa.sa_mask, SIGALRM);
657 sa.sa_flags = 0;
658 sigaction(SIGALRM, &sa, NULL);
659
660 alarm(MAX_WAIT_TIME);
661 while ((str = bozodgetln(httpd, STDIN_FILENO, &len, bozo_read)) != NULL) {
662 alarm(0);
663 if (alarmhit) {
664 (void)bozo_http_error(httpd, 408, NULL,
665 "request timed out");
666 goto cleanup;
667 }
668 line++;
669
670 if (line == 1) {
671
672 if (len < 1) {
673 (void)bozo_http_error(httpd, 404, NULL,
674 "null method");
675 goto cleanup;
676 }
677 bozowarn(httpd,
678 "got request ``%s'' from host %s to port %s",
679 str,
680 host ? host : addr ? addr : "<local>",
681 port ? port : "<stdin>");
682
683 /* we allocate return space in file and query only */
684 parse_request(httpd, str, &method, &file, &query, &proto);
685 request->hr_file = file;
686 request->hr_query = query;
687 if (method == NULL) {
688 (void)bozo_http_error(httpd, 404, NULL,
689 "null method");
690 goto cleanup;
691 }
692 if (file == NULL) {
693 (void)bozo_http_error(httpd, 404, NULL,
694 "null file");
695 goto cleanup;
696 }
697
698 /*
699 * note that we parse the proto first, so that we
700 * can more properly parse the method and the url.
701 */
702
703 if (process_proto(request, proto) ||
704 process_method(request, method)) {
705 goto cleanup;
706 }
707
708 debug((httpd, DEBUG_FAT, "got file \"%s\" query \"%s\"",
709 request->hr_file,
710 request->hr_query ? request->hr_query : "<none>"));
711
712 /* http/0.9 has no header processing */
713 if (request->hr_proto == httpd->consts.http_09)
714 break;
715 } else { /* incoming headers */
716 bozoheaders_t *hdr;
717
718 if (*str == '\0')
719 break;
720
721 val = bozostrnsep(&str, ":", &len);
722 debug((httpd, DEBUG_EXPLODING,
723 "read_req2: after bozostrnsep: str ``%s'' val ``%s''",
724 str, val));
725 if (val == NULL || len == -1) {
726 (void)bozo_http_error(httpd, 404, request,
727 "no header");
728 goto cleanup;
729 }
730 while (*str == ' ' || *str == '\t')
731 len--, str++;
732 while (*val == ' ' || *val == '\t')
733 val++;
734
735 if (bozo_got_header_length(request, len))
736 goto cleanup;
737
738 if (bozo_auth_check_headers(request, val, str, len))
739 goto next_header;
740
741 hdr = addmerge_reqheader(request, val, str, len);
742
743 if (strcasecmp(hdr->h_header, "content-type") == 0)
744 request->hr_content_type = hdr->h_value;
745 else if (strcasecmp(hdr->h_header, "content-length") == 0)
746 request->hr_content_length = hdr->h_value;
747 else if (strcasecmp(hdr->h_header, "host") == 0)
748 request->hr_host = bozostrdup(httpd, request,
749 hdr->h_value);
750 /* RFC 2616 (HTTP/1.1): 14.20 */
751 else if (strcasecmp(hdr->h_header, "expect") == 0) {
752 (void)bozo_http_error(httpd, 417, request,
753 "we don't support Expect:");
754 goto cleanup;
755 }
756 else if (strcasecmp(hdr->h_header, "referrer") == 0 ||
757 strcasecmp(hdr->h_header, "referer") == 0)
758 request->hr_referrer = hdr->h_value;
759 else if (strcasecmp(hdr->h_header, "range") == 0)
760 request->hr_range = hdr->h_value;
761 else if (strcasecmp(hdr->h_header,
762 "if-modified-since") == 0)
763 request->hr_if_modified_since = hdr->h_value;
764 else if (strcasecmp(hdr->h_header,
765 "accept-encoding") == 0)
766 request->hr_accept_encoding = hdr->h_value;
767
768 debug((httpd, DEBUG_FAT, "adding header %s: %s",
769 hdr->h_header, hdr->h_value));
770 }
771 next_header:
772 alarm(MAX_WAIT_TIME);
773 }
774
775 /* now, clear it all out */
776 alarm(0);
777 signal(SIGALRM, SIG_DFL);
778
779 /* RFC1945, 8.3 */
780 if (request->hr_method == HTTP_POST &&
781 request->hr_content_length == NULL) {
782 (void)bozo_http_error(httpd, 400, request,
783 "missing content length");
784 goto cleanup;
785 }
786
787 /* RFC 2616 (HTTP/1.1), 14.23 & 19.6.1.1 */
788 if (request->hr_proto == httpd->consts.http_11 &&
789 /*(strncasecmp(request->hr_file, "http://", 7) != 0) &&*/
790 request->hr_host == NULL) {
791 (void)bozo_http_error(httpd, 400, request,
792 "missing Host header");
793 goto cleanup;
794 }
795
796 if (request->hr_range != NULL) {
797 debug((httpd, DEBUG_FAT, "hr_range: %s", request->hr_range));
798 /* support only simple ranges %d- and %d-%d */
799 if (strchr(request->hr_range, ',') == NULL) {
800 const char *rstart, *dash;
801
802 rstart = strchr(request->hr_range, '=');
803 if (rstart != NULL) {
804 rstart++;
805 dash = strchr(rstart, '-');
806 if (dash != NULL && dash != rstart) {
807 dash++;
808 request->hr_have_range = 1;
809 request->hr_first_byte_pos =
810 strtoll(rstart, NULL, 10);
811 if (request->hr_first_byte_pos < 0)
812 request->hr_first_byte_pos = 0;
813 if (*dash != '\0') {
814 request->hr_last_byte_pos =
815 strtoll(dash, NULL, 10);
816 if (request->hr_last_byte_pos < 0)
817 request->hr_last_byte_pos = -1;
818 }
819 }
820 }
821 }
822 }
823
824 debug((httpd, DEBUG_FAT, "bozo_read_request returns url %s in request",
825 request->hr_file));
826 return request;
827
828 cleanup:
829 bozo_clean_request(request);
830
831 return NULL;
832 }
833
834 static int
835 mmap_and_write_part(bozohttpd_t *httpd, int fd, off_t first_byte_pos, size_t sz)
836 {
837 size_t mappedsz, wroffset;
838 off_t mappedoffset;
839 char *addr;
840 void *mappedaddr;
841
842 /*
843 * we need to ensure that both the size *and* offset arguments to
844 * mmap() are page-aligned. our formala for this is:
845 *
846 * input offset: first_byte_pos
847 * input size: sz
848 *
849 * mapped offset = page align truncate (input offset)
850 * mapped size =
851 * page align extend (input offset - mapped offset + input size)
852 * write offset = input offset - mapped offset
853 *
854 * we use the write offset in all writes
855 */
856 mappedoffset = first_byte_pos & ~(httpd->page_size - 1);
857 mappedsz = (size_t)
858 (first_byte_pos - mappedoffset + sz + httpd->page_size - 1) &
859 ~(httpd->page_size - 1);
860 wroffset = (size_t)(first_byte_pos - mappedoffset);
861
862 addr = mmap(0, mappedsz, PROT_READ, MAP_SHARED, fd, mappedoffset);
863 if (addr == (char *)-1) {
864 bozowarn(httpd, "mmap failed: %s", strerror(errno));
865 return -1;
866 }
867 mappedaddr = addr;
868
869 #ifdef MADV_SEQUENTIAL
870 (void)madvise(addr, sz, MADV_SEQUENTIAL);
871 #endif
872 while (sz > BOZO_WRSZ) {
873 if (bozo_write(httpd, STDOUT_FILENO, addr + wroffset,
874 BOZO_WRSZ) != BOZO_WRSZ) {
875 bozowarn(httpd, "write failed: %s", strerror(errno));
876 goto out;
877 }
878 debug((httpd, DEBUG_OBESE, "wrote %d bytes", BOZO_WRSZ));
879 sz -= BOZO_WRSZ;
880 addr += BOZO_WRSZ;
881 }
882 if (sz && (size_t)bozo_write(httpd, STDOUT_FILENO, addr + wroffset,
883 sz) != sz) {
884 bozowarn(httpd, "final write failed: %s", strerror(errno));
885 goto out;
886 }
887 debug((httpd, DEBUG_OBESE, "wrote %d bytes", (int)sz));
888 out:
889 if (munmap(mappedaddr, mappedsz) < 0) {
890 bozowarn(httpd, "munmap failed");
891 return -1;
892 }
893
894 return 0;
895 }
896
897 static int
898 parse_http_date(const char *val, time_t *timestamp)
899 {
900 char *remainder;
901 struct tm tm;
902
903 if ((remainder = strptime(val, "%a, %d %b %Y %T GMT", &tm)) == NULL &&
904 (remainder = strptime(val, "%a, %d-%b-%y %T GMT", &tm)) == NULL &&
905 (remainder = strptime(val, "%a %b %d %T %Y", &tm)) == NULL)
906 return 0; /* Invalid HTTP date format */
907
908 if (*remainder)
909 return 0; /* No trailing garbage */
910
911 *timestamp = timegm(&tm);
912 return 1;
913 }
914
915 /*
916 * given an url, encode it ala rfc 3986. ie, escape ? and friends.
917 * note that this function returns a static buffer, and thus needs
918 * to be updated for any sort of parallel processing. escape only
919 * chosen characters for absolute redirects
920 */
921 char *
922 bozo_escape_rfc3986(bozohttpd_t *httpd, const char *url, int absolute)
923 {
924 static char *buf;
925 static size_t buflen = 0;
926 size_t len;
927 const char *s;
928 char *d;
929
930 len = strlen(url);
931 if (buflen < len * 3 + 1) {
932 buflen = len * 3 + 1;
933 buf = bozorealloc(httpd, buf, buflen);
934 }
935
936 for (len = 0, s = url, d = buf; *s;) {
937 if (*s & 0x80)
938 goto encode_it;
939 switch (*s) {
940 case ':':
941 case '?':
942 case '#':
943 case '[':
944 case ']':
945 case '@':
946 case '!':
947 case '$':
948 case '&':
949 case '\'':
950 case '(':
951 case ')':
952 case '*':
953 case '+':
954 case ',':
955 case ';':
956 case '=':
957 case '%':
958 case '"':
959 if (absolute)
960 goto leave_it;
961 case '\n':
962 case '\r':
963 case ' ':
964 encode_it:
965 snprintf(d, 4, "%%%02X", *s++);
966 d += 3;
967 len += 3;
968 break;
969 leave_it:
970 default:
971 *d++ = *s++;
972 len++;
973 break;
974 }
975 }
976 buf[len] = 0;
977
978 return buf;
979 }
980
981 /*
982 * do automatic redirection -- if there are query parameters or userdir for
983 * the URL we will tack these on to the new (redirected) URL.
984 */
985 static void
986 handle_redirect(bozo_httpreq_t *request, const char *url, int absolute)
987 {
988 bozohttpd_t *httpd = request->hr_httpd;
989 char *finalurl, *urlbuf;
990 #ifndef NO_USER_SUPPORT
991 char *userbuf;
992 #endif /* !NO_USER_SUPPORT */
993 char portbuf[20];
994 const char *scheme, *query, *quest;
995 const char *hostname = BOZOHOST(httpd, request);
996 int absproto = 0; /* absolute redirect provides own schema */
997
998 if (url == NULL) {
999 bozoasprintf(httpd, &urlbuf, "/%s/", request->hr_file);
1000 url = urlbuf;
1001 } else
1002 urlbuf = NULL;
1003
1004 #ifndef NO_USER_SUPPORT
1005 if (request->hr_user && !absolute) {
1006 bozoasprintf(httpd, &userbuf, "/~%s%s", request->hr_user, url);
1007 url = userbuf;
1008 } else
1009 userbuf = NULL;
1010 #endif /* !NO_USER_SUPPORT */
1011
1012 if (absolute) {
1013 char *sep = NULL;
1014 const char *s;
1015
1016 /*
1017 * absolute redirect may specify own protocol i.e. to redirect
1018 * to another schema like https:// or ftp://.
1019 * Details: RFC 3986, section 3.
1020 */
1021
1022 /* 1. check if url contains :// */
1023 sep = strstr(url, "://");
1024
1025 /*
1026 * RFC 3986, section 3.1:
1027 * scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
1028 */
1029 if (sep) {
1030 for (s = url; s != sep;) {
1031 if (!isalnum((int)*s) &&
1032 *s != '+' && *s != '-' && *s != '.')
1033 break;
1034 if (++s == sep) {
1035 absproto = 1;
1036 }
1037 }
1038 }
1039 }
1040
1041 /* construct final redirection url */
1042
1043 scheme = absproto ? "" : httpd->sslinfo ? "https://" : "http://";
1044
1045 if (absolute) {
1046 hostname = "";
1047 portbuf[0] = '\0';
1048 } else {
1049 const char *defport = httpd->sslinfo ? "443" : "80";
1050
1051 if (request->hr_serverport &&
1052 strcmp(request->hr_serverport, defport) != 0)
1053 snprintf(portbuf, sizeof(portbuf), ":%s",
1054 request->hr_serverport);
1055 else
1056 portbuf[0] = '\0';
1057 }
1058
1059 url = bozo_escape_rfc3986(httpd, url, absolute);
1060
1061 if (request->hr_query && strlen(request->hr_query)) {
1062 query = request->hr_query;
1063 quest = "?";
1064 } else {
1065 query = quest = "";
1066 }
1067
1068 bozoasprintf(httpd, &finalurl, "%s%s%s%s%s%s",
1069 scheme, hostname, portbuf, url, quest, query);
1070
1071 bozowarn(httpd, "redirecting %s", finalurl);
1072 debug((httpd, DEBUG_FAT, "redirecting %s", finalurl));
1073
1074 bozo_printf(httpd, "%s 301 Document Moved\r\n", request->hr_proto);
1075 if (request->hr_proto != httpd->consts.http_09)
1076 bozo_print_header(request, NULL, "text/html", NULL);
1077 if (request->hr_proto != httpd->consts.http_09)
1078 bozo_printf(httpd, "Location: %s\r\n", finalurl);
1079 bozo_printf(httpd, "\r\n");
1080 if (request->hr_method == HTTP_HEAD)
1081 goto head;
1082 bozo_printf(httpd, "<html><head><title>Document Moved</title></head>\n");
1083 bozo_printf(httpd, "<body><h1>Document Moved</h1>\n");
1084 bozo_printf(httpd, "This document had moved <a href=\"%s\">here</a>\n",
1085 finalurl);
1086 bozo_printf(httpd, "</body></html>\n");
1087 head:
1088 bozo_flush(httpd, stdout);
1089 free(urlbuf);
1090 free(finalurl);
1091 #ifndef NO_USER_SUPPORT
1092 free(userbuf);
1093 #endif /* !NO_USER_SUPPORT */
1094 }
1095
1096 /*
1097 * Like strncmp(), but s_esc may contain characters escaped by \.
1098 * The len argument does not include the backslashes used for escaping,
1099 * that is: it gives the raw len, after unescaping the string.
1100 */
1101 static int
1102 esccmp(const char *s_plain, const char *s_esc, size_t len)
1103 {
1104 bool esc = false;
1105
1106 while (len) {
1107 if (!esc && *s_esc == '\\') {
1108 esc = true;
1109 s_esc++;
1110 continue;
1111 }
1112 esc = false;
1113 if (*s_plain == 0 || *s_esc == 0 || *s_plain != *s_esc)
1114 return *s_esc - *s_plain;
1115 s_esc++;
1116 s_plain++;
1117 len--;
1118 }
1119 return 0;
1120 }
1121
1122 /*
1123 * Check if the request refers to a uri that is mapped via a .bzremap.
1124 * We have /requested/path:/re/mapped/to/this.html lines in there,
1125 * and the : separator may be use in the left hand side escaped with
1126 * \ to encode a path containig a : character.
1127 */
1128 static void
1129 check_mapping(bozo_httpreq_t *request)
1130 {
1131 bozohttpd_t *httpd = request->hr_httpd;
1132 char *file = request->hr_file, *newfile;
1133 void *fmap;
1134 const char *replace, *map_to, *p;
1135 struct stat st;
1136 int mapfile;
1137 size_t avail, len, rlen, reqlen, num_esc = 0;
1138 bool escaped = false;
1139
1140 mapfile = open(REMAP_FILE, O_RDONLY, 0);
1141 if (mapfile == -1)
1142 return;
1143 debug((httpd, DEBUG_FAT, "remap file found"));
1144 if (fstat(mapfile, &st) == -1) {
1145 bozowarn(httpd, "could not stat " REMAP_FILE ", errno: %d",
1146 errno);
1147 close(mapfile);
1148 return;
1149 }
1150
1151 fmap = mmap(NULL, st.st_size, PROT_READ, 0, mapfile, 0);
1152 if (fmap == NULL) {
1153 bozowarn(httpd, "could not mmap " REMAP_FILE ", error %d",
1154 errno);
1155 close(mapfile);
1156 return;
1157 }
1158 reqlen = strlen(file);
1159 for (p = fmap, avail = st.st_size; avail; ) {
1160 /*
1161 * We have lines like:
1162 * /this/url:/replacement/that/url
1163 * If we find a matching left hand side, replace will point
1164 * to it and len will be its length. map_to will point to
1165 * the right hand side and rlen wil be its length.
1166 * If we have no match, both pointers will be NULL.
1167 */
1168
1169 /* skip empty lines */
1170 while ((*p == '\r' || *p == '\n') && avail) {
1171 p++;
1172 avail--;
1173 }
1174 replace = p;
1175 escaped = false;
1176 while (avail) {
1177 if (*p == '\r' || *p == '\n')
1178 break;
1179 if (!escaped && *p == ':')
1180 break;
1181 if (escaped) {
1182 escaped = false;
1183 num_esc++;
1184 } else if (*p == '\\') {
1185 escaped = true;
1186 }
1187 p++;
1188 avail--;
1189 }
1190 if (!avail || *p != ':') {
1191 replace = NULL;
1192 map_to = NULL;
1193 break;
1194 }
1195 len = p - replace - num_esc;
1196 /*
1197 * reqlen < len: the left hand side is too long, can't be a
1198 * match
1199 * reqlen == len: full string has to match
1200 * reqlen > len: make sure there is a path separator at 'len'
1201 * avail < 2: we are at eof, missing right hand side
1202 */
1203 if (avail < 2 || reqlen < len ||
1204 (reqlen == len && esccmp(file, replace, len) != 0) ||
1205 (reqlen > len && (file[len] != '/' ||
1206 esccmp(file, replace, len) != 0))) {
1207
1208 /* non-match, skip to end of line and continue */
1209 while (*p != '\r' && *p != '\n' && avail) {
1210 p++;
1211 avail--;
1212 }
1213 replace = NULL;
1214 map_to = NULL;
1215 continue;
1216 }
1217 p++;
1218 avail--;
1219
1220 /* found a match, parse the target */
1221 map_to = p;
1222 while (*p != '\r' && *p != '\n' && avail) {
1223 p++;
1224 avail--;
1225 }
1226 rlen = p - map_to;
1227 break;
1228 }
1229
1230 if (replace && map_to) {
1231 newfile = bozomalloc(httpd, strlen(file) + rlen - len + 1);
1232 memcpy(newfile, map_to, rlen);
1233 strcpy(newfile+rlen, file + len);
1234 debug((httpd, DEBUG_NORMAL, "remapping found ``%s'' ",
1235 newfile));
1236 free(request->hr_file);
1237 request->hr_file = newfile;
1238 }
1239
1240 munmap(fmap, st.st_size);
1241 close(mapfile);
1242 }
1243
1244 /*
1245 * deal with virtual host names; we do this:
1246 * if we have a virtual path root (httpd->virtbase), and we are given a
1247 * virtual host spec (Host: ho.st or http://ho.st/), see if this
1248 * directory exists under httpd->virtbase. if it does, use this as the
1249 # new slashdir.
1250 */
1251 static int
1252 check_virtual(bozo_httpreq_t *request)
1253 {
1254 bozohttpd_t *httpd = request->hr_httpd;
1255 char *file = request->hr_file, *s;
1256 size_t len;
1257
1258 /*
1259 * convert http://virtual.host/ to request->hr_host
1260 */
1261 debug((httpd, DEBUG_OBESE, "checking for http:// virtual host in ``%s''",
1262 file));
1263 if (strncasecmp(file, "http://", 7) == 0) {
1264 /* we would do virtual hosting here? */
1265 file += 7;
1266 /* RFC 2616 (HTTP/1.1), 5.2: URI takes precedence over Host: */
1267 free(request->hr_host);
1268 request->hr_host = bozostrdup(httpd, request, file);
1269 if ((s = strchr(request->hr_host, '/')) != NULL)
1270 *s = '\0';
1271 s = strchr(file, '/');
1272 free(request->hr_file);
1273 request->hr_file = bozostrdup(httpd, request, s ? s : "/");
1274 debug((httpd, DEBUG_OBESE, "got host ``%s'' file is now ``%s''",
1275 request->hr_host, request->hr_file));
1276 } else if (!request->hr_host)
1277 goto use_slashdir;
1278
1279 /*
1280 * canonicalise hr_host - that is, remove any :80.
1281 */
1282 len = strlen(request->hr_host);
1283 if (len > 3 && strcmp(request->hr_host + len - 3, ":80") == 0) {
1284 request->hr_host[len - 3] = '\0';
1285 len = strlen(request->hr_host);
1286 }
1287
1288 if (!httpd->virtbase) {
1289
1290 /*
1291 * if we don't use vhost support, then set virthostname if
1292 * user supplied Host header. It will be used for possible
1293 * redirections
1294 */
1295
1296 if (request->hr_host) {
1297 s = strrchr(request->hr_host, ':');
1298 if (s != NULL)
1299 /* truncate Host: as we want to copy it without port part */
1300 *s = '\0';
1301 request->hr_virthostname = bozostrdup(httpd, request,
1302 request->hr_host);
1303 if (s != NULL)
1304 /* fix Host: again, if we truncated it */
1305 *s = ':';
1306 }
1307
1308 goto use_slashdir;
1309 }
1310
1311 /*
1312 * ok, we have a virtual host, use opendir(3) to find a case
1313 * insensitive match for the virtual host we are asked for.
1314 * note that if the virtual host is the same as the master,
1315 * we don't need to do anything special.
1316 */
1317 debug((httpd, DEBUG_OBESE,
1318 "check_virtual: checking host `%s' under httpd->virtbase `%s' "
1319 "for file `%s'",
1320 request->hr_host, httpd->virtbase, request->hr_file));
1321 if (strncasecmp(httpd->virthostname, request->hr_host, len) != 0) {
1322 s = 0;
1323 DIR *dirp;
1324 struct dirent *d;
1325
1326 if ((dirp = opendir(httpd->virtbase)) != NULL) {
1327 while ((d = readdir(dirp)) != NULL) {
1328 if (strcmp(d->d_name, ".") == 0 ||
1329 strcmp(d->d_name, "..") == 0) {
1330 continue;
1331 }
1332 debug((httpd, DEBUG_OBESE, "looking at dir``%s''",
1333 d->d_name));
1334 if (strcmp(d->d_name, request->hr_host) == 0) {
1335 /* found it, punch it */
1336 debug((httpd, DEBUG_OBESE, "found it punch it"));
1337 request->hr_virthostname =
1338 bozostrdup(httpd, request, d->d_name);
1339 bozoasprintf(httpd, &s, "%s/%s",
1340 httpd->virtbase,
1341 request->hr_virthostname);
1342 break;
1343 }
1344 }
1345 closedir(dirp);
1346 }
1347 else {
1348 debug((httpd, DEBUG_FAT, "opendir %s failed: %s",
1349 httpd->virtbase, strerror(errno)));
1350 }
1351 if (s == 0) {
1352 if (httpd->unknown_slash)
1353 goto use_slashdir;
1354 return bozo_http_error(httpd, 404, request,
1355 "unknown URL");
1356 }
1357 } else
1358 use_slashdir:
1359 s = httpd->slashdir;
1360
1361 /*
1362 * ok, nailed the correct slashdir, chdir to it
1363 */
1364 if (chdir(s) < 0)
1365 return bozo_http_error(httpd, 404, request,
1366 "can't chdir to slashdir");
1367
1368 /*
1369 * is there a mapping for this request?
1370 */
1371 check_mapping(request);
1372
1373 return 0;
1374 }
1375
1376 /*
1377 * checks to see if this request has a valid .bzredirect file. returns
1378 * 0 when no redirection happend, or 1 when handle_redirect() has been
1379 * called, -1 on error.
1380 */
1381 static int
1382 check_bzredirect(bozo_httpreq_t *request)
1383 {
1384 bozohttpd_t *httpd = request->hr_httpd;
1385 struct stat sb;
1386 char dir[MAXPATHLEN], redir[MAXPATHLEN], redirpath[MAXPATHLEN + 1],
1387 path[MAXPATHLEN];
1388 char *basename, *finalredir;
1389 int rv, absolute;
1390
1391 /*
1392 * if this pathname is really a directory, but doesn't end in /,
1393 * use it as the directory to look for the redir file.
1394 */
1395 if((size_t)snprintf(dir, sizeof(dir), "%s", request->hr_file + 1) >=
1396 sizeof(dir)) {
1397 bozo_http_error(httpd, 404, request,
1398 "file path too long");
1399 return -1;
1400 }
1401 debug((httpd, DEBUG_FAT, "check_bzredirect: dir %s", dir));
1402 basename = strrchr(dir, '/');
1403
1404 if ((!basename || basename[1] != '\0') &&
1405 lstat(dir, &sb) == 0 && S_ISDIR(sb.st_mode)) {
1406 strcpy(path, dir);
1407 } else if (basename == NULL) {
1408 strcpy(path, ".");
1409 strcpy(dir, "");
1410 } else {
1411 *basename++ = '\0';
1412 bozo_check_special_files(request, basename);
1413 strcpy(path, dir);
1414 }
1415
1416 debug((httpd, DEBUG_FAT, "check_bzredirect: path %s", path));
1417
1418 if ((size_t)snprintf(redir, sizeof(redir), "%s/%s", path,
1419 REDIRECT_FILE) >= sizeof(redir)) {
1420 bozo_http_error(httpd, 404, request,
1421 "redirectfile path too long");
1422 return -1;
1423 }
1424 if (lstat(redir, &sb) == 0) {
1425 if (!S_ISLNK(sb.st_mode))
1426 return 0;
1427 absolute = 0;
1428 } else {
1429 if((size_t)snprintf(redir, sizeof(redir), "%s/%s", path,
1430 ABSREDIRECT_FILE) >= sizeof(redir)) {
1431 bozo_http_error(httpd, 404, request,
1432 "redirectfile path too long");
1433 return -1;
1434 }
1435 if (lstat(redir, &sb) < 0 || !S_ISLNK(sb.st_mode))
1436 return 0;
1437 absolute = 1;
1438 }
1439 debug((httpd, DEBUG_FAT, "check_bzredirect: calling readlink"));
1440 rv = readlink(redir, redirpath, sizeof redirpath - 1);
1441 if (rv == -1 || rv == 0) {
1442 debug((httpd, DEBUG_FAT, "readlink failed"));
1443 return 0;
1444 }
1445 redirpath[rv] = '\0';
1446 debug((httpd, DEBUG_FAT, "readlink returned \"%s\"", redirpath));
1447
1448 /* check if we need authentication */
1449 snprintf(path, sizeof(path), "%s/", dir);
1450 if (bozo_auth_check(request, path))
1451 return 1;
1452
1453 /* now we have the link pointer, redirect to the real place */
1454 if (!absolute && redirpath[0] != '/') {
1455 if ((size_t)snprintf(finalredir = redir, sizeof(redir), "%s%s/%s",
1456 (strlen(dir) > 0 ? "/" : ""), dir, redirpath) >= sizeof(redir)) {
1457 bozo_http_error(httpd, 404, request,
1458 "redirect path too long");
1459 return -1;
1460 }
1461 } else
1462 finalredir = redirpath;
1463
1464 debug((httpd, DEBUG_FAT, "check_bzredirect: new redir %s", finalredir));
1465 handle_redirect(request, finalredir, absolute);
1466 return 1;
1467 }
1468
1469 /* this fixes the %HH hack that RFC2396 requires. */
1470 int
1471 bozo_decode_url_percent(bozo_httpreq_t *request, char *str)
1472 {
1473 bozohttpd_t *httpd = request->hr_httpd;
1474 char *s, *t, buf[3];
1475 char *end; /* if end is not-zero, we don't translate beyond that */
1476
1477 end = str + strlen(str);
1478
1479 /* fast forward to the first % */
1480 if ((s = strchr(str, '%')) == NULL)
1481 return 0;
1482
1483 t = s;
1484 do {
1485 if (end && s >= end) {
1486 debug((httpd, DEBUG_EXPLODING,
1487 "fu_%%: past end, filling out.."));
1488 while (*s)
1489 *t++ = *s++;
1490 break;
1491 }
1492 debug((httpd, DEBUG_EXPLODING,
1493 "fu_%%: got s == %%, s[1]s[2] == %c%c",
1494 s[1], s[2]));
1495 if (s[1] == '\0' || s[2] == '\0') {
1496 (void)bozo_http_error(httpd, 400, request,
1497 "percent hack missing two chars afterwards");
1498 return 1;
1499 }
1500 if (s[1] == '0' && s[2] == '0') {
1501 (void)bozo_http_error(httpd, 404, request,
1502 "percent hack was %00");
1503 return 1;
1504 }
1505 if (s[1] == '2' && s[2] == 'f') {
1506 (void)bozo_http_error(httpd, 404, request,
1507 "percent hack was %2f (/)");
1508 return 1;
1509 }
1510
1511 buf[0] = *++s;
1512 buf[1] = *++s;
1513 buf[2] = '\0';
1514 s++;
1515 *t = (char)strtol(buf, NULL, 16);
1516 debug((httpd, DEBUG_EXPLODING,
1517 "fu_%%: strtol put '%02x' into *t", *t));
1518 if (*t++ == '\0') {
1519 (void)bozo_http_error(httpd, 400, request,
1520 "percent hack got a 0 back");
1521 return 1;
1522 }
1523
1524 while (*s && *s != '%') {
1525 if (end && s >= end)
1526 break;
1527 *t++ = *s++;
1528 }
1529 } while (*s);
1530 *t = '\0';
1531
1532 debug((httpd, DEBUG_FAT, "bozo_decode_url_percent returns `%s'",
1533 request->hr_file));
1534
1535 return 0;
1536 }
1537
1538 /*
1539 * transform_request does this:
1540 * - ``expand'' %20 crapola
1541 * - punt if it doesn't start with /
1542 * - look for "http://myname/" and deal with it.
1543 * - maybe call bozo_process_cgi()
1544 * - check for ~user and call bozo_user_transform() if so
1545 * - if the length > 1, check for trailing slash. if so,
1546 * add the index.html file
1547 * - if the length is 1, return the index.html file
1548 * - disallow anything ending up with a file starting
1549 * at "/" or having ".." in it.
1550 * - anything else is a really weird internal error
1551 * - returns malloced file to serve, if unhandled
1552 */
1553 static int
1554 transform_request(bozo_httpreq_t *request, int *isindex)
1555 {
1556 bozohttpd_t *httpd = request->hr_httpd;
1557 char *file, *newfile = NULL;
1558 size_t len;
1559
1560 file = NULL;
1561 *isindex = 0;
1562 debug((httpd, DEBUG_FAT, "tf_req: file %s", request->hr_file));
1563 if (bozo_decode_url_percent(request, request->hr_file)) {
1564 goto bad_done;
1565 }
1566 if (check_virtual(request)) {
1567 goto bad_done;
1568 }
1569 file = request->hr_file;
1570
1571 if (file[0] != '/') {
1572 (void)bozo_http_error(httpd, 404, request, "unknown URL");
1573 goto bad_done;
1574 }
1575
1576 /* omit additional slashes at the beginning */
1577 while (file[1] == '/')
1578 file++;
1579
1580 /* fix file provided by user as it's used in other handlers */
1581 request->hr_file = file;
1582
1583 len = strlen(file);
1584
1585 #ifndef NO_USER_SUPPORT
1586 /* first of all expand user path */
1587 if (len > 1 && httpd->enable_users && file[1] == '~') {
1588 if (file[2] == '\0') {
1589 (void)bozo_http_error(httpd, 404, request,
1590 "missing username");
1591 goto bad_done;
1592 }
1593 if (strchr(file + 2, '/') == NULL) {
1594 char *userredirecturl;
1595 bozoasprintf(httpd, &userredirecturl, "%s/", file);
1596 handle_redirect(request, userredirecturl, 0);
1597 free(userredirecturl);
1598 return 0;
1599 }
1600 debug((httpd, DEBUG_FAT, "calling bozo_user_transform"));
1601
1602 if (!bozo_user_transform(request))
1603 return 0;
1604
1605 file = request->hr_file;
1606 len = strlen(file);
1607 }
1608 #endif /* NO_USER_SUPPORT */
1609
1610
1611 switch (check_bzredirect(request)) {
1612 case -1:
1613 goto bad_done;
1614 case 1:
1615 return 0;
1616 }
1617
1618 if (len > 1) {
1619 debug((httpd, DEBUG_FAT, "file[len-1] == %c", file[len-1]));
1620 if (file[len-1] == '/') { /* append index.html */
1621 *isindex = 1;
1622 debug((httpd, DEBUG_FAT, "appending index.html"));
1623 newfile = bozomalloc(httpd,
1624 len + strlen(httpd->index_html) + 1);
1625 strcpy(newfile, file + 1);
1626 strcat(newfile, httpd->index_html);
1627 } else
1628 newfile = bozostrdup(httpd, request, file + 1);
1629 } else if (len == 1) {
1630 debug((httpd, DEBUG_EXPLODING, "tf_req: len == 1"));
1631 newfile = bozostrdup(httpd, request, httpd->index_html);
1632 *isindex = 1;
1633 } else { /* len == 0 ? */
1634 (void)bozo_http_error(httpd, 500, request,
1635 "request->hr_file is nul?");
1636 goto bad_done;
1637 }
1638
1639 if (newfile == NULL) {
1640 (void)bozo_http_error(httpd, 500, request, "internal failure");
1641 goto bad_done;
1642 }
1643
1644 /*
1645 * stop traversing outside our domain
1646 *
1647 * XXX true security only comes from our parent using chroot(2)
1648 * before execve(2)'ing us. or our own built in chroot(2) support.
1649 */
1650
1651 debug((httpd, DEBUG_FAT, "newfile: %s", newfile));
1652
1653 if (*newfile == '/' || strcmp(newfile, "..") == 0 ||
1654 strstr(newfile, "/..") || strstr(newfile, "../")) {
1655 (void)bozo_http_error(httpd, 403, request, "illegal request");
1656 goto bad_done;
1657 }
1658
1659 if (bozo_auth_check(request, newfile))
1660 goto bad_done;
1661
1662 if (strlen(newfile)) {
1663 request->hr_oldfile = request->hr_file;
1664 request->hr_file = newfile;
1665 }
1666
1667 if (bozo_process_cgi(request))
1668 return 0;
1669
1670 if (bozo_process_lua(request))
1671 return 0;
1672
1673 debug((httpd, DEBUG_FAT, "transform_request set: %s", newfile));
1674 return 1;
1675 bad_done:
1676 debug((httpd, DEBUG_FAT, "transform_request returning: 0"));
1677 free(newfile);
1678 return 0;
1679 }
1680
1681 /*
1682 * can_gzip checks if the request supports and prefers gzip encoding.
1683 *
1684 * XXX: we do not consider the associated q with gzip in making our
1685 * decision which is broken.
1686 */
1687
1688 static int
1689 can_gzip(bozo_httpreq_t *request)
1690 {
1691 const char *pos;
1692 const char *tmp;
1693 size_t len;
1694
1695 /* First we decide if the request can be gzipped at all. */
1696
1697 /* not if we already are encoded... */
1698 tmp = bozo_content_encoding(request, request->hr_file);
1699 if (tmp && *tmp)
1700 return 0;
1701
1702 /* not if we are not asking for the whole file... */
1703 if (request->hr_last_byte_pos != -1 || request->hr_have_range)
1704 return 0;
1705
1706 /* Then we determine if gzip is on the cards. */
1707
1708 for (pos = request->hr_accept_encoding; pos && *pos; pos += len) {
1709 while (*pos == ' ')
1710 pos++;
1711
1712 len = strcspn(pos, ";,");
1713
1714 if ((len == 4 && strncasecmp("gzip", pos, 4) == 0) ||
1715 (len == 6 && strncasecmp("x-gzip", pos, 6) == 0))
1716 return 1;
1717
1718 if (pos[len] == ';')
1719 len += strcspn(&pos[len], ",");
1720
1721 if (pos[len])
1722 len++;
1723 }
1724
1725 return 0;
1726 }
1727
1728 /*
1729 * bozo_process_request does the following:
1730 * - check the request is valid
1731 * - process cgi-bin if necessary
1732 * - transform a filename if necesarry
1733 * - return the HTTP request
1734 */
1735 void
1736 bozo_process_request(bozo_httpreq_t *request)
1737 {
1738 bozohttpd_t *httpd = request->hr_httpd;
1739 struct stat sb;
1740 time_t timestamp;
1741 char *file;
1742 const char *type, *encoding;
1743 int fd, isindex;
1744
1745 /*
1746 * note that transform_request chdir()'s if required. also note
1747 * that cgi is handed here. if transform_request() returns 0
1748 * then the request has been handled already.
1749 */
1750 if (transform_request(request, &isindex) == 0)
1751 return;
1752
1753 fd = -1;
1754 encoding = NULL;
1755 if (can_gzip(request)) {
1756 bozoasprintf(httpd, &file, "%s.gz", request->hr_file);
1757 fd = open(file, O_RDONLY);
1758 if (fd >= 0)
1759 encoding = "gzip";
1760 free(file);
1761 }
1762
1763 file = request->hr_file;
1764
1765 if (fd < 0)
1766 fd = open(file, O_RDONLY);
1767
1768 if (fd < 0) {
1769 debug((httpd, DEBUG_FAT, "open failed: %s", strerror(errno)));
1770 switch (errno) {
1771 case EPERM:
1772 case EACCES:
1773 (void)bozo_http_error(httpd, 403, request,
1774 "no permission to open file");
1775 break;
1776 case ENAMETOOLONG:
1777 /*FALLTHROUGH*/
1778 case ENOENT:
1779 if (!bozo_dir_index(request, file, isindex))
1780 (void)bozo_http_error(httpd, 404, request,
1781 "no file");
1782 break;
1783 default:
1784 (void)bozo_http_error(httpd, 500, request, "open file");
1785 }
1786 goto cleanup_nofd;
1787 }
1788 if (fstat(fd, &sb) < 0) {
1789 (void)bozo_http_error(httpd, 500, request, "can't fstat");
1790 goto cleanup;
1791 }
1792 if (S_ISDIR(sb.st_mode)) {
1793 handle_redirect(request, NULL, 0);
1794 goto cleanup;
1795 }
1796
1797 if (request->hr_if_modified_since &&
1798 parse_http_date(request->hr_if_modified_since, ×tamp) &&
1799 timestamp >= sb.st_mtime) {
1800 /* XXX ignore subsecond of timestamp */
1801 bozo_printf(httpd, "%s 304 Not Modified\r\n",
1802 request->hr_proto);
1803 bozo_printf(httpd, "\r\n");
1804 bozo_flush(httpd, stdout);
1805 goto cleanup;
1806 }
1807
1808 /* validate requested range */
1809 if (request->hr_last_byte_pos == -1 ||
1810 request->hr_last_byte_pos >= sb.st_size)
1811 request->hr_last_byte_pos = sb.st_size - 1;
1812 if (request->hr_have_range &&
1813 request->hr_first_byte_pos > request->hr_last_byte_pos) {
1814 request->hr_have_range = 0; /* punt */
1815 request->hr_first_byte_pos = 0;
1816 request->hr_last_byte_pos = sb.st_size - 1;
1817 }
1818 debug((httpd, DEBUG_FAT, "have_range %d first_pos %lld last_pos %lld",
1819 request->hr_have_range,
1820 (long long)request->hr_first_byte_pos,
1821 (long long)request->hr_last_byte_pos));
1822 if (request->hr_have_range)
1823 bozo_printf(httpd, "%s 206 Partial Content\r\n",
1824 request->hr_proto);
1825 else
1826 bozo_printf(httpd, "%s 200 OK\r\n", request->hr_proto);
1827
1828 if (request->hr_proto != httpd->consts.http_09) {
1829 type = bozo_content_type(request, file);
1830 if (!encoding)
1831 encoding = bozo_content_encoding(request, file);
1832
1833 bozo_print_header(request, &sb, type, encoding);
1834 bozo_printf(httpd, "\r\n");
1835 }
1836 bozo_flush(httpd, stdout);
1837
1838 if (request->hr_method != HTTP_HEAD) {
1839 off_t szleft, cur_byte_pos;
1840
1841 szleft =
1842 request->hr_last_byte_pos - request->hr_first_byte_pos + 1;
1843 cur_byte_pos = request->hr_first_byte_pos;
1844
1845 retry:
1846 while (szleft) {
1847 size_t sz;
1848
1849 if ((off_t)httpd->mmapsz < szleft)
1850 sz = httpd->mmapsz;
1851 else
1852 sz = (size_t)szleft;
1853 if (mmap_and_write_part(httpd, fd, cur_byte_pos, sz)) {
1854 if (errno == ENOMEM) {
1855 httpd->mmapsz /= 2;
1856 if (httpd->mmapsz >= httpd->page_size)
1857 goto retry;
1858 }
1859 goto cleanup;
1860 }
1861 cur_byte_pos += sz;
1862 szleft -= sz;
1863 }
1864 }
1865 cleanup:
1866 close(fd);
1867 cleanup_nofd:
1868 close(STDIN_FILENO);
1869 close(STDOUT_FILENO);
1870 /*close(STDERR_FILENO);*/
1871 }
1872
1873 /* make sure we're not trying to access special files */
1874 int
1875 bozo_check_special_files(bozo_httpreq_t *request, const char *name)
1876 {
1877 bozohttpd_t *httpd = request->hr_httpd;
1878
1879 /* ensure basename(name) != special files */
1880 if (strcmp(name, DIRECT_ACCESS_FILE) == 0)
1881 return bozo_http_error(httpd, 403, request,
1882 "no permission to open direct access file");
1883 if (strcmp(name, REDIRECT_FILE) == 0)
1884 return bozo_http_error(httpd, 403, request,
1885 "no permission to open redirect file");
1886 if (strcmp(name, ABSREDIRECT_FILE) == 0)
1887 return bozo_http_error(httpd, 403, request,
1888 "no permission to open redirect file");
1889 if (strcmp(name, REMAP_FILE) == 0)
1890 return bozo_http_error(httpd, 403, request,
1891 "no permission to open redirect file");
1892 return bozo_auth_check_special_files(request, name);
1893 }
1894
1895 /* generic header printing routine */
1896 void
1897 bozo_print_header(bozo_httpreq_t *request,
1898 struct stat *sbp, const char *type, const char *encoding)
1899 {
1900 bozohttpd_t *httpd = request->hr_httpd;
1901 off_t len;
1902 char date[40];
1903 bozoheaders_t *hdr;
1904
1905 SIMPLEQ_FOREACH(hdr, &request->hr_replheaders, h_next) {
1906 bozo_printf(httpd, "%s: %s\r\n", hdr->h_header,
1907 hdr->h_value);
1908 }
1909
1910 bozo_printf(httpd, "Date: %s\r\n", bozo_http_date(date, sizeof(date)));
1911 bozo_printf(httpd, "Server: %s\r\n", httpd->server_software);
1912 bozo_printf(httpd, "Accept-Ranges: bytes\r\n");
1913 if (sbp) {
1914 char filedate[40];
1915 struct tm *tm;
1916
1917 tm = gmtime(&sbp->st_mtime);
1918 strftime(filedate, sizeof filedate,
1919 "%a, %d %b %Y %H:%M:%S GMT", tm);
1920 bozo_printf(httpd, "Last-Modified: %s\r\n", filedate);
1921 }
1922 if (type && *type)
1923 bozo_printf(httpd, "Content-Type: %s\r\n", type);
1924 if (encoding && *encoding)
1925 bozo_printf(httpd, "Content-Encoding: %s\r\n", encoding);
1926 if (sbp) {
1927 if (request->hr_have_range) {
1928 len = request->hr_last_byte_pos -
1929 request->hr_first_byte_pos +1;
1930 bozo_printf(httpd,
1931 "Content-Range: bytes %qd-%qd/%qd\r\n",
1932 (long long) request->hr_first_byte_pos,
1933 (long long) request->hr_last_byte_pos,
1934 (long long) sbp->st_size);
1935 } else
1936 len = sbp->st_size;
1937 bozo_printf(httpd, "Content-Length: %qd\r\n", (long long)len);
1938 }
1939 if (request->hr_proto == httpd->consts.http_11)
1940 bozo_printf(httpd, "Connection: close\r\n");
1941 bozo_flush(httpd, stdout);
1942 }
1943
1944 #ifndef NO_DEBUG
1945 void
1946 debug__(bozohttpd_t *httpd, int level, const char *fmt, ...)
1947 {
1948 va_list ap;
1949 int savederrno;
1950
1951 /* only log if the level is low enough */
1952 if (httpd->debug < level)
1953 return;
1954
1955 savederrno = errno;
1956 va_start(ap, fmt);
1957 if (httpd->logstderr) {
1958 vfprintf(stderr, fmt, ap);
1959 fputs("\n", stderr);
1960 } else
1961 vsyslog(LOG_DEBUG, fmt, ap);
1962 va_end(ap);
1963 errno = savederrno;
1964 }
1965 #endif /* NO_DEBUG */
1966
1967 /* these are like warn() and err(), except for syslog not stderr */
1968 void
1969 bozowarn(bozohttpd_t *httpd, const char *fmt, ...)
1970 {
1971 va_list ap;
1972
1973 va_start(ap, fmt);
1974 if (httpd->logstderr || isatty(STDERR_FILENO)) {
1975 //fputs("warning: ", stderr);
1976 vfprintf(stderr, fmt, ap);
1977 fputs("\n", stderr);
1978 } else
1979 vsyslog(LOG_INFO, fmt, ap);
1980 va_end(ap);
1981 }
1982
1983 void
1984 bozoerr(bozohttpd_t *httpd, int code, const char *fmt, ...)
1985 {
1986 va_list ap;
1987
1988 va_start(ap, fmt);
1989 if (httpd->logstderr || isatty(STDERR_FILENO)) {
1990 //fputs("error: ", stderr);
1991 vfprintf(stderr, fmt, ap);
1992 fputs("\n", stderr);
1993 } else
1994 vsyslog(LOG_ERR, fmt, ap);
1995 va_end(ap);
1996 exit(code);
1997 }
1998
1999 void
2000 bozoasprintf(bozohttpd_t *httpd, char **str, const char *fmt, ...)
2001 {
2002 va_list ap;
2003 int e;
2004
2005 va_start(ap, fmt);
2006 e = vasprintf(str, fmt, ap);
2007 va_end(ap);
2008
2009 if (e < 0)
2010 bozoerr(httpd, EXIT_FAILURE, "asprintf");
2011 }
2012
2013 /*
2014 * this escapes HTML tags. returns allocated escaped
2015 * string if needed, or NULL on allocation failure or
2016 * lack of escape need.
2017 * call with NULL httpd in error paths, to avoid recursive
2018 * malloc failure. call with valid httpd in normal paths
2019 * to get automatic allocation failure handling.
2020 */
2021 char *
2022 bozo_escape_html(bozohttpd_t *httpd, const char *url)
2023 {
2024 int i, j;
2025 char *tmp;
2026 size_t len;
2027
2028 for (i = 0, j = 0; url[i]; i++) {
2029 switch (url[i]) {
2030 case '<':
2031 case '>':
2032 j += 4;
2033 break;
2034 case '&':
2035 j += 5;
2036 break;
2037 }
2038 }
2039
2040 if (j == 0)
2041 return NULL;
2042
2043 /*
2044 * we need to handle being called from different
2045 * pathnames.
2046 */
2047 len = strlen(url) + j;
2048 if (httpd)
2049 tmp = bozomalloc(httpd, len);
2050 else if ((tmp = malloc(len)) == 0)
2051 return NULL;
2052
2053 for (i = 0, j = 0; url[i]; i++) {
2054 switch (url[i]) {
2055 case '<':
2056 memcpy(tmp + j, "<", 4);
2057 j += 4;
2058 break;
2059 case '>':
2060 memcpy(tmp + j, ">", 4);
2061 j += 4;
2062 break;
2063 case '&':
2064 memcpy(tmp + j, "&", 5);
2065 j += 5;
2066 break;
2067 default:
2068 tmp[j++] = url[i];
2069 }
2070 }
2071 tmp[j] = 0;
2072
2073 return tmp;
2074 }
2075
2076 /* short map between error code, and short/long messages */
2077 static struct errors_map {
2078 int code; /* HTTP return code */
2079 const char *shortmsg; /* short version of message */
2080 const char *longmsg; /* long version of message */
2081 } errors_map[] = {
2082 { 400, "400 Bad Request", "The request was not valid", },
2083 { 401, "401 Unauthorized", "No authorization", },
2084 { 403, "403 Forbidden", "Access to this item has been denied",},
2085 { 404, "404 Not Found", "This item has not been found", },
2086 { 408, "408 Request Timeout", "This request took too long", },
2087 { 413, "413 Payload Too Large", "Use smaller requests", },
2088 { 417, "417 Expectation Failed","Expectations not available", },
2089 { 420, "420 Enhance Your Calm","Chill, Winston", },
2090 { 500, "500 Internal Error", "An error occured on the server", },
2091 { 501, "501 Not Implemented", "This request is not available", },
2092 { 0, NULL, NULL, },
2093 };
2094
2095 static const char *help = "DANGER! WILL ROBINSON! DANGER!";
2096
2097 static const char *
2098 http_errors_short(int code)
2099 {
2100 struct errors_map *ep;
2101
2102 for (ep = errors_map; ep->code; ep++)
2103 if (ep->code == code)
2104 return (ep->shortmsg);
2105 return (help);
2106 }
2107
2108 static const char *
2109 http_errors_long(int code)
2110 {
2111 struct errors_map *ep;
2112
2113 for (ep = errors_map; ep->code; ep++)
2114 if (ep->code == code)
2115 return (ep->longmsg);
2116 return (help);
2117 }
2118
2119 /* the follow functions and variables are used in handling HTTP errors */
2120 /* ARGSUSED */
2121 int
2122 bozo_http_error(bozohttpd_t *httpd, int code, bozo_httpreq_t *request,
2123 const char *msg)
2124 {
2125 char portbuf[20];
2126 const char *header = http_errors_short(code);
2127 const char *reason = http_errors_long(code);
2128 const char *proto = (request && request->hr_proto) ?
2129 request->hr_proto : httpd->consts.http_11;
2130 int size;
2131 bozoheaders_t *hdr;
2132
2133 debug((httpd, DEBUG_FAT, "bozo_http_error %d: %s", code, msg));
2134 if (header == NULL || reason == NULL) {
2135 bozoerr(httpd, 1,
2136 "bozo_http_error() failed (short = %p, long = %p)",
2137 header, reason);
2138 return code;
2139 }
2140
2141 if (request && request->hr_serverport &&
2142 strcmp(request->hr_serverport, "80") != 0)
2143 snprintf(portbuf, sizeof(portbuf), ":%s",
2144 request->hr_serverport);
2145 else
2146 portbuf[0] = '\0';
2147
2148 if (request && request->hr_file) {
2149 char *file = NULL, *user = NULL, *user_escaped = NULL;
2150 int file_alloc = 0;
2151 const char *hostname = BOZOHOST(httpd, request);
2152
2153 /* bozo_escape_html() failure here is just too bad. */
2154 file = bozo_escape_html(NULL, request->hr_file);
2155 if (file == NULL)
2156 file = request->hr_file;
2157 else
2158 file_alloc = 1;
2159
2160 #ifndef NO_USER_SUPPORT
2161 if (request->hr_user != NULL) {
2162 user_escaped = bozo_escape_html(NULL, request->hr_user);
2163 if (user_escaped == NULL)
2164 user_escaped = request->hr_user;
2165 /* expand username to ~user/ */
2166 bozoasprintf(httpd, &user, "~%s/", user_escaped);
2167 if (user_escaped != request->hr_user)
2168 free(user_escaped);
2169 }
2170 #endif /* !NO_USER_SUPPORT */
2171
2172 size = snprintf(httpd->errorbuf, BUFSIZ,
2173 "<html><head><title>%s</title></head>\n"
2174 "<body><h1>%s</h1>\n"
2175 "%s%s: <pre>%s</pre>\n"
2176 "<hr><address><a href=\"//%s%s/\">%s%s</a></address>\n"
2177 "</body></html>\n",
2178 header, header,
2179 user ? user : "", file,
2180 reason, hostname, portbuf, hostname, portbuf);
2181 free(user);
2182 if (size >= (int)BUFSIZ) {
2183 bozowarn(httpd,
2184 "bozo_http_error buffer too small, truncated");
2185 size = (int)BUFSIZ;
2186 }
2187
2188 if (file_alloc)
2189 free(file);
2190 } else
2191 size = 0;
2192
2193 bozo_printf(httpd, "%s %s\r\n", proto, header);
2194
2195 if (request) {
2196 bozo_auth_check_401(request, code);
2197 SIMPLEQ_FOREACH(hdr, &request->hr_replheaders, h_next) {
2198 bozo_printf(httpd, "%s: %s\r\n", hdr->h_header,
2199 hdr->h_value);
2200 }
2201 }
2202
2203 bozo_printf(httpd, "Content-Type: text/html\r\n");
2204 bozo_printf(httpd, "Content-Length: %d\r\n", size);
2205 bozo_printf(httpd, "Server: %s\r\n", httpd->server_software);
2206 if (request && request->hr_allow)
2207 bozo_printf(httpd, "Allow: %s\r\n", request->hr_allow);
2208 bozo_printf(httpd, "\r\n");
2209 /* According to the RFC 2616 sec. 9.4 HEAD method MUST NOT return a
2210 * message-body in the response */
2211 if (size && request && request->hr_method != HTTP_HEAD)
2212 bozo_printf(httpd, "%s", httpd->errorbuf);
2213 bozo_flush(httpd, stdout);
2214
2215 return code;
2216 }
2217
2218 /* Below are various modified libc functions */
2219
2220 /*
2221 * returns -1 in lenp if the string ran out before finding a delimiter,
2222 * but is otherwise the same as strsep. Note that the length must be
2223 * correctly passed in.
2224 */
2225 char *
2226 bozostrnsep(char **strp, const char *delim, ssize_t *lenp)
2227 {
2228 char *s;
2229 const char *spanp;
2230 int c, sc;
2231 char *tok;
2232
2233 if ((s = *strp) == NULL)
2234 return (NULL);
2235 for (tok = s;;) {
2236 if (lenp && --(*lenp) == -1)
2237 return (NULL);
2238 c = *s++;
2239 spanp = delim;
2240 do {
2241 if ((sc = *spanp++) == c) {
2242 if (c == 0)
2243 s = NULL;
2244 else
2245 s[-1] = '\0';
2246 *strp = s;
2247 return (tok);
2248 }
2249 } while (sc != 0);
2250 }
2251 /* NOTREACHED */
2252 }
2253
2254 /*
2255 * inspired by fgetln(3), but works for fd's. should work identically
2256 * except it, however, does *not* return the newline, and it does nul
2257 * terminate the string.
2258 */
2259 char *
2260 bozodgetln(bozohttpd_t *httpd, int fd, ssize_t *lenp,
2261 ssize_t (*readfn)(bozohttpd_t *, int, void *, size_t))
2262 {
2263 ssize_t len;
2264 int got_cr = 0;
2265 char c, *nbuffer;
2266
2267 /* initialise */
2268 if (httpd->getln_buflen == 0) {
2269 /* should be plenty for most requests */
2270 httpd->getln_buflen = 128;
2271 httpd->getln_buffer = malloc((size_t)httpd->getln_buflen);
2272 if (httpd->getln_buffer == NULL) {
2273 httpd->getln_buflen = 0;
2274 return NULL;
2275 }
2276 }
2277 len = 0;
2278
2279 /*
2280 * we *have* to read one byte at a time, to not break cgi
2281 * programs (for we pass stdin off to them). could fix this
2282 * by becoming a fd-passing program instead of just exec'ing
2283 * the program
2284 *
2285 * the above is no longer true, we are the fd-passing
2286 * program already.
2287 */
2288 for (; readfn(httpd, fd, &c, 1) == 1; ) {
2289 debug((httpd, DEBUG_EXPLODING, "bozodgetln read %c", c));
2290
2291 if (len >= httpd->getln_buflen - 1) {
2292 httpd->getln_buflen *= 2;
2293 debug((httpd, DEBUG_EXPLODING, "bozodgetln: "
2294 "reallocating buffer to buflen %zu",
2295 httpd->getln_buflen));
2296 nbuffer = bozorealloc(httpd, httpd->getln_buffer,
2297 (size_t)httpd->getln_buflen);
2298 httpd->getln_buffer = nbuffer;
2299 }
2300
2301 httpd->getln_buffer[len++] = c;
2302 if (c == '\r') {
2303 got_cr = 1;
2304 continue;
2305 } else if (c == '\n') {
2306 /*
2307 * HTTP/1.1 spec says to ignore CR and treat
2308 * LF as the real line terminator. even though
2309 * the same spec defines CRLF as the line
2310 * terminator, it is recommended in section 19.3
2311 * to do the LF trick for tolerance.
2312 */
2313 if (got_cr)
2314 len -= 2;
2315 else
2316 len -= 1;
2317 break;
2318 }
2319
2320 }
2321 httpd->getln_buffer[len] = '\0';
2322 debug((httpd, DEBUG_OBESE, "bozodgetln returns: ``%s'' with len %zd",
2323 httpd->getln_buffer, len));
2324 *lenp = len;
2325 return httpd->getln_buffer;
2326 }
2327
2328 void *
2329 bozorealloc(bozohttpd_t *httpd, void *ptr, size_t size)
2330 {
2331 void *p;
2332
2333 p = realloc(ptr, size);
2334 if (p)
2335 return p;
2336
2337 (void)bozo_http_error(httpd, 500, NULL, "memory allocation failure");
2338 exit(EXIT_FAILURE);
2339 }
2340
2341 void *
2342 bozomalloc(bozohttpd_t *httpd, size_t size)
2343 {
2344 void *p;
2345
2346 p = malloc(size);
2347 if (p)
2348 return p;
2349
2350 (void)bozo_http_error(httpd, 500, NULL, "memory allocation failure");
2351 exit(EXIT_FAILURE);
2352 }
2353
2354 char *
2355 bozostrdup(bozohttpd_t *httpd, bozo_httpreq_t *request, const char *str)
2356 {
2357 char *p;
2358
2359 p = strdup(str);
2360 if (p)
2361 return p;
2362
2363 if (!request)
2364 bozoerr(httpd, EXIT_FAILURE, "strdup");
2365
2366 (void)bozo_http_error(httpd, 500, request, "memory allocation failure");
2367 exit(EXIT_FAILURE);
2368 }
2369
2370 /* set default values in bozohttpd_t struct */
2371 int
2372 bozo_init_httpd(bozohttpd_t *httpd)
2373 {
2374 /* make sure everything is clean */
2375 (void) memset(httpd, 0x0, sizeof(*httpd));
2376
2377 /* constants */
2378 httpd->consts.http_09 = "HTTP/0.9";
2379 httpd->consts.http_10 = "HTTP/1.0";
2380 httpd->consts.http_11 = "HTTP/1.1";
2381 httpd->consts.text_plain = "text/plain";
2382
2383 /* mmap region size */
2384 httpd->mmapsz = BOZO_MMAPSZ;
2385
2386 /* error buffer for bozo_http_error() */
2387 if ((httpd->errorbuf = malloc(BUFSIZ)) == NULL) {
2388 (void) fprintf(stderr,
2389 "bozohttpd: memory_allocation failure\n");
2390 return 0;
2391 }
2392 #ifndef NO_LUA_SUPPORT
2393 SIMPLEQ_INIT(&httpd->lua_states);
2394 #endif
2395 return 1;
2396 }
2397
2398 /* set default values in bozoprefs_t struct */
2399 int
2400 bozo_init_prefs(bozohttpd_t *httpd, bozoprefs_t *prefs)
2401 {
2402 /* make sure everything is clean */
2403 (void) memset(prefs, 0x0, sizeof(*prefs));
2404
2405 /* set up default values */
2406 if (!bozo_set_pref(httpd, prefs, "server software", SERVER_SOFTWARE) ||
2407 !bozo_set_pref(httpd, prefs, "index.html", INDEX_HTML) ||
2408 !bozo_set_pref(httpd, prefs, "public_html", PUBLIC_HTML))
2409 return 0;
2410
2411 return 1;
2412 }
2413
2414 /* set default values */
2415 int
2416 bozo_set_defaults(bozohttpd_t *httpd, bozoprefs_t *prefs)
2417 {
2418 return bozo_init_httpd(httpd) && bozo_init_prefs(httpd, prefs);
2419 }
2420
2421 /* set the virtual host name, port and root */
2422 int
2423 bozo_setup(bozohttpd_t *httpd, bozoprefs_t *prefs, const char *vhost,
2424 const char *root)
2425 {
2426 struct passwd *pw;
2427 extern char **environ;
2428 static char *cleanenv[1] = { NULL };
2429 uid_t uid;
2430 int uidset = 0;
2431 char *chrootdir;
2432 char *username;
2433 char *portnum;
2434 char *cp;
2435 int dirtyenv;
2436
2437 dirtyenv = 0;
2438
2439 if (vhost == NULL) {
2440 httpd->virthostname = bozomalloc(httpd, MAXHOSTNAMELEN+1);
2441 if (gethostname(httpd->virthostname, MAXHOSTNAMELEN+1) < 0)
2442 bozoerr(httpd, 1, "gethostname");
2443 httpd->virthostname[MAXHOSTNAMELEN] = '\0';
2444 } else {
2445 httpd->virthostname = bozostrdup(httpd, NULL, vhost);
2446 }
2447 httpd->slashdir = bozostrdup(httpd, NULL, root);
2448 if ((portnum = bozo_get_pref(prefs, "port number")) != NULL) {
2449 httpd->bindport = bozostrdup(httpd, NULL, portnum);
2450 }
2451
2452 /* go over preferences now */
2453 if ((cp = bozo_get_pref(prefs, "numeric")) != NULL &&
2454 strcmp(cp, "true") == 0) {
2455 httpd->numeric = 1;
2456 }
2457 if ((cp = bozo_get_pref(prefs, "log to stderr")) != NULL &&
2458 strcmp(cp, "true") == 0) {
2459 httpd->logstderr = 1;
2460 }
2461 if ((cp = bozo_get_pref(prefs, "bind address")) != NULL) {
2462 httpd->bindaddress = bozostrdup(httpd, NULL, cp);
2463 }
2464 if ((cp = bozo_get_pref(prefs, "background")) != NULL) {
2465 httpd->background = atoi(cp);
2466 }
2467 if ((cp = bozo_get_pref(prefs, "foreground")) != NULL &&
2468 strcmp(cp, "true") == 0) {
2469 httpd->foreground = 1;
2470 }
2471 if ((cp = bozo_get_pref(prefs, "pid file")) != NULL) {
2472 httpd->pidfile = bozostrdup(httpd, NULL, cp);
2473 }
2474 if ((cp = bozo_get_pref(prefs, "unknown slash")) != NULL &&
2475 strcmp(cp, "true") == 0) {
2476 httpd->unknown_slash = 1;
2477 }
2478 if ((cp = bozo_get_pref(prefs, "virtual base")) != NULL) {
2479 httpd->virtbase = bozostrdup(httpd, NULL, cp);
2480 }
2481 if ((cp = bozo_get_pref(prefs, "enable users")) != NULL &&
2482 strcmp(cp, "true") == 0) {
2483 httpd->enable_users = 1;
2484 }
2485 if ((cp = bozo_get_pref(prefs, "enable user cgibin")) != NULL &&
2486 strcmp(cp, "true") == 0) {
2487 httpd->enable_cgi_users = 1;
2488 }
2489 if ((cp = bozo_get_pref(prefs, "dirty environment")) != NULL &&
2490 strcmp(cp, "true") == 0) {
2491 dirtyenv = 1;
2492 }
2493 if ((cp = bozo_get_pref(prefs, "hide dots")) != NULL &&
2494 strcmp(cp, "true") == 0) {
2495 httpd->hide_dots = 1;
2496 }
2497 if ((cp = bozo_get_pref(prefs, "directory indexing")) != NULL &&
2498 strcmp(cp, "true") == 0) {
2499 httpd->dir_indexing = 1;
2500 }
2501 if ((cp = bozo_get_pref(prefs, "public_html")) != NULL) {
2502 httpd->public_html = bozostrdup(httpd, NULL, cp);
2503 }
2504 httpd->server_software =
2505 bozostrdup(httpd, NULL, bozo_get_pref(prefs, "server software"));
2506 httpd->index_html =
2507 bozostrdup(httpd, NULL, bozo_get_pref(prefs, "index.html"));
2508
2509 /*
2510 * initialise ssl and daemon mode if necessary.
2511 */
2512 bozo_ssl_init(httpd);
2513 bozo_daemon_init(httpd);
2514
2515 username = bozo_get_pref(prefs, "username");
2516 if (username != NULL) {
2517 if ((pw = getpwnam(username)) == NULL)
2518 bozoerr(httpd, 1, "getpwnam(%s): %s", username,
2519 strerror(errno));
2520 if (initgroups(pw->pw_name, pw->pw_gid) == -1)
2521 bozoerr(httpd, 1, "initgroups: %s", strerror(errno));
2522 if (setgid(pw->pw_gid) == -1)
2523 bozoerr(httpd, 1, "setgid(%u): %s", pw->pw_gid,
2524 strerror(errno));
2525 uid = pw->pw_uid;
2526 uidset = 1;
2527 }
2528 /*
2529 * handle chroot.
2530 */
2531 if ((chrootdir = bozo_get_pref(prefs, "chroot dir")) != NULL) {
2532 httpd->rootdir = bozostrdup(httpd, NULL, chrootdir);
2533 if (chdir(httpd->rootdir) == -1)
2534 bozoerr(httpd, 1, "chdir(%s): %s", httpd->rootdir,
2535 strerror(errno));
2536 if (chroot(httpd->rootdir) == -1)
2537 bozoerr(httpd, 1, "chroot(%s): %s", httpd->rootdir,
2538 strerror(errno));
2539 }
2540
2541 if (uidset && setuid(uid) == -1)
2542 bozoerr(httpd, 1, "setuid(%d): %s", uid, strerror(errno));
2543
2544 /*
2545 * prevent info leakage between different compartments.
2546 * some PATH values in the environment would be invalided
2547 * by chroot. cross-user settings might result in undesirable
2548 * effects.
2549 */
2550 if ((chrootdir != NULL || username != NULL) && !dirtyenv)
2551 environ = cleanenv;
2552
2553 #ifdef _SC_PAGESIZE
2554 httpd->page_size = (long)sysconf(_SC_PAGESIZE);
2555 #else
2556 httpd->page_size = 4096;
2557 #endif
2558 debug((httpd, DEBUG_OBESE, "myname is %s, slashdir is %s",
2559 httpd->virthostname, httpd->slashdir));
2560
2561 return 1;
2562 }
2563
2564 int
2565 bozo_get_version(char *buf, size_t size)
2566 {
2567 return snprintf(buf, size, "%s", SERVER_SOFTWARE);
2568 }
2569