/*
   Copyright (C) 2000-2006  Ulric Eriksson <ulric@siag.nu>

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston,
   MA 02111-1307, USA.
*/

#include "config.h"

#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <netdb.h>
#include <sys/types.h>
#include <assert.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <time.h>
#ifdef TIME_WITH_SYS_TIME
#include <sys/time.h>
#endif
#include <sys/wait.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/stat.h>
#ifdef HAVE_POLL
#include <sys/poll.h>
#endif	/* HAVE_POLL */
#ifdef HAVE_KQUEUE
#include <sys/event.h>
#endif
#include <fcntl.h>
#include <unistd.h>
#include <syslog.h>
#include <string.h>
#include <pwd.h>

#ifdef HAVE_SSL
#include <openssl/ssl.h>
#include <openssl/err.h>

#define SRV_SSL_V23 0
#define SRV_SSL_V2 1
#define SRV_SSL_V3 2
#define SRV_SSL_TLS1 3

static char ssl_compat;
static char require_peer_cert;
static char ssl_protocol;
static char *certfile;
static char *keyfile;
static char *cacert_dir;
static char *cacert_file;
static SSL_CTX *ssl_context = NULL;
#endif  /* HAVE_SSL */

#define BUFFER_MAX 	(32*1024)

#define CLIENTS_MAX	2048	/* max clients */
#define SERVERS_MAX	16	/* max servers */
#define ACLS_MAX	10	/* max acls */
#define CONNECTIONS_MAX	256	/* max simultaneous connections */
#define TIMEOUT		5	/* default timeout for non reachable hosts */
#define BLACKLIST_TIME	30	/* how long to shun a server that is down */
#define TRACKING_TIME	0	/* how long a client is remembered */
#define KEEP_MAX	100	/* how much to keep from the URI */

typedef struct {
	int downfd, upfd;
	unsigned char *downb, *downbptr, *upb, *upbptr;
	int downn, upn;
	int clt;
	int index;		/* server index */
#ifdef HAVE_KQUEUE
	int i;			/* connection index */
#endif
#ifdef HAVE_SSL
	SSL *ssl;
#endif
} connection;

typedef struct {
	int status;		/* last failed connection attempt */
	int acl;		/* which clients can use this server */
	int port;
	struct in_addr addr;
	int c;			/* connections */
	int weight;		/* default 1 */
	int maxc;		/* max connections, soft limit */
	int hard;		/* max connections, hard limit */
	unsigned long long sx, rx;	/* bytes sent, received */
} server;

typedef struct {
	time_t last;		/* last time this client made a connection */
	struct in_addr addr;	/* of client */
	int cno;		/* server used last time */
	long connects;
	long long csx, crx;
} client;

typedef struct {
	unsigned int ip, mask;
	int permit;
} acl;

static int nservers;		/* number of servers */
static int current;		/* current server */
static int nacls[ACLS_MAX];
static client *clients;
static server *servers;
static acl *acls[ACLS_MAX];
static int emerg_server = -1;	/* server of last resort */
static int emergency = 0;	/* are we using the emergency server? */
static int abuse_server = -1;	/* server for naughty clients */
static connection *conns;

static int debuglevel;
static int asciidump;
static int foreground;
static int loopflag;
static int exit_enabled = 0;


static int clients_max = CLIENTS_MAX;
static int servers_max = SERVERS_MAX;
static int connections_max = CONNECTIONS_MAX;
static int connections_used = 0;
static int connections_last = 0;
static int timeout = TIMEOUT;
static int blacklist_time = BLACKLIST_TIME;
static int tracking_time = TRACKING_TIME;
static int roundrobin = 0;
static int weight = 0;
static int hash = 0;
static int stubborn = 0;
static int nblock = 1;
static int delayed_forward = 0;
static int do_stats = 0;
static int do_restart_log = 0;
static int use_poll = 0;
static int use_kqueue = 0;
static int http = 0;
static int client_acl, control_acl;

static int port;

static char *cfgfile = NULL;
static char *logfile = NULL;
static FILE *logfp = NULL;
static struct sockaddr_in logserver;
static int logsock = -1;
static char *pidfile = NULL;
static FILE *pidfp = NULL;
static char *webfile = NULL;
static char *listenport = NULL;
static char *ctrlport = NULL;
static int listenfd, ctrlfd;
static char *e_server = NULL;
static char *a_server = NULL;
static char *jail = NULL;
static char *user = NULL;

static struct sigaction alrmaction, hupaction, termaction, usr1action;

static void debug(char *fmt, ...)
{
	time_t now;
	struct tm *nowtm;
	char nowstr[80];
	char b[4096];
	va_list ap;
	va_start(ap, fmt);
	vsnprintf(b, sizeof b, fmt, ap);
	now=time(NULL);
	nowtm = localtime(&now);
	strftime(nowstr, sizeof(nowstr), "%Y-%m-%d %H:%M:%S", nowtm);
	if (foreground) {
		fprintf(stderr, "%s: %s\n", nowstr, b);
	} else {
		openlog("pen", LOG_CONS, LOG_USER);
		syslog(LOG_DEBUG, "%s\n", b);
		closelog();
	}
	va_end(ap);
}

static void error(char *fmt, ...)
{
	char b[4096];
	va_list ap;
	va_start(ap, fmt);
	vsnprintf(b, sizeof b, fmt, ap);
	if (foreground) {
		fprintf(stderr, "%s\n", b);
	} else {
		openlog("pen", LOG_CONS, LOG_USER);
		syslog(LOG_ERR, "%s\n", b);
		closelog();
	}
	va_end(ap);
	exit(1);
}

static void *pen_malloc(size_t n)
{
	void *q = malloc(n);
	if (!q) error("Can't malloc %ld bytes", (long)n);
	return q;
}

static void *pen_calloc(size_t n, size_t s)
{
	void *q = calloc(n, s);
	if (!q) error("Can't calloc %ld bytes", (long)n*s);
	return q;
}

static void *pen_realloc(void *p, size_t n)
{
	void *q = realloc(p, n);
	if (!q) error("Can't realloc %ld bytes", (long)n);
	return q;
}

static char *pen_strdup(char *p)
{
	size_t len = strlen(p);
	char *b = pen_malloc(len+1);
	memcpy(b, p, len);
	b[len] = '\0';
	return b;
}

static int pen_strncasecmp(const char *p, const char *q, size_t n)
{
	size_t i = 0;
	int c = 0;

	while ((i < n) && !(c = toupper(*p)-toupper(*q)) && *p) {
		p++;
		q++;
		i++;
	}
	return c;
}

static char *pen_strcasestr(const char *haystack, const char *needle)
{
	char *p = (char *)haystack;
	int n = strlen(needle);

	while (*p) {
		if (!pen_strncasecmp(p, needle, n)) return p;
		p++;
	}
	return NULL;
}

#ifdef HAVE_SSL
static int ssl_verify_cb(int ok, X509_STORE_CTX *ctx)
{
	char buffer[256];

	X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert),
			buffer, sizeof(buffer));
	if (ok) {
		debug("SSL: Certificate OK: %s", buffer);
	} else {
		switch (ctx->error) {
		case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
			debug("SSL: Cert error: CA not known: %s", buffer);
			break;
		case X509_V_ERR_CERT_NOT_YET_VALID:
			debug("SSL: Cert error: Cert not yet valid: %s",
				buffer);
			break;
		case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
			debug("SSL: Cert error: illegal \'not before\' field: %s",
				buffer);
			break;
		case X509_V_ERR_CERT_HAS_EXPIRED:
			debug("SSL: Cert error: Cert expired: %s", buffer);
			break;
		case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
			debug("SSL: Cert error: invalid \'not after\' field: %s",
				buffer);
			break;
		default:
			debug("SSL: Cert error: unknown error %d in %s",
				ctx->error, buffer);
			break;
		}
	}
	return ok;
}

static RSA *ssl_temp_rsa_cb(SSL *ssl, int export, int keylength)
{
	static RSA *rsa = NULL;

	if (rsa == NULL)
		rsa = RSA_generate_key(512, RSA_F4, NULL, NULL);
	return rsa;
}

static int ssl_init(void)
{
	int err;

	SSL_load_error_strings();
	SSLeay_add_ssl_algorithms();
	switch (ssl_protocol) {
	case SRV_SSL_V2:
		ssl_context = SSL_CTX_new(SSLv2_method());
		break;
	case SRV_SSL_V3:
		ssl_context = SSL_CTX_new(SSLv3_method());
		break;
	default:
	case SRV_SSL_V23:
		ssl_context = SSL_CTX_new(SSLv23_method());
		break;
	case SRV_SSL_TLS1:
		ssl_context = SSL_CTX_new(TLSv1_method());
		break;
	}
	if (ssl_context == NULL) {
		err = ERR_get_error();
		error("SSL: Error allocating context: %s",
			ERR_error_string(err, NULL));
	}
	if (ssl_compat) {
		SSL_CTX_set_options(ssl_context, SSL_OP_ALL);
	}
	if (certfile == NULL || *certfile == 0) {
		debug("SSL: No cert file specified in config file!");
		error("The server MUST have a certificate!");
	}
	if (keyfile == NULL || *keyfile == 0)
		keyfile = certfile;
	if (certfile != NULL && *certfile != 0) {
		if (!SSL_CTX_use_certificate_file(ssl_context, certfile,
						SSL_FILETYPE_PEM)) {
			err = ERR_get_error();
			error("SSL: error reading certificate from file %s: %s",
				certfile, ERR_error_string(err, NULL));
		}
		if (!SSL_CTX_use_PrivateKey_file(ssl_context, keyfile,
						SSL_FILETYPE_PEM)) {
			err = ERR_get_error();
			error("SSL: error reading private key from file %s: %s",
				keyfile, ERR_error_string(err, NULL));
		}
		if (!SSL_CTX_check_private_key(ssl_context)) {
			error("SSL: Private key does not match public key in cert!");
		}
	}
	if (cacert_dir != NULL && *cacert_dir == 0)
		cacert_dir = NULL;
	if (cacert_file != NULL && *cacert_file == 0)
		cacert_file = NULL;
	if (cacert_dir != NULL || cacert_file != NULL) {
		if (!SSL_CTX_load_verify_locations(ssl_context,
					cacert_file, cacert_dir)) {
			err = ERR_get_error();
			debug("SSL: Error error setting CA cert locations: %s",
				ERR_error_string(err, NULL));
			cacert_file = cacert_dir = NULL;
		}
	}
	if (cacert_dir == NULL && cacert_file == NULL) {  /* no verify locations loaded */
		debug("SSL: No verify locations, trying default");
		if (!SSL_CTX_set_default_verify_paths(ssl_context)) {
			err = ERR_get_error();
			debug("SSL: Error error setting default CA cert location: %s",
				ERR_error_string(err, NULL));
			debug("continuing anyway...");
		}
	}
	SSL_CTX_set_tmp_rsa_callback(ssl_context, ssl_temp_rsa_cb);
	if (require_peer_cert) {
		SSL_CTX_set_verify(ssl_context,
			SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
			ssl_verify_cb);
	} else {
		SSL_CTX_set_verify(ssl_context,
			SSL_VERIFY_NONE,
			ssl_verify_cb);
	}

	SSL_CTX_set_client_CA_list(ssl_context,
			SSL_load_client_CA_file(certfile));

	return 0;
}

