/* thttpd.c - tiny/turbo/throttling HTTP server
**
** (c) 1995,1998,1999,2000,2001 by Jef Poskanzer <jef@mail.acme.com>.
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
** SUCH DAMAGE.
*/

#include "main.h"
#include "thttpd.h"
#include "version.h"

#include <sys/param.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/uio.h>

#include <errno.h>
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include <pwd.h>
#ifdef HAVE_GRP_H
#include <grp.h>
#endif
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//#include <syslog.h>
#include <time.h>
#include <unistd.h>

#include "fdwatch.h"
#include "libhttpd.h"
//#include "mmc.h"
#include "timers.h"
#include "match.h"

#ifndef SHUT_WR
#define SHUT_WR 1
#endif

#ifndef HAVE_INT64T
typedef long long int64_t;
#endif


static char *argv0;
static bool _debug;
//static int debug;
static unsigned short port;
static char *dir;
static char *data_dir;
static int do_chroot, no_log, no_symlink_check, do_vhost, do_global_passwd;
static char *cgi_pattern;
static int cgi_limit;
static int cgi_timelimit;
static char *url_pattern;
static int no_empty_referers;
static char *local_pattern;
static char *logfile;
static char *throttlefile;
static char *hostname;
static char *pidfile;
static char *user;
static char *charset;
static char *p3p;
static int max_age;


typedef struct
{
	char *pattern;
	long max_limit, min_limit;
	long rate;
	off_t bytes_since_avg;
	int num_sending;
} throttletab;
static throttletab *throttles;
static int numthrottles, maxthrottles;

#define THROTTLE_NOLIMIT -1


typedef struct
{
	int conn_state;
	int next_free_connect;
	httpd_conn *hc;
	int tnums[MAXTHROTTLENUMS];		/* throttle indexes */
	int numtnums;
	long max_limit, min_limit;
	time_t started_at, active_at;
	Timer *wakeup_timer;
	Timer *linger_timer;
	long wouldblock_delay;
	off_t bytes;
	off_t end_byte_index;
	off_t next_byte_index;
} connecttab;
static connecttab *connects;
static int num_connects, max_connects, first_free_connect;
static int httpd_conn_count;

/* The connection states. */
#define CNST_FREE 0
#define CNST_READING 1
#define CNST_SENDING 2
#define CNST_PAUSING 3
#define CNST_LINGERING 4


static httpd_server *hs = (httpd_server *) 0;
int terminate = 0;
time_t start_time, stats_time;
long stats_connections;
off_t stats_bytes;
int stats_simultaneous;

static volatile int got_hup, got_usr1, watchdog_flag;


/* Forwards. */
static void parse_args(int argc, char **argv);
/*static void usage (void);
static void read_config (char *filename);
static void value_required (char *name, char *value);
static void no_value_required (char *name, char *value);*/
static char *e_strdup(char *oldstr);
static void lookup_hostname(httpd_sockaddr * sa4P, size_t sa4_len,
														int *gotv4P, httpd_sockaddr * sa6P,
														size_t sa6_len, int *gotv6P);
static void read_throttlefile(char *throttlefile);
static void shut_down(void);
static int handle_newconnect(struct timeval *tvP, int listen_fd);
static void handle_read(connecttab * c, struct timeval *tvP);
static void handle_send(connecttab * c, struct timeval *tvP);
static void handle_linger(connecttab * c, struct timeval *tvP);
static int check_throttles(connecttab * c);
static void clear_throttles(connecttab * c, struct timeval *tvP);
static void update_throttles(ClientData client_data, struct timeval *nowP);
static void finish_connection(connecttab * c, struct timeval *tvP);
static void clear_connection(connecttab * c, struct timeval *tvP);
static void really_clear_connection(connecttab * c, struct timeval *tvP);
static void idle(ClientData client_data, struct timeval *nowP);
static void wakeup_connection(ClientData client_data, struct timeval *nowP);
static void linger_clear_connection(ClientData client_data,
																		struct timeval *nowP);
static void occasional(ClientData client_data, struct timeval *nowP);
#ifdef STATS_TIME
static void show_stats(ClientData client_data, struct timeval *nowP);
#endif /* STATS_TIME */
static void logstats(struct timeval *nowP);
static void thttpd_logstats(long secs);


/* SIGTERM and SIGINT say to exit immediately. */
static void handle_term(int sig)
{
	/* Don't need to set up the handler again, since it's a one-shot. */

	shut_down();
	syslog(LOG_NOTICE, "exiting due to signal %d", sig);
	closelog();
	exit(1);
}


/* SIGCHLD - a chile process exitted, so we need to reap the zombie */
static void handle_chld(int sig)
{
	const int oerrno = errno;
	pid_t pid;
	int status;

#ifndef HAVE_SIGSET
	/* Set up handler again. */
	(void) signal(SIGCHLD, handle_chld);
#endif /* ! HAVE_SIGSET */

	/* Reap defunct children until there aren't any more. */
	for (;;)
	{
#ifdef HAVE_WAITPID
		pid = waitpid((pid_t) - 1, &status, WNOHANG);
#else	/* HAVE_WAITPID */
		pid = wait3(&status, WNOHANG, (struct rusage *) 0);
#endif /* HAVE_WAITPID */
		if ((int) pid == 0)					/* none left */
			break;
		if ((int) pid < 0)
		{
			if (errno == EINTR || errno == EAGAIN)
				continue;
			/* ECHILD shouldn't happen with the WNOHANG option,
			 ** but with some kernels it does anyway.  Ignore it.
			 */
			if (errno != ECHILD)
				syslog(LOG_ERR, "child wait - %m");
			break;
		}
		/* Decrement the CGI count.  Note that this is not accurate, since
		 ** each CGI can involve two or even three child processes.
		 ** Decrementing for each child means that when there is heavy CGI
		 ** activity, the count will be lower than it should be, and therefore
		 ** more CGIs will be allowed than should be.
		 */
		if (hs != (httpd_server *) 0)
		{
			--hs->cgi_count;
			if (hs->cgi_count < 0)
				hs->cgi_count = 0;
		}
	}

	/* Restore previous errno. */
	errno = oerrno;
}


/* SIGHUP says to re-open the log file. */
static void handle_hup(int sig)
{
	const int oerrno = errno;

#ifndef HAVE_SIGSET
	/* Set up handler again. */
	(void) signal(SIGHUP, handle_hup);
#endif /* ! HAVE_SIGSET */

	/* Just set a flag that we got the signal. */
	got_hup = 1;

	/* Restore previous errno. */
	errno = oerrno;
}


/* SIGUSR1 says to exit as soon as all current connections are done. */
static void handle_usr1(int sig)
{
	/* Don't need to set up the handler again, since it's a one-shot. */

	if (!_debug && num_connects == 0)
	{
		/* If there are no active connections we want to exit immediately
		 ** here.  Not only is it faster, but without any connections the
		 ** main loop won't wake up until the next new connection.
		 */
		shut_down();
		syslog(LOG_NOTICE, "exiting");
		closelog();
		exit(0);
	}

	/* Otherwise, just set a flag that we got the signal. */
	got_usr1 = 1;

	/* Don't need to restore old errno, since we didn't do any syscalls. */
}


/* SIGUSR2 says to generate the stats syslogs immediately. */
static void handle_usr2(int sig)
{
	const int oerrno = errno;

#ifndef HAVE_SIGSET
	/* Set up handler again. */
	(void) signal(SIGUSR2, handle_usr2);
#endif /* ! HAVE_SIGSET */

	logstats((struct timeval *) 0);

	/* Restore previous errno. */
	errno = oerrno;
}


/* SIGALRM is used as a watchdog. */
static void handle_alrm(int sig)
{
	const int oerrno = errno;

	/* If nothing has been happening */
	if (!watchdog_flag)
	{
		/* Try changing dirs to someplace we can write. */
		if (chdir("/tmp")); // We don't care if chdir() fails.
		/* Dump core. */
		abort();
	}
	watchdog_flag = 0;

#ifndef HAVE_SIGSET
	/* Set up handler again. */
	(void) signal(SIGALRM, handle_alrm);
#endif /* ! HAVE_SIGSET */
	/* Set up alarm again. */
	(void) alarm(OCCASIONAL_TIME * 3);

	/* Restore previous errno. */
	errno = oerrno;
}


