File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / libpdel / http / http_server.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Tue Feb 21 23:25:53 2012 UTC (12 years, 3 months ago) by misho
Branches: libpdel, MAIN
CVS tags: v0_5_3, HEAD
libpdel


/*
 * 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 */
};

/* 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(SSLv2_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, (char *)&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 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 = (char *)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;

	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
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)
{
	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;

	for (hash = 0, s = vhost->host; *s != '\0'; s++)
		hash = (hash * 31) + (u_char)tolower(*s);
	return (hash);
}



FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>