#endif  /* HAVE_SSL */

static void add_acl(int a, unsigned int ip, unsigned int mask, int permit)
{
	int i;

	if (debuglevel) {
		debug("add_acl(%d, %x, %x, %d)", a, ip, mask, permit);
	}
	if (a < 0 || a >= ACLS_MAX) {
		debug("add_acl: %d outside (0,%d)", a, ACLS_MAX);
		return;
	}
	i = nacls[a]++;
	acls[a] = pen_realloc(acls[a], nacls[a]*sizeof(acl));
	acls[a][i].ip = ip;
	acls[a][i].mask = mask;
	acls[a][i].permit = permit;
}

static void del_acl(int a)
{
	if (debuglevel) {
		debug("del_acl(%d)", a);
	}
	if (a < 0 || a >= ACLS_MAX) {
		debug("del_acl: %d outside (0,%d)", a, ACLS_MAX);
		return;
	}
	free(acls[a]);
	acls[a] = NULL;
	nacls[a] = 0;
}

static int match_acl(int a, unsigned int client)
{
	int i;
	int permit = 0;
	acl *ap = acls[a];
	if (debuglevel) {
		debug("match_acl(%d, %u)", a, client);
	}
	for (i = 0; i < nacls[a]; i++) {
		if ((client & ap[i].mask) == ap[i].ip) {
			return ap[i].permit;
		} else {
			permit = ap[i].permit;
		}
	}
	return !permit;
}

static void webstats(void)
{
	FILE *fp;
	int i;
	time_t now;
	struct tm *nowtm;
	char nowstr[80];

	fp = fopen(webfile, "w");
	if (fp == NULL) return;
	now=time(NULL);
	nowtm = localtime(&now);
	strftime(nowstr, sizeof(nowstr), "%Y-%m-%d %H:%M:%S", nowtm);
	fprintf(fp,
		"<html>\n"
		"<head>\n"
		"<title>Pen status page</title>\n"
		"</head>\n"
		"<body bgcolor=\"#ffffff\">"
		"<h1>Pen status page</h1>\n");
	fprintf(fp,
		"Time %s, %d servers, %d current<p>\n",
		nowstr, nservers, current);
	fprintf(fp,
		"<table bgcolor=\"#c0c0c0\">\n"
		"<tr>\n"
		"<td bgcolor=\"#80f080\">server</td>\n"
		"<td bgcolor=\"#80f080\">address</td>\n"
		"<td bgcolor=\"#80f080\">status</td>\n"
		"<td bgcolor=\"#80f080\">port</td>\n"
		"<td bgcolor=\"#80f080\">connections</td>\n"
		"<td bgcolor=\"#80f080\">max soft</td>\n"
		"<td bgcolor=\"#80f080\">max hard</td>\n"
		"<td bgcolor=\"#80f080\">sent</td>\n"
		"<td bgcolor=\"#80f080\">received</td>\n"
		"<td bgcolor=\"#80f080\">weight</td>\n"
		"</tr>\n");
	for (i = 0; i < nservers; i++) {
		fprintf(fp,
			"<tr>\n"
			"<td>%d</td>\n"
			"<td>%s</td>\n"
			"<td>%d</td>\n"
			"<td>%d</td>\n"
			"<td>%d</td>\n"
			"<td>%d</td>\n"
			"<td>%d</td>\n"
			"<td>%llu</td>\n"
			"<td>%llu</td>\n"
			"<td>%d</td>\n"
			"</tr>\n",
			i, inet_ntoa(servers[i].addr),
			servers[i].status, servers[i].port,
			servers[i].c, servers[i].maxc, servers[i].hard,
			servers[i].sx, servers[i].rx,
			servers[i].weight);
	}
	fprintf(fp, "</table>\n");

	fprintf(fp, "<h2>Active clients</h2>");
	fprintf(fp, "Max number of clients: %d<p>", clients_max);
	fprintf(fp,
		"<table bgcolor=\"#c0c0c0\">\n"
		"<tr>\n"
		"<td bgcolor=\"#80f080\">client</td>\n"
		"<td bgcolor=\"#80f080\">address</td>\n"
		"<td bgcolor=\"#80f080\">age(secs)</td>\n"
		"<td bgcolor=\"#80f080\">last server</td>\n"
		"<td bgcolor=\"#80f080\">connects</td>\n"
		"<td bgcolor=\"#80f080\">sent</td>\n"
		"<td bgcolor=\"#80f080\">received</td>\n"
		"</tr>\n");
	for (i = 0; i < clients_max; i++) {
		if (clients[i].last == 0) continue;
		fprintf(fp,
			"<tr>\n"
			"<td>%d</td>\n"
			"<td>%s</td>\n"
			"<td>%ld</td>\n"
			"<td>%d</td>\n"
			"<td>%ld</td>\n"
			"<td>%lld</td>\n"
			"<td>%lld</td>\n"
			"</tr>\n",
			i, inet_ntoa(clients[i].addr),
			(long)(now-clients[i].last), clients[i].cno, clients[i].connects,
			clients[i].csx, clients[i].crx);
	}
	fprintf(fp, "</table>\n");

	fprintf(fp, "<h2>Active connections</h2>");
	fprintf(fp, "Number of connections: %d max, %d used, %d last<p>",
		connections_max, connections_used, connections_last);
	fprintf(fp,
		"<table bgcolor=\"#c0c0c0\">\n"
		"<tr>\n"
		"<td bgcolor=\"#80f080\">connection</td>\n"
		"<td bgcolor=\"#80f080\">downfd</td>\n"
		"<td bgcolor=\"#80f080\">upfd</td>\n"
		"<td bgcolor=\"#80f080\">pending data down</td>\n"
		"<td bgcolor=\"#80f080\">pending data up</td>\n"
		"<td bgcolor=\"#80f080\">client</td>\n"
		"<td bgcolor=\"#80f080\">server</td>\n"
		"</tr>\n");
	for (i = 0; i < connections_max; i++) {
		if (conns[i].downfd == -1) continue;
		fprintf(fp,
			"<tr>\n"
			"<td>%d</td>\n"
			"<td>%d</td>\n"
			"<td>%d</td>\n"
			"<td>%d</td>\n"
			"<td>%d</td>\n"
			"<td>%d</td>\n"
			"<td>%d</td>\n"
			"</tr>\n",
			i, conns[i].downfd, conns[i].upfd,
			conns[i].downn, conns[i].upn,
			conns[i].clt, conns[i].index);
	}
	fprintf(fp, "</table>\n");
	fprintf(fp,
		"</body>\n"
		"</html>\n");
	fclose(fp);
}

static void textstats(void)
{
	int i;
	time_t now;
	struct tm *nowtm;
	char nowstr[80];

	now=time(NULL);
	nowtm = localtime(&now);
	strftime(nowstr, sizeof(nowstr), "%Y-%m-%d %H:%M:%S", nowtm);

	debug("Time %s, %d servers, %d current",
		nowstr, nservers, current);
	for (i = 0; i < nservers; i++) {
		debug("Server %d status:\n"
			"address %s\n"
			"%d\n"
			"port %d\n"
			"%d connections (%d soft, %d hard)\n"
			"%llu sent, %llu received\n",
			i, inet_ntoa(servers[i].addr),
			servers[i].status, servers[i].port,
			servers[i].c, servers[i].maxc, servers[i].hard,
			servers[i].sx, servers[i].rx);
	}
	debug("Max number of clients: %d", clients_max);
	debug("Active clients:");
	for (i = 0; i < clients_max; i++) {
		if (clients[i].last == 0) continue;
		debug("Client %d status:\n"
			"address %s\n"
			"last used %ld\n"
			"last server %d\n",
			"connects  %ld\n",
			"sent  %llu\n",
			"received  %llu\n",
			i, inet_ntoa(clients[i].addr),
			(long)(now-clients[i].last), clients[i].cno, clients[i].connects,
			clients[i].csx, clients[i].crx);
	}
	debug("Max number of connections: %d", connections_max);
	debug("Active connections:");
	for (i = 0; i < connections_max; i++) {
		if (conns[i].downfd == -1) continue;
		debug("Connection %d status:\n"
			"downfd = %d, upfd = %d\n"
			"pending data %d down, %d up\n"
			"client %d, server %d\n",
			i, conns[i].downfd, conns[i].upfd,
			conns[i].downn, conns[i].upn,
			conns[i].clt, conns[i].index);
	}
}


static void stats(int dummy)
{
	do_stats=1;
	sigaction(SIGUSR1, &usr1action, NULL);
}

static void restart_log(int dummy)
{
	do_restart_log=1;
	sigaction(SIGHUP, &hupaction, NULL);
}

static void quit(int dummy)
{
	loopflag = 0;
}

/* Return index of known client, otherwise -1 */
static int lookup_client(struct in_addr cli)
{
	int i;
	unsigned long ad = cli.s_addr;
	time_t now = time(NULL);

	for (i = 0; i < clients_max; i++) {
		if (clients[i].addr.s_addr == ad) break;
	}
	if (i == clients_max) i = -1;
	else if (tracking_time > 0 && clients[i].last+tracking_time < now) {
		/* too old, recycle */
		clients[i].last = 0;
		i = -1;
	}
	if (debuglevel) debug("Client %s has index %d", inet_ntoa(cli), i);
	return i;
}