static void re_open_logfile(void)
{
	FILE *logfp;

	if (no_log || hs == (httpd_server *) 0)
		return;

	/* Re-open the log file. */
	if (logfile != (char *) 0 && strcmp(logfile, "-") != 0)
	{
		syslog(LOG_NOTICE, "re-opening logfile");
		logfp = fopen(logfile, "a");
		if (logfp == (FILE *) 0)
		{
			syslog(LOG_CRIT, "re-opening %.80s - %m", logfile);
			return;
		}
		(void) fcntl(fileno(logfp), F_SETFD, 1);
		httpd_set_logfp(hs, logfp);
	}
}


int thttpd_main(int argc, char **argv, bool debug)
{
	char *cp;
	struct passwd *pwd;
	uid_t uid = 32767;
	gid_t gid = 32767;
	char cwd[MAXPATHLEN + 1];
	FILE *logfp;
	int num_ready;
	int cnum;
	connecttab *c;
	httpd_conn *hc;
	httpd_sockaddr sa4;
	httpd_sockaddr sa6;
	int gotv4, gotv6;
	struct timeval tv;

	argv0 = argv[0];
	_debug = debug;

	cp = strrchr(argv0, '/');
	if (cp != (char *) 0)
		++cp;
	else
		cp = argv0;

	//openlog( cp, LOG_NDELAY|LOG_PID, LOG_FACILITY );

	/* Handle command-line arguments. */
	parse_args(argc, argv);
	//debug = 1;
	do_chroot = 0;
	if (_debug)
	{
		cgi_limit = 1;
		cgi_timelimit = 0;
	}

	/* Read zone info now, in case we chroot(). */
	tzset();

	/* Look up hostname now, in case we chroot(). */
	lookup_hostname(&sa4, sizeof(sa4), &gotv4, &sa6, sizeof(sa6), &gotv6);
	if (!(gotv4 || gotv6))
	{
		syslog(LOG_ERR, "can't find any valid address");
		(void) fprintf(stderr, "%s: can't find any valid address\n", argv0);
		exit(1);
	}

	/* Throttle file. */
	numthrottles = 0;
	maxthrottles = 0;
	throttles = (throttletab *) 0;
	if (throttlefile != (char *) 0)
		read_throttlefile(throttlefile);

	/* If we're root and we're going to become another user, get the uid/gid
	 ** now.
	 */
	if (getuid() == 0)
	{
		pwd = getpwnam(user);
		if (pwd == (struct passwd *) 0)
		{
			syslog(LOG_CRIT, "unknown user - '%.80s'", user);
			(void) fprintf(stderr, "%s: unknown user - '%s'\n", argv0, user);
			exit(1);
		}
		uid = pwd->pw_uid;
		gid = pwd->pw_gid;
	}

	/* Log file. */
#if 0
	if (logfile != (char *) 0)
	{
		if (strcmp(logfile, "/dev/null") == 0)
		{
			no_log = 1;
			logfp = (FILE *) 0;
		}
		else if (strcmp(logfile, "-") == 0)
			logfp = stdout;
		else
		{
			logfp = fopen(logfile, "a");
			if (logfp == (FILE *) 0)
			{
				syslog(LOG_CRIT, "%.80s - %m", logfile);
				perror(logfile);
				exit(1);
			}
			if (logfile[0] != '/')
			{
				syslog(LOG_WARNING,
							 "logfile is not an absolute path, you may not be able to re-open it");
				(void) fprintf(stderr,
											 "%s: logfile is not an absolute path, you may not be able to re-open it\n",
											 argv0);
			}
			(void) fcntl(fileno(logfp), F_SETFD, 1);
			if (getuid() == 0)
			{
				/* If we are root then we chown the log file to the user we'll
				 ** be switching to.
				 */
				if (fchown(fileno(logfp), uid, gid) < 0)
				{
					syslog(LOG_WARNING, "fchown logfile - %m");
					perror("fchown logfile");
				}
			}
		}
	}
	else
#endif
		logfp = (FILE *) 0;

#if 0
	/* Switch directories if requested. */
	if (dir != (char *) 0)
	{
		if (chdir(dir) < 0)
		{
			syslog(LOG_CRIT, "chdir - %m");
			perror("chdir");
			exit(1);
		}
	}
#ifdef USE_USER_DIR
	else if (getuid() == 0)
	{
		/* No explicit directory was specified, we're root, and the
		 ** USE_USER_DIR option is set - switch to the specified user's
		 ** home dir.
		 */
		if (chdir(pwd->pw_dir) < 0)
		{
			syslog(LOG_CRIT, "chdir - %m");
			perror("chdir");
			exit(1);
		}
	}
#endif /* USE_USER_DIR */
#endif

	/* Get current directory. */
	if (getcwd(cwd, sizeof(cwd) - 1))
	{
		if (cwd[strlen(cwd) - 1] != '/')
			strcat(cwd, "/");
	}
	else
		strcpy(cwd, "/");

	if (0)												//!debug)
	{
		/* We're not going to use stdin stdout or stderr from here on, so close
		 ** them to save file descriptors.
		 */
		(void) fclose(stdin);
		if (logfp != stdout)
			(void) fclose(stdout);
		(void) fclose(stderr);

		/* Daemonize - make ourselves a subprocess. */
#ifdef HAVE_DAEMON
		if (daemon(1, 1) < 0)
		{
			syslog(LOG_CRIT, "daemon - %m");
			exit(1);
		}
#else	/* HAVE_DAEMON */
		switch (fork())
		{
			case 0:
				break;
			case -1:
				syslog(LOG_CRIT, "fork - %m");
				exit(1);
			default:
				exit(0);
		}
#ifdef HAVE_SETSID
		(void) setsid();
#endif /* HAVE_SETSID */
#endif /* HAVE_DAEMON */
	}
	else
	{
		/* Even if we don't daemonize, we still want to disown our parent
		 ** process.
		 */
#ifdef HAVE_SETSID
		(void) setsid();
#endif /* HAVE_SETSID */
	}

#if 0
	if (pidfile != (char *) 0)
	{
		/* Write the PID file. */
		FILE *pidfp = fopen(pidfile, "w");
		if (pidfp == (FILE *) 0)
		{
			syslog(LOG_CRIT, "%.80s - %m", pidfile);
			exit(1);
		}
		(void) fprintf(pidfp, "%d\n", (int) getpid());
		(void) fclose(pidfp);
	}
#endif

	/* Initialize the fdwatch package.  Have to do this before chroot,
	 ** if /dev/poll is used.
	 */
	max_connects = fdwatch_get_nfiles();
	if (max_connects < 0)
	{
		syslog(LOG_CRIT, "fdwatch initialization failure");
		exit(1);
	}
	max_connects -= SPARE_FDS;

	/* Chroot if requested. */
#if 0
	if (0)												//do_chroot)
	{
		if (chroot(cwd) < 0)
		{
			syslog(LOG_CRIT, "chroot - %m");
			perror("chroot");
			exit(1);
		}
		/* If we're logging and the logfile's pathname begins with the
		 ** chroot tree's pathname, then elide the chroot pathname so
		 ** that the logfile pathname still works from inside the chroot
		 ** tree.
		 */
		if (logfile != (char *) 0 && strcmp(logfile, "-") != 0)
		{
			if (strncmp(logfile, cwd, strlen(cwd)) == 0)
			{
				(void) strcpy(logfile, &logfile[strlen(cwd) - 1]);
				/* (We already guaranteed that cwd ends with a slash, so leaving
				 ** that slash in logfile makes it an absolute pathname within
				 ** the chroot tree.)
				 */
			}
			else
			{
				syslog(LOG_WARNING,
							 "logfile is not within the chroot tree, you will not be able to re-open it");
				(void) fprintf(stderr,
											 "%s: logfile is not within the chroot tree, you will not be able to re-open it\n",
											 argv0);
			}
		}
		(void) strcpy(cwd, "/");
		/* Always chdir to / after a chroot. */
		if (chdir(cwd) < 0)
		{
			syslog(LOG_CRIT, "chroot chdir - %m");
			perror("chroot chdir");
			exit(1);
		}
	}
#endif

	/* Switch directories again if requested. */
#if 0
	if (data_dir != (char *) 0)
	{
		if (chdir(data_dir) < 0)
		{
			syslog(LOG_CRIT, "data_dir chdir - %m");
			perror("data_dir chdir");
			exit(1);
		}
	}
#endif

	/* Set up to catch signals. */
#ifdef HAVE_SIGSET
	(void) sigset(SIGTERM, handle_term);
	(void) sigset(SIGINT, handle_term);
	(void) sigset(SIGCHLD, handle_chld);
	(void) sigset(SIGPIPE, SIG_IGN);	/* get EPIPE instead */
	(void) sigset(SIGHUP, handle_hup);
	(void) sigset(SIGUSR1, handle_usr1);
	(void) sigset(SIGUSR2, handle_usr2);
	(void) sigset(SIGALRM, handle_alrm);
#else	/* HAVE_SIGSET */
	(void) signal(SIGTERM, handle_term);
	(void) signal(SIGINT, handle_term);
	(void) signal(SIGCHLD, handle_chld);
	(void) signal(SIGPIPE, SIG_IGN);	/* get EPIPE instead */
	(void) signal(SIGHUP, handle_hup);
	(void) signal(SIGUSR1, handle_usr1);
	(void) signal(SIGUSR2, handle_usr2);
	(void) signal(SIGALRM, handle_alrm);
#endif /* HAVE_SIGSET */
	got_hup = 0;
	got_usr1 = 0;
	watchdog_flag = 0;
	(void) alarm(OCCASIONAL_TIME * 3);

	/* Initialize the timer package. */
	tmr_init();

	/* Initialize the HTTP layer.  Got to do this before giving up root,
	 ** so that we can bind to a privileged port.
	 */
	hs = httpd_initialize(hostname,
												gotv4 ? &sa4 : (httpd_sockaddr *) 0,
												gotv6 ? &sa6 : (httpd_sockaddr *) 0, port,
												cgi_pattern, cgi_limit, cgi_timelimit, charset, p3p,
												max_age, cwd, no_log, logfp, no_symlink_check,
												do_vhost, do_global_passwd, url_pattern,
												local_pattern, no_empty_referers);
	if (hs == (httpd_server *) 0)
		exit(1);

	hs->debug = _debug;

	/* Set up the occasional timer. */
	if (tmr_create
			((struct timeval *) 0, occasional, JunkClientData,
			 OCCASIONAL_TIME * 1000L, 1) == (Timer *) 0)
	{
		syslog(LOG_CRIT, "tmr_create(occasional) failed");
		exit(1);
	}
	/* Set up the idle timer. */
	if (tmr_create((struct timeval *) 0, idle, JunkClientData, 5 * 1000L, 1) ==
			(Timer *) 0)
	{
		syslog(LOG_CRIT, "tmr_create(idle) failed");
		exit(1);
	}
	if (numthrottles > 0)
	{
		/* Set up the throttles timer. */
		if (tmr_create
				((struct timeval *) 0, update_throttles, JunkClientData,
				 THROTTLE_TIME * 1000L, 1) == (Timer *) 0)
		{
			syslog(LOG_CRIT, "tmr_create(update_throttles) failed");
			exit(1);
		}
	}
#ifdef STATS_TIME
	/* Set up the stats timer. */
	if (tmr_create
			((struct timeval *) 0, show_stats, JunkClientData, STATS_TIME * 1000L,
			 1) == (Timer *) 0)
	{
		syslog(LOG_CRIT, "tmr_create(show_stats) failed");
		exit(1);
	}
#endif /* STATS_TIME */
	start_time = stats_time = time((time_t *) 0);
	stats_connections = 0;
	stats_bytes = 0;
	stats_simultaneous = 0;

	/* If we're root, try to become someone else. */
	if (getuid() == 0)
	{
		/* Set aux groups to null. */
		if (setgroups(0, (const gid_t *) 0) < 0)
		{
			syslog(LOG_CRIT, "setgroups - %m");
			exit(1);
		}
		/* Set primary group. */
		if (setgid(gid) < 0)
		{
			syslog(LOG_CRIT, "setgid - %m");
			exit(1);
		}
		/* Try setting aux groups correctly - not critical if this fails. */
		if (initgroups(user, gid) < 0)
			syslog(LOG_WARNING, "initgroups - %m");
#ifdef HAVE_SETLOGIN
		/* Set login name. */
		(void) setlogin(user);
#endif /* HAVE_SETLOGIN */
		/* Set uid. */
		if (setuid(uid) < 0)
		{
			syslog(LOG_CRIT, "setuid - %m");
			exit(1);
		}
		/* Check for unnecessary security exposure. */
		if (!do_chroot)
			syslog(LOG_WARNING,
						 "started as root without requesting chroot(), warning only");
	}

	/* Initialize our connections table. */
	connects = NEW(connecttab, max_connects);
	if (connects == (connecttab *) 0)
	{
		syslog(LOG_CRIT, "out of memory allocating a connecttab");
		exit(1);
	}
	for (cnum = 0; cnum < max_connects; ++cnum)
	{
		connects[cnum].conn_state = CNST_FREE;
		connects[cnum].next_free_connect = cnum + 1;
		connects[cnum].hc = (httpd_conn *) 0;
	}
	connects[max_connects - 1].next_free_connect = -1;	/* end of link list */
	first_free_connect = 0;
	num_connects = 0;
	httpd_conn_count = 0;

	if (hs != (httpd_server *) 0)
	{
		if (hs->listen4_fd != -1)
			fdwatch_add_fd(hs->listen4_fd, (void *) 0, FDW_READ);
		if (hs->listen6_fd != -1)
			fdwatch_add_fd(hs->listen6_fd, (void *) 0, FDW_READ);
	}

	/* Main loop. */
	(void) gettimeofday(&tv, (struct timezone *) 0);
	while ((!terminate) || num_connects > 0)
	{
		/* Do we need to re-open the log file? */
		if (got_hup)
		{
			re_open_logfile();
			got_hup = 0;
		}

		/* Do the fd watch. */
		num_ready = fdwatch(tmr_mstimeout(&tv));
		if (num_ready < 0)
		{
			if (errno == EINTR || errno == EAGAIN)
				continue;								/* try again */
			syslog(LOG_ERR, "fdwatch - %m");
			exit(1);
		}
		(void) gettimeofday(&tv, (struct timezone *) 0);

		if (num_ready == 0)
		{
			/* No fd's are ready - run the timers. */
			tmr_run(&tv);
			continue;
		}

		/* Is it a new connection? */
		if (hs != (httpd_server *) 0 && hs->listen6_fd != -1 &&
				fdwatch_check_fd(hs->listen6_fd))
		{
			if (handle_newconnect(&tv, hs->listen6_fd))
				/* Go around the loop and do another fdwatch, rather than
				 ** dropping through and processing existing connections.
				 ** New connections always get priority.
				 */
				continue;
		}
		if (hs != (httpd_server *) 0 && hs->listen4_fd != -1 &&
				fdwatch_check_fd(hs->listen4_fd))
		{
			if (handle_newconnect(&tv, hs->listen4_fd))
				/* Go around the loop and do another fdwatch, rather than
				 ** dropping through and processing existing connections.
				 ** New connections always get priority.
				 */
				continue;
		}

		/* Find the connections that need servicing. */
		while ((c =
						(connecttab *) fdwatch_get_next_client_data()) !=
					 (connecttab *) - 1)
		{
			if (c == (connecttab *) 0)
				continue;
			hc = c->hc;
			if (!fdwatch_check_fd(hc->conn_fd))
				/* Something went wrong. */
				clear_connection(c, &tv);
			else
				switch (c->conn_state)
				{
					case CNST_READING:
						handle_read(c, &tv);
						break;
					case CNST_SENDING:
						handle_send(c, &tv);
						break;
					case CNST_LINGERING:
						handle_linger(c, &tv);
						break;
				}
		}
		tmr_run(&tv);

		if (!_debug && got_usr1 && !terminate)
		{
			terminate = 1;
			if (hs != (httpd_server *) 0)
			{
				if (hs->listen4_fd != -1)
					fdwatch_del_fd(hs->listen4_fd);
				if (hs->listen6_fd != -1)
					fdwatch_del_fd(hs->listen6_fd);
				httpd_unlisten(hs);
			}
		}
	}

	/* The main loop terminated. */
	shut_down();
	syslog(LOG_NOTICE, "exiting");
	//closelog();
	exit(0);
}


