bozohttpd.c revision 1.93 1 /* $NetBSD: bozohttpd.c,v 1.93 2018/11/22 08:54:08 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/20181122"
113 #endif
114
115 #ifndef PUBLIC_HTML
116 #define PUBLIC_HTML "public_html"
117 #endif
118
119 #ifndef USE_ARG
120 #define USE_ARG(x) /*LINTED*/(void)&(x)
121 #endif
122
123 /*
124 * And so it begins ..
125 */
126
127 #include <sys/param.h>
128 #include <sys/socket.h>
129 #include <sys/time.h>
130 #include <sys/mman.h>
131
132 #include <arpa/inet.h>
133
134 #include <ctype.h>
135 #include <dirent.h>
136 #include <errno.h>
137 #include <fcntl.h>
138 #include <netdb.h>
139 #include <pwd.h>
140 #include <grp.h>
141 #include <signal.h>
142 #include <stdarg.h>
143 #include <stdlib.h>
144 #include <stdbool.h>
145 #include <string.h>
146 #include <syslog.h>
147 #include <time.h>
148 #include <unistd.h>
149
150 #include "bozohttpd.h"
151
152 #ifndef INITIAL_TIMEOUT
153 #define INITIAL_TIMEOUT "30" /* wait for 30 seconds initially */
154 #endif
155 #ifndef HEADER_WAIT_TIME
156 #define HEADER_WAIT_TIME "10" /* need more headers every 10 seconds */
157 #endif
158 #ifndef TOTAL_MAX_REQ_TIME
159 #define TOTAL_MAX_REQ_TIME "600" /* must have total request in 600 */
160 #endif /* seconds */
161
162 /* if monotonic time is not available try real time. */
163 #ifndef CLOCK_MONOTONIC
164 #define CLOCK_MONOTONIC CLOCK_REALTIME
165 #endif
166
167 /* variables and functions */
168 #ifndef LOG_FTP
169 #define LOG_FTP LOG_DAEMON
170 #endif
171
172 /*
173 * List of special file that we should never serve.
174 */
175 struct {
176 const char *file;
177 const char *name;
178 } specials[] = {
179 { DIRECT_ACCESS_FILE, "rejected direct access request" },
180 { REDIRECT_FILE, "rejected redirect request" },
181 { ABSREDIRECT_FILE, "rejected absredirect request" },
182 { REMAP_FILE, "rejected remap request" },
183 { AUTH_FILE, "rejected authfile request" },
184 { NULL, NULL },
185 };
186
187 volatile sig_atomic_t timeout_hit;
188
189 /*
190 * check there's enough space in the prefs and names arrays.
191 */
192 static int
193 size_arrays(bozoprefs_t *bozoprefs, size_t needed)
194 {
195 char **temp;
196
197 if (bozoprefs->size == 0) {
198 /* only get here first time around */
199 bozoprefs->name = calloc(sizeof(char *), needed);
200 if (bozoprefs->name == NULL)
201 return 0;
202 bozoprefs->value = calloc(sizeof(char *), needed);
203 if (bozoprefs->value == NULL) {
204 free(bozoprefs->name);
205 return 0;
206 }
207 bozoprefs->size = needed;
208 } else if (bozoprefs->count == bozoprefs->size) {
209 /* only uses 'needed' when filled array */
210 temp = realloc(bozoprefs->name, sizeof(char *) * needed);
211 if (temp == NULL)
212 return 0;
213 bozoprefs->name = temp;
214 temp = realloc(bozoprefs->value, sizeof(char *) * needed);
215 if (temp == NULL)
216 return 0;
217 bozoprefs->value = temp;
218 bozoprefs->size += needed;
219 }
220 return 1;
221 }
222
223 static ssize_t
224 findvar(bozoprefs_t *bozoprefs, const char *name)
225 {
226 size_t i;
227
228 for (i = 0; i < bozoprefs->count; i++)
229 if (strcmp(bozoprefs->name[i], name) == 0)
230 return (ssize_t)i;
231 return -1;
232 }
233
234 int
235 bozo_set_pref(bozohttpd_t *httpd, bozoprefs_t *bozoprefs,
236 const char *name, const char *value)
237 {
238 ssize_t i;
239
240 if ((i = findvar(bozoprefs, name)) < 0) {
241 /* add the element to the array */
242 if (!size_arrays(bozoprefs, bozoprefs->size + 15))
243 return 0;
244 i = bozoprefs->count++;
245 bozoprefs->name[i] = bozostrdup(httpd, NULL, name);
246 } else {
247 /* replace the element in the array */
248 if (bozoprefs->value[i]) {
249 free(bozoprefs->value[i]);
250 bozoprefs->value[i] = NULL;
251 }
252 }
253 bozoprefs->value[i] = bozostrdup(httpd, NULL, value);
254 return 1;
255 }
256
257 /*
258 * get a variable's value, or NULL
259 */
260 char *
261 bozo_get_pref(bozoprefs_t *bozoprefs, const char *name)
262 {
263 ssize_t i;
264
265 i = findvar(bozoprefs, name);
266 return i < 0 ? NULL : bozoprefs->value[i];
267 }
268
269 char *
270 bozo_http_date(char *date, size_t datelen)
271 {
272 struct tm *tm;
273 time_t now;
274
275 /* Sun, 06 Nov 1994 08:49:37 GMT */
276 now = time(NULL);
277 tm = gmtime(&now); /* HTTP/1.1 spec rev 06 sez GMT only */
278 strftime(date, datelen, "%a, %d %b %Y %H:%M:%S GMT", tm);
279 return date;
280 }
281
282 /*
283 * convert "in" into the three parts of a request (first line).
284 * we allocate into file and query, but return pointers into
285 * "in" for proto and method.
286 */
287 static void
288 parse_request(bozohttpd_t *httpd, char *in, char **method, char **file,
289 char **query, char **proto)
290 {
291 ssize_t len;
292 char *val;
293
294 USE_ARG(httpd);
295 debug((httpd, DEBUG_EXPLODING, "parse in: %s", in));
296 *method = *file = *query = *proto = NULL;
297
298 len = (ssize_t)strlen(in);
299 val = bozostrnsep(&in, " \t\n\r", &len);
300 if (len < 1 || val == NULL)
301 return;
302 *method = val;
303
304 while (*in == ' ' || *in == '\t')
305 in++;
306 val = bozostrnsep(&in, " \t\n\r", &len);
307 if (len < 1) {
308 if (len == 0)
309 *file = val;
310 else
311 *file = in;
312 } else {
313 *file = val;
314
315 *query = strchr(*file, '?');
316 if (*query)
317 *(*query)++ = '\0';
318
319 if (in) {
320 while (*in && (*in == ' ' || *in == '\t'))
321 in++;
322 if (*in)
323 *proto = in;
324 }
325 }
326
327 /* allocate private copies */
328 *file = bozostrdup(httpd, NULL, *file);
329 if (*query)
330 *query = bozostrdup(httpd, NULL, *query);
331
332 debug((httpd, DEBUG_FAT,
333 "url: method: \"%s\" file: \"%s\" query: \"%s\" proto: \"%s\"",
334 *method, *file, *query, *proto));
335 }
336
337 /*
338 * cleanup a bozo_httpreq_t after use
339 */
340 void
341 bozo_clean_request(bozo_httpreq_t *request)
342 {
343 struct bozoheaders *hdr, *ohdr = NULL;
344
345 if (request == NULL)
346 return;
347
348 /* If SSL enabled cleanup SSL structure. */
349 bozo_ssl_destroy(request->hr_httpd);
350
351 /* clean up request */
352 free(request->hr_remotehost);
353 free(request->hr_remoteaddr);
354 free(request->hr_serverport);
355 free(request->hr_virthostname);
356 free(request->hr_file);
357 free(request->hr_oldfile);
358 free(request->hr_query);
359 free(request->hr_host);
360 bozo_user_free(request->hr_user);
361 bozo_auth_cleanup(request);
362 for (hdr = SIMPLEQ_FIRST(&request->hr_headers); hdr;
363 hdr = SIMPLEQ_NEXT(hdr, h_next)) {
364 free(hdr->h_value);
365 free(hdr->h_header);
366 free(ohdr);
367 ohdr = hdr;
368 }
369 free(ohdr);
370 ohdr = NULL;
371 for (hdr = SIMPLEQ_FIRST(&request->hr_replheaders); hdr;
372 hdr = SIMPLEQ_NEXT(hdr, h_next)) {
373 free(hdr->h_value);
374 free(hdr->h_header);
375 free(ohdr);
376 ohdr = hdr;
377 }
378 free(ohdr);
379
380 free(request);
381 }
382
383 /*
384 * send a HTTP/1.1 408 response if we timeout.
385 */
386 /* ARGSUSED */
387 static void
388 alarmer(int sig)
389 {
390 timeout_hit = 1;
391 }
392
393
394 /*
395 * set a timeout for "initial", "header", or "request".
396 */
397 int
398 bozo_set_timeout(bozohttpd_t *httpd, bozoprefs_t *prefs,
399 const char *target, const char *time)
400 {
401 const char *cur, *timeouts[] = {
402 "initial timeout",
403 "header timeout",
404 "request timeout",
405 NULL,
406 };
407 /* adjust minlen if more timeouts appear with conflicting names */
408 const size_t minlen = 1;
409 size_t len = strlen(target);
410
411 for (cur = timeouts[0]; len >= minlen && *cur; cur++) {
412 if (strncmp(target, cur, len) == 0) {
413 bozo_set_pref(httpd, prefs, cur, time);
414 return 0;
415 }
416 }
417 return 1;
418 }
419
420 /*
421 * a list of header quirks: currently, a list of headers that
422 * can't be folded into a single line.
423 */
424 const char *header_quirks[] = { "WWW-Authenticate", NULL };
425
426 /*
427 * add or merge this header (val: str) into the requests list
428 */
429 static bozoheaders_t *
430 addmerge_header(bozo_httpreq_t *request, struct qheaders *headers,
431 const char *val, const char *str, ssize_t len)
432 {
433 struct bozohttpd_t *httpd = request->hr_httpd;
434 struct bozoheaders *hdr = NULL;
435 const char **quirk;
436
437 USE_ARG(len);
438 for (quirk = header_quirks; *quirk; quirk++)
439 if (strcasecmp(*quirk, val) == 0)
440 break;
441
442 if (*quirk == NULL) {
443 /* do we exist already? */
444 SIMPLEQ_FOREACH(hdr, headers, h_next) {
445 if (strcasecmp(val, hdr->h_header) == 0)
446 break;
447 }
448 }
449
450 if (hdr) {
451 /* yup, merge it in */
452 char *nval;
453
454 bozoasprintf(httpd, &nval, "%s, %s", hdr->h_value, str);
455 free(hdr->h_value);
456 hdr->h_value = nval;
457 } else {
458 /* nope, create a new one */
459
460 hdr = bozomalloc(httpd, sizeof *hdr);
461 hdr->h_header = bozostrdup(httpd, request, val);
462 if (str && *str)
463 hdr->h_value = bozostrdup(httpd, request, str);
464 else
465 hdr->h_value = bozostrdup(httpd, request, " ");
466
467 SIMPLEQ_INSERT_TAIL(headers, hdr, h_next);
468 request->hr_nheaders++;
469 }
470
471 return hdr;
472 }
473
474 bozoheaders_t *
475 addmerge_reqheader(bozo_httpreq_t *request, const char *val, const char *str,
476 ssize_t len)
477 {
478
479 return addmerge_header(request, &request->hr_headers, val, str, len);
480 }
481
482 bozoheaders_t *
483 addmerge_replheader(bozo_httpreq_t *request, const char *val, const char *str,
484 ssize_t len)
485 {
486
487 return addmerge_header(request, &request->hr_replheaders,
488 val, str, len);
489 }
490
491 /*
492 * as the prototype string is not constant (eg, "HTTP/1.1" is equivalent
493 * to "HTTP/001.01"), we MUST parse this.
494 */
495 static int
496 process_proto(bozo_httpreq_t *request, const char *proto)
497 {
498 struct bozohttpd_t *httpd = request->hr_httpd;
499 char majorstr[16], *minorstr;
500 int majorint, minorint;
501
502 if (proto == NULL) {
503 got_proto_09:
504 request->hr_proto = httpd->consts.http_09;
505 debug((httpd, DEBUG_FAT, "request %s is http/0.9",
506 request->hr_file));
507 return 0;
508 }
509
510 if (strncasecmp(proto, "HTTP/", 5) != 0)
511 goto bad;
512 strncpy(majorstr, proto + 5, sizeof majorstr);
513 majorstr[sizeof(majorstr)-1] = 0;
514 minorstr = strchr(majorstr, '.');
515 if (minorstr == NULL)
516 goto bad;
517 *minorstr++ = 0;
518
519 majorint = atoi(majorstr);
520 minorint = atoi(minorstr);
521
522 switch (majorint) {
523 case 0:
524 if (minorint != 9)
525 break;
526 goto got_proto_09;
527 case 1:
528 if (minorint == 0)
529 request->hr_proto = httpd->consts.http_10;
530 else if (minorint == 1)
531 request->hr_proto = httpd->consts.http_11;
532 else
533 break;
534
535 debug((httpd, DEBUG_FAT, "request %s is %s",
536 request->hr_file, request->hr_proto));
537 SIMPLEQ_INIT(&request->hr_headers);
538 request->hr_nheaders = 0;
539 return 0;
540 }
541 bad:
542 return bozo_http_error(httpd, 404, NULL, "unknown prototype");
543 }
544
545 /*
546 * process each type of HTTP method, setting this HTTP requests
547 * method type.
548 */
549 static struct method_map {
550 const char *name;
551 int type;
552 } method_map[] = {
553 { "GET", HTTP_GET, },
554 { "POST", HTTP_POST, },
555 { "HEAD", HTTP_HEAD, },
556 #if 0 /* other non-required http/1.1 methods */
557 { "OPTIONS", HTTP_OPTIONS, },
558 { "PUT", HTTP_PUT, },
559 { "DELETE", HTTP_DELETE, },
560 { "TRACE", HTTP_TRACE, },
561 { "CONNECT", HTTP_CONNECT, },
562 #endif
563 { NULL, 0, },
564 };
565
566 static int
567 process_method(bozo_httpreq_t *request, const char *method)
568 {
569 struct bozohttpd_t *httpd = request->hr_httpd;
570 struct method_map *mmp;
571
572 if (request->hr_proto == httpd->consts.http_11)
573 request->hr_allow = "GET, HEAD, POST";
574
575 for (mmp = method_map; mmp->name; mmp++)
576 if (strcasecmp(method, mmp->name) == 0) {
577 request->hr_method = mmp->type;
578 request->hr_methodstr = mmp->name;
579 return 0;
580 }
581
582 return bozo_http_error(httpd, 404, request, "unknown method");
583 }
584
585 /* check header byte count */
586 static int
587 bozo_got_header_length(bozo_httpreq_t *request, size_t len)
588 {
589 request->hr_header_bytes += len;
590 if (request->hr_header_bytes < BOZO_HEADERS_MAX_SIZE)
591 return 0;
592
593 return bozo_http_error(request->hr_httpd, 413, request,
594 "too many headers");
595 }
596
597 /*
598 * This function reads a http request from stdin, returning a pointer to a
599 * bozo_httpreq_t structure, describing the request.
600 */
601 bozo_httpreq_t *
602 bozo_read_request(bozohttpd_t *httpd)
603 {
604 struct sigaction sa;
605 char *str, *val, *method, *file, *proto, *query;
606 char *host, *addr, *port;
607 char bufport[10];
608 char hbuf[NI_MAXHOST], abuf[NI_MAXHOST];
609 struct sockaddr_storage ss;
610 ssize_t len;
611 int line = 0;
612 socklen_t slen;
613 bozo_httpreq_t *request;
614 struct timespec ots, ts;
615
616 /*
617 * if we're in daemon mode, bozo_daemon_fork() will return here twice
618 * for each call. once in the child, returning 0, and once in the
619 * parent, returning 1. for each child, then we can setup SSL, and
620 * the parent can signal the caller there was no request to process
621 * and it will wait for another.
622 */
623 if (bozo_daemon_fork(httpd))
624 return NULL;
625 if (bozo_ssl_accept(httpd))
626 return NULL;
627
628 request = bozomalloc(httpd, sizeof(*request));
629 memset(request, 0, sizeof(*request));
630 request->hr_httpd = httpd;
631 request->hr_allow = request->hr_host = NULL;
632 request->hr_content_type = request->hr_content_length = NULL;
633 request->hr_range = NULL;
634 request->hr_last_byte_pos = -1;
635 request->hr_if_modified_since = NULL;
636 request->hr_virthostname = NULL;
637 request->hr_file = NULL;
638 request->hr_oldfile = NULL;
639 SIMPLEQ_INIT(&request->hr_replheaders);
640 bozo_auth_init(request);
641
642 slen = sizeof(ss);
643 if (getpeername(0, (struct sockaddr *)(void *)&ss, &slen) < 0)
644 host = addr = NULL;
645 else {
646 if (getnameinfo((struct sockaddr *)(void *)&ss, slen,
647 abuf, sizeof abuf, NULL, 0, NI_NUMERICHOST) == 0)
648 addr = abuf;
649 else
650 addr = NULL;
651 if (httpd->numeric == 0 &&
652 getnameinfo((struct sockaddr *)(void *)&ss, slen,
653 hbuf, sizeof hbuf, NULL, 0, 0) == 0)
654 host = hbuf;
655 else
656 host = NULL;
657 }
658 if (host != NULL)
659 request->hr_remotehost = bozostrdup(httpd, request, host);
660 if (addr != NULL)
661 request->hr_remoteaddr = bozostrdup(httpd, request, addr);
662 slen = sizeof(ss);
663
664 /*
665 * Override the bound port from the request value, so it works even
666 * if passed through a proxy that doesn't rewrite the port.
667 */
668 if (httpd->bindport) {
669 if (strcmp(httpd->bindport, "80") != 0)
670 port = httpd->bindport;
671 else
672 port = NULL;
673 } else {
674 if (getsockname(0, (struct sockaddr *)(void *)&ss, &slen) < 0)
675 port = NULL;
676 else {
677 if (getnameinfo((struct sockaddr *)(void *)&ss, slen,
678 NULL, 0, bufport, sizeof bufport,
679 NI_NUMERICSERV) == 0)
680 port = bufport;
681 else
682 port = NULL;
683 }
684 }
685 if (port != NULL)
686 request->hr_serverport = bozostrdup(httpd, request, port);
687
688 /*
689 * setup a timer to make sure the request is not hung
690 */
691 sa.sa_handler = alarmer;
692 sigemptyset(&sa.sa_mask);
693 sigaddset(&sa.sa_mask, SIGALRM);
694 sa.sa_flags = 0;
695 sigaction(SIGALRM, &sa, NULL);
696
697 if (clock_gettime(CLOCK_MONOTONIC, &ots) != 0) {
698 bozo_http_error(httpd, 500, NULL, "clock_gettime failed");
699 goto cleanup;
700 }
701
702 alarm(httpd->initial_timeout);
703 while ((str = bozodgetln(httpd, STDIN_FILENO, &len, bozo_read)) != NULL) {
704 alarm(0);
705
706 if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
707 bozo_http_error(httpd, 500, NULL, "clock_gettime failed");
708 goto cleanup;
709 }
710 /*
711 * don't timeout if old tv_sec is not more than current
712 * tv_sec, or if current tv_sec is less than the request
713 * timeout (these shouldn't happen, but the first could
714 * if monotonic time is not available.)
715 *
716 * the other timeout and header size checks should ensure
717 * that even if time it set backwards or forwards a very
718 * long way, timeout will eventually happen, even if this
719 * one fails.
720 */
721 if (ts.tv_sec > ots.tv_sec &&
722 ts.tv_sec > httpd->request_timeout &&
723 ts.tv_sec - httpd->request_timeout > ots.tv_sec)
724 timeout_hit = 1;
725
726 if (timeout_hit) {
727 bozo_http_error(httpd, 408, NULL, "request timed out");
728 goto cleanup;
729 }
730 line++;
731
732 if (line == 1) {
733 if (len < 1) {
734 bozo_http_error(httpd, 404, NULL, "null method");
735 goto cleanup;
736 }
737 bozowarn(httpd,
738 "got request ``%s'' from host %s to port %s",
739 str,
740 host ? host : addr ? addr : "<local>",
741 port ? port : "<stdin>");
742
743 /* we allocate return space in file and query only */
744 parse_request(httpd, str, &method, &file, &query, &proto);
745 request->hr_file = file;
746 request->hr_query = query;
747 if (method == NULL) {
748 bozo_http_error(httpd, 404, NULL, "null method");
749 goto cleanup;
750 }
751 if (file == NULL) {
752 bozo_http_error(httpd, 404, NULL, "null file");
753 goto cleanup;
754 }
755
756 /*
757 * note that we parse the proto first, so that we
758 * can more properly parse the method and the url.
759 */
760
761 if (process_proto(request, proto) ||
762 process_method(request, method)) {
763 goto cleanup;
764 }
765
766 debug((httpd, DEBUG_FAT, "got file \"%s\" query \"%s\"",
767 request->hr_file,
768 request->hr_query ? request->hr_query : "<none>"));
769
770 /* http/0.9 has no header processing */
771 if (request->hr_proto == httpd->consts.http_09)
772 break;
773 } else { /* incoming headers */
774 bozoheaders_t *hdr;
775
776 if (*str == '\0')
777 break;
778
779 val = bozostrnsep(&str, ":", &len);
780 debug((httpd, DEBUG_EXPLODING, "read_req2: after "
781 "bozostrnsep: str `%s' val `%s'", str, val));
782 if (val == NULL || len == -1) {
783 bozo_http_error(httpd, 404, request, "no header");
784 goto cleanup;
785 }
786 while (*str == ' ' || *str == '\t')
787 len--, str++;
788 while (*val == ' ' || *val == '\t')
789 val++;
790
791 if (bozo_got_header_length(request, len))
792 goto cleanup;
793
794 if (bozo_auth_check_headers(request, val, str, len))
795 goto next_header;
796
797 hdr = addmerge_reqheader(request, val, str, len);
798
799 if (strcasecmp(hdr->h_header, "content-type") == 0)
800 request->hr_content_type = hdr->h_value;
801 else if (strcasecmp(hdr->h_header, "content-length") == 0)
802 request->hr_content_length = hdr->h_value;
803 else if (strcasecmp(hdr->h_header, "host") == 0) {
804 if (request->hr_host) {
805 /* RFC 7230 (HTTP/1.1): 5.4 */
806 bozo_http_error(httpd, 400, request,
807 "Only allow one Host: header");
808 goto cleanup;
809 }
810 request->hr_host = bozostrdup(httpd, request,
811 hdr->h_value);
812 }
813 /* RFC 2616 (HTTP/1.1): 14.20 */
814 else if (strcasecmp(hdr->h_header, "expect") == 0) {
815 bozo_http_error(httpd, 417, request,
816 "we don't support Expect:");
817 goto cleanup;
818 }
819 else if (strcasecmp(hdr->h_header, "referrer") == 0 ||
820 strcasecmp(hdr->h_header, "referer") == 0)
821 request->hr_referrer = hdr->h_value;
822 else if (strcasecmp(hdr->h_header, "range") == 0)
823 request->hr_range = hdr->h_value;
824 else if (strcasecmp(hdr->h_header,
825 "if-modified-since") == 0)
826 request->hr_if_modified_since = hdr->h_value;
827 else if (strcasecmp(hdr->h_header,
828 "accept-encoding") == 0)
829 request->hr_accept_encoding = hdr->h_value;
830
831 debug((httpd, DEBUG_FAT, "adding header %s: %s",
832 hdr->h_header, hdr->h_value));
833 }
834 next_header:
835 alarm(httpd->header_timeout);
836 }
837
838 /* now, clear it all out */
839 alarm(0);
840 signal(SIGALRM, SIG_DFL);
841
842 /* RFC1945, 8.3 */
843 if (request->hr_method == HTTP_POST &&
844 request->hr_content_length == NULL) {
845 bozo_http_error(httpd, 400, request, "missing content length");
846 goto cleanup;
847 }
848
849 /* RFC 2616 (HTTP/1.1), 14.23 & 19.6.1.1 */
850 if (request->hr_proto == httpd->consts.http_11 &&
851 /*(strncasecmp(request->hr_file, "http://", 7) != 0) &&*/
852 request->hr_host == NULL) {
853 bozo_http_error(httpd, 400, request, "missing Host header");
854 goto cleanup;
855 }
856
857 if (request->hr_range != NULL) {
858 debug((httpd, DEBUG_FAT, "hr_range: %s", request->hr_range));
859 /* support only simple ranges %d- and %d-%d */
860 if (strchr(request->hr_range, ',') == NULL) {
861 const char *rstart, *dash;
862
863 rstart = strchr(request->hr_range, '=');
864 if (rstart != NULL) {
865 rstart++;
866 dash = strchr(rstart, '-');
867 if (dash != NULL && dash != rstart) {
868 dash++;
869 request->hr_have_range = 1;
870 request->hr_first_byte_pos =
871 strtoll(rstart, NULL, 10);
872 if (request->hr_first_byte_pos < 0)
873 request->hr_first_byte_pos = 0;
874 if (*dash != '\0') {
875 request->hr_last_byte_pos =
876 strtoll(dash, NULL, 10);
877 if (request->hr_last_byte_pos < 0)
878 request->hr_last_byte_pos = -1;
879 }
880 }
881 }
882 }
883 }
884
885 debug((httpd, DEBUG_FAT, "bozo_read_request returns url %s in request",
886 request->hr_file));
887 return request;
888
889 cleanup:
890 bozo_clean_request(request);
891
892 return NULL;
893 }
894
895 static int
896 mmap_and_write_part(bozohttpd_t *httpd, int fd, off_t first_byte_pos, size_t sz)
897 {
898 size_t mappedsz, wroffset;
899 off_t mappedoffset;
900 char *addr;
901 void *mappedaddr;
902
903 /*
904 * we need to ensure that both the size *and* offset arguments to
905 * mmap() are page-aligned. our formala for this is:
906 *
907 * input offset: first_byte_pos
908 * input size: sz
909 *
910 * mapped offset = page align truncate (input offset)
911 * mapped size =
912 * page align extend (input offset - mapped offset + input size)
913 * write offset = input offset - mapped offset
914 *
915 * we use the write offset in all writes
916 */
917 mappedoffset = first_byte_pos & ~(httpd->page_size - 1);
918 mappedsz = (size_t)
919 (first_byte_pos - mappedoffset + sz + httpd->page_size - 1) &
920 ~(httpd->page_size - 1);
921 wroffset = (size_t)(first_byte_pos - mappedoffset);
922
923 addr = mmap(0, mappedsz, PROT_READ, MAP_SHARED, fd, mappedoffset);
924 if (addr == (char *)-1) {
925 bozowarn(httpd, "mmap failed: %s", strerror(errno));
926 return -1;
927 }
928 mappedaddr = addr;
929
930 #ifdef MADV_SEQUENTIAL
931 (void)madvise(addr, sz, MADV_SEQUENTIAL);
932 #endif
933 while (sz > BOZO_WRSZ) {
934 if (bozo_write(httpd, STDOUT_FILENO, addr + wroffset,
935 BOZO_WRSZ) != BOZO_WRSZ) {
936 bozowarn(httpd, "write failed: %s", strerror(errno));
937 goto out;
938 }
939 debug((httpd, DEBUG_OBESE, "wrote %d bytes", BOZO_WRSZ));
940 sz -= BOZO_WRSZ;
941 addr += BOZO_WRSZ;
942 }
943 if (sz && (size_t)bozo_write(httpd, STDOUT_FILENO, addr + wroffset,
944 sz) != sz) {
945 bozowarn(httpd, "final write failed: %s", strerror(errno));
946 goto out;
947 }
948 debug((httpd, DEBUG_OBESE, "wrote %d bytes", (int)sz));
949 out:
950 if (munmap(mappedaddr, mappedsz) < 0) {
951 bozowarn(httpd, "munmap failed");
952 return -1;
953 }
954
955 return 0;
956 }
957
958 static int
959 parse_http_date(const char *val, time_t *timestamp)
960 {
961 char *remainder;
962 struct tm tm;
963
964 if ((remainder = strptime(val, "%a, %d %b %Y %T GMT", &tm)) == NULL &&
965 (remainder = strptime(val, "%a, %d-%b-%y %T GMT", &tm)) == NULL &&
966 (remainder = strptime(val, "%a %b %d %T %Y", &tm)) == NULL)
967 return 0; /* Invalid HTTP date format */
968
969 if (*remainder)
970 return 0; /* No trailing garbage */
971
972 *timestamp = timegm(&tm);
973 return 1;
974 }
975
976 /*
977 * given an url, encode it ala rfc 3986. ie, escape ? and friends.
978 * note that this function returns a static buffer, and thus needs
979 * to be updated for any sort of parallel processing. escape only
980 * chosen characters for absolute redirects
981 */
982 char *
983 bozo_escape_rfc3986(bozohttpd_t *httpd, const char *url, int absolute)
984 {
985 static char *buf;
986 static size_t buflen = 0;
987 size_t len;
988 const char *s;
989 char *d;
990
991 len = strlen(url);
992 if (buflen < len * 3 + 1) {
993 buflen = len * 3 + 1;
994 buf = bozorealloc(httpd, buf, buflen);
995 }
996
997 for (len = 0, s = url, d = buf; *s;) {
998 if (*s & 0x80)
999 goto encode_it;
1000 switch (*s) {
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 case '"':
1020 if (absolute)
1021 goto leave_it;
1022 case '\n':
1023 case '\r':
1024 case ' ':
1025 encode_it:
1026 snprintf(d, 4, "%%%02X", *s++);
1027 d += 3;
1028 len += 3;
1029 break;
1030 leave_it:
1031 default:
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];
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 return -1;
1482 }
1483 if (lstat(redir, &sb) == 0) {
1484 if (!S_ISLNK(sb.st_mode))
1485 return 0;
1486 absolute = 0;
1487 } else {
1488 if ((size_t)snprintf(redir, sizeof(redir), "%s/%s", path,
1489 ABSREDIRECT_FILE) >= sizeof(redir)) {
1490 bozo_http_error(httpd, 404, request,
1491 "redirectfile path too long");
1492 return -1;
1493 }
1494 if (lstat(redir, &sb) < 0 || !S_ISLNK(sb.st_mode))
1495 return 0;
1496 absolute = 1;
1497 }
1498 debug((httpd, DEBUG_FAT, "check_bzredirect: calling readlink"));
1499 rv = readlink(redir, redirpath, sizeof redirpath - 1);
1500 if (rv == -1 || rv == 0) {
1501 debug((httpd, DEBUG_FAT, "readlink failed"));
1502 return 0;
1503 }
1504 redirpath[rv] = '\0';
1505 debug((httpd, DEBUG_FAT, "readlink returned \"%s\"", redirpath));
1506
1507 /* check if we need authentication */
1508 snprintf(path, sizeof(path), "%s/", dir);
1509 if (bozo_auth_check(request, path))
1510 return 1;
1511
1512 /* now we have the link pointer, redirect to the real place */
1513 if (!absolute && redirpath[0] != '/') {
1514 if ((size_t)snprintf(finalredir = redir, sizeof(redir), "%s%s/%s",
1515 (strlen(dir) > 0 ? "/" : ""), dir, redirpath) >= sizeof(redir)) {
1516 bozo_http_error(httpd, 404, request,
1517 "redirect path too long");
1518 return -1;
1519 }
1520 } else
1521 finalredir = redirpath;
1522
1523 debug((httpd, DEBUG_FAT, "check_bzredirect: new redir %s", finalredir));
1524 handle_redirect(request, finalredir, absolute);
1525 return 1;
1526 }
1527
1528 /* this fixes the %HH hack that RFC2396 requires. */
1529 int
1530 bozo_decode_url_percent(bozo_httpreq_t *request, char *str)
1531 {
1532 bozohttpd_t *httpd = request->hr_httpd;
1533 char *s, *t, buf[3];
1534 char *end; /* if end is not-zero, we don't translate beyond that */
1535
1536 end = str + strlen(str);
1537
1538 /* fast forward to the first % */
1539 if ((s = strchr(str, '%')) == NULL)
1540 return 0;
1541
1542 t = s;
1543 do {
1544 if (end && s >= end) {
1545 debug((httpd, DEBUG_EXPLODING,
1546 "fu_%%: past end, filling out.."));
1547 while (*s)
1548 *t++ = *s++;
1549 break;
1550 }
1551 debug((httpd, DEBUG_EXPLODING,
1552 "fu_%%: got s == %%, s[1]s[2] == %c%c",
1553 s[1], s[2]));
1554 if (s[1] == '\0' || s[2] == '\0')
1555 return bozo_http_error(httpd, 400, request,
1556 "percent hack missing two chars afterwards");
1557 if (s[1] == '0' && s[2] == '0')
1558 return bozo_http_error(httpd, 404, request,
1559 "percent hack was %00");
1560 if (s[1] == '2' && s[2] == 'f')
1561 return bozo_http_error(httpd, 404, request,
1562 "percent hack was %2f (/)");
1563
1564 buf[0] = *++s;
1565 buf[1] = *++s;
1566 buf[2] = '\0';
1567 s++;
1568 *t = (char)strtol(buf, NULL, 16);
1569 debug((httpd, DEBUG_EXPLODING,
1570 "fu_%%: strtol put '%02x' into *t", *t));
1571 if (*t++ == '\0')
1572 return bozo_http_error(httpd, 400, request,
1573 "percent hack got a 0 back");
1574
1575 while (*s && *s != '%') {
1576 if (end && s >= end)
1577 break;
1578 *t++ = *s++;
1579 }
1580 } while (*s);
1581 *t = '\0';
1582
1583 debug((httpd, DEBUG_FAT, "bozo_decode_url_percent returns `%s'",
1584 request->hr_file));
1585
1586 return 0;
1587 }
1588
1589 /*
1590 * transform_request does this:
1591 * - ``expand'' %20 crapola
1592 * - punt if it doesn't start with /
1593 * - look for "http://myname/" and deal with it.
1594 * - maybe call bozo_process_cgi()
1595 * - check for ~user and call bozo_user_transform() if so
1596 * - if the length > 1, check for trailing slash. if so,
1597 * add the index.html file
1598 * - if the length is 1, return the index.html file
1599 * - disallow anything ending up with a file starting
1600 * at "/" or having ".." in it.
1601 * - anything else is a really weird internal error
1602 * - returns malloced file to serve, if unhandled
1603 */
1604 static int
1605 transform_request(bozo_httpreq_t *request, int *isindex)
1606 {
1607 bozohttpd_t *httpd = request->hr_httpd;
1608 char *file, *newfile = NULL;
1609 size_t len;
1610
1611 file = NULL;
1612 *isindex = 0;
1613 debug((httpd, DEBUG_FAT, "tf_req: file %s", request->hr_file));
1614
1615 if (bozo_decode_url_percent(request, request->hr_file) ||
1616 check_virtual(request))
1617 goto bad_done;
1618
1619 file = request->hr_file;
1620
1621 if (file[0] != '/') {
1622 bozo_http_error(httpd, 404, request, "unknown URL");
1623 goto bad_done;
1624 }
1625
1626 /* omit additional slashes at the beginning */
1627 while (file[1] == '/')
1628 file++;
1629
1630 /* fix file provided by user as it's used in other handlers */
1631 request->hr_file = file;
1632
1633 len = strlen(file);
1634
1635 #ifndef NO_USER_SUPPORT
1636 /* first of all expand user path */
1637 if (len > 1 && httpd->enable_users && file[1] == '~') {
1638 if (file[2] == '\0') {
1639 bozo_http_error(httpd, 404, request,
1640 "missing username");
1641 goto bad_done;
1642 }
1643 if (strchr(file + 2, '/') == NULL) {
1644 char *userredirecturl;
1645
1646 bozoasprintf(httpd, &userredirecturl, "%s/", file);
1647 handle_redirect(request, userredirecturl, 0);
1648 free(userredirecturl);
1649 return 0;
1650 }
1651 debug((httpd, DEBUG_FAT, "calling bozo_user_transform"));
1652
1653 if (!bozo_user_transform(request))
1654 return 0;
1655
1656 file = request->hr_file;
1657 len = strlen(file);
1658 }
1659 #endif /* NO_USER_SUPPORT */
1660
1661
1662 switch (check_bzredirect(request)) {
1663 case -1:
1664 goto bad_done;
1665 case 0:
1666 break;
1667 default:
1668 return 0;
1669 }
1670
1671 if (len > 1) {
1672 debug((httpd, DEBUG_FAT, "file[len-1] == %c", file[len-1]));
1673 if (file[len-1] == '/') { /* append index.html */
1674 *isindex = 1;
1675 debug((httpd, DEBUG_FAT, "appending index.html"));
1676 newfile = bozomalloc(httpd,
1677 len + strlen(httpd->index_html) + 1);
1678 strcpy(newfile, file + 1);
1679 strcat(newfile, httpd->index_html);
1680 } else
1681 newfile = bozostrdup(httpd, request, file + 1);
1682 } else if (len == 1) {
1683 debug((httpd, DEBUG_EXPLODING, "tf_req: len == 1"));
1684 newfile = bozostrdup(httpd, request, httpd->index_html);
1685 *isindex = 1;
1686 } else { /* len == 0 ? */
1687 bozo_http_error(httpd, 500, request, "request->hr_file is nul");
1688 goto bad_done;
1689 }
1690
1691 if (newfile == NULL) {
1692 bozo_http_error(httpd, 500, request, "internal failure");
1693 goto bad_done;
1694 }
1695
1696 /*
1697 * stop traversing outside our domain
1698 *
1699 * XXX true security only comes from our parent using chroot(2)
1700 * before execve(2)'ing us. or our own built in chroot(2) support.
1701 */
1702
1703 debug((httpd, DEBUG_FAT, "newfile: %s", newfile));
1704
1705 if (*newfile == '/' || strcmp(newfile, "..") == 0 ||
1706 strstr(newfile, "/..") || strstr(newfile, "../")) {
1707 bozo_http_error(httpd, 403, request, "illegal request");
1708 goto bad_done;
1709 }
1710
1711 if (bozo_auth_check(request, newfile))
1712 goto bad_done;
1713
1714 if (strlen(newfile)) {
1715 request->hr_oldfile = request->hr_file;
1716 request->hr_file = newfile;
1717 }
1718
1719 if (bozo_process_cgi(request) ||
1720 bozo_process_lua(request))
1721 return 0;
1722
1723 debug((httpd, DEBUG_FAT, "transform_request set: %s", newfile));
1724 return 1;
1725
1726 bad_done:
1727 debug((httpd, DEBUG_FAT, "transform_request returning: 0"));
1728 free(newfile);
1729 return 0;
1730 }
1731
1732 /*
1733 * can_gzip checks if the request supports and prefers gzip encoding.
1734 *
1735 * XXX: we do not consider the associated q with gzip in making our
1736 * decision which is broken.
1737 */
1738
1739 static int
1740 can_gzip(bozo_httpreq_t *request)
1741 {
1742 const char *pos;
1743 const char *tmp;
1744 size_t len;
1745
1746 /* First we decide if the request can be gzipped at all. */
1747
1748 /* not if we already are encoded... */
1749 tmp = bozo_content_encoding(request, request->hr_file);
1750 if (tmp && *tmp)
1751 return 0;
1752
1753 /* not if we are not asking for the whole file... */
1754 if (request->hr_last_byte_pos != -1 || request->hr_have_range)
1755 return 0;
1756
1757 /* Then we determine if gzip is on the cards. */
1758
1759 for (pos = request->hr_accept_encoding; pos && *pos; pos += len) {
1760 while (*pos == ' ')
1761 pos++;
1762
1763 len = strcspn(pos, ";,");
1764
1765 if ((len == 4 && strncasecmp("gzip", pos, 4) == 0) ||
1766 (len == 6 && strncasecmp("x-gzip", pos, 6) == 0))
1767 return 1;
1768
1769 if (pos[len] == ';')
1770 len += strcspn(&pos[len], ",");
1771
1772 if (pos[len])
1773 len++;
1774 }
1775
1776 return 0;
1777 }
1778
1779 /*
1780 * bozo_process_request does the following:
1781 * - check the request is valid
1782 * - process cgi-bin if necessary
1783 * - transform a filename if necesarry
1784 * - return the HTTP request
1785 */
1786 void
1787 bozo_process_request(bozo_httpreq_t *request)
1788 {
1789 bozohttpd_t *httpd = request->hr_httpd;
1790 struct stat sb;
1791 time_t timestamp;
1792 char *file;
1793 const char *type, *encoding;
1794 int fd, isindex;
1795
1796 /*
1797 * note that transform_request chdir()'s if required. also note
1798 * that cgi is handed here. if transform_request() returns 0
1799 * then the request has been handled already.
1800 */
1801 if (transform_request(request, &isindex) == 0)
1802 return;
1803
1804 fd = -1;
1805 encoding = NULL;
1806 if (can_gzip(request)) {
1807 bozoasprintf(httpd, &file, "%s.gz", request->hr_file);
1808 fd = open(file, O_RDONLY);
1809 if (fd >= 0)
1810 encoding = "gzip";
1811 free(file);
1812 }
1813
1814 file = request->hr_file;
1815
1816 if (fd < 0)
1817 fd = open(file, O_RDONLY);
1818
1819 if (fd < 0) {
1820 debug((httpd, DEBUG_FAT, "open failed: %s", strerror(errno)));
1821 switch (errno) {
1822 case EPERM:
1823 case EACCES:
1824 bozo_http_error(httpd, 403, request,
1825 "no permission to open file");
1826 break;
1827 case ENAMETOOLONG:
1828 /*FALLTHROUGH*/
1829 case ENOENT:
1830 if (!bozo_dir_index(request, file, isindex))
1831 bozo_http_error(httpd, 404, request, "no file");
1832 break;
1833 default:
1834 bozo_http_error(httpd, 500, request, "open file");
1835 }
1836 goto cleanup_nofd;
1837 }
1838 if (fstat(fd, &sb) < 0) {
1839 bozo_http_error(httpd, 500, request, "can't fstat");
1840 goto cleanup;
1841 }
1842 if (S_ISDIR(sb.st_mode)) {
1843 handle_redirect(request, NULL, 0);
1844 goto cleanup;
1845 }
1846
1847 if (request->hr_if_modified_since &&
1848 parse_http_date(request->hr_if_modified_since, ×tamp) &&
1849 timestamp >= sb.st_mtime) {
1850 /* XXX ignore subsecond of timestamp */
1851 bozo_printf(httpd, "%s 304 Not Modified\r\n",
1852 request->hr_proto);
1853 bozo_printf(httpd, "\r\n");
1854 bozo_flush(httpd, stdout);
1855 goto cleanup;
1856 }
1857
1858 /* validate requested range */
1859 if (request->hr_last_byte_pos == -1 ||
1860 request->hr_last_byte_pos >= sb.st_size)
1861 request->hr_last_byte_pos = sb.st_size - 1;
1862 if (request->hr_have_range &&
1863 request->hr_first_byte_pos > request->hr_last_byte_pos) {
1864 request->hr_have_range = 0; /* punt */
1865 request->hr_first_byte_pos = 0;
1866 request->hr_last_byte_pos = sb.st_size - 1;
1867 }
1868 debug((httpd, DEBUG_FAT, "have_range %d first_pos %lld last_pos %lld",
1869 request->hr_have_range,
1870 (long long)request->hr_first_byte_pos,
1871 (long long)request->hr_last_byte_pos));
1872 if (request->hr_have_range)
1873 bozo_printf(httpd, "%s 206 Partial Content\r\n",
1874 request->hr_proto);
1875 else
1876 bozo_printf(httpd, "%s 200 OK\r\n", request->hr_proto);
1877
1878 if (request->hr_proto != httpd->consts.http_09) {
1879 type = bozo_content_type(request, file);
1880 if (!encoding)
1881 encoding = bozo_content_encoding(request, file);
1882
1883 bozo_print_header(request, &sb, type, encoding);
1884 bozo_printf(httpd, "\r\n");
1885 }
1886 bozo_flush(httpd, stdout);
1887
1888 if (request->hr_method != HTTP_HEAD) {
1889 off_t szleft, cur_byte_pos;
1890
1891 szleft =
1892 request->hr_last_byte_pos - request->hr_first_byte_pos + 1;
1893 cur_byte_pos = request->hr_first_byte_pos;
1894
1895 retry:
1896 while (szleft) {
1897 size_t sz;
1898
1899 if ((off_t)httpd->mmapsz < szleft)
1900 sz = httpd->mmapsz;
1901 else
1902 sz = (size_t)szleft;
1903 if (mmap_and_write_part(httpd, fd, cur_byte_pos, sz)) {
1904 if (errno == ENOMEM) {
1905 httpd->mmapsz /= 2;
1906 if (httpd->mmapsz >= httpd->page_size)
1907 goto retry;
1908 }
1909 goto cleanup;
1910 }
1911 cur_byte_pos += sz;
1912 szleft -= sz;
1913 }
1914 }
1915 cleanup:
1916 close(fd);
1917 cleanup_nofd:
1918 close(STDIN_FILENO);
1919 close(STDOUT_FILENO);
1920 /*close(STDERR_FILENO);*/
1921 }
1922
1923 /* make sure we're not trying to access special files */
1924 int
1925 bozo_check_special_files(bozo_httpreq_t *request, const char *name)
1926 {
1927 bozohttpd_t *httpd = request->hr_httpd;
1928
1929 for (size_t 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 }
2080 }
2081
2082 if (j == 0)
2083 return NULL;
2084
2085 /*
2086 * we need to handle being called from different
2087 * pathnames.
2088 */
2089 len = strlen(url) + j;
2090 if (httpd)
2091 tmp = bozomalloc(httpd, len);
2092 else if ((tmp = malloc(len)) == 0)
2093 return NULL;
2094
2095 for (i = 0, j = 0; url[i]; i++) {
2096 switch (url[i]) {
2097 case '<':
2098 memcpy(tmp + j, "<", 4);
2099 j += 4;
2100 break;
2101 case '>':
2102 memcpy(tmp + j, ">", 4);
2103 j += 4;
2104 break;
2105 case '&':
2106 memcpy(tmp + j, "&", 5);
2107 j += 5;
2108 break;
2109 default:
2110 tmp[j++] = url[i];
2111 }
2112 }
2113 tmp[j] = 0;
2114
2115 return tmp;
2116 }
2117
2118 /* short map between error code, and short/long messages */
2119 static struct errors_map {
2120 int code; /* HTTP return code */
2121 const char *shortmsg; /* short version of message */
2122 const char *longmsg; /* long version of message */
2123 } errors_map[] = {
2124 { 400, "400 Bad Request", "The request was not valid", },
2125 { 401, "401 Unauthorized", "No authorization", },
2126 { 403, "403 Forbidden", "Access to this item has been denied",},
2127 { 404, "404 Not Found", "This item has not been found", },
2128 { 408, "408 Request Timeout", "This request took too long", },
2129 { 413, "413 Payload Too Large", "Use smaller requests", },
2130 { 417, "417 Expectation Failed","Expectations not available", },
2131 { 420, "420 Enhance Your Calm","Chill, Winston", },
2132 { 500, "500 Internal Error", "An error occured on the server", },
2133 { 501, "501 Not Implemented", "This request is not available", },
2134 { 0, NULL, NULL, },
2135 };
2136
2137 static const char *help = "DANGER! WILL ROBINSON! DANGER!";
2138
2139 static const char *
2140 http_errors_short(int code)
2141 {
2142 struct errors_map *ep;
2143
2144 for (ep = errors_map; ep->code; ep++)
2145 if (ep->code == code)
2146 return (ep->shortmsg);
2147 return (help);
2148 }
2149
2150 static const char *
2151 http_errors_long(int code)
2152 {
2153 struct errors_map *ep;
2154
2155 for (ep = errors_map; ep->code; ep++)
2156 if (ep->code == code)
2157 return (ep->longmsg);
2158 return (help);
2159 }
2160
2161 /* the follow functions and variables are used in handling HTTP errors */
2162 /* ARGSUSED */
2163 int
2164 bozo_http_error(bozohttpd_t *httpd, int code, bozo_httpreq_t *request,
2165 const char *msg)
2166 {
2167 char portbuf[20];
2168 const char *header = http_errors_short(code);
2169 const char *reason = http_errors_long(code);
2170 const char *proto = (request && request->hr_proto) ?
2171 request->hr_proto : httpd->consts.http_11;
2172 int size;
2173 bozoheaders_t *hdr;
2174
2175 debug((httpd, DEBUG_FAT, "bozo_http_error %d: %s", code, msg));
2176 if (header == NULL || reason == NULL) {
2177 bozoerr(httpd, 1,
2178 "bozo_http_error() failed (short = %p, long = %p)",
2179 header, reason);
2180 return code;
2181 }
2182
2183 if (request && request->hr_serverport &&
2184 strcmp(request->hr_serverport, "80") != 0)
2185 snprintf(portbuf, sizeof(portbuf), ":%s",
2186 request->hr_serverport);
2187 else
2188 portbuf[0] = '\0';
2189
2190 if (request && request->hr_file) {
2191 char *file = NULL, *user = NULL;
2192 int file_alloc = 0;
2193 const char *hostname = BOZOHOST(httpd, request);
2194
2195 /* bozo_escape_html() failure here is just too bad. */
2196 file = bozo_escape_html(NULL, request->hr_file);
2197 if (file == NULL)
2198 file = request->hr_file;
2199 else
2200 file_alloc = 1;
2201
2202 #ifndef NO_USER_SUPPORT
2203 if (request->hr_user != NULL) {
2204 char *user_escaped;
2205
2206 user_escaped = bozo_escape_html(NULL, request->hr_user);
2207 if (user_escaped == NULL)
2208 user_escaped = request->hr_user;
2209 /* expand username to ~user/ */
2210 bozoasprintf(httpd, &user, "~%s/", user_escaped);
2211 if (user_escaped != request->hr_user)
2212 free(user_escaped);
2213 }
2214 #endif /* !NO_USER_SUPPORT */
2215
2216 size = snprintf(httpd->errorbuf, BUFSIZ,
2217 "<html><head><title>%s</title></head>\n"
2218 "<body><h1>%s</h1>\n"
2219 "%s%s: <pre>%s</pre>\n"
2220 "<hr><address><a href=\"//%s%s/\">%s%s</a></address>\n"
2221 "</body></html>\n",
2222 header, header,
2223 user ? user : "", file,
2224 reason, hostname, portbuf, hostname, portbuf);
2225 free(user);
2226 if (size >= (int)BUFSIZ) {
2227 bozowarn(httpd,
2228 "bozo_http_error buffer too small, truncated");
2229 size = (int)BUFSIZ;
2230 }
2231
2232 if (file_alloc)
2233 free(file);
2234 } else
2235 size = 0;
2236
2237 bozo_printf(httpd, "%s %s\r\n", proto, header);
2238
2239 if (request) {
2240 bozo_auth_check_401(request, code);
2241 SIMPLEQ_FOREACH(hdr, &request->hr_replheaders, h_next) {
2242 bozo_printf(httpd, "%s: %s\r\n", hdr->h_header,
2243 hdr->h_value);
2244 }
2245 }
2246
2247 bozo_printf(httpd, "Content-Type: text/html\r\n");
2248 bozo_printf(httpd, "Content-Length: %d\r\n", size);
2249 bozo_printf(httpd, "Server: %s\r\n", httpd->server_software);
2250 if (request && request->hr_allow)
2251 bozo_printf(httpd, "Allow: %s\r\n", request->hr_allow);
2252 /* RFC 7231 (HTTP/1.1) 6.5.7 */
2253 if (code == 408 && request->hr_proto == httpd->consts.http_11)
2254 bozo_printf(httpd, "Connection: close\r\n");
2255 bozo_printf(httpd, "\r\n");
2256 /* According to the RFC 2616 sec. 9.4 HEAD method MUST NOT return a
2257 * message-body in the response */
2258 if (size && request && request->hr_method != HTTP_HEAD)
2259 bozo_printf(httpd, "%s", httpd->errorbuf);
2260 bozo_flush(httpd, stdout);
2261
2262 return code;
2263 }
2264
2265 /* Below are various modified libc functions */
2266
2267 /*
2268 * returns -1 in lenp if the string ran out before finding a delimiter,
2269 * but is otherwise the same as strsep. Note that the length must be
2270 * correctly passed in.
2271 */
2272 char *
2273 bozostrnsep(char **strp, const char *delim, ssize_t *lenp)
2274 {
2275 char *s;
2276 const char *spanp;
2277 int c, sc;
2278 char *tok;
2279
2280 if ((s = *strp) == NULL)
2281 return (NULL);
2282 for (tok = s;;) {
2283 if (lenp && --(*lenp) == -1)
2284 return (NULL);
2285 c = *s++;
2286 spanp = delim;
2287 do {
2288 if ((sc = *spanp++) == c) {
2289 if (c == 0)
2290 s = NULL;
2291 else
2292 s[-1] = '\0';
2293 *strp = s;
2294 return (tok);
2295 }
2296 } while (sc != 0);
2297 }
2298 /* NOTREACHED */
2299 }
2300
2301 /*
2302 * inspired by fgetln(3), but works for fd's. should work identically
2303 * except it, however, does *not* return the newline, and it does nul
2304 * terminate the string.
2305 */
2306 char *
2307 bozodgetln(bozohttpd_t *httpd, int fd, ssize_t *lenp,
2308 ssize_t (*readfn)(bozohttpd_t *, int, void *, size_t))
2309 {
2310 ssize_t len;
2311 int got_cr = 0;
2312 char c, *nbuffer;
2313
2314 /* initialise */
2315 if (httpd->getln_buflen == 0) {
2316 /* should be plenty for most requests */
2317 httpd->getln_buflen = 128;
2318 httpd->getln_buffer = malloc((size_t)httpd->getln_buflen);
2319 if (httpd->getln_buffer == NULL) {
2320 httpd->getln_buflen = 0;
2321 return NULL;
2322 }
2323 }
2324 len = 0;
2325
2326 /*
2327 * we *have* to read one byte at a time, to not break cgi
2328 * programs (for we pass stdin off to them). could fix this
2329 * by becoming a fd-passing program instead of just exec'ing
2330 * the program
2331 *
2332 * the above is no longer true, we are the fd-passing
2333 * program already.
2334 */
2335 for (; readfn(httpd, fd, &c, 1) == 1; ) {
2336 debug((httpd, DEBUG_EXPLODING, "bozodgetln read %c", c));
2337
2338 if (len >= httpd->getln_buflen - 1) {
2339 httpd->getln_buflen *= 2;
2340 debug((httpd, DEBUG_EXPLODING, "bozodgetln: "
2341 "reallocating buffer to buflen %zu",
2342 httpd->getln_buflen));
2343 nbuffer = bozorealloc(httpd, httpd->getln_buffer,
2344 (size_t)httpd->getln_buflen);
2345 httpd->getln_buffer = nbuffer;
2346 }
2347
2348 httpd->getln_buffer[len++] = c;
2349 if (c == '\r') {
2350 got_cr = 1;
2351 continue;
2352 } else if (c == '\n') {
2353 /*
2354 * HTTP/1.1 spec says to ignore CR and treat
2355 * LF as the real line terminator. even though
2356 * the same spec defines CRLF as the line
2357 * terminator, it is recommended in section 19.3
2358 * to do the LF trick for tolerance.
2359 */
2360 if (got_cr)
2361 len -= 2;
2362 else
2363 len -= 1;
2364 break;
2365 }
2366
2367 }
2368 httpd->getln_buffer[len] = '\0';
2369 debug((httpd, DEBUG_OBESE, "bozodgetln returns: '%s' with len %zd",
2370 httpd->getln_buffer, len));
2371 *lenp = len;
2372 return httpd->getln_buffer;
2373 }
2374
2375 void *
2376 bozorealloc(bozohttpd_t *httpd, void *ptr, size_t size)
2377 {
2378 void *p;
2379
2380 p = realloc(ptr, size);
2381 if (p)
2382 return p;
2383
2384 bozo_http_error(httpd, 500, NULL, "memory allocation failure");
2385 exit(EXIT_FAILURE);
2386 }
2387
2388 void *
2389 bozomalloc(bozohttpd_t *httpd, size_t size)
2390 {
2391 void *p;
2392
2393 p = malloc(size);
2394 if (p)
2395 return p;
2396
2397 bozo_http_error(httpd, 500, NULL, "memory allocation failure");
2398 exit(EXIT_FAILURE);
2399 }
2400
2401 char *
2402 bozostrdup(bozohttpd_t *httpd, bozo_httpreq_t *request, const char *str)
2403 {
2404 char *p;
2405
2406 p = strdup(str);
2407 if (p)
2408 return p;
2409
2410 if (!request)
2411 bozoerr(httpd, EXIT_FAILURE, "strdup");
2412
2413 bozo_http_error(httpd, 500, request, "memory allocation failure");
2414 exit(EXIT_FAILURE);
2415 }
2416
2417 /* set default values in bozohttpd_t struct */
2418 int
2419 bozo_init_httpd(bozohttpd_t *httpd)
2420 {
2421 /* make sure everything is clean */
2422 (void) memset(httpd, 0x0, sizeof(*httpd));
2423
2424 /* constants */
2425 httpd->consts.http_09 = "HTTP/0.9";
2426 httpd->consts.http_10 = "HTTP/1.0";
2427 httpd->consts.http_11 = "HTTP/1.1";
2428 httpd->consts.text_plain = "text/plain";
2429
2430 /* mmap region size */
2431 httpd->mmapsz = BOZO_MMAPSZ;
2432
2433 /* error buffer for bozo_http_error() */
2434 if ((httpd->errorbuf = malloc(BUFSIZ)) == NULL) {
2435 fprintf(stderr,
2436 "bozohttpd: memory_allocation failure\n");
2437 return 0;
2438 }
2439 #ifndef NO_LUA_SUPPORT
2440 SIMPLEQ_INIT(&httpd->lua_states);
2441 #endif
2442 return 1;
2443 }
2444
2445 /* set default values in bozoprefs_t struct */
2446 int
2447 bozo_init_prefs(bozohttpd_t *httpd, bozoprefs_t *prefs)
2448 {
2449 int rv = 0;
2450
2451 /* make sure everything is clean */
2452 (void) memset(prefs, 0x0, sizeof(*prefs));
2453
2454 /* set up default values */
2455 if (!bozo_set_pref(httpd, prefs, "server software", SERVER_SOFTWARE))
2456 rv = 1;
2457 if (!bozo_set_pref(httpd, prefs, "index.html", INDEX_HTML))
2458 rv = 1;
2459 if (!bozo_set_pref(httpd, prefs, "public_html", PUBLIC_HTML))
2460 rv = 1;
2461 if (!bozo_set_pref(httpd, prefs, "initial timeout", INITIAL_TIMEOUT))
2462 rv = 1;
2463 if (!bozo_set_pref(httpd, prefs, "header timeout", HEADER_WAIT_TIME))
2464 rv = 1;
2465 if (!bozo_set_pref(httpd, prefs, "request timeout", TOTAL_MAX_REQ_TIME))
2466 rv = 1;
2467
2468 return rv;
2469 }
2470
2471 /* set default values */
2472 int
2473 bozo_set_defaults(bozohttpd_t *httpd, bozoprefs_t *prefs)
2474 {
2475 return bozo_init_httpd(httpd) && bozo_init_prefs(httpd, prefs);
2476 }
2477
2478 /* set the virtual host name, port and root */
2479 int
2480 bozo_setup(bozohttpd_t *httpd, bozoprefs_t *prefs, const char *vhost,
2481 const char *root)
2482 {
2483 struct passwd *pw;
2484 extern char **environ;
2485 static char *cleanenv[1] = { NULL };
2486 uid_t uid;
2487 int uidset = 0;
2488 char *chrootdir;
2489 char *username;
2490 char *portnum;
2491 char *cp;
2492 int dirtyenv;
2493
2494 dirtyenv = 0;
2495
2496 if (vhost == NULL) {
2497 httpd->virthostname = bozomalloc(httpd, MAXHOSTNAMELEN+1);
2498 if (gethostname(httpd->virthostname, MAXHOSTNAMELEN+1) < 0)
2499 bozoerr(httpd, 1, "gethostname");
2500 httpd->virthostname[MAXHOSTNAMELEN] = '\0';
2501 } else {
2502 httpd->virthostname = bozostrdup(httpd, NULL, vhost);
2503 }
2504 httpd->slashdir = bozostrdup(httpd, NULL, root);
2505 if ((portnum = bozo_get_pref(prefs, "port number")) != NULL) {
2506 httpd->bindport = bozostrdup(httpd, NULL, portnum);
2507 }
2508
2509 /* go over preferences now */
2510 if ((cp = bozo_get_pref(prefs, "numeric")) != NULL &&
2511 strcmp(cp, "true") == 0) {
2512 httpd->numeric = 1;
2513 }
2514 if ((cp = bozo_get_pref(prefs, "log to stderr")) != NULL &&
2515 strcmp(cp, "true") == 0) {
2516 httpd->logstderr = 1;
2517 }
2518 if ((cp = bozo_get_pref(prefs, "bind address")) != NULL) {
2519 httpd->bindaddress = bozostrdup(httpd, NULL, cp);
2520 }
2521 if ((cp = bozo_get_pref(prefs, "background")) != NULL) {
2522 httpd->background = atoi(cp);
2523 }
2524 if ((cp = bozo_get_pref(prefs, "foreground")) != NULL &&
2525 strcmp(cp, "true") == 0) {
2526 httpd->foreground = 1;
2527 }
2528 if ((cp = bozo_get_pref(prefs, "pid file")) != NULL) {
2529 httpd->pidfile = bozostrdup(httpd, NULL, cp);
2530 }
2531 if ((cp = bozo_get_pref(prefs, "unknown slash")) != NULL &&
2532 strcmp(cp, "true") == 0) {
2533 httpd->unknown_slash = 1;
2534 }
2535 if ((cp = bozo_get_pref(prefs, "virtual base")) != NULL) {
2536 httpd->virtbase = bozostrdup(httpd, NULL, cp);
2537 }
2538 if ((cp = bozo_get_pref(prefs, "enable users")) != NULL &&
2539 strcmp(cp, "true") == 0) {
2540 httpd->enable_users = 1;
2541 }
2542 if ((cp = bozo_get_pref(prefs, "enable user cgibin")) != NULL &&
2543 strcmp(cp, "true") == 0) {
2544 httpd->enable_cgi_users = 1;
2545 }
2546 if ((cp = bozo_get_pref(prefs, "dirty environment")) != NULL &&
2547 strcmp(cp, "true") == 0) {
2548 dirtyenv = 1;
2549 }
2550 if ((cp = bozo_get_pref(prefs, "hide dots")) != NULL &&
2551 strcmp(cp, "true") == 0) {
2552 httpd->hide_dots = 1;
2553 }
2554 if ((cp = bozo_get_pref(prefs, "directory indexing")) != NULL &&
2555 strcmp(cp, "true") == 0) {
2556 httpd->dir_indexing = 1;
2557 }
2558 if ((cp = bozo_get_pref(prefs, "public_html")) != NULL) {
2559 httpd->public_html = bozostrdup(httpd, NULL, cp);
2560 }
2561 if ((cp = bozo_get_pref(prefs, "initial timeout")) != NULL) {
2562 httpd->initial_timeout = atoi(cp);
2563 }
2564 if ((cp = bozo_get_pref(prefs, "header timeout")) != NULL) {
2565 httpd->header_timeout = atoi(cp);
2566 }
2567 if ((cp = bozo_get_pref(prefs, "request timeout")) != NULL) {
2568 httpd->request_timeout = atoi(cp);
2569 }
2570 httpd->server_software =
2571 bozostrdup(httpd, NULL, bozo_get_pref(prefs, "server software"));
2572 httpd->index_html =
2573 bozostrdup(httpd, NULL, bozo_get_pref(prefs, "index.html"));
2574
2575 /*
2576 * initialise ssl and daemon mode if necessary.
2577 */
2578 bozo_ssl_init(httpd);
2579 bozo_daemon_init(httpd);
2580
2581 username = bozo_get_pref(prefs, "username");
2582 if (username != NULL) {
2583 if ((pw = getpwnam(username)) == NULL)
2584 bozoerr(httpd, 1, "getpwnam(%s): %s", username,
2585 strerror(errno));
2586 if (initgroups(pw->pw_name, pw->pw_gid) == -1)
2587 bozoerr(httpd, 1, "initgroups: %s", strerror(errno));
2588 if (setgid(pw->pw_gid) == -1)
2589 bozoerr(httpd, 1, "setgid(%u): %s", pw->pw_gid,
2590 strerror(errno));
2591 uid = pw->pw_uid;
2592 uidset = 1;
2593 }
2594 /*
2595 * handle chroot.
2596 */
2597 if ((chrootdir = bozo_get_pref(prefs, "chroot dir")) != NULL) {
2598 httpd->rootdir = bozostrdup(httpd, NULL, chrootdir);
2599 if (chdir(httpd->rootdir) == -1)
2600 bozoerr(httpd, 1, "chdir(%s): %s", httpd->rootdir,
2601 strerror(errno));
2602 if (chroot(httpd->rootdir) == -1)
2603 bozoerr(httpd, 1, "chroot(%s): %s", httpd->rootdir,
2604 strerror(errno));
2605 }
2606
2607 if (uidset && setuid(uid) == -1)
2608 bozoerr(httpd, 1, "setuid(%d): %s", uid, strerror(errno));
2609
2610 /*
2611 * prevent info leakage between different compartments.
2612 * some PATH values in the environment would be invalided
2613 * by chroot. cross-user settings might result in undesirable
2614 * effects.
2615 */
2616 if ((chrootdir != NULL || username != NULL) && !dirtyenv)
2617 environ = cleanenv;
2618
2619 #ifdef _SC_PAGESIZE
2620 httpd->page_size = (long)sysconf(_SC_PAGESIZE);
2621 #else
2622 httpd->page_size = 4096;
2623 #endif
2624 debug((httpd, DEBUG_OBESE, "myname is %s, slashdir is %s",
2625 httpd->virthostname, httpd->slashdir));
2626
2627 return 1;
2628 }
2629
2630 int
2631 bozo_get_version(char *buf, size_t size)
2632 {
2633 return snprintf(buf, size, "%s", SERVER_SOFTWARE);
2634 }
2635