/* Store client and return index */
static int store_client(int clino, struct in_addr cli, int ch)
{
	int i;
	int empty = -1;		/* first empty slot */
	int oldest = -1;	/* in case we need to recycle */

	if (clino == -1) {
		for (i = 0; i < clients_max; i++) {
			if (clients[i].addr.s_addr == cli.s_addr) break; /* XXX */
			if (empty != -1) continue;
			if (clients[i].last == 0) {
				empty = i;
				continue;
			}
			if (oldest == -1 || (clients[i].last < clients[oldest].last)) {
				oldest = i;
			}
		}
	
		if (i == clients_max) {
			if (empty != -1) i = empty;
			else i = oldest;
		}
		clients[i].connects = 0;
		clients[i].csx = 0;
		clients[i].crx = 0;
	}
	else 
		i = clino;

	clients[i].last = time(NULL);
	clients[i].addr = cli;
	clients[i].cno = ch;
	clients[i].connects++;

	if (debuglevel) {
		debug("Client %s has index %d and server %d",
			inet_ntoa(cli), i, ch);
	}
	servers[ch].c++;

	return i;
}

static void dump(unsigned char *p, int n)
{
	int i;

	fprintf(stderr, "%d: ", n);
	for (i = 0; i < n; i++) {
		if (asciidump) {
			fprintf(stderr, "%c",
				(isprint(p[i])||isspace(p[i]))?p[i]:'.');
		} else {
			fprintf(stderr, "%02x ", (int)p[i]);
		}
	}
	fprintf(stderr, "\n");
}


static int getport(char *p)
{
	struct servent *s = getservbyname(p, "tcp");
	if (s == NULL) {
		return atoi(p);
	} else {
		return ntohs(s->s_port);
	}
}

static void setipaddress(struct in_addr *a, char *p)
{
	struct hostent *h = gethostbyname(p);
	if (h == NULL) {
		if ((a->s_addr = inet_addr(p)) == -1) {
			error("unknown or invalid address [%s]\n", p);
		}
	} else {
		memcpy(a, h->h_addr, h->h_length);
	}
}

static void setaddress(struct in_addr *a, int *port, char *s,
		int dp, int *maxc, int *hard, int *weight)
{
	struct hostent *h;
	char address[1024], pno[100];
	int n = sscanf(s, "%999[^:]:%99[^:]:%d:%d:%d",
		address, pno, maxc, hard, weight);

	if (n > 1) *port = getport(pno);
	else *port = dp;
	if (n < 3) *maxc = 0;
	if (n < 4) *hard = 0;
	if (n < 5) *weight = 0;

	if (debuglevel)
		debug("n = %d, address = %s, pno = %d, maxc1 = %d, hard = %d, weight = %d",
			n, address, *port, *maxc, *hard, *weight);

	if (!(h = gethostbyname(address))) {
		if ((a->s_addr = inet_addr(address)) == -1) {
			error("unknown or invalid address [%s]\n", address);
		}
	} else {
		memcpy(a, h->h_addr, h->h_length);
	}
}

/* Log format is:

   + client_ip server_ip request
*/
static void netlog(int fd, int i, unsigned char *r, int n)
{
	int j, k;
	char b[1024];
	if (debuglevel) debug("netlog(%d, %d, %p, %d)", fd, i, r, n);
	strcpy(b, "+ ");
	k = 2;
	strcpy(b+k, inet_ntoa(clients[conns[i].clt].addr));
	k += strlen(b+k);
	b[k++] = ' ';
	strcpy(b+k, inet_ntoa(servers[conns[i].index].addr));
	k += strlen(b+k);
	b[k++] = ' ';

	/* We have already used k bytes from b. This means that we want
	   no more than (sizeof b-(k+1)) bytes from r. The +1 is for the
	   trailing newline.
	*/
	j = sizeof b-(k+1);
	if (n > j) n = j;
	for (j = 0; j < n && r[j] != '\r' && r[j] != '\n'; j++) {
		b[k++] = r[j];
	}
	b[k++] = '\n';
	sendto(fd, b, k, 0, (struct sockaddr *)&logserver, sizeof logserver);
}

/* Log format is:

    client_ip timestamp server_ip request
*/
static void log(FILE *fp, int i, unsigned char *b, int n)
{
	int j;
	if (n > KEEP_MAX) n = KEEP_MAX;
	fprintf(fp, "%s ", inet_ntoa(clients[conns[i].clt].addr));
	fprintf(fp, "%ld ", (long)time(NULL));
	fprintf(fp, "%s ", inet_ntoa(servers[conns[i].index].addr));
	for (j = 0; j < n && b[j] != '\r' && b[j] != '\n'; j++) {
		fprintf(fp, "%c", isascii(b[j])?b[j]:'.');
	}
	fprintf(fp, "\n");
}

static int rewrite_request(int i, int n, char *b)
{
	char *q;
	char p[BUFFER_MAX];
	int pl;

	b[n] = '\0';

	if (debuglevel > 1) debug("rewrite_request(%d, %d, %s)", i, n, b);

	if (pen_strncasecmp(b, "GET ", 4) &&
	    pen_strncasecmp(b, "POST ", 5) &&
	    pen_strncasecmp(b, "HEAD ", 5)) {
		return n;	/* You can't touch this */
	}
	if (debuglevel) debug("Looking for CRLFCRLF");
	q = strstr(b, "\r\n\r\n");
	/* Steve Hall <steveh@intrapower.com.au> tells me that
	   apparently some clients send \n\n instead */
	if (!q) {
		if (debuglevel) debug("Looking for LFLF");
		q = strstr(b, "\n\n");
	}
	if (!q) return n;		/* not a header */
#if 0	/* how is that supposed to happen? */
	if (q >= b+n) return n;		/* outside of buffer */
#endif
	/* Look for existing X-Forwarded-For */
	if (debuglevel) debug("Looking for X-Forwarded-For");

	if (pen_strcasestr(b, "\nX-Forwarded-For:")) return n;

	if (debuglevel) debug("Adding X-Forwarded-For");
	/* Didn't find one, add our own */
	sprintf(p, "\r\nX-Forwarded-For: %s",
		inet_ntoa(clients[conns[i].clt].addr));
	pl=strlen(p);
	if (n+pl > BUFFER_MAX) return n;

	memmove(q+pl, q, b+n-q);
	memmove(q, p, pl);

	n += pl;
	return n;
}

static int copy_up(int i)
{
	int rc;
	int from = conns[i].downfd;
	int to = conns[i].upfd;
	int serverindex = conns[i].index;

	char b[BUFFER_MAX];

#ifdef HAVE_SSL
	SSL *ssl = conns[i].ssl;

	if (ssl) {
		rc = SSL_read(ssl, b, BUFFER_MAX);
		if (rc < 0) {
			int err = SSL_get_error(ssl, rc);
			if (err == SSL_ERROR_WANT_READ ||
			    err == SSL_ERROR_WANT_WRITE) {
				return 0;
			}
		}
	} else {
		rc = read(from, b, BUFFER_MAX);
	}
#else
	rc = read(from, b, BUFFER_MAX);
#endif  /* HAVE_SSL */

	if (debuglevel > 1) debug("copy_up(%d) %d bytes", i, rc);

	if (rc <= 0) {
		return -1;
	} else {
		int n;

		if (http) {
			rc = rewrite_request(i, rc, b);
		}
		if (debuglevel > 2) dump(b, rc);

		if (logfp) {
			log(logfp, i, b, rc);
			if (debuglevel > 2) log(stderr, i, b, rc);
		}
		if (logsock != -1) {
			netlog(logsock, i, b, rc);
		}

		if (delayed_forward) n = 0;
		else n = write(to, b, rc);	/* no ssl here */

		if (n < 0) {
			if (!nblock || errno != EAGAIN) return -1;
			n = 0;
		}
		if (n != rc) {
			if (debuglevel > 1) {
				debug("copy_up saving %d bytes in up buffer",
					rc-n);
			}
			conns[i].upn = rc-n;
			conns[i].upbptr = conns[i].upb = pen_malloc(rc-n);
			memcpy(conns[i].upb, b+n, rc-n);
		}
		servers[serverindex].sx += rc;
		clients[conns[i].clt].crx += rc;
	}
	return 0;
}

static int copy_down(int i)
{
	int rc;
	int from = conns[i].upfd;
	int to = conns[i].downfd;
	int serverindex = conns[i].index;
#ifdef HAVE_SSL
	SSL *ssl = conns[i].ssl;
#endif

	char b[BUFFER_MAX];

	rc = read(from, b, BUFFER_MAX);	/* no ssl here */

	if (debuglevel > 1) debug("copy_down(%d) %d bytes", i, rc);
	if (debuglevel > 2) dump(b, rc);

	if (rc <= 0) {
		return -1;
	} else {
		int n;

		if (delayed_forward) {
			n = 0;
		} else {
#ifdef HAVE_SSL
			if (ssl) {
				n = SSL_write(ssl, b, rc);
				if (n < 0) {
					int err = SSL_get_error(ssl, n);
					if (err == SSL_ERROR_WANT_READ ||
					    err == SSL_ERROR_WANT_WRITE) {
						return 0;
					}
				}
			} else {
				n = write(to, b, rc);
			}
#else
			n = write(to, b, rc);
#endif
		}

		if (n < 0) {
			if (!nblock || errno != EAGAIN) return -1;
			n = 0;
		}
		if (n != rc) {
			if (debuglevel > 1) {
				debug("copy_down saving %d bytes in down buffer",
					rc-n);
			}
			conns[i].downn = rc-n;
			conns[i].downbptr = conns[i].downb = pen_malloc(rc-n);
			memcpy(conns[i].downb, b+n, rc-n);
		}
		servers[serverindex].rx += rc;
		clients[conns[i].clt].csx += n;
	}
	return 0;
}

static void alarm_handler(int dummy)
{
	if (debuglevel) debug("alarm_handler(%d)", dummy);
}

#ifdef HAVE_SSL
static void store_conn(int downfd, SSL *ssl, int upfd, int clt, int index)
#else
static void store_conn(int downfd, int upfd, int clt, int index)
#endif
{
	int i, fl;

	if (nblock) {
		if ((fl = fcntl(downfd, F_GETFL, 0)) == -1)
			error("Can't fcntl, errno = %d", errno);
		if (fcntl(downfd, F_SETFL, fl | O_NONBLOCK) == -1)
			error("Can't fcntl, errno = %d", errno);
		if ((fl = fcntl(upfd, F_GETFL, 0)) == -1)
			error("Can't fcntl, errno = %d", errno);
		if (fcntl(upfd, F_SETFL, fl | O_NONBLOCK) == -1)
			error("Can't fcntl, errno = %d", errno);
	}
	i = connections_last;
	do {
		if (conns[i].upfd == -1) break;
		i++;
		if (i >= connections_max) i = 0;
	} while (i != connections_last);
	/* The next test is not necessary, because we don't call
	   store_conn unless there are empty connection slots.
	   Keep the test anyway for now. */
	if (conns[i].upfd == -1) {
		connections_last = i;
		connections_used++;
		conns[i].upfd = upfd;
		conns[i].downfd = downfd;
#ifdef HAVE_SSL
		conns[i].ssl = ssl;
#endif
		conns[i].clt = clt;
		conns[i].index = index;
#ifdef HAVE_KQUEUE
		/* kqueue can store a pointer to arbitrary data with each
		 * event, but it must be a pointer. We store a pointer to
		 * conns[i]. copy_up/down then require i again, which we
		 * can't get without storing i within the struct itself. */
		conns[i].i = i;
#endif
		current = index;
	} else {
		if (debuglevel)
			debug("Connection table full (%d slots), can't store connection.\n"
			      "Try restarting with -x %d",
			      connections_max, 2*connections_max);
		close(downfd);
		close(upfd);
	}
	if (debuglevel) debug("store_conn: connections_used = %d",
				connections_used);
}