static void parse_args(int argc, char **argv)
{
	char *env;
	int val;

	//debug = 0;
	port = DEFAULT_PORT;
	dir = (char *) 0;
	data_dir = (char *) 0;
#ifdef ALWAYS_CHROOT
	do_chroot = 1;
#else	/* ALWAYS_CHROOT */
	do_chroot = 0;
#endif /* ALWAYS_CHROOT */
	no_log = 0;
	no_symlink_check = do_chroot;
#ifdef ALWAYS_VHOST
	do_vhost = 1;
#else	/* ALWAYS_VHOST */
	do_vhost = 0;
#endif /* ALWAYS_VHOST */
#ifdef ALWAYS_GLOBAL_PASSWD
	do_global_passwd = 1;
#else	/* ALWAYS_GLOBAL_PASSWD */
	do_global_passwd = 0;
#endif /* ALWAYS_GLOBAL_PASSWD */
#ifdef CGI_PATTERN
	cgi_pattern = CGI_PATTERN;
#else	/* CGI_PATTERN */
	cgi_pattern = (char *) 0;
#endif /* CGI_PATTERN */
#ifdef CGI_LIMIT
	cgi_limit = CGI_LIMIT;
#else	/* CGI_LIMIT */
	cgi_limit = 0;
#endif /* CGI_LIMIT */
#ifdef CGI_TIMELIMIT
	cgi_timelimit = CGI_TIMELIMIT;
#else	/* CGI_LIMIT */
	cgi_timelimit = 0;
#endif /* CGI_LIMIT */
	url_pattern = (char *) 0;
	no_empty_referers = 0;
	local_pattern = (char *) 0;
	throttlefile = (char *) 0;
	hostname = (char *) 0;
	logfile = (char *) 0;
	pidfile = (char *) 0;
	user = DEFAULT_USER;
	charset = DEFAULT_CHARSET;
	p3p = "";
	max_age = -1;

	env = getenv("GB_HTTPD_HOST");
	if (env && *env)
		hostname = env;


	env = getenv("GB_HTTPD_PORT");
	if (env && *env)
	{
		port = (unsigned short) atoi(env);
		if (port == 0)
			port = 80;
	}

	env = getenv("GB_HTTPD_TIMEOUT");
	if (env && *env)
	{
		val = atoi(env);
		if (val == 0)
		{
			if (env[0] == '0' && env[1] == 0)
				cgi_timelimit = 0;
		}
		else
			cgi_timelimit = val;
	}

#if 0
	argn = 1;
	while (argn < argc && argv[argn][0] == '-')
	{
		if (strcmp(argv[argn], "-V") == 0)
		{
			(void) printf("%s\n", SERVER_SOFTWARE);
			exit(0);
		}
		else if (strcmp(argv[argn], "-C") == 0 && argn + 1 < argc)
		{
			++argn;
			read_config(argv[argn]);
		}
		else if (strcmp(argv[argn], "-p") == 0 && argn + 1 < argc)
		{
			++argn;
			port = (unsigned short) atoi(argv[argn]);
		}
		else if (strcmp(argv[argn], "-d") == 0 && argn + 1 < argc)
		{
			++argn;
			dir = argv[argn];
		}
		else if (strcmp(argv[argn], "-r") == 0)
		{
			do_chroot = 1;
			no_symlink_check = 1;
		}
		else if (strcmp(argv[argn], "-nor") == 0)
		{
			do_chroot = 0;
			no_symlink_check = 0;
		}
		else if (strcmp(argv[argn], "-dd") == 0 && argn + 1 < argc)
		{
			++argn;
			data_dir = argv[argn];
		}
		else if (strcmp(argv[argn], "-s") == 0)
			no_symlink_check = 0;
		else if (strcmp(argv[argn], "-nos") == 0)
			no_symlink_check = 1;
		else if (strcmp(argv[argn], "-u") == 0 && argn + 1 < argc)
		{
			++argn;
			user = argv[argn];
		}
		else if (strcmp(argv[argn], "-c") == 0 && argn + 1 < argc)
		{
			++argn;
			cgi_pattern = argv[argn];
		}
		else if (strcmp(argv[argn], "-t") == 0 && argn + 1 < argc)
		{
			++argn;
			throttlefile = argv[argn];
		}
		else if (strcmp(argv[argn], "-h") == 0 && argn + 1 < argc)
		{
			++argn;
			hostname = argv[argn];
		}
		else if (strcmp(argv[argn], "-l") == 0 && argn + 1 < argc)
		{
			++argn;
			logfile = argv[argn];
		}
		else if (strcmp(argv[argn], "-v") == 0)
			do_vhost = 1;
		else if (strcmp(argv[argn], "-nov") == 0)
			do_vhost = 0;
		else if (strcmp(argv[argn], "-g") == 0)
			do_global_passwd = 1;
		else if (strcmp(argv[argn], "-nog") == 0)
			do_global_passwd = 0;
		else if (strcmp(argv[argn], "-i") == 0 && argn + 1 < argc)
		{
			++argn;
			pidfile = argv[argn];
		}
		else if (strcmp(argv[argn], "-T") == 0 && argn + 1 < argc)
		{
			++argn;
			charset = argv[argn];
		}
		else if (strcmp(argv[argn], "-P") == 0 && argn + 1 < argc)
		{
			++argn;
			p3p = argv[argn];
		}
		else if (strcmp(argv[argn], "-M") == 0 && argn + 1 < argc)
		{
			++argn;
			max_age = atoi(argv[argn]);
		}
		else if (strcmp(argv[argn], "-D") == 0)
			debug = 1;
		else
			usage();
		++argn;
	}
	if (argn != argc)
		usage();
#endif
}

