eris

Very small inetd http server
git clone https://git.woozle.org/neale/eris.git

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}