static void close_conn(int i)
{
	int index = conns[i].index;
	servers[index].c -= 1;
	if (conns[i].upfd > 0) close(conns[i].upfd);
	if (conns[i].downfd > 0) close(conns[i].downfd);
	conns[i].upfd = conns[i].downfd = -1;
	if (conns[i].downn) {
		free(conns[i].downb);
		conns[i].downn=0;
	}
	if (conns[i].upn) {
		free(conns[i].upb);
		conns[i].upn=0;
	}
#ifdef HAVE_SSL
	if (conns[i].ssl) {
		SSL_free(conns[i].ssl);
		conns[i].ssl = 0;
	}
#endif
	connections_used--;
	if (debuglevel) debug("close_conn: connections_used = %d",
				connections_used);
}


static void usage(void)
{
	printf("usage:\n"
	       "  pen [-C addr:port] [-X] [-b sec] [-S N] [-c N] [-e host[:port]] \\\n"
	       "	  [-t sec] [-x N] [-w dir] [-HPWadfhrs] \\\n"
	       "          [-o option] \\\n"
	       "	  [-E certfile] [-K keyfile] \\\n"
	       "	  [-G cacertfile] [-A cacertdir] \\\n"
	       "	  [-Z] [-R] [-L protocol] \\\n"
	       "	  [host:]port h1[:p1[:maxc1[:hard1[:weight1]]]] [h2[:p2[:maxc2[:hard2[:weight2]]]]] ...\n"
	       "\n"
	       "  -B host:port abuse server for naughty clients\n"
	       "  -C port   control port\n"
	       "  -T sec    tracking time in seconds (0 = forever) [%d]\n"
	       "  -H	add X-Forwarded-For header in http requests\n"
	       "  -P	use poll() rather than select()\n"
	       "  -W    use weight for server selection\n"
	       "  -X	enable 'exit' command for control port\n"
	       "  -a	debugging dumps in ascii format\n"
	       "  -b sec    blacklist time in seconds [%d]\n"
	       "  -S N      max number of servers [%d]\n"
	       "  -c N      max number of clients [%d]\n"
	       "  -d	debugging on (repeat -d for more)\n"
	       "  -e host:port emergency server of last resort\n"
	       "  -f	stay in foregound\n"
	       "  -h	use hash for initial server selection\n"
	       "  -j dir    run in chroot\n"
	       "  -F file   name of configuration file\n"
	       "  -l file   logging on\n"
	       "  -n	do not make sockets nonblocking\n"
	       "  -r	bypass client tracking in server selection\n"
	       "  -s	stubborn selection, i.e. don't fail over\n"
	       "  -t sec    connect timeout in seconds [%d]\n"
	       "  -u user   run as alternative user\n"
	       "  -p file   write pid to file\n"
	       "  -x N      max number of simultaneous connections [%d]\n"
	       "  -w file   save statistics in HTML format in a file\n"
	       "  -o option use option in penctl format\n"
	       "  -E certfile   use the given certificate in PEM format\n"
	       "  -K keyfile    use the given key in PEM format (may be contained in cert)\n"
	       "  -G cacertfile file containing the CA's certificate\n"
	       "  -A cacertdir  directory containing CA certificates in hashed format\n"
	       "  -Z	    use SSL compatibility mode\n"
	       "  -R	    require valid peer certificate\n"
	       "  -L protocol   ssl23 (default), ssl2, ssl3 or tls1\n"
	       "\n"
	       "example:\n"
	       "  pen smtp mailhost1:smtp mailhost2:25 mailhost3\n"
	       "\n",
	       TRACKING_TIME, BLACKLIST_TIME, SERVERS_MAX, CLIENTS_MAX, TIMEOUT, CONNECTIONS_MAX);

	exit(0);
}

static void background(void)
{
#ifdef HAVE_DAEMON
	daemon(0, 0);
#else
	int childpid;
	if ((childpid = fork()) < 0) {
		error("Can't fork");
	} else {
		if (childpid > 0) {
			exit(0);	/* parent */
		}
	}
	setsid();
	signal(SIGCHLD, SIG_IGN);
#endif
}

static void init(int argc, char **argv)
{
	int i;
	int server;

	conns = pen_calloc(connections_max, sizeof *conns);
	clients = pen_calloc(clients_max, sizeof *clients);
	/* one extra server slot for the emergency server */
	/* and one for the abuse server */
	/* Check that servers_max is big enough for the command line */
	if ((argc-1) > servers_max) {
		debug("command line specifies %d servers, max is %d; attempting to compensate",
			argc-1, servers_max);
		servers_max = argc-1;
	}
	servers = pen_calloc(servers_max+2, sizeof *servers);

	nservers = 0;
	current = 0;

	server = 0;

	for (i = 1; i < argc; i++) {
		servers[server].status = 0;
		servers[server].c = 0;	/* connections... */
		setaddress(&servers[server].addr, &servers[server].port,
			argv[i], port,
			&servers[server].maxc, &servers[server].hard,
			&servers[server].weight);
		servers[server].sx = 0;
		servers[server].rx = 0;

		nservers++;
		server++;
	}
	while (nservers < servers_max) {
		servers[server].status = 0;
		servers[server].c = 0;	/* connections... */
		setaddress(&servers[server].addr, &servers[server].port,
			"0.0.0.0", 0,
			&servers[server].maxc, &servers[server].hard,
			&servers[server].weight);
		servers[server].sx = 0;
		servers[server].rx = 0;

		nservers++;
		server++;
	}
	if (e_server) {
		emerg_server = server;
		servers[server].status = 0;
		servers[server].c = 0;	/* connections... */
		setaddress(&servers[server].addr, &servers[server].port,
			   e_server, port,
			   &servers[server].maxc, &servers[server].hard,
			   &servers[server].weight);
		servers[server].sx = 0;
		servers[server].rx = 0;
		server++;
	}

	if (a_server) {
		abuse_server = server;
		servers[server].status = 0;
		servers[server].c = 0;	/* connections... */
		setaddress(&servers[server].addr, &servers[server].port,
			   a_server, port,
			   &servers[server].maxc, &servers[server].hard,
			   &servers[server].weight);
		servers[server].sx = 0;
		servers[server].rx = 0;
		server++;
	}

	for (i = 0; i < clients_max; i++) {
		clients[i].last = 0;
		clients[i].addr.s_addr = 0;
		clients[i].cno = 0;
		clients[i].connects = 0;
		clients[i].csx = 0;
		clients[i].crx = 0;
	}
	for (i = 0; i < connections_max; i++) {
		conns[i].upfd = -1;
		conns[i].downfd = -1;
		conns[i].upn = 0;
		conns[i].downn = 0;
	}

	if (debuglevel) {
		debug("servers:");
		for (i = 0; i < nservers; i++) {
			debug("%2d %s:%d:%d:%d", i,
				inet_ntoa(servers[i].addr), servers[i].port,
				servers[i].maxc, servers[i].hard);
		}
	}
}

/* return upstream file descriptor */
/* sticky = 1 if this client has used the server before */
static int try_server(int index, int sticky, unsigned int cli_addr)
{
	struct sockaddr_in serv_addr;
	int upfd;
	int n;
	int now = (int)time(NULL);
	if (debuglevel) debug("Trying server %d at time %d", index, now);
	if (servers[index].port == 0) {
		if (debuglevel) debug("No port for you!");
		return -1;
	}
	if (now-servers[index].status < blacklist_time) {
		if (debuglevel) debug("Server %d is blacklisted", index);
		return -1;
	}
	if (servers[index].maxc != 0 &&
	    (servers[index].c >= servers[index].maxc) &&
	    (sticky == 0 || servers[index].c >= servers[index].hard)) {
		if (debuglevel)
			debug("Server %d is overloaded: sticky=%d, maxc=%d, hard=%d",
				index, sticky,
				servers[index].maxc, servers[index].hard);
		return -1;
	}
	if (!match_acl(servers[index].acl, cli_addr)) {
		if (debuglevel) debug("try_server: denied by acl");
		return -1;
	}
	upfd = socket(AF_INET, SOCK_STREAM, 0);
	if (upfd < 0) error("Error opening socket: %d", errno);
	memset(&serv_addr, 0, sizeof serv_addr);
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = servers[index].addr.s_addr;
	serv_addr.sin_port = htons(servers[index].port);
	sigaction(SIGALRM, &alrmaction, NULL);
	alarm(timeout);
	n = connect(upfd, (struct sockaddr *)&serv_addr, sizeof serv_addr);
	alarm(0);	/* cancel scheduled timeout, if there is one */
	if (n == -1) {
		if (servers[index].status == 0)
			debug("Server %d failed, retry in %d sec: %s", index, blacklist_time, strerror(errno));
		servers[index].status = (int)time(NULL);
		close(upfd);
		return -1;
	}
	if (servers[index].status) {
		servers[index].status=0;
		debug("Server %d ok", index);
	}
	if (debuglevel) debug("Successful connect to server %d", index);
	return upfd;
}

static void open_log(char *logfile)
{
	if (logfp) {
		fclose(logfp);
		logfp = NULL;
	}
	if (logsock >= 0) {
		close(logsock);
		logsock = -1;
	}
	if (logfile) {
		char *p = strchr(logfile, ':');
		if (p && logfile[0] != '/') {	/* log to net */
			struct hostent *hp;
			if (debuglevel) debug("net log to %s", logfile);
			*p++ = '\0';
			logsock = socket(PF_INET, SOCK_DGRAM, 0);
			if (logsock < 0) error("Can't create log socket");
			logserver.sin_family = AF_INET;
			hp = gethostbyname(logfile);
			if (hp == NULL) error("Bogus host %s", logfile);
			memcpy(&logserver.sin_addr.s_addr,
				hp->h_addr, hp->h_length);
			logserver.sin_port = htons(atoi(p));
		} else {	/* log to file */
			if (debuglevel) debug("file log to %s", logfile);
			logfp = fopen(logfile, "a");
			if (!logfp) error("Can't open logfile %s", logfile);
		}
	}
}