#if 0
static void usage(void)
{
	(void) fprintf(stderr,
								 "usage:  %s [-C configfile] [-p port] [-d dir] [-r|-nor] [-dd data_dir] [-s|-nos] [-v|-nov] [-g|-nog] [-u user] [-c cgipat] [-t throttles] [-h host] [-l logfile] [-i pidfile] [-T charset] [-P P3P] [-M maxage] [-V] [-D]\n",
								 argv0);
	exit(1);
}
#endif

#if 0
static void read_config(char *filename)
{
	FILE *fp;
	char line[10000];
	char *cp;
	char *cp2;
	char *name;
	char *value;

	fp = fopen(filename, "r");
	if (fp == (FILE *) 0)
	{
		perror(filename);
		exit(1);
	}

	while (fgets(line, sizeof(line), fp) != (char *) 0)
	{
		/* Trim comments. */
		if ((cp = strchr(line, '#')) != (char *) 0)
			*cp = '\0';

		/* Skip leading whitespace. */
		cp = line;
		cp += strspn(cp, " \t\n\r");

		/* Split line into words. */
		while (*cp != '\0')
		{
			/* Find next whitespace. */
			cp2 = cp + strcspn(cp, " \t\n\r");
			/* Insert EOS and advance next-word pointer. */
			while (*cp2 == ' ' || *cp2 == '\t' || *cp2 == '\n' || *cp2 == '\r')
				*cp2++ = '\0';
			/* Split into name and value. */
			name = cp;
			value = strchr(name, '=');
			if (value != (char *) 0)
				*value++ = '\0';
			/* Interpret. */
			if (strcasecmp(name, "debug") == 0)
			{
				no_value_required(name, value);
				debug = 1;
			}
			else if (strcasecmp(name, "port") == 0)
			{
				value_required(name, value);
				port = (unsigned short) atoi(value);
			}
			else if (strcasecmp(name, "dir") == 0)
			{
				value_required(name, value);
				dir = e_strdup(value);
			}
			else if (strcasecmp(name, "chroot") == 0)
			{
				no_value_required(name, value);
				do_chroot = 1;
				no_symlink_check = 1;
			}
			else if (strcasecmp(name, "nochroot") == 0)
			{
				no_value_required(name, value);
				do_chroot = 0;
				no_symlink_check = 0;
			}
			else if (strcasecmp(name, "data_dir") == 0)
			{
				value_required(name, value);
				data_dir = e_strdup(value);
			}
			else if (strcasecmp(name, "symlink") == 0)
			{
				no_value_required(name, value);
				no_symlink_check = 0;
			}
			else if (strcasecmp(name, "nosymlink") == 0)
			{
				no_value_required(name, value);
				no_symlink_check = 1;
			}
			else if (strcasecmp(name, "symlinks") == 0)
			{
				no_value_required(name, value);
				no_symlink_check = 0;
			}
			else if (strcasecmp(name, "nosymlinks") == 0)
			{
				no_value_required(name, value);
				no_symlink_check = 1;
			}
			else if (strcasecmp(name, "user") == 0)
			{
				value_required(name, value);
				user = e_strdup(value);
			}
			else if (strcasecmp(name, "cgipat") == 0)
			{
				value_required(name, value);
				cgi_pattern = e_strdup(value);
			}
			else if (strcasecmp(name, "cgilimit") == 0)
			{
				value_required(name, value);
				cgi_limit = atoi(value);
			}
			else if (strcasecmp(name, "cgitimelimit") == 0)
			{
				value_required(name, value);
				cgi_timelimit = atoi(value);
			}
			else if (strcasecmp(name, "urlpat") == 0)
			{
				value_required(name, value);
				url_pattern = e_strdup(value);
			}
			else if (strcasecmp(name, "noemptyreferers") == 0)
			{
				no_value_required(name, value);
				no_empty_referers = 1;
			}
			else if (strcasecmp(name, "localpat") == 0)
			{
				value_required(name, value);
				local_pattern = e_strdup(value);
			}
			else if (strcasecmp(name, "throttles") == 0)
			{
				value_required(name, value);
				throttlefile = e_strdup(value);
			}
			else if (strcasecmp(name, "host") == 0)
			{
				value_required(name, value);
				hostname = e_strdup(value);
			}
			else if (strcasecmp(name, "logfile") == 0)
			{
				value_required(name, value);
				logfile = e_strdup(value);
			}
			else if (strcasecmp(name, "vhost") == 0)
			{
				no_value_required(name, value);
				do_vhost = 1;
			}
			else if (strcasecmp(name, "novhost") == 0)
			{
				no_value_required(name, value);
				do_vhost = 0;
			}
			else if (strcasecmp(name, "globalpasswd") == 0)
			{
				no_value_required(name, value);
				do_global_passwd = 1;
			}
			else if (strcasecmp(name, "noglobalpasswd") == 0)
			{
				no_value_required(name, value);
				do_global_passwd = 0;
			}
			else if (strcasecmp(name, "pidfile") == 0)
			{
				value_required(name, value);
				pidfile = e_strdup(value);
			}
			else if (strcasecmp(name, "charset") == 0)
			{
				value_required(name, value);
				charset = e_strdup(value);
			}
			else if (strcasecmp(name, "p3p") == 0)
			{
				value_required(name, value);
				p3p = e_strdup(value);
			}
			else if (strcasecmp(name, "max_age") == 0)
			{
				value_required(name, value);
				max_age = atoi(value);
			}
			else
			{
				(void) fprintf(stderr, "%s: unknown config option '%s'\n",
											 argv0, name);
				exit(1);
			}

			/* Advance to next word. */
			cp = cp2;
			cp += strspn(cp, " \t\n\r");
		}
	}

	(void) fclose(fp);
}

