text: 
  /*
   * Copyright (c) 2001-2002 Packet Design, LLC.
   * All rights reserved.
   * 
   * Subject to the following obligations and disclaimer of warranty,
   * use and redistribution of this software, in source or object code
   * forms, with or without modifications are expressly permitted by
   * Packet Design; provided, however, that:
   * 
   *    (i)  Any and all reproductions of the source or object code
   *         must include the copyright notice above and the following
   *         disclaimer of warranties; and
   *    (ii) No rights are granted, in any manner or form, to use
   *         Packet Design trademarks, including the mark "PACKET DESIGN"
   *         on advertising, endorsements, or otherwise except as such
   *         appears in the above copyright notice or in the software.
   * 
   * THIS SOFTWARE IS BEING PROVIDED BY PACKET DESIGN "AS IS", AND
   * TO THE MAXIMUM EXTENT PERMITTED BY LAW, PACKET DESIGN MAKES NO
   * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING
   * THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED
   * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
   * OR NON-INFRINGEMENT.  PACKET DESIGN DOES NOT WARRANT, GUARANTEE,
   * OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS
   * OF THE USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY,
   * RELIABILITY OR OTHERWISE.  IN NO EVENT SHALL PACKET DESIGN BE
   * LIABLE FOR ANY DAMAGES RESULTING FROM OR ARISING OUT OF ANY USE
   * OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY DIRECT,
   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL
   * DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF
   * USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER 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 PACKET DESIGN IS ADVISED OF
   * THE POSSIBILITY OF SUCH DAMAGE.
   *
   * Author: Archie Cobbs <archie@freebsd.org>
   */
  
  #include <sys/types.h>
  #include <sys/param.h>
  #include <sys/socket.h>
  #include <sys/syslog.h>
  #include <sys/queue.h>
  
  #include <stdlib.h>
  #include <stdarg.h>
  #include <unistd.h>
  #include <errno.h>
  #include <assert.h>
  #include <unistd.h>
  #include <string.h>
  #include <fcntl.h>
  #include <ctype.h>
  #include <limits.h>
  #include <regex.h>
  #include <pthread.h>
  #include <netdb.h>
  
  #include <netinet/in_systm.h>
  #include <netinet/in.h>
  #include <arpa/inet.h>
  
  #include <openssl/ssl.h>
  #include <openssl/err.h>
  
  #include "structs/structs.h"
  #include "structs/type/array.h"
  
  #include "io/ssl_fp.h"
  #include "http/http_defs.h"
  #include "http/http_server.h"
  #include "http/http_internal.h"
  #include "http/http_servlet.h"
  #include "util/pevent.h"
  #include "util/ghash.h"
  #include "util/typed_mem.h"
  
  /*
   * Embedded HTTP[S] web server.
   */
  
  #define MAX_CONNECTIONS		1024
  #define HTTP_SERVER_TIMEOUT	90
  
  /* HTTP server */
  struct http_server {
  	struct pevent_ctx	*ctx;		/* event context */
  	struct pevent		*conn_event;	/* new connection event */
  	int			sock;		/* accept(2) socket */
  	SSL_CTX			*ssl;		/* ssl context, if doing ssl */
  	char			*pkey_pw;	/* ssl private key password */
  	char			*server_name;	/* server name */
  	struct ghash		*vhosts;	/* virtual hosts */
  	http_proxy_t		*proxy_handler;	/* proxy handler */
  	void			*proxy_arg;	/* proxy handler cookie */
  	LIST_HEAD(,http_connection)
  				conn_list;	/* active connections */
  	int			max_conn;	/* max number of connections */
  	int			num_conn;	/* number of connections */
  	http_logger_t		*logger;	/* error logging routine */
  	pthread_mutex_t		mutex;		/* mutex */
  	u_char			stopping;	/* server being stopped */
  #if PDEL_DEBUG
  	int			mutex_count;	/* mutex count */
  #endif
  };
  
  /* Virtual host */
  struct http_virthost {
  	struct http_server	*server;	/* back pointer to server */
  	char			*host;		/* virtual hostname */
  	LIST_HEAD(,http_servlet_hook)
  				servlets;	/* registered servlets */
  };
  
  struct const_http_virthost {
  	struct http_server	*server;	/* back pointer to server */
  	const char		*host;		/* virtual hostname */
  	LIST_HEAD(,http_servlet_hook)
  				servlets;	/* registered servlets */
  };
  
  /* What a registered servlet looks like */
  struct http_servlet_hook {
  	struct http_servlet	*servlet;	/* servlet */
  	struct http_virthost	*vhost;		/* back pointer to vhost */
  #if PDEL_DEBUG
  	char			*spat;		/* string pattern */
  #endif
  	regex_t			pat;		/* regex pattern */
  	int		 	order;		/* processing order */
  	pthread_rwlock_t	rwlock;		/* servlet lock */
  	LIST_ENTRY(http_servlet_hook)
  				next;		/* next in vhost list */
  };
  
  /*
   * Internal functions
   */
  static void	*http_server_connection_main(void *arg);
  static void	http_server_connection_cleanup(void *arg);
  static void	http_server_dispatch(struct http_request *req,
  			struct http_response *resp);
  static int	http_server_ssl_pem_password_cb(char *buf, int size,
  			int rwflag, void *udata);
  
  static pevent_handler_t		http_server_accept;
  
  static ghash_equal_t		http_server_virthost_equal;
  static ghash_hash_t		http_server_virthost_hash;
  
  static ssl_logger_t		http_server_ssl_logger;
  
  /*********************************************************************
  		    SERVER START/STOP ROUTINES
  *********************************************************************/
  
  /*
   * Create and start a new server. The server runs in a separate thread.
   */
  struct http_server *
  http_server_start(struct pevent_ctx *ctx, struct in_addr ip,
  	u_int16_t port, const struct http_server_ssl *ssl,
  	const char *server_name, http_logger_t *logger)
  {
  	static const int one = 1;
  	struct http_server *serv;
  	struct sockaddr_in sin;
  	int got_mutex = 0;
  
  	/* Initialize new server structure */
  	if ((serv = MALLOC("http_server", sizeof(*serv))) == NULL) {
  		(*logger)(LOG_ERR, "%s: %s", "realloc", strerror(errno));
  		return (NULL);
  	}
  	memset(serv, 0, sizeof(*serv));
  	LIST_INIT(&serv->conn_list);
  	serv->ctx = ctx;
  	serv->logger = logger;
  	serv->sock = -1;
  	serv->max_conn = MAX_CONNECTIONS;	/* XXX make configurable */
  
  	/* Copy server name */
  	if ((serv->server_name
  	    = STRDUP("http_server.server_name", server_name)) == NULL) {
  		(*logger)(LOG_ERR, "%s: %s", "strdup", strerror(errno));
  		goto fail;
  	}
  
  	/* Create virtual host hash table */
  	if ((serv->vhosts = ghash_create(serv, 0, 0, "http_server.vhosts",
  	    http_server_virthost_hash, http_server_virthost_equal, NULL,
  	    NULL)) == NULL) {
  		(*logger)(LOG_ERR, "%s: %s", "strdup", strerror(errno));
  		goto fail;
  	}
  
  	/* Initialize ssl */
  	if (ssl != NULL) {
  
  		/* Initialize SSL stuff */
  		_http_ssl_init();
  
  		/* Initialize SSL context for this server */
  		if ((serv->ssl = SSL_CTX_new(SSLv23_server_method())) == NULL) {
  			ssl_log(http_server_ssl_logger, serv);
  			goto fail;
  		}
  
  		/* Set callback for getting private key file password */
  		SSL_CTX_set_default_passwd_cb(serv->ssl,
  		    http_server_ssl_pem_password_cb);
  		SSL_CTX_set_default_passwd_cb_userdata(serv->ssl, serv);
  		if (serv->pkey_pw != NULL
  		    && (serv->pkey_pw = STRDUP("http_server.pkey_pw",
  		      ssl->pkey_password)) == NULL) {
  			(*serv->logger)(LOG_ERR, "%s: %s",
  			    "strdup", strerror(errno));
  			goto fail;
  		}
  
  		/* Read in certificate and private key files */
  		if (SSL_CTX_use_certificate_file(serv->ssl,
  		    ssl->cert_path, SSL_FILETYPE_PEM) <= 0) {
  			ssl_log(http_server_ssl_logger, serv);
  			goto fail;
  		}
  		if (SSL_CTX_use_PrivateKey_file(serv->ssl,
  		    ssl->pkey_path, SSL_FILETYPE_PEM) <= 0) {
  			ssl_log(http_server_ssl_logger, serv);
  			goto fail;
  		}
  
  		/* Verify that certificate actually signs the public key */
  		if (!SSL_CTX_check_private_key(serv->ssl)) {
  			(*serv->logger)(LOG_ERR,
  			    "certificate %s does not match private key %s",
  			    ssl->cert_path, ssl->pkey_path);
  			goto fail;
  		}
  	}
  
  	/* Create socket */
  	if ((serv->sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
  		(*serv->logger)(LOG_ERR, "%s: %s", "socket", strerror(errno));
  		goto fail;
  	}
  	(void)fcntl(serv->sock, F_SETFD, 1);
  	if (setsockopt(serv->sock, SOL_SOCKET,
  	    SO_REUSEADDR, &one, sizeof(one)) == -1) {
  		(*serv->logger)(LOG_ERR,
  		    "%s: %s", "setsockopt", strerror(errno));
  		goto fail;
  	}
  	memset(&sin, 0, sizeof(sin));
  #if !defined(__linux__) && !defined(__sun__)
  	sin.sin_len = sizeof(sin);
  #endif
  	sin.sin_family = AF_INET;
  	sin.sin_port = htons(port);
  	sin.sin_addr = ip;
  	if (bind(serv->sock, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
  		(*serv->logger)(LOG_ERR, "%s: %s", "bind", strerror(errno));
  		goto fail;
  	}
  	if (listen(serv->sock, 1024) == -1) {
  		(*serv->logger)(LOG_ERR, "%s: %s", "listen", strerror(errno));
  		goto fail;
  	}
  
  	/* Initialize mutex */
  	if ((errno = pthread_mutex_init(&serv->mutex, NULL)) != 0) {
  		(*serv->logger)(LOG_ERR, "%s: %s",
  		    "pthread_mutex_init", strerror(errno));
  		goto fail;
  	}
  	got_mutex = 1;
  
  	/* Start accepting connections */
  	if (pevent_register(serv->ctx, &serv->conn_event, PEVENT_RECURRING,
  	    &serv->mutex, http_server_accept, serv, PEVENT_READ, serv->sock)
  	    == -1) {
  		(*serv->logger)(LOG_ERR, "%s: %s",
  		    "pevent_register", strerror(errno));
  		goto fail;
  	}
  	DBG(HTTP, "server %p started", serv);
  
  	/* Done */
  	return (serv);
  
  fail:
  	/* Cleanup after failure */
  	if (got_mutex)
  		pthread_mutex_destroy(&serv->mutex);
  	if (serv->pkey_pw != NULL) {
  		memset(serv->pkey_pw, 0, strlen(serv->pkey_pw));
  		FREE("http_server.pkey_pw", serv->pkey_pw);
  	}
  	if (serv->sock != -1)
  		(void)close(serv->sock);
  	if (serv->ssl != NULL)
  		SSL_CTX_free(serv->ssl);
  	ghash_destroy(&serv->vhosts);
  	FREE("http_server.server_name", serv->server_name);
  	FREE("http_server", serv);
  	return (NULL);
  }
  
  /*
   * Stop HTTP server.
   */
  void
  http_server_stop(struct http_server **sp)
  {
  	struct http_server *const serv = *sp;
  	struct http_connection *conn;
  
  	/* Already stopped? */
  	if (serv == NULL)
  		return;
  	*sp = NULL;
  
  	/* Set 'stopping' flag to prevent new servlet registrations */
  	if (serv->stopping)
  		return;
  	serv->stopping = 1;
  	DBG(HTTP, "stopping server %p", serv);
  
  	/* Acquire mutex */
  	MUTEX_LOCK(&serv->mutex, serv->mutex_count);
  
  	/* Stop accepting new connections */
  	pevent_unregister(&serv->conn_event);
  
  	/*
  	 * Destroy all registered servlets. Because we must unlock the
  	 * server to do this, don't rely on iteration. Instead, start at
  	 * the beginning each time.
  	 */
  	while (ghash_size(serv->vhosts) > 0) {
  		struct http_servlet_hook *hook;
  		struct http_servlet *servlet;
  		struct http_virthost *vhost;
  		struct ghash_walk walk;
  
  		/* Get the first virtual host */
  		ghash_walk_init(serv->vhosts, &walk);
  		vhost = ghash_walk_next(serv->vhosts, &walk);
  		assert(vhost != NULL);
  
  		/* Get the first servlet in this virtual host */
  		assert(!LIST_EMPTY(&vhost->servlets));
  		hook = LIST_FIRST(&vhost->servlets);
  		servlet = hook->servlet;
  
  		/* Unlock server and nuke the servlet */
  		MUTEX_UNLOCK(&serv->mutex, serv->mutex_count);
  		http_server_destroy_servlet(&servlet);
  		MUTEX_LOCK(&serv->mutex, serv->mutex_count);
  	}
  
  	/* Kill any remaining connections */
  	while (!LIST_EMPTY(&serv->conn_list)) {
  
  		/* Kill active connections; they will clean up themselves */
  		LIST_FOREACH(conn, &serv->conn_list, next) {
  			if (conn->started && conn->tid != 0) {
  				DBG(HTTP, "canceling conn %p (thread %p)",
  				    conn, conn->tid);
  				assert(conn->tid != pthread_self());
  				pthread_cancel(conn->tid);
  				conn->tid = 0;		/* don't cancel twice */
  			}
  		}
  
  		/* Wait for outstanding connections to complete */
  		MUTEX_UNLOCK(&serv->mutex, serv->mutex_count);
  		usleep(100000);
  		MUTEX_LOCK(&serv->mutex, serv->mutex_count);
  	}
  
  	/* Free SSL context */
  	if (serv->ssl != NULL)
  		SSL_CTX_free(serv->ssl);
  
  	/* Close listen socket */
  	(void)close(serv->sock);
  
  	/* Free strings */
  	if (serv->pkey_pw != NULL) {
  		memset(serv->pkey_pw, 0, strlen(serv->pkey_pw));
  		FREE("http_server.pkey_pw", serv->pkey_pw);
  	}
  	FREE("http_server.server_name", serv->server_name);
  
  	/* Free virtual hosts hash table */
  	assert(ghash_size(serv->vhosts) == 0);
  	ghash_destroy(&serv->vhosts);
  
  	/* Free server structure itself */
  	MUTEX_UNLOCK(&serv->mutex, serv->mutex_count);
  	pthread_mutex_destroy(&serv->mutex);
  	DBG(HTTP, "freeing server %p", serv);
  	FREE("http_server", serv);
  }
  
  /*********************************************************************
  			SERVLET REGISTRATION
  *********************************************************************/
  
  /*
   * Register a servlet
   */
  int
  http_server_register_servlet(struct http_server *serv,
  	struct http_servlet *servlet, const char *vhost,
  	const char *urlpat, int order)
  {
  	struct http_servlet_hook *hook;
  	struct const_http_virthost vhost_key;
  	int got_rwlock = 0;
  	int got_regex = 0;
  	char ebuf[128];
  	int r;
  
  	/* Sanity */
  	if (servlet == NULL) {
  		errno = EINVAL;
  		return (-1);
  	}
  	if (servlet->hook != NULL) {
  		errno = EALREADY;
  		return (-1);
  	}
  	if (serv->stopping) {
  		errno = ESRCH;
  		return (-1);
  	}
  	if (vhost == NULL)
  		vhost = "";
  
  	/* Create new servlet hook */
  	if ((hook = MALLOC("http_servlet_hook", sizeof(*hook))) == NULL) {
  		(*serv->logger)(LOG_ERR, "%s: %s", "malloc", strerror(errno));
  		return (-1);
  	}
  	memset(hook, 0, sizeof(*hook));
  	hook->servlet = servlet;
  	hook->order = order;
  #if PDEL_DEBUG
  	if ((hook->spat = STRDUP("http_servlet_hook.spat", urlpat)) == NULL) {
  		(*serv->logger)(LOG_ERR, "%s: %s", "malloc", strerror(errno));
  		goto fail;
  	}
  #endif
  
  	/* Compile URL regular expression */
  	if ((r = regcomp(&hook->pat, urlpat, REG_EXTENDED | REG_NOSUB)) != 0) {
  		regerror(r, &hook->pat, ebuf, sizeof(ebuf));
  		(*serv->logger)(LOG_ERR,
  		    "invalid URL pattern \"%s\": %s", urlpat, ebuf);
  		goto fail;
  	}
  	got_regex = 1;
  
  	/* Initialize read/write lock */
  	if ((errno = pthread_rwlock_init(&hook->rwlock, NULL)) != 0) {
  		(*serv->logger)(LOG_ERR, "%s: %s",
  		    "pthread_rwlock_init", strerror(errno));
  		goto fail;
  	}
  	got_rwlock = 1;
  
  	/* Lock server */
  	MUTEX_LOCK(&serv->mutex, serv->mutex_count);
  
  	/* Find virtual host; create a new one if necessary */
  	vhost_key.host = vhost;
  	if ((hook->vhost = ghash_get(serv->vhosts, &vhost_key)) == NULL) {
  
  		/* Create a new virtual host */
  		if ((hook->vhost = MALLOC("http_virthost",
  		    sizeof(*hook->vhost))) == NULL) {
  			MUTEX_UNLOCK(&serv->mutex, serv->mutex_count);
  			(*serv->logger)(LOG_ERR, "%s: %s",
  			    "malloc", strerror(errno));
  			goto fail;
  		}
  		memset(hook->vhost, 0, sizeof(*hook->vhost));
  		if ((hook->vhost->host
  		    = STRDUP("http_virthost.host", vhost)) == NULL) {
  			MUTEX_UNLOCK(&serv->mutex, serv->mutex_count);
  			(*serv->logger)(LOG_ERR, "%s: %s",
  			    "malloc", strerror(errno));
  			goto fail;
  		}
  		LIST_INIT(&hook->vhost->servlets);
  
  		/* Add virtual host to server's list */
  		if ((r = ghash_put(serv->vhosts, hook->vhost)) == -1) {
  			MUTEX_UNLOCK(&serv->mutex, serv->mutex_count);
  			goto fail;
  		}
  		assert(r == 0);
  		hook->vhost->server = serv;
  	}
  
  	/* Register servlet with virtual host */
  	LIST_INSERT_HEAD(&hook->vhost->servlets, hook, next);
  	servlet->hook = hook;
  
  	/* Unlock server */
  	MUTEX_UNLOCK(&serv->mutex, serv->mutex_count);
  
  	/* Done */
  	return (0);
  
  fail:
  	/* Clean up after failure */
  	if (hook->vhost != NULL) {
  		FREE("http_virthost.host", hook->vhost->host);
  		FREE("http_virthost", hook->vhost);
  	}
  	if (got_rwlock)
  		pthread_rwlock_destroy(&hook->rwlock);
  	if (got_regex)
  		regfree(&hook->pat);
  #if PDEL_DEBUG
  	FREE("http_servlet_hook.spat", hook->spat);
  #endif
  	FREE("http_servlet_hook", hook);
  	return (-1);
  }
  
  /*
   * Destroy a servlet, unregistering it if necessary.
   *
   * If it's the last servlet in the virtual host, remove the virtual host too.
   */
  void
  http_server_destroy_servlet(struct http_servlet **servletp)
  {
  	struct http_servlet *const servlet = *servletp;
  	struct http_servlet_hook *hook;
  	struct http_virthost *vhost;
  	struct http_server *serv;
  	int r;
  
  	/* Sanity */
  	if (servlet == NULL)
  		return;
  	*servletp = NULL;
  	hook = servlet->hook;
  
  	/* If servlet is not registered, just destroy it */
  	if (hook == NULL)
  		goto done;
  	vhost = hook->vhost;
  	serv = vhost->server;
  
  	/* Lock server */
  	MUTEX_LOCK(&serv->mutex, serv->mutex_count);
  
  	/* Unregister servlet from its virtual host */
  	LIST_REMOVE(hook, next);
  	hook->vhost = NULL;				/* for good measure */
  
  	/* Remove the virtual host if it has no more servlets */
  	if (LIST_EMPTY(&vhost->servlets)) {
  		r = ghash_remove(serv->vhosts, vhost);
  		assert(r == 1);
  		vhost->server = NULL;			/* for good measure */
  		FREE("http_virthost.host", vhost->host);
  		FREE("http_virthost", vhost);
  	}
  
  	/* Unlock server */
  	MUTEX_UNLOCK(&serv->mutex, serv->mutex_count);
  
  	/* Wait for any threads running this servlet to finish */
  	r = pthread_rwlock_wrlock(&hook->rwlock);
  	assert(r == 0);
  	r = pthread_rwlock_unlock(&hook->rwlock);
  	assert(r == 0);
  
  	/* Destroy servlet hook */
  	regfree(&hook->pat);
  	pthread_rwlock_destroy(&hook->rwlock);
  #if PDEL_DEBUG
  	FREE("http_servlet_hook.spat", hook->spat);
  #endif
  	FREE("http_servlet_hook", hook);
  	servlet->hook = NULL;				/* for good measure */
  
  done:
  	/* Destroy servlet itself */
  	(*servlet->destroy)(servlet);
  }
  
  /*
   * Set proxy servlet
   */
  extern void
  http_server_set_proxy_handler(struct http_server *serv,
  	http_proxy_t *handler, void *arg)
  {
  	MUTEX_LOCK(&serv->mutex, serv->mutex_count);
  	serv->proxy_handler = handler;
  	serv->proxy_arg = arg;
  	MUTEX_UNLOCK(&serv->mutex, serv->mutex_count);
  }
  
  /*********************************************************************
  			SERVER MAIN THREAD
  *********************************************************************/
  
  /*
   * Accept a new connection.
   *
   * The mutex will be locked when this is called.
   */
  static void
  http_server_accept(void *arg)
  {
  	struct http_server *const serv = arg;
  	struct http_connection *conn;
  	struct sockaddr_in sin;
  	socklen_t slen = sizeof(sin);
  	int sock;
  
  	/* Accept next connection */
  	if ((sock = accept(serv->sock, (struct sockaddr *)&sin, &slen)) == -1) {
  		if (errno != ECONNABORTED && errno != ENOTCONN) {
  			(*serv->logger)(LOG_ERR, "%s: %s",
  			    "accept", strerror(errno));
  		}
  		return;
  	}
  	(void)fcntl(sock, F_SETFD, 1);
  
  	/* Get new connection object */
  	if ((conn = _http_connection_create(NULL, sock, 1, sin.sin_addr,
  	    ntohs(sin.sin_port), serv->ssl, serv->logger,
  	    HTTP_SERVER_TIMEOUT)) == NULL) {
  		(*serv->logger)(LOG_ERR, "%s: %s",
  		    "_http_connection_create", strerror(errno));
  		(void)close(sock);
  		return;
  	}
  	conn->owner = serv;
  
  	/* Add to server's list of active connections */
  	LIST_INSERT_HEAD(&serv->conn_list, conn, next);
  	serv->num_conn++;
  
  	/* Spawn a new thread to handle request */
  	if ((errno = pthread_create(&conn->tid, NULL,
  	    http_server_connection_main, conn)) != 0) {
  		(*serv->logger)(LOG_ERR, "%s: %s",
  		    "pthread_create", strerror(errno));
  		conn->tid = 0;
  		LIST_REMOVE(conn, next);
  		serv->num_conn--;
  		_http_connection_free(&conn);
  	}
  
  	/* Detach thread */
  	pthread_detach(conn->tid);
  	DBG(HTTP, "spawning %p for connection %p from %s:%u",
  	    conn->tid, conn, inet_ntoa(conn->remote_ip), conn->remote_port);
  
  	/* If maximum number of connections reached, stop accepting new ones */
  	if (serv->num_conn >= serv->max_conn)
  		pevent_unregister(&serv->conn_event);
  }
  
  /*********************************************************************
  		    SERVER CONNECTION MAIN THREAD
  *********************************************************************/
  
  /*
   * HTTP connection thread main routine.
   */
  static void *
  http_server_connection_main(void *arg)
  {
  	struct http_connection *const conn = arg;
  	struct http_server *const serv = conn->owner;
  
  	/* Add shutdown cleanup hook */
  	pthread_cleanup_push(http_server_connection_cleanup, conn);
  	conn->started = 1;		/* avoids pthread_cancel() race */
  	DBG(HTTP, "connection %p started", conn);
  
  	/* Read requests as long as connection is kept alive */
  	conn->keep_alive = 1;
  	while (1) {
  		struct http_request *const req = conn->req;
  		struct http_response *const resp = conn->resp;
  		const char *hval;
  		char dbuf[64];
  		struct tm tm;
  		time_t now;
  
  		/* Read in request */
  		if (_http_request_read(conn) == -1) {
  			if (errno == ENOTCONN)	/* remote side disconnected */
  				break;
  			conn->keep_alive = 0;
  			goto send_response;
  		}
  
  		/* Set default response headers */
  		now = time(NULL);
  		strftime(dbuf, sizeof(dbuf),
  		    HTTP_TIME_FMT_RFC1123, gmtime_r(&now, &tm));
  		if (http_response_set_header(resp, 0,
  		      HDR_REPLY_VERSION, HTTP_PROTO_1_1) == -1
  		    || http_response_set_header(resp, 0, HDR_REPLY_STATUS,
  		      "%d", HTTP_STATUS_OK) == -1
  		    || http_response_set_header(resp, 0, HDR_REPLY_REASON,
  		      "%s", http_response_status_msg(HTTP_STATUS_OK)) == -1
  		    || http_response_set_header(resp, 0, HTTP_HEADER_SERVER,
  		      "%s", serv->server_name) == -1
  		    || http_response_set_header(resp, 0, HTTP_HEADER_DATE,
  		      "%s", dbuf) == -1
  		    || http_response_set_header(resp, 0,
  		      HTTP_HEADER_LAST_MODIFIED, "%s", dbuf) == -1) {
  			conn->keep_alive = 0;
  			goto send_response;
  		}
  
  		/* Turn off keep-alive if client doesn't want it */
  		if (!_http_head_want_keepalive(req->msg->head))
  			conn->keep_alive = 0;
  
  		/* Handle request */
  		http_server_dispatch(req, resp);
  
  		/* Set Connection: header if not already set */
  		if (((hval = http_request_get_method(req)) == NULL
  		      || strcmp(hval, HTTP_METHOD_CONNECT) != 0)
  		    && http_response_get_header(resp,
  		      _http_message_connection_header(resp->msg)) == NULL) {
  			http_response_set_header(resp, 0,
  			    _http_message_connection_header(resp->msg),
  			    conn->keep_alive ? "Keep-Alive" : "Close");
  		}
  
  		/* Slurp up any remaining entity data if keep-alive */
  		if (conn->keep_alive && req != NULL && !feof(req->msg->input)) {
  			static char buf[1024];
  
  			while (fgets(buf, sizeof(buf), req->msg->input) != NULL)
  				;
  		}
  
  send_response:
  		/* Send back headers (if not already sent) */
  		http_response_send_headers(resp, 0);
  
  		/* Send back body (if it was buffered) */
  		_http_message_send_body(resp->msg);
  
  		/* Determine if we can still keep this connection alive */
  		if (!resp->msg->no_body
  		    && (http_response_get_header(resp,
  		       HTTP_HEADER_CONTENT_LENGTH) == NULL
  		      || ferror(conn->fp)
  		      || feof(conn->fp)
  		      || !_http_head_want_keepalive(resp->msg->head)))
  			conn->keep_alive = 0;
  
  		/* Close connection unless keeping it alive */
  		if (!conn->keep_alive)
  			break;
  
  		/* Reset request & response for next time */
  		_http_request_free(&conn->req);
  		_http_response_free(&conn->resp);
  		if (_http_request_new(conn) == -1
  		    || _http_response_new(conn) == -1)
  			break;
  	}
  
  	/* Done with this connection */
  	DBG(HTTP, "done with connection %p", conn);
  	pthread_cleanup_pop(1);
  	return (NULL);
  }
  
  /*
   * Cleanup when shutting down an HTTP server connection.
   */
  static void
  http_server_connection_cleanup(void *arg)
  {
  	struct http_connection *conn = arg;
  	struct http_server *const serv = conn->owner;
  
  	/* Lock server */
  	DBG(HTTP, "connection %p cleaning up", conn);
  	MUTEX_LOCK(&serv->mutex, serv->mutex_count);
  
  	/* Restart accepting new connections if needed */
  	if (serv->conn_event == NULL && !serv->stopping) {
  		if (pevent_register(serv->ctx, &serv->conn_event,
  		    PEVENT_RECURRING, &serv->mutex, http_server_accept,
  		    serv, PEVENT_READ, serv->sock) == -1) {
  			(*serv->logger)(LOG_ERR, "%s: %s",
  			    "pevent_register", strerror(errno));
  		}
  	}
  
  	/* Remove this connection from the list of connections */
  	LIST_REMOVE(conn, next);
  	serv->num_conn--;
  
  	/* Unlock server */
  	MUTEX_UNLOCK(&serv->mutex, serv->mutex_count);
  
  	/* Free connection */
  	_http_connection_free(&conn);
  }
  
  /*
   * Dispatch a request
   */
  static void
  http_server_dispatch(struct http_request *req, struct http_response *resp)
  {
  	struct http_connection *conn = req->msg->conn;
  	struct http_server *const serv = conn->owner;
  	const char *url = http_request_get_path(req);
  	struct http_virthost vhost_key;
  	char buf[MAXHOSTNAMELEN];
  	const char *hval;
  	int order;
  	int done;
  
  	/* Special handling for proxy requests */
  	if (conn->proxy) {
  		http_proxy_t *proxy_handler;
  		void *proxy_arg;
  
  		/* Get proxy handler from server */
  		MUTEX_LOCK(&serv->mutex, serv->mutex_count);
  		proxy_handler = serv->proxy_handler;
  		proxy_arg = serv->proxy_arg;
  		MUTEX_UNLOCK(&serv->mutex, serv->mutex_count);
  
  		/* Handle request */
  		if (proxy_handler == NULL) {
  			http_response_send_error(resp,
  			    (strcmp(http_request_get_method(req),
  			      HTTP_METHOD_CONNECT) == 0) ?
  				HTTP_STATUS_METHOD_NOT_ALLOWED :
  				HTTP_STATUS_NOT_FOUND, NULL);
  			return;
  		}
  		(*proxy_handler)(proxy_arg, req, resp);
  		return;
  	}
  
  	/* Extract the desired virtual host from the request */
  	if ((hval = http_request_get_header(req, HTTP_HEADER_HOST)) != NULL) {
  		char *s;
  
  		strlcpy(buf, hval, sizeof(buf));
  		if ((s = strchr(buf, ':')) != NULL)		/* trim port */
  			*s = '\0';
  	} else
  		*buf = '\0';			/* fall back to default vhost */
  	vhost_key.host = buf;
  
  	/* Lock server */
  	MUTEX_LOCK(&serv->mutex, serv->mutex_count);
  
  	/* Choose and fix desired virtual host before running any servlets */
  	if (ghash_get(serv->vhosts, &vhost_key) == NULL)
  		*buf = '\0';			/* fall back to default vhost */
  
  	/* Unlock server */
  	MUTEX_UNLOCK(&serv->mutex, serv->mutex_count);
  
  	/* Execute matching vhost servlets, in order, until there is output */
  	for (done = 0, order = INT_MIN; !done; ) {
  		struct http_servlet_hook *best = NULL;
  		struct http_servlet_hook *hook;
  		struct http_virthost *vhost;
  		int r;
  
  		/* Lock server */
  		MUTEX_LOCK(&serv->mutex, serv->mutex_count);
  
  		/* Find virtual host (do this each time because we unlock) */
  		if ((vhost = ghash_get(serv->vhosts, &vhost_key)) == NULL) {
  			MUTEX_UNLOCK(&serv->mutex, serv->mutex_count);
  			break;
  		}
  
  		/* Find lowest order virtual host servlet that matches */
  		LIST_FOREACH(hook, &vhost->servlets, next) {
  
  			/* Skip if we've already tried it */
  			if (hook->order <= order)
  				continue;
  
  			/* Compare URL path, choosing best match as we go */
  			if ((r = regexec(&hook->pat, url, 0, NULL, 0)) == 0) {
  				if (best == NULL || hook->order < best->order)
  					best = hook;
  				continue;
  			}
  
  			/* If it didn't match, try the next servlet */
  			if (r == REG_NOMATCH)
  				continue;
  
  			/* We got some weird error from regexec() */
  			MUTEX_UNLOCK(&serv->mutex, serv->mutex_count);
  			regerror(r, &hook->pat, buf, sizeof(buf));
  			(*serv->logger)(LOG_ERR, "%s: %s", "regexec", buf);
  			http_response_send_error(resp,
  			    HTTP_STATUS_INTERNAL_SERVER_ERROR,
  			    "regexec: %s", buf);
  			return;
  		}
  
  		/* If no matching servlet was found, we're done */
  		if ((hook = best) == NULL) {
  			MUTEX_UNLOCK(&serv->mutex, serv->mutex_count);
  			break;
  		}
  
  		/* Update lowest order for next iteration */
  		order = hook->order;
  
  		/* Lock servlet to prevent it from going away */
  		r = pthread_rwlock_rdlock(&hook->rwlock);
  		assert(r == 0);
  
  		/* Add cleanup hook that unlocks servlet */
  		pthread_cleanup_push((void (*)(void *))pthread_rwlock_unlock,
  		    &hook->rwlock);
  
  		/* Unlock server */
  		MUTEX_UNLOCK(&serv->mutex, serv->mutex_count);
  
  		/* Execute servlet */
  		done = (*hook->servlet->run)(hook->servlet, req, resp);
  
  		/* Unlock servlet */
  		pthread_cleanup_pop(1);
  
  		/* If servlet returned an error, bail out */
  		if (done == -1) {
  			http_response_send_errno_error(resp);
  			break;
  		}
  	}
  
  	/* No matching servlet found? */
  	if (!done)
  		http_response_send_error(resp, HTTP_STATUS_NOT_FOUND, NULL);
  }
  
  /*********************************************************************
  			MISC ROUTINES
  *********************************************************************/
  
  /*
   * SSL callback to get the password for encrypted private key files.
   */
  static int
  http_server_ssl_pem_password_cb(char *buf, int size, int rwflag, void *udata)
  {
  	struct http_server *const serv = udata;
  
  	(void)rwflag;
  	if (serv->pkey_pw == NULL) {
  		(*serv->logger)(LOG_ERR,
  		    "SSL private key is encrypted but no key was supplied");
  		return (-1);
  	}
  	strlcpy(buf, serv->pkey_pw, size);
  	return (strlen(buf));
  }
  
  /*
   * SSL callback for logging.
   */
  static void __printflike(3, 4)
  http_server_ssl_logger(void *arg, int sev, const char *fmt, ...)
  {
  	struct http_server *const serv = arg;
  	va_list args;
  	char *s;
  
  	/* Prepend "startup" message to error message */
  	va_start(args, fmt);
  	VASPRINTF(TYPED_MEM_TEMP, &s, fmt, args);
  	va_end(args);
  	if (s == NULL)
  		return;
  	(*serv->logger)(sev, "%s: %s", "startup error", s);
  	FREE(TYPED_MEM_TEMP, s);
  }
  
  /*
   * Virtual host hashing routines
   */
  static int
  http_server_virthost_equal(struct ghash *g,
  	const void *item1, const void *item2)
  {
  	(void)g;
  	const struct http_virthost *const vhost1 = item1;
  	const struct http_virthost *const vhost2 = item2;
  
  	return (strcasecmp(vhost1->host, vhost2->host) == 0);
  }
  
  u_int32_t
  http_server_virthost_hash(struct ghash *g, const void *item)
  {
  	const struct http_virthost *const vhost = item;
  	u_int32_t hash;
  	const char *s;
  
  	(void)g;
  	for (hash = 0, s = vhost->host; *s != '\0'; s++)
  		hash = (hash * 31) + (u_char)tolower(*s);
  	return (hash);
  }
  
  
  