static void read_cfg(char *);

static void write_cfg(char *p)
{
	int i, j;
	struct in_addr ip;
	time_t now;
	struct tm *nowtm;
	char nowstr[80];
	FILE *fp = fopen(p, "w");
	if (!fp) debug("Can't open file '%s'", p);
	now = time(NULL);
	nowtm = localtime(&now);
	strftime(nowstr, sizeof(nowstr), "%Y-%m-%d %H:%M:%S", nowtm);
	fprintf(fp, "# Generated by pen %s\n", nowstr);
	fprintf(fp, "# pen");
	if (foreground) fprintf(fp, " -f");
	if (exit_enabled) fprintf(fp, " -X");
	if (servers_max != SERVERS_MAX)
		fprintf(fp, " -s %d", servers_max);
	if (clients_max != CLIENTS_MAX)
		fprintf(fp, " -c %d", clients_max);
	if (e_server) fprintf(fp, " -e %s", e_server);
	if (a_server) fprintf(fp, " -B %s", a_server);
	if (jail) fprintf(fp, " -j '%s'", jail);
	if (pidfile) fprintf(fp, " -p '%s'", pidfile);
	if (user) fprintf(fp, " -u '%s'", user);
	if (connections_max != CONNECTIONS_MAX)
		fprintf(fp, " -x %d", connections_max);
	fprintf(fp, " -F '%s' -C %s %s\n", p, ctrlport, listenport);
	for (i = 0; i < ACLS_MAX; i++) {
		fprintf(fp, "no acl %d\n", i);
		for (j = 0; j < nacls[i]; j++) {
			memcpy(&ip, &acls[i][j].ip, 4);
			fprintf(fp, "acl %d %s %s ", i,
				acls[i][j].permit?"permit":"deny",
				inet_ntoa(ip));
			memcpy(&ip, &acls[i][j].mask, 4);
			fprintf(fp, "%s\n", inet_ntoa(ip));
		}
	}
	if (asciidump) fprintf(fp, "ascii\n");
	else fprintf(fp, "no ascii\n");
	fprintf(fp, "blacklist %d\n", blacklist_time);
	if (nblock) fprintf(fp, "no block\n");
	else fprintf(fp, "block\n");
	fprintf(fp, "client_acl %d\n", client_acl);
	fprintf(fp, "control_acl %d\n", control_acl);
	fprintf(fp, "debug %d\n", debuglevel);
	if (delayed_forward) fprintf(fp, "delayed_forward\n");
	else fprintf(fp, "no delayed_forward\n");
	if (hash) fprintf(fp, "hash\n");
	else fprintf(fp, "no hash\n");
	if (http) fprintf(fp, "http\n");
	else fprintf(fp, "no http\n");
	if (logfile) fprintf(fp, "log %s\n", logfile);
	else fprintf(fp, "no log\n");
	if (roundrobin) fprintf(fp, "roundrobin\n");
	else fprintf(fp, "no roundrobin\n");
	for (i = 0; i < nservers; i++) {
		fprintf(fp,
			"server %d acl %d address %s port %d max %d hard %d\n",
			i, servers[i].acl,
			inet_ntoa(servers[i].addr), servers[i].port,
			servers[i].maxc, servers[i].hard);
	}
	if (stubborn) fprintf(fp, "stubborn\n");
	else fprintf(fp, "no stubborn\n");
	fprintf(fp, "timeout %d\n", timeout);
	fprintf(fp, "tracking %d\n", tracking_time);
	if (webfile) fprintf(fp, "web_stats %s\n", webfile);
	else fprintf(fp, "no web_stats\n");
	fclose(fp);
}

static void do_cmd(char *b, void (*output)(char *, void *), void *op)
{
	char *p, *q;
	int n, fl;
	FILE *fp;

	if (debuglevel) {
		debug("do_cmd(%s, %p, %p)", b, output, op);
	}
	p = strchr(b, '\r');
	if (p) *p = '\0';
	p = strchr(b, '\n');
	if (p) *p = '\0';
	p = strtok(b, " ");
	if (p == NULL) return;
	if (!strcmp(p, "acl")) {
		char *no, *pd, *ip, *ma;
		/* acl N permit|deny ipaddr [mask] */
		if ((no = strtok(NULL, " ")) &&
		    (pd = strtok(NULL, " ")) &&
		    (ip = strtok(NULL, " "))) {
			int a = atoi(no);
			int permit;
			struct in_addr ipaddr, mask;
			ma = strtok(NULL, " ");
			if (!ma) ma = "255.255.255.255";
			if (!strcmp(pd, "permit")) permit = 1;
			else if (!strcmp(pd, "deny")) permit = 0;
			else {
				debug("acl: expected permit|deny");
				return;
			}
			if (!inet_aton(ip, &ipaddr)) {
				debug("acl: bogus address '%s'\n", ip);
				return;
			}
			if (!inet_aton(ma, &mask)) {
				debug("acl: bogus mask '%s'\n", ma);
				return;
			}
			add_acl(a, ipaddr.s_addr, mask.s_addr, permit);
		}
	} else if (!strcmp(p, "ascii")) {
		asciidump = 1;
	} else if (!strcmp(p, "blacklist")) {
		p = strtok(NULL, " ");
		if (p) blacklist_time = atoi(p);
		sprintf(b, "%d\n", blacklist_time);
		output(b, op);
	} else if (!strcmp(p, "block")) {
		nblock = 0;
		fl = fcntl(listenfd, F_GETFL, 0);
		fcntl(listenfd, F_SETFL, fl & ~O_NONBLOCK);
		fl = fcntl(ctrlfd, F_GETFL, 0);
		fcntl(ctrlfd, F_SETFL, fl & ~O_NONBLOCK);
	} else if (!strcmp(p, "client_acl")) {
		p = strtok(NULL, " ");
		if (p) client_acl = atoi(p);
		if (client_acl < 0 || client_acl >= ACLS_MAX)
			client_acl = 0;
		sprintf(b, "%d\n", client_acl);
		output(b, op);
	} else if (!strcmp(p, "clients_max")) {
		sprintf(b, "%d\n", clients_max);
		output(b, op);
	} else if (!strcmp(p, "conn_max")) {
		sprintf(b, "%d\n", connections_max);
		output(b, op);
	} else if (!strcmp(p, "control")) {
		sprintf(b, "%s\n", ctrlport);
		output(b, op);
	} else if (!strcmp(p, "control_acl")) {
		p = strtok(NULL, " ");
		if (p) control_acl = atoi(p);
		if (control_acl < 0 || control_acl >= ACLS_MAX)
			control_acl = 0;
		sprintf(b, "%d\n", control_acl);
		output(b, op);
	} else if (!strcmp(p, "debug")) {
		p = strtok(NULL, " ");
		if (p) debuglevel = atoi(p);
		sprintf(b, "%d\n", debuglevel);
		output(b, op);
	} else if (!strcmp(p, "delayed_forward")) {
		delayed_forward = 1;
	} else if (!strcmp(p, "exit")) {
		if (exit_enabled) {
			quit(0);
		} else {
			sprintf(b,
				"Exit is not enabled; restart with -X flag\n");
			output(b, op);
		}
	} else if (!strcmp(p, "hash")) {
		hash = 1;
	} else if (!strcmp(p, "http")) {
		http = 1;
	} else if (!strcmp(p, "include")) {
		p = strtok(NULL, " ");
		if (p) {
			read_cfg(p);
		} else {
			debug("Usage: include filename");
		}
	} else if (!strcmp(p, "listen")) {
		sprintf(b, "%s\n", listenport);
		output(b, op);
	} else if (!strcmp(p, "log")) {
		p = strtok(NULL, " ");
		if (p) {
			free(logfile);
			logfile = pen_strdup(p);
			open_log(logfile);
		}
		if (logfile) {
			sprintf(b, "%s\n", logfile);
			output(b, op);
		}
	} else if (!strcmp(p, "mode")) {
		sprintf(b, "%sblock %sdelayed_forward %shash %sroundrobin %sstubborn\n",
			nblock?"no ":"",
			delayed_forward?"":"no ",
			hash?"":"no ",
			roundrobin?"":"no ",
			stubborn?"":"no ");
		output(b, op);
	} else if (!strcmp(p, "no")) {
		p = strtok(NULL, " ");
		if (p == NULL) return;
		if (!strcmp(p, "acl")) {
			int a;
			p = strtok(NULL, " ");
			a = atoi(p);
			del_acl(a);
		} else if (!strcmp(p, "ascii")) {
			asciidump = 0;
		} else if (!strcmp(p, "block")) {
			nblock = 1;
			fl = fcntl(listenfd, F_GETFL, 0);
			fcntl(listenfd, F_SETFL, fl | O_NONBLOCK);
			fl = fcntl(ctrlfd, F_GETFL, 0);
			fcntl(ctrlfd, F_SETFL, fl | O_NONBLOCK);
		} else if (!strcmp(p, "delayed_forward")) {
			delayed_forward = 0;
		} else if (!strcmp(p, "hash")) {
			hash = 0;
		} else if (!strcmp(p, "http")) {
			http = 0;
		} else if (!strcmp(p, "log")) {
			logfile = NULL;
			if (logfp) fclose(logfp);
			logfp = NULL;
		} else if (!strcmp(p, "roundrobin")) {
			roundrobin = 0;
		} else if (!strcmp(p, "stubborn")) {
			stubborn = 0;
		} else if (!strcmp(p, "web_stats")) {
			webfile = NULL;
		} else if (!strcmp(p, "weight")) {
			weight = 0;
		}
	} else if (!strcmp(p, "pid")) {
		sprintf(b, "%ld\n", (long)getpid());
		output(b, op);
	} else if (!strcmp(p, "recent")) {
		time_t when = time(NULL);
		p = strtok(NULL, " ");
		if (p) when -= atoi(p);
		else when -= 300;
		for (n = 0; n < clients_max; n++) {
			if (clients[n].last < when) continue;
			sprintf(b, "%s connects %ld sx %lld rx %lld\n",
				inet_ntoa(clients[n].addr),
				clients[n].connects,
				clients[n].csx, clients[n].crx);
			output(b, op);
		}
	} else if (!strcmp(p, "roundrobin")) {
		roundrobin = 1;
	} else if (!strcmp(p, "server")) {
		p = strtok(NULL, " ");
		if (p == NULL) return;
		n = atoi(p);
		if (n < 0 || n >= nservers) return;
		while ((p = strtok(NULL, " ")) && (q = strtok(NULL, " "))) {
			if (!strcmp(p, "acl")) {
				servers[n].acl = atoi(q);
			} else if (!strcmp(p, "address")) {
				setipaddress(&servers[n].addr, q);
			} else if (!strcmp(p, "port")) {
				servers[n].port = atoi(q);
			} else if (!strcmp(p, "max")) {
				servers[n].maxc = atoi(q);
			} else if (!strcmp(p, "hard")) {
				servers[n].hard = atoi(q);
			} else if (!strcmp(p, "blacklist")) {
				servers[n].status = time(NULL)+atoi(q)-blacklist_time;
			} else if (!strcmp(p, "weight")) {
				servers[n].weight = atoi(q);
			}
		}
	} else if (!strcmp(p, "servers")) {
		for (n = 0; n < nservers; n++) {
			sprintf(b, "%d addr %s port %d conn %d max %d hard %d weight %d sx %llu rx %llu\n",
				n, inet_ntoa(servers[n].addr), servers[n].port,
				servers[n].c, servers[n].maxc, servers[n].hard,
				servers[n].weight,
				servers[n].sx, servers[n].rx);
			output(b, op);
		}
	} else if (!strcmp(p, "status")) {
		p = webfile;
		webfile = "/tmp/webfile.html";
		webstats();
		fp = fopen(webfile, "r");
		webfile = p;
		if (fp == NULL) return;
		while (fgets(b, sizeof b, fp)) {
			output(b, op);
		}
		fclose(fp);
	} else if (!strcmp(p, "stubborn")) {
		stubborn = 1;
	} else if (!strcmp(p, "timeout")) {
		p = strtok(NULL, " ");
		if (p) timeout = atoi(p);
		sprintf(b, "%d\n", timeout);
		output(b, op);
	} else if (!strcmp(p, "tracking")) {
		p = strtok(NULL, " ");
		if (p) tracking_time = atoi(p);
		sprintf(b, "%d\n", tracking_time);
		output(b, op);
	} else if (!strcmp(p, "web_stats")) {
		p = strtok(NULL, " ");
		if (p) {
			free(webfile);
			webfile = pen_strdup(p);
		}
		if (webfile) {
			sprintf(b, "%s\n", webfile);
			output(b, op);
		}
	} else if (!strcmp(p, "weight")) {
		weight = 1;
	} else if (!strcmp(p, "write")) {
		p = strtok(NULL, " ");
		if (!p) p = cfgfile;
		if (p) {
			write_cfg(p);
		} else {
			debug("write: no file");
		}
	} else {
		debug("do_cmd: ignoring command starting with '%s'", p);
	}
}