static void value_required(char *name, char *value)
{
	if (value == (char *) 0)
	{
		(void) fprintf(stderr, "%s: value required for %s option\n", argv0, name);
		exit(1);
	}
}


static void no_value_required(char *name, char *value)
{
	if (value != (char *) 0)
	{
		(void) fprintf(stderr, "%s: no value required for %s option\n",
									 argv0, name);
		exit(1);
	}
}
#endif


static char *e_strdup(char *oldstr)
{
	char *newstr;

	newstr = strdup(oldstr);
	if (newstr == (char *) 0)
	{
		syslog(LOG_CRIT, "out of memory copying a string");
		(void) fprintf(stderr, "%s: out of memory copying a string\n", argv0);
		exit(1);
	}
	return newstr;
}


static void
lookup_hostname(httpd_sockaddr * sa4P, size_t sa4_len, int *gotv4P,
								httpd_sockaddr * sa6P, size_t sa6_len, int *gotv6P)
{
#ifdef USE_IPV6

	struct addrinfo hints;
	char portstr[10];
	int gaierr;
	struct addrinfo *ai;
	struct addrinfo *ai2;
	struct addrinfo *aiv6;
	struct addrinfo *aiv4;

	(void) memset(&hints, 0, sizeof(hints));
	hints.ai_family = PF_UNSPEC;
	hints.ai_flags = AI_PASSIVE;
	hints.ai_socktype = SOCK_STREAM;
	(void) snprintf(portstr, sizeof(portstr), "%d", (int) port);
	if ((gaierr = getaddrinfo(hostname, portstr, &hints, &ai)) != 0)
	{
		syslog(LOG_CRIT, "getaddrinfo %.80s - %.80s",
					 hostname, gai_strerror(gaierr));
		(void) fprintf(stderr, "%s: getaddrinfo %s - %s\n",
									 argv0, hostname, gai_strerror(gaierr));
		exit(1);
	}

	/* Find the first IPv6 and IPv4 entries. */
	aiv6 = (struct addrinfo *) 0;
	aiv4 = (struct addrinfo *) 0;
	for (ai2 = ai; ai2 != (struct addrinfo *) 0; ai2 = ai2->ai_next)
	{
		switch (ai2->ai_family)
		{
			case AF_INET6:
				if (aiv6 == (struct addrinfo *) 0)
					aiv6 = ai2;
				break;
			case AF_INET:
				if (aiv4 == (struct addrinfo *) 0)
					aiv4 = ai2;
				break;
		}
	}

	if (aiv6 == (struct addrinfo *) 0)
		*gotv6P = 0;
	else
	{
		if (sa6_len < aiv6->ai_addrlen)
		{
			syslog(LOG_CRIT, "%.80s - sockaddr too small (%lu < %lu)",
						 hostname, (unsigned long) sa6_len,
						 (unsigned long) aiv6->ai_addrlen);
			exit(1);
		}
		(void) memset(sa6P, 0, sa6_len);
		(void) memmove(sa6P, aiv6->ai_addr, aiv6->ai_addrlen);
		*gotv6P = 1;
	}

	if (aiv4 == (struct addrinfo *) 0)
		*gotv4P = 0;
	else
	{
		if (sa4_len < aiv4->ai_addrlen)
		{
			syslog(LOG_CRIT, "%.80s - sockaddr too small (%lu < %lu)",
						 hostname, (unsigned long) sa4_len,
						 (unsigned long) aiv4->ai_addrlen);
			exit(1);
		}
		(void) memset(sa4P, 0, sa4_len);
		(void) memmove(sa4P, aiv4->ai_addr, aiv4->ai_addrlen);
		*gotv4P = 1;
	}

	freeaddrinfo(ai);

#else	/* USE_IPV6 */

	struct hostent *he;

	*gotv6P = 0;

	(void) memset(sa4P, 0, sa4_len);
	sa4P->sa.sa_family = AF_INET;
	if (hostname == (char *) 0)
		sa4P->sa_in.sin_addr.s_addr = htonl(INADDR_ANY);
	else
	{
		sa4P->sa_in.sin_addr.s_addr = inet_addr(hostname);
		if ((int) sa4P->sa_in.sin_addr.s_addr == -1)
		{
			he = gethostbyname(hostname);
			if (he == (struct hostent *) 0)
			{
#ifdef HAVE_HSTRERROR
				syslog(LOG_CRIT, "gethostbyname %.80s - %.80s",
							 hostname, hstrerror(h_errno));
				(void) fprintf(stderr, "%s: gethostbyname %s - %s\n",
											 argv0, hostname, hstrerror(h_errno));
#else	/* HAVE_HSTRERROR */
				syslog(LOG_CRIT, "gethostbyname %.80s failed", hostname);
				(void) fprintf(stderr, "%s: gethostbyname %s failed\n", argv0,
											 hostname);
#endif /* HAVE_HSTRERROR */
				exit(1);
			}
			if (he->h_addrtype != AF_INET)
			{
				syslog(LOG_CRIT, "%.80s - non-IP network address", hostname);
				(void) fprintf(stderr, "%s: %s - non-IP network address\n",
											 argv0, hostname);
				exit(1);
			}
			(void) memmove(&sa4P->sa_in.sin_addr.s_addr, he->h_addr, he->h_length);
		}
	}
	sa4P->sa_in.sin_port = htons(port);
	*gotv4P = 1;

#endif /* USE_IPV6 */
}


