Add httpd(8), an attempt to turn the relayd(8) codebase into a simple
authorreyk <reyk@openbsd.org>
Sat, 12 Jul 2014 23:34:54 +0000 (23:34 +0000)
committerreyk <reyk@openbsd.org>
Sat, 12 Jul 2014 23:34:54 +0000 (23:34 +0000)
web server.  It is not finished yet and I just started it today, but
the goal is to provide an HTTP server that a) provides minimal
features, b) serves static files, c) provides FastCGI support, and d)
follows common coding practices of OpenBSD.

It will neither support plugins, nor custom memory allocators, EBCDIC
support, PCRE or any other things that can be found elsewhere.
httpd(8) is not intended to provide a fully-featured replacement for
nginx(8) or the Apache, but it will provide enough functionality that
is needed in the OpenBSD base system.

ok deraadt@

14 files changed:
usr.sbin/httpd/Makefile [new file with mode: 0644]
usr.sbin/httpd/config.c [new file with mode: 0644]
usr.sbin/httpd/control.c [new file with mode: 0644]
usr.sbin/httpd/http.h [new file with mode: 0644]
usr.sbin/httpd/httpd.8 [new file with mode: 0644]
usr.sbin/httpd/httpd.c [new file with mode: 0644]
usr.sbin/httpd/httpd.conf.5 [new file with mode: 0644]
usr.sbin/httpd/httpd.h [new file with mode: 0644]
usr.sbin/httpd/log.c [new file with mode: 0644]
usr.sbin/httpd/parse.y [new file with mode: 0644]
usr.sbin/httpd/proc.c [new file with mode: 0644]
usr.sbin/httpd/server.c [new file with mode: 0644]
usr.sbin/httpd/server_file.c [new file with mode: 0644]
usr.sbin/httpd/server_http.c [new file with mode: 0644]