static void output_net(char *b, void *op)
{
	int *fp = op;
	write(*fp, b, strlen(b));
}

static void output_file(char *b, void *op)
{
	FILE *fp = op;
	fputs(b, fp);
}

static void do_ctrl(int downfd, struct sockaddr_in *cli_addr)
{
	char b[4096];
	int n, max_b = sizeof b;

	if (!match_acl(control_acl, cli_addr->sin_addr.s_addr)) {
		debug("do_ctrl: not from there");
	} else {
		n = read(downfd, b, max_b-1);
		if (n != -1) {
			b[n] = '\0';
			do_cmd(b, output_net, &downfd);
		}
	}
	close(downfd);
}

static void read_cfg(char *cf)
{
	FILE *fp;
	char b[4096];

	if (cf == NULL) return;

	fp = fopen(cf, "r");
	if (fp == NULL) {
		debug("Can't read config file '%s'\n", cf);
		return;
	}
	while (fgets(b, sizeof b, fp)) {
		do_cmd(b, output_file, stdout);
	}
	fclose(fp);
}

static int server_by_weight(void)
{
	int best_server = -1;
	int best_load = -1;
	int i, load;
	int now = (int)time(NULL);

	if (debuglevel) debug("server_by_weight()");
	for (i = 0; i < nservers; i++) {
		if (now-servers[i].status < blacklist_time || servers[i].weight == 0) {
			continue;
		}
		load = servers[i].c/servers[i].weight;
		if (best_server == -1 || load < best_load) {
			if (debuglevel)
				debug("Server %d has load %d",
					i, load);
			best_load = load;
			best_server = i;
		}
	}
	if (debuglevel) debug("Least loaded server = %d", best_server);
	return best_server;
}

static void add_client(int downfd, struct sockaddr_in *cli_addr)
{
	int upfd = -1, clino = -1, index = -1, n, pd;
	unsigned int cli = cli_addr->sin_addr.s_addr;
#ifdef HAVE_SSL
	SSL *ssl = NULL;

	/* check the ssl stuff before picking servers */
	if (ssl_context) {
		ssl = SSL_new(ssl_context);
		if (ssl == NULL) {
			int err = ERR_get_error();
			debug("SSL: error allocating handle: %s",
				ERR_error_string(err, NULL));
			goto Failure;
		}
		SSL_set_fd(ssl, downfd);
		SSL_set_accept_state(ssl);
	}
#endif

	pd = match_acl(client_acl, cli);
	if (!pd) {
		if (debuglevel) debug("add_client: denied by acl");
		if (abuse_server != -1) {
			upfd = try_server(abuse_server, 0, cli);
			if (upfd != -1) goto Success;
		}
		goto Failure;
	}

	if (roundrobin) {
		if (debuglevel) debug("Bypassing client tracking");
	} else {
		clino = lookup_client(cli_addr->sin_addr);
		if (debuglevel) debug("lookup_client returns %d", clino);
		if (clino != -1) {
			index = clients[clino].cno;
			if (index != emerg_server && index != abuse_server) {
				upfd = try_server(index, 1, cli);
				if (upfd != -1) goto Success;
			}
		}
	}
	if (weight) {
		index = server_by_weight();
		if (index != -1) {
			upfd = try_server(index, 0, cli);
			if (upfd != -1) goto Success;
		}
	}
	if (hash) {
		index = cli % nservers;
		upfd = try_server(index, 0, cli);
		if (upfd != -1) goto Success;
	}

	if (!stubborn) {
		index = current;
		do {
			index = (index + 1) % nservers;
			if ((upfd = try_server(index, 0, cli)) != -1) goto Success;
		} while (index != current);
	}
	/* if we get here, we're dead */
	if (emerg_server != -1) {
		if (!emergency) 
			debug("Using emergency server");
		emergency=1;
		if ((upfd = try_server(emerg_server, 0, cli)) != -1) goto Success2;
	}
	debug("Couldn't find a server for client");
Failure:
	if (downfd != -1) close(downfd);
	if (upfd != -1) close(upfd);
	return;
Success:
	emergency=0;
Success2:
	n = store_client(clino, cli_addr->sin_addr, index);
#ifdef HAVE_SSL
	store_conn(downfd, ssl, upfd, n, index);
#else
	store_conn(downfd, upfd, n, index);
#endif
	return;
}

static int open_listener(char *a)
{
	int listenfd;
	struct sockaddr_in serv_addr;
	char b[1024], *p;
	int one = 1;
	int fl;

	memset(&serv_addr, 0, sizeof serv_addr);
	serv_addr.sin_family = AF_INET;
	p = strchr(a, ':');
	if (p) {
		strncpy(b, a, sizeof b);
		b[sizeof b-1] = '\0';
		p = strchr(b, ':');
		*p = '\0';
		port = getport(p+1);
		setipaddress(&serv_addr.sin_addr, b);
		snprintf(b, (sizeof(b) - 1), "%s", inet_ntoa(serv_addr.sin_addr));
	} else {
		port = getport(a);
		serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
		sprintf(b, "0.0.0.0");
	}
	serv_addr.sin_port = htons(port);

	if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		error("can't open stream socket");
	}

	if (debuglevel) debug("local address=[%s:%d]", b, port);

	setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof one);
	if (bind(listenfd, (struct sockaddr *) &serv_addr,
		 sizeof serv_addr) < 0) {
		error("can't bind local address");
	}

	if (nblock) {
		if ((fl = fcntl(listenfd, F_GETFL, 0)) == -1)
			error("Can't fcntl, errno = %d", errno);
		if (fcntl(listenfd, F_SETFL, fl | O_NONBLOCK) == -1)
			error("Can't fcntl, errno = %d", errno);
	}
	listen(listenfd, 50);
	return listenfd;
}

static int flush_down(int i)
{
	int n;

#ifdef HAVE_SSL
	SSL *ssl = conns[i].ssl;

	if (ssl) {
		n = SSL_write(ssl, conns[i].downbptr, conns[i].downn);
		if (n < 0) {
			int err = SSL_get_error(ssl, n);
			if (err == SSL_ERROR_WANT_READ ||
			    err == SSL_ERROR_WANT_WRITE) {
				return 0;
			}
		}
	} else {
		n = write(conns[i].downfd, conns[i].downbptr, conns[i].downn);
	}
#else
	n = write(conns[i].downfd, conns[i].downbptr, conns[i].downn);
#endif  /* HAVE_SSL */

	if (debuglevel > 1) debug("flush_down(%d) %d bytes", i, n);
	if (n > 0) {
		conns[i].downn -= n;
		if (conns[i].downn == 0) 
			free(conns[i].downb);
		else
			conns[i].downbptr += n;
		clients[conns[i].clt].csx += n;
	}
	return n;
}

/* This only talks upstream and does not need ssl */
static int flush_up(int i)
{
	int n = write(conns[i].upfd, conns[i].upbptr, conns[i].upn);

	if (debuglevel > 1) debug("flush_up(%d) %d bytes", i, n);
	if (n > 0) {
		conns[i].upn -= n;
		if (conns[i].upn == 0)
			free(conns[i].upb);
		else
			conns[i].upbptr += n;
	}
	return n;
}