static void read_throttlefile(char *throttlefile)
{
	FILE *fp;
	char buf[5000];
	char *cp;
	int len;
	char pattern[5000];
	long max_limit, min_limit;
	struct timeval tv;

	fp = fopen(throttlefile, "r");
	if (fp == (FILE *) 0)
	{
		syslog(LOG_CRIT, "%.80s - %m", throttlefile);
		perror(throttlefile);
		exit(1);
	}

	(void) gettimeofday(&tv, (struct timezone *) 0);

	while (fgets(buf, sizeof(buf), fp) != (char *) 0)
	{
		/* Nuke comments. */
		cp = strchr(buf, '#');
		if (cp != (char *) 0)
			*cp = '\0';

		/* Nuke trailing whitespace. */
		len = strlen(buf);
		while (len > 0 &&
					 (buf[len - 1] == ' ' || buf[len - 1] == '\t' ||
						buf[len - 1] == '\n' || buf[len - 1] == '\r'))
			buf[--len] = '\0';

		/* Ignore empty lines. */
		if (len == 0)
			continue;

		/* Parse line. */
		if (sscanf
				(buf, " %4900[^ \t] %ld-%ld", pattern, &min_limit, &max_limit) == 3)
		{
		}
		else if (sscanf(buf, " %4900[^ \t] %ld", pattern, &max_limit) == 2)
			min_limit = 0;
		else
		{
			syslog(LOG_CRIT, "unparsable line in %.80s - %.80s", throttlefile, buf);
			(void) fprintf(stderr,
										 "%s: unparsable line in %.80s - %.80s\n",
										 argv0, throttlefile, buf);
			continue;
		}

		/* Nuke any leading slashes in pattern. */
		if (pattern[0] == '/')
			(void) strcpy(pattern, &pattern[1]);
		while ((cp = strstr(pattern, "|/")) != (char *) 0)
			(void) strcpy(cp + 1, cp + 2);

		/* Check for room in throttles. */
		if (numthrottles >= maxthrottles)
		{
			if (maxthrottles == 0)
			{
				maxthrottles = 100;			/* arbitrary */
				throttles = NEW(throttletab, maxthrottles);
			}
			else
			{
				maxthrottles *= 2;
				throttles = RENEW(throttles, throttletab, maxthrottles);
			}
			if (throttles == (throttletab *) 0)
			{
				syslog(LOG_CRIT, "out of memory allocating a throttletab");
				(void) fprintf(stderr,
											 "%s: out of memory allocating a throttletab\n", argv0);
				exit(1);
			}
		}

		/* Add to table. */
		throttles[numthrottles].pattern = e_strdup(pattern);
		throttles[numthrottles].max_limit = max_limit;
		throttles[numthrottles].min_limit = min_limit;
		throttles[numthrottles].rate = 0;
		throttles[numthrottles].bytes_since_avg = 0;
		throttles[numthrottles].num_sending = 0;

		++numthrottles;
	}
	(void) fclose(fp);
}


static void shut_down(void)
{
	int cnum;
	struct timeval tv;

	(void) gettimeofday(&tv, (struct timezone *) 0);
	logstats(&tv);
	for (cnum = 0; cnum < max_connects; ++cnum)
	{
		if (connects[cnum].conn_state != CNST_FREE)
			httpd_close_conn(connects[cnum].hc, &tv);
		if (connects[cnum].hc != (httpd_conn *) 0)
		{
			httpd_destroy_conn(connects[cnum].hc);
			free((void *) connects[cnum].hc);
			--httpd_conn_count;
			connects[cnum].hc = (httpd_conn *) 0;
		}
	}
	if (hs != (httpd_server *) 0)
	{
		httpd_server *ths = hs;
		hs = (httpd_server *) 0;
		if (ths->listen4_fd != -1)
			fdwatch_del_fd(ths->listen4_fd);
		if (ths->listen6_fd != -1)
			fdwatch_del_fd(ths->listen6_fd);
		httpd_terminate(ths);
	}
	//mmc_destroy();
	tmr_destroy();
	free((void *) connects);
	if (throttles != (throttletab *) 0)
		free((void *) throttles);
}


static int handle_newconnect(struct timeval *tvP, int listen_fd)
{
	connecttab *c;
	//ClientData client_data;

	/* This loops until the accept() fails, trying to start new
	 ** connections as fast as possible so we don't overrun the
	 ** listen queue.
	 */
	for (;;)
	{
		/* Is there room in the connection table? */
		if (num_connects >= max_connects)
		{
			/* Out of connection slots.  Run the timers, then the
			 ** existing connections, and maybe we'll free up a slot
			 ** by the time we get back here.
			 */
			syslog(LOG_WARNING, "too many connections!");
			tmr_run(tvP);
			return 0;
		}
		/* Get the first free connection entry off the free list. */
		if (first_free_connect == -1
				|| connects[first_free_connect].conn_state != CNST_FREE)
		{
			syslog(LOG_CRIT, "the connects free list is messed up");
			exit(1);
		}
		c = &connects[first_free_connect];
		/* Make the httpd_conn if necessary. */
		if (c->hc == (httpd_conn *) 0)
		{
			c->hc = NEW(httpd_conn, 1);
			if (c->hc == (httpd_conn *) 0)
			{
				syslog(LOG_CRIT, "out of memory allocating an httpd_conn");
				exit(1);
			}
			c->hc->initialized = 0;
			++httpd_conn_count;
		}

		/* Get the connection. */
		switch (httpd_get_conn(hs, listen_fd, c->hc))
		{
				/* Some error happened.  Run the timers, then the
				 ** existing connections.  Maybe the error will clear.
				 */
			case GC_FAIL:
				tmr_run(tvP);
				return 0;

				/* No more connections to accept for now. */
			case GC_NO_MORE:
				return 1;
		}
		c->conn_state = CNST_READING;
		/* Pop it off the free list. */
		first_free_connect = c->next_free_connect;
		c->next_free_connect = -1;
		++num_connects;
		//client_data.p = c;
		c->active_at = tvP->tv_sec;
		c->wakeup_timer = (Timer *) 0;
		c->linger_timer = (Timer *) 0;
		c->next_byte_index = 0;
		c->numtnums = 0;

		/* Set the connection file descriptor to no-delay mode. */
		httpd_set_ndelay(c->hc->conn_fd);

		fdwatch_add_fd(c->hc->conn_fd, c, FDW_READ);

		++stats_connections;
		if (num_connects > stats_simultaneous)
			stats_simultaneous = num_connects;
	}
}

static void check_paused(ClientData client_data, struct timeval *tvP)
{
	connecttab *c = client_data.p;
	int result;

	result = httpd_check_paused(c->hc);

	if (result == 1)
	{
		if (tmr_create((struct timeval *)0, check_paused, client_data, 100, 0) == NULL)
		{
			syslog(LOG_CRIT, "tmr_create(check_paused) failed");
			exit(1);
		}
	}
	else if (result == 0)
	{
		c->conn_state = CNST_READING;
	}
	else
		finish_connection(c, tvP);
}

static void handle_read(connecttab * c, struct timeval *tvP)
{
	int sz;
	int action;
	httpd_conn *hc = c->hc;
	ClientData client_data;

	/* Is there room in our buffer to read more bytes? */
	if (hc->read_idx >= hc->read_size)
	{
		if (hc->read_size > 5000)
		{
			httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, "");
			finish_connection(c, tvP);
			return;
		}
		httpd_realloc_str(&hc->read_buf, &hc->read_size, hc->read_size + 1000);
	}

	/* Read some more bytes. */
	sz = read(hc->conn_fd, &(hc->read_buf[hc->read_idx]),
						hc->read_size - hc->read_idx);
	if (sz == 0)
	{
		httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, "");
		finish_connection(c, tvP);
		return;
	}
	if (sz < 0)
	{
		/* Ignore EINTR and EAGAIN.  Also ignore EWOULDBLOCK.  At first glance
		 ** you would think that connections returned by fdwatch as readable
		 ** should never give an EWOULDBLOCK; however, this apparently can
		 ** happen if a packet gets garbled.
		 */
		if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)
			return;
		httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, "");
		finish_connection(c, tvP);
		return;
	}
	hc->read_idx += sz;
	c->active_at = tvP->tv_sec;

	/* Do we have a complete request yet? */
	switch (httpd_got_request(hc))
	{
		case GR_NO_REQUEST:
			return;
		case GR_BAD_REQUEST:
			httpd_send_err(hc, 400, httpd_err400title, "", httpd_err400form, "");
			finish_connection(c, tvP);
			return;
	}

	/* Yes.  Try parsing and resolving it. */
	if (httpd_parse_request(hc) < 0)
	{
		finish_connection(c, tvP);
		return;
	}

	/* Check the throttle table */
	if (!check_throttles(c))
	{
		httpd_send_err(hc, 503, httpd_err503title, "", httpd_err503form,
									 hc->encodedurl);
		finish_connection(c, tvP);
		return;
	}

	/* Start the connection going. */
	action = httpd_start_request(hc, tvP);
	//syslog(LOG_INFO, "[%p] action = %d", hc, action);

	if (action < 0)
	{
		/* Something went wrong.  Close down the connection. */
		finish_connection(c, tvP);
		return;
	}

	if (action == 1)
	{
		//syslog(LOG_INFO, "[%p] pausing connection", hc);
		c->conn_state = CNST_PAUSING;
		client_data.p = c;
		if (tmr_create((struct timeval *)0, check_paused, client_data, 100, 0) == NULL)
		{
			syslog(LOG_CRIT, "tmr_create(check_paused) failed");
			exit(1);
		}
		return;
	}

	/* Fill in end_byte_index. */
	if (hc->got_range)
	{
		c->next_byte_index = hc->first_byte_index;
		c->end_byte_index = hc->last_byte_index + 1;
	}
	else if (hc->bytes_to_send < 0)
		c->end_byte_index = 0;
	else
		c->end_byte_index = hc->bytes_to_send;

	/* Check if it's already handled. */
	if (hc->file_address == (char *) 0)
	{
		/* No file address means someone else is handling it. */
		int tind;
		for (tind = 0; tind < c->numtnums; ++tind)
			throttles[c->tnums[tind]].bytes_since_avg += hc->bytes_sent;
		c->next_byte_index = hc->bytes_sent;
		finish_connection(c, tvP);
		return;
	}
	if (c->next_byte_index >= c->end_byte_index)
	{
		/* There's nothing to send. */
		finish_connection(c, tvP);
		return;
	}

	/* Cool, we have a valid connection and a file to send to it. */
	c->conn_state = CNST_SENDING;
	c->started_at = tvP->tv_sec;
	c->wouldblock_delay = 0;

	fdwatch_del_fd(hc->conn_fd);
	fdwatch_add_fd(hc->conn_fd, c, FDW_WRITE);
}


