musvaage
·
2024-05-01
eris.c
1/*
2 * simple httpd to be started from tcpserver
3 */
4#define _FILE_OFFSET_BITS 64
5#include <stdlib.h>
6#include <unistd.h>
7#include <ctype.h>
8#include <sys/types.h>
9#include <sys/stat.h>
10#include <fcntl.h>
11#include <time.h>
12#include <stdio.h>
13#include <string.h>
14#include <signal.h>
15#include <sys/wait.h>
16#include <grp.h>
17#include <errno.h>
18#include <sys/select.h>
19#include <sys/socket.h>
20#include <netinet/in.h>
21#include <netinet/tcp.h>
22#include <dirent.h>
23#include <limits.h>
24
25#include "strings.h"
26#include "mime.h"
27#include "timerfc.h"
28#include "version.h"
29
30#ifdef __linux__
31#include <sys/sendfile.h>
32#else
33#define sendfile(a, b, c, d) -1
34#endif
35
36#ifndef min
37#define min(a,b) ((a)<(b)?(a):(b))
38#endif
39
40/*
41 * Some things I use for debugging
42 */
43#define DUMPf(fmt, args...) fprintf(stderr, "%s:%s:%d " fmt "\n", __FILE__, __FUNCTION__, __LINE__, ##args)
44#define DUMP() DUMPf("")
45#define DUMP_d(v) DUMPf("%s = %d", #v, v)
46#define DUMP_u(v) DUMPf("%s = %u", #v, v)
47#define DUMP_x(v) DUMPf("%s = 0x%x", #v, v)
48#define DUMP_s(v) DUMPf("%s = %s", #v, v)
49#define DUMP_c(v) DUMPf("%s = %c", #v, v)
50#define DUMP_p(v) DUMPf("%s = %p", #v, v)
51#define DUMP_buf(v, l) DUMPf("%s = %.*s", #v, (int)(l), v)
52
53/*
54 * Wait this long (seconds) for a valid HTTP request
55 */
56#define READTIMEOUT 2
57
58/*
59 * Wait this long (seconds) for a non-file send to complete
60 */
61#define WRITETIMEOUT 10
62
63/*
64 * Quit if we can't write at least this many bytes per second
65 */
66#define MIN_WRITE_RATE 2560
67
68/*
69 * Wait this long for CGI to complete
70 */
71#define CGI_TIMEOUT (5*60)
72
73/*
74 * How long each sendfile call can take
75 */
76#define SENDFILE_TIMEOUT ((int)(SIZE_MAX / MIN_WRITE_RATE))
77
78/*
79 * Maximum size of a request header (the whole block)
80 */
81#define MAXHEADERLEN 8192
82
83/*
84 * Maximum size of a request line
85 */
86#define MAXREQUESTLEN 2048
87
88/*
89 * Maximum number of header fields
90 */
91#define MAXHEADERFIELDS 60
92
93#define BUFFER_SIZE 8192
94
95/*
96 * Options
97 */
98int doauth = 0;
99int docgi = 0;
100int doidx = 0;
101int nochdir = 0;
102int redirect = 0;
103int portappend = 0;
104char *connector = NULL;
105
106
107/*
108 * Variables that persist between requests
109 */
110int cwd;
111int keepalive = 0;
112char *remote_addr = NULL;
113char *remote_ident = NULL;
114
115/*
116 * Things that are really super convenient to have globally.
117 * These could be put into a struct for a threading version
118 * of eris.
119 */
120enum { GET, POST, HEAD, CONNECT } method;
121char *host;
122char *user_agent;
123char *refer;
124char *path;
125int http_version;
126char *content_type;
127size_t content_length;
128off_t range_start, range_end;
129time_t ims;
130
131
132#define BUFFER_SIZE 8192
133
134
135/** Log a request */
136void
137dolog(int code, off_t len)
138{ /* write a log line to stderr */
139 sanitize(host);
140 sanitize(user_agent);
141 sanitize(refer);
142
143 fprintf(stderr, "%s %d %lu %s %s %s %s\n", remote_addr, code, (unsigned long) len, host, user_agent, refer, path);
144}
145
146void
147header(unsigned int code, const char *httpcomment)
148{
149 printf("HTTP/1.%d %u %s\r\n", http_version, code, httpcomment);
150 printf("Server: %s\r\n", FNORD);
151 printf("Connection: %s\r\n", keepalive ? "keep-alive" : "close");
152
153}
154
155void
156eoh()
157{
158 printf("\r\n");
159}
160
161/*
162 * output an error message and exit
163 */
164void
165badrequest(long code, const char *httpcomment, const char *message)
166{
167 size_t msglen = 0;
168
169 keepalive = 0;
170 header(code, httpcomment);
171 if (message) {
172 msglen = (strlen(message) * 2) + 15;
173
174 printf("Content-Length: %lu\r\nContent-Type: text/html\r\n\r\n", (unsigned long) msglen);
175 printf("<title>%s</title>%s", message, message);
176 }
177 printf("\r\n");
178 fflush(stdout);
179 dolog(code, msglen);
180
181 exit(0);
182}
183
184void
185env(const char *k, const char *v)
186{
187 if (v) {
188 setenv(k, v, 1);
189 }
190}
191
192
193void
194not_found()
195{
196 char msg[] = "The requested URL does not exist here.";
197
198 header(404, "Not Found");
199 printf("Content-Type: text/html\r\n");
200 printf("Content-Length: %lu\r\n", (unsigned long) sizeof msg);
201 printf("\r\n");
202 printf("%s\n", msg); /* sizeof msg includes the NULL */
203 dolog(404, sizeof msg);
204 fflush(stdout);
205}
206
207char *
208proto_getenv(char *proto, char *name)
209{
210 char buf[80];
211
212 snprintf(buf, sizeof buf, "%s%s", proto, name);
213 return getenv(buf);
214}
215
216void
217get_ucspi_env()
218{
219 char *ucspi = getenv("PROTO");
220 char *ip = NULL;
221 char *port = NULL;
222
223 if (ucspi) {
224 char *p;
225
226 /*
227 * Busybox, as usual, has the right idea
228 */
229 if ((p = proto_getenv(ucspi, "REMOTEADDR"))) {
230 remote_addr = strdup(p);
231 } else {
232 ip = proto_getenv(ucspi, "REMOTEIP");
233 port = proto_getenv(ucspi, "REMOTEPORT");
234 }
235
236 if ((p = proto_getenv(ucspi, "REMOTEINFO"))) {
237 remote_ident = strdup(p);
238 }
239 }
240
241 if (!ip) {
242 // stunnel
243 ip = getenv("REMOTE_HOST");
244 port = getenv("REMOTE_PORT");
245 }
246
247 if (ip) {
248 char buf[80];
249
250 snprintf(buf, sizeof buf, "%s:%s", ip, port);
251 remote_addr = strdup(buf);
252 }
253}
254
255void
256parse_options(int argc, char *argv[])
257{
258 int opt;
259
260 while (-1 != (opt = getopt(argc, argv, "acdhkpro:v."))) {
261 switch (opt) {
262 case 'a':
263 doauth = 1;
264 break;
265 case 'c':
266 docgi = 1;
267 break;
268 case 'd':
269 doidx = 1;
270 break;
271 case '.':
272 nochdir = 1;
273 break;
274 case 'p':
275 portappend = 1;
276 break;
277 case 'r':
278 redirect = 1;
279 break;
280 case 'o':
281 connector = optarg;
282 break;
283 case 'v':
284 printf("%s\n", FNORD);
285 exit(0);
286 case 'h':
287 default:
288 fprintf(stderr, "Usage: %s [OPTIONS]\n", argv[0]);
289 fprintf(stderr, "\n");
290 fprintf(stderr, "-a Enable authentication\n");
291 fprintf(stderr, "-c Enable CGI\n");
292 fprintf(stderr, "-d Enable directory listing\n");
293 fprintf(stderr, "-. Serve out of ./ (no vhosting)\n");
294 fprintf(stderr, "-h Print this message and exit\n");
295 fprintf(stderr, "-p Append port to hostname directory\n");
296 fprintf(stderr, "-r Enable symlink redirection\n");
297 fprintf(stderr, "-o HANDLER Path to HTTP CONNECT handler\n");
298 fprintf(stderr, "-v Print version and exit\n");
299 exit(69);
300 }
301 }
302}
303
304/*
305 * CGI stuff
306 */
307static void
308sigchld(int sig)
309{
310 while (waitpid(0, NULL, WNOHANG) > 0);
311}
312
313static void
314sigalarm_cgi(int sig)
315{
316 /*
317 * send this out regardless of whether we've already sent a header, to maybe help with debugging
318 */
319 badrequest(504, "Gateway Timeout", "The CGI is being too slow.");
320}
321
322static void
323cgi_child(const char *relpath)
324{
325 env("GATEWAY_INTERFACE", "CGI/1.1");
326 env("SERVER_SOFTWARE", FNORD);
327 env("REQUEST_URI", path);
328 env("SERVER_NAME", host);
329 env("SCRIPT_NAME", relpath);
330 env("REMOTE_ADDR", remote_addr);
331 env("REMOTE_IDENT", remote_ident);
332 if (content_length) {
333 char cl[20];
334
335 snprintf(cl, sizeof cl, "%llu", (unsigned long long) content_length);
336 env("CONTENT_LENGTH", cl);
337 env("CONTENT_TYPE", content_type);
338 }
339
340 /*
341 * Try to change to CGI's directory
342 */
343 {
344 char *delim = strrchr(relpath, '/');
345
346 if (delim) {
347 *delim = '\0';
348 if (0 == chdir(relpath)) {
349 relpath = delim + 1;
350 }
351 }
352 }
353
354 execl(relpath, relpath, NULL);
355 exit(1);
356}
357
358void
359cgi_parent(int cin, int cout, int passthru)
360{
361 char cgiheader[BUFFER_SIZE];
362 size_t cgiheaderlen = 0;
363 FILE *cinf = fdopen(cin, "rb");
364 size_t size = 0;
365 int header_sent = 0;
366 int code = 200;
367
368 fcntl(cin, F_SETFL, O_NONBLOCK);
369 signal(SIGCHLD, sigchld);
370 signal(SIGPIPE, SIG_IGN); /* NO! no signal! */
371 signal(SIGALRM, sigalarm_cgi);
372
373 while (1) {
374 int nfds;
375 fd_set rfds, wfds;
376
377 FD_ZERO(&rfds);
378 FD_ZERO(&wfds);
379 FD_SET(cin, &rfds);
380 nfds = cin;
381
382 if (content_length) {
383 /*
384 * have post data
385 */
386 FD_SET(cout, &wfds);
387 if (cout > nfds) {
388 nfds = cout;
389 }
390 } else if (cout >= 0) {
391 close(cout); /* no post data */
392 cout = -1;
393 }
394
395 alarm(CGI_TIMEOUT);
396 if (-1 == select(nfds + 1, &rfds, &wfds, NULL, NULL)) {
397 break;
398 }
399
400 if (FD_ISSET(cin, &rfds)) {
401 if (passthru) {
402 /*
403 * Pass everything through verbatim
404 */
405 size_t len;
406
407 /*
408 * Re-use this big buffer
409 */
410 len = fread(cgiheader, 1, sizeof cgiheader, cinf);
411 if (0 == len) {
412 /*
413 * CGI is done
414 */
415 break;
416 }
417 fwrite(cgiheader, 1, len, stdout);
418
419 /*
420 * Naively assume the CGI knows best about sending stuff
421 */
422 fflush(stdout);
423 size += len;
424 } else {
425 /*
426 * Interpret header fields
427 */
428 size_t readlen = (sizeof cgiheader) - cgiheaderlen;
429
430 if (NULL == fgets(cgiheader + cgiheaderlen, readlen, cinf)) {
431 /*
432 * EOF or error
433 */
434 badrequest(500, "CGI Error", "CGI output too weird");
435 }
436 cgiheaderlen = strlen(cgiheader);
437
438 if ('\n' == cgiheader[cgiheaderlen - 1]) {
439 /*
440 * We read a whole line
441 */
442 size_t len;
443 char *val;
444
445 len = extract_header_field(cgiheader, &val, 0);
446 if (!len) {
447 /*
448 * We've read the entire header block
449 */
450 passthru = 1;
451 eoh();
452 } else {
453 if (!header_sent) {
454 if (!strcasecmp(cgiheader, "Location")) {
455 header(302, "CGI Redirect");
456 printf("%s: %s\r\n\r\n", cgiheader, val);
457 dolog(302, 0);
458 exit(0);
459 } else if (!strcasecmp(cgiheader, "Status")) {
460 char *txt;
461
462 if (val) {
463 code = (int) strtol(val, &txt, 10);
464 } else {
465 code = 0;
466 }
467 if (code < 100) {
468 header(500, "Internal Error");
469 printf("CGI returned Status: %d\n", code);
470 dolog(500, 0);
471 exit(0);
472 }
473 for (; *txt == ' '; txt += 1);
474 header(code, txt);
475 } else {
476 header(200, "OK");
477 printf("Pragma: no-cache\r\n");
478 }
479 header_sent = 1;
480 }
481 printf("%s: %s\r\n", cgiheader, val);
482 cgiheaderlen = 0;
483 }
484 }
485 }
486 } else if (FD_ISSET(cout, &wfds)) {
487 /*
488 * write to cgi the post data
489 */
490 if (content_length) {
491 size_t len;
492 char buf[BUFFER_SIZE];
493 size_t nmemb = min(BUFFER_SIZE, content_length);
494 char *p = buf;
495
496 len = fread(buf, 1, nmemb, stdin);
497 if (len < 1) {
498 break;
499 }
500 content_length -= len;
501
502 while (len > 0) {
503 size_t wlen = write(cout, p, len);
504
505 if (wlen == -1) {
506 break;
507 }
508 len -= wlen;
509 p += wlen;
510 }
511 } else {
512 close(cout);
513 }
514 }
515 }
516
517 fflush(stdout);
518 dolog(code, size);
519}
520
521void
522serve_cgi(char *relpath)
523{
524 int pid;
525 int cin[2];
526 int cout[2];
527
528 if (pipe(cin) || pipe(cout)) {
529 badrequest(500, "Internal Server Error", "Server Resource problem.");
530 }
531
532 pid = fork();
533 if (-1 == pid) {
534 badrequest(500, "Internal Server Error", "Unable to fork.");
535 }
536 if (pid) {
537 close(cin[1]);
538 close(cout[0]);
539
540 /*
541 * Eris is not this smart yet
542 */
543 keepalive = 0;
544
545 cgi_parent(cin[0], cout[1], 0);
546
547 exit(0);
548 } else {
549 close(cwd);
550 close(cout[1]);
551 close(cin[0]);
552
553 dup2(cout[0], 0);
554 dup2(cin[1], 1);
555
556 close(cout[0]);
557 close(cin[1]);
558
559 cgi_child(relpath);
560 }
561}
562
563/*
564 * Main HTTPd
565 */
566
567ssize_t
568fake_sendfile(int out_fd, int in_fd, off_t * offset, size_t count)
569{
570 char buf[BUFFER_SIZE];
571 ssize_t l, m;
572
573 /*
574 * is mmap quicker? does it matter?
575 */
576 if (-1 == lseek(in_fd, *offset, SEEK_SET)) {
577 /*
578 * We're screwed. The most helpful thing we can do now is die.
579 */
580 fprintf(stderr, "Unable to seek. Dying.\n");
581 exit(0);
582 }
583 l = read(in_fd, buf, min(count, sizeof buf));
584 if (-1 == l) {
585 /*
586 * Also screwed.
587 */
588 fprintf(stderr, "Unable to read an open file. Dying.\n");
589 exit(0);
590 }
591 *offset += l;
592
593 while (l) {
594 m = write(out_fd, buf, l);
595 if (-1 == m) {
596 /*
597 * ALSO screwed.
598 */
599 fprintf(stderr, "Unable to write to client: %m (req %s). Dying.\n", path);
600 exit(0);
601 }
602 l -= m;
603 }
604
605 return l;
606}
607
608void
609serve_file(int fd, char *filename, struct stat *st)
610{
611 off_t len, remain;
612
613 if (method == POST) {
614 badrequest(405, "Method Not Supported", "POST is not supported by this URL");
615 }
616
617 if (st->st_mtime <= ims) {
618 header(304, "Not Changed");
619 dolog(304, 0);
620 eoh();
621 return;
622 }
623
624 header(200, "OK");
625 printf("Content-Type: %s\r\n", getmimetype(filename));
626
627 if ((range_end == 0) || (range_end > st->st_size)) {
628 range_end = st->st_size;
629 }
630 len = range_end - range_start;
631 printf("Content-Length: %llu\r\n", (unsigned long long) len);
632
633 {
634 struct tm *tp;
635 char buf[40];
636
637 tp = gmtime(&(st->st_mtime));
638
639 strftime(buf, sizeof buf, "%a, %d %b %Y %H:%M:%S GMT", tp);
640 printf("Last-Modified: %s\r\n", buf);
641 }
642
643 eoh();
644 fflush(stdout);
645
646 if (method == HEAD) {
647 return;
648 }
649
650 for (remain = len; remain;) {
651 size_t count = min(remain, SIZE_MAX);
652 ssize_t sent;
653
654 alarm(SENDFILE_TIMEOUT);
655 sent = sendfile(1, fd, &range_start, count);
656 if (-1 == sent) {
657 sent = fake_sendfile(1, fd, &range_start, count);
658 }
659 remain -= sent;
660 }
661
662 dolog(200, len);
663}
664
665void
666serve_idx(int fd, char *path)
667{
668 DIR *d = fdopendir(fd);
669 struct dirent *de;
670
671 if (method == POST) {
672 badrequest(405, "Method Not Supported", "POST is not supported by this URL");
673 }
674
675 keepalive = 0;
676 header(200, "OK");
677 printf("Content-Type: text/html\r\n");
678 eoh();
679
680 printf("<!DOCTYPE html>\r<html><head><title>");
681 html_esc(stdout, path);
682 printf("</title></head><body><h1>Directory Listing: ");
683 html_esc(stdout, path);
684 printf("</h1><pre>\n");
685 if (path[1]) {
686 printf("<a href=\"../\">Parent Directory</a>\n");
687 }
688
689 while ((de = readdir(d))) {
690 char *name = de->d_name;
691 char symlink[PATH_MAX];
692 struct stat st;
693
694 if (name[0] == '.') {
695 continue; /* hidden files -> skip */
696 }
697 if (lstat(name, &st)) {
698 continue; /* can't stat -> skip */
699 }
700
701 if (S_ISDIR(st.st_mode)) {
702 printf("[DIR] ");
703 } else if (S_ISLNK(st.st_mode)) {
704 ssize_t len = readlink(de->d_name, symlink, (sizeof symlink) - 1);
705
706 if (len < 1) {
707 continue;
708 }
709 name = symlink;
710 printf("[LNK] "); /* symlink */
711 } else if (S_ISREG(st.st_mode)) {
712 printf("%10llu", (unsigned long long) st.st_size);
713 } else {
714 continue; /* not a file we can provide -> skip */
715 }
716
717 /*
718 * write a href
719 */
720 printf(" <a href=\"");
721 url_esc(stdout, name);
722 if (S_ISDIR(st.st_mode)) {
723 printf("/");
724 }
725 printf("\">");
726 url_esc(stdout, name);
727 printf("</a>\n");
728 }
729 printf("</pre></body></html>");
730
731 dolog(200, 0);
732}
733
734void
735find_serve_file(char *relpath)
736{
737 int fd;
738 struct stat st;
739
740 /*
741 * Open fspath. If that worked,
742 */
743 if ((fd = open(relpath, O_RDONLY)) > -1) {
744 fstat(fd, &st);
745 /*
746 * If it is a directory,
747 */
748 if (S_ISDIR(st.st_mode)) {
749 char path2[PATH_MAX];
750 int fd2;
751
752 /*
753 * Redirect if it doesn't end with /
754 */
755 if (!endswith(path, "/")) {
756 header(301, "Redirect");
757 printf("Location: %s/\r\n", path);
758 eoh();
759 return;
760 }
761
762 /*
763 * Open relpath + "index.html". If that worked,
764 */
765 snprintf(path2, sizeof path2, "%sindex.html", relpath);
766 if ((fd2 = open(path2, O_RDONLY)) > -1) {
767 /*
768 * serve that file and return.
769 */
770 fstat(fd2, &st);
771 serve_file(fd2, path2, &st);
772 close(fd2);
773 close(fd);
774 return;
775 } else {
776 if (docgi) {
777 snprintf(path2, sizeof path2, "%sindex.cgi", relpath);
778 if (!stat(path2, &st)) {
779 return serve_cgi(path2);
780 }
781 }
782 if (doidx) {
783 serve_idx(fd, relpath + 1);
784 close(fd);
785 return;
786 }
787 return not_found();
788 }
789 } else {
790 if (docgi && endswith(relpath, ".cgi")) {
791 close(fd);
792 return serve_cgi(relpath);
793 }
794 serve_file(fd, relpath, &st);
795 }
796 } else {
797 if (docgi && (errno == ENOTDIR)) {
798 char *p;
799
800 if ((p = strstr(relpath, ".cgi"))) {
801 p += 4;
802 env("PATH_INFO", p);
803 *p = 0;
804 if (!stat(relpath, &st)) {
805 close(fd);
806 return serve_cgi(relpath);
807 }
808 }
809 }
810 return not_found();
811 }
812}
813
814void
815handle_request()
816{
817 char request[MAXREQUESTLEN];
818 char fspath[PATH_MAX];
819 char buf[MAXHEADERLEN];
820 char *p;
821
822 /*
823 * Initialize globals
824 */
825 host = NULL;
826 user_agent = NULL;
827 refer = NULL;
828 path = NULL;
829 range_start = 0;
830 range_end = 0;
831 content_type = NULL;
832 content_length = 0;
833 ims = 0;
834
835 alarm(READTIMEOUT);
836
837 /*
838 * Read request line first
839 */
840 request[0] = 0;
841 if (NULL == fgets(request, sizeof request, stdin)) {
842 /*
843 * They must have hung up!
844 */
845 exit(0);
846 }
847 if (!strncmp(request, "GET /", 5)) {
848 method = GET;
849 p = request + 4;
850 } else if (!strncmp(request, "POST /", 6)) {
851 method = POST;
852 p = request + 5;
853 } else if (!strncmp(request, "HEAD /", 6)) {
854 method = HEAD;
855 p = request + 5;
856 } else if (connector && !strncmp(request, "CONNECT ", 8)) {
857 method = CONNECT;
858 p = request + 8;
859 } else {
860 /*
861 * This also handles the case where fgets does nothing
862 */
863 badrequest(405, "Method Not Allowed", "Unsupported HTTP method.");
864 }
865
866 if (docgi) {
867 p[-1] = 0;
868 env("REQUEST_METHOD", request);
869 }
870
871 /*
872 * Interpret path into fspath.
873 */
874 path = p;
875 {
876 char *fsp = fspath;
877 char *query_string = NULL;
878
879 *(fsp++) = '.';
880 for (; *p != ' '; p += 1) {
881 char c = *p;
882
883 switch (c) {
884 case 0:
885 badrequest(413, "Request Entity Too Large", "The HTTP request was too long");
886 case '\n':
887 badrequest(505, "Version Not Supported", "HTTP/0.9 not supported");
888 case '?':
889 query_string = p + 1;
890 break;
891 case '%':
892 if ((!query_string) && p[1] && p[2]) {
893 int a = fromhex(p[1]);
894 int b = fromhex(p[2]);
895
896 if ((a >= 0) && (b >= 0)) {
897 c = (a << 4) | b;
898 p += 2;
899 }
900 }
901 break;
902 }
903
904 if ((!query_string) && (fsp - fspath + 1 < sizeof fspath)) {
905 *(fsp++) = c;
906 }
907 }
908 *fsp = 0;
909
910 /*
911 * Change "/." to "/:" to keep "hidden" files such and prevent directory traversal
912 */
913 while ((fsp = strstr(fspath, "/."))) {
914 *(fsp + 1) = ':';
915 }
916
917
918 *(p++) = 0; /* NULL-terminate path */
919
920 if (docgi && query_string) {
921 env("QUERY_STRING", query_string);
922 }
923 }
924
925 http_version = -1;
926 if (!strncmp(p, "HTTP/1.", 7) && p[8] && ((p[8] == '\r') || (p[8] == '\n'))) {
927 http_version = p[7] - '0';
928 }
929 if (!((http_version == 0) || (http_version == 1))) {
930 http_version = 0;
931 badrequest(505, "Version Not Supported", "HTTP version not supported");
932 }
933 if (http_version == 1) {
934 keepalive = 1;
935 } else {
936 keepalive = 0;
937 }
938 if (docgi) {
939 p[8] = 0;
940 env("SERVER_PROTOCOL", p);
941 }
942
943 /*
944 * Read header fields
945 */
946 {
947 char *base = buf;
948 char *lastchar = base + (sizeof buf) - 2;
949 int nheaders = 0;
950
951 *lastchar = 0;
952 while (1) {
953 char *cgi_name = base;
954 char *p;
955 int plen = (sizeof buf) - (base - buf);
956 char *name, *val;
957 size_t len;
958
959 /*
960 * 40 is totally arbitrary here.
961 */
962 if (plen < 40) {
963 badrequest(431, "Request Header Too Large", "The HTTP header block was too large");
964 }
965 if (nheaders++ >= MAXHEADERFIELDS) {
966 badrequest(431, "Request Header Too Large", "Too many HTTP Headers");
967 }
968 strcpy(cgi_name, "HTTP_");
969 plen -= 5;
970 p = cgi_name + 5;
971
972 if (NULL == fgets(p, plen, stdin)) {
973 badrequest(500, "OS Error", "OS error reading headers");
974 }
975 if (*lastchar) {
976 badrequest(431, "Request Header Too Large", "An HTTP header field was too large");
977 }
978
979 len = extract_header_field(p, &val, 1);
980 if (!len) {
981 /*
982 * blank line
983 */
984 break;
985 }
986 if (!val) {
987 badrequest(400, "Invalid header", "Unable to parse header block");
988 }
989
990 name = p;
991
992 /*
993 * Set up CGI environment variables
994 */
995 if (docgi) {
996 env(cgi_name, val);
997 }
998
999 /*
1000 * By default, re-use buffer space
1001 */
1002 base = cgi_name;
1003
1004 /*
1005 * Handle special header fields
1006 */
1007 if (!strcmp(name, "HOST")) {
1008 host = val;
1009 base = name + len + 1;
1010 } else if (!strcmp(name, "USER_AGENT")) {
1011 user_agent = val;
1012 base = name + len + 1;
1013 } else if (!strcmp(name, "REFERER")) {
1014 refer = val;
1015 base = name + len + 1;
1016 } else if (!strcmp(name, "CONTENT_TYPE")) {
1017 content_type = val;
1018 base = name + len + 1;
1019 } else if (!strcmp(name, "CONTENT_LENGTH")) {
1020 content_length = (size_t) strtoull(val, NULL, 10);
1021 } else if (!strcmp(name, "CONNECTION")) {
1022 if (!strcasecmp(val, "keep-alive")) {
1023 keepalive = 1;
1024 } else {
1025 keepalive = 0;
1026 }
1027 } else if (!strcmp(name, "IF_MODIFIED_SINCE")) {
1028 ims = timerfc(val);
1029 } else if (!strcmp(name, "RANGE")) {
1030 /*
1031 * Range: bytes=17-23
1032 */
1033 /*
1034 * Range: bytes=23-
1035 */
1036 if (!strncmp(val, "bytes=", 6)) {
1037 p = val + 6;
1038 range_start = (off_t) strtoull(p, &p, 10);
1039 if (*p == '-') {
1040 range_end = (off_t) strtoull(p + 1, NULL, 10);
1041 } else {
1042 range_end = 0;
1043 }
1044 }
1045 }
1046 }
1047 }
1048
1049 /*
1050 * Try to change into the appropriate directory
1051 */
1052 if (!nochdir) {
1053 char fn[PATH_MAX];
1054
1055 if (host) {
1056 snprintf(fn, sizeof(fn), "%s", host);
1057 } else {
1058 fn[0] = 0;
1059 }
1060 if (fn[0] == '.') {
1061 fn[0] = ':';
1062 }
1063 for (p = fn; *p; p += 1) {
1064 switch (*p) {
1065 case '/':
1066 *p = ':';
1067 break;
1068 case ':':
1069 *p = 0;
1070 break;
1071 case 'A' ... 'Z':
1072 *p ^= ' ';
1073 break;
1074 }
1075 }
1076
1077 if ((-1 == chdir(fn)) && (-1 == chdir("default"))) {
1078 badrequest(404, "Not Found", "This host is not served here");
1079 }
1080 }
1081
1082 if (method == CONNECT) {
1083 execl(connector, connector, path, NULL);
1084 badrequest(500, "Unable to exec connector", strerror(errno));
1085 }
1086
1087 /*
1088 * Serve the file
1089 */
1090 alarm(WRITETIMEOUT);
1091 find_serve_file(fspath);
1092 fflush(stdout);
1093
1094 return;
1095}
1096
1097int
1098main(int argc, char *argv[], const char *const *envp)
1099{
1100 parse_options(argc, argv);
1101
1102 cwd = open(".", O_RDONLY);
1103
1104 signal(SIGPIPE, SIG_IGN);
1105 get_ucspi_env();
1106
1107 while (1) {
1108 handle_request();
1109 if (!keepalive) {
1110 break;
1111 }
1112 if (-1 == fchdir(cwd)) {
1113 break;
1114 }
1115 }
1116
1117 return 0;
1118}