static void mainloop_select(void)
{
	int downfd, clilen;
	struct sockaddr_in cli_addr;
	fd_set w_read, w_write, w_error;
	int i, w_max;
	usr1action.sa_handler = stats;
	sigemptyset(&usr1action.sa_mask);
	usr1action.sa_flags = 0;
	sigaction(SIGUSR1, &usr1action, NULL);

	hupaction.sa_handler = restart_log;
	sigemptyset(&hupaction.sa_mask);
	hupaction.sa_flags = 0;
	sigaction(SIGHUP, &hupaction, NULL);

	termaction.sa_handler = quit;
	sigemptyset(&termaction.sa_mask);
	termaction.sa_flags = 0;
	sigaction(SIGTERM, &termaction, NULL);

	alrmaction.sa_handler = alarm_handler;
	sigemptyset(&alrmaction.sa_mask);
	alrmaction.sa_flags = 0;
	signal(SIGPIPE, SIG_IGN);

	loopflag = 1;

	if (debuglevel) debug("mainloop_select()");
	while (loopflag) {
		int n;

		if (do_stats) {
			if (webfile) webstats();
			else textstats();
			do_stats=0;
		}
		if (do_restart_log) {
			if (logfp) {
				fclose(logfp);
				logfp = fopen(logfile, "a");
				if (!logfp) 
					error("Can't open logfile %s", logfile);
			}
			read_cfg(cfgfile);
			do_restart_log=0;
		}
		FD_ZERO(&w_read);
		FD_ZERO(&w_write);
		FD_ZERO(&w_error);
		w_max = 0;
		/* no point accepting connections we can't handle */
		if (debuglevel > 1) debug("last = %d, used = %d, max = %d",
					connections_last,
					connections_used,
					connections_max);
		if (connections_used < connections_max) {
			FD_SET(listenfd, &w_read);  /* new connections */
			w_max = listenfd+1;
		} else {
			if (debuglevel) debug("Not listening");
		}
		if (ctrlfd != -1) {
			FD_SET(ctrlfd, &w_read);
			if (ctrlfd > listenfd) w_max = ctrlfd+1;
		}

		/* add sockets from open connections */
		for (i = 0; i < connections_max; i++) {
			if (conns[i].downfd == -1) continue;
			if (conns[i].upn == 0) {
				FD_SET(conns[i].downfd, &w_read);
				if (conns[i].downfd+1 > w_max) {
					w_max = conns[i].downfd+1;
				}
			} else {
				FD_SET(conns[i].upfd, &w_write);
				if (conns[i].upfd+1 > w_max) {
					w_max = conns[i].upfd+1;
				}
			}
			if (conns[i].downn == 0) {
				FD_SET(conns[i].upfd, &w_read);
				if (conns[i].upfd+1 > w_max) {
					w_max = conns[i].upfd+1;
				}
			} else {
				FD_SET(conns[i].downfd, &w_write);
				if (conns[i].downfd+1 > w_max) {
					w_max = conns[i].downfd+1;
				}
			}
		}

		/* Wait for a connection from a client process. */
		n = select(w_max, &w_read, &w_write, /*&w_error*/0, 0);
		if (n < 0 && errno != EINTR) {
			perror("select");
			error("Error on select");
		}
		if (n <= 0) continue;

		if (FD_ISSET(listenfd, &w_read)) {
			clilen = sizeof cli_addr;
			downfd = accept(listenfd,
				(struct sockaddr *) &cli_addr, &clilen);
			if (downfd < 0) {
				if (debuglevel) perror("accept");
				continue;
			}
			if (clilen == 0) {
				if (debuglevel) perror("clilen");
				continue;
			}
			add_client(downfd, &cli_addr);
		}

		/* check control port */
		if (ctrlfd != -1 && FD_ISSET(ctrlfd, &w_read)) {
			clilen = sizeof cli_addr;
			downfd = accept(ctrlfd,
				(struct sockaddr *) &cli_addr, &clilen);
			if (downfd < 0) {
				if (debuglevel) perror("accept");
				continue;
			}
			if (clilen == 0) {
				if (debuglevel) perror("clilen");
				continue;
			}
			do_ctrl(downfd, &cli_addr);
		}

		/* check sockets from open connections */
		for (i = 0; i < connections_max; i++) {
			if (conns[i].downfd == -1) continue;
			if (FD_ISSET(conns[i].downfd, &w_read)) {
				if (copy_up(i) < 0) {
					close_conn(i);
					continue;
				}
			}
			if (FD_ISSET(conns[i].upfd, &w_read)) {
				if (copy_down(i) < 0) {
					close_conn(i);
					continue;
				}
			}
			if (FD_ISSET(conns[i].downfd, &w_write)) {
				if (flush_down(i) < 0) {
					close_conn(i);
					continue;
				}
			}
			if (FD_ISSET(conns[i].upfd, &w_write)) {
				if (flush_up(i) < 0) {
					close_conn(i);
					continue;
				}
			}
		}
	}
}

#ifdef HAVE_POLL
static void dump_pollfd(struct pollfd *ufds, int nfds)
{
	int i;
	for (i = 0; i < nfds; i++) {
		debug("%d: <%d,%d,%d>", i,
			ufds[i].fd, (int)ufds[i].events, (int)ufds[i].revents);
	}
}

static void mainloop_poll(void)
{
	int downfd, clilen;
	struct sockaddr_in cli_addr;
	struct pollfd *ufds;
	int i, j, nfds;
	short downevents, upevents;
	signal(SIGUSR1, stats);
	signal(SIGHUP, restart_log);
	signal(SIGTERM, quit);
	signal(SIGPIPE, SIG_IGN);

	loopflag = 1;

	ufds = pen_malloc((connections_max*2+2)*sizeof *ufds);

	if (debuglevel) debug("POLLIN = %d, POLLOUT = %d", (int)POLLIN, (int)POLLOUT);
	if (debuglevel) debug("mainloop_poll()");
	while (loopflag) {
		int n;

		if (do_stats) {
			if (webfile) webstats();
			else textstats();
			do_stats=0;
		}
		if (do_restart_log) {
			if (logfp) {
				fclose(logfp);
				logfp = fopen(logfile, "a");
				if (!logfp) 
					error("Can't open logfile %s", logfile);
			}
			read_cfg(cfgfile);
			do_restart_log=0;
		}
		nfds = 0;
		ufds[nfds].fd = listenfd;
		ufds[nfds++].events = POLLIN;	/* new connections */
		if (ctrlfd != -1) {
			ufds[nfds].fd = ctrlfd;
			ufds[nfds++].events = POLLIN;
		}

		/* add sockets from open connections */
		if (debuglevel) debug("filling pollfd structure");
		for (i = 0; i < connections_max; i++) {
			if (conns[i].downfd == -1) continue;
			upevents = downevents = 0;

			if (conns[i].upn == 0) downevents |= POLLIN;
			else upevents |= POLLOUT;

			if (conns[i].downn == 0) upevents |= POLLIN;
			else downevents |= POLLOUT;

			if (downevents) {
				ufds[nfds].fd = conns[i].downfd;
				ufds[nfds++].events = downevents;
			}
			if (upevents) {
				ufds[nfds].fd = conns[i].upfd;
				ufds[nfds++].events = upevents;
			}
		}

		if (debuglevel) dump_pollfd(ufds, nfds);

		/* Wait for a connection from a client process. */
		n = poll(ufds, nfds, -1);
		if (debuglevel) debug("n = %d", n);
		if (n < 0 && errno != EINTR) {
			perror("poll");
			error("Error on poll");
		}
		if (n <= 0) continue;

		if (debuglevel) dump_pollfd(ufds, nfds);
		j = 0;
		if (debuglevel) debug("checking pollfd structure");
		if (debuglevel) debug("revents[%d] = %d", j, (int)ufds[j].revents);
		if (ufds[j].revents & POLLIN) {
			clilen = sizeof cli_addr;
			downfd = accept(listenfd,
				(struct sockaddr *) &cli_addr, &clilen);
			if (downfd < 0) {
				if (debuglevel) perror("accept");
				continue;
			}
			if (clilen == 0) {
				if (debuglevel) perror("clilen");
				continue;
			}
			add_client(downfd, &cli_addr);
		}
		j++;

		/* check control port */
		if (ctrlfd != -1 && (ufds[j++].revents & POLLIN)) {
			if (debuglevel) debug("revents[%d] = %d", j-1, (int)POLLIN);
			clilen = sizeof cli_addr;
			downfd = accept(ctrlfd,
				(struct sockaddr *) &cli_addr, &clilen);
			if (downfd < 0) {
				if (debuglevel) perror("accept");
				continue;
			}
			if (clilen == 0) {
				if (debuglevel) perror("clilen");
				continue;
			}
			do_ctrl(downfd, &cli_addr);
			j++;
		}

		/* check sockets from open connections */
		for (i = 0; i < connections_max; i++) {
			if (conns[i].downfd == -1) continue;

			if (conns[i].downfd != ufds[j].fd) downevents = 0;
			else downevents = ufds[j++].revents;

			if (conns[i].upfd != ufds[j].fd) upevents = 0;
			else upevents = ufds[j++].revents;

			if (debuglevel) {
				debug("conn = %d, upevents = %d, downevents = %d",
					i, upevents, downevents);
				if (downevents || upevents) {
					debug("down[%d] = %d, up[%d] = %d",
						i, downevents, i, upevents);
			}
		}
			if (downevents & POLLIN) {
				if (copy_up(i) < 0) {
					close_conn(i);
					continue;
				}
			}
			if (upevents & POLLIN) {
				if (copy_down(i) < 0) {
					close_conn(i);
					continue;
				}
			}
			if (downevents & POLLOUT) {
				if (flush_down(i) < 0) {
					close_conn(i);
					continue;
				}
			}
			if (upevents & POLLOUT) {
				if (flush_up(i) < 0) {
					close_conn(i);
					continue;
				}
			}
		}
	}
}
#endif	/* HAVE_POLL */

#ifdef HAVE_KQUEUE

static void dump_kevent(struct kevent *kev, int nfds)
{
	int i;
	struct kevent *kptr;
	for (i = 0; i < nfds; i++) {
		kptr = &kev[i];
		debug("kev[%d]: <i=%d, f=%d, f=%d, ff=%d, d=%d u=%p>", i,
		    kptr->ident, kptr->filter, kptr->flags,
		    kptr->fflags, kptr->data, kptr->udata);
	}

}