static void handle_send(connecttab * c, struct timeval *tvP)
{
	size_t max_bytes;
	int sz, coast;
	ClientData client_data;
	time_t elapsed;
	httpd_conn *hc = c->hc;
	int tind;

	if (c->max_limit == THROTTLE_NOLIMIT)
		max_bytes = 1000000000L;
	else
		max_bytes = c->max_limit / 4;	/* send at most 1/4 seconds worth */

	/* Do we need to write the headers first? */
	if (hc->responselen == 0)
	{
		/* No, just write the file. */
		sz = write(hc->conn_fd, &(hc->file_address[c->next_byte_index]),
							 MIN(c->end_byte_index - c->next_byte_index, max_bytes));
	}
	else
	{
		/* Yes.  We'll combine headers and file into a single writev(),
		 ** hoping that this generates a single packet.
		 */
		struct iovec iv[2];

		iv[0].iov_base = hc->response;
		iv[0].iov_len = hc->responselen;
		iv[1].iov_base = &(hc->file_address[c->next_byte_index]);
		iv[1].iov_len = MIN(c->end_byte_index - c->next_byte_index, max_bytes);
		sz = writev(hc->conn_fd, iv, 2);
	}

	if (sz < 0 && errno == EINTR)
		return;

	if (sz == 0 || (sz < 0 && (errno == EWOULDBLOCK || errno == EAGAIN)))
	{
		/* This shouldn't happen, but some kernels, e.g.
		 ** SunOS 4.1.x, are broken and select() says that
		 ** O_NDELAY sockets are always writable even when
		 ** they're actually not.
		 **
		 ** Current workaround is to block sending on this
		 ** socket for a brief adaptively-tuned period.
		 ** Fortunately we already have all the necessary
		 ** blocking code, for use with throttling.
		 */
		c->wouldblock_delay += MIN_WOULDBLOCK_DELAY;
		c->conn_state = CNST_PAUSING;
		fdwatch_del_fd(hc->conn_fd);
		client_data.p = c;
		if (c->wakeup_timer != (Timer *) 0)
			syslog(LOG_ERR, "replacing non-null wakeup_timer!");
		c->wakeup_timer =
			tmr_create(tvP, wakeup_connection, client_data, c->wouldblock_delay, 0);
		if (c->wakeup_timer == (Timer *) 0)
		{
			syslog(LOG_CRIT, "tmr_create(wakeup_connection) failed");
			exit(1);
		}
		return;
	}

	if (sz < 0)
	{
		/* Something went wrong, close this connection.
		 **
		 ** If it's just an EPIPE, don't bother logging, that
		 ** just means the client hung up on us.
		 **
		 ** On some systems, write() occasionally gives an EINVAL.
		 ** Dunno why, something to do with the socket going
		 ** bad.  Anyway, we don't log those either.
		 **
		 ** And ECONNRESET isn't interesting either.
		 */
		if (errno != EPIPE && errno != EINVAL && errno != ECONNRESET)
			syslog(LOG_ERR, "write - %m sending %.80s", hc->encodedurl);
		clear_connection(c, tvP);
		return;
	}

	/* Ok, we wrote something. */
	c->active_at = tvP->tv_sec;
	/* Was this a headers + file writev()? */
	if (hc->responselen > 0)
	{
		/* Yes; did we write only part of the headers? */
		if (sz < hc->responselen)
		{
			/* Yes; move the unwritten part to the front of the buffer. */
			int newlen = hc->responselen - sz;
			(void) memmove(hc->response, &(hc->response[sz]), newlen);
			hc->responselen = newlen;
			sz = 0;
		}
		else
		{
			/* Nope, we wrote the full headers, so adjust accordingly. */
			sz -= hc->responselen;
			hc->responselen = 0;
		}
	}
	/* And update how much of the file we wrote. */
	c->next_byte_index += sz;
	c->hc->bytes_sent += sz;
	for (tind = 0; tind < c->numtnums; ++tind)
		throttles[c->tnums[tind]].bytes_since_avg += sz;

	/* Are we done? */
	if (c->next_byte_index >= c->end_byte_index)
	{
		/* This connection is finished! */
		finish_connection(c, tvP);
		return;
	}

	/* Tune the (blockheaded) wouldblock delay. */
	if (c->wouldblock_delay > MIN_WOULDBLOCK_DELAY)
		c->wouldblock_delay -= MIN_WOULDBLOCK_DELAY;

	/* If we're throttling, check if we're sending too fast. */
	if (c->max_limit != THROTTLE_NOLIMIT)
	{
		elapsed = tvP->tv_sec - c->started_at;
		if (elapsed == 0)
			elapsed = 1;							/* count at least one second */
		if (c->hc->bytes_sent / elapsed > c->max_limit)
		{
			c->conn_state = CNST_PAUSING;
			fdwatch_del_fd(hc->conn_fd);
			/* How long should we wait to get back on schedule?  If less
			 ** than a second (integer math rounding), use 1/2 second.
			 */
			coast = c->hc->bytes_sent / c->max_limit - elapsed;
			client_data.p = c;
			if (c->wakeup_timer != (Timer *) 0)
				syslog(LOG_ERR, "replacing non-null wakeup_timer!");
			c->wakeup_timer = tmr_create(tvP, wakeup_connection, client_data,
																	 coast > 0 ? (coast * 1000L) : 500L, 0);
			if (c->wakeup_timer == (Timer *) 0)
			{
				syslog(LOG_CRIT, "tmr_create(wakeup_connection) failed");
				exit(1);
			}
		}
	}
	/* (No check on min_limit here, that only controls connection startups.) */
}


static void handle_linger(connecttab * c, struct timeval *tvP)
{
	char buf[4096];
	int r;

	/* In lingering-close mode we just read and ignore bytes.  An error
	 ** or EOF ends things, otherwise we go until a timeout.
	 */
	r = read(c->hc->conn_fd, buf, sizeof(buf));
	if (r < 0 && (errno == EINTR || errno == EAGAIN))
		return;
	if (r <= 0)
		really_clear_connection(c, tvP);
}


static int check_throttles(connecttab * c)
{
	int tnum;
	long l;

	c->numtnums = 0;
	c->max_limit = c->min_limit = THROTTLE_NOLIMIT;
	for (tnum = 0; tnum < numthrottles && c->numtnums < MAXTHROTTLENUMS; ++tnum)
		if (match(throttles[tnum].pattern, c->hc->expnfilename))
		{
			/* If we're way over the limit, don't even start. */
			if (throttles[tnum].rate > throttles[tnum].max_limit * 2)
				return 0;
			/* Also don't start if we're under the minimum. */
			if (throttles[tnum].rate < throttles[tnum].min_limit)
				return 0;
			if (throttles[tnum].num_sending < 0)
			{
				syslog(LOG_ERR,
							 "throttle sending count was negative - shouldn't happen!");
				throttles[tnum].num_sending = 0;
			}
			c->tnums[c->numtnums++] = tnum;
			++throttles[tnum].num_sending;
			l = throttles[tnum].max_limit / throttles[tnum].num_sending;
			if (c->max_limit == THROTTLE_NOLIMIT)
				c->max_limit = l;
			else
				c->max_limit = MIN(c->max_limit, l);
			l = throttles[tnum].min_limit;
			if (c->min_limit == THROTTLE_NOLIMIT)
				c->min_limit = l;
			else
				c->min_limit = MAX(c->min_limit, l);
		}
	return 1;
}

static void clear_throttles(connecttab * c, struct timeval *tvP)
{
	int tind;

	for (tind = 0; tind < c->numtnums; ++tind)
		--throttles[c->tnums[tind]].num_sending;
}