diff --git a/usr.sbin/httpd/Makefile b/usr.sbin/httpd/Makefile
new file mode 100644 (file)
index 0000000..a6c482f
--- /dev/null
@@ -0,0 +1,19 @@
+#      $OpenBSD: Makefile,v 1.20 2014/07/12 23:34:54 reyk Exp $
+
+PROG=          httpd
+SRCS=          parse.y
+SRCS+=         config.c control.c httpd.c log.c proc.c
+SRCS+=         server.c server_http.c server_file.c
+MAN=           httpd.8 httpd.conf.5
+
+LDADD=         -levent -lssl -lcrypto -lutil
+DPADD=         ${LIBEVENT} ${LIBSSL} ${LIBCRYPTO} ${LIBUTIL}
+#DEBUG=                -g -DDEBUG=3
+CFLAGS+=       -Wall -I${.CURDIR} -Werror
+CFLAGS+=       -Wstrict-prototypes -Wmissing-prototypes
+CFLAGS+=       -Wmissing-declarations
+CFLAGS+=       -Wshadow -Wpointer-arith
+CFLAGS+=       -Wsign-compare
+CLEANFILES+=   y.tab.h
+
+.include <bsd.prog.mk>
diff --git a/usr.sbin/httpd/config.c b/usr.sbin/httpd/config.c
new file mode 100644 (file)
index 0000000..e59f6ed
--- /dev/null
@@ -0,0 +1,228 @@
+/*     $OpenBSD: config.c,v 1.1 2014/07/12 23:34:54 reyk Exp $ */
+
+/*
+ * Copyright (c) 2011 - 2014 Reyk Floeter <reyk@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+
+#include <net/if.h>
+#include <net/pfvar.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <net/route.h>
+
+#include <ctype.h>
+#include <unistd.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <ifaddrs.h>
+
+#include <openssl/ssl.h>
+
+#include "httpd.h"
+
+int
+config_init(struct httpd *env)
+{
+       struct privsep  *ps = env->sc_ps;
+       u_int            what;
+
+       /* Global configuration */
+       if (privsep_process == PROC_PARENT) {
+               env->sc_prefork_server = SERVER_NUMPROC;
+
+               ps->ps_what[PROC_PARENT] = CONFIG_ALL;
+               ps->ps_what[PROC_SERVER] = CONFIG_SERVERS;
+       }
+
+       /* Other configuration */
+       what = ps->ps_what[privsep_process];
+
+       if (what & CONFIG_SERVERS) {
+               if ((env->sc_servers =
+                   calloc(1, sizeof(*env->sc_servers))) == NULL)
+                       return (-1);
+               TAILQ_INIT(env->sc_servers);
+       }
+
+       return (0);
+}
+
+void
+config_purge(struct httpd *env, u_int reset)
+{
+       struct privsep          *ps = env->sc_ps;
+       struct server           *srv;
+       u_int                    what;
+
+       what = ps->ps_what[privsep_process] & reset;
+
+       if (what & CONFIG_SERVERS && env->sc_servers != NULL) {
+               while ((srv = TAILQ_FIRST(env->sc_servers)) != NULL) {
+                       TAILQ_REMOVE(env->sc_servers, srv, srv_entry);
+                       free(srv);
+               }
+       }
+}
+
+int
+config_setreset(struct httpd *env, u_int reset)
+{
+       struct privsep  *ps = env->sc_ps;
+       int              id;
+
+       for (id = 0; id < PROC_MAX; id++) {
+               if ((reset & ps->ps_what[id]) == 0 ||
+                   id == privsep_process)
+                       continue;
+               proc_compose_imsg(ps, id, -1, IMSG_CTL_RESET, -1,
+                   &reset, sizeof(reset));
+       }
+
+       return (0);
+}
+
+int
+config_getreset(struct httpd *env, struct imsg *imsg)
+{
+       u_int            mode;
+
+       IMSG_SIZE_CHECK(imsg, &mode);
+       memcpy(&mode, imsg->data, sizeof(mode));
+
+       config_purge(env, mode);
+
+       return (0);
+}
+
+int
+config_getcfg(struct httpd *env, struct imsg *imsg)
+{
+       struct privsep          *ps = env->sc_ps;
+       struct ctl_flags         cf;
+       u_int                    what;
+
+       if (IMSG_DATA_SIZE(imsg) != sizeof(cf))
+               return (0); /* ignore */
+
+       /* Update runtime flags */
+       memcpy(&cf, imsg->data, sizeof(cf));
+       env->sc_opts = cf.cf_opts;
+       env->sc_flags = cf.cf_flags;
+
+       what = ps->ps_what[privsep_process];
+
+       if (privsep_process != PROC_PARENT)
+               proc_compose_imsg(env->sc_ps, PROC_PARENT, -1,
+                   IMSG_CFG_DONE, -1, NULL, 0);
+
+       return (0);
+}
+
+int
+config_setserver(struct httpd *env, struct server *srv)
+{
+       struct privsep          *ps = env->sc_ps;
+       struct server_config     s;
+       int                      id;
+       int                      fd, n, m;
+       struct iovec             iov[6];
+       size_t                   c;
+       u_int                    what;
+
+       /* opens listening sockets etc. */
+       if (server_privinit(srv) == -1)
+               return (-1);
+
+       for (id = 0; id < PROC_MAX; id++) {
+               what = ps->ps_what[id];
+
+               if ((what & CONFIG_SERVERS) == 0 || id == privsep_process)
+                       continue;
+
+               DPRINTF("%s: sending server %s to %s fd %d", __func__,
+                   srv->srv_conf.name, ps->ps_title[id], srv->srv_s);
+
+               memcpy(&s, &srv->srv_conf, sizeof(s));
+
+               c = 0;
+               iov[c].iov_base = &s;
+               iov[c++].iov_len = sizeof(s);
+
+               if (id == PROC_SERVER) {
+                       /* XXX imsg code will close the fd after 1st call */
+                       n = -1;
+                       proc_range(ps, id, &n, &m);
+                       for (n = 0; n < m; n++) {
+                               if ((fd = dup(srv->srv_s)) == -1)
+                                       return (-1);
+                               proc_composev_imsg(ps, id, n,
+                                   IMSG_CFG_SERVER, fd, iov, c);
+                       }
+               } else {
+                       proc_composev_imsg(ps, id, -1, IMSG_CFG_SERVER, -1,
+                           iov, c);
+               }
+       }
+
+       close(srv->srv_s);
+       srv->srv_s = -1;
+
+       return (0);
+}
+
+int
+config_getserver(struct httpd *env, struct imsg *imsg)
+{
+#ifdef DEBUG
+       struct privsep          *ps = env->sc_ps;
+#endif
+       struct server           *srv;
+       u_int8_t                *p = imsg->data;
+       size_t                   s;
+
+       if ((srv = calloc(1, sizeof(*srv))) == NULL) {
+               close(imsg->fd);
+               return (-1);
+       }
+
+       IMSG_SIZE_CHECK(imsg, &srv->srv_conf);
+       memcpy(&srv->srv_conf, p, sizeof(srv->srv_conf));
+       s = sizeof(srv->srv_conf);
+
+       srv->srv_s = imsg->fd;
+
+       SPLAY_INIT(&srv->srv_clients);
+       TAILQ_INSERT_TAIL(env->sc_servers, srv, srv_entry);
+
+       DPRINTF("%s: %s %d received configuration \"%s\"", __func__,
+           ps->ps_title[privsep_process], ps->ps_instance,
+           srv->srv_conf.name);
+
+       return (0);
+}
diff --git a/usr.sbin/httpd/control.c b/usr.sbin/httpd/control.c
new file mode 100644 (file)
index 0000000..3a8552a
--- /dev/null
@@ -0,0 +1,337 @@
+/*     $OpenBSD: control.c,v 1.1 2014/07/12 23:34:54 reyk Exp $        */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/queue.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <net/if.h>
+
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include <openssl/ssl.h>
+
+#include "httpd.h"
+
+#define        CONTROL_BACKLOG 5
+
+struct ctl_connlist ctl_conns;
+
+void            control_accept(int, short, void *);
+void            control_close(int, struct control_sock *);
+
+int
+control_init(struct privsep *ps, struct control_sock *cs)
+{
+       struct httpd            *env = ps->ps_env;
+       struct sockaddr_un       sun;
+       int                      fd;
+       mode_t                   old_umask, mode;
+
+       if (cs->cs_name == NULL)
+               return (0);
+
+       if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+               log_warn("%s: socket", __func__);
+               return (-1);
+       }
+
+       sun.sun_family = AF_UNIX;
+       if (strlcpy(sun.sun_path, cs->cs_name,
+           sizeof(sun.sun_path)) >= sizeof(sun.sun_path)) {
+               log_warn("%s: %s name too long", __func__, cs->cs_name);
+               close(fd);
+               return (-1);
+       }
+
+       if (unlink(cs->cs_name) == -1)
+               if (errno != ENOENT) {
+                       log_warn("%s: unlink %s", __func__, cs->cs_name);
+                       close(fd);
+                       return (-1);
+               }
+
+       if (cs->cs_restricted) {
+               old_umask = umask(S_IXUSR|S_IXGRP|S_IXOTH);
+               mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
+       } else {
+               old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
+               mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP;
+       }
+
+       if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
+               log_warn("%s: bind: %s", __func__, cs->cs_name);
+               close(fd);
+               (void)umask(old_umask);
+               return (-1);
+       }
+       (void)umask(old_umask);
+
+       if (chmod(cs->cs_name, mode) == -1) {
+               log_warn("%s: chmod", __func__);
+               close(fd);
+               (void)unlink(cs->cs_name);
+               return (-1);
+       }
+
+       socket_set_blockmode(fd, BM_NONBLOCK);
+       cs->cs_fd = fd;
+       cs->cs_env = env;
+
+       return (0);
+}
+
+int
+control_listen(struct control_sock *cs)
+{
+       if (cs->cs_name == NULL)
+               return (0);
+
+       if (listen(cs->cs_fd, CONTROL_BACKLOG) == -1) {
+               log_warn("%s: listen", __func__);
+               return (-1);
+       }
+
+       event_set(&cs->cs_ev, cs->cs_fd, EV_READ,
+           control_accept, cs);
+       event_add(&cs->cs_ev, NULL);
+       evtimer_set(&cs->cs_evt, control_accept, cs);
+
+       return (0);
+}
+
+void
+control_cleanup(struct control_sock *cs)
+{
+       if (cs->cs_name == NULL)
+               return;
+       event_del(&cs->cs_ev);
+       event_del(&cs->cs_evt);
+       (void)unlink(cs->cs_name);
+}
+
+/* ARGSUSED */
+void
+control_accept(int listenfd, short event, void *arg)
+{
+       int                      connfd;
+       socklen_t                len;
+       struct sockaddr_un       sun;
+       struct ctl_conn         *c;
+       struct control_sock     *cs = arg;
+
+       event_add(&cs->cs_ev, NULL);
+       if ((event & EV_TIMEOUT))
+               return;
+
+       len = sizeof(sun);
+       if ((connfd = accept(listenfd,
+           (struct sockaddr *)&sun, &len)) == -1) {
+               /*
+                * Pause accept if we are out of file descriptors, or
+                * libevent will haunt us here too.
+                */
+               if (errno == ENFILE || errno == EMFILE) {
+                       struct timeval evtpause = { 1, 0 };
+
+                       event_del(&cs->cs_ev);
+                       evtimer_add(&cs->cs_evt, &evtpause);
+               } else if (errno != EWOULDBLOCK && errno != EINTR &&
+                   errno != ECONNABORTED)
+                       log_warn("%s: accept", __func__);
+               return;
+       }
+
+       socket_set_blockmode(connfd, BM_NONBLOCK);
+
+       if ((c = calloc(1, sizeof(struct ctl_conn))) == NULL) {
+               close(connfd);
+               log_warn("%s: calloc", __func__);
+               return;
+       }
+
+       imsg_init(&c->iev.ibuf, connfd);
+       c->iev.handler = control_dispatch_imsg;
+       c->iev.events = EV_READ;
+       c->iev.data = cs;       /* proc.c cheats (reuses the handler) */
+       event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events,
+           c->iev.handler, cs);
+       event_add(&c->iev.ev, NULL);
+
+       TAILQ_INSERT_TAIL(&ctl_conns, c, entry);
+}
+
+struct ctl_conn *
+control_connbyfd(int fd)
+{
+       struct ctl_conn *c;
+
+       for (c = TAILQ_FIRST(&ctl_conns); c != NULL && c->iev.ibuf.fd != fd;
+           c = TAILQ_NEXT(c, entry))
+               ;       /* nothing */
+
+       return (c);
+}
+
+void
+control_close(int fd, struct control_sock *cs)
+{
+       struct ctl_conn *c;
+
+       if ((c = control_connbyfd(fd)) == NULL) {
+               log_warn("%s: fd %d not found", __func__, fd);
+               return;
+       }
+
+       msgbuf_clear(&c->iev.ibuf.w);
+       TAILQ_REMOVE(&ctl_conns, c, entry);
+
+       event_del(&c->iev.ev);
+       close(c->iev.ibuf.fd);
+
+       /* Some file descriptors are available again. */
+       if (evtimer_pending(&cs->cs_evt, NULL)) {
+               evtimer_del(&cs->cs_evt);
+               event_add(&cs->cs_ev, NULL);
+       }
+
+       free(c);
+}
+
+/* ARGSUSED */
+void
+control_dispatch_imsg(int fd, short event, void *arg)
+{
+       struct control_sock     *cs = arg;
+       struct ctl_conn         *c;
+       struct imsg              imsg;
+       int                      n;
+       int                      verbose;
+       struct httpd            *env = cs->cs_env;
+
+       if ((c = control_connbyfd(fd)) == NULL) {
+               log_warn("%s: fd %d not found", __func__, fd);
+               return;
+       }
+
+       if (event & EV_READ) {
+               if ((n = imsg_read(&c->iev.ibuf)) == -1 || n == 0) {
+                       control_close(fd, cs);
+                       return;
+               }
+       }
+
+       if (event & EV_WRITE) {
+               if (msgbuf_write(&c->iev.ibuf.w) <= 0 && errno != EAGAIN) {
+                       control_close(fd, cs);
+                       return;
+               }
+       }
+
+       for (;;) {
+               if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) {
+                       control_close(fd, cs);
+                       return;
+               }
+
+               if (n == 0)
+                       break;
+
+               if (c->waiting) {
+                       log_debug("%s: unexpected imsg %d",
+                           __func__, imsg.hdr.type);
+                       imsg_free(&imsg);
+                       control_close(fd, cs);
+                       return;
+               }
+
+               switch (imsg.hdr.type) {
+               case IMSG_CTL_SHUTDOWN:
+               case IMSG_CTL_RELOAD:
+                       proc_forward_imsg(env->sc_ps, &imsg, PROC_PARENT, -1);
+                       break;
+               case IMSG_CTL_NOTIFY:
+                       if (c->flags & CTL_CONN_NOTIFY) {
+                               log_debug("%s: "
+                                   "client requested notify more than once",
+                                   __func__);
+                               imsg_compose_event(&c->iev, IMSG_CTL_FAIL,
+                                   0, 0, -1, NULL, 0);
+                               break;
+                       }
+                       c->flags |= CTL_CONN_NOTIFY;
+                       break;
+               case IMSG_CTL_VERBOSE:
+                       IMSG_SIZE_CHECK(&imsg, &verbose);
+
+                       memcpy(&verbose, imsg.data, sizeof(verbose));
+
+                       proc_forward_imsg(env->sc_ps, &imsg, PROC_PARENT, -1);
+                       proc_forward_imsg(env->sc_ps, &imsg, PROC_SERVER, -1);
+
+                       memcpy(imsg.data, &verbose, sizeof(verbose));
+                       control_imsg_forward(&imsg);
+                       log_verbose(verbose);
+                       break;
+               default:
+                       log_debug("%s: error handling imsg %d",
+                           __func__, imsg.hdr.type);
+                       break;
+               }
+               imsg_free(&imsg);
+       }
+
+       imsg_event_add(&c->iev);
+}
+
+void
+control_imsg_forward(struct imsg *imsg)
+{
+       struct ctl_conn *c;
+
+       TAILQ_FOREACH(c, &ctl_conns, entry)
+               if (c->flags & CTL_CONN_NOTIFY)
+                       imsg_compose_event(&c->iev, imsg->hdr.type,
+                           0, imsg->hdr.pid, -1, imsg->data,
+                           imsg->hdr.len - IMSG_HEADER_SIZE);
+}
+
+void
+socket_set_blockmode(int fd, enum blockmodes bm)
+{
+       int     flags;
+
+       if ((flags = fcntl(fd, F_GETFL, 0)) == -1)
+               fatal("fcntl F_GETFL");
+
+       if (bm == BM_NONBLOCK)
+               flags |= O_NONBLOCK;
+       else
+               flags &= ~O_NONBLOCK;
+
+       if ((flags = fcntl(fd, F_SETFL, flags)) == -1)
+               fatal("fcntl F_SETFL");
+}
diff --git a/usr.sbin/httpd/http.h b/usr.sbin/httpd/http.h
new file mode 100644 (file)
index 0000000..76d98e3
--- /dev/null
@@ -0,0 +1,139 @@
+/*     $OpenBSD: http.h,v 1.1 2014/07/12 23:34:54 reyk Exp $   */
+
+/*
+ * Copyright (c) 2012 - 2014 Reyk Floeter <reyk@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _HTTPD_HTTP_H
+#define _HTTPD_HTTP_H
+
+enum httpmethod {
+       HTTP_METHOD_NONE        = 0,
+
+       /* HTTP/1.1, RFC 2616 */
+       HTTP_METHOD_GET,
+       HTTP_METHOD_HEAD,
+       HTTP_METHOD_POST,
+       HTTP_METHOD_PUT,
+       HTTP_METHOD_DELETE,
+       HTTP_METHOD_OPTIONS,
+       HTTP_METHOD_TRACE,
+       HTTP_METHOD_CONNECT,
+
+       /* WebDAV, RFC 4918 */
+       HTTP_METHOD_PROPFIND,
+       HTTP_METHOD_PROPPATCH,
+       HTTP_METHOD_MKCOL,
+       HTTP_METHOD_COPY,
+       HTTP_METHOD_MOVE,
+       HTTP_METHOD_LOCK,
+       HTTP_METHOD_UNLOCK,
+
+       /* PATCH, RFC 5789 */
+       HTTP_METHOD_PATCH,
+
+       /* Server response (internal value) */
+       HTTP_METHOD_RESPONSE
+};
+
+struct http_method {
+       enum httpmethod          method_id;
+       const char              *method_name;
+};
+#define HTTP_METHODS           {                       \
+       { HTTP_METHOD_GET,              "GET" },        \
+       { HTTP_METHOD_HEAD,             "HEAD" },       \
+       { HTTP_METHOD_POST,             "POST" },       \
+       { HTTP_METHOD_PUT,              "PUT" },        \
+       { HTTP_METHOD_DELETE,           "DELETE" },     \
+       { HTTP_METHOD_OPTIONS,          "OPTIONS" },    \
+       { HTTP_METHOD_TRACE,            "TRACE" },      \
+       { HTTP_METHOD_CONNECT,          "CONNECT" },    \
+       { HTTP_METHOD_PROPFIND,         "PROPFIND" },   \
+       { HTTP_METHOD_PROPPATCH,        "PROPPATCH" },  \
+       { HTTP_METHOD_MKCOL,            "MKCOL" },      \
+       { HTTP_METHOD_COPY,             "COPY" },       \
+       { HTTP_METHOD_MOVE,             "MOVE" },       \
+       { HTTP_METHOD_LOCK,             "LOCK" },       \
+       { HTTP_METHOD_UNLOCK,           "UNLOCK" },     \
+       { HTTP_METHOD_PATCH,            "PATCH" },      \
+       { HTTP_METHOD_NONE,             NULL }          \
+}
+
+struct http_error {
+       int                      error_code;
+       const char              *error_name;
+};
+#define HTTP_ERRORS            {                       \
+       { 100,  "Continue" },                           \
+       { 101,  "Switching Protocols" },                \
+       { 200,  "OK" },                                 \
+       { 201,  "Created" },                            \
+       { 202,  "Accepted" },                           \
+       { 203,  "Non-Authorative Information" },        \
+       { 204,  "No Content" },                         \
+       { 205,  "Reset Content" },                      \
+       { 206,  "Partial Content" },                    \
+       { 300,  "Multiple Choices" },                   \
+       { 301,  "Moved Permanently" },                  \
+       { 302,  "Moved Temporarily" },                  \
+       { 303,  "See Other" },                          \
+       { 304,  "Not Modified" },                       \
+       { 307,  "Temporary Redirect" },                 \
+       { 400,  "Bad Request" },                        \
+       { 401,  "Unauthorized" },                       \
+       { 402,  "Payment Required" },                   \
+       { 403,  "Forbidden" },                          \
+       { 404,  "Not Found" },                          \
+       { 405,  "Method Not Allowed" },                 \
+       { 406,  "Not Acceptable" },                     \
+       { 407,  "Proxy Authentication Required" },      \
+       { 408,  "Request Timeout" },                    \
+       { 409,  "Conflict" },                           \
+       { 410,  "Gone" },                               \
+       { 411,  "Length Required" },                    \
+       { 412,  "Precondition Failed" },                \
+       { 413,  "Request Entity Too Large" },           \
+       { 414,  "Request-URL Too Long" },               \
+       { 415,  "Unsupported Media Type" },             \
+       { 416,  "Requested Range Not Satisfiable" },    \
+       { 417,  "Expectation Failed" },                 \
+       { 500,  "Internal Server Error" },              \
+       { 501,  "Not Implemented" },                    \
+       { 502,  "Bad Gateway" },                        \
+       { 503,  "Service Unavailable" },                \
+       { 504,  "Gateway Timeout" },                    \
+       { 505,  "HTTP Version Not Supported" },         \
+       { 0,    NULL }                                  \
+}
+
+/* Used during runtime */
+struct http_descriptor {
+       struct kv                http_pathquery;
+#define http_path               http_pathquery.kv_key
+#define http_query              http_pathquery.kv_value
+#define http_rescode            http_pathquery.kv_key
+#define http_resmesg            http_pathquery.kv_value
+
+       char                    *http_version;
+       enum httpmethod          http_method;
+       int                      http_chunked;
+
+       /* A tree of headers and attached lists for repeated headers. */
+       struct kvtree            http_headers;
+       struct kv               *http_lastheader;
+};
+
+#endif /* _HTTPD_HTTP_H */
diff --git a/usr.sbin/httpd/httpd.8 b/usr.sbin/httpd/httpd.8
new file mode 100644 (file)
index 0000000..3c996e7
--- /dev/null
@@ -0,0 +1,55 @@
+.\"    $OpenBSD: httpd.8,v 1.36 2014/07/12 23:34:54 reyk Exp $
+.\"
+.\" Copyright (c) 2014 Reyk Floeter <reyk@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: July 12 2014 $
+.Dt HTTPD 8
+.Os
+.Sh NAME
+.Nm httpd
+.Nd HTTP daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl dnv
+.Op Fl D Ar macro Ns = Ns Ar value
+.Op Fl f Ar file
+.Sh DESCRIPTION
+.Nm
+is a simple HTTP server that serves static files.
+.El
+.Sh FILES
+.Bl -tag -width "/var/run/httpd.sockXX" -compact
+.It /etc/httpd.conf
+Default configuration file.
+.It /var/run/httpd.sock
+.Ux Ns -domain
+socket used for communication with
+.Xr httpctl 8 .
+.El
+.Sh SEE ALSO
+.Xr httpd.conf 5 ,
+.Sh HISTORY
+.Nm
+is based on
+.Xr relayd 8 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+program was written by
+.An Reyk Floeter Aq Mt reyk@openbsd.org .
+.Sh CAVEATS
+.Nm
+is not finished yet.
diff --git a/usr.sbin/httpd/httpd.c b/usr.sbin/httpd/httpd.c
new file mode 100644 (file)
index 0000000..c08db55
--- /dev/null
@@ -0,0 +1,737 @@
+/*     $OpenBSD: httpd.c,v 1.1 2014/07/12 23:34:54 reyk Exp $  */
+
+/*
+ * Copyright (c) 2014 Reyk Floeter <reyk@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/resource.h>
+#include <sys/hash.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <fnmatch.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <sha1.h>
+#include <md5.h>
+
+#include <openssl/ssl.h>
+
+#include "httpd.h"
+
+__dead void     usage(void);
+
+int             parent_configure(struct httpd *);
+void            parent_configure_done(struct httpd *);
+void            parent_reload(struct httpd *, u_int, const char *);
+void            parent_sig_handler(int, short, void *);
+void            parent_shutdown(struct httpd *);
+int             parent_dispatch_server(int, struct privsep_proc *,
+                   struct imsg *);
+
+struct httpd                   *httpd_env;
+
+static struct privsep_proc procs[] = {
+       { "server",     PROC_SERVER, parent_dispatch_server, server }
+};
+
+void
+parent_sig_handler(int sig, short event, void *arg)
+{
+       struct privsep  *ps = arg;
+       int              die = 0, status, fail, id;
+       pid_t            pid;
+       char            *cause;
+
+       switch (sig) {
+       case SIGTERM:
+       case SIGINT:
+               die = 1;
+               /* FALLTHROUGH */
+       case SIGCHLD:
+               do {
+                       pid = waitpid(WAIT_ANY, &status, WNOHANG);
+                       if (pid <= 0)
+                               continue;
+
+                       fail = 0;
+                       if (WIFSIGNALED(status)) {
+                               fail = 1;
+                               asprintf(&cause, "terminated; signal %d",
+                                   WTERMSIG(status));
+                       } else if (WIFEXITED(status)) {
+                               if (WEXITSTATUS(status) != 0) {
+                                       fail = 1;
+                                       asprintf(&cause, "exited abnormally");
+                               } else
+                                       asprintf(&cause, "exited okay");
+                       } else
+                               fatalx("unexpected cause of SIGCHLD");
+
+                       die = 1;
+
+                       for (id = 0; id < PROC_MAX; id++)
+                               if (pid == ps->ps_pid[id]) {
+                                       if (fail)
+                                               log_warnx("lost child: %s %s",
+                                                   ps->ps_title[id], cause);
+                                       break;
+                               }
+
+                       free(cause);
+               } while (pid > 0 || (pid == -1 && errno == EINTR));
+
+               if (die)
+                       parent_shutdown(ps->ps_env);
+               break;
+       case SIGHUP:
+               log_info("%s: reload requested with SIGHUP", __func__);
+
+               /*
+                * This is safe because libevent uses async signal handlers
+                * that run in the event loop and not in signal context.
+                */
+               parent_reload(ps->ps_env, CONFIG_RELOAD, NULL);
+               break;
+       case SIGPIPE:
+               /* ignore */
+               break;
+       default:
+               fatalx("unexpected signal");
+       }
+}
+
+__dead void
+usage(void)
+{
+       extern char     *__progname;
+
+       fprintf(stderr, "usage: %s [-dnv] [-D macro=value] [-f file]\n",
+           __progname);
+       exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+       int                      c;
+       int                      debug = 0, verbose = 0;
+       u_int32_t                opts = 0;
+       struct httpd            *env;
+       struct privsep          *ps;
+       const char              *conffile = CONF_FILE;
+
+       while ((c = getopt(argc, argv, "dD:nf:v")) != -1) {
+               switch (c) {
+               case 'd':
+                       debug = 2;
+                       break;
+               case 'D':
+                       if (cmdline_symset(optarg) < 0)
+                               log_warnx("could not parse macro definition %s",
+                                   optarg);
+                       break;
+               case 'n':
+                       debug = 2;
+                       opts |= HTTPD_OPT_NOACTION;
+                       break;
+               case 'f':
+                       conffile = optarg;
+                       break;
+               case 'v':
+                       verbose++;
+                       opts |= HTTPD_OPT_VERBOSE;
+                       break;
+               default:
+                       usage();
+               }
+       }
+
+       log_init(debug ? debug : 1);    /* log to stderr until daemonized */
+
+       argc -= optind;
+       if (argc > 0)
+               usage();
+
+       if ((env = calloc(1, sizeof(*env))) == NULL ||
+           (ps = calloc(1, sizeof(*ps))) == NULL)
+               exit(1);
+
+       httpd_env = env;
+       env->sc_ps = ps;
+       ps->ps_env = env;
+       TAILQ_INIT(&ps->ps_rcsocks);
+       env->sc_conffile = conffile;
+       env->sc_opts = opts;
+
+       if (parse_config(env->sc_conffile, env) == -1)
+               exit(1);
+
+       if (debug)
+               env->sc_opts |= HTTPD_OPT_LOGUPDATE;
+
+       if (geteuid())
+               errx(1, "need root privileges");
+
+       if ((ps->ps_pw =  getpwnam(HTTPD_USER)) == NULL)
+               errx(1, "unknown user %s", HTTPD_USER);
+
+       /* Configure the control socket */
+       ps->ps_csock.cs_name = HTTPD_SOCKET;
+
+       log_init(debug);
+       log_verbose(verbose);
+
+       if (!debug && daemon(1, 0) == -1)
+               err(1, "failed to daemonize");
+
+       if (env->sc_opts & HTTPD_OPT_NOACTION)
+               ps->ps_noaction = 1;
+       else
+               log_info("startup");
+
+       ps->ps_instances[PROC_SERVER] = env->sc_prefork_server;
+       ps->ps_ninstances = env->sc_prefork_server;
+
+       proc_init(ps, procs, nitems(procs));
+
+       setproctitle("parent");
+
+       event_init();
+
+       signal_set(&ps->ps_evsigint, SIGINT, parent_sig_handler, ps);
+       signal_set(&ps->ps_evsigterm, SIGTERM, parent_sig_handler, ps);
+       signal_set(&ps->ps_evsigchld, SIGCHLD, parent_sig_handler, ps);
+       signal_set(&ps->ps_evsighup, SIGHUP, parent_sig_handler, ps);
+       signal_set(&ps->ps_evsigpipe, SIGPIPE, parent_sig_handler, ps);
+
+       signal_add(&ps->ps_evsigint, NULL);
+       signal_add(&ps->ps_evsigterm, NULL);
+       signal_add(&ps->ps_evsigchld, NULL);
+       signal_add(&ps->ps_evsighup, NULL);
+       signal_add(&ps->ps_evsigpipe, NULL);
+
+       proc_listen(ps, procs, nitems(procs));
+
+       if (load_config(env->sc_conffile, env) == -1) {
+               proc_kill(env->sc_ps);
+               exit(1);
+       }
+
+       if (env->sc_opts & HTTPD_OPT_NOACTION) {
+               fprintf(stderr, "configuration OK\n");
+               proc_kill(env->sc_ps);
+               exit(0);
+       }
+
+       if (parent_configure(env) == -1)
+               fatalx("configuration failed");
+
+       event_dispatch();
+
+       parent_shutdown(env);
+       /* NOTREACHED */
+
+       return (0);
+}
+
+int
+parent_configure(struct httpd *env)
+{
+       int                      id;
+       struct ctl_flags         cf;
+       int                      ret = -1;
+       struct server           *srv;
+
+       TAILQ_FOREACH(srv, env->sc_servers, srv_entry) {
+               if (config_setserver(env, srv) == -1)
+                       fatal("create server");
+       }
+
+       /* The servers need to reload their config. */
+       env->sc_reload = env->sc_prefork_server;
+
+       for (id = 0; id < PROC_MAX; id++) {
+               if (id == privsep_process)
+                       continue;
+               cf.cf_opts = env->sc_opts;
+               cf.cf_flags = env->sc_flags;
+
+               proc_compose_imsg(env->sc_ps, id, -1, IMSG_CFG_DONE, -1,
+                   &cf, sizeof(cf));
+       }
+
+       ret = 0;
+
+       config_purge(env, CONFIG_ALL & ~CONFIG_SERVERS);
+       return (ret);
+}
+
+void
+parent_reload(struct httpd *env, u_int reset, const char *filename)
+{
+       if (env->sc_reload) {
+               log_debug("%s: already in progress: %d pending",
+                   __func__, env->sc_reload);
+               return;
+       }
+
+       /* Switch back to the default config file */
+       if (filename == NULL || *filename == '\0')
+               filename = env->sc_conffile;
+
+       log_debug("%s: level %d config file %s", __func__, reset, filename);
+
+       config_purge(env, CONFIG_ALL);
+
+       if (reset == CONFIG_RELOAD) {
+               if (load_config(filename, env) == -1) {
+                       log_debug("%s: failed to load config file %s",
+                           __func__, filename);
+               }
+
+               config_setreset(env, CONFIG_ALL);
+
+               if (parent_configure(env) == -1) {
+                       log_debug("%s: failed to commit config from %s",
+                           __func__, filename);
+               }
+       } else
+               config_setreset(env, reset);
+}
+
+void
+parent_configure_done(struct httpd *env)
+{
+       int      id;
+
+       if (env->sc_reload == 0) {
+               log_warnx("%s: configuration already finished", __func__);
+               return;
+       }
+
+       env->sc_reload--;
+       if (env->sc_reload == 0) {
+               for (id = 0; id < PROC_MAX; id++) {
+                       if (id == privsep_process)
+                               continue;
+
+                       proc_compose_imsg(env->sc_ps, id, -1, IMSG_CTL_START,
+                           -1, NULL, 0);
+               }
+       }
+}
+
+void
+parent_shutdown(struct httpd *env)
+{
+       config_purge(env, CONFIG_ALL);
+
+       proc_kill(env->sc_ps);
+       control_cleanup(&env->sc_ps->ps_csock);
+
+       free(env->sc_ps);
+       free(env);
+
+       log_info("parent terminating, pid %d", getpid());
+
+       exit(0);
+}
+
+int
+parent_dispatch_server(int fd, struct privsep_proc *p, struct imsg *imsg)
+{
+       struct httpd            *env = p->p_env;
+
+       switch (imsg->hdr.type) {
+       case IMSG_CFG_DONE:
+               parent_configure_done(env);
+               break;
+       default:
+               return (-1);
+       }
+
+       return (0);
+}
+
+/*
+ * Utility functions
+ */
+
+void
+event_again(struct event *ev, int fd, short event,
+    void (*fn)(int, short, void *),
+    struct timeval *start, struct timeval *end, void *arg)
+{
+       struct timeval tv_next, tv_now, tv;
+
+       getmonotime(&tv_now);
+       memcpy(&tv_next, end, sizeof(tv_next));
+       timersub(&tv_now, start, &tv_now);
+       timersub(&tv_next, &tv_now, &tv_next);
+
+       memset(&tv, 0, sizeof(tv));
+       if (timercmp(&tv_next, &tv, >))
+               memcpy(&tv, &tv_next, sizeof(tv));
+
+       event_del(ev);
+       event_set(ev, fd, event, fn, arg);
+       event_add(ev, &tv);
+}
+
+const char *
+canonicalize_host(const char *host, char *name, size_t len)
+{
+       struct sockaddr_in       sin4;
+       struct sockaddr_in6      sin6;
+       u_int                    i, j;
+       size_t                   plen;
+       char                     c;
+
+       if (len < 2)
+               goto fail;
+
+       /*
+        * Canonicalize an IPv4/6 address
+        */
+       if (inet_pton(AF_INET, host, &sin4) == 1)
+               return (inet_ntop(AF_INET, &sin4, name, len));
+       if (inet_pton(AF_INET6, host, &sin6) == 1)
+               return (inet_ntop(AF_INET6, &sin6, name, len));
+
+       /*
+        * Canonicalize a hostname
+        */
+
+       /* 1. remove repeated dots and convert upper case to lower case */
+       plen = strlen(host);
+       memset(name, 0, len);
+       for (i = j = 0; i < plen; i++) {
+               if (j >= (len - 1))
+                       goto fail;
+               c = tolower(host[i]);
+               if ((c == '.') && (j == 0 || name[j - 1] == '.'))
+                       continue;
+               name[j++] = c;
+       }
+
+       /* 2. remove trailing dots */
+       for (i = j; i > 0; i--) {
+               if (name[i - 1] != '.')
+                       break;
+               name[i - 1] = '\0';
+               j--;
+       }
+       if (j <= 0)
+               goto fail;
+
+       return (name);
+
+ fail:
+       errno = EINVAL;
+       return (NULL);
+}
+
+void
+socket_rlimit(int maxfd)
+{
+       struct rlimit    rl;
+
+       if (getrlimit(RLIMIT_NOFILE, &rl) == -1)
+               fatal("socket_rlimit: failed to get resource limit");
+       log_debug("%s: max open files %llu", __func__, rl.rlim_max);
+
+       /*
+        * Allow the maximum number of open file descriptors for this
+        * login class (which should be the class "daemon" by default).
+        */
+       if (maxfd == -1)
+               rl.rlim_cur = rl.rlim_max;
+       else
+               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 *
+get_string(u_int8_t *ptr, size_t len)
+{
+       size_t   i;
+       char    *str;
+
+       for (i = 0; i < len; i++)
+               if (!(isprint(ptr[i]) || isspace(ptr[i])))
+                       break;
+
+       if ((str = calloc(1, i + 1)) == NULL)
+               return (NULL);
+       memcpy(str, ptr, i);
+
+       return (str);
+}
+
+void *
+get_data(u_int8_t *ptr, size_t len)
+{
+       u_int8_t        *data;
+
+       if ((data = calloc(1, len)) == NULL)
+               return (NULL);
+       memcpy(data, ptr, len);
+
+       return (data);
+}
+
+int
+accept_reserve(int sockfd, struct sockaddr *addr, socklen_t *addrlen,
+    int reserve, volatile int *counter)
+{
+       int ret;
+       if (getdtablecount() + reserve +
+           *counter >= getdtablesize()) {
+               errno = EMFILE;
+               return (-1);
+       }
+
+       if ((ret = accept(sockfd, addr, addrlen)) > -1) {
+               (*counter)++;
+               DPRINTF("%s: inflight incremented, now %d",__func__, *counter);
+       }
+       return (ret);
+}
+
+struct kv *
+kv_add(struct kvtree *keys, char *key, char *value)
+{
+       struct kv       *kv, *oldkv;
+
+       if (key == NULL)
+               return (NULL);
+       if ((kv = calloc(1, sizeof(*kv))) == NULL)
+               return (NULL);
+       if ((kv->kv_key = strdup(key)) == NULL) {
+               free(kv);
+               return (NULL);
+       }
+       if (value != NULL &&
+           (kv->kv_value = strdup(value)) == NULL) {
+               free(kv->kv_key);
+               free(kv);
+               return (NULL);
+       }
+       TAILQ_INIT(&kv->kv_children);
+
+       if ((oldkv = RB_INSERT(kvtree, keys, kv)) != NULL) {
+               TAILQ_INSERT_TAIL(&oldkv->kv_children, kv, kv_entry);
+               kv->kv_parent = oldkv;
+       }
+
+       return (kv);
+}
+
+int
+kv_set(struct kv *kv, char *fmt, ...)
+{
+       va_list           ap;
+       char            *value = NULL;
+       struct kv       *ckv;
+
+       va_start(ap, fmt);
+       if (vasprintf(&value, fmt, ap) == -1)
+               return (-1);
+       va_end(ap);
+
+       /* Remove all children */
+       while ((ckv = TAILQ_FIRST(&kv->kv_children)) != NULL) {
+               TAILQ_REMOVE(&kv->kv_children, ckv, kv_entry);
+               kv_free(ckv);
+               free(ckv);
+       }
+
+       /* Set the new value */
+       if (kv->kv_value != NULL)
+               free(kv->kv_value);
+       kv->kv_value = value;
+
+       return (0);
+}
+
+int
+kv_setkey(struct kv *kv, char *fmt, ...)
+{
+       va_list  ap;
+       char    *key = NULL;
+
+       va_start(ap, fmt);
+       if (vasprintf(&key, fmt, ap) == -1)
+               return (-1);
+       va_end(ap);
+
+       if (kv->kv_key != NULL)
+               free(kv->kv_key);
+       kv->kv_key = key;
+
+       return (0);
+}
+
+void
+kv_delete(struct kvtree *keys, struct kv *kv)
+{
+       struct kv       *ckv;
+
+       RB_REMOVE(kvtree, keys, kv);
+
+       /* Remove all children */
+       while ((ckv = TAILQ_FIRST(&kv->kv_children)) != NULL) {
+               TAILQ_REMOVE(&kv->kv_children, ckv, kv_entry);
+               kv_free(ckv);
+               free(ckv);
+       }
+
+       kv_free(kv);
+       free(kv);
+}
+
+struct kv *
+kv_extend(struct kvtree *keys, struct kv *kv, char *value)
+{
+       char            *newvalue;
+
+       if (kv == NULL) {
+               return (NULL);
+       } else if (kv->kv_value != NULL) {
+               if (asprintf(&newvalue, "%s%s", kv->kv_value, value) == -1)
+                       return (NULL);
+
+               free(kv->kv_value);
+               kv->kv_value = newvalue;
+       } else if ((kv->kv_value = strdup(value)) == NULL)
+               return (NULL);
+
+       return (kv);
+}
+
+void
+kv_purge(struct kvtree *keys)
+{
+       struct kv       *kv;
+
+       while ((kv = RB_MIN(kvtree, keys)) != NULL)
+               kv_delete(keys, kv);
+}
+
+void
+kv_free(struct kv *kv)
+{
+       if (kv->kv_type == KEY_TYPE_NONE)
+               return;
+       if (kv->kv_key != NULL) {
+               free(kv->kv_key);
+       }
+       kv->kv_key = NULL;
+       if (kv->kv_value != NULL) {
+               free(kv->kv_value);
+       }
+       kv->kv_value = NULL;
+       memset(kv, 0, sizeof(*kv));
+}
+
+struct kv *
+kv_inherit(struct kv *dst, struct kv *src)
+{
+       memset(dst, 0, sizeof(*dst));
+       memcpy(dst, src, sizeof(*dst));
+       TAILQ_INIT(&dst->kv_children);
+
+       if (src->kv_key != NULL) {
+               if ((dst->kv_key = strdup(src->kv_key)) == NULL) {
+                       kv_free(dst);
+                       return (NULL);
+               }
+       }
+       if (src->kv_value != NULL) {
+               if ((dst->kv_value = strdup(src->kv_value)) == NULL) {
+                       kv_free(dst);
+                       return (NULL);
+               }
+       }
+
+       return (dst);
+}
+
+int
+kv_log(struct evbuffer *log, struct kv *kv)
+{
+       char    *msg;
+
+       if (log == NULL)
+               return (0);
+       if (asprintf(&msg, " [%s%s%s]",
+           kv->kv_key == NULL ? "(unknown)" : kv->kv_key,
+           kv->kv_value == NULL ? "" : ": ",
+           kv->kv_value == NULL ? "" : kv->kv_value) == -1)
+               return (-1);
+       if (evbuffer_add(log, msg, strlen(msg)) == -1) {
+               free(msg);
+               return (-1);
+       }
+       free(msg);
+
+       return (0);
+}
+
+struct kv *
+kv_find(struct kvtree *keys, struct kv *kv)
+{
+       struct kv       *match;
+       const char      *key;
+
+       if (kv->kv_flags & KV_FLAG_GLOBBING) {
+               /* Test header key using shell globbing rules */
+               key = kv->kv_key == NULL ? "" : kv->kv_key;
+               RB_FOREACH(match, kvtree, keys) {
+                       if (fnmatch(key, match->kv_key, FNM_CASEFOLD) == 0)
+                               break;
+               }
+       } else {
+               /* Fast tree-based lookup only works without globbing */
+               match = RB_FIND(kvtree, keys, kv);
+       }
+
+       return (match);
+}
+
+int
+kv_cmp(struct kv *a, struct kv *b)
+{
+       return (strcasecmp(a->kv_key, b->kv_key));
+}
+
+RB_GENERATE(kvtree, kv, kv_node, kv_cmp);
diff --git a/usr.sbin/httpd/httpd.conf.5 b/usr.sbin/httpd/httpd.conf.5
new file mode 100644 (file)
index 0000000..a2c0cc4
--- /dev/null
@@ -0,0 +1,124 @@
+.\"    $OpenBSD: httpd.conf.5,v 1.1 2014/07/12 23:34:54 reyk Exp $
+.\"
+.\" Copyright (c) 2014 Reyk Floeter <reyk@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: July 12 2014 $
+.Dt HTTPD.CONF 5
+.Os
+.Sh NAME
+.Nm httpd.conf
+.Nd HTTP daemon configuration file
+.Sh DESCRIPTION
+.Nm
+is the configuration file for the HTTP daemon,
+.Xr httpd 8 .
+.Sh SECTIONS
+.Nm
+is divided into three main sections:
+.Bl -tag -width xxxx
+.It Sy Macros
+User-defined variables may be defined and used later, simplifying the
+configuration file.
+.It Sy Global Configuration
+Global settings for
+.Xr httpd 8 .
+.It Sy Servers
+Listening HTTP web servers.
+.El
+.Pp
+Within the sections,
+a host
+.Ar address
+can be specified by IPv4 address, IPv6 address, interface name,
+interface group, or DNS hostname.
+If the address is an interface name,
+.Xr httpd 8
+will look up the first IPv4 address and any other IPv4 and IPv6
+addresses of the specified network interface.
+A
+.Ar port
+can be specified by number or name.
+The port name to number mappings are found in the file
+.Pa /etc/services ;
+see
+.Xr services 5
+for details.
+.Pp
+The current line can be extended over multiple lines using a backslash
+.Pq Sq \e .
+Comments can be put anywhere in the file using a hash mark
+.Pq Sq # ,
+and extend to the end of the current line.
+Care should be taken when commenting out multi-line text:
+the comment is effective until the end of the entire block.
+.Pp
+Argument names not beginning with a letter, digit, or underscore
+must be quoted.
+.Pp
+Additional configuration files can be included with the
+.Ic include
+keyword, for example:
+.Bd -literal -offset indent
+include "/etc/httpd.conf.local"
+.Ed
+.Sh MACROS
+Macros can be defined that will later be expanded in context.
+Macro names must start with a letter, digit, or underscore,
+and may contain any of those characters.
+Macro names may not be reserved words (for example,
+.Ic table ,
+.Ic relay ,
+or
+.Ic timeout ) .
+Macros are not expanded inside quotes.
+.Pp
+For example:
+.Bd -literal -offset indent
+ext_ip="10.0.0.1"
+server \*(Ltwww\*(Gt {
+       listen on $ext_ip port 80
+}
+.Ed
+.Sh GLOBAL CONFIGURATION
+Here are the settings that can be set globally:
+.Bl -tag -width Ds
+.It Xo
+.Ic log
+.Pq Ic updates Ns | Ns Ic all
+.Xc
+Set logging verbosity.
+.It Ic prefork Ar number
+Run the specified number of server processes.
+This increases the performance and prevents delays when connecting
+to a server.
+.Xr httpd 8
+runs 3 server processes by default.
+.El
+.Sh SERVERS
+The configured web servers.
+.Pp
+The following general table options are available:
+.Bl -tag -width Ds
+.It Ic listen on Ar address Ic port Ar number
+Set the listen address and port.
+.El
+.Sh SEE ALSO
+.Xr httpd 8 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Xr httpd 8
+program was written by
+.An Reyk Floeter Aq Mt reyk@openbsd.org .
diff --git a/usr.sbin/httpd/httpd.h b/usr.sbin/httpd/httpd.h
new file mode 100644 (file)
index 0000000..d4fd4ca
--- /dev/null
@@ -0,0 +1,463 @@
+/*     $OpenBSD: httpd.h,v 1.1 2014/07/12 23:34:54 reyk Exp $  */
+
+/*
+ * Copyright (c) 2006 - 2014 Reyk Floeter <reyk@openbsd.org>
+ * Copyright (c) 2006, 2007 Pierre-Yves Ritschard <pyr@openbsd.org>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _HTTPD_H
+#define _HTTPD_H
+
+#include <sys/tree.h>
+
+#include <sys/param.h>         /* MAXHOSTNAMELEN */
+#include <limits.h>
+#include <imsg.h>
+
+#define CONF_FILE              "/etc/httpd.conf"
+#define HTTPD_SOCKET           "/var/run/httpd.sock"
+#define HTTPD_USER             "www"
+#define HTTPD_SERVERNAME       "OpenBSD httpd"
+#define FD_RESERVE             5
+
+#define SERVER_MAX_CLIENTS     1024
+#define SERVER_TIMEOUT         600
+#define SERVER_CACHESIZE       -1      /* use default size */
+#define SERVER_NUMPROC         3
+#define SERVER_MAXPROC         32
+#define SERVER_MAXHEADERLENGTH 8192
+#define SERVER_BACKLOG         10
+#define SERVER_OUTOF_FD_RETRIES        5
+
+#define CONFIG_RELOAD          0x00
+#define CONFIG_SERVERS         0x01
+#define CONFIG_ALL             0xff
+
+#define TCPFLAG_NODELAY                0x01
+#define TCPFLAG_NNODELAY       0x02
+#define TCPFLAG_SACK           0x04
+#define TCPFLAG_NSACK          0x08
+#define TCPFLAG_BUFSIZ         0x10
+#define TCPFLAG_IPTTL          0x20
+#define TCPFLAG_IPMINTTL       0x40
+#define TCPFLAG_NSPLICE                0x80
+#define TCPFLAG_DEFAULT                0x00
+
+#define TCPFLAG_BITS                                           \
+       "\10\01NODELAY\02NO_NODELAY\03SACK\04NO_SACK"           \
+       "\05SOCKET_BUFFER_SIZE\06IP_TTL\07IP_MINTTL\10NO_SPLICE"
+
+enum httpchunk {
+       TOREAD_UNLIMITED                = -1,
+       TOREAD_HTTP_HEADER              = -2,
+       TOREAD_HTTP_CHUNK_LENGTH        = -3,
+       TOREAD_HTTP_CHUNK_TRAILER       = -4
+};
+
+#if DEBUG > 1
+#define DPRINTF                log_debug
+#else
+#define DPRINTF(x...)  do {} while(0)
+#endif
+
+struct ctl_flags {
+       u_int8_t         cf_opts;
+       u_int32_t        cf_flags;
+};
+
+enum key_type {
+       KEY_TYPE_NONE           = 0,
+       KEY_TYPE_COOKIE,
+       KEY_TYPE_HEADER,
+       KEY_TYPE_PATH,
+       KEY_TYPE_QUERY,
+       KEY_TYPE_URL,
+       KEY_TYPE_MAX
+};
+
+TAILQ_HEAD(kvlist, kv);
+RB_HEAD(kvtree, kv);
+
+struct kv {
+       char                    *kv_key;
+       char                    *kv_value;
+
+       enum key_type            kv_type;
+
+#define KV_FLAG_INVALID                 0x01
+#define KV_FLAG_GLOBBING        0x02
+       u_int8_t                 kv_flags;
+
+       struct kvlist            kv_children;
+       struct kv               *kv_parent;
+       TAILQ_ENTRY(kv)          kv_entry;
+
+       RB_ENTRY(kv)             kv_node;
+};
+
+struct portrange {
+       in_port_t                val[2];
+       u_int8_t                 op;
+};
+
+struct address {
+       struct sockaddr_storage  ss;
+       int                      ipproto;
+       struct portrange         port;
+       char                     ifname[IFNAMSIZ];
+       TAILQ_ENTRY(address)     entry;
+};
+TAILQ_HEAD(addresslist, address);
+
+/* initially control.h */
+struct control_sock {
+       const char      *cs_name;
+       struct event     cs_ev;
+       struct event     cs_evt;
+       int              cs_fd;
+       int              cs_restricted;
+       void            *cs_env;
+
+       TAILQ_ENTRY(control_sock) cs_entry;
+};
+TAILQ_HEAD(control_socks, control_sock);
+
+struct {
+       struct event     ev;
+       int              fd;
+} control_state;
+
+enum blockmodes {
+       BM_NORMAL,
+       BM_NONBLOCK
+};
+
+struct imsgev {
+       struct imsgbuf           ibuf;
+       void                    (*handler)(int, short, void *);
+       struct event             ev;
+       struct privsep_proc     *proc;
+       void                    *data;
+       short                    events;
+};
+
+#define IMSG_SIZE_CHECK(imsg, p) do {                          \
+       if (IMSG_DATA_SIZE(imsg) < sizeof(*p))                  \
+               fatalx("bad length imsg received");             \
+} while (0)
+#define IMSG_DATA_SIZE(imsg)   ((imsg)->hdr.len - IMSG_HEADER_SIZE)
+
+struct ctl_conn {
+       TAILQ_ENTRY(ctl_conn)    entry;
+       u_int8_t                 flags;
+       u_int                    waiting;
+#define CTL_CONN_NOTIFY                 0x01
+       struct imsgev            iev;
+
+};
+TAILQ_HEAD(ctl_connlist, ctl_conn);
+
+enum imsg_type {
+       IMSG_NONE,
+       IMSG_CTL_OK,
+       IMSG_CTL_FAIL,
+       IMSG_CTL_VERBOSE,
+       IMSG_CTL_RESET,
+       IMSG_CTL_SHUTDOWN,
+       IMSG_CTL_RELOAD,
+       IMSG_CTL_NOTIFY,
+       IMSG_CTL_END,
+       IMSG_CTL_START,
+       IMSG_CFG_SERVER,
+       IMSG_CFG_DONE
+};
+
+enum privsep_procid {
+       PROC_ALL        = -1,
+       PROC_PARENT     = 0,
+       PROC_SERVER,
+       PROC_MAX
+} privsep_process;
+
+/* Attach the control socket to the following process */
+#define PROC_CONTROL   PROC_PARENT
+
+struct privsep_pipes {
+       int                             *pp_pipes[PROC_MAX];
+};
+
+struct privsep {
+       struct privsep_pipes            *ps_pipes[PROC_MAX];
+       struct privsep_pipes            *ps_pp;
+
+       struct imsgev                   *ps_ievs[PROC_MAX];
+       const char                      *ps_title[PROC_MAX];
+       pid_t                            ps_pid[PROC_MAX];
+       u_int8_t                         ps_what[PROC_MAX];
+
+       u_int                            ps_instances[PROC_MAX];
+       u_int                            ps_ninstances;
+       u_int                            ps_instance;
+
+       struct control_sock              ps_csock;
+       struct control_socks             ps_rcsocks;
+
+       /* Event and signal handlers */
+       struct event                     ps_evsigint;
+       struct event                     ps_evsigterm;
+       struct event                     ps_evsigchld;
+       struct event                     ps_evsighup;
+       struct event                     ps_evsigpipe;
+
+       int                              ps_noaction;
+       struct passwd                   *ps_pw;
+       struct httpd                    *ps_env;
+};
+
+struct privsep_proc {
+       const char              *p_title;
+       enum privsep_procid      p_id;
+       int                     (*p_cb)(int, struct privsep_proc *,
+                                   struct imsg *);
+       pid_t                   (*p_init)(struct privsep *,
+                                   struct privsep_proc *);
+       void                    (*p_shutdown)(void);
+       u_int                    p_instance;
+       const char              *p_chroot;
+       struct privsep          *p_ps;
+       struct httpd            *p_env;
+};
+
+struct client {
+       u_int32_t                clt_id;
+       pid_t                    clt_pid;
+       void                    *clt_server;
+       u_int32_t                clt_serverid;
+
+       int                      clt_s;
+       in_port_t                clt_port;
+       struct sockaddr_storage  clt_ss;
+       struct bufferevent      *clt_bev;
+       struct evbuffer         *clt_output;
+       struct event             clt_ev;
+       void                    *clt_desc;
+
+       int                      clt_fd;
+       struct bufferevent      *clt_file;
+
+       off_t                    clt_toread;
+       int                      clt_line;
+       size_t                   clt_headerlen;
+       int                      clt_done;
+
+       struct evbuffer         *clt_log;
+       struct timeval           clt_timeout;
+       struct timeval           clt_tv_start;
+       struct timeval           clt_tv_last;
+       struct event             clt_inflightevt;
+
+       SPLAY_ENTRY(client)      clt_nodes;
+};
+SPLAY_HEAD(client_tree, client);
+
+struct server_config {
+       u_int32_t                id;
+       u_int32_t                flags;
+       char                     name[MAXHOSTNAMELEN];
+       in_port_t                port;
+       struct sockaddr_storage  ss;
+       struct timeval           timeout;
+};
+
+struct server {
+       TAILQ_ENTRY(server)      srv_entry;
+       struct server_config     srv_conf;
+
+       u_int8_t                 srv_tcpflags;
+       int                      srv_tcpbufsiz;
+       int                      srv_tcpbacklog;
+       u_int8_t                 srv_tcpipttl;
+       u_int8_t                 srv_tcpipminttl;
+
+       int                      srv_s;
+       struct bufferevent      *srv_bev;
+       int                      srv_dsts;
+       struct bufferevent      *srv_dstbev;
+
+       struct event             srv_ev;
+       struct event             srv_evt;
+
+       struct client_tree       srv_clients;
+};
+TAILQ_HEAD(serverlist, server);
+
+struct httpd {
+       u_int8_t                 sc_opts;
+       u_int32_t                sc_flags;
+       const char              *sc_conffile;
+       struct event             sc_ev;
+       u_int16_t                sc_prefork_server;
+       u_int16_t                sc_id;
+
+       struct serverlist       *sc_servers;
+
+       struct privsep          *sc_ps;
+       int                      sc_reload;
+};
+
+#define HTTPD_OPT_VERBOSE              0x01
+#define HTTPD_OPT_NOACTION             0x04
+#define HTTPD_OPT_LOGUPDATE            0x08
+#define HTTPD_OPT_LOGNOTIFY            0x10
+#define HTTPD_OPT_LOGALL               0x18
+
+/* control.c */
+int     control_init(struct privsep *, struct control_sock *);
+int     control_listen(struct control_sock *);
+void    control_cleanup(struct control_sock *);
+void    control_dispatch_imsg(int, short, void *);
+void    control_imsg_forward(struct imsg *);
+struct ctl_conn        *
+        control_connbyfd(int);
+void    socket_set_blockmode(int, enum blockmodes);
+
+extern  struct ctl_connlist ctl_conns;
+
+/* parse.y */
+int     parse_config(const char *, struct httpd *);
+int     load_config(const char *, struct httpd *);
+int     cmdline_symset(char *);
+
+/* server.c */
+pid_t   server(struct privsep *, struct privsep_proc *);
+int     server_privinit(struct server *);
+int     server_socket_af(struct sockaddr_storage *, in_port_t);
+in_port_t
+        server_socket_getport(struct sockaddr_storage *);
+void    server_write(struct bufferevent *, void *);
+void    server_read(struct bufferevent *, void *);
+void    server_error(struct bufferevent *, short, void *);
+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_print(struct client *, const char *);
+int     server_bufferevent_write_buffer(struct client *,
+           struct evbuffer *);
+int     server_bufferevent_write_chunk(struct client *,
+           struct evbuffer *, size_t);
+int     server_bufferevent_add(struct event *, int);
+int     server_bufferevent_write(struct client *, void *, size_t);
+
+SPLAY_PROTOTYPE(client_tree, client, clt_nodes, server_client_cmp);
+
+/* server_http.c */
+void    server_http_init(struct server *);
+void    server_http(struct httpd *);
+int     server_httpdesc_init(struct client *);
+void    server_read_http(struct bufferevent *, void *);
+void    server_abort_http(struct client *, u_int, const char *);
+u_int   server_httpmethod_byname(const char *);
+const char
+       *server_httpmethod_byid(u_int);
+const char
+       *server_httperror_byid(u_int);
+void    server_read_httpcontent(struct bufferevent *, void *);
+void    server_read_httpchunks(struct bufferevent *, void *);
+int     server_writeheader_kv(struct client *, struct kv *);
+int     server_writeheader_http(struct client *);
+int     server_writeresponse_http(struct client *);
+void    server_reset_http(struct client *);
+void    server_close_http(struct client *);
+
+/* server_file.c */
+int     server_response(struct client *);
+
+/* httpd.c */
+void            event_again(struct event *, int, short,
+                   void (*)(int, short, void *),
+                   struct timeval *, struct timeval *, void *);
+const char     *canonicalize_host(const char *, char *, size_t);
+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           *get_string(u_int8_t *, size_t);
+void           *get_data(u_int8_t *, size_t);
+int             accept_reserve(int, struct sockaddr *, socklen_t *, int,
+                    volatile int *);
+struct kv      *kv_add(struct kvtree *, char *, char *);
+int             kv_set(struct kv *, char *, ...);
+int             kv_setkey(struct kv *, char *, ...);
+void            kv_delete(struct kvtree *, struct kv *);
+struct kv      *kv_extend(struct kvtree *, struct kv *, char *);
+void            kv_purge(struct kvtree *);
+void            kv_free(struct kv *);
+struct kv      *kv_inherit(struct kv *, struct kv *);
+int             kv_log(struct evbuffer *, struct kv *);
+struct kv      *kv_find(struct kvtree *, struct kv *);
+int             kv_cmp(struct kv *, struct kv *);
+RB_PROTOTYPE(kvtree, kv, kv_node, kv_cmp);
+
+/* log.c */
+void   log_init(int);
+void   log_verbose(int);
+void   log_warn(const char *, ...) __attribute__((__format__ (printf, 1, 2)));
+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   vlog(int, const char *, va_list) __attribute__((__format__ (printf, 2, 0)));
+__dead void fatal(const char *);
+__dead void fatalx(const char *);
+const char *print_host(struct sockaddr_storage *, char *, size_t);
+const char *print_time(struct timeval *, struct timeval *, char *, size_t);
+const char *printb_flags(const u_int32_t, const char *);
+void    getmonotime(struct timeval *);
+
+/* proc.c */
+void    proc_init(struct privsep *, struct privsep_proc *, u_int);
+void    proc_kill(struct privsep *);
+void    proc_listen(struct privsep *, struct privsep_proc *, size_t);
+void    proc_dispatch(int, short event, void *);
+pid_t   proc_run(struct privsep *, struct privsep_proc *,
+           struct privsep_proc *, u_int,
+           void (*)(struct privsep *, struct privsep_proc *, void *), void *);
+void    proc_range(struct privsep *, enum privsep_procid, int *, int *);
+int     proc_compose_imsg(struct privsep *, enum privsep_procid, int,
+           u_int16_t, int, void *, u_int16_t);
+int     proc_composev_imsg(struct privsep *, enum privsep_procid, int,
+           u_int16_t, int, const struct iovec *, int);
+int     proc_forward_imsg(struct privsep *, struct imsg *,
+           enum privsep_procid, int);
+struct imsgbuf *
+        proc_ibuf(struct privsep *, enum privsep_procid, int);
+struct imsgev *
+        proc_iev(struct privsep *, enum privsep_procid, int);
+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);
+int     imsg_composev_event(struct imsgev *, u_int16_t, u_int32_t,
+           pid_t, int, const struct iovec *, int);
+
+/* config.c */
+int     config_init(struct httpd *);
+void    config_purge(struct httpd *, u_int);
+int     config_setreset(struct httpd *, u_int);
+int     config_getreset(struct httpd *, struct imsg *);
+int     config_getcfg(struct httpd *, struct imsg *);
+int     config_setserver(struct httpd *, struct server *);
+int     config_getserver(struct httpd *, struct imsg *);
+
+#endif /* _HTTPD_H */
diff --git a/usr.sbin/httpd/log.c b/usr.sbin/httpd/log.c
new file mode 100644 (file)
index 0000000..059cac6
--- /dev/null
@@ -0,0 +1,251 @@
+/*     $OpenBSD: log.c,v 1.1 2014/07/12 23:34:54 reyk Exp $    */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/tree.h>
+
+#include <net/if.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <event.h>
+#include <netdb.h>
+#include <ctype.h>
+
+#include <openssl/ssl.h>
+
+#include "httpd.h"
+
+int     debug;
+int     verbose;
+
+void    vlog(int, const char *, va_list)
+           __attribute__((__format__ (printf, 2, 0)));
+void    logit(int, const char *, ...)
+           __attribute__((__format__ (printf, 2, 3)));
+
+void
+log_init(int n_debug)
+{
+       extern char     *__progname;
+
+       debug = n_debug;
+       verbose = n_debug;
+
+       if (!debug)
+               openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON);
+
+       tzset();
+}
+
+void
+log_verbose(int v)
+{
+       verbose = v;
+}
+
+void
+logit(int pri, const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       vlog(pri, fmt, ap);
+       va_end(ap);
+}
+
+void
+vlog(int pri, const char *fmt, va_list ap)
+{
+       char    *nfmt;
+
+       if (debug) {
+               /* best effort in out of mem situations */
+               if (asprintf(&nfmt, "%s\n", fmt) == -1) {
+                       vfprintf(stderr, fmt, ap);
+                       fprintf(stderr, "\n");
+               } else {
+                       vfprintf(stderr, nfmt, ap);
+                       free(nfmt);
+               }
+               fflush(stderr);
+       } else
+               vsyslog(pri, fmt, ap);
+}
+
+
+void
+log_warn(const char *emsg, ...)
+{
+       char    *nfmt;
+       va_list  ap;
+
+       /* best effort to even work in out of memory situations */
+       if (emsg == NULL)
+               logit(LOG_CRIT, "%s", strerror(errno));
+       else {
+               va_start(ap, emsg);
+
+               if (asprintf(&nfmt, "%s: %s", emsg, strerror(errno)) == -1) {
+                       /* we tried it... */
+                       vlog(LOG_CRIT, emsg, ap);
+                       logit(LOG_CRIT, "%s", strerror(errno));
+               } else {
+                       vlog(LOG_CRIT, nfmt, ap);
+                       free(nfmt);
+               }
+               va_end(ap);
+       }
+}
+
+void
+log_warnx(const char *emsg, ...)
+{
+       va_list  ap;
+
+       va_start(ap, emsg);
+       vlog(LOG_CRIT, emsg, ap);
+       va_end(ap);
+}
+
+void
+log_info(const char *emsg, ...)
+{
+       va_list  ap;
+
+       va_start(ap, emsg);
+       vlog(LOG_INFO, emsg, ap);
+       va_end(ap);
+}
+
+void
+log_debug(const char *emsg, ...)
+{
+       va_list  ap;
+
+       if (verbose > 1) {
+               va_start(ap, emsg);
+               vlog(LOG_DEBUG, emsg, ap);
+               va_end(ap);
+       }
+}
+
+void
+fatal(const char *emsg)
+{
+       if (emsg == NULL)
+               logit(LOG_CRIT, "fatal: %s", strerror(errno));
+       else
+               if (errno)
+                       logit(LOG_CRIT, "fatal: %s: %s",
+                           emsg, strerror(errno));
+               else
+                       logit(LOG_CRIT, "fatal: %s", emsg);
+
+       exit(1);
+}
+
+void
+fatalx(const char *emsg)
+{
+       errno = 0;
+       fatal(emsg);
+}
+
+const char *
+print_host(struct sockaddr_storage *ss, char *buf, size_t len)
+{
+       if (getnameinfo((struct sockaddr *)ss, ss->ss_len,
+           buf, len, NULL, 0, NI_NUMERICHOST) != 0) {
+               buf[0] = '\0';
+               return (NULL);
+       }
+       return (buf);
+}
+
+const char *
+print_time(struct timeval *a, struct timeval *b, char *buf, size_t len)
+{
+       struct timeval          tv;
+       u_long                  h, sec, min;
+
+       timerclear(&tv);
+       timersub(a, b, &tv);
+       sec = tv.tv_sec % 60;
+       min = tv.tv_sec / 60 % 60;
+       h = tv.tv_sec / 60 / 60;
+
+       snprintf(buf, len, "%.2lu:%.2lu:%.2lu", h, min, sec);
+       return (buf);
+}
+
+const char *
+printb_flags(const u_int32_t v, const char *bits)
+{
+       static char      buf[2][BUFSIZ];
+       static int       idx = 0;
+       int              i, any = 0;
+       char             c, *p, *r;
+
+       p = r = buf[++idx % 2];
+       memset(p, 0, BUFSIZ);
+
+       if (bits) {
+               bits++;
+               while ((i = *bits++)) {
+                       if (v & (1 << (i - 1))) {
+                               if (any) {
+                                       *p++ = ',';
+                                       *p++ = ' ';
+                               }
+                               any = 1;
+                               for (; (c = *bits) > 32; bits++) {
+                                       if (c == '_')
+                                               *p++ = ' ';
+                                       else
+                                               *p++ = tolower((u_char)c);
+                               }
+                       } else
+                               for (; *bits > 32; bits++)
+                                       ;
+               }
+       }
+
+       return (r);
+}
+
+void
+getmonotime(struct timeval *tv)
+{
+       struct timespec  ts;
+
+       if (clock_gettime(CLOCK_MONOTONIC, &ts))
+               fatal("clock_gettime");
+
+       TIMESPEC_TO_TIMEVAL(tv, &ts);
+}
diff --git a/usr.sbin/httpd/parse.y b/usr.sbin/httpd/parse.y
new file mode 100644 (file)
index 0000000..b7e5b25
--- /dev/null
@@ -0,0 +1,1144 @@
+/*     $OpenBSD: parse.y,v 1.1 2014/07/12 23:34:54 reyk Exp $  */
+
+/*
+ * Copyright (c) 2007 - 2014 Reyk Floeter <reyk@openbsd.org>
+ * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org>
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org>
+ * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
+ * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
+ * Copyright (c) 2001 Markus Friedl.  All rights reserved.
+ * Copyright (c) 2001 Daniel Hartmeier.  All rights reserved.
+ * Copyright (c) 2001 Theo de Raadt.  All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+%{
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/queue.h>
+#include <sys/ioctl.h>
+#include <sys/hash.h>
+
+#include <net/if.h>
+#include <net/pfvar.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <net/route.h>
+
+#include <ctype.h>
+#include <unistd.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <ifaddrs.h>
+#include <syslog.h>
+
+#include <openssl/ssl.h>
+
+#include "httpd.h"
+#include "http.h"
+
+TAILQ_HEAD(files, file)                 files = TAILQ_HEAD_INITIALIZER(files);
+static struct file {
+       TAILQ_ENTRY(file)        entry;
+       FILE                    *stream;
+       char                    *name;
+       int                      lineno;
+       int                      errors;
+} *file, *topfile;
+struct file    *pushfile(const char *, int);
+int             popfile(void);
+int             check_file_secrecy(int, const char *);
+int             yyparse(void);
+int             yylex(void);
+int             yyerror(const char *, ...);
+int             kw_cmp(const void *, const void *);
+int             lookup(char *);
+int             lgetc(int);
+int             lungetc(int);
+int             findeol(void);
+
+TAILQ_HEAD(symhead, sym)        symhead = TAILQ_HEAD_INITIALIZER(symhead);
+struct sym {
+       TAILQ_ENTRY(sym)         entry;
+       int                      used;
+       int                      persist;
+       char                    *nam;
+       char                    *val;
+};
+int             symset(const char *, const char *, int);
+char           *symget(const char *);
+
+struct httpd           *conf = NULL;
+static int              errors = 0;
+static int              loadcfg = 0;
+uint32_t                last_server_id = 0;
+
+static struct server   *srv = NULL;
+struct serverlist       servers;
+
+struct address *host_v4(const char *);
+struct address *host_v6(const char *);
+int             host_dns(const char *, struct addresslist *,
+                   int, struct portrange *, const char *, int);
+int             host_if(const char *, struct addresslist *,
+                   int, struct portrange *, const char *, int);
+int             host(const char *, struct addresslist *,
+                   int, struct portrange *, const char *, int);
+void            host_free(struct addresslist *);
+int             getservice(char *);
+int             is_if_in_group(const char *, const char *);
+
+typedef struct {
+       union {
+               int64_t                  number;
+               char                    *string;
+               struct timeval           tv;
+               struct portrange         port;
+               struct {
+                       struct sockaddr_storage  ss;
+                       char                     name[MAXHOSTNAMELEN];
+               }                        addr;
+       } v;
+       int lineno;
+} YYSTYPE;
+
+%}
+
+%token ALL PORT LISTEN PREFORK SERVER ERROR INCLUDE LOG VERBOSE ON
+%token UPDATES INCLUDE
+%token <v.string>      STRING
+%token  <v.number>     NUMBER
+%type  <v.number>      loglevel
+%type  <v.port>        port
+
+%%
+
+grammar                : /* empty */
+               | grammar include '\n'
+               | grammar '\n'
+               | grammar varset '\n'
+               | grammar main '\n'
+               | grammar server '\n'
+               | grammar error '\n'            { file->errors++; }
+               ;
+
+include                : INCLUDE STRING                {
+                       struct file     *nfile;
+
+                       if ((nfile = pushfile($2, 0)) == NULL) {
+                               yyerror("failed to include file %s", $2);
+                               free($2);
+                               YYERROR;
+                       }
+                       free($2);
+
+                       file = nfile;
+                       lungetc('\n');
+               }
+               ;
+
+varset         : STRING '=' STRING     {
+                       if (symset($1, $3, 0) == -1)
+                               fatal("cannot store variable");
+                       free($1);
+                       free($3);
+               }
+               ;
+
+main           : LOG loglevel          {
+                       if (loadcfg)
+                               break;
+                       conf->sc_opts |= $2;
+               }
+               | PREFORK NUMBER        {
+                       if (loadcfg)
+                               break;
+                       if ($2 <= 0 || $2 > SERVER_MAXPROC) {
+                               yyerror("invalid number of preforked "
+                                   "servers: %d", $2);
+                               YYERROR;
+                       }
+                       conf->sc_prefork_server = $2;
+               }
+               ;
+
+server         : SERVER STRING         {
+                       struct server   *s;
+
+                       if (!loadcfg) {
+                               free($2);
+                               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");
+
+                       if (strlcpy(s->srv_conf.name, $2,
+                           sizeof(s->srv_conf.name)) >=
+                           sizeof(s->srv_conf.name)) {
+                               yyerror("server name truncated");
+                               free($2);
+                               free(s);
+                               YYERROR;
+                       }
+                       free($2);
+
+                       s->srv_conf.id = ++last_server_id;
+                       s->srv_conf.timeout.tv_sec = SERVER_TIMEOUT;
+
+                       if (last_server_id == INT_MAX) {
+                               yyerror("too many servers defined");
+                               free(s);
+                               YYERROR;
+                       }
+                       srv = s;
+               } '{' optnl serveropts_l '}'    {
+                       SPLAY_INIT(&srv->srv_clients);
+                       TAILQ_INSERT_TAIL(conf->sc_servers, srv, srv_entry);
+               }
+               ;
+
+serveropts_l   : serveropts_l serveroptsl nl
+               | serveroptsl optnl
+               ;
+
+serveroptsl    : LISTEN ON STRING port {
+                       struct addresslist       al;
+                       struct address          *h;
+                       struct server           *s;
+
+                       if (srv->srv_conf.ss.ss_family != AF_UNSPEC) {
+                               yyerror("listen address already specified");
+                               free($3);
+                               YYERROR;
+                       } else
+                               s = srv;
+                       if ($4.op != PF_OP_EQ) {
+                               yyerror("invalid port");
+                               free($3);
+                               YYERROR;
+                       }
+
+                       TAILQ_INIT(&al);
+                       if (host($3, &al, 1, &$4, NULL, -1) <= 0) {
+                               yyerror("invalid listen ip: %s", $3);
+                               free($3);
+                               YYERROR;
+                       }
+                       free($3);
+                       h = TAILQ_FIRST(&al);
+                       memcpy(&srv->srv_conf.ss, &h->ss,
+                           sizeof(s->srv_conf.ss));
+                       s->srv_conf.port = h->port.val[0];
+                       host_free(&al);
+               }
+               ;
+
+port           : PORT STRING {
+                       char            *a, *b;
+                       int              p[2];
+
+                       p[0] = p[1] = 0;
+
+                       a = $2;
+                       b = strchr($2, ':');
+                       if (b == NULL)
+                               $$.op = PF_OP_EQ;
+                       else {
+                               *b++ = '\0';
+                               if ((p[1] = getservice(b)) == -1) {
+                                       free($2);
+                                       YYERROR;
+                               }
+                               $$.op = PF_OP_RRG;
+                       }
+                       if ((p[0] = getservice(a)) == -1) {
+                               free($2);
+                               YYERROR;
+                       }
+                       $$.val[0] = p[0];
+                       $$.val[1] = p[1];
+                       free($2);
+               }
+               | PORT NUMBER {
+                       if ($2 <= 0 || $2 >= (int)USHRT_MAX) {
+                               yyerror("invalid port: %d", $2);
+                               YYERROR;
+                       }
+                       $$.val[0] = htons($2);
+                       $$.op = PF_OP_EQ;
+               }
+               ;
+
+loglevel       : UPDATES               { $$ = HTTPD_OPT_LOGUPDATE; }
+               | ALL                   { $$ = HTTPD_OPT_LOGALL; }
+               ;
+
+comma          : ','
+               | nl
+               | /* empty */
+               ;
+
+optnl          : '\n' optnl
+               |
+               ;
+
+nl             : '\n' optnl
+               ;
+
+%%
+
+struct keywords {
+       const char      *k_name;
+       int              k_val;
+};
+
+int
+yyerror(const char *fmt, ...)
+{
+       va_list          ap;
+       char            *nfmt;
+
+       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);
+       va_end(ap);
+       free(nfmt);
+       return (0);
+}
+
+int
+kw_cmp(const void *k, const void *e)
+{
+       return (strcmp(k, ((const struct keywords *)e)->k_name));
+}
+
+int
+lookup(char *s)
+{
+       /* this has to be sorted always */
+       static const struct keywords keywords[] = {
+               { "all",                ALL },
+               { "include",            INCLUDE },
+               { "listen",             LISTEN },
+               { "log",                LOG },
+               { "on",                 ON },
+               { "port",               PORT },
+               { "prefork",            PREFORK },
+               { "server",             SERVER },
+               { "updates",            UPDATES }
+       };
+       const struct keywords   *p;
+
+       p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
+           sizeof(keywords[0]), kw_cmp);
+
+       if (p)
+               return (p->k_val);
+       else
+               return (STRING);
+}
+
+#define MAXPUSHBACK    128
+
+u_char *parsebuf;
+int     parseindex;
+u_char  pushback_buffer[MAXPUSHBACK];
+int     pushback_index = 0;
+
+int
+lgetc(int quotec)
+{
+       int             c, next;
+
+       if (parsebuf) {
+               /* Read character from the parsebuffer instead of input. */
+               if (parseindex >= 0) {
+                       c = parsebuf[parseindex++];
+                       if (c != '\0')
+                               return (c);
+                       parsebuf = NULL;
+               } else
+                       parseindex++;
+       }
+
+       if (pushback_index)
+               return (pushback_buffer[--pushback_index]);
+
+       if (quotec) {
+               if ((c = getc(file->stream)) == EOF) {
+                       yyerror("reached end of file while parsing "
+                           "quoted string");
+                       if (file == topfile || popfile() == EOF)
+                               return (EOF);
+                       return (quotec);
+               }
+               return (c);
+       }
+
+       while ((c = getc(file->stream)) == '\\') {
+               next = getc(file->stream);
+               if (next != '\n') {
+                       c = next;
+                       break;
+               }
+               yylval.lineno = file->lineno;
+               file->lineno++;
+       }
+
+       while (c == EOF) {
+               if (file == topfile || popfile() == EOF)
+                       return (EOF);
+               c = getc(file->stream);
+       }
+       return (c);
+}
+
+int
+lungetc(int c)
+{
+       if (c == EOF)
+               return (EOF);
+       if (parsebuf) {
+               parseindex--;
+               if (parseindex >= 0)
+                       return (c);
+       }
+       if (pushback_index < MAXPUSHBACK-1)
+               return (pushback_buffer[pushback_index++] = c);
+       else
+               return (EOF);
+}
+
+int
+findeol(void)
+{
+       int     c;
+
+       parsebuf = NULL;
+
+       /* skip to either EOF or the first real EOL */
+       while (1) {
+               if (pushback_index)
+                       c = pushback_buffer[--pushback_index];
+               else
+                       c = lgetc(0);
+               if (c == '\n') {
+                       file->lineno++;
+                       break;
+               }
+               if (c == EOF)
+                       break;
+       }
+       return (ERROR);
+}
+
+int
+yylex(void)
+{
+       u_char   buf[8096];
+       u_char  *p, *val;
+       int      quotec, next, c;
+       int      token;
+
+top:
+       p = buf;
+       while ((c = lgetc(0)) == ' ' || c == '\t')
+               ; /* nothing */
+
+       yylval.lineno = file->lineno;
+       if (c == '#')
+               while ((c = lgetc(0)) != '\n' && c != EOF)
+                       ; /* nothing */
+       if (c == '$' && parsebuf == NULL) {
+               while (1) {
+                       if ((c = lgetc(0)) == EOF)
+                               return (0);
+
+                       if (p + 1 >= buf + sizeof(buf) - 1) {
+                               yyerror("string too long");
+                               return (findeol());
+                       }
+                       if (isalnum(c) || c == '_') {
+                               *p++ = c;
+                               continue;
+                       }
+                       *p = '\0';
+                       lungetc(c);
+                       break;
+               }
+               val = symget(buf);
+               if (val == NULL) {
+                       yyerror("macro '%s' not defined", buf);
+                       return (findeol());
+               }
+               parsebuf = val;
+               parseindex = 0;
+               goto top;
+       }
+
+       switch (c) {
+       case '\'':
+       case '"':
+               quotec = c;
+               while (1) {
+                       if ((c = lgetc(quotec)) == EOF)
+                               return (0);
+                       if (c == '\n') {
+                               file->lineno++;
+                               continue;
+                       } else if (c == '\\') {
+                               if ((next = lgetc(quotec)) == EOF)
+                                       return (0);
+                               if (next == quotec || c == ' ' || c == '\t')
+                                       c = next;
+                               else if (next == '\n') {
+                                       file->lineno++;
+                                       continue;
+                               } else
+                                       lungetc(next);
+                       } else if (c == quotec) {
+                               *p = '\0';
+                               break;
+                       }
+                       if (p + 1 >= buf + sizeof(buf) - 1) {
+                               yyerror("string too long");
+                               return (findeol());
+                       }
+                       *p++ = c;
+               }
+               yylval.v.string = strdup(buf);
+               if (yylval.v.string == NULL)
+                       err(1, "yylex: strdup");
+               return (STRING);
+       }
+
+#define allowed_to_end_number(x) \
+       (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
+
+       if (c == '-' || isdigit(c)) {
+               do {
+                       *p++ = c;
+                       if ((unsigned)(p-buf) >= sizeof(buf)) {
+                               yyerror("string too long");
+                               return (findeol());
+                       }
+               } while ((c = lgetc(0)) != EOF && isdigit(c));
+               lungetc(c);
+               if (p == buf + 1 && buf[0] == '-')
+                       goto nodigits;
+               if (c == EOF || allowed_to_end_number(c)) {
+                       const char *errstr = NULL;
+
+                       *p = '\0';
+                       yylval.v.number = strtonum(buf, LLONG_MIN,
+                           LLONG_MAX, &errstr);
+                       if (errstr) {
+                               yyerror("\"%s\" invalid number: %s",
+                                   buf, errstr);
+                               return (findeol());
+                       }
+                       return (NUMBER);
+               } else {
+nodigits:
+                       while (p > buf + 1)
+                               lungetc(*--p);
+                       c = *--p;
+                       if (c == '-')
+                               return (c);
+               }
+       }
+
+#define allowed_in_string(x) \
+       (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
+       x != '{' && x != '}' && x != '<' && x != '>' && \
+       x != '!' && x != '=' && x != '#' && \
+       x != ',' && x != '/'))
+
+       if (isalnum(c) || c == ':' || c == '_') {
+               do {
+                       *p++ = c;
+                       if ((unsigned)(p-buf) >= sizeof(buf)) {
+                               yyerror("string too long");
+                               return (findeol());
+                       }
+               } while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
+               lungetc(c);
+               *p = '\0';
+               if ((token = lookup(buf)) == STRING)
+                       if ((yylval.v.string = strdup(buf)) == NULL)
+                               err(1, "yylex: strdup");
+               return (token);
+       }
+       if (c == '\n') {
+               yylval.lineno = file->lineno;
+               file->lineno++;
+       }
+       if (c == EOF)
+               return (0);
+       return (c);
+}
+
+int
+check_file_secrecy(int fd, const char *fname)
+{
+       struct stat     st;
+
+       if (fstat(fd, &st)) {
+               log_warn("cannot stat %s", fname);
+               return (-1);
+       }
+       if (st.st_uid != 0 && st.st_uid != getuid()) {
+               log_warnx("%s: owner not root or current user", fname);
+               return (-1);
+       }
+       if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
+               log_warnx("%s: group writable or world read/writable", fname);
+               return (-1);
+       }
+       return (0);
+}
+
+struct file *
+pushfile(const char *name, int secret)
+{
+       struct file     *nfile;
+
+       if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
+               log_warn("%s: malloc", __func__);
+               return (NULL);
+       }
+       if ((nfile->name = strdup(name)) == NULL) {
+               log_warn("%s: malloc", __func__);
+               free(nfile);
+               return (NULL);
+       }
+       if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
+               log_warn("%s: %s", __func__, nfile->name);
+               free(nfile->name);
+               free(nfile);
+               return (NULL);
+       } else if (secret &&
+           check_file_secrecy(fileno(nfile->stream), nfile->name)) {
+               fclose(nfile->stream);
+               free(nfile->name);
+               free(nfile);
+               return (NULL);
+       }
+       nfile->lineno = 1;
+       TAILQ_INSERT_TAIL(&files, nfile, entry);
+       return (nfile);
+}
+
+int
+popfile(void)
+{
+       struct file     *prev;
+
+       if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
+               prev->errors += file->errors;
+
+       TAILQ_REMOVE(&files, file, entry);
+       fclose(file->stream);
+       free(file->name);
+       free(file);
+       file = prev;
+       return (file ? 0 : EOF);
+}
+
+int
+parse_config(const char *filename, struct httpd *x_conf)
+{
+       struct sym      *sym, *next;
+
+       conf = x_conf;
+       if (config_init(conf) == -1) {
+               log_warn("%s: cannot initialize configuration", __func__);
+               return (-1);
+       }
+
+       errors = 0;
+
+       if ((file = pushfile(filename, 0)) == NULL)
+               return (-1);
+
+       topfile = file;
+       setservent(1);
+
+       yyparse();
+       errors = file->errors;
+       popfile();
+
+       endservent();
+       endprotoent();
+
+       /* Free macros */
+       for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) {
+               next = TAILQ_NEXT(sym, entry);
+               if (!sym->persist) {
+                       free(sym->nam);
+                       free(sym->val);
+                       TAILQ_REMOVE(&symhead, sym, entry);
+                       free(sym);
+               }
+       }
+
+       return (errors ? -1 : 0);
+}
+
+int
+load_config(const char *filename, struct httpd *x_conf)
+{
+       struct sym              *sym, *next;
+
+       conf = x_conf;
+       conf->sc_flags = 0;
+
+       loadcfg = 1;
+       errors = 0;
+       last_server_id = 0;
+
+       srv = NULL;
+
+       if ((file = pushfile(filename, 0)) == NULL)
+               return (-1);
+
+       topfile = file;
+       setservent(1);
+
+       yyparse();
+       errors = file->errors;
+       popfile();
+
+       endservent();
+       endprotoent();
+
+       /* Free macros and check which have not been used. */
+       for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) {
+               next = TAILQ_NEXT(sym, entry);
+               if ((conf->sc_opts & HTTPD_OPT_VERBOSE) && !sym->used)
+                       fprintf(stderr, "warning: macro '%s' not "
+                           "used\n", sym->nam);
+               if (!sym->persist) {
+                       free(sym->nam);
+                       free(sym->val);
+                       TAILQ_REMOVE(&symhead, sym, entry);
+                       free(sym);
+               }
+       }
+
+       if (TAILQ_EMPTY(conf->sc_servers)) {
+               log_warnx("no actions, nothing to do");
+               errors++;
+       }
+
+       return (errors ? -1 : 0);
+}
+
+int
+symset(const char *nam, const char *val, int persist)
+{
+       struct sym      *sym;
+
+       for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam);
+           sym = TAILQ_NEXT(sym, entry))
+               ;       /* nothing */
+
+       if (sym != NULL) {
+               if (sym->persist == 1)
+                       return (0);
+               else {
+                       free(sym->nam);
+                       free(sym->val);
+                       TAILQ_REMOVE(&symhead, sym, entry);
+                       free(sym);
+               }
+       }
+       if ((sym = calloc(1, sizeof(*sym))) == NULL)
+               return (-1);
+
+       sym->nam = strdup(nam);
+       if (sym->nam == NULL) {
+               free(sym);
+               return (-1);
+       }
+       sym->val = strdup(val);
+       if (sym->val == NULL) {
+               free(sym->nam);
+               free(sym);
+               return (-1);
+       }
+       sym->used = 0;
+       sym->persist = persist;
+       TAILQ_INSERT_TAIL(&symhead, sym, entry);
+       return (0);
+}
+
+int
+cmdline_symset(char *s)
+{
+       char    *sym, *val;
+       int     ret;
+       size_t  len;
+
+       if ((val = strrchr(s, '=')) == NULL)
+               return (-1);
+
+       len = strlen(s) - strlen(val) + 1;
+       if ((sym = malloc(len)) == NULL)
+               errx(1, "cmdline_symset: malloc");
+
+       (void)strlcpy(sym, s, len);
+
+       ret = symset(sym, val + 1, 1);
+       free(sym);
+
+       return (ret);
+}
+
+char *
+symget(const char *nam)
+{
+       struct sym      *sym;
+
+       TAILQ_FOREACH(sym, &symhead, entry)
+               if (strcmp(nam, sym->nam) == 0) {
+                       sym->used = 1;
+                       return (sym->val);
+               }
+       return (NULL);
+}
+
+struct address *
+host_v4(const char *s)
+{
+       struct in_addr           ina;
+       struct sockaddr_in      *sain;
+       struct address          *h;
+
+       memset(&ina, 0, sizeof(ina));
+       if (inet_pton(AF_INET, s, &ina) != 1)
+               return (NULL);
+
+       if ((h = calloc(1, sizeof(*h))) == NULL)
+               fatal(NULL);
+       sain = (struct sockaddr_in *)&h->ss;
+       sain->sin_len = sizeof(struct sockaddr_in);
+       sain->sin_family = AF_INET;
+       sain->sin_addr.s_addr = ina.s_addr;
+
+       return (h);
+}
+
+struct address *
+host_v6(const char *s)
+{
+       struct addrinfo          hints, *res;
+       struct sockaddr_in6     *sa_in6;
+       struct address          *h = NULL;
+
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_family = AF_INET6;
+       hints.ai_socktype = SOCK_DGRAM; /* dummy */
+       hints.ai_flags = AI_NUMERICHOST;
+       if (getaddrinfo(s, "0", &hints, &res) == 0) {
+               if ((h = calloc(1, sizeof(*h))) == NULL)
+                       fatal(NULL);
+               sa_in6 = (struct sockaddr_in6 *)&h->ss;
+               sa_in6->sin6_len = sizeof(struct sockaddr_in6);
+               sa_in6->sin6_family = AF_INET6;
+               memcpy(&sa_in6->sin6_addr,
+                   &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr,
+                   sizeof(sa_in6->sin6_addr));
+               sa_in6->sin6_scope_id =
+                   ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id;
+
+               freeaddrinfo(res);
+       }
+
+       return (h);
+}
+
+int
+host_dns(const char *s, struct addresslist *al, int max,
+    struct portrange *port, const char *ifname, int ipproto)
+{
+       struct addrinfo          hints, *res0, *res;
+       int                      error, cnt = 0;
+       struct sockaddr_in      *sain;
+       struct sockaddr_in6     *sin6;
+       struct address          *h;
+
+       if ((cnt = host_if(s, al, max, port, ifname, ipproto)) != 0)
+               return (cnt);
+
+       memset(&hints, 0, sizeof(hints));
+       hints.ai_family = PF_UNSPEC;
+       hints.ai_socktype = SOCK_DGRAM; /* DUMMY */
+       error = getaddrinfo(s, NULL, &hints, &res0);
+       if (error == EAI_AGAIN || error == EAI_NODATA || error == EAI_NONAME)
+               return (0);
+       if (error) {
+               log_warnx("%s: could not parse \"%s\": %s", __func__, s,
+                   gai_strerror(error));
+               return (-1);
+       }
+
+       for (res = res0; res && cnt < max; res = res->ai_next) {
+               if (res->ai_family != AF_INET &&
+                   res->ai_family != AF_INET6)
+                       continue;
+               if ((h = calloc(1, sizeof(*h))) == NULL)
+                       fatal(NULL);
+
+               if (port != NULL)
+                       memcpy(&h->port, port, sizeof(h->port));
+               if (ifname != NULL) {
+                       if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >=
+                           sizeof(h->ifname))
+                               log_warnx("%s: interface name truncated",
+                                   __func__);
+                       freeaddrinfo(res0);
+                       free(h);
+                       return (-1);
+               }
+               if (ipproto != -1)
+                       h->ipproto = ipproto;
+               h->ss.ss_family = res->ai_family;
+
+               if (res->ai_family == AF_INET) {
+                       sain = (struct sockaddr_in *)&h->ss;
+                       sain->sin_len = sizeof(struct sockaddr_in);
+                       sain->sin_addr.s_addr = ((struct sockaddr_in *)
+                           res->ai_addr)->sin_addr.s_addr;
+               } else {
+                       sin6 = (struct sockaddr_in6 *)&h->ss;
+                       sin6->sin6_len = sizeof(struct sockaddr_in6);
+                       memcpy(&sin6->sin6_addr, &((struct sockaddr_in6 *)
+                           res->ai_addr)->sin6_addr, sizeof(struct in6_addr));
+               }
+
+               TAILQ_INSERT_HEAD(al, h, entry);
+               cnt++;
+       }
+       if (cnt == max && res) {
+               log_warnx("%s: %s resolves to more than %d hosts", __func__,
+                   s, max);
+       }
+       freeaddrinfo(res0);
+       return (cnt);
+}
+
+int
+host_if(const char *s, struct addresslist *al, int max,
+    struct portrange *port, const char *ifname, int ipproto)
+{
+       struct ifaddrs          *ifap, *p;
+       struct sockaddr_in      *sain;
+       struct sockaddr_in6     *sin6;
+       struct address          *h;
+       int                      cnt = 0, af;
+
+       if (getifaddrs(&ifap) == -1)
+               fatal("getifaddrs");
+
+       /* First search for IPv4 addresses */
+       af = AF_INET;
+
+ nextaf:
+       for (p = ifap; p != NULL && cnt < max; p = p->ifa_next) {
+               if (p->ifa_addr->sa_family != af ||
+                   (strcmp(s, p->ifa_name) != 0 &&
+                   !is_if_in_group(p->ifa_name, s)))
+                       continue;
+               if ((h = calloc(1, sizeof(*h))) == NULL)
+                       fatal("calloc");
+
+               if (port != NULL)
+                       memcpy(&h->port, port, sizeof(h->port));
+               if (ifname != NULL) {
+                       if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >=
+                           sizeof(h->ifname))
+                               log_warnx("%s: interface name truncated",
+                                   __func__);
+                       freeifaddrs(ifap);
+                       return (-1);
+               }
+               if (ipproto != -1)
+                       h->ipproto = ipproto;
+               h->ss.ss_family = af;
+
+               if (af == AF_INET) {
+                       sain = (struct sockaddr_in *)&h->ss;
+                       sain->sin_len = sizeof(struct sockaddr_in);
+                       sain->sin_addr.s_addr = ((struct sockaddr_in *)
+                           p->ifa_addr)->sin_addr.s_addr;
+               } else {
+                       sin6 = (struct sockaddr_in6 *)&h->ss;
+                       sin6->sin6_len = sizeof(struct sockaddr_in6);
+                       memcpy(&sin6->sin6_addr, &((struct sockaddr_in6 *)
+                           p->ifa_addr)->sin6_addr, sizeof(struct in6_addr));
+                       sin6->sin6_scope_id = ((struct sockaddr_in6 *)
+                           p->ifa_addr)->sin6_scope_id;
+               }
+
+               TAILQ_INSERT_HEAD(al, h, entry);
+               cnt++;
+       }
+       if (af == AF_INET) {
+               /* Next search for IPv6 addresses */
+               af = AF_INET6;
+               goto nextaf;
+       }
+
+       if (cnt > max) {
+               log_warnx("%s: %s resolves to more than %d hosts", __func__,
+                   s, max);
+       }
+       freeifaddrs(ifap);
+       return (cnt);
+}
+
+int
+host(const char *s, struct addresslist *al, int max,
+    struct portrange *port, const char *ifname, int ipproto)
+{
+       struct address *h;
+
+       h = host_v4(s);
+
+       /* IPv6 address? */
+       if (h == NULL)
+               h = host_v6(s);
+
+       if (h != NULL) {
+               if (port != NULL)
+                       memcpy(&h->port, port, sizeof(h->port));
+               if (ifname != NULL) {
+                       if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >=
+                           sizeof(h->ifname)) {
+                               log_warnx("%s: interface name truncated",
+                                   __func__);
+                               free(h);
+                               return (-1);
+                       }
+               }
+               if (ipproto != -1)
+                       h->ipproto = ipproto;
+
+               TAILQ_INSERT_HEAD(al, h, entry);
+               return (1);
+       }
+
+       return (host_dns(s, al, max, port, ifname, ipproto));
+}
+
+void
+host_free(struct addresslist *al)
+{
+       struct address   *h;
+
+       while ((h = TAILQ_FIRST(al)) != NULL) {
+               TAILQ_REMOVE(al, h, entry);
+               free(h);
+       }
+}
+
+int
+getservice(char *n)
+{
+       struct servent  *s;
+       const char      *errstr;
+       long long        llval;
+
+       llval = strtonum(n, 0, UINT16_MAX, &errstr);
+       if (errstr) {
+               s = getservbyname(n, "tcp");
+               if (s == NULL)
+                       s = getservbyname(n, "udp");
+               if (s == NULL) {
+                       yyerror("unknown port %s", n);
+                       return (-1);
+               }
+               return (s->s_port);
+       }
+
+       return (htons((u_short)llval));
+}
+
+int
+is_if_in_group(const char *ifname, const char *groupname)
+{
+       unsigned int             len;
+       struct ifgroupreq        ifgr;
+       struct ifg_req          *ifg;
+       int                      s;
+       int                      ret = 0;
+
+       if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
+               err(1, "socket");
+
+       memset(&ifgr, 0, sizeof(ifgr));
+       if (strlcpy(ifgr.ifgr_name, ifname, IFNAMSIZ) >= IFNAMSIZ)
+               err(1, "IFNAMSIZ");
+       if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1) {
+               if (errno == EINVAL || errno == ENOTTY)
+                       goto end;
+               err(1, "SIOCGIFGROUP");
+       }
+
+       len = ifgr.ifgr_len;
+       ifgr.ifgr_groups =
+           (struct ifg_req *)calloc(len / sizeof(struct ifg_req),
+               sizeof(struct ifg_req));
+       if (ifgr.ifgr_groups == NULL)
+               err(1, "getifgroups");
+       if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1)
+               err(1, "SIOCGIFGROUP");
+
+       ifg = ifgr.ifgr_groups;
+       for (; ifg && len >= sizeof(struct ifg_req); ifg++) {
+               len -= sizeof(struct ifg_req);
+               if (strcmp(ifg->ifgrq_group, groupname) == 0) {
+                       ret = 1;
+                       break;
+               }
+       }
+       free(ifgr.ifgr_groups);
+
+end:
+       close(s);
+       return (ret);
+}
diff --git a/usr.sbin/httpd/proc.c b/usr.sbin/httpd/proc.c
new file mode 100644 (file)
index 0000000..7a6d571
--- /dev/null
@@ -0,0 +1,627 @@
+/*     $OpenBSD: proc.c,v 1.1 2014/07/12 23:34:54 reyk Exp $   */
+
+/*
+ * Copyright (c) 2010 - 2014 Reyk Floeter <reyk@openbsd.org>
+ * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/tree.h>
+
+#include <net/if.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <pwd.h>
+#include <event.h>
+
+#include <openssl/ssl.h>
+
+#include "httpd.h"
+
+void    proc_open(struct privsep *, struct privsep_proc *,
+           struct privsep_proc *, size_t);
+void    proc_close(struct privsep *);
+int     proc_ispeer(struct privsep_proc *, u_int, enum privsep_procid);
+void    proc_shutdown(struct privsep_proc *);
+void    proc_sig_handler(int, short, void *);
+void    proc_range(struct privsep *, enum privsep_procid, int *, int *);
+
+int
+proc_ispeer(struct privsep_proc *procs, u_int nproc, enum privsep_procid type)
+{
+       u_int   i;
+
+       for (i = 0; i < nproc; i++)
+               if (procs[i].p_id == type)
+                       return (1);
+       return (0);
+}
+
+void
+proc_init(struct privsep *ps, struct privsep_proc *procs, u_int nproc)
+{
+       u_int                    i, j, src, dst;
+       struct privsep_pipes    *pp;
+
+       /*
+        * Allocate pipes for all process instances (incl. parent)
+        *
+        * - ps->ps_pipes: N:M mapping
+        * N source processes connected to M destination processes:
+        * [src][instances][dst][instances], for example
+        * [PROC_RELAY][3][PROC_CA][3]
+        *
+        * - ps->ps_pp: per-process 1:M part of ps->ps_pipes
+        * Each process instance has a destination array of socketpair fds:
+        * [dst][instances], for example
+        * [PROC_PARENT][0]
+        */
+       for (src = 0; src < PROC_MAX; src++) {
+               /* Allocate destination array for each process */
+               if ((ps->ps_pipes[src] = calloc(ps->ps_ninstances,
+                   sizeof(struct privsep_pipes))) == NULL)
+                       fatal("proc_init: calloc");
+
+               for (i = 0; i < ps->ps_ninstances; i++) {
+                       pp = &ps->ps_pipes[src][i];
+
+                       for (dst = 0; dst < PROC_MAX; dst++) {
+                               /* Allocate maximum fd integers */
+                               if ((pp->pp_pipes[dst] =
+                                   calloc(ps->ps_ninstances,
+                                   sizeof(int))) == NULL)
+                                       fatal("proc_init: calloc");
+
+                               /* Mark fd as unused */
+                               for (j = 0; j < ps->ps_ninstances; j++)
+                                       pp->pp_pipes[dst][j] = -1;
+                       }
+               }
+       }
+
+       /*
+        * Setup and run the parent and its children
+        */
+       privsep_process = PROC_PARENT;
+       ps->ps_instances[PROC_PARENT] = 1;
+       ps->ps_title[PROC_PARENT] = "parent";
+       ps->ps_pid[PROC_PARENT] = getpid();
+       ps->ps_pp = &ps->ps_pipes[privsep_process][0];
+
+       for (i = 0; i < nproc; i++) {
+               /* Default to 1 process instance */
+               if (ps->ps_instances[procs[i].p_id] < 1)
+                       ps->ps_instances[procs[i].p_id] = 1;
+               ps->ps_title[procs[i].p_id] = procs[i].p_title;
+       }
+
+       proc_open(ps, NULL, procs, nproc);
+
+       /* Engage! */
+       for (i = 0; i < nproc; i++)
+               ps->ps_pid[procs[i].p_id] = (*procs[i].p_init)(ps, &procs[i]);
+}
+
+void
+proc_kill(struct privsep *ps)
+{
+       pid_t            pid;
+       u_int            i;
+
+       if (privsep_process != PROC_PARENT)
+               return;
+
+       for (i = 0; i < PROC_MAX; i++) {
+               if (ps->ps_pid[i] == 0)
+                       continue;
+               killpg(ps->ps_pid[i], SIGTERM);
+       }
+
+       do {
+               pid = waitpid(WAIT_ANY, NULL, 0);
+       } while (pid != -1 || (pid == -1 && errno == EINTR));
+
+       proc_close(ps);
+}
+
+void
+proc_open(struct privsep *ps, struct privsep_proc *p,
+    struct privsep_proc *procs, size_t nproc)
+{
+       struct privsep_pipes    *pa, *pb;
+       int                      fds[2];
+       u_int                    i, j, src, proc;
+
+       if (p == NULL)
+               src = privsep_process; /* parent */
+       else
+               src = p->p_id;
+
+       /*
+        * Open socket pairs for our peers
+        */     
+       for (proc = 0; proc < nproc; proc++) {
+               procs[proc].p_ps = ps;
+               procs[proc].p_env = ps->ps_env;
+
+               for (i = 0; i < ps->ps_instances[src]; i++) {
+                       for (j = 0; j < ps->ps_instances[procs[proc].p_id];
+                           j++) {
+                               pa = &ps->ps_pipes[src][i];
+                               pb = &ps->ps_pipes[procs[proc].p_id][j];
+
+                               /* Check if fds are already set by peer */
+                               if (pa->pp_pipes[procs[proc].p_id][j] != -1)
+                                       continue;
+
+                               if (socketpair(AF_UNIX, SOCK_STREAM,
+                                   PF_UNSPEC, fds) == -1)
+                                       fatal("socketpair");
+
+                               socket_set_blockmode(fds[0], BM_NONBLOCK);
+                               socket_set_blockmode(fds[1], BM_NONBLOCK);
+
+                               pa->pp_pipes[procs[proc].p_id][j] = fds[0];
+                               pb->pp_pipes[src][i] = fds[1];
+                       }
+               }
+       }
+}
+
+void
+proc_listen(struct privsep *ps, struct privsep_proc *procs, size_t nproc)
+{
+       u_int                    i, dst, src, n, m;
+       struct privsep_pipes    *pp;
+
+       /*
+        * Close unused pipes
+        */
+       for (src = 0; src < PROC_MAX; src++) {
+               for (n = 0; n < ps->ps_instances[src]; n++) {
+                       /* Ingore current process */
+                       if (src == (u_int)privsep_process &&
+                           n == ps->ps_instance)
+                               continue;
+
+                       pp = &ps->ps_pipes[src][n];
+
+                       for (dst = 0; dst < PROC_MAX; dst++) {
+                               if (src == dst)
+                                       continue;
+                               for (m = 0; m < ps->ps_instances[dst]; m++) {
+                                       if (pp->pp_pipes[dst][m] == -1)
+                                               continue;
+
+                                       /* Close and invalidate fd */
+                                       close(pp->pp_pipes[dst][m]);
+                                       pp->pp_pipes[dst][m] = -1;
+                               }
+                       }
+               }
+       }
+
+       src = privsep_process;
+       ps->ps_pp = pp = &ps->ps_pipes[src][ps->ps_instance];
+
+       /*
+        * Listen on appropriate pipes
+        */
+       for (i = 0; i < nproc; i++) {
+               dst = procs[i].p_id;
+
+               if (src == dst)
+                       fatal("proc_listen: cannot peer with oneself");
+
+               if ((ps->ps_ievs[dst] = calloc(ps->ps_instances[dst],
+                   sizeof(struct imsgev))) == NULL)
+                       fatal("proc_open");
+
+               for (n = 0; n < ps->ps_instances[dst]; n++) {
+                       if (pp->pp_pipes[dst][n] == -1)
+                               continue;
+
+                       imsg_init(&(ps->ps_ievs[dst][n].ibuf),
+                           pp->pp_pipes[dst][n]);
+                       ps->ps_ievs[dst][n].handler = proc_dispatch;
+                       ps->ps_ievs[dst][n].events = EV_READ;
+                       ps->ps_ievs[dst][n].proc = &procs[i];
+                       ps->ps_ievs[dst][n].data = &ps->ps_ievs[dst][n];
+                       procs[i].p_instance = n;
+
+                       event_set(&(ps->ps_ievs[dst][n].ev),
+                           ps->ps_ievs[dst][n].ibuf.fd,
+                           ps->ps_ievs[dst][n].events,
+                           ps->ps_ievs[dst][n].handler,
+                           ps->ps_ievs[dst][n].data);
+                       event_add(&(ps->ps_ievs[dst][n].ev), NULL);
+               }
+       }
+}
+
+void
+proc_close(struct privsep *ps)
+{
+       u_int                    dst, n;
+       struct privsep_pipes    *pp;
+
+       if (ps == NULL)
+               return;
+
+       pp = ps->ps_pp;
+
+       for (dst = 0; dst < PROC_MAX; dst++) {
+               if (ps->ps_ievs[dst] == NULL)
+                       continue;
+
+               for (n = 0; n < ps->ps_instances[dst]; n++) {
+                       if (pp->pp_pipes[dst][n] == -1)
+                               continue;
+
+                       /* Cancel the fd, close and invalidate the fd */
+                       event_del(&(ps->ps_ievs[dst][n].ev));
+                       imsg_clear(&(ps->ps_ievs[dst][n].ibuf));
+                       close(pp->pp_pipes[dst][n]);
+                       pp->pp_pipes[dst][n] = -1;
+               }
+               free(ps->ps_ievs[dst]);
+       }
+}
+
+void
+proc_shutdown(struct privsep_proc *p)
+{
+       struct privsep  *ps = p->p_ps;
+
+       if (p->p_id == PROC_CONTROL && ps)
+               control_cleanup(&ps->ps_csock);
+
+       if (p->p_shutdown != NULL)
+               (*p->p_shutdown)();
+
+       proc_close(ps);
+
+       log_info("%s exiting, pid %d", p->p_title, getpid());
+
+       _exit(0);
+}
+
+void
+proc_sig_handler(int sig, short event, void *arg)
+{
+       struct privsep_proc     *p = arg;
+
+       switch (sig) {
+       case SIGINT:
+       case SIGTERM:
+               proc_shutdown(p);
+               break;
+       case SIGCHLD:
+       case SIGHUP:
+       case SIGPIPE:
+               /* ignore */
+               break;
+       default:
+               fatalx("proc_sig_handler: unexpected signal");
+               /* NOTREACHED */
+       }
+}
+
+pid_t
+proc_run(struct privsep *ps, struct privsep_proc *p,
+    struct privsep_proc *procs, u_int nproc,
+    void (*init)(struct privsep *, struct privsep_proc *, void *), void *arg)
+{
+       pid_t                    pid;
+       struct passwd           *pw;
+       const char              *root;
+       struct control_sock     *rcs;
+       u_int                    n;
+
+       if (ps->ps_noaction)
+               return (0);
+
+       proc_open(ps, p, procs, nproc);
+
+       /* Fork child handlers */
+       switch (pid = fork()) {
+       case -1:
+               fatal("proc_run: cannot fork");
+       case 0:
+               /* Set the process group of the current process */
+               setpgrp(0, getpid());
+               break;
+       default:
+               return (pid);
+       }
+
+       pw = ps->ps_pw;
+
+       if (p->p_id == PROC_CONTROL && ps->ps_instance == 0) {
+               if (control_init(ps, &ps->ps_csock) == -1)
+                       fatalx(p->p_title);
+               TAILQ_FOREACH(rcs, &ps->ps_rcsocks, cs_entry)
+                       if (control_init(ps, rcs) == -1)
+                               fatalx(p->p_title);
+       }
+
+       /* Change root directory */
+       if (p->p_chroot != NULL)
+               root = p->p_chroot;
+       else
+               root = pw->pw_dir;
+
+       if (chroot(root) == -1)
+               fatal("proc_run: chroot");
+       if (chdir("/") == -1)
+               fatal("proc_run: chdir(\"/\")");
+
+       privsep_process = p->p_id;
+
+       setproctitle("%s", p->p_title);
+
+       if (setgroups(1, &pw->pw_gid) ||
+           setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
+           setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
+               fatal("proc_run: cannot drop privileges");
+
+       /* Fork child handlers */
+       for (n = 1; n < ps->ps_instances[p->p_id]; n++) {
+               if (fork() == 0) {
+                       ps->ps_instance = p->p_instance = n;
+                       break;
+               }
+       }
+
+#ifdef DEBUG
+       log_debug("%s: %s %d/%d, pid %d", __func__, p->p_title,
+           ps->ps_instance + 1, ps->ps_instances[p->p_id], getpid());
+#endif
+
+       event_init();
+
+       signal_set(&ps->ps_evsigint, SIGINT, proc_sig_handler, p);
+       signal_set(&ps->ps_evsigterm, SIGTERM, proc_sig_handler, p);
+       signal_set(&ps->ps_evsigchld, SIGCHLD, proc_sig_handler, p);
+       signal_set(&ps->ps_evsighup, SIGHUP, proc_sig_handler, p);
+       signal_set(&ps->ps_evsigpipe, SIGPIPE, proc_sig_handler, p);
+
+       signal_add(&ps->ps_evsigint, NULL);
+       signal_add(&ps->ps_evsigterm, NULL);
+       signal_add(&ps->ps_evsigchld, NULL);
+       signal_add(&ps->ps_evsighup, NULL);
+       signal_add(&ps->ps_evsigpipe, NULL);
+
+       proc_listen(ps, procs, nproc);
+
+       if (p->p_id == PROC_CONTROL && ps->ps_instance == 0) {
+               TAILQ_INIT(&ctl_conns);
+               if (control_listen(&ps->ps_csock) == -1)
+                       fatalx(p->p_title);
+               TAILQ_FOREACH(rcs, &ps->ps_rcsocks, cs_entry)
+                       if (control_listen(rcs) == -1)
+                               fatalx(p->p_title);
+       }
+
+       if (init != NULL)
+               init(ps, p, arg);
+
+       event_dispatch();
+
+       proc_shutdown(p);
+
+       return (0);
+}
+
+void
+proc_dispatch(int fd, short event, void *arg)
+{
+       struct imsgev           *iev = arg;
+       struct privsep_proc     *p = iev->proc;
+       struct privsep          *ps = p->p_ps;
+       struct imsgbuf          *ibuf;
+       struct imsg              imsg;
+       ssize_t                  n;
+       int                      verbose;
+       const char              *title;
+
+       title = ps->ps_title[privsep_process];
+       ibuf = &iev->ibuf;
+
+       if (event & EV_READ) {
+               if ((n = imsg_read(ibuf)) == -1)
+                       fatal(title);
+               if (n == 0) {
+                       /* this pipe is dead, so remove the event handler */
+                       event_del(&iev->ev);
+                       event_loopexit(NULL);
+                       return;
+               }
+       }
+
+       if (event & EV_WRITE) {
+               if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN)
+                       fatal(title);
+       }
+
+       for (;;) {
+               if ((n = imsg_get(ibuf, &imsg)) == -1)
+                       fatal(title);
+               if (n == 0)
+                       break;
+
+#if DEBUG > 1
+               log_debug("%s: %s %d got imsg %d from %s %d",
+                   __func__, title, ps->ps_instance + 1,
+                   imsg.hdr.type, p->p_title, p->p_instance);
+#endif
+
+               /*
+                * Check the message with the program callback
+                */
+               if ((p->p_cb)(fd, p, &imsg) == 0) {
+                       /* Message was handled by the callback, continue */
+                       imsg_free(&imsg);
+                       continue;
+               }
+
+               /*
+                * Generic message handling
+                */
+               switch (imsg.hdr.type) {
+               case IMSG_CTL_VERBOSE:
+                       IMSG_SIZE_CHECK(&imsg, &verbose);
+                       memcpy(&verbose, imsg.data, sizeof(verbose));
+                       log_verbose(verbose);
+                       break;
+               default:
+                       log_warnx("%s: %s %d got invalid imsg %d from %s %d",
+                           __func__, title, ps->ps_instance + 1,
+                           imsg.hdr.type, p->p_title, p->p_instance);
+                       fatalx(title);
+               }
+               imsg_free(&imsg);
+       }
+       imsg_event_add(iev);
+}
+
+/*
+ * imsg helper functions
+ */
+
+void
+imsg_event_add(struct imsgev *iev)
+{
+       if (iev->handler == NULL) {
+               imsg_flush(&iev->ibuf);
+               return;
+       }
+
+       iev->events = EV_READ;
+       if (iev->ibuf.w.queued)
+               iev->events |= EV_WRITE;
+
+       event_del(&iev->ev);
+       event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev->data);
+       event_add(&iev->ev, NULL);
+}
+
+int
+imsg_compose_event(struct imsgev *iev, u_int16_t type, u_int32_t peerid,
+    pid_t pid, int fd, void *data, u_int16_t datalen)
+{
+       int     ret;
+
+       if ((ret = imsg_compose(&iev->ibuf, type, peerid,
+           pid, fd, data, datalen)) == -1)
+               return (ret);
+       imsg_event_add(iev);
+       return (ret);
+}
+
+int
+imsg_composev_event(struct imsgev *iev, u_int16_t type, u_int32_t peerid,
+    pid_t pid, int fd, const struct iovec *iov, int iovcnt)
+{
+       int     ret;
+
+       if ((ret = imsg_composev(&iev->ibuf, type, peerid,
+           pid, fd, iov, iovcnt)) == -1)
+               return (ret);
+       imsg_event_add(iev);
+       return (ret);
+}
+
+void
+proc_range(struct privsep *ps, enum privsep_procid id, int *n, int *m)
+{
+       if (*n == -1) {
+               /* Use a range of all target instances */
+               *n = 0;
+               *m = ps->ps_instances[id];
+       } else {
+               /* Use only a single slot of the specified peer process */
+               *m = *n + 1;
+       }
+}
+
+int
+proc_compose_imsg(struct privsep *ps, enum privsep_procid id, int n,
+    u_int16_t type, int fd, void *data, u_int16_t datalen)
+{
+       int      m;
+
+       proc_range(ps, id, &n, &m);
+       for (; n < m; n++) {
+               if (imsg_compose_event(&ps->ps_ievs[id][n],
+                   type, -1, 0, fd, data, datalen) == -1)
+                       return (-1);
+       }
+
+       return (0);
+}
+
+int
+proc_composev_imsg(struct privsep *ps, enum privsep_procid id, int n,
+    u_int16_t type, int fd, const struct iovec *iov, int iovcnt)
+{
+       int      m;
+
+       proc_range(ps, id, &n, &m);
+       for (; n < m; n++)
+               if (imsg_composev_event(&ps->ps_ievs[id][n],
+                   type, -1, 0, fd, iov, iovcnt) == -1)
+                       return (-1);
+
+       return (0);
+}
+
+int
+proc_forward_imsg(struct privsep *ps, struct imsg *imsg,
+    enum privsep_procid id, int n)
+{
+       return (proc_compose_imsg(ps, id, n, imsg->hdr.type,
+           imsg->fd, imsg->data, IMSG_DATA_SIZE(imsg)));
+}
+
+struct imsgbuf *
+proc_ibuf(struct privsep *ps, enum privsep_procid id, int n)
+{
+       int      m;
+
+       proc_range(ps, id, &n, &m);
+       return (&ps->ps_ievs[id][n].ibuf);
+}
+
+struct imsgev *
+proc_iev(struct privsep *ps, enum privsep_procid id, int n)
+{
+       int      m;
+
+       proc_range(ps, id, &n, &m);
+       return (&ps->ps_ievs[id][n]);
+}
diff --git a/usr.sbin/httpd/server.c b/usr.sbin/httpd/server.c
new file mode 100644 (file)
index 0000000..fe62b40
--- /dev/null
@@ -0,0 +1,643 @@
+/*     $OpenBSD: server.c,v 1.1 2014/07/12 23:34:54 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 - 2014 Reyk Floeter <reyk@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/tree.h>
+#include <sys/hash.h>
+
+#include <net/if.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <err.h>
+#include <pwd.h>
+#include <event.h>
+#include <fnmatch.h>
+
+#include <openssl/dh.h>
+#include <openssl/ssl.h>
+
+#include "httpd.h"
+
+int             server_dispatch_parent(int, struct privsep_proc *,
+                   struct imsg *);
+void            server_shutdown(void);
+
+void            server_init(struct privsep *, struct privsep_proc *p, void *);
+void            server_launch(void);
+int             server_socket(struct sockaddr_storage *, in_port_t,
+                   struct server *, int, int);
+int             server_socket_listen(struct sockaddr_storage *, in_port_t,
+                   struct server *);
+
+void            server_accept(int, short, void *);
+void            server_input(struct client *);
+
+extern void     bufferevent_read_pressure_cb(struct evbuffer *, size_t,
+                   size_t, void *);
+
+volatile int server_clients;
+volatile int server_inflight = 0;
+u_int32_t server_cltid;
+
+static struct httpd            *env = NULL;
+int                             proc_id;
+
+static struct privsep_proc procs[] = {
+       { "parent",     PROC_PARENT,    server_dispatch_parent }
+};
+
+pid_t
+server(struct privsep *ps, struct privsep_proc *p)
+{
+       pid_t    pid;
+       env = ps->ps_env;
+       pid = proc_run(ps, p, procs, nitems(procs), server_init, NULL);
+       server_http(env);
+       return (pid);
+}
+
+void
+server_shutdown(void)
+{
+       config_purge(env, CONFIG_ALL);
+       usleep(200);    /* XXX server needs to shutdown last */
+}
+
+int
+server_privinit(struct server *srv)
+{
+       log_debug("%s: adding server %s", __func__, srv->srv_conf.name);
+
+       if ((srv->srv_s = server_socket_listen(&srv->srv_conf.ss,
+           srv->srv_conf.port, srv)) == -1)
+               return (-1);
+
+       return (0);
+}
+
+void
+server_init(struct privsep *ps, struct privsep_proc *p, void *arg)
+{
+       server_http(ps->ps_env);
+
+       if (config_init(ps->ps_env) == -1)
+               fatal("failed to initialize configuration");
+
+       /* Set to current prefork id */
+       proc_id = p->p_instance;
+
+       /* We use a custom shutdown callback */
+       p->p_shutdown = server_shutdown;
+
+       /* Unlimited file descriptors (use system limits) */
+       socket_rlimit(-1);
+
+#if 0
+       /* Schedule statistics timer */
+       evtimer_set(&env->sc_statev, server_statistics, NULL);
+       memcpy(&tv, &env->sc_statinterval, sizeof(tv));
+       evtimer_add(&env->sc_statev, &tv);
+#endif
+}
+
+void
+server_launch(void)
+{
+       struct server           *srv;
+
+       TAILQ_FOREACH(srv, env->sc_servers, srv_entry) {
+               server_http_init(srv);
+
+               log_debug("%s: running server %s", __func__,
+                   srv->srv_conf.name);
+
+               event_set(&srv->srv_ev, srv->srv_s, EV_READ,
+                   server_accept, srv);
+               event_add(&srv->srv_ev, NULL);
+               evtimer_set(&srv->srv_evt, server_accept, srv);
+       }
+}
+
+int
+server_socket_af(struct sockaddr_storage *ss, in_port_t port)
+{
+       switch (ss->ss_family) {
+       case AF_INET:
+               ((struct sockaddr_in *)ss)->sin_port = port;
+               ((struct sockaddr_in *)ss)->sin_len =
+                   sizeof(struct sockaddr_in);
+               break;
+       case AF_INET6:
+               ((struct sockaddr_in6 *)ss)->sin6_port = port;
+               ((struct sockaddr_in6 *)ss)->sin6_len =
+                   sizeof(struct sockaddr_in6);
+               break;
+       default:
+               return (-1);
+       }
+
+       return (0);
+}
+
+in_port_t
+server_socket_getport(struct sockaddr_storage *ss)
+{
+       switch (ss->ss_family) {
+       case AF_INET:
+               return (((struct sockaddr_in *)ss)->sin_port);
+       case AF_INET6:
+               return (((struct sockaddr_in6 *)ss)->sin6_port);
+       default:
+               return (0);
+       }
+
+       /* NOTREACHED */
+       return (0);
+}
+
+int
+server_socket(struct sockaddr_storage *ss, in_port_t port,
+    struct server *srv, int fd, int reuseport)
+{
+       struct linger   lng;
+       int             s = -1, val;
+
+       if (server_socket_af(ss, port) == -1)
+               goto bad;
+
+       s = fd == -1 ? socket(ss->ss_family, SOCK_STREAM, IPPROTO_TCP) : fd;
+       if (s == -1)
+               goto bad;
+
+       /*
+        * Socket options
+        */
+       memset(&lng, 0, sizeof(lng));
+       if (setsockopt(s, SOL_SOCKET, SO_LINGER, &lng, sizeof(lng)) == -1)
+               goto bad;
+       if (reuseport) {
+               val = 1;
+               if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &val,
+                       sizeof(int)) == -1)
+                       goto bad;
+       }
+       if (fcntl(s, F_SETFL, O_NONBLOCK) == -1)
+               goto bad;
+       if (srv->srv_tcpflags & TCPFLAG_BUFSIZ) {
+               val = srv->srv_tcpbufsiz;
+               if (setsockopt(s, SOL_SOCKET, SO_RCVBUF,
+                   &val, sizeof(val)) == -1)
+                       goto bad;
+               val = srv->srv_tcpbufsiz;
+               if (setsockopt(s, SOL_SOCKET, SO_SNDBUF,
+                   &val, sizeof(val)) == -1)
+                       goto bad;
+       }
+
+       /*
+        * IP options
+        */
+       if (srv->srv_tcpflags & TCPFLAG_IPTTL) {
+               val = (int)srv->srv_tcpipttl;
+               if (setsockopt(s, IPPROTO_IP, IP_TTL,
+                   &val, sizeof(val)) == -1)
+                       goto bad;
+       }
+       if (srv->srv_tcpflags & TCPFLAG_IPMINTTL) {
+               val = (int)srv->srv_tcpipminttl;
+               if (setsockopt(s, IPPROTO_IP, IP_MINTTL,
+                   &val, sizeof(val)) == -1)
+                       goto bad;
+       }
+
+       /*
+        * TCP options
+        */
+       if (srv->srv_tcpflags & (TCPFLAG_NODELAY|TCPFLAG_NNODELAY)) {
+               if (srv->srv_tcpflags & TCPFLAG_NNODELAY)
+                       val = 0;
+               else
+                       val = 1;
+               if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY,
+                   &val, sizeof(val)) == -1)
+                       goto bad;
+       }
+       if (srv->srv_tcpflags & (TCPFLAG_SACK|TCPFLAG_NSACK)) {
+               if (srv->srv_tcpflags & TCPFLAG_NSACK)
+                       val = 0;
+               else
+                       val = 1;
+               if (setsockopt(s, IPPROTO_TCP, TCP_SACK_ENABLE,
+                   &val, sizeof(val)) == -1)
+                       goto bad;
+       }
+
+       return (s);
+
+ bad:
+       if (s != -1)
+               close(s);
+       return (-1);
+}
+
+int
+server_socket_listen(struct sockaddr_storage *ss, in_port_t port,
+    struct server *srv)
+{
+       int s;
+
+       if ((s = server_socket(ss, port, srv, -1, 1)) == -1)
+               return (-1);
+
+       if (bind(s, (struct sockaddr *)ss, ss->ss_len) == -1)
+               goto bad;
+       if (listen(s, srv->srv_tcpbacklog) == -1)
+               goto bad;
+
+       return (s);
+
+ bad:
+       close(s);
+       return (-1);
+}
+
+void
+server_input(struct client *clt)
+{
+       struct server   *srv = clt->clt_server;
+       evbuffercb       inrd = server_read;
+       evbuffercb       inwr = server_write;
+
+       if (server_httpdesc_init(clt) == -1) {
+               server_close(clt,
+                   "failed to allocate http descriptor");
+               return;
+       }
+
+       clt->clt_toread = TOREAD_HTTP_HEADER;
+       inrd = server_read_http;
+
+       /*
+        * Client <-> Server
+        */
+       clt->clt_bev = bufferevent_new(clt->clt_s, inrd, inwr,
+           server_error, clt);
+       if (clt->clt_bev == NULL) {
+               server_close(clt, "failed to allocate input buffer event");
+               return;
+       }
+
+       bufferevent_settimeout(clt->clt_bev,
+           srv->srv_conf.timeout.tv_sec, srv->srv_conf.timeout.tv_sec);
+       bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE);
+}
+
+void
+server_write(struct bufferevent *bev, void *arg)
+{
+       struct client           *clt = arg;
+
+       getmonotime(&clt->clt_tv_last);
+
+       if (clt->clt_done)
+               goto done;
+       return;
+ done:
+       server_close(clt, "done");
+       return;
+}
+
+void
+server_dump(struct client *clt, const void *buf, size_t len)
+{
+       if (!len)
+               return;
+
+       /*
+        * This function will dump the specified message directly
+        * to the underlying client, without waiting for success
+        * of non-blocking events etc. This is useful to print an
+        * error message before gracefully closing the client.
+        */
+#if 0
+       if (cre->ssl != NULL)
+               (void)SSL_write(cre->ssl, buf, len);
+       else
+#endif
+               (void)write(clt->clt_s, buf, len);
+}
+
+void
+server_read(struct bufferevent *bev, void *arg)
+{
+       struct client           *clt = arg;
+       struct evbuffer         *src = EVBUFFER_INPUT(bev);
+
+       getmonotime(&clt->clt_tv_last);
+
+       if (!EVBUFFER_LENGTH(src))
+               return;
+       if (server_bufferevent_write_buffer(clt, src) == -1)
+               goto fail;
+       if (clt->clt_done)
+               goto done;
+       bufferevent_enable(bev, EV_READ);
+       return;
+ done:
+       server_close(clt, "done");
+       return;
+ fail:
+       server_close(clt, strerror(errno));
+}
+
+void
+server_error(struct bufferevent *bev, short error, void *arg)
+{
+       struct client           *clt = arg;
+
+       if (error & EVBUFFER_TIMEOUT) {
+               server_close(clt, "buffer event timeout");
+               return;
+       }
+       if (error & EVBUFFER_ERROR && errno == EFBIG) {
+               bufferevent_enable(bev, EV_READ);
+               return;
+       }
+       if (error & (EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF)) {
+               bufferevent_disable(bev, EV_READ|EV_WRITE);
+
+               clt->clt_done = 1;
+
+               return;
+//             server_close(con, "done");
+       }
+       server_close(clt, "buffer event error");
+       return;
+}
+
+void
+server_accept(int fd, short event, void *arg)
+{
+       struct server           *srv = arg;
+       struct client           *clt = NULL;
+       socklen_t                slen;
+       struct sockaddr_storage  ss;
+       int                      s = -1;
+
+       event_add(&srv->srv_ev, NULL);
+       if ((event & EV_TIMEOUT))
+               return;
+
+       slen = sizeof(ss);
+       if ((s = accept_reserve(fd, (struct sockaddr *)&ss,
+           &slen, FD_RESERVE, &server_inflight)) == -1) {
+               /*
+                * Pause accept if we are out of file descriptors, or
+                * libevent will haunt us here too.
+                */
+               if (errno == ENFILE || errno == EMFILE) {
+                       struct timeval evtpause = { 1, 0 };
+
+                       event_del(&srv->srv_ev);
+                       evtimer_add(&srv->srv_evt, &evtpause);
+                       log_debug("%s: deferring connections", __func__);
+               }
+               return;
+       }
+       if (server_clients >= SERVER_MAX_CLIENTS)
+               goto err;
+
+       if (fcntl(s, F_SETFL, O_NONBLOCK) == -1)
+               goto err;
+
+       if ((clt = calloc(1, sizeof(*clt))) == NULL)
+               goto err;
+
+       clt->clt_s = s;
+       clt->clt_fd = -1;
+       clt->clt_toread = TOREAD_UNLIMITED;
+       clt->clt_server = srv;
+       clt->clt_id = ++server_cltid;
+       clt->clt_serverid = srv->srv_conf.id;
+       clt->clt_pid = getpid();
+       switch (ss.ss_family) {
+       case AF_INET:
+               clt->clt_port = ((struct sockaddr_in *)&ss)->sin_port;
+               break;
+       case AF_INET6:
+               clt->clt_port = ((struct sockaddr_in6 *)&ss)->sin6_port;
+               break;
+       }
+       memcpy(&clt->clt_ss, &ss, sizeof(clt->clt_ss));
+
+       getmonotime(&clt->clt_tv_start);
+       memcpy(&clt->clt_tv_last, &clt->clt_tv_start, sizeof(clt->clt_tv_last));
+
+       server_clients++;
+       SPLAY_INSERT(client_tree, &srv->srv_clients, clt);
+
+       /* Increment the per-relay client counter */
+       //srv->srv_stats[proc_id].last++;
+
+       /* Pre-allocate output buffer */
+       clt->clt_output = evbuffer_new();
+       if (clt->clt_output == NULL) {
+               server_close(clt, "failed to allocate output buffer");
+               return;
+       }
+
+       /* Pre-allocate log buffer */
+       clt->clt_log = evbuffer_new();
+       if (clt->clt_log == NULL) {
+               server_close(clt, "failed to allocate log buffer");
+               return;
+       }
+
+       server_input(clt);
+       return;
+
+ err:
+       if (s != -1) {
+               close(s);
+               if (clt != NULL)
+                       free(clt);
+               /*
+                * the client struct was not completly set up, but still
+                * counted as an inflight client. account for this.
+                */
+               server_inflight--;
+               log_debug("%s: inflight decremented, now %d",
+                   __func__, server_inflight);
+       }
+}
+
+void
+server_close(struct client *clt, const char *msg)
+{
+       char             ibuf[128], obuf[128], *ptr = NULL;
+       struct server   *srv = clt->clt_server;
+
+       SPLAY_REMOVE(client_tree, &srv->srv_clients, clt);
+
+       event_del(&clt->clt_ev);
+       if (clt->clt_bev != NULL)
+               bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE);
+       if (clt->clt_file != NULL)
+               bufferevent_disable(clt->clt_file, EV_READ|EV_WRITE);
+
+       if ((env->sc_opts & HTTPD_OPT_LOGUPDATE) && msg != NULL) {
+               memset(&ibuf, 0, sizeof(ibuf));
+               memset(&obuf, 0, sizeof(obuf));
+               (void)print_host(&clt->clt_ss, ibuf, sizeof(ibuf));
+               (void)print_host(&srv->srv_conf.ss, obuf, sizeof(obuf));
+               if (EVBUFFER_LENGTH(clt->clt_log) &&
+                   evbuffer_add_printf(clt->clt_log, "\r\n") != -1)
+                       ptr = evbuffer_readline(clt->clt_log);
+               log_info("server %s, "
+                   "client %d (%d active), %s -> %s:%d, "
+                   "%s%s%s", srv->srv_conf.name, clt->clt_id, server_clients,
+                   ibuf, obuf, ntohs(clt->clt_port), msg,
+                   ptr == NULL ? "" : ",", ptr == NULL ? "" : ptr);
+               if (ptr != NULL)
+                       free(ptr);
+       }
+
+       if (clt->clt_bev != NULL)
+               bufferevent_free(clt->clt_bev);
+       else if (clt->clt_output != NULL)
+               evbuffer_free(clt->clt_output);
+
+       if (clt->clt_file != NULL)
+               bufferevent_free(clt->clt_file);
+       if (clt->clt_fd != -1)
+               close(clt->clt_fd);
+
+       if (clt->clt_s != -1) {
+               close(clt->clt_s);
+               if (/* XXX */ -1) {
+                       /*
+                        * the output was never connected,
+                        * thus this was an inflight client.
+                        */
+                       server_inflight--;
+                       log_debug("%s: clients inflight decremented, now %d",
+                           __func__, server_inflight);
+               }
+       }
+
+       if (clt->clt_log != NULL)
+               evbuffer_free(clt->clt_log);
+
+       free(clt);
+       server_clients--;
+}
+
+int
+server_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
+{
+       switch (imsg->hdr.type) {
+       case IMSG_CFG_SERVER:
+               config_getserver(env, imsg);
+               break;
+       case IMSG_CFG_DONE:
+               config_getcfg(env, imsg);
+               break;
+       case IMSG_CTL_START:
+               server_launch();
+               break;
+       case IMSG_CTL_RESET:
+               config_getreset(env, imsg);
+               break;
+       default:
+               return (-1);
+       }
+
+       return (0);
+}
+
+int
+server_bufferevent_add(struct event *ev, int timeout)
+{
+       struct timeval tv, *ptv = NULL;
+
+       if (timeout) {
+               timerclear(&tv);
+               tv.tv_sec = timeout;
+               ptv = &tv;
+       }
+
+       return (event_add(ev, ptv));
+}
+
+int
+server_bufferevent_print(struct client *clt, const char *str)
+{
+       if (clt->clt_bev == NULL)
+               return (evbuffer_add(clt->clt_output, str, strlen(str)));
+       return (bufferevent_write(clt->clt_bev, str, strlen(str)));
+}
+
+int
+server_bufferevent_write_buffer(struct client *clt, struct evbuffer *buf)
+{
+       if (clt->clt_bev == NULL)
+               return (evbuffer_add_buffer(clt->clt_output, buf));
+       return (bufferevent_write_buffer(clt->clt_bev, buf));
+}
+
+int
+server_bufferevent_write_chunk(struct client *clt,
+    struct evbuffer *buf, size_t size)
+{
+       int ret;
+       ret = server_bufferevent_write(clt, buf->buffer, size);
+       if (ret != -1)
+               evbuffer_drain(buf, size);
+       return (ret);
+}
+
+int
+server_bufferevent_write(struct client *clt, void *data, size_t size)
+{
+       if (clt->clt_bev == NULL)
+               return (evbuffer_add(clt->clt_output, data, size));
+       return (bufferevent_write(clt->clt_bev, data, size));
+}
+
+int
+server_client_cmp(struct client *a, struct client *b)
+{
+       return ((int)a->clt_id - b->clt_id);
+}
+
+SPLAY_GENERATE(client_tree, client, clt_nodes, server_client_cmp);
diff --git a/usr.sbin/httpd/server_file.c b/usr.sbin/httpd/server_file.c
new file mode 100644 (file)
index 0000000..d9920df
--- /dev/null
@@ -0,0 +1,126 @@
+/*     $OpenBSD: server_file.c,v 1.1 2014/07/12 23:34:54 reyk Exp $    */
+
+/*
+ * Copyright (c) 2006 - 2014 Reyk Floeter <reyk@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/tree.h>
+#include <sys/hash.h>
+
+#include <net/if.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <err.h>
+#include <event.h>
+
+#include <openssl/ssl.h>
+
+#include "httpd.h"
+#include "http.h"
+
+int
+server_response(struct client *clt)
+{
+       struct http_descriptor  *desc = clt->clt_desc;
+       struct server           *srv = clt->clt_server;
+       struct kv               *kv;
+       const char              *errstr = NULL;
+       int                      fd = -1;
+       char                     path[MAXPATHLEN];
+       struct stat              st;
+
+       if (desc->http_path == NULL)
+               goto fail;
+
+       /* 
+        * XXX This is not ready XXX
+        * XXX Don't expect anything from this code yet,
+        */
+
+       strlcpy(path, "/htdocs", sizeof(path));
+       if (desc->http_path[0] != '/')
+               strlcat(path, "/", sizeof(path));
+       strlcat(path, desc->http_path, sizeof(path));
+       if (desc->http_path[strlen(desc->http_path) - 1] == '/')
+               strlcat(path, "index.html", sizeof(path));
+
+       if (access(path, R_OK) == -1) {
+               switch (errno) {
+               case EACCES:
+                       server_abort_http(clt, 403, path);
+                       break;
+               case ENOENT:
+                       server_abort_http(clt, 404, path);
+                       break;
+               default:
+                       server_abort_http(clt, 500, path);
+                       break;
+               }
+               return (-1);
+       }
+
+       if ((fd = open(path, O_RDONLY)) == -1 || fstat(fd, &st) == -1)
+               goto fail;
+
+       /* XXX verify results XXX */
+       kv_purge(&desc->http_headers);
+       kv_add(&desc->http_headers, "Server", HTTPD_SERVERNAME);
+       kv_add(&desc->http_headers, "Connection", "close");
+       if ((kv = kv_add(&desc->http_headers, "Content-Length", NULL)) != NULL)
+               kv_set(kv, "%ld", st.st_size);
+       kv_setkey(&desc->http_pathquery, "200");
+       kv_set(&desc->http_pathquery, "%s", server_httperror_byid(200));
+
+       if (server_writeresponse_http(clt) == -1 ||
+           server_bufferevent_print(clt, "\r\n") == -1 ||
+           server_writeheader_http(clt) == -1 ||
+           server_bufferevent_print(clt, "\r\n") == -1)
+               goto fail;
+
+       clt->clt_fd = fd;
+       clt->clt_file = bufferevent_new(clt->clt_fd, server_read,
+           server_write, server_error, clt);
+       if (clt->clt_file == NULL) {
+               errstr = "failed to allocate file buffer event";
+               goto fail;
+       }
+
+       bufferevent_settimeout(clt->clt_file,
+           srv->srv_conf.timeout.tv_sec, srv->srv_conf.timeout.tv_sec);
+       bufferevent_enable(clt->clt_file, EV_READ|EV_WRITE);
+
+       return (0);
+ fail:
+       if (errstr == NULL)
+               errstr = strerror(errno);
+       server_abort_http(clt, 500, errstr);
+       return (-1);
+}
diff --git a/usr.sbin/httpd/server_http.c b/usr.sbin/httpd/server_http.c
new file mode 100644 (file)
index 0000000..bf5dcb7
--- /dev/null
@@ -0,0 +1,735 @@
+/*     $OpenBSD: server_http.c,v 1.1 2014/07/12 23:34:54 reyk Exp $    */
+
+/*
+ * Copyright (c) 2006 - 2014 Reyk Floeter <reyk@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/tree.h>
+#include <sys/hash.h>
+
+#include <net/if.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <err.h>
+#include <pwd.h>
+#include <event.h>
+#include <fnmatch.h>
+
+#include <openssl/ssl.h>
+
+#include "httpd.h"
+#include "http.h"
+
+static int      server_httpmethod_cmp(const void *, const void *);
+static int      server_httperror_cmp(const void *, const void *);
+void            server_httpdesc_free(struct http_descriptor *);
+
+static struct httpd    *env = NULL;
+
+static struct http_method       http_methods[] = HTTP_METHODS;
+static struct http_error        http_errors[] = HTTP_ERRORS;
+
+void
+server_http(struct httpd *x_env)
+{
+       if (x_env != NULL)
+               env = x_env;
+
+       DPRINTF("%s: sorting lookup tables, pid %d", __func__, getpid());
+
+       /* Sort the HTTP lookup arrays */
+       qsort(http_methods, sizeof(http_methods) /
+           sizeof(http_methods[0]) - 1,
+           sizeof(http_methods[0]), server_httpmethod_cmp);
+       qsort(http_errors, sizeof(http_errors) /
+           sizeof(http_errors[0]) - 1,
+           sizeof(http_errors[0]), server_httperror_cmp);
+}
+
+void
+server_http_init(struct server *srv)
+{
+       /* nothing */   
+}
+
+int
+server_httpdesc_init(struct client *clt)
+{
+       struct http_descriptor  *desc;
+
+       if ((desc = calloc(1, sizeof(*desc))) == NULL)
+               return (-1);
+
+       RB_INIT(&desc->http_headers);
+       clt->clt_desc = desc;
+
+       return (0);
+}
+
+void
+server_httpdesc_free(struct http_descriptor *desc)
+{
+       if (desc->http_path != NULL) {
+               free(desc->http_path);
+               desc->http_path = NULL;
+       }
+       if (desc->http_query != NULL) {
+               free(desc->http_query);
+               desc->http_query = NULL;
+       }
+       if (desc->http_version != NULL) {
+               free(desc->http_version);
+               desc->http_version = NULL;
+       }
+       kv_purge(&desc->http_headers);
+}
+
+void
+server_read_http(struct bufferevent *bev, void *arg)
+{
+       struct client           *clt = arg;
+       struct http_descriptor  *desc = clt->clt_desc;
+       struct evbuffer         *src = EVBUFFER_INPUT(bev);
+       char                    *line = NULL, *key, *value;
+       const char              *errstr;
+       size_t                   size, linelen;
+       struct kv               *hdr = NULL;
+
+       getmonotime(&clt->clt_tv_last);
+
+       size = EVBUFFER_LENGTH(src);
+       DPRINTF("%s: session %d: size %lu, to read %lld",
+           __func__, clt->clt_id, size, clt->clt_toread);
+       if (!size) {
+               clt->clt_toread = TOREAD_HTTP_HEADER;
+               goto done;
+       }
+
+       while (!clt->clt_done && (line = evbuffer_readline(src)) != NULL) {
+               linelen = strlen(line);
+
+               /*
+                * An empty line indicates the end of the request.
+                * libevent already stripped the \r\n for us.
+                */
+               if (!linelen) {
+                       clt->clt_done = 1;
+                       free(line);
+                       break;
+               }
+               key = line;
+
+               /* Limit the total header length minus \r\n */
+               clt->clt_headerlen += linelen;
+               if (clt->clt_headerlen > SERVER_MAXHEADERLENGTH) {
+                       free(line);
+                       server_abort_http(clt, 413, "request too large");
+                       return;
+               }
+
+               /*
+                * The first line is the GET/POST/PUT/... request,
+                * subsequent lines are HTTP headers.
+                */
+               if (++clt->clt_line == 1)
+                       value = strchr(key, ' ');
+               else if (*key == ' ' || *key == '\t')
+                       /* Multiline headers wrap with a space or tab */
+                       value = NULL;
+               else
+                       value = strchr(key, ':');
+               if (value == NULL) {
+                       if (clt->clt_line == 1) {
+                               free(line);
+                               server_abort_http(clt, 400, "malformed");
+                               return;
+                       }
+
+                       /* Append line to the last header, if present */
+                       if (kv_extend(&desc->http_headers,
+                           desc->http_lastheader, line) == NULL) {
+                               free(line);
+                               goto fail;
+                       }
+
+                       free(line);
+                       continue;
+               }
+               if (*value == ':') {
+                       *value++ = '\0';
+                       value += strspn(value, " \t\r\n");
+               } else {
+                       *value++ = '\0';
+               }
+
+               DPRINTF("%s: session %d: header '%s: %s'", __func__,
+                   clt->clt_id, key, value);
+
+               /*
+                * Identify and handle specific HTTP request methods
+                */
+               if (clt->clt_line == 1) {
+                       if ((desc->http_method = server_httpmethod_byname(key))
+                           == HTTP_METHOD_NONE)
+                               goto fail;
+
+                       /*
+                        * Decode request path and query
+                        */
+                       desc->http_path = strdup(value);
+                       if (desc->http_path == NULL) {
+                               free(line);
+                               goto fail;
+                       }
+                       desc->http_version = strchr(desc->http_path, ' ');
+                       if (desc->http_version != NULL)
+                               *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.
+                        */
+                       if (desc->http_version != NULL &&
+                           (desc->http_version =
+                           strdup(desc->http_version)) == NULL) {
+                               free(line);
+                               goto fail;
+                       }
+                       if (desc->http_query != NULL &&
+                           (desc->http_query =
+                           strdup(desc->http_query)) == NULL) {
+                               free(line);
+                               goto fail;
+                       }
+               } else if (desc->http_method != HTTP_METHOD_NONE &&
+                   strcasecmp("Content-Length", key) == 0) {
+                       if (desc->http_method == HTTP_METHOD_TRACE ||
+                           desc->http_method == HTTP_METHOD_CONNECT) {
+                               /*
+                                * These method should not have a body
+                                * and thus no Content-Length header.
+                                */
+                               server_abort_http(clt, 400, "malformed");
+                               goto abort;
+                       }
+
+                       /*
+                        * Need to read data from the client after the
+                        * HTTP header.
+                        * XXX What about non-standard clients not using
+                        * the carriage return? And some browsers seem to
+                        * include the line length in the content-length.
+                        */
+                       clt->clt_toread = strtonum(value, 0, LLONG_MAX,
+                           &errstr);
+                       if (errstr) {
+                               server_abort_http(clt, 500, errstr);
+                               goto abort;
+                       }
+               }
+
+               if (strcasecmp("Transfer-Encoding", key) == 0 &&
+                   strcasecmp("chunked", value) == 0)
+                       desc->http_chunked = 1;
+
+               if (clt->clt_line != 1) {
+                       if ((hdr = kv_add(&desc->http_headers, key,
+                           value)) == NULL) {
+                               free(line);
+                               goto fail;
+                       }
+                       desc->http_lastheader = hdr;
+               }
+
+               free(line);
+       }
+       if (clt->clt_done) {
+               if (desc->http_method == HTTP_METHOD_NONE) {
+                       server_abort_http(clt, 406, "no method");
+                       return;
+               }
+
+               switch (desc->http_method) {
+               case HTTP_METHOD_CONNECT:
+                       /* Data stream */
+                       clt->clt_toread = TOREAD_UNLIMITED;
+                       bev->readcb = server_read;
+                       break;
+               case HTTP_METHOD_DELETE:
+               case HTTP_METHOD_GET:
+               case HTTP_METHOD_HEAD:
+               case HTTP_METHOD_OPTIONS:
+                       clt->clt_toread = 0;
+                       break;
+               case HTTP_METHOD_POST:
+               case HTTP_METHOD_PUT:
+               case HTTP_METHOD_RESPONSE:
+                       /* HTTP request payload */
+                       if (clt->clt_toread > 0)
+                               bev->readcb = server_read_httpcontent;
+
+                       /* Single-pass HTTP body */
+                       if (clt->clt_toread < 0) {
+                               clt->clt_toread = TOREAD_UNLIMITED;
+                               bev->readcb = server_read;
+                       }
+                       break;
+               default:
+                       /* HTTP handler */
+                       clt->clt_toread = TOREAD_HTTP_HEADER;
+                       bev->readcb = server_read_http;
+                       break;
+               }
+               if (desc->http_chunked) {
+                       /* Chunked transfer encoding */
+                       clt->clt_toread = TOREAD_HTTP_CHUNK_LENGTH;
+                       bev->readcb = server_read_httpchunks;
+               }
+
+ done:
+               if (clt->clt_toread <= 0) {
+                       if (server_response(clt) == -1)
+                               return;
+               }
+
+               server_reset_http(clt);
+       }
+       if (clt->clt_done) {
+               server_close(clt, "done");
+               return;
+       }
+       if (EVBUFFER_LENGTH(src) && bev->readcb != server_read_http)
+               bev->readcb(bev, arg);
+       bufferevent_enable(bev, EV_READ);
+       return;
+ fail:
+       server_abort_http(clt, 500, strerror(errno));
+       return;
+ abort:
+       free(line);
+}
+
+void
+server_read_httpcontent(struct bufferevent *bev, void *arg)
+{
+       struct client           *clt = arg;
+       struct evbuffer         *src = EVBUFFER_INPUT(bev);
+       size_t                   size;
+
+       getmonotime(&clt->clt_tv_last);
+
+       size = EVBUFFER_LENGTH(src);
+       DPRINTF("%s: session %d: size %lu, to read %lld", __func__,
+           clt->clt_id, size, clt->clt_toread);
+       if (!size)
+               return;
+
+       if (clt->clt_toread > 0) {
+               /* Read content data */
+               if ((off_t)size > clt->clt_toread) {
+                       size = clt->clt_toread;
+                       if (server_bufferevent_write_chunk(clt,
+                           src, size) == -1)
+                               goto fail;
+                       clt->clt_toread = 0;
+               } else {
+                       if (server_bufferevent_write_buffer(clt, src) == -1)
+                               goto fail;
+                       clt->clt_toread -= size;
+               }
+               DPRINTF("%s: done, size %lu, to read %lld", __func__,
+                   size, clt->clt_toread);
+       }
+       if (clt->clt_toread == 0) {
+               clt->clt_toread = TOREAD_HTTP_HEADER;
+               bev->readcb = server_read_http;
+       }
+       if (clt->clt_done)
+               goto done;
+       if (bev->readcb != server_read_httpcontent)
+               bev->readcb(bev, arg);
+       bufferevent_enable(bev, EV_READ);
+       return;
+ done:
+       server_close(clt, "last http content read");
+       return;
+ fail:
+       server_close(clt, strerror(errno));
+}
+
+void
+server_read_httpchunks(struct bufferevent *bev, void *arg)
+{
+       struct client           *clt = arg;
+       struct evbuffer         *src = EVBUFFER_INPUT(bev);
+       char                    *line;
+       long long                llval;
+       size_t                   size;
+
+       getmonotime(&clt->clt_tv_last);
+
+       size = EVBUFFER_LENGTH(src);
+       DPRINTF("%s: session %d: size %lu, to read %lld", __func__,
+           clt->clt_id, size, clt->clt_toread);
+       if (!size)
+               return;
+
+       if (clt->clt_toread > 0) {
+               /* Read chunk data */
+               if ((off_t)size > clt->clt_toread) {
+                       size = clt->clt_toread;
+                       if (server_bufferevent_write_chunk(clt, src, size)
+                           == -1)
+                               goto fail;
+                       clt->clt_toread = 0;
+               } else {
+                       if (server_bufferevent_write_buffer(clt, src) == -1)
+                               goto fail;
+                       clt->clt_toread -= size;
+               }
+               DPRINTF("%s: done, size %lu, to read %lld", __func__,
+                   size, clt->clt_toread);
+       }
+       switch (clt->clt_toread) {
+       case TOREAD_HTTP_CHUNK_LENGTH:
+               line = evbuffer_readline(src);
+               if (line == NULL) {
+                       /* Ignore empty line, continue */
+                       bufferevent_enable(bev, EV_READ);
+                       return;
+               }
+               if (strlen(line) == 0) {
+                       free(line);
+                       goto next;
+               }
+
+               /*
+                * Read prepended chunk size in hex, ignore the trailer.
+                * The returned signed value must not be negative.
+                */
+               if (sscanf(line, "%llx", &llval) != 1 || llval < 0) {
+                       free(line);
+                       server_close(clt, "invalid chunk size");
+                       return;
+               }
+
+               if (server_bufferevent_print(clt, line) == -1 ||
+                   server_bufferevent_print(clt, "\r\n") == -1) {
+                       free(line);
+                       goto fail;
+               }
+               free(line);
+
+               if ((clt->clt_toread = llval) == 0) {
+                       DPRINTF("%s: last chunk", __func__);
+                       clt->clt_toread = TOREAD_HTTP_CHUNK_TRAILER;
+               }
+               break;
+       case TOREAD_HTTP_CHUNK_TRAILER:
+               /* Last chunk is 0 bytes followed by trailer and empty line */
+               line = evbuffer_readline(src);
+               if (line == NULL) {
+                       /* Ignore empty line, continue */
+                       bufferevent_enable(bev, EV_READ);
+                       return;
+               }
+               if (server_bufferevent_print(clt, line) == -1 ||
+                   server_bufferevent_print(clt, "\r\n") == -1) {
+                       free(line);
+                       goto fail;
+               }
+               if (strlen(line) == 0) {
+                       /* Switch to HTTP header mode */
+                       clt->clt_toread = TOREAD_HTTP_HEADER;
+                       bev->readcb = server_read_http;
+               }
+               free(line);
+               break;
+       case 0:
+               /* Chunk is terminated by an empty newline */
+               line = evbuffer_readline(src);
+               if (line != NULL)
+                       free(line);
+               if (server_bufferevent_print(clt, "\r\n") == -1)
+                       goto fail;
+               clt->clt_toread = TOREAD_HTTP_CHUNK_LENGTH;
+               break;
+       }
+
+ next:
+       if (clt->clt_done)
+               goto done;
+       if (EVBUFFER_LENGTH(src))
+               bev->readcb(bev, arg);
+       bufferevent_enable(bev, EV_READ);
+       return;
+
+ done:
+       server_close(clt, "last http chunk read (done)");
+       return;
+ fail:
+       server_close(clt, strerror(errno));
+}
+
+void
+server_reset_http(struct client *clt)
+{
+       struct http_descriptor  *desc = clt->clt_desc;
+
+       server_httpdesc_free(desc);
+       desc->http_method = 0;
+       desc->http_chunked = 0;
+       clt->clt_headerlen = 0;
+       clt->clt_line = 0;
+       clt->clt_done = 0;
+}
+
+void
+server_abort_http(struct client *clt, u_int code, const char *msg)
+{
+       struct server           *srv = clt->clt_server;
+       struct bufferevent      *bev = clt->clt_bev;
+       const char              *httperr = NULL, *text = "";
+       char                    *httpmsg;
+       time_t                   t;
+       struct tm               *lt;
+       char                     tmbuf[32], hbuf[128];
+       const char              *style;
+
+       if ((httperr = server_httperror_byid(code)) == NULL)
+               httperr = "Unknown Error";
+
+       if (bev == NULL)
+               goto done;
+
+       /* Some system information */
+       if (print_host(&srv->srv_conf.ss, hbuf, sizeof(hbuf)) == NULL)
+               goto done;
+
+       /* RFC 2616 "tolerates" asctime() */
+       time(&t);
+       lt = localtime(&t);
+       tmbuf[0] = '\0';
+       if (asctime_r(lt, tmbuf) != NULL)
+               tmbuf[strlen(tmbuf) - 1] = '\0';        /* skip final '\n' */
+
+       /* Do not send details of the Internal Server Error */
+       if (code != 500)
+               text = msg;
+
+       /* A CSS stylesheet allows minimal customization by the user */
+       style = "body { background-color: white; color: black; }";
+
+       /* Generate simple HTTP+HTML error document */
+       if (asprintf(&httpmsg,
+           "HTTP/1.0 %03d %s\r\n"
+           "Date: %s\r\n"
+           "Server: %s\r\n"
+           "Connection: close\r\n"
+           "Content-Type: text/html\r\n"
+           "\r\n"
+           "<!DOCTYPE HTML PUBLIC "
+           "\"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"
+           "<html>\n"
+           "<head>\n"
+           "<title>%03d %s</title>\n"
+           "<style type=\"text/css\"><!--\n%s\n--></style>\n"
+           "</head>\n"
+           "<body>\n"
+           "<h1>%s</h1>\n"
+           "<div id='m'>%s</div>\n"
+           "<hr><address>%s at %s port %d</address>\n"
+           "</body>\n"
+           "</html>\n",
+           code, httperr, tmbuf, HTTPD_SERVERNAME,
+           code, httperr, style, httperr, text,
+           HTTPD_SERVERNAME, hbuf, ntohs(srv->srv_conf.port)) == -1)
+               goto done;
+
+       /* Dump the message without checking for success */
+       server_dump(clt, httpmsg, strlen(httpmsg));
+       free(httpmsg);
+
+ done:
+       if (asprintf(&httpmsg, "%s (%03d %s)", msg, code, httperr) == -1)
+               server_close(clt, msg);
+       else {
+               server_close(clt, httpmsg);
+               free(httpmsg);
+       }
+}
+
+void
+server_close_http(struct client *clt)
+{
+       struct http_descriptor *desc    = clt->clt_desc;
+
+       if (desc == NULL)
+               return;
+       server_httpdesc_free(desc);
+       free(desc);
+}
+
+int
+server_writeresponse_http(struct client *clt)
+{
+       struct http_descriptor  *desc = (struct http_descriptor *)clt->clt_desc;
+
+       DPRINTF("version: %s rescode: %s resmsg: %s", desc->http_version,
+           desc->http_rescode, desc->http_resmesg);
+
+       if (server_bufferevent_print(clt, desc->http_version) == -1 ||
+           server_bufferevent_print(clt, " ") == -1 ||
+           server_bufferevent_print(clt, desc->http_rescode) == -1 ||
+           server_bufferevent_print(clt, " ") == -1 ||
+           server_bufferevent_print(clt, desc->http_resmesg) == -1)
+               return (-1);
+
+       return (0);
+}
+
+int
+server_writeheader_kv(struct client *clt, struct kv *hdr)
+{
+       char                    *ptr;
+       const char              *key;
+
+       if (hdr->kv_flags & KV_FLAG_INVALID)
+               return (0);
+
+       /* The key might have been updated in the parent */
+       if (hdr->kv_parent != NULL && hdr->kv_parent->kv_key != NULL)
+               key = hdr->kv_parent->kv_key;
+       else
+               key = hdr->kv_key;
+
+       ptr = hdr->kv_value;
+       DPRINTF("%s: ptr %s", __func__, ptr);
+       if (server_bufferevent_print(clt, key) == -1 ||
+           (ptr != NULL &&
+           (server_bufferevent_print(clt, ": ") == -1 ||
+           server_bufferevent_print(clt, ptr) == -1 ||
+           server_bufferevent_print(clt, "\r\n") == -1)))
+               return (-1);
+       DPRINTF("%s: %s: %s", __func__, key,
+           hdr->kv_value == NULL ? "" : hdr->kv_value);
+
+       return (0);
+}
+
+int
+server_writeheader_http(struct client *clt)
+{
+       struct kv               *hdr, *kv;
+       struct http_descriptor  *desc = (struct http_descriptor *)clt->clt_desc;
+
+       RB_FOREACH(hdr, kvtree, &desc->http_headers) {
+               if (server_writeheader_kv(clt, hdr) == -1)
+                       return (-1);
+               TAILQ_FOREACH(kv, &hdr->kv_children, kv_entry) {
+                       if (server_writeheader_kv(clt, kv) == -1)
+                               return (-1);
+               }
+       }
+
+       return (0);
+}
+
+enum httpmethod
+server_httpmethod_byname(const char *name)
+{
+       enum httpmethod          id = HTTP_METHOD_NONE;
+       struct http_method       method, *res = NULL;
+
+       /* Set up key */
+       method.method_name = name;
+
+       /*
+        * RFC 2616 section 5.1.1 says that the method is case
+        * sensitive so we don't do a strcasecmp here.
+        */
+       if ((res = bsearch(&method, http_methods,
+           sizeof(http_methods) / sizeof(http_methods[0]) - 1,
+           sizeof(http_methods[0]), server_httpmethod_cmp)) != NULL)
+               id = res->method_id;
+
+       return (id);
+}
+
+const char *
+server_httpmethod_byid(u_int id)
+{
+       const char      *name = NULL;
+       int              i;
+
+       for (i = 0; http_methods[i].method_name != NULL; i++) {
+               if (http_methods[i].method_id == id) {
+                       name = http_methods[i].method_name;
+                       break;
+               }
+       }
+
+       return (name);
+}
+
+static int
+server_httpmethod_cmp(const void *a, const void *b)
+{
+       const struct http_method *ma = a;
+       const struct http_method *mb = b;
+       return (strcmp(ma->method_name, mb->method_name));
+}
+
+const char *
+server_httperror_byid(u_int id)
+{
+       struct http_error        error, *res = NULL;
+
+       /* Set up key */
+       error.error_code = (int)id;
+
+       res = bsearch(&error, http_errors,
+           sizeof(http_errors) / sizeof(http_errors[0]) - 1,
+           sizeof(http_errors[0]), server_httperror_cmp);
+
+       return (res->error_name);
+}
+
+static int
+server_httperror_cmp(const void *a, const void *b)
+{
+       const struct http_error *ea = a;
+       const struct http_error *eb = b;
+       return (ea->error_code - eb->error_code);
+}