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