static void update_throttles(ClientData client_data, struct timeval *nowP)
{
	int tnum, tind;
	int cnum;
	connecttab *c;
	long l;

	/* Update the average sending rate for each throttle.  This is only used
	 ** when new connections start up.
	 */
	for (tnum = 0; tnum < numthrottles; ++tnum)
	{
		throttles[tnum].rate =
			(2 * throttles[tnum].rate +
			 throttles[tnum].bytes_since_avg / THROTTLE_TIME) / 3;
		throttles[tnum].bytes_since_avg = 0;
		/* Log a warning message if necessary. */
		if (throttles[tnum].rate > throttles[tnum].max_limit
				&& throttles[tnum].num_sending != 0)
		{
			if (throttles[tnum].rate > throttles[tnum].max_limit * 2)
				syslog(LOG_NOTICE,
							 "throttle #%d '%.80s' rate %ld greatly exceeding limit %ld; %d sending",
							 tnum, throttles[tnum].pattern, throttles[tnum].rate,
							 throttles[tnum].max_limit, throttles[tnum].num_sending);
			else
				syslog(LOG_INFO,
							 "throttle #%d '%.80s' rate %ld exceeding limit %ld; %d sending",
							 tnum, throttles[tnum].pattern, throttles[tnum].rate,
							 throttles[tnum].max_limit, throttles[tnum].num_sending);
		}
		if (throttles[tnum].rate < throttles[tnum].min_limit
				&& throttles[tnum].num_sending != 0)
		{
			syslog(LOG_NOTICE,
						 "throttle #%d '%.80s' rate %ld lower than minimum %ld; %d sending",
						 tnum, throttles[tnum].pattern, throttles[tnum].rate,
						 throttles[tnum].min_limit, throttles[tnum].num_sending);
		}
	}

	/* Now update the sending rate on all the currently-sending connections,
	 ** redistributing it evenly.
	 */
	for (cnum = 0; cnum < max_connects; ++cnum)
	{
		c = &connects[cnum];
		if (c->conn_state == CNST_SENDING || c->conn_state == CNST_PAUSING)
		{
			c->max_limit = THROTTLE_NOLIMIT;
			for (tind = 0; tind < c->numtnums; ++tind)
			{
				tnum = c->tnums[tind];
				l = throttles[tnum].max_limit / throttles[tnum].num_sending;
				if (c->max_limit == THROTTLE_NOLIMIT)
					c->max_limit = l;
				else
					c->max_limit = MIN(c->max_limit, l);
			}
		}
	}
}


static void finish_connection(connecttab * c, struct timeval *tvP)
{
	/* If we haven't actually sent the buffered response yet, do so now. */
	httpd_write_response(c->hc);

	/* And clear. */
	clear_connection(c, tvP);
}


static void clear_connection(connecttab * c, struct timeval *tvP)
{
	ClientData client_data;

	if (c->wakeup_timer != (Timer *) 0)
	{
		tmr_cancel(c->wakeup_timer);
		c->wakeup_timer = 0;
	}

	/* This is our version of Apache's lingering_close() routine, which is
	 ** their version of the often-broken SO_LINGER socket option.  For why
	 ** this is necessary, see http://www.apache.org/docs/misc/fin_wait_2.html
	 ** What we do is delay the actual closing for a few seconds, while reading
	 ** any bytes that come over the connection.  However, we don't want to do
	 ** this unless it's necessary, because it ties up a connection slot and
	 ** file descriptor which means our maximum connection-handling rate
	 ** is lower.  So, elsewhere we set a flag when we detect the few
	 ** circumstances that make a lingering close necessary.  If the flag
	 ** isn't set we do the real close now.
	 */
	if (c->conn_state == CNST_LINGERING)
	{
		/* If we were already lingering, shut down for real. */
		tmr_cancel(c->linger_timer);
		c->linger_timer = (Timer *) 0;
		c->hc->should_linger = 0;
	}
	if (c->hc->should_linger)
	{
		if (c->conn_state != CNST_PAUSING)
			fdwatch_del_fd(c->hc->conn_fd);
		c->conn_state = CNST_LINGERING;
		shutdown(c->hc->conn_fd, SHUT_WR);
		fdwatch_add_fd(c->hc->conn_fd, c, FDW_READ);
		client_data.p = c;
		if (c->linger_timer != (Timer *) 0)
			syslog(LOG_ERR, "replacing non-null linger_timer!");
		c->linger_timer =
			tmr_create(tvP, linger_clear_connection, client_data, LINGER_TIME, 0);
		if (c->linger_timer == (Timer *) 0)
		{
			syslog(LOG_CRIT, "tmr_create(linger_clear_connection) failed");
			exit(1);
		}
	}
	else
		really_clear_connection(c, tvP);
}


static void really_clear_connection(connecttab * c, struct timeval *tvP)
{
	stats_bytes += c->hc->bytes_sent;
	if (c->conn_state != CNST_PAUSING)
		fdwatch_del_fd(c->hc->conn_fd);
	httpd_close_conn(c->hc, tvP);
	clear_throttles(c, tvP);
	if (c->linger_timer != (Timer *) 0)
	{
		tmr_cancel(c->linger_timer);
		c->linger_timer = 0;
	}
	c->conn_state = CNST_FREE;
	c->next_free_connect = first_free_connect;
	first_free_connect = c - connects;	/* division by sizeof is implied */
	--num_connects;
}


static void idle(ClientData client_data, struct timeval *nowP)
{
	int cnum;
	connecttab *c;

	for (cnum = 0; cnum < max_connects; ++cnum)
	{
		c = &connects[cnum];
		switch (c->conn_state)
		{
			case CNST_READING:
				if (nowP->tv_sec - c->active_at >= IDLE_READ_TIMELIMIT)
				{
					syslog(LOG_INFO,
								 "%.80s connection timed out reading",
								 httpd_ntoa(&c->hc->client_addr));
					httpd_send_err(c->hc, 408, httpd_err408title, "",
												 httpd_err408form, "");
					finish_connection(c, nowP);
				}
				break;
			case CNST_SENDING:
			case CNST_PAUSING:
				if (nowP->tv_sec - c->active_at >= IDLE_SEND_TIMELIMIT)
				{
					syslog(LOG_INFO,
								 "%.80s connection timed out sending",
								 httpd_ntoa(&c->hc->client_addr));
					clear_connection(c, nowP);
				}
				break;
		}
	}
}


static void wakeup_connection(ClientData client_data, struct timeval *nowP)
{
	connecttab *c;

	c = (connecttab *) client_data.p;
	c->wakeup_timer = (Timer *) 0;
	if (c->conn_state == CNST_PAUSING)
	{
		c->conn_state = CNST_SENDING;
		fdwatch_add_fd(c->hc->conn_fd, c, FDW_WRITE);
	}
}

static void
linger_clear_connection(ClientData client_data, struct timeval *nowP)
{
	connecttab *c;

	c = (connecttab *) client_data.p;
	c->linger_timer = (Timer *) 0;
	really_clear_connection(c, nowP);
}


static void occasional(ClientData client_data, struct timeval *nowP)
{
	//mmc_cleanup(nowP);
	tmr_cleanup();
	watchdog_flag = 1;						/* let the watchdog know that we are alive */
}


#ifdef STATS_TIME
static void show_stats(ClientData client_data, struct timeval *nowP)
{
	logstats(nowP);
}
#endif /* STATS_TIME */


/* Generate debugging statistics syslog messages for all packages. */
static void logstats(struct timeval *nowP)
{
	struct timeval tv;
	time_t now;
	long up_secs, stats_secs;

	if (nowP == (struct timeval *) 0)
	{
		(void) gettimeofday(&tv, (struct timezone *) 0);
		nowP = &tv;
	}
	now = nowP->tv_sec;
	up_secs = now - start_time;
	stats_secs = now - stats_time;
	if (stats_secs == 0)
		stats_secs = 1;							/* fudge */
	stats_time = now;
	syslog(LOG_INFO,
				 "up %ld seconds, stats for %ld seconds:", up_secs, stats_secs);

	thttpd_logstats(stats_secs);
	httpd_logstats(stats_secs);
	//mmc_logstats(stats_secs);
	fdwatch_logstats(stats_secs);
	tmr_logstats(stats_secs);
}


/* Generate debugging statistics syslog message. */
static void thttpd_logstats(long secs)
{
	if (secs > 0)
		syslog(LOG_INFO,
					 "  gb.httpd - %ld connections (%g/sec), %d max simultaneous, %lld bytes (%g/sec), %d httpd_conns allocated",
					 stats_connections, (float) stats_connections / secs,
					 stats_simultaneous, (int64_t) stats_bytes,
					 (float) stats_bytes / secs, httpd_conn_count);
	stats_connections = 0;
	stats_bytes = 0;
	stats_simultaneous = 0;
}
