BSDSec

deadsimple BSD Security Advisories and Announcements

httpd errata

Many people want to test the new httpd in OpenBSD 5.6; so we decided
to provide various improvements from -current for 5.6.
See the description below for more details.


untrusted comment: signature from openbsd 5.6 base private key
RWR0EANmo9nqhn3Gnfk2/2x+xII6do92zreKp/t5zOwfkVgsQAI4ZCPkWAazbbnWNV7Ptkle876f/kb6C2KuvnTqvwUItsyvogA
OpenBSD 5.6 errata 9, Nov 18, 2014:  httpd was developed very rapidly
in the weeks before 5.6 release, and it has a few flaws.  It would be
nice to get these flaws fully remediated before the next release, and
that requires the community to want to use it.  Therefore here is a
"jumbo" patch that brings in the most important fixes.

Apply patch using:

    signify -Vep /etc/signify/openbsd-56-base.pub -x 009_httpd.patch.sig \
        -m - | (cd /usr/src && patch -p0)

Then build and install httpd:

    cd /usr/src/usr.sbin/httpd
    make obj
    make
    make install

Index: usr.sbin/httpd/config.c
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/config.c,v
retrieving revision 1.21
diff -u -p -r1.21 config.c
--- usr.sbin/httpd/config.c	6 Aug 2014 18:21:14 -0000	1.21
+++ usr.sbin/httpd/config.c	18 Nov 2014 15:02:54 -0000
@@ -223,7 +223,7 @@ config_getserver_config(struct httpd *en
 #ifdef DEBUG
 	struct privsep		*ps = env->sc_ps;
 #endif
-	struct server_config	*srv_conf;
+	struct server_config	*srv_conf, *parent;
 	u_int8_t		*p = imsg->data;
 	u_int			 f;
 
@@ -233,18 +233,28 @@ config_getserver_config(struct httpd *en
 	IMSG_SIZE_CHECK(imsg, srv_conf);
 	memcpy(srv_conf, p, sizeof(*srv_conf));
 
+	/* Reset these variables to avoid free'ing invalid pointers */
+	serverconfig_reset(srv_conf);
+
+	TAILQ_FOREACH(parent, &srv->srv_hosts, entry) {
+		if (strcmp(parent->name, srv_conf->name) == 0)
+			break;
+	}
+	if (parent == NULL)
+		parent = &srv->srv_conf;
+
 	if (srv_conf->flags & SRVFLAG_LOCATION) {
 		/* Inherit configuration from the parent */
 		f = SRVFLAG_INDEX|SRVFLAG_NO_INDEX;
 		if ((srv_conf->flags & f) == 0) {
-			srv_conf->flags |= srv->srv_conf.flags & f;
-			(void)strlcpy(srv_conf->index, srv->srv_conf.index,
+			srv_conf->flags |= parent->flags & f;
+			(void)strlcpy(srv_conf->index, parent->index,
 			    sizeof(srv_conf->index));
 		}
 
 		f = SRVFLAG_AUTO_INDEX|SRVFLAG_NO_AUTO_INDEX;
 		if ((srv_conf->flags & f) == 0)
-			srv_conf->flags |= srv->srv_conf.flags & f;
+			srv_conf->flags |= parent->flags & f;
 
 		f = SRVFLAG_SOCKET|SRVFLAG_FCGI;
 		if ((srv_conf->flags & f) == SRVFLAG_FCGI) {
@@ -255,48 +265,48 @@ config_getserver_config(struct httpd *en
 
 		f = SRVFLAG_ROOT;
 		if ((srv_conf->flags & f) == 0) {
-			srv_conf->flags |= srv->srv_conf.flags & f;
-			(void)strlcpy(srv_conf->root, srv->srv_conf.root,
+			srv_conf->flags |= parent->flags & f;
+			(void)strlcpy(srv_conf->root, parent->root,
 			    sizeof(srv_conf->root));
 		}
 
 		f = SRVFLAG_FCGI|SRVFLAG_NO_FCGI;
 		if ((srv_conf->flags & f) == 0)
-			srv_conf->flags |= srv->srv_conf.flags & f;
+			srv_conf->flags |= parent->flags & f;
 
 		f = SRVFLAG_LOG|SRVFLAG_NO_LOG;
 		if ((srv_conf->flags & f) == 0) {
-			srv_conf->flags |= srv->srv_conf.flags & f;
-			srv_conf->logformat = srv->srv_conf.logformat;
+			srv_conf->flags |= parent->flags & f;
+			srv_conf->logformat = parent->logformat;
 		}
 
 		f = SRVFLAG_SYSLOG|SRVFLAG_NO_SYSLOG;
 		if ((srv_conf->flags & f) == 0)
-			srv_conf->flags |= srv->srv_conf.flags & f;
+			srv_conf->flags |= parent->flags & f;
 
 		f = SRVFLAG_SSL;
-		srv_conf->flags |= srv->srv_conf.flags & f;
+		srv_conf->flags |= parent->flags & f;
 
 		f = SRVFLAG_ACCESS_LOG;
 		if ((srv_conf->flags & f) == 0) {
-			srv_conf->flags |= srv->srv_conf.flags & f;
+			srv_conf->flags |= parent->flags & f;
 			(void)strlcpy(srv_conf->accesslog,
-			    srv->srv_conf.accesslog,
+			    parent->accesslog,
 			    sizeof(srv_conf->accesslog));
 		}
 
 		f = SRVFLAG_ERROR_LOG;
 		if ((srv_conf->flags & f) == 0) {
-			srv_conf->flags |= srv->srv_conf.flags & f;
+			srv_conf->flags |= parent->flags & f;
 			(void)strlcpy(srv_conf->errorlog,
-			    srv->srv_conf.errorlog,
+			    parent->errorlog,
 			    sizeof(srv_conf->errorlog));
 		}
 
-		memcpy(&srv_conf->timeout, &srv->srv_conf.timeout,
+		memcpy(&srv_conf->timeout, &parent->timeout,
 		    sizeof(srv_conf->timeout));
-		srv_conf->maxrequests = srv->srv_conf.maxrequests;
-		srv_conf->maxrequestbody = srv->srv_conf.maxrequestbody;
+		srv_conf->maxrequests = parent->maxrequests;
+		srv_conf->maxrequestbody = parent->maxrequestbody;
 
 		DPRINTF("%s: %s %d location \"%s\", "
 		    "parent \"%s\", flags: %s",
@@ -330,6 +340,9 @@ config_getserver(struct httpd *env, stru
 	IMSG_SIZE_CHECK(imsg, &srv_conf);
 	memcpy(&srv_conf, p, sizeof(srv_conf));
 	s = sizeof(srv_conf);
+
+	/* Reset these variables to avoid free'ing invalid pointers */
+	serverconfig_reset(&srv_conf);
 
 	if ((u_int)(IMSG_DATA_SIZE(imsg) - s) <
 	    (srv_conf.ssl_cert_len + srv_conf.ssl_key_len)) {
Index: usr.sbin/httpd/http.h
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/http.h,v
retrieving revision 1.5
diff -u -p -r1.5 http.h
--- usr.sbin/httpd/http.h	3 Aug 2014 21:33:27 -0000	1.5
+++ usr.sbin/httpd/http.h	18 Nov 2014 15:02:54 -0000
@@ -44,6 +44,32 @@ enum httpmethod {
 	HTTP_METHOD_LOCK,
 	HTTP_METHOD_UNLOCK,
 
+	/* WebDAV Versioning Extension, RFC 3253 */
+	HTTP_METHOD_VERSION_CONTROL,
+	HTTP_METHOD_REPORT,
+	HTTP_METHOD_CHECKOUT,
+	HTTP_METHOD_CHECKIN,
+	HTTP_METHOD_UNCHECKOUT,
+	HTTP_METHOD_MKWORKSPACE,
+	HTTP_METHOD_UPDATE,
+	HTTP_METHOD_LABEL,
+	HTTP_METHOD_MERGE,
+	HTTP_METHOD_BASELINE_CONTROL,
+	HTTP_METHOD_MKACTIVITY,
+
+	/* WebDAV Ordered Collections, RFC 3648 */
+	HTTP_METHOD_ORDERPATCH,
+
+	/* WebDAV Access Control, RFC 3744 */
+	HTTP_METHOD_ACL,
+
+	/* WebDAV Redirect Reference Resources, RFC 4437 */
+	HTTP_METHOD_MKREDIRECTREF,
+	HTTP_METHOD_UPDATEREDIRECTREF,
+
+	/* WebDAV Search, RFC 5323 */
+	HTTP_METHOD_SEARCH,
+
 	/* PATCH, RFC 5789 */
 	HTTP_METHOD_PATCH,
 
@@ -71,6 +97,22 @@ struct http_method {
 	{ HTTP_METHOD_MOVE,		"MOVE" },	\
 	{ HTTP_METHOD_LOCK,		"LOCK" },	\
 	{ HTTP_METHOD_UNLOCK,		"UNLOCK" },	\
+	{ HTTP_METHOD_VERSION_CONTROL,	"VERSION-CONTROL" }, \
+	{ HTTP_METHOD_REPORT,		"REPORT" },	\
+	{ HTTP_METHOD_CHECKOUT,		"CHECKOUT" },	\
+	{ HTTP_METHOD_CHECKIN,		"CHECKIN" },	\
+	{ HTTP_METHOD_UNCHECKOUT,	"UNCHECKOUT" },	\
+	{ HTTP_METHOD_MKWORKSPACE,	"MKWORKSPACE" }, \
+	{ HTTP_METHOD_UPDATE,		"UPDATE" },	\
+	{ HTTP_METHOD_LABEL,		"LABEL" },	\
+	{ HTTP_METHOD_MERGE,		"MERGE" },	\
+	{ HTTP_METHOD_BASELINE_CONTROL,	"BASELINE-CONTROL" }, \
+	{ HTTP_METHOD_MKACTIVITY,	"MKACTIVITY" },	\
+	{ HTTP_METHOD_ORDERPATCH,	"ORDERPATCH" },	\
+	{ HTTP_METHOD_ACL,		"ACL" },	\
+	{ HTTP_METHOD_MKREDIRECTREF,	"MKREDIRECTREF" }, \
+	{ HTTP_METHOD_UPDATEREDIRECTREF, "UPDATEREDIRECTREF" }, \
+	{ HTTP_METHOD_SEARCH,		"SEARCH" },	\
 	{ HTTP_METHOD_PATCH,		"PATCH" },	\
 	{ HTTP_METHOD_NONE,		NULL }		\
 }
@@ -155,6 +197,9 @@ struct http_descriptor {
 	enum httpmethod		 http_method;
 	int			 http_chunked;
 	char			*http_version;
+
+	/* Rewritten path remains NULL if not used */
+	char			*http_path_alias;
 
 	/* A tree of headers and attached lists for repeated headers. */
 	struct kv		*http_lastheader;
Index: usr.sbin/httpd/httpd.c
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/httpd.c,v
retrieving revision 1.17
diff -u -p -r1.17 httpd.c
--- usr.sbin/httpd/httpd.c	5 Aug 2014 15:36:59 -0000	1.17
+++ usr.sbin/httpd/httpd.c	18 Nov 2014 15:02:54 -0000
@@ -289,10 +289,20 @@ parent_configure(struct httpd *env)
 			fatal("send media");
 	}
 
+	/* First send the servers... */
 	TAILQ_FOREACH(srv, env->sc_servers, srv_entry) {
+		if (srv->srv_conf.flags & SRVFLAG_LOCATION)
+			continue;
 		if (config_setserver(env, srv) == -1)
 			fatal("send server");
 	}
+	/* ...and now send the locations */
+	TAILQ_FOREACH(srv, env->sc_servers, srv_entry) {
+		if ((srv->srv_conf.flags & SRVFLAG_LOCATION) == 0)
+			continue;
+		if (config_setserver(env, srv) == -1)
+			fatal("send location");
+	}
 
 	/* The servers need to reload their config. */
 	env->sc_reload = env->sc_prefork_server + 1;
@@ -526,6 +536,46 @@ canonicalize_host(const char *host, char
 }
 
 const char *
+url_decode(char *url)
+{
+	char	*p, *q;
+	char	 hex[3];
+	u_long	 x;
+
+	hex[2] = '\0';
+	p = q = url;
+
+	while (*p != '\0') {
+		switch (*p) {
+		case '%':
+			/* Encoding character is followed by two hex chars */
+			if (!(isxdigit(p[1]) && isxdigit(p[2])))
+				return (NULL);
+
+			hex[0] = p[1];
+			hex[1] = p[2];
+
+			/*
+			 * We don't have to validate "hex" because it is
+			 * guaranteed to include two hex chars followed by nul.
+			 */
+			x = strtoul(hex, NULL, 16);		
+			*q = (char)x;
+			p += 2;
+			break;
+		default:
+			*q = *p;
+			break;
+		}
+		p++;
+		q++;
+	}
+	*q = '\0';
+
+	return(url);
+}
+
+const char *
 canonicalize_path(const char *input, char *path, size_t len)
 {
 	const char	*i;
@@ -580,28 +630,33 @@ canonicalize_path(const char *input, cha
 	return (path);
 }
 
-ssize_t
-path_info(char *name)
+size_t
+path_info(char *path)
 {
-	char		*p, *start, *end;
-	char		 path[MAXPATHLEN];
+	char		*p, *start, *end, ch;
 	struct stat	 st;
-
-	if (strlcpy(path, name, sizeof(path)) >= sizeof(path))
-		return (-1);
+	int		 ret;
 
 	start = path;
 	end = start + strlen(path);
 
 	for (p = end; p > start; p--) {
-		if (*p != '/')
+		/* Scan every path component from the end and at each '/' */
+		if (p < end && *p != '/')
 			continue;
-		if (stat(path, &st) == 0)
-			break;
+
+		/* Temporarily cut the path component out */
+		ch = *p;
 		*p = '\0';
+		ret = stat(path, &st);
+		*p = ch;
+
+		/* Break if the initial path component was found */
+		if (ret == 0)
+			break;
 	}
 
-	return (strlen(path));
+	return (p - start);
 }
 
 void
@@ -623,6 +678,40 @@ socket_rlimit(int maxfd)
 		rl.rlim_cur = MAX(rl.rlim_max, (rlim_t)maxfd);
 	if (setrlimit(RLIMIT_NOFILE, &rl) == -1)
 		fatal("socket_rlimit: failed to set resource limit");
+}
+
+char *
+evbuffer_getline(struct evbuffer *evb)
+{
+	u_int8_t	*ptr = EVBUFFER_DATA(evb);
+	size_t		 len = EVBUFFER_LENGTH(evb);
+	char		*str;
+	u_int		 i;
+
+	/* Safe version of evbuffer_readline() */
+	if ((str = get_string(ptr, len)) == NULL)
+		return (NULL);
+
+	for (i = 0; str[i] != '\0'; i++) {
+		if (str[i] == '\r' || str[i] == '\n')
+			break;
+	}
+
+	if (i == len) {
+		free(str);
+		return (NULL);
+	}
+
+	str[i] = '\0';
+
+	if ((i + 1) < len) {
+		if (ptr[i] == '\r' && ptr[i + 1] == '\n')
+			i++;
+	}
+
+	evbuffer_drain(evb, ++i);
+
+	return (str);
 }
 
 char *
Index: usr.sbin/httpd/httpd.h
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/httpd.h,v
retrieving revision 1.51
diff -u -p -r1.51 httpd.h
--- usr.sbin/httpd/httpd.h	6 Aug 2014 18:21:14 -0000	1.51
+++ usr.sbin/httpd/httpd.h	18 Nov 2014 15:02:54 -0000
@@ -276,7 +276,8 @@ struct client {
 	size_t			 clt_buflen;
 	struct evbuffer		*clt_output;
 	struct event		 clt_ev;
-	void			*clt_desc;
+	void			*clt_descreq;
+	void			*clt_descresp;
 	int			 clt_sndbufsiz;
 
 	int			 clt_fd;
@@ -294,6 +295,8 @@ struct client {
 	int			 clt_fcgi_toread;
 	int			 clt_fcgi_padding_len;
 	int			 clt_fcgi_type;
+	int			 clt_fcgi_chunked;
+	int			 clt_fcgi_end;
 	struct evbuffer		*clt_srvevb;
 
 	struct evbuffer		*clt_log;
@@ -463,6 +466,8 @@ pid_t	 server(struct privsep *, struct p
 int	 server_ssl_load_keypair(struct server *);
 int	 server_privinit(struct server *);
 void	 server_purge(struct server *);
+void	 serverconfig_free(struct server_config *);
+void	 serverconfig_reset(struct server_config *);
 int	 server_socket_af(struct sockaddr_storage *, in_port_t);
 in_port_t
 	 server_socket_getport(struct sockaddr_storage *);
@@ -477,6 +482,8 @@ void	 server_sendlog(struct server_confi
 void	 server_close(struct client *, const char *);
 void	 server_dump(struct client *, const void *, size_t);
 int	 server_client_cmp(struct client *, struct client *);
+int	 server_bufferevent_printf(struct client *, const char *, ...)
+	    __attribute__((__format__ (printf, 2, 3)));
 int	 server_bufferevent_print(struct client *, const char *);
 int	 server_bufferevent_write_buffer(struct client *,
 	    struct evbuffer *);
@@ -508,17 +515,20 @@ const char
 void	 server_read_httpcontent(struct bufferevent *, void *);
 void	 server_read_httpchunks(struct bufferevent *, void *);
 int	 server_writeheader_http(struct client *clt, struct kv *, void *);
-int	 server_headers(struct client *,
+int	 server_headers(struct client *, void *,
 	    int (*)(struct client *, struct kv *, void *), void *);
 int	 server_writeresponse_http(struct client *);
 int	 server_response_http(struct client *, u_int, struct media_type *,
-	    size_t);
+	    size_t, time_t);
 void	 server_reset_http(struct client *);
 void	 server_close_http(struct client *);
 int	 server_response(struct httpd *, struct client *);
+struct server_config *
+	 server_getlocation(struct client *, const char *);
 const char *
 	 server_http_host(struct sockaddr_storage *, char *, size_t);
-void	 server_http_date(char *, size_t);
+char	*server_http_parsehost(char *, char *, size_t, int *);
+ssize_t	 server_http_time(time_t, char *, size_t);
 int	 server_log_http(struct client *, u_int, size_t);
 
 /* server_file.c */
@@ -533,13 +543,15 @@ int	 fcgi_add_stdin(struct client *, str
 void		 event_again(struct event *, int, short,
 		    void (*)(int, short, void *),
 		    struct timeval *, struct timeval *, void *);
+const char	*url_decode(char *);
 const char	*canonicalize_host(const char *, char *, size_t);
 const char	*canonicalize_path(const char *, char *, size_t);
-ssize_t		 path_info(char *);
+size_t		 path_info(char *);
 void		 imsg_event_add(struct imsgev *);
 int		 imsg_compose_event(struct imsgev *, u_int16_t, u_int32_t,
 		    pid_t, int, void *, u_int16_t);
 void		 socket_rlimit(int);
+char		*evbuffer_getline(struct evbuffer *);
 char		*get_string(u_int8_t *, size_t);
 void		*get_data(u_int8_t *, size_t);
 int		 sockaddr_cmp(struct sockaddr *, struct sockaddr *, int);
@@ -575,6 +587,7 @@ void	log_warn(const char *, ...) __attri
 void	log_warnx(const char *, ...) __attribute__((__format__ (printf, 1, 2)));
 void	log_info(const char *, ...) __attribute__((__format__ (printf, 1, 2)));
 void	log_debug(const char *, ...) __attribute__((__format__ (printf, 1, 2)));
+void	logit(int, const char *, ...) __attribute__((__format__ (printf, 2, 3)));
 void	vlog(int, const char *, va_list) __attribute__((__format__ (printf, 2, 0)));
 __dead void fatal(const char *);
 __dead void fatalx(const char *);
Index: usr.sbin/httpd/logger.c
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/logger.c,v
retrieving revision 1.5
diff -u -p -r1.5 logger.c
--- usr.sbin/httpd/logger.c	6 Aug 2014 12:56:58 -0000	1.5
+++ usr.sbin/httpd/logger.c	18 Nov 2014 15:02:55 -0000
@@ -194,6 +194,9 @@ logger_open(struct server *srv, struct s
 {
 	struct log_file	*log, *logfile = NULL, *errfile = NULL;
 
+	if (srv_conf->flags & SRVFLAG_SYSLOG)
+		return(0);
+
 	/* disassociate */
 	srv_conf->logaccess = srv_conf->logerror = NULL;
 
Index: usr.sbin/httpd/parse.y
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/parse.y,v
retrieving revision 1.34
diff -u -p -r1.34 parse.y
--- usr.sbin/httpd/parse.y	6 Aug 2014 20:29:54 -0000	1.34
+++ usr.sbin/httpd/parse.y	18 Nov 2014 15:02:55 -0000
@@ -180,7 +180,7 @@ main		: PREFORK NUMBER	{
 				break;
 			if ($2 <= 0 || $2 > SERVER_MAXPROC) {
 				yyerror("invalid number of preforked "
-				    "servers: %d", $2);
+				    "servers: %lld", $2);
 				YYERROR;
 			}
 			conf->sc_prefork_server = $2;
@@ -198,15 +198,6 @@ server		: SERVER STRING		{
 				YYACCEPT;
 			}
 
-			TAILQ_FOREACH(s, conf->sc_servers, srv_entry)
-				if (!strcmp(s->srv_conf.name, $2))
-					break;
-			if (s != NULL) {
-				yyerror("server %s defined twice", $2);
-				free($2);
-				YYERROR;
-			}
-
 			if ((s = calloc(1, sizeof (*s))) == NULL)
 				fatal("out of memory");
 
@@ -252,18 +243,46 @@ server		: SERVER STRING		{
 			srv_conf = &srv->srv_conf;
 
 			SPLAY_INIT(&srv->srv_clients);
-			TAILQ_INSERT_TAIL(conf->sc_servers, srv, srv_entry);
 		} '{' optnl serveropts_l '}'	{
+			struct server	*s = NULL;
+
+			TAILQ_FOREACH(s, conf->sc_servers, srv_entry) {
+				if ((s->srv_conf.flags &
+				    SRVFLAG_LOCATION) == 0 &&
+				    strcmp(s->srv_conf.name,
+				    srv->srv_conf.name) == 0 &&
+				    s->srv_conf.port == srv->srv_conf.port &&
+				    sockaddr_cmp(
+				    (struct sockaddr *)&s->srv_conf.ss,
+				    (struct sockaddr *)&srv->srv_conf.ss,
+				    s->srv_conf.prefixlen) == 0)
+					break;
+			}
+			if (s != NULL) {
+				yyerror("server \"%s\" defined twice",
+				    srv->srv_conf.name);
+				serverconfig_free(srv_conf);
+				free(srv);
+				YYABORT;
+			}
+
 			if (srv->srv_conf.ss.ss_family == AF_UNSPEC) {
 				yyerror("listen address not specified");
-				free($2);
+				serverconfig_free(srv_conf);
+				free(srv);
 				YYERROR;
 			}
+
 			if (server_ssl_load_keypair(srv) == -1) {
 				yyerror("failed to load public/private keys "
 				    "for server %s", srv->srv_conf.name);
+				serverconfig_free(srv_conf);
+				free(srv);
 				YYERROR;
 			}
+
+			TAILQ_INSERT_TAIL(conf->sc_servers, srv, srv_entry);
+
 			srv = NULL;
 			srv_conf = NULL;
 		}
@@ -367,17 +386,6 @@ serveroptsl	: LISTEN ON STRING optssl po
 				YYACCEPT;
 			}
 
-			TAILQ_FOREACH(s, conf->sc_servers, srv_entry)
-				if (strcmp(s->srv_conf.name,
-				    srv->srv_conf.name) == 0 &&
-				    strcmp(s->srv_conf.location, $2) == 0)
-					break;
-			if (s != NULL) {
-				yyerror("location %s defined twice", $2);
-				free($2);
-				YYERROR;
-			}
-
 			if ((s = calloc(1, sizeof (*s))) == NULL)
 				fatal("out of memory");
 
@@ -416,12 +424,31 @@ serveroptsl	: LISTEN ON STRING optssl po
 			srv = s;
 			srv_conf = &srv->srv_conf;
 			SPLAY_INIT(&srv->srv_clients);
-			TAILQ_INSERT_TAIL(conf->sc_servers, srv, srv_entry);
 		} '{' optnl serveropts_l '}'	{
+			struct server	*s = NULL;
+
+			TAILQ_FOREACH(s, conf->sc_servers, srv_entry) {
+				if ((s->srv_conf.flags & SRVFLAG_LOCATION) &&
+				    s->srv_conf.id == srv_conf->id &&
+				    strcmp(s->srv_conf.location,
+				    srv_conf->location) == 0)
+					break;
+			}
+			if (s != NULL) {
+				yyerror("location \"%s\" defined twice",
+				    srv->srv_conf.location);
+				serverconfig_free(srv_conf);
+				free(srv);
+				YYABORT;
+			}
+
+			TAILQ_INSERT_TAIL(conf->sc_servers, srv, srv_entry);
+
 			srv = parentsrv;
 			srv_conf = &parentsrv->srv_conf;
 			parentsrv = NULL;
 		}
+		| include
 		;
 
 fastcgi		: NO FCGI		{
@@ -623,7 +650,7 @@ tcpflags	: SACK			{ srv_conf->tcpflags |
 		}
 		| BACKLOG NUMBER	{
 			if ($2 < 0 || $2 > SERVER_MAX_CLIENTS) {
-				yyerror("invalid backlog: %d", $2);
+				yyerror("invalid backlog: %lld", $2);
 				YYERROR;
 			}
 			srv_conf->tcpbacklog = $2;
@@ -631,13 +658,13 @@ tcpflags	: SACK			{ srv_conf->tcpflags |
 		| SOCKET BUFFER NUMBER	{
 			srv_conf->tcpflags |= TCPFLAG_BUFSIZ;
 			if ((srv_conf->tcpbufsiz = $3) < 0) {
-				yyerror("invalid socket buffer size: %d", $3);
+				yyerror("invalid socket buffer size: %lld", $3);
 				YYERROR;
 			}
 		}
 		| IP STRING NUMBER	{
 			if ($3 < 0) {
-				yyerror("invalid ttl: %d", $3);
+				yyerror("invalid ttl: %lld", $3);
 				free($2);
 				YYERROR;
 			}
@@ -694,6 +721,9 @@ medianamesl	: STRING				{
 			}
 			free($1);
 
+			if (!loadcfg)
+				break;
+
 			if (media_add(conf->sc_mediatypes, &media) == NULL) {
 				yyerror("failed to add media type");
 				YYERROR;
@@ -729,7 +759,7 @@ port		: PORT STRING {
 		}
 		| PORT NUMBER {
 			if ($2 <= 0 || $2 >= (int)USHRT_MAX) {
-				yyerror("invalid port: %d", $2);
+				yyerror("invalid port: %lld", $2);
 				YYERROR;
 			}
 			$$.val[0] = htons($2);
@@ -740,7 +770,7 @@ port		: PORT STRING {
 timeout		: NUMBER
 		{
 			if ($1 < 0) {
-				yyerror("invalid timeout: %d\n", $1);
+				yyerror("invalid timeout: %lld", $1);
 				YYERROR;
 			}
 			$$.tv_sec = $1;
@@ -771,15 +801,15 @@ int
 yyerror(const char *fmt, ...)
 {
 	va_list		 ap;
-	char		*nfmt;
+	char		*msg;
 
 	file->errors++;
 	va_start(ap, fmt);
-	if (asprintf(&nfmt, "%s:%d: %s", file->name, yylval.lineno, fmt) == -1)
-		fatalx("yyerror asprintf");
-	vlog(LOG_CRIT, nfmt, ap);
+	if (vasprintf(&msg, fmt, ap) == -1)
+		fatalx("yyerror vasprintf");
 	va_end(ap);
-	free(nfmt);
+	logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
+	free(msg);
 	return (0);
 }
 
Index: usr.sbin/httpd/server.c
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/server.c,v
retrieving revision 1.39
diff -u -p -r1.39 server.c
--- usr.sbin/httpd/server.c	6 Aug 2014 18:38:11 -0000	1.39
+++ usr.sbin/httpd/server.c	18 Nov 2014 15:02:55 -0000
@@ -285,8 +285,7 @@ server_purge(struct server *srv)
 
 		/* It might point to our own "default" entry */
 		if (srv_conf != &srv->srv_conf) {
-			free(srv_conf->ssl_cert);
-			free(srv_conf->ssl_key);
+			serverconfig_free(srv_conf);
 			free(srv_conf);
 		}
 	}
@@ -297,6 +296,22 @@ server_purge(struct server *srv)
 	free(srv);
 }
 
+void
+serverconfig_free(struct server_config *srv_conf)
+{
+	free(srv_conf->ssl_cert_file);
+	free(srv_conf->ssl_cert);
+	free(srv_conf->ssl_key_file);
+	free(srv_conf->ssl_key);
+}
+
+void
+serverconfig_reset(struct server_config *srv_conf)
+{
+	srv_conf->ssl_cert_file = srv_conf->ssl_cert =
+	    srv_conf->ssl_key_file = srv_conf->ssl_key = NULL;
+}
+
 struct server *
 server_byaddr(struct sockaddr *addr, in_port_t port)
 {
@@ -750,23 +765,36 @@ void
 server_error(struct bufferevent *bev, short error, void *arg)
 {
 	struct client		*clt = arg;
+	struct evbuffer		*dst;
 
 	if (error & EVBUFFER_TIMEOUT) {
 		server_close(clt, "buffer event timeout");
 		return;
 	}
-	if (error & EVBUFFER_ERROR && errno == EFBIG) {
-		bufferevent_enable(bev, EV_READ);
+	if (error & EVBUFFER_ERROR) {
+		if (errno == EFBIG) {
+			bufferevent_enable(bev, EV_READ);
+			return;
+		}
+		server_close(clt, "buffer event error");
 		return;
 	}
 	if (error & (EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF)) {
 		bufferevent_disable(bev, EV_READ|EV_WRITE);
 
 		clt->clt_done = 1;
+
+		dst = EVBUFFER_OUTPUT(clt->clt_bev);
+		if (EVBUFFER_LENGTH(dst)) {
+			/* Finish writing all data first */
+			bufferevent_enable(clt->clt_bev, EV_WRITE);
+			return;
+		}
+
 		server_close(clt, "done");
 		return;
 	}
-	server_close(clt, "buffer event error");
+	server_close(clt, "unknown event error");
 	return;
 }
 
@@ -1109,6 +1137,26 @@ server_bufferevent_add(struct event *ev,
 	}
 
 	return (event_add(ev, ptv));
+}
+
+int
+server_bufferevent_printf(struct client *clt, const char *fmt, ...)
+{
+	int	 ret;
+	va_list	 ap;
+	char	*str;
+
+	va_start(ap, fmt);
+	ret = vasprintf(&str, fmt, ap);
+	va_end(ap);
+
+	if (ret == -1)
+		return (ret);
+
+	ret = server_bufferevent_print(clt, str);
+	free(str);
+
+	return (ret);
 }
 
 int
Index: usr.sbin/httpd/server_fcgi.c
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/server_fcgi.c,v
retrieving revision 1.29
diff -u -p -r1.29 server_fcgi.c
--- usr.sbin/httpd/server_fcgi.c	7 Aug 2014 12:43:22 -0000	1.29
+++ usr.sbin/httpd/server_fcgi.c	18 Nov 2014 15:02:55 -0000
@@ -87,22 +87,24 @@ struct server_fcgi_param {
 int	server_fcgi_header(struct client *, u_int);
 void	server_fcgi_read(struct bufferevent *, void *);
 int	server_fcgi_writeheader(struct client *, struct kv *, void *);
+int	server_fcgi_writechunk(struct client *);
+int	server_fcgi_getheaders(struct client *);
 int	fcgi_add_param(struct server_fcgi_param *, const char *, const char *,
 	    struct client *);
-int	get_status(struct evbuffer *);
 
 int
 server_fcgi(struct httpd *env, struct client *clt)
 {
 	struct server_fcgi_param	 param;
-	char				 hbuf[MAXHOSTNAMELEN];
 	struct server_config		*srv_conf = clt->clt_srv_conf;
-	struct http_descriptor		*desc	= clt->clt_desc;
+	struct http_descriptor		*desc = clt->clt_descreq;
 	struct sockaddr_un		 sun;
 	struct fcgi_record_header	*h;
 	struct fcgi_begin_request_body	*begin;
 	size_t				 len;
-	ssize_t				 scriptlen;
+	char				 hbuf[MAXHOSTNAMELEN];
+	size_t				 scriptlen;
+	int				 pathlen;
 	int				 fd = -1, ret;
 	const char			*errstr = NULL;
 	char				*str, *p, *script = NULL;
@@ -189,14 +191,21 @@ server_fcgi(struct httpd *env, struct cl
 	h->type = FCGI_PARAMS;
 	h->content_len = param.total_len = 0;
 
-	if (asprintf(&script, "%s%s", srv_conf->root,
-	    desc->http_path) == -1 ||
-	    (scriptlen = path_info(script)) == -1) {
+	if ((pathlen = asprintf(&script, "%s%s", srv_conf->root,
+	    desc->http_path_alias != NULL ?
+	    desc->http_path_alias : desc->http_path)) == -1) {
 		errstr = "failed to get script name";
 		goto fail;
 	}
 
-	if (scriptlen) {
+	scriptlen = path_info(script);
+	/*
+	 * no part of root should show up in PATH_INFO.
+	 * therefore scriptlen should be >= strlen(root)
+	 */
+	if (scriptlen < strlen(srv_conf->root))
+		scriptlen = strlen(srv_conf->root);
+	if ((int)scriptlen < pathlen) {
 		if (fcgi_add_param(&param, "PATH_INFO",
 		    script + scriptlen, clt) == -1) {
 			errstr = "failed to encode param";
@@ -239,7 +248,7 @@ server_fcgi(struct httpd *env, struct cl
 	}
 
 	/* Add HTTP_* headers */
-	if (server_headers(clt, server_fcgi_writeheader, &param) == -1) {
+	if (server_headers(clt, desc, server_fcgi_writeheader, &param) == -1) {
 		errstr = "failed to encode param";
 		goto fail;
 	}
@@ -337,11 +346,14 @@ server_fcgi(struct httpd *env, struct cl
 		fcgi_add_stdin(clt, NULL);
 	}
 
-	/*
-	 * persist is not supported yet because we don't get the
-	 * Content-Length from slowcgi and don't support chunked encoding.
-	 */
-	clt->clt_persist = 0;
+	if (strcmp(desc->http_version, "HTTP/1.1") == 0) {
+		clt->clt_fcgi_chunked = 1;
+	} else {
+		/* HTTP/1.0 does not support chunked encoding */
+		clt->clt_fcgi_chunked = 0;
+		clt->clt_persist = 0;
+	}
+	clt->clt_fcgi_end = 0;
 	clt->clt_done = 0;
 
 	free(script);
@@ -444,9 +456,9 @@ server_fcgi_read(struct bufferevent *bev
 	char				*ptr;
 
 	do {
-		len = bufferevent_read(bev, &buf, clt->clt_fcgi_toread);
+		len = bufferevent_read(bev, buf, clt->clt_fcgi_toread);
 		/* XXX error handling */
-		evbuffer_add(clt->clt_srvevb, &buf, len);
+		evbuffer_add(clt->clt_srvevb, buf, len);
 		clt->clt_fcgi_toread -= len;
 		DPRINTF("%s: len: %lu toread: %d state: %d", __func__, len,
 		    clt->clt_fcgi_toread, clt->clt_fcgi_state);
@@ -478,9 +490,10 @@ server_fcgi_read(struct bufferevent *bev
 
 			/* fallthrough if content_len == 0 */
 		case FCGI_READ_CONTENT:
-			if (clt->clt_fcgi_type == FCGI_STDERR &&
-			    EVBUFFER_LENGTH(clt->clt_srvevb) > 0) {
-				if ((ptr = get_string(
+			switch (clt->clt_fcgi_type) {
+			case FCGI_STDERR:
+				if (EVBUFFER_LENGTH(clt->clt_srvevb) > 0 &&
+				    (ptr = get_string(
 				    EVBUFFER_DATA(clt->clt_srvevb),
 				    EVBUFFER_LENGTH(clt->clt_srvevb)))
 				    != NULL) {
@@ -488,14 +501,27 @@ server_fcgi_read(struct bufferevent *bev
 					    IMSG_LOG_ERROR, "%s", ptr);
 					free(ptr);
 				}
-			}
-			if (clt->clt_fcgi_type == FCGI_STDOUT &&
-			    EVBUFFER_LENGTH(clt->clt_srvevb) > 0) {
-				if (++clt->clt_chunk == 1)
-					server_fcgi_header(clt,
-					    get_status(clt->clt_srvevb));
-				server_bufferevent_write_buffer(clt,
-				    clt->clt_srvevb);
+				break;
+			case FCGI_STDOUT:
+				if (++clt->clt_chunk == 1) {
+					if (server_fcgi_header(clt,
+					    server_fcgi_getheaders(clt))
+					    == -1) {
+						server_abort_http(clt, 500,
+						    "malformed fcgi headers");
+						return;
+					}
+					if (!EVBUFFER_LENGTH(clt->clt_srvevb))
+						break;
+				}
+				/* FALLTHROUGH */
+			case FCGI_END_REQUEST:
+				if (server_fcgi_writechunk(clt) == -1) {
+					server_abort_http(clt, 500,
+					    "encoding error");
+					return;
+				}
+				break;
 			}
 			evbuffer_drain(clt->clt_srvevb,
 			    EVBUFFER_LENGTH(clt->clt_srvevb));
@@ -523,9 +549,11 @@ server_fcgi_read(struct bufferevent *bev
 int
 server_fcgi_header(struct client *clt, u_int code)
 {
-	struct http_descriptor	*desc = clt->clt_desc;
+	struct http_descriptor	*desc = clt->clt_descreq;
+	struct http_descriptor	*resp = clt->clt_descresp;
 	const char		*error;
 	char			 tmbuf[32];
+	struct kv		*kv, key;
 
 	if (desc == NULL || (error = server_httperror_byid(code)) == NULL)
 		return (-1);
@@ -533,34 +561,49 @@ server_fcgi_header(struct client *clt, u
 	if (server_log_http(clt, code, 0) == -1)
 		return (-1);
 
-	kv_purge(&desc->http_headers);
-
 	/* Add error codes */
-	if (kv_setkey(&desc->http_pathquery, "%lu", code) == -1 ||
-	    kv_set(&desc->http_pathquery, "%s", error) == -1)
+	if (kv_setkey(&resp->http_pathquery, "%lu", code) == -1 ||
+	    kv_set(&resp->http_pathquery, "%s", error) == -1)
 		return (-1);
 
 	/* Add headers */
-	if (kv_add(&desc->http_headers, "Server", HTTPD_SERVERNAME) == NULL)
+	if (kv_add(&resp->http_headers, "Server", HTTPD_SERVERNAME) == NULL)
 		return (-1);
 
+	/* Set chunked encoding */
+	if (clt->clt_fcgi_chunked) {
+		/* XXX Should we keep and handle Content-Length instead? */
+		key.kv_key = "Content-Length";
+		if ((kv = kv_find(&resp->http_headers, &key)) != NULL)
+			kv_delete(&resp->http_headers, kv);
+
+		/*
+		 * XXX What if the FastCGI added some kind of Transfer-Encoding?
+		 * XXX like gzip, deflate or even "chunked"?
+		 */
+		if (kv_add(&resp->http_headers,
+		    "Transfer-Encoding", "chunked") == NULL)
+			return (-1);
+	}
+
 	/* Is it a persistent connection? */
 	if (clt->clt_persist) {
-		if (kv_add(&desc->http_headers,
+		if (kv_add(&resp->http_headers,
 		    "Connection", "keep-alive") == NULL)
 			return (-1);
-	} else if (kv_add(&desc->http_headers, "Connection", "close") == NULL)
+	} else if (kv_add(&resp->http_headers, "Connection", "close") == NULL)
 		return (-1);
 
-	/* Date header is mandatory and should be added last */
-	server_http_date(tmbuf, sizeof(tmbuf));
-	if (kv_add(&desc->http_headers, "Date", tmbuf) == NULL)
+	/* Date header is mandatory and should be added as late as possible */
+	if (server_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0 ||
+	    kv_add(&resp->http_headers, "Date", tmbuf) == NULL)
 		return (-1);
 
 	/* Write initial header (fcgi might append more) */
 	if (server_writeresponse_http(clt) == -1 ||
 	    server_bufferevent_print(clt, "\r\n") == -1 ||
-	    server_headers(clt, server_writeheader_http, NULL) == -1)
+	    server_headers(clt, resp, server_writeheader_http, NULL) == -1 ||
+	    server_bufferevent_print(clt, "\r\n") == -1)
 		return (-1);
 
 	return (0);
@@ -608,26 +651,63 @@ server_fcgi_writeheader(struct client *c
 }
 
 int
-get_status(struct evbuffer *bev)
+server_fcgi_writechunk(struct client *clt)
 {
-	int code;
-	char *statusline, *tok;
-	const char *errstr;
-
-	/* XXX This is a hack. We need to parse the response header. */
-	code = 200;
-	if (strncmp(EVBUFFER_DATA(bev), "Status: ", strlen("Status: ")) == 0) {
-		statusline = get_string(EVBUFFER_DATA(bev),
-		    EVBUFFER_LENGTH(bev));
-		if (strtok(statusline, " ") != NULL) {
-			if ((tok = strtok(NULL, " ")) != NULL) {
-				code = (int) strtonum(tok, 100, 600, &errstr);
-				if (errstr != NULL || server_httperror_byid(
-				   code) == NULL)
-					code = 200;
-			}
+	struct evbuffer *evb = clt->clt_srvevb;
+	size_t		 len;
+
+	if (clt->clt_fcgi_type == FCGI_END_REQUEST) {
+		len = 0;
+	} else
+		len = EVBUFFER_LENGTH(evb);
+
+	/* If len is 0, make sure to write the end marker only once */
+	if (len == 0 && clt->clt_fcgi_end++)
+		return (0);
+
+	if (clt->clt_fcgi_chunked) {
+		if (server_bufferevent_printf(clt, "%zx\r\n", len) == -1 ||
+		    server_bufferevent_write_chunk(clt, evb, len) == -1 ||
+		    server_bufferevent_print(clt, "\r\n") == -1)
+			return (-1);
+	} else
+		return (server_bufferevent_write_buffer(clt, evb));
+
+	return (0);
+}
+
+int
+server_fcgi_getheaders(struct client *clt)
+{
+	struct http_descriptor	*resp = clt->clt_descresp;
+	struct evbuffer		*evb = clt->clt_srvevb;
+	int			 code = 200;
+	char			*line, *key, *value;
+	const char		*errstr;
+
+	while ((line = evbuffer_getline(evb)) != NULL && *line != '\0') {
+		key = line;
+
+		if ((value = strchr(key, ':')) == NULL)
+			break;
+		if (*value == ':') {
+			*value++ = '\0';
+			value += strspn(value, " \t");
+		} else {
+			*value++ = '\0';
+		}
+
+		if (strcasecmp("Status", key) == 0) {
+			value[strcspn(value, " \t")] = '\0';
+			code = (int)strtonum(value, 100, 600, &errstr);
+			if (errstr != NULL || server_httperror_byid(
+			    code) == NULL)
+				code = 200;
+		} else {
+			(void)kv_add(&resp->http_headers, key, value);
 		}
-		free(statusline);
+		free(line);
 	}
-	return code;
+
+	return (code);
 }
Index: usr.sbin/httpd/server_file.c
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/server_file.c,v
retrieving revision 1.31
diff -u -p -r1.31 server_file.c
--- usr.sbin/httpd/server_file.c	6 Aug 2014 11:24:12 -0000	1.31
+++ usr.sbin/httpd/server_file.c	18 Nov 2014 15:02:55 -0000
@@ -46,42 +46,36 @@
 #include "httpd.h"
 #include "http.h"
 
-int	 server_file_access(struct client *, char *, size_t,
+int	 server_file_access(struct httpd *, struct client *, char *, size_t);
+int	 server_file_request(struct httpd *, struct client *, char *,
 	    struct stat *);
-int	 server_file_index(struct httpd *, struct client *);
+int	 server_file_index(struct httpd *, struct client *, struct stat *);
+int	 server_file_method(struct client *);
 
 int
-server_file_access(struct client *clt, char *path, size_t len,
-    struct stat *st)
+server_file_access(struct httpd *env, struct client *clt,
+    char *path, size_t len)
 {
-	struct http_descriptor	*desc = clt->clt_desc;
+	struct http_descriptor	*desc = clt->clt_descreq;
 	struct server_config	*srv_conf = clt->clt_srv_conf;
-	struct stat		 stb;
+	struct stat		 st;
 	char			*newpath;
+	int			 ret;
 
 	errno = 0;
 
-	switch (desc->http_method) {
-	case HTTP_METHOD_GET:
-	case HTTP_METHOD_HEAD:
-		break;
-	default:
-		/* Other methods are not allowed */
-		return (405);
-	}
-
 	if (access(path, R_OK) == -1) {
 		goto fail;
-	} else if (stat(path, st) == -1) {
+	} else if (stat(path, &st) == -1) {
 		goto fail;
-	} else if (S_ISDIR(st->st_mode)) {
+	} else if (S_ISDIR(st.st_mode)) {
 		/* Deny access if directory indexing is disabled */
 		if (srv_conf->flags & SRVFLAG_NO_INDEX) {
 			errno = EACCES;
 			goto fail;
 		}
 
-		if (!len) {
+		if (desc->http_path_alias != NULL) {
 			/* Recursion - the index "file" is a directory? */
 			errno = EINVAL;
 			goto fail;
@@ -93,21 +87,31 @@ server_file_access(struct client *clt, c
 			    srv_conf->flags & SRVFLAG_SSL ? "s" : "",
 			    desc->http_host, desc->http_path) == -1)
 				return (500);
-			free(desc->http_path);
-			desc->http_path = newpath;
+			/* Path alias will be used for the redirection */
+			desc->http_path_alias = newpath;
 
 			/* Indicate that the file has been moved */
 			return (301);
 		}
 
-		/* Otherwise append the default index file */
+		/* Append the default index file to the location */
+		if (asprintf(&newpath, "%s%s", desc->http_path,
+		    srv_conf->index) == -1)
+			return (500);
+		desc->http_path_alias = newpath;
+		if (server_getlocation(clt, newpath) != srv_conf) {
+			/* The location has changed */
+			return (server_file(env, clt));
+		}
+
+		/* Otherwise append the default index file to the path */
 		if (strlcat(path, srv_conf->index, len) >= len) {
 			errno = EACCES;
 			goto fail;
 		}
 
-		/* Check again but set len to 0 to avoid recursion */
-		if (server_file_access(clt, path, 0, &stb) == 404) {
+		ret = server_file_access(env, clt, path, len);
+		if (ret == 404) {
 			/*
 			 * Index file not found; fail if auto-indexing is
 			 * not enabled, otherwise return success but
@@ -118,17 +122,17 @@ server_file_access(struct client *clt, c
 				errno = EACCES;
 				goto fail;
 			}
-		} else {
-			/* return updated stat from index file */
-			memcpy(st, &stb, sizeof(*st));
+
+			return (server_file_index(env, clt, &st));
 		}
-	} else if (!S_ISREG(st->st_mode)) {
+		return (ret);
+	} else if (!S_ISREG(st.st_mode)) {
 		/* Don't follow symlinks and ignore special files */
 		errno = EACCES;
 		goto fail;
 	}
 
-	return (0);
+	return (server_file_request(env, clt, path, &st));
 
  fail:
 	switch (errno) {
@@ -146,31 +150,69 @@ server_file_access(struct client *clt, c
 int
 server_file(struct httpd *env, struct client *clt)
 {
-	struct http_descriptor	*desc = clt->clt_desc;
+	struct http_descriptor	*desc = clt->clt_descreq;
 	struct server_config	*srv_conf = clt->clt_srv_conf;
-	struct media_type	*media;
-	const char		*errstr = NULL;
-	int			 fd = -1, ret, code = 500;
 	char			 path[MAXPATHLEN];
-	struct stat		 st;
+	const char		*errstr = NULL;
+	int			 ret = 500;
+
+	if (srv_conf->flags & SRVFLAG_FCGI)
+		return (server_fcgi(env, clt));
 
 	/* Request path is already canonicalized */
 	if ((size_t)snprintf(path, sizeof(path), "%s%s",
-	    srv_conf->root, desc->http_path) >= sizeof(path)) {
+	    srv_conf->root,
+	    desc->http_path_alias != NULL ?
+	    desc->http_path_alias : desc->http_path) >= sizeof(path)) {
 		errstr = desc->http_path;
 		goto abort;
 	}
 
 	/* Returns HTTP status code on error */
-	if ((ret = server_file_access(clt, path, sizeof(path), &st)) != 0) {
-		code = ret;
-		errstr = desc->http_path;
+	if ((ret = server_file_access(env, clt, path, sizeof(path))) > 0) {
+		errstr = desc->http_path_alias != NULL ?
+		    desc->http_path_alias : desc->http_path;
 		goto abort;
 	}
 
-	if (S_ISDIR(st.st_mode)) {
-		/* List directory index */
-		return (server_file_index(env, clt));
+	return (ret);
+
+ abort:
+	if (errstr == NULL)
+		errstr = strerror(errno);
+	server_abort_http(clt, ret, errstr);
+	return (-1);
+}
+
+int
+server_file_method(struct client *clt)
+{
+	struct http_descriptor	*desc = clt->clt_descreq;
+
+	switch (desc->http_method) {
+	case HTTP_METHOD_GET:
+	case HTTP_METHOD_HEAD:
+		return (0);
+	default:
+		/* Other methods are not allowed */
+		errno = EACCES;
+		return (405);
+	}
+	/* NOTREACHED */
+}
+
+int
+server_file_request(struct httpd *env, struct client *clt, char *path,
+    struct stat *st)
+{
+	struct server_config	*srv_conf = clt->clt_srv_conf;
+	struct media_type	*media;
+	const char		*errstr = NULL;
+	int			 fd = -1, ret, code = 500;
+
+	if ((ret = server_file_method(clt)) != 0) {
+		code = ret;
+		goto abort;
 	}
 
 	/* Now open the file, should be readable or we have another problem */
@@ -178,7 +220,8 @@ server_file(struct httpd *env, struct cl
 		goto abort;
 
 	media = media_find(env->sc_mediatypes, path);
-	ret = server_response_http(clt, 200, media, st.st_size);
+	ret = server_response_http(clt, 200, media, st->st_size,
+	    MIN(time(NULL), st->st_mtim.tv_sec));
 	switch (ret) {
 	case -1:
 		goto fail;
@@ -225,20 +268,25 @@ server_file(struct httpd *env, struct cl
 }
 
 int
-server_file_index(struct httpd *env, struct client *clt)
+server_file_index(struct httpd *env, struct client *clt, struct stat *st)
 {
 	char			  path[MAXPATHLEN];
 	char			  tmstr[21];
-	struct http_descriptor	 *desc = clt->clt_desc;
+	struct http_descriptor	 *desc = clt->clt_descreq;
 	struct server_config	 *srv_conf = clt->clt_srv_conf;
 	struct dirent		**namelist, *dp;
 	int			  namesize, i, ret, fd = -1, namewidth, skip;
+	int			  code = 500;
 	struct evbuffer		 *evb = NULL;
 	struct media_type	 *media;
 	const char		 *style;
-	struct stat		  st;
 	struct tm		  tm;
-	time_t			  t;
+	time_t			  t, dir_mtime;
+
+	if ((ret = server_file_method(clt)) != 0) {
+		code = ret;
+		goto abort;
+	}
 
 	/* Request path is already canonicalized */
 	if ((size_t)snprintf(path, sizeof(path), "%s%s",
@@ -249,6 +297,9 @@ server_file_index(struct httpd *env, str
 	if ((fd = open(path, O_RDONLY)) == -1)
 		goto abort;
 
+	/* Save last modification time */
+	dir_mtime = MIN(time(NULL), st->st_mtim.tv_sec);
+
 	if ((evb = evbuffer_new()) == NULL)
 		goto abort;
 
@@ -260,7 +311,7 @@ server_file_index(struct httpd *env, str
 
 	/* A CSS stylesheet allows minimal customization by the user */
 	style = "body { background-color: white; color: black; font-family: "
-	    "sans-serif; }";
+	    "sans-serif; }\nhr { border: 0; border-bottom: 1px dashed; }\n";
 	/* Generate simple HTML index document */
 	if (evbuffer_add_printf(evb,
 	    "<!DOCTYPE HTML PUBLIC "
@@ -280,12 +331,12 @@ server_file_index(struct httpd *env, str
 		dp = namelist[i];
 
 		if (skip ||
-		    fstatat(fd, dp->d_name, &st, 0) == -1) {
+		    fstatat(fd, dp->d_name, st, 0) == -1) {
 			free(dp);
 			continue;
 		}
 
-		t = st.st_mtime;
+		t = st->st_mtime;
 		localtime_r(&t, &tm);
 		strftime(tmstr, sizeof(tmstr), "%d-%h-%Y %R", &tm);
 		namewidth = 51 - strlen(dp->d_name);
@@ -293,18 +344,18 @@ server_file_index(struct httpd *env, str
 		if (dp->d_name[0] == '.' &&
 		    !(dp->d_name[1] == '.' && dp->d_name[2] == '\0')) {
 			/* ignore hidden files starting with a dot */
-		} else if (S_ISDIR(st.st_mode)) {
+		} else if (S_ISDIR(st->st_mode)) {
 			namewidth -= 1; /* trailing slash */
 			if (evbuffer_add_printf(evb,
 			    "<a href=\"%s\">%s/</a>%*s%s%20s\n",
 			    dp->d_name, dp->d_name,
 			    MAX(namewidth, 0), " ", tmstr, "-") == -1)
 				skip = 1;
-		} else if (S_ISREG(st.st_mode)) {
+		} else if (S_ISREG(st->st_mode)) {
 			if (evbuffer_add_printf(evb,
 			    "<a href=\"%s\">%s</a>%*s%s%20llu\n",
 			    dp->d_name, dp->d_name,
-			    MAX(namewidth, 0), " ", tmstr, st.st_size) == -1)
+			    MAX(namewidth, 0), " ", tmstr, st->st_size) == -1)
 				skip = 1;
 		}
 		free(dp);
@@ -320,7 +371,8 @@ server_file_index(struct httpd *env, str
 	fd = -1;
 
 	media = media_find(env->sc_mediatypes, "index.html");
-	ret = server_response_http(clt, 200, media, EVBUFFER_LENGTH(evb));
+	ret = server_response_http(clt, 200, media, EVBUFFER_LENGTH(evb),
+	    dir_mtime);
 	switch (ret) {
 	case -1:
 		goto fail;
@@ -356,7 +408,7 @@ server_file_index(struct httpd *env, str
 		close(fd);
 	if (evb != NULL)
 		evbuffer_free(evb);
-	server_abort_http(clt, 500, desc->http_path);
+	server_abort_http(clt, code, desc->http_path);
 	return (-1);
 }
 
@@ -370,8 +422,16 @@ server_file_error(struct bufferevent *be
 		server_close(clt, "buffer event timeout");
 		return;
 	}
+	if (error & EVBUFFER_ERROR) {
+		if (errno == EFBIG) {
+			bufferevent_enable(bev, EV_READ);
+			return;
+		}
+		server_close(clt, "buffer event error");
+		return;
+	}
 	if (error & (EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF)) {
-		bufferevent_disable(bev, EV_READ);
+		bufferevent_disable(bev, EV_READ|EV_WRITE);
 
 		clt->clt_done = 1;
 
@@ -396,10 +456,6 @@ server_file_error(struct bufferevent *be
 		server_close(clt, "done");
 		return;
 	}
-	if (error & EVBUFFER_ERROR && errno == EFBIG) {
-		bufferevent_enable(bev, EV_READ);
-		return;
-	}
-	server_close(clt, "buffer event error");
+	server_close(clt, "unknown event error");
 	return;
 }
Index: usr.sbin/httpd/server_http.c
===================================================================
RCS file: /cvs/src/usr.sbin/httpd/server_http.c,v
retrieving revision 1.42
diff -u -p -r1.42 server_http.c
--- usr.sbin/httpd/server_http.c	6 Aug 2014 18:21:14 -0000	1.42
+++ usr.sbin/httpd/server_http.c	18 Nov 2014 15:02:55 -0000
@@ -86,9 +86,15 @@ server_httpdesc_init(struct client *clt)
 
 	if ((desc = calloc(1, sizeof(*desc))) == NULL)
 		return (-1);
+	RB_INIT(&desc->http_headers);
+	clt->clt_descreq = desc;
 
+	if ((desc = calloc(1, sizeof(*desc))) == NULL) {
+		/* req will be cleaned up later */
+		return (-1);
+	}
 	RB_INIT(&desc->http_headers);
-	clt->clt_desc = desc;
+	clt->clt_descresp = desc;
 
 	return (0);
 }
@@ -96,10 +102,16 @@ server_httpdesc_init(struct client *clt)
 void
 server_httpdesc_free(struct http_descriptor *desc)
 {
+	if (desc == NULL)
+		return;
 	if (desc->http_path != NULL) {
 		free(desc->http_path);
 		desc->http_path = NULL;
 	}
+	if (desc->http_path_alias != NULL) {
+		free(desc->http_path_alias);
+		desc->http_path_alias = NULL;
+	}
 	if (desc->http_query != NULL) {
 		free(desc->http_query);
 		desc->http_query = NULL;
@@ -114,6 +126,8 @@ server_httpdesc_free(struct http_descrip
 	}
 	kv_purge(&desc->http_headers);
 	desc->http_lastheader = NULL;
+	desc->http_method = 0;
+	desc->http_chunked = 0;
 }
 
 void
@@ -121,7 +135,7 @@ server_read_http(struct bufferevent *bev
 {
 	struct client		*clt = arg;
 	struct server_config	*srv_conf = clt->clt_srv_conf;
-	struct http_descriptor	*desc = clt->clt_desc;
+	struct http_descriptor	*desc = clt->clt_descreq;
 	struct evbuffer		*src = EVBUFFER_INPUT(bev);
 	char			*line = NULL, *key, *value;
 	const char		*errstr;
@@ -215,18 +229,20 @@ server_read_http(struct bufferevent *bev
 				goto fail;
 			}
 			desc->http_version = strchr(desc->http_path, ' ');
-			if (desc->http_version != NULL)
-				*desc->http_version++ = '\0';
+			if (desc->http_version == NULL) {
+				free(line);
+				goto fail;
+			}
+			*desc->http_version++ = '\0';
 			desc->http_query = strchr(desc->http_path, '?');
 			if (desc->http_query != NULL)
 				*desc->http_query++ = '\0';
 
 			/*
 			 * Have to allocate the strings because they could
-			 * be changed independetly by the filters later.
+			 * be changed independently by the filters later.
 			 */
-			if (desc->http_version != NULL &&
-			    (desc->http_version +			if ((desc->http_version  			    strdup(desc->http_version)) == NULL) {
 				free(line);
 				goto fail;
@@ -300,11 +316,36 @@ server_read_http(struct bufferevent *bev
 		case HTTP_METHOD_GET:
 		case HTTP_METHOD_HEAD:
 		case HTTP_METHOD_OPTIONS:
+		/* WebDAV methods */
+		case HTTP_METHOD_COPY:
 			clt->clt_toread = 0;
 			break;
 		case HTTP_METHOD_POST:
 		case HTTP_METHOD_PUT:
 		case HTTP_METHOD_RESPONSE:
+		/* WebDAV methods */
+		case HTTP_METHOD_PROPFIND:
+		case HTTP_METHOD_PROPPATCH:
+		case HTTP_METHOD_MKCOL:
+		case HTTP_METHOD_LOCK:
+		case HTTP_METHOD_UNLOCK:
+		case HTTP_METHOD_VERSION_CONTROL:
+		case HTTP_METHOD_REPORT:
+		case HTTP_METHOD_CHECKOUT:
+		case HTTP_METHOD_CHECKIN:
+		case HTTP_METHOD_UNCHECKOUT:
+		case HTTP_METHOD_MKWORKSPACE:
+		case HTTP_METHOD_UPDATE:
+		case HTTP_METHOD_LABEL:
+		case HTTP_METHOD_MERGE:
+		case HTTP_METHOD_BASELINE_CONTROL:
+		case HTTP_METHOD_MKACTIVITY:
+		case HTTP_METHOD_ORDERPATCH:
+		case HTTP_METHOD_ACL:
+		case HTTP_METHOD_MKREDIRECTREF:
+		case HTTP_METHOD_UPDATEREDIRECTREF:
+		case HTTP_METHOD_SEARCH:
+		case HTTP_METHOD_PATCH:
 			/* HTTP request payload */
 			if (clt->clt_toread > 0)
 				bev->readcb = server_read_httpcontent;
@@ -316,10 +357,8 @@ server_read_http(struct bufferevent *bev
 			}
 			break;
 		default:
-			/* HTTP handler */
-			clt->clt_toread = TOREAD_HTTP_HEADER;
-			bev->readcb = server_read_http;
-			break;
+			server_abort_http(clt, 405, "method not allowed");
+			return;
 		}
 		if (desc->http_chunked) {
 			/* Chunked transfer encoding */
@@ -514,12 +553,10 @@ server_read_httpchunks(struct buffereven
 void
 server_reset_http(struct client *clt)
 {
-	struct http_descriptor	*desc = clt->clt_desc;
 	struct server		*srv = clt->clt_srv;
 
-	server_httpdesc_free(desc);
-	desc->http_method = 0;
-	desc->http_chunked = 0;
+	server_httpdesc_free(clt->clt_descreq);
+	server_httpdesc_free(clt->clt_descresp);
 	clt->clt_headerlen = 0;
 	clt->clt_line = 0;
 	clt->clt_done = 0;
@@ -530,16 +567,16 @@ server_reset_http(struct client *clt)
 	server_log(clt, NULL);
 }
 
-void
-server_http_date(char *tmbuf, size_t len)
+ssize_t
+server_http_time(time_t t, char *tmbuf, size_t len)
 {
-	time_t			 t;
 	struct tm		 tm;
 
 	/* New HTTP/1.1 RFC 7231 prefers IMF-fixdate from RFC 5322 */
-	time(&t);
-	gmtime_r(&t, &tm);
-	strftime(tmbuf, len, "%a, %d %h %Y %T %Z", &tm);
+	if (t == -1 || gmtime_r(&t, &tm) == NULL)
+		return (-1);
+	else
+		return (strftime(tmbuf, len, "%a, %d %h %Y %T %Z", &tm));
 }
 
 const char *
@@ -574,6 +611,55 @@ server_http_host(struct sockaddr_storage
 	return (buf);
 }
 
+char *
+server_http_parsehost(char *host, char *buf, size_t len, int *portval)
+{
+	char		*start, *end, *port;
+	const char	*errstr = NULL;
+
+	if (strlcpy(buf, host, len) >= len) {
+		log_debug("%s: host name too long", __func__);
+		return (NULL);
+	}
+
+	start = buf;
+	end = port = NULL;
+
+	if (*start == '[' && (end = strchr(start, ']')) != NULL) {
+		/* Address enclosed in [] with port, eg. [2001:db8::1]:80 */
+		start++;
+		*end++ = '\0';
+		if ((port = strchr(end, ':')) == NULL || *port == '\0')
+			port = NULL;
+		else
+			port++;
+		memmove(buf, start, strlen(start) + 1);
+	} else if ((end = strchr(start, ':')) != NULL) {
+		/* Name or address with port, eg. www.example.com:80 */
+		*end++ = '\0';
+		port = end;
+	} else {
+		/* Name or address with default port, eg. www.example.com */
+		port = NULL;
+	}
+
+	if (port != NULL) {
+		/* Save the requested port */
+		*portval = strtonum(port, 0, 0xffff, &errstr);
+		if (errstr != NULL) {
+			log_debug("%s: invalid port: %s", __func__,
+			    strerror(errno));
+			return (NULL);
+		}
+		*portval = htons(*portval);
+	} else {
+		/* Port not given, indicate the default port */
+		*portval = -1;
+	}
+
+	return (start);
+}
+
 void
 server_abort_http(struct client *clt, u_int code, const char *msg)
 {
@@ -598,13 +684,11 @@ server_abort_http(struct client *clt, u_
 	if (print_host(&srv_conf->ss, hbuf, sizeof(hbuf)) == NULL)
 		goto done;
 
-	server_http_date(tmbuf, sizeof(tmbuf));
+	if (server_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0)
+		goto done;
 
 	/* Do not send details of the Internal Server Error */
 	switch (code) {
-	case 500:
-		/* Do not send details of the Internal Server Error */
-		break;
 	case 301:
 	case 302:
 		if (asprintf(&extraheader, "Location: %s\r\n", msg) == -1) {
@@ -613,7 +697,6 @@ server_abort_http(struct client *clt, u_
 		}
 		break;
 	default:
-		text = msg;
 		break;
 	}
 
@@ -665,12 +748,17 @@ server_abort_http(struct client *clt, u_
 void
 server_close_http(struct client *clt)
 {
-	struct http_descriptor *desc	= clt->clt_desc;
+	struct http_descriptor *desc;
 
-	if (desc == NULL)
-		return;
+	desc = clt->clt_descreq;
+	server_httpdesc_free(desc);
+	free(desc);
+	clt->clt_descreq = NULL;
+
+	desc = clt->clt_descresp;
 	server_httpdesc_free(desc);
 	free(desc);
+	clt->clt_descresp = NULL;
 }
 
 int
@@ -678,13 +766,17 @@ server_response(struct httpd *httpd, str
 {
 	char			 path[MAXPATHLEN];
 	char			 hostname[MAXHOSTNAMELEN];
-	struct http_descriptor	*desc	= clt->clt_desc;
+	struct http_descriptor	*desc = clt->clt_descreq;
+	struct http_descriptor	*resp = clt->clt_descresp;
 	struct server		*srv = clt->clt_srv;
-	struct server_config	*srv_conf = &srv->srv_conf, *location;
+	struct server_config	*srv_conf = &srv->srv_conf;
 	struct kv		*kv, key, *host;
+	int			 portval = -1;
+	char			*hostval;
 
 	/* Canonicalize the request path */
 	if (desc->http_path == NULL ||
+	    url_decode(desc->http_path) == NULL ||
 	    canonicalize_path(desc->http_path, path, sizeof(path)) == NULL)
 		goto fail;
 	free(desc->http_path);
@@ -726,11 +818,16 @@ server_response(struct httpd *httpd, str
 	 * XXX the Host can also appear in the URL path.
 	 */
 	if (host != NULL) {
-		/* XXX maybe better to turn srv_hosts into a tree */
+		if ((hostval = server_http_parsehost(host->kv_value,
+		    hostname, sizeof(hostname), &portval)) == NULL)
+			goto fail;
+
 		TAILQ_FOREACH(srv_conf, &srv->srv_hosts, entry) {
 			if ((srv_conf->flags & SRVFLAG_LOCATION) == 0 &&
-			    fnmatch(srv_conf->name, host->kv_value,
-			    FNM_CASEFOLD) == 0) {
+			    fnmatch(srv_conf->name, hostname,
+			    FNM_CASEFOLD) == 0 &&
+			    (portval == -1 ||
+			    (portval != -1 && portval == srv_conf->port))) {
 				/* Replace host configuration */
 				clt->clt_srv_conf = srv_conf;
 				srv_conf = NULL;
@@ -755,31 +852,46 @@ server_response(struct httpd *httpd, str
 	if ((desc->http_host = strdup(hostname)) == NULL)
 		goto fail;
 
+	/* Now fill in the mandatory parts of the response descriptor */
+	resp->http_method = desc->http_method;
+	if ((resp->http_version = strdup(desc->http_version)) == NULL)
+		goto fail;
+
+	/* Now search for the location */
+	srv_conf = server_getlocation(clt, desc->http_path);
+
+	return (server_file(httpd, clt));
+ fail:
+	server_abort_http(clt, 400, "bad request");
+	return (-1);
+}
+
+struct server_config *
+server_getlocation(struct client *clt, const char *path)
+{
+	struct server		*srv = clt->clt_srv;
+	struct server_config	*srv_conf = clt->clt_srv_conf, *location;
+
 	/* Now search for the location */
 	TAILQ_FOREACH(location, &srv->srv_hosts, entry) {
 		if ((location->flags & SRVFLAG_LOCATION) &&
 		    location->id == srv_conf->id &&
-		    fnmatch(location->location, desc->http_path,
-		    FNM_CASEFOLD) == 0) {
+		    fnmatch(location->location, path, FNM_CASEFOLD) == 0) {
 			/* Replace host configuration */
 			clt->clt_srv_conf = srv_conf = location;
 			break;
 		}
 	}
 
-	if (srv_conf->flags & SRVFLAG_FCGI)
-		return (server_fcgi(httpd, clt));
-	return (server_file(httpd, clt));
- fail:
-	server_abort_http(clt, 400, "bad request");
-	return (-1);
+	return (srv_conf);
 }
 
 int
 server_response_http(struct client *clt, u_int code,
-    struct media_type *media, size_t size)
+    struct media_type *media, size_t size, time_t mtime)
 {
-	struct http_descriptor	*desc = clt->clt_desc;
+	struct http_descriptor	*desc = clt->clt_descreq;
+	struct http_descriptor	*resp = clt->clt_descresp;
 	const char		*error;
 	struct kv		*ct, *cl;
 	char			 tmbuf[32];
@@ -790,51 +902,54 @@ server_response_http(struct client *clt,
 	if (server_log_http(clt, code, size) == -1)
 		return (-1);
 
-	kv_purge(&desc->http_headers);
-
 	/* Add error codes */
-	if (kv_setkey(&desc->http_pathquery, "%lu", code) == -1 ||
-	    kv_set(&desc->http_pathquery, "%s", error) == -1)
+	if (kv_setkey(&resp->http_pathquery, "%lu", code) == -1 ||
+	    kv_set(&resp->http_pathquery, "%s", error) == -1)
 		return (-1);
 
 	/* Add headers */
-	if (kv_add(&desc->http_headers, "Server", HTTPD_SERVERNAME) == NULL)
+	if (kv_add(&resp->http_headers, "Server", HTTPD_SERVERNAME) == NULL)
 		return (-1);
 
 	/* Is it a persistent connection? */
 	if (clt->clt_persist) {
-		if (kv_add(&desc->http_headers,
+		if (kv_add(&resp->http_headers,
 		    "Connection", "keep-alive") == NULL)
 			return (-1);
-	} else if (kv_add(&desc->http_headers, "Connection", "close") == NULL)
+	} else if (kv_add(&resp->http_headers, "Connection", "close") == NULL)
 		return (-1);
 
 	/* Set media type */
-	if ((ct = kv_add(&desc->http_headers, "Content-Type", NULL)) == NULL ||
+	if ((ct = kv_add(&resp->http_headers, "Content-Type", NULL)) == NULL ||
 	    kv_set(ct, "%s/%s",
 	    media == NULL ? "application" : media->media_type,
 	    media == NULL ? "octet-stream" : media->media_subtype) == -1)
 		return (-1);
 
 	/* Set content length, if specified */
-	if (size && ((cl -	    kv_add(&desc->http_headers, "Content-Length", NULL)) == NULL ||
-	    kv_set(cl, "%ld", size) == -1))
+	if ((cl +	    kv_add(&resp->http_headers, "Content-Length", NULL)) == NULL ||
+	    kv_set(cl, "%ld", size) == -1)
+		return (-1);
+
+	/* Set last modification time */
+	if (server_http_time(mtime, tmbuf, sizeof(tmbuf)) <= 0 ||
+	    kv_add(&resp->http_headers, "Last-Modified", tmbuf) == NULL)
 		return (-1);
 
-	/* Date header is mandatory and should be added last */
-	server_http_date(tmbuf, sizeof(tmbuf));
-	if (kv_add(&desc->http_headers, "Date", tmbuf) == NULL)
+	/* Date header is mandatory and should be added as late as possible */
+	if (server_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0 ||
+	    kv_add(&resp->http_headers, "Date", tmbuf) == NULL)
 		return (-1);
 
 	/* Write completed header */
 	if (server_writeresponse_http(clt) == -1 ||
 	    server_bufferevent_print(clt, "\r\n") == -1 ||
-	    server_headers(clt, server_writeheader_http, NULL) == -1 ||
+	    server_headers(clt, resp, server_writeheader_http, NULL) == -1 ||
 	    server_bufferevent_print(clt, "\r\n") == -1)
 		return (-1);
 
-	if (desc->http_method == HTTP_METHOD_HEAD) {
+	if (size == 0 || resp->http_method == HTTP_METHOD_HEAD) {
 		bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE);
 		if (clt->clt_persist)
 			clt->clt_toread = TOREAD_HTTP_HEADER;
@@ -850,7 +965,7 @@ server_response_http(struct client *clt,
 int
 server_writeresponse_http(struct client *clt)
 {
-	struct http_descriptor	*desc = (struct http_descriptor *)clt->clt_desc;
+	struct http_descriptor	*desc = clt->clt_descresp;
 
 	DPRINTF("version: %s rescode: %s resmsg: %s", desc->http_version,
 	    desc->http_rescode, desc->http_resmesg);
@@ -894,11 +1009,11 @@ server_writeheader_http(struct client *c
 }
 
 int
-server_headers(struct client *clt,
+server_headers(struct client *clt, void *descp,
     int (*hdr_cb)(struct client *, struct kv *, void *), void *arg)
 {
 	struct kv		*hdr, *kv;
-	struct http_descriptor	*desc = (struct http_descriptor *)clt->clt_desc;
+	struct http_descriptor	*desc = descp;
 
 	RB_FOREACH(hdr, kvtree, &desc->http_headers) {
 		if ((hdr_cb)(clt, hdr, arg) == -1)
@@ -932,7 +1047,7 @@ server_httpmethod_byname(const char *nam
 const char *
 server_httpmethod_byid(u_int id)
 {
-	const char	*name = NULL;
+	const char	*name = "<UNKNOWN>";
 	int		 i;
 
 	for (i = 0; http_methods[i].method_name != NULL; i++) {
@@ -996,7 +1111,7 @@ server_log_http(struct client *clt, u_in
 		return (-1);
 	if ((srv_conf->flags & SRVFLAG_LOG) == 0)
 		return (0);
-	if ((desc = clt->clt_desc) == NULL)
+	if ((desc = clt->clt_descreq) == NULL)
 		return (-1);
 
 	if ((t = time(NULL)) == -1)