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)