static void mainloop_kqueue(void)
{
	int downfd, clilen;
	int kq;
	struct kevent *kev, *kptr;
	connection *conn;
	struct sockaddr_in cli_addr;
	int i, j, nfds;
	short downevents, upevents;

	loopflag = 1;

	kq = kqueue();
	if (kq == -1)
	{
		perror("kqueue");
		error("Error creating kernel queue");
	}

	kev = pen_malloc((connections_max*2+2)*sizeof *kev);

	if (debuglevel) debug("mainloop_kqueue()");
	while (loopflag) {
		int n;

		if (do_stats) {
			if (webfile) webstats();
			else textstats();
			do_stats=0;
		}
		if (do_restart_log) {
			if (logfp) {
				fclose(logfp);
				logfp = fopen(logfile, "a");
				if (!logfp) 
					error("Can't open logfile %s", logfile);
			}
			read_cfg(cfgfile);
			do_restart_log=0;
		}
		kptr = kev;
		nfds = 0;

		/* FIXME: these two don't need to be done every loop;
		 * they'll persist until the sockets are closed. But moving
		 * them outside the loop adds complications (kptr initial
		 * setting etc) */
		EV_SET(kptr++, listenfd, EVFILT_READ, EV_ADD, 0, 0, 0);
		nfds++;
		if (ctrlfd != -1) {
			EV_SET(kptr++, ctrlfd, EVFILT_READ, EV_ADD, 0, 0, 0);
			nfds++;
		}

		/* add sockets from open connections */
		if (debuglevel) debug("filling kqueue structure");
		for (i = 0; i < connections_max; i++) {
			if (conns[i].downfd == -1) continue;
			upevents = downevents = 0;

			if (conns[i].upn == 0)
			{
				EV_SET(kptr++, conns[i].downfd, EVFILT_READ,
				    EV_ADD | EV_ONESHOT, 0, 0, &conns[i]);
				nfds++;
			}
			else
			{
				EV_SET(kptr++, conns[i].upfd, EVFILT_WRITE,
				    EV_ADD | EV_ONESHOT, 0, 0, &conns[i]);
				nfds++;
			}

			if (conns[i].downn == 0)
			{
				EV_SET(kptr++, conns[i].upfd, EVFILT_READ,
				    EV_ADD | EV_ONESHOT, 0, 0, &conns[i]);
				nfds++;
			}
			else
			{
				EV_SET(kptr++, conns[i].downfd, EVFILT_WRITE,
				    EV_ADD | EV_ONESHOT, 0, 0, &conns[i]);
				nfds++;
			}
		}

		if (debuglevel) dump_kevent(kev, nfds);

		/* Wait for a connection from a client process. */
		n = kevent(kq, kev, nfds, kev, nfds, NULL);
		if (debuglevel) debug("n = %d", n);
		if (n < 0 && errno != EINTR) {
			perror("kevent");
			error("Error on kevent");
		}
		if (n <= 0) continue;

		if (debuglevel) debug("checking kqueue structure");
		if (debuglevel) dump_kevent(kev, n);

		for(j = 0 ; j < n ; j++)
		{
			kptr = &kev[j];

			/* check listener */
			if (kptr->ident == listenfd)
			{
				clilen = sizeof cli_addr;
				downfd = accept(listenfd,
					(struct sockaddr *) &cli_addr, &clilen);
				if (downfd < 0) {
					if (debuglevel) perror("accept");
					continue;
				}
				if (clilen == 0) {
					if (debuglevel) perror("clilen");
					continue;
				}
				add_client(downfd, &cli_addr);
				continue;
			}

			/* check control port */
			if (ctrlfd != -1 && kptr->ident == ctrlfd)
			{
				clilen = sizeof cli_addr;
				downfd = accept(ctrlfd,
					(struct sockaddr *) &cli_addr, &clilen);
				if (downfd < 0) {
					if (debuglevel) perror("accept");
					continue;
				}
				if (clilen == 0) {
					if (debuglevel) perror("clilen");
					continue;
				}
				do_ctrl(downfd, &cli_addr);
				continue;
			}

			/* check sockets from open connections */
			conn = (connection *)kptr->udata;
			if (conn->downfd == -1) continue;

			if (conn->downfd != kptr->ident)
				downevents = 0;
			else
				downevents = kptr->filter;

			if (conn->upfd != kptr->ident)
				upevents = 0;
			else
				upevents = kptr->filter;

			i = conn->i;
			if (debuglevel) {
				debug("conn = %d, upevents = %d, downevents = %d",
					i, upevents, downevents);
			}

			if (downevents == EVFILT_READ) {
				if (copy_up(i) < 0) {
					close_conn(i);
					continue;
				}
			}
			if (upevents == EVFILT_READ) {
				if (copy_down(i) < 0) {
					close_conn(i);
					continue;
				}
			}
			if (downevents == EVFILT_WRITE) {
				if (flush_down(i) < 0) {
					close_conn(i);
					continue;
				}
			}
			if (upevents == EVFILT_WRITE) {
				if (flush_up(i) < 0) {
					close_conn(i);
					continue;
				}
			}
		}
	}
}
#endif	/* HAVE_KQUEUE */

static int options(int argc, char **argv)
{
	int c;
	char b[1024];

#ifdef HAVE_SSL
	char *opt = "B:C:F:S:T:b:c:e:j:l:o:p:t:u:w:x:DHPQWXadfhnrsE:K:G:A:ZRL:";
#else
	char *opt = "B:C:F:S:T:b:c:e:j:l:o:p:t:u:w:x:DHPQWXadfhnrs";
#endif

	while ((c = getopt(argc, argv, opt)) != -1) {
		switch (c) {
		case 'B':
			a_server = optarg;
			break;
		case 'C':
			ctrlport = optarg;
			break;
		case 'D':
			delayed_forward = 1;
			break;
		case 'F':
			cfgfile = optarg;
			break;
		case 'H':
			http = 1;
			break;
		case 'Q':
			use_kqueue = 1;
			break;
		case 'P':
			use_poll = 1;
			break;
		case 'S':
			servers_max = atoi(optarg);
			break;
		case 'T':
			tracking_time = atoi(optarg);
			break;
		case 'W':
			weight = 1;
			break;
		case 'X':
	    		exit_enabled = 1;
	    		break;
		case 'a':
			asciidump = 1;
			break;
		case 'b':
			blacklist_time = atoi(optarg);
			break;
		case 'c':
			clients_max = atoi(optarg);
			break;
		case 'd':
			debuglevel++;
			break;
		case 'e':
			e_server = optarg;
			break;
		case 'f':
			foreground = 1;
			break;
		case 'h':
			hash = 1;
			break;
		case 'j':
			jail = optarg;
			break;
		case 'l':
			logfile = pen_strdup(optarg);
			break;
		case 'n':
			nblock = 0;
			break;
		case 'o':
			snprintf(b, sizeof b, "%s", optarg);
			do_cmd(optarg, output_file, stdout);
			break;
		case 'p':
			pidfile = optarg;
			break;
		case 'r':
			roundrobin = 1;
			break;
		case 's':
			stubborn = 1;
			break;
		case 't':
			timeout = atoi(optarg);
			if (timeout < 1) {
				usage();
			}
			break;
		case 'u':
			user = optarg;
			break;
		case 'x':
			connections_max = atoi(optarg);
			break;
		case 'w':
			webfile = pen_strdup(optarg);
			break;
#ifdef HAVE_SSL
		case 'E':
			certfile = optarg;
			break;
		case 'K':
			keyfile = optarg;
			break;
		case 'G':
			cacert_file = optarg;
			break;
		case 'A':
			cacert_dir = optarg;
			break;
		case 'Z':
			ssl_compat = 1;
			break;
		case 'R':
			require_peer_cert = 1;
			break;
		case 'L':
			if (strcmp(optarg, "ssl23") == 0)
				ssl_protocol = SRV_SSL_V23;
			else if (strcmp(optarg, "ssl2") == 0)
				ssl_protocol = SRV_SSL_V2;
			else if (strcmp(optarg, "ssl3") == 0)
				ssl_protocol = SRV_SSL_V3;
			else if (strcmp(optarg, "tls1") == 0)
				ssl_protocol = SRV_SSL_TLS1;
			else {
				fprintf(stderr, "protocol version %s not known\n", optarg);
				exit(1);
			}
			break;
#endif  /* HAVE_SSL */
		case '?':
		default:
			usage();
		}
	}

	return optind;
}

int main(int argc, char **argv)
{
	int i;
	struct passwd *pwd = NULL;
	struct rlimit r;
	int n = options(argc, argv);
	argc -= n;
	argv += n;

	if (argc < 1) {
		usage();
	}

	if ((connections_max*2+10) > FD_SETSIZE && !use_poll) 
		error("Number of simultaneous connections to large.\n"
		      "Maximum is %d, or re-build pen with larger FD_SETSIZE",
		      (FD_SETSIZE-10)/2);
	
	getrlimit(RLIMIT_CORE, &r);
	r.rlim_cur = r.rlim_max;
	setrlimit(RLIMIT_CORE, &r);

	signal(SIGCHLD, SIG_IGN);

	if (!foreground) {
		background();
	}

#ifdef HAVE_SSL
	if (certfile) {
		ssl_init();
	}
#endif

	/* we must open listeners before dropping privileges */
	if (ctrlport) ctrlfd = open_listener(ctrlport);
	else ctrlfd = -1;

	listenport = argv[0];
	listenfd = open_listener(listenport);
	init(argc, argv);

	/* we must look up user id before chrooting */
	if (user) {
		if (debuglevel) debug("Run as user %s", user);
		pwd = getpwnam(user);
		if (pwd == NULL) error("Can't getpwnam(%s)", user);
	}

	/* we must chroot before dropping privileges */
	if (jail) {
		if (debuglevel) debug("Run in %s", jail);
		if (chroot(jail) == -1) error("Can't chroot(%s)", jail);
	}

	/* ready to defang ourselves */
	if (pwd) {
		if (setgid(pwd->pw_gid) == -1)
			error("Can't setgid(%d)", (int)pwd->pw_gid);
		if (setuid(pwd->pw_uid) == -1)
			error("Can't setuid(%d)", (int)pwd->pw_uid);
	}

	read_cfg(cfgfile);
	open_log(logfile);
	if (pidfile) {
		pidfp = fopen(pidfile, "w");
		if (!pidfp) {
			error("Can't create pidfile %s", pidfile);
			exit(1);
		}
		fprintf(pidfp, "%d", (int)getpid());
		fclose(pidfp);
	}

#ifdef HAVE_KQUEUE
	if (use_kqueue) mainloop_kqueue();
#else
	if (use_kqueue) error("You don't have kqueue()");
#endif /* HAVE_KQUEUE */

#ifdef HAVE_POLL
	if (use_poll) mainloop_poll();
#else
	if (use_poll) error("You don't have poll()");
#endif	/* HAVE_POLL */

	if (!use_kqueue && !use_poll) mainloop_select();

	if (debuglevel) debug("Exiting, cleaning up...");
	if (logfp) fclose(logfp);
	for (i = 0; i < connections_max; i++) {
		close_conn(i);
	}
	close(listenfd);
	if (pidfile) {
		unlink(pidfile);
	}
	return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1