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