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