aboutsummaryrefslogtreecommitdiff
path: root/httpd
diff options
context:
space:
mode:
authorReyk Floeter <reyk@esdenera.com>2015-03-07 21:15:37 +0100
committerReyk Floeter <reyk@esdenera.com>2015-03-07 21:15:37 +0100
commit7fdb196d97b9af7ed5e238ce8dca1ed5ad21ebbe (patch)
tree580763c74a99af82056996f27fa2277554b13d71 /httpd
parentfa2f6dd5e2c33c291f059a90713e585abfc310ef (diff)
downloadhttpd-7fdb196d97b9af7ed5e238ce8dca1ed5ad21ebbe.tar.gz
httpd-7fdb196d97b9af7ed5e238ce8dca1ed5ad21ebbe.zip
Move files the git repository
Diffstat (limited to 'httpd')
-rw-r--r--httpd/Makefile19
-rw-r--r--httpd/config.c589
-rw-r--r--httpd/control.c334
-rw-r--r--httpd/http.h253
-rw-r--r--httpd/httpd.8102
-rw-r--r--httpd/httpd.c1281
-rw-r--r--httpd/httpd.conf.5533
-rw-r--r--httpd/httpd.h688
-rw-r--r--httpd/log.c242
-rw-r--r--httpd/logger.c312
-rw-r--r--httpd/parse.y2062
-rw-r--r--httpd/proc.c622
-rw-r--r--httpd/server.c1221
-rw-r--r--httpd/server_fcgi.c729
-rw-r--r--httpd/server_file.c469
-rw-r--r--httpd/server_http.c1425
16 files changed, 10881 insertions, 0 deletions
diff --git a/httpd/Makefile b/httpd/Makefile
new file mode 100644
index 0000000..885ad42
--- /dev/null
+++ b/httpd/Makefile
@@ -0,0 +1,19 @@
+# $OpenBSD: Makefile,v 1.27 2015/02/23 10:39:10 reyk Exp $
+
+PROG= httpd
+SRCS= parse.y
+SRCS+= config.c control.c httpd.c log.c logger.c proc.c
+SRCS+= server.c server_http.c server_file.c server_fcgi.c
+MAN= httpd.8 httpd.conf.5
+
+LDADD= -levent -ltls -lssl -lcrypto -lutil
+DPADD= ${LIBEVENT} ${LIBTLS} ${LIBSSL} ${LIBCRYPTO} ${LIBUTIL}
+#DEBUG= -g -DDEBUG=3 -O0
+CFLAGS+= -Wall -I${.CURDIR}
+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/httpd/config.c b/httpd/config.c
new file mode 100644
index 0000000..2dc9159
--- /dev/null
+++ b/httpd/config.c
@@ -0,0 +1,589 @@
+/* $OpenBSD: config.c,v 1.36 2015/02/23 11:48:41 reyk Exp $ */
+
+/*
+ * Copyright (c) 2011 - 2015 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/tree.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <imsg.h>
+
+#include "httpd.h"
+
+int config_getserver_config(struct httpd *, struct server *,
+ struct imsg *);
+int config_getserver_auth(struct httpd *, struct server_config *);
+
+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|CONFIG_MEDIA|CONFIG_AUTH;
+ ps->ps_what[PROC_LOGGER] = 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);
+ }
+
+ if (what & CONFIG_MEDIA) {
+ if ((env->sc_mediatypes =
+ calloc(1, sizeof(*env->sc_mediatypes))) == NULL)
+ return (-1);
+ RB_INIT(env->sc_mediatypes);
+ }
+
+ if (what & CONFIG_AUTH) {
+ if ((env->sc_auth =
+ calloc(1, sizeof(*env->sc_auth))) == NULL)
+ return (-1);
+ TAILQ_INIT(env->sc_auth);
+ }
+
+ return (0);
+}
+
+void
+config_purge(struct httpd *env, u_int reset)
+{
+ struct privsep *ps = env->sc_ps;
+ struct server *srv;
+ struct auth *auth;
+ 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)
+ server_purge(srv);
+ }
+
+ if (what & CONFIG_MEDIA && env->sc_mediatypes != NULL)
+ media_purge(env->sc_mediatypes);
+
+ if (what & CONFIG_AUTH && env->sc_auth != NULL) {
+ while ((auth = TAILQ_FIRST(env->sc_auth)) != NULL) {
+ auth_free(env->sc_auth, auth);
+ free(auth);
+ }
+ }
+}
+
+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 %s \"%s[%u]\" to %s fd %d", __func__,
+ (srv->srv_conf.flags & SRVFLAG_LOCATION) ?
+ "location" : "server",
+ srv->srv_conf.name, srv->srv_conf.id,
+ 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 (srv->srv_conf.return_uri_len != 0) {
+ iov[c].iov_base = srv->srv_conf.return_uri;
+ iov[c++].iov_len = srv->srv_conf.return_uri_len;
+ }
+ if (srv->srv_conf.tls_cert_len != 0) {
+ iov[c].iov_base = srv->srv_conf.tls_cert;
+ iov[c++].iov_len = srv->srv_conf.tls_cert_len;
+ }
+ if (srv->srv_conf.tls_key_len != 0) {
+ iov[c].iov_base = srv->srv_conf.tls_key;
+ iov[c++].iov_len = srv->srv_conf.tls_key_len;
+ }
+
+ if (id == PROC_SERVER &&
+ (srv->srv_conf.flags & SRVFLAG_LOCATION) == 0) {
+ /* 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 (srv->srv_s == -1)
+ fd = -1;
+ else 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);
+ }
+ }
+
+ return (0);
+}
+
+int
+config_getserver_auth(struct httpd *env, struct server_config *srv_conf)
+{
+ struct privsep *ps = env->sc_ps;
+
+ if ((ps->ps_what[privsep_process] & CONFIG_AUTH) == 0 ||
+ (srv_conf->flags & SRVFLAG_AUTH) == 0)
+ return (0);
+
+ if ((srv_conf->auth = auth_byid(env->sc_auth,
+ srv_conf->auth_id)) == NULL)
+ return (-1);
+
+ return (0);
+}
+
+int
+config_getserver_config(struct httpd *env, struct server *srv,
+ struct imsg *imsg)
+{
+#ifdef DEBUG
+ struct privsep *ps = env->sc_ps;
+#endif
+ struct server_config *srv_conf, *parent;
+ u_int8_t *p = imsg->data;
+ u_int f;
+ size_t s;
+
+ if ((srv_conf = calloc(1, sizeof(*srv_conf))) == NULL)
+ return (-1);
+
+ IMSG_SIZE_CHECK(imsg, srv_conf);
+ memcpy(srv_conf, p, sizeof(*srv_conf));
+ s = sizeof(*srv_conf);
+
+ /* Reset these variables to avoid free'ing invalid pointers */
+ serverconfig_reset(srv_conf);
+
+ TAILQ_FOREACH(parent, &srv->srv_hosts, entry) {
+ if (strcmp(parent->name, srv_conf->name) == 0)
+ break;
+ }
+ if (parent == NULL)
+ parent = &srv->srv_conf;
+
+ if (config_getserver_auth(env, srv_conf) != 0)
+ goto fail;
+
+ /*
+ * Get variable-length values for the virtual host. The tls_* ones
+ * aren't needed in the virtual hosts unless we implement SNI.
+ */
+ if (srv_conf->return_uri_len != 0) {
+ if ((srv_conf->return_uri = get_data(p + s,
+ srv_conf->return_uri_len)) == NULL)
+ goto fail;
+ s += srv_conf->return_uri_len;
+ }
+
+ if (srv_conf->flags & SRVFLAG_LOCATION) {
+ /* Inherit configuration from the parent */
+ f = SRVFLAG_INDEX|SRVFLAG_NO_INDEX;
+ if ((srv_conf->flags & f) == 0) {
+ srv_conf->flags |= parent->flags & f;
+ (void)strlcpy(srv_conf->index, parent->index,
+ sizeof(srv_conf->index));
+ }
+
+ f = SRVFLAG_AUTO_INDEX|SRVFLAG_NO_AUTO_INDEX;
+ if ((srv_conf->flags & f) == 0)
+ srv_conf->flags |= parent->flags & f;
+
+ f = SRVFLAG_SOCKET|SRVFLAG_FCGI;
+ if ((srv_conf->flags & f) == SRVFLAG_FCGI) {
+ srv_conf->flags |= f;
+ (void)strlcpy(srv_conf->socket, HTTPD_FCGI_SOCKET,
+ sizeof(srv_conf->socket));
+ }
+
+ f = SRVFLAG_ROOT;
+ if ((srv_conf->flags & f) == 0) {
+ srv_conf->flags |= parent->flags & f;
+ (void)strlcpy(srv_conf->root, parent->root,
+ sizeof(srv_conf->root));
+ }
+
+ f = SRVFLAG_FCGI|SRVFLAG_NO_FCGI;
+ if ((srv_conf->flags & f) == 0)
+ srv_conf->flags |= parent->flags & f;
+
+ f = SRVFLAG_LOG|SRVFLAG_NO_LOG;
+ if ((srv_conf->flags & f) == 0) {
+ srv_conf->flags |= parent->flags & f;
+ srv_conf->logformat = parent->logformat;
+ }
+
+ f = SRVFLAG_SYSLOG|SRVFLAG_NO_SYSLOG;
+ if ((srv_conf->flags & f) == 0)
+ srv_conf->flags |= parent->flags & f;
+
+ f = SRVFLAG_AUTH|SRVFLAG_NO_AUTH;
+ if ((srv_conf->flags & f) == 0) {
+ srv_conf->flags |= parent->flags & f;
+ srv_conf->auth = parent->auth;
+ srv_conf->auth_id = parent->auth_id;
+ (void)strlcpy(srv_conf->auth_realm,
+ parent->auth_realm,
+ sizeof(srv_conf->auth_realm));
+ }
+
+ f = SRVFLAG_TLS;
+ srv_conf->flags |= parent->flags & f;
+
+ f = SRVFLAG_ACCESS_LOG;
+ if ((srv_conf->flags & f) == 0) {
+ srv_conf->flags |= parent->flags & f;
+ (void)strlcpy(srv_conf->accesslog,
+ parent->accesslog,
+ sizeof(srv_conf->accesslog));
+ }
+
+ f = SRVFLAG_ERROR_LOG;
+ if ((srv_conf->flags & f) == 0) {
+ srv_conf->flags |= parent->flags & f;
+ (void)strlcpy(srv_conf->errorlog,
+ parent->errorlog,
+ sizeof(srv_conf->errorlog));
+ }
+
+ f = SRVFLAG_BLOCK|SRVFLAG_NO_BLOCK;
+ if ((srv_conf->flags & f) == 0) {
+ free(srv_conf->return_uri);
+ srv_conf->flags |= parent->flags & f;
+ srv_conf->return_code = parent->return_code;
+ srv_conf->return_uri_len = parent->return_uri_len;
+ if (srv_conf->return_uri_len &&
+ (srv_conf->return_uri =
+ strdup(parent->return_uri)) == NULL)
+ goto fail;
+ }
+
+ memcpy(&srv_conf->timeout, &parent->timeout,
+ sizeof(srv_conf->timeout));
+ srv_conf->maxrequests = parent->maxrequests;
+ srv_conf->maxrequestbody = parent->maxrequestbody;
+
+ DPRINTF("%s: %s %d location \"%s\", "
+ "parent \"%s[%u]\", flags: %s",
+ __func__, ps->ps_title[privsep_process], ps->ps_instance,
+ srv_conf->location, parent->name, parent->id,
+ printb_flags(srv_conf->flags, SRVFLAG_BITS));
+ } else {
+ /* Add a new "virtual" server */
+ DPRINTF("%s: %s %d server \"%s[%u]\", parent \"%s[%u]\", "
+ "flags: %s", __func__,
+ ps->ps_title[privsep_process], ps->ps_instance,
+ srv_conf->name, srv_conf->id, parent->name, parent->id,
+ printb_flags(srv_conf->flags, SRVFLAG_BITS));
+ }
+
+ TAILQ_INSERT_TAIL(&srv->srv_hosts, srv_conf, entry);
+
+ return (0);
+
+ fail:
+ serverconfig_free(srv_conf);
+ free(srv_conf);
+ return (-1);
+}
+
+int
+config_getserver(struct httpd *env, struct imsg *imsg)
+{
+#ifdef DEBUG
+ struct privsep *ps = env->sc_ps;
+#endif
+ struct server *srv = NULL;
+ struct server_config srv_conf;
+ u_int8_t *p = imsg->data;
+ size_t s;
+
+ IMSG_SIZE_CHECK(imsg, &srv_conf);
+ memcpy(&srv_conf, p, sizeof(srv_conf));
+ s = sizeof(srv_conf);
+
+ /* Reset these variables to avoid free'ing invalid pointers */
+ serverconfig_reset(&srv_conf);
+
+ if ((IMSG_DATA_SIZE(imsg) - s) <
+ (srv_conf.tls_cert_len + srv_conf.tls_key_len +
+ srv_conf.return_uri_len)) {
+ log_debug("%s: invalid message length", __func__);
+ goto fail;
+ }
+
+ /* Check if server with matching listening socket already exists */
+ if ((srv = server_byaddr((struct sockaddr *)
+ &srv_conf.ss, srv_conf.port)) != NULL) {
+ /* Add "host" to existing listening server */
+ if (imsg->fd != -1) {
+ if (srv->srv_s == -1)
+ srv->srv_s = imsg->fd;
+ else
+ close(imsg->fd);
+ }
+ return (config_getserver_config(env, srv, imsg));
+ }
+
+ if (srv_conf.flags & SRVFLAG_LOCATION)
+ fatalx("invalid location");
+
+ /* Otherwise create a new server */
+ if ((srv = calloc(1, sizeof(*srv))) == NULL)
+ goto fail;
+
+ memcpy(&srv->srv_conf, &srv_conf, sizeof(srv->srv_conf));
+ srv->srv_s = imsg->fd;
+
+ if (config_getserver_auth(env, &srv->srv_conf) != 0)
+ goto fail;
+
+ SPLAY_INIT(&srv->srv_clients);
+ TAILQ_INIT(&srv->srv_hosts);
+
+ TAILQ_INSERT_TAIL(&srv->srv_hosts, &srv->srv_conf, entry);
+ TAILQ_INSERT_TAIL(env->sc_servers, srv, srv_entry);
+
+ DPRINTF("%s: %s %d configuration \"%s[%u]\", flags: %s", __func__,
+ ps->ps_title[privsep_process], ps->ps_instance,
+ srv->srv_conf.name, srv->srv_conf.id,
+ printb_flags(srv->srv_conf.flags, SRVFLAG_BITS));
+
+ /*
+ * Get all variable-length values for the parent server.
+ */
+ if (srv->srv_conf.return_uri_len != 0) {
+ if ((srv->srv_conf.return_uri = get_data(p + s,
+ srv->srv_conf.return_uri_len)) == NULL)
+ goto fail;
+ s += srv->srv_conf.return_uri_len;
+ }
+ if (srv->srv_conf.tls_cert_len != 0) {
+ if ((srv->srv_conf.tls_cert = get_data(p + s,
+ srv->srv_conf.tls_cert_len)) == NULL)
+ goto fail;
+ s += srv->srv_conf.tls_cert_len;
+ }
+ if (srv->srv_conf.tls_key_len != 0) {
+ if ((srv->srv_conf.tls_key = get_data(p + s,
+ srv->srv_conf.tls_key_len)) == NULL)
+ goto fail;
+ s += srv->srv_conf.tls_key_len;
+ }
+
+ return (0);
+
+ fail:
+ if (imsg->fd != -1)
+ close(imsg->fd);
+ if (srv != NULL) {
+ free(srv->srv_conf.tls_cert);
+ free(srv->srv_conf.tls_key);
+ }
+ free(srv);
+
+ return (-1);
+}
+
+int
+config_setmedia(struct httpd *env, struct media_type *media)
+{
+ struct privsep *ps = env->sc_ps;
+ int id;
+ u_int what;
+
+ for (id = 0; id < PROC_MAX; id++) {
+ what = ps->ps_what[id];
+
+ if ((what & CONFIG_MEDIA) == 0 || id == privsep_process)
+ continue;
+
+ DPRINTF("%s: sending media \"%s\" to %s", __func__,
+ media->media_name, ps->ps_title[id]);
+
+ proc_compose_imsg(ps, id, -1, IMSG_CFG_MEDIA, -1,
+ media, sizeof(*media));
+ }
+
+ return (0);
+}
+
+int
+config_getmedia(struct httpd *env, struct imsg *imsg)
+{
+#ifdef DEBUG
+ struct privsep *ps = env->sc_ps;
+#endif
+ struct media_type media;
+ u_int8_t *p = imsg->data;
+
+ IMSG_SIZE_CHECK(imsg, &media);
+ memcpy(&media, p, sizeof(media));
+
+ if (media_add(env->sc_mediatypes, &media) == NULL) {
+ log_debug("%s: failed to add media \"%s\"",
+ __func__, media.media_name);
+ return (-1);
+ }
+
+ DPRINTF("%s: %s %d received media \"%s\"", __func__,
+ ps->ps_title[privsep_process], ps->ps_instance,
+ media.media_name);
+
+ return (0);
+}
+
+int
+config_setauth(struct httpd *env, struct auth *auth)
+{
+ struct privsep *ps = env->sc_ps;
+ int id;
+ u_int what;
+
+ for (id = 0; id < PROC_MAX; id++) {
+ what = ps->ps_what[id];
+
+ if ((what & CONFIG_AUTH) == 0 || id == privsep_process)
+ continue;
+
+ DPRINTF("%s: sending auth \"%s[%u]\" to %s", __func__,
+ auth->auth_htpasswd, auth->auth_id, ps->ps_title[id]);
+
+ proc_compose_imsg(ps, id, -1, IMSG_CFG_AUTH, -1,
+ auth, sizeof(*auth));
+ }
+
+ return (0);
+}
+
+int
+config_getauth(struct httpd *env, struct imsg *imsg)
+{
+#ifdef DEBUG
+ struct privsep *ps = env->sc_ps;
+#endif
+ struct auth auth;
+ u_int8_t *p = imsg->data;
+
+ IMSG_SIZE_CHECK(imsg, &auth);
+ memcpy(&auth, p, sizeof(auth));
+
+ if (auth_add(env->sc_auth, &auth) == NULL) {
+ log_debug("%s: failed to add auth \"%s[%u]\"",
+ __func__, auth.auth_htpasswd, auth.auth_id);
+ return (-1);
+ }
+
+ DPRINTF("%s: %s %d received auth \"%s[%u]\"", __func__,
+ ps->ps_title[privsep_process], ps->ps_instance,
+ auth.auth_htpasswd, auth.auth_id);
+
+ return (0);
+}
diff --git a/httpd/control.c b/httpd/control.c
new file mode 100644
index 0000000..fe60375
--- /dev/null
+++ b/httpd/control.c
@@ -0,0 +1,334 @@
+/* $OpenBSD: control.c,v 1.6 2015/01/21 22:21:05 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/stat.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <imsg.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:
+ case IMSG_CTL_REOPEN:
+ 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/httpd/http.h b/httpd/http.h
new file mode 100644
index 0000000..00cf235
--- /dev/null
+++ b/httpd/http.h
@@ -0,0 +1,253 @@
+/* $OpenBSD: http.h,v 1.12 2015/02/11 12:52:01 florian Exp $ */
+
+/*
+ * Copyright (c) 2012 - 2015 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 _HTTP_H
+#define _HTTP_H
+
+#define HTTP_PORT 80
+#define HTTPS_PORT 443
+
+enum httpmethod {
+ HTTP_METHOD_NONE = 0,
+
+ /* HTTP/1.1, RFC 7231 */
+ 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,
+
+ /* WebDAV Versioning Extension, RFC 3253 */
+ HTTP_METHOD_VERSION_CONTROL,
+ HTTP_METHOD_REPORT,
+ HTTP_METHOD_CHECKOUT,
+ HTTP_METHOD_CHECKIN,
+ HTTP_METHOD_UNCHECKOUT,
+ HTTP_METHOD_MKWORKSPACE,
+ HTTP_METHOD_UPDATE,
+ HTTP_METHOD_LABEL,
+ HTTP_METHOD_MERGE,
+ HTTP_METHOD_BASELINE_CONTROL,
+ HTTP_METHOD_MKACTIVITY,
+
+ /* WebDAV Ordered Collections, RFC 3648 */
+ HTTP_METHOD_ORDERPATCH,
+
+ /* WebDAV Access Control, RFC 3744 */
+ HTTP_METHOD_ACL,
+
+ /* WebDAV Redirect Reference Resources, RFC 4437 */
+ HTTP_METHOD_MKREDIRECTREF,
+ HTTP_METHOD_UPDATEREDIRECTREF,
+
+ /* WebDAV Search, RFC 5323 */
+ HTTP_METHOD_SEARCH,
+
+ /* PATCH, RFC 5789 */
+ HTTP_METHOD_PATCH,
+
+ /* 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_VERSION_CONTROL, "VERSION-CONTROL" }, \
+ { HTTP_METHOD_REPORT, "REPORT" }, \
+ { HTTP_METHOD_CHECKOUT, "CHECKOUT" }, \
+ { HTTP_METHOD_CHECKIN, "CHECKIN" }, \
+ { HTTP_METHOD_UNCHECKOUT, "UNCHECKOUT" }, \
+ { HTTP_METHOD_MKWORKSPACE, "MKWORKSPACE" }, \
+ { HTTP_METHOD_UPDATE, "UPDATE" }, \
+ { HTTP_METHOD_LABEL, "LABEL" }, \
+ { HTTP_METHOD_MERGE, "MERGE" }, \
+ { HTTP_METHOD_BASELINE_CONTROL, "BASELINE-CONTROL" }, \
+ { HTTP_METHOD_MKACTIVITY, "MKACTIVITY" }, \
+ { HTTP_METHOD_ORDERPATCH, "ORDERPATCH" }, \
+ { HTTP_METHOD_ACL, "ACL" }, \
+ { HTTP_METHOD_MKREDIRECTREF, "MKREDIRECTREF" }, \
+ { HTTP_METHOD_UPDATEREDIRECTREF, "UPDATEREDIRECTREF" }, \
+ { HTTP_METHOD_SEARCH, "SEARCH" }, \
+ { HTTP_METHOD_PATCH, "PATCH" }, \
+ { HTTP_METHOD_NONE, NULL } \
+}
+
+struct http_error {
+ int error_code;
+ const char *error_name;
+};
+
+/*
+ * HTTP status codes based on IANA assignments (2014-06-11 version):
+ * https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
+ * plus legacy (306) and non-standard (420).
+ */
+#define HTTP_ERRORS { \
+ { 100, "Continue" }, \
+ { 101, "Switching Protocols" }, \
+ { 102, "Processing" }, \
+ /* 103-199 unassigned */ \
+ { 200, "OK" }, \
+ { 201, "Created" }, \
+ { 202, "Accepted" }, \
+ { 203, "Non-Authoritative Information" }, \
+ { 204, "No Content" }, \
+ { 205, "Reset Content" }, \
+ { 206, "Partial Content" }, \
+ { 207, "Multi-Status" }, \
+ { 208, "Already Reported" }, \
+ /* 209-225 unassigned */ \
+ { 226, "IM Used" }, \
+ /* 227-299 unassigned */ \
+ { 300, "Multiple Choices" }, \
+ { 301, "Moved Permanently" }, \
+ { 302, "Found" }, \
+ { 303, "See Other" }, \
+ { 304, "Not Modified" }, \
+ { 305, "Use Proxy" }, \
+ { 306, "Switch Proxy" }, \
+ { 307, "Temporary Redirect" }, \
+ { 308, "Permanent Redirect" }, \
+ /* 309-399 unassigned */ \
+ { 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, "Payload Too Large" }, \
+ { 414, "URI Too Long" }, \
+ { 415, "Unsupported Media Type" }, \
+ { 416, "Range Not Satisfiable" }, \
+ { 417, "Expectation Failed" }, \
+ { 418, "I'm a teapot" }, \
+ /* 419-421 unassigned */ \
+ { 420, "Enhance Your Calm" }, \
+ { 422, "Unprocessable Entity" }, \
+ { 423, "Locked" }, \
+ { 424, "Failed Dependency" }, \
+ /* 425 unassigned */ \
+ { 426, "Upgrade Required" }, \
+ /* 427 unassigned */ \
+ { 428, "Precondition Required" }, \
+ { 429, "Too Many Requests" }, \
+ /* 430 unassigned */ \
+ { 431, "Request Header Fields Too Large" }, \
+ /* 432-450 unassigned */ \
+ { 451, "Unavailable For Legal Reasons" }, \
+ /* 452-499 unassigned */ \
+ { 500, "Internal Server Error" }, \
+ { 501, "Not Implemented" }, \
+ { 502, "Bad Gateway" }, \
+ { 503, "Service Unavailable" }, \
+ { 504, "Gateway Timeout" }, \
+ { 505, "HTTP Version Not Supported" }, \
+ { 506, "Variant Also Negotiates" }, \
+ { 507, "Insufficient Storage" }, \
+ { 508, "Loop Detected" }, \
+ /* 509 unassigned */ \
+ { 510, "Not Extended" }, \
+ { 511, "Network Authentication Required" }, \
+ /* 512-599 unassigned */ \
+ { 0, NULL } \
+}
+
+struct http_mediatype {
+ char *media_name;
+ char *media_type;
+ char *media_subtype;
+};
+/*
+ * Some default media types based on (2014-08-04 version):
+ * https://www.iana.org/assignments/media-types/media-types.xhtml
+ */
+#define MEDIA_TYPES { \
+ { "css", "text", "css" }, \
+ { "html", "text", "html" }, \
+ { "txt", "text", "plain" }, \
+ { "gif", "image", "gif" }, \
+ { "jpeg", "image", "jpeg" }, \
+ { "jpg", "image", "jpeg" }, \
+ { "png", "image", "png" }, \
+ { "svg", "image", "svg+xml" }, \
+ { "js", "application", "javascript" }, \
+ { NULL } \
+}
+
+/* Used during runtime */
+struct http_descriptor {
+ struct kv http_pathquery;
+ struct kv http_matchquery;
+#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
+#define query_key http_matchquery.kv_key
+#define query_val http_matchquery.kv_value
+
+ char *http_host;
+ enum httpmethod http_method;
+ int http_chunked;
+ char *http_version;
+
+ /* Rewritten path remains NULL if not used */
+ char *http_path_alias;
+
+ /* A tree of headers and attached lists for repeated headers. */
+ struct kv *http_lastheader;
+ struct kvtree http_headers;
+};
+
+#endif /* _HTTP_H */
diff --git a/httpd/httpd.8 b/httpd/httpd.8
new file mode 100644
index 0000000..eb35096
--- /dev/null
+++ b/httpd/httpd.8
@@ -0,0 +1,102 @@
+.\" $OpenBSD: httpd.8,v 1.50 2015/02/24 07:56:06 bentley 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: February 24 2015 $
+.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
+The
+.Nm
+daemon is an HTTP server with FastCGI and TLS support.
+.Pp
+The FastCGI implementation has optional socket support.
+.Nm
+can log to
+.Xr syslog 3
+or per-server files with several standard formats.
+.Pp
+.Nm
+rereads its configuration file when it receives
+.Dv SIGHUP
+and reopens log files when it receives
+.Dv SIGUSR1 .
+.Pp
+The options are as follows:
+.Bl -tag -width Dssmacro=value
+.It Fl D Ar macro Ns = Ns Ar value
+Set a
+.Ar macro
+to a
+.Ar value .
+Macros can be referenced in the configuration files.
+.It Fl d
+Debug mode.
+Create one server and don't detach or become a daemon.
+This allows for easy monitoring of
+.Nm .
+.It Fl f Ar file
+Specifies the configuration file.
+The default is
+.Pa /etc/httpd.conf .
+.It Fl n
+Check that the configuration is valid, but don't start any servers.
+.It Fl v
+Verbose mode.
+Multiple
+.Fl v
+options increases the verbosity.
+.El
+.Sh FILES
+.Bl -tag -width "/etc/ssl/private/server.key" -compact
+.It Pa /etc/httpd.conf
+Default configuration file.
+.It Pa /etc/ssl/private/server.key
+Default SSL/TLS server key.
+.It Pa /etc/ssl/server.crt
+Default SSL/TLS server certificate.
+.It Pa /var/run/httpd.sock
+.Ux Ns -domain
+socket used for communication with
+.Nm .
+.It Pa /var/www/logs/access.log
+Default access log file.
+.It Pa /var/www/logs/error.log
+Default error log file.
+.El
+.Sh SEE ALSO
+.Xr httpd.conf 5
+.Sh HISTORY
+The
+.Nm
+program first appeared in
+.Ox 5.6 .
+.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 .
diff --git a/httpd/httpd.c b/httpd/httpd.c
new file mode 100644
index 0000000..c6f183f
--- /dev/null
+++ b/httpd/httpd.c
@@ -0,0 +1,1281 @@
+/* $OpenBSD: httpd.c,v 1.35 2015/02/23 18:43:18 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/param.h> /* nitems */
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/resource.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <signal.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 "httpd.h"
+
+#define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b))
+
+__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_reopen(struct httpd *);
+void parent_sig_handler(int, short, void *);
+void parent_shutdown(struct httpd *);
+int parent_dispatch_server(int, struct privsep_proc *,
+ struct imsg *);
+int parent_dispatch_logger(int, struct privsep_proc *,
+ struct imsg *);
+
+struct httpd *httpd_env;
+
+static struct privsep_proc procs[] = {
+ { "server", PROC_SERVER, parent_dispatch_server, server },
+ { "logger", PROC_LOGGER, parent_dispatch_logger, logger }
+};
+
+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;
+ case SIGUSR1:
+ log_info("%s: reopen requested with SIGUSR1", __func__);
+
+ parent_reopen(ps->ps_env);
+ 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;
+ unsigned int proc;
+ 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 (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;
+
+ if (env->sc_chroot == NULL)
+ env->sc_chroot = ps->ps_pw->pw_dir;
+ for (proc = 0; proc < nitems(procs); proc++)
+ procs[proc].p_chroot = env->sc_chroot;
+
+ if (env->sc_logdir == NULL) {
+ if (asprintf(&env->sc_logdir, "%s%s", env->sc_chroot,
+ HTTPD_LOGROOT) == -1)
+ errx(1, "malloc failed");
+ }
+
+ 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_set(&ps->ps_evsigusr1, SIGUSR1, 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);
+ signal_add(&ps->ps_evsigusr1, 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;
+ struct media_type *media;
+ struct auth *auth;
+
+ RB_FOREACH(media, mediatypes, env->sc_mediatypes) {
+ if (config_setmedia(env, media) == -1)
+ fatal("send media");
+ }
+
+ TAILQ_FOREACH(auth, env->sc_auth, auth_entry) {
+ if (config_setauth(env, auth) == -1)
+ fatal("send auth");
+ }
+
+ /* First send the servers... */
+ TAILQ_FOREACH(srv, env->sc_servers, srv_entry) {
+ if (srv->srv_conf.flags & SRVFLAG_LOCATION)
+ continue;
+ if (config_setserver(env, srv) == -1)
+ fatal("send server");
+ }
+ /* ...and now send the locations */
+ TAILQ_FOREACH(srv, env->sc_servers, srv_entry) {
+ if ((srv->srv_conf.flags & SRVFLAG_LOCATION) == 0)
+ continue;
+ if (config_setserver(env, srv) == -1)
+ fatal("send location");
+ }
+
+ /* The servers need to reload their config. */
+ env->sc_reload = env->sc_prefork_server + 1;
+
+ 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);
+ 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_reopen(struct httpd *env)
+{
+ proc_compose_imsg(env->sc_ps, PROC_LOGGER, -1, IMSG_CTL_REOPEN,
+ -1, NULL, 0);
+}
+
+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);
+}
+
+int
+parent_dispatch_logger(int fd, struct privsep_proc *p, struct imsg *imsg)
+{
+ struct httpd *env = p->p_env;
+ u_int v;
+ char *str = NULL;
+
+ switch (imsg->hdr.type) {
+ case IMSG_CTL_RESET:
+ IMSG_SIZE_CHECK(imsg, &v);
+ memcpy(&v, imsg->data, sizeof(v));
+ parent_reload(env, v, NULL);
+ break;
+ case IMSG_CTL_RELOAD:
+ if (IMSG_DATA_SIZE(imsg) > 0)
+ str = get_string(imsg->data, IMSG_DATA_SIZE(imsg));
+ parent_reload(env, CONFIG_RELOAD, str);
+ if (str != NULL)
+ free(str);
+ break;
+ case IMSG_CTL_SHUTDOWN:
+ parent_shutdown(env);
+ break;
+ case IMSG_CTL_REOPEN:
+ parent_reopen(env);
+ break;
+ case IMSG_CFG_DONE:
+ parent_configure_done(env);
+ break;
+ case IMSG_LOG_OPEN:
+ if (logger_open_priv(imsg) == -1)
+ fatalx("failed to open log file");
+ 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);
+}
+
+int
+expand_string(char *label, size_t len, const char *srch, const char *repl)
+{
+ char *tmp;
+ char *p, *q;
+
+ if ((tmp = calloc(1, len)) == NULL) {
+ log_debug("%s: calloc", __func__);
+ return (-1);
+ }
+ p = q = label;
+ while ((q = strstr(p, srch)) != NULL) {
+ *q = '\0';
+ if ((strlcat(tmp, p, len) >= len) ||
+ (strlcat(tmp, repl, len) >= len)) {
+ log_debug("%s: string too long", __func__);
+ free(tmp);
+ return (-1);
+ }
+ q += strlen(srch);
+ p = q;
+ }
+ if (strlcat(tmp, p, len) >= len) {
+ log_debug("%s: string too long", __func__);
+ free(tmp);
+ return (-1);
+ }
+ (void)strlcpy(label, tmp, len); /* always fits */
+ free(tmp);
+
+ return (0);
+}
+
+const char *
+canonicalize_host(const char *host, char *name, size_t len)
+{
+ struct sockaddr_in sin4;
+ struct sockaddr_in6 sin6;
+ size_t 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);
+}
+
+const char *
+url_decode(char *url)
+{
+ char *p, *q;
+ char hex[3];
+ u_long x;
+
+ hex[2] = '\0';
+ p = q = url;
+
+ while (*p != '\0') {
+ switch (*p) {
+ case '%':
+ /* Encoding character is followed by two hex chars */
+ if (!(isxdigit(p[1]) && isxdigit(p[2])))
+ return (NULL);
+
+ hex[0] = p[1];
+ hex[1] = p[2];
+
+ /*
+ * We don't have to validate "hex" because it is
+ * guaranteed to include two hex chars followed by nul.
+ */
+ x = strtoul(hex, NULL, 16);
+ *q = (char)x;
+ p += 2;
+ break;
+ default:
+ *q = *p;
+ break;
+ }
+ p++;
+ q++;
+ }
+ *q = '\0';
+
+ return (url);
+}
+
+const char *
+canonicalize_path(const char *input, char *path, size_t len)
+{
+ const char *i;
+ char *p, *start, *end;
+
+ /* assuming input starts with '/' and is nul-terminated */
+ i = input;
+ p = path;
+
+ if (*input != '/' || len < 3)
+ return (NULL);
+
+ start = p;
+ end = p + (len - 1);
+
+ while (*i != '\0') {
+ /* Detect truncation */
+ if (p >= end)
+ return (NULL);
+
+ /* 1. check for special path elements */
+ if (i[0] == '/') {
+ if (i[1] == '/') {
+ /* a) skip repeating '//' slashes */
+ while (i[1] == '/')
+ i++;
+ continue;
+ } else if (i[1] == '.' && i[2] == '.' &&
+ (i[3] == '/' || i[3] == '\0')) {
+ /* b) revert '..' to previous directory */
+ i += 3;
+ while (p > start && *p != '/')
+ p--;
+ *p = '\0';
+ continue;
+ } else if (i[1] == '.' &&
+ (i[2] == '/' || i[2] == '\0')) {
+ /* c) skip unnecessary '.' current dir */
+ i += 2;
+ continue;
+ }
+ }
+
+ /* 2. copy any other characters */
+ *p++ = *i;
+ i++;
+ }
+ if (p == start)
+ *p++ = '/';
+ *p++ = '\0';
+
+ return (path);
+}
+
+size_t
+path_info(char *path)
+{
+ char *p, *start, *end, ch;
+ struct stat st;
+ int ret;
+
+ start = path;
+ end = start + strlen(path);
+
+ for (p = end; p > start; p--) {
+ /* Scan every path component from the end and at each '/' */
+ if (p < end && *p != '/')
+ continue;
+
+ /* Temporarily cut the path component out */
+ ch = *p;
+ *p = '\0';
+ ret = stat(path, &st);
+ *p = ch;
+
+ /* Break if the initial path component was found */
+ if (ret == 0)
+ break;
+ }
+
+ return (p - start);
+}
+
+char *
+url_encode(const char *src)
+{
+ static char hex[] = "0123456789ABCDEF";
+ char *dp, *dst;
+ unsigned char c;
+
+ /* We need 3 times the memory if every letter is encoded. */
+ if ((dst = calloc(3, strlen(src) + 1)) == NULL)
+ return (NULL);
+
+ for (dp = dst; *src != 0; src++) {
+ c = (unsigned char) *src;
+ if (c == ' ' || c == '#' || c == '%' || c == '?' || c == '"' ||
+ c == '&' || c == '<' || c <= 0x1f || c >= 0x7f) {
+ *dp++ = '%';
+ *dp++ = hex[c >> 4];
+ *dp++ = hex[c & 0x0f];
+ } else
+ *dp++ = *src;
+ }
+ return (dst);
+}
+
+char*
+escape_html(const char* src)
+{
+ char *dp, *dst;
+
+ /* We need 5 times the memory if every letter is "<" or ">". */
+ if ((dst = calloc(5, strlen(src) + 1)) == NULL)
+ return NULL;
+
+ for (dp = dst; *src != 0; src++) {
+ if (*src == '<') {
+ *dp++ = '&';
+ *dp++ = 'l';
+ *dp++ = 't';
+ *dp++ = ';';
+ } else if (*src == '>') {
+ *dp++ = '&';
+ *dp++ = 'g';
+ *dp++ = 't';
+ *dp++ = ';';
+ } else if (*src == '&') {
+ *dp++ = '&';
+ *dp++ = 'a';
+ *dp++ = 'm';
+ *dp++ = 'p';
+ *dp++ = ';';
+ } else
+ *dp++ = *src;
+ }
+ return (dst);
+}
+
+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 = MAXIMUM(rl.rlim_max, (rlim_t)maxfd);
+ if (setrlimit(RLIMIT_NOFILE, &rl) == -1)
+ fatal("socket_rlimit: failed to set resource limit");
+}
+
+char *
+evbuffer_getline(struct evbuffer *evb)
+{
+ u_int8_t *ptr = EVBUFFER_DATA(evb);
+ size_t len = EVBUFFER_LENGTH(evb);
+ char *str;
+ size_t i;
+
+ /* Safe version of evbuffer_readline() */
+ if ((str = get_string(ptr, len)) == NULL)
+ return (NULL);
+
+ for (i = 0; str[i] != '\0'; i++) {
+ if (str[i] == '\r' || str[i] == '\n')
+ break;
+ }
+
+ if (i == len) {
+ free(str);
+ return (NULL);
+ }
+
+ str[i] = '\0';
+
+ if ((i + 1) < len) {
+ if (ptr[i] == '\r' && ptr[i + 1] == '\n')
+ i++;
+ }
+
+ evbuffer_drain(evb, ++i);
+
+ return (str);
+}
+
+char *
+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
+sockaddr_cmp(struct sockaddr *a, struct sockaddr *b, int prefixlen)
+{
+ struct sockaddr_in *a4, *b4;
+ struct sockaddr_in6 *a6, *b6;
+ u_int32_t av[4], bv[4], mv[4];
+
+ if (a->sa_family == AF_UNSPEC || b->sa_family == AF_UNSPEC)
+ return (0);
+ else if (a->sa_family > b->sa_family)
+ return (1);
+ else if (a->sa_family < b->sa_family)
+ return (-1);
+
+ if (prefixlen == -1)
+ memset(&mv, 0xff, sizeof(mv));
+
+ switch (a->sa_family) {
+ case AF_INET:
+ a4 = (struct sockaddr_in *)a;
+ b4 = (struct sockaddr_in *)b;
+
+ av[0] = a4->sin_addr.s_addr;
+ bv[0] = b4->sin_addr.s_addr;
+ if (prefixlen != -1)
+ mv[0] = prefixlen2mask(prefixlen);
+
+ if ((av[0] & mv[0]) > (bv[0] & mv[0]))
+ return (1);
+ if ((av[0] & mv[0]) < (bv[0] & mv[0]))
+ return (-1);
+ break;
+ case AF_INET6:
+ a6 = (struct sockaddr_in6 *)a;
+ b6 = (struct sockaddr_in6 *)b;
+
+ memcpy(&av, &a6->sin6_addr.s6_addr, 16);
+ memcpy(&bv, &b6->sin6_addr.s6_addr, 16);
+ if (prefixlen != -1)
+ prefixlen2mask6(prefixlen, mv);
+
+ if ((av[3] & mv[3]) > (bv[3] & mv[3]))
+ return (1);
+ if ((av[3] & mv[3]) < (bv[3] & mv[3]))
+ return (-1);
+ if ((av[2] & mv[2]) > (bv[2] & mv[2]))
+ return (1);
+ if ((av[2] & mv[2]) < (bv[2] & mv[2]))
+ return (-1);
+ if ((av[1] & mv[1]) > (bv[1] & mv[1]))
+ return (1);
+ if ((av[1] & mv[1]) < (bv[1] & mv[1]))
+ return (-1);
+ if ((av[0] & mv[0]) > (bv[0] & mv[0]))
+ return (1);
+ if ((av[0] & mv[0]) < (bv[0] & mv[0]))
+ return (-1);
+ break;
+ }
+
+ return (0);
+}
+
+u_int32_t
+prefixlen2mask(u_int8_t prefixlen)
+{
+ if (prefixlen == 0)
+ return (0);
+
+ if (prefixlen > 32)
+ prefixlen = 32;
+
+ return (htonl(0xffffffff << (32 - prefixlen)));
+}
+
+struct in6_addr *
+prefixlen2mask6(u_int8_t prefixlen, u_int32_t *mask)
+{
+ static struct in6_addr s6;
+ int i;
+
+ if (prefixlen > 128)
+ prefixlen = 128;
+
+ memset(&s6, 0, sizeof(s6));
+ for (i = 0; i < prefixlen / 8; i++)
+ s6.s6_addr[i] = 0xff;
+ i = prefixlen % 8;
+ if (i)
+ s6.s6_addr[prefixlen / 8] = 0xff00 >> i;
+
+ memcpy(mask, &s6, sizeof(s6));
+
+ return (&s6);
+}
+
+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_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);
+
+struct media_type *
+media_add(struct mediatypes *types, struct media_type *media)
+{
+ struct media_type *entry;
+
+ if ((entry = RB_FIND(mediatypes, types, media)) != NULL) {
+ log_debug("%s: duplicated entry for \"%s\"", __func__,
+ media->media_name);
+ return (NULL);
+ }
+
+ if ((entry = malloc(sizeof(*media))) == NULL)
+ return (NULL);
+
+ memcpy(entry, media, sizeof(*entry));
+ if (media->media_encoding != NULL &&
+ (entry->media_encoding = strdup(media->media_encoding)) == NULL) {
+ free(entry);
+ return (NULL);
+ }
+ RB_INSERT(mediatypes, types, entry);
+
+ return (entry);
+}
+
+void
+media_delete(struct mediatypes *types, struct media_type *media)
+{
+ RB_REMOVE(mediatypes, types, media);
+ if (media->media_encoding != NULL)
+ free(media->media_encoding);
+ free(media);
+}
+
+void
+media_purge(struct mediatypes *types)
+{
+ struct media_type *media;
+
+ while ((media = RB_MIN(mediatypes, types)) != NULL)
+ media_delete(types, media);
+}
+
+struct media_type *
+media_find(struct mediatypes *types, char *file)
+{
+ struct media_type *match, media;
+ char *p;
+
+ /* Last component of the file name */
+ p = strchr(file, '\0');
+ while (p > file && p[-1] != '.' && p[-1] != '/')
+ p--;
+ if (*p == '\0')
+ return (NULL);
+
+ if (strlcpy(media.media_name, p,
+ sizeof(media.media_name)) >=
+ sizeof(media.media_name)) {
+ return (NULL);
+ }
+
+ /* Find media type by extension name */
+ match = RB_FIND(mediatypes, types, &media);
+
+ return (match);
+}
+
+int
+media_cmp(struct media_type *a, struct media_type *b)
+{
+ return (strcasecmp(a->media_name, b->media_name));
+}
+
+RB_GENERATE(mediatypes, media_type, media_entry, media_cmp);
+
+struct auth *
+auth_add(struct serverauth *serverauth, struct auth *auth)
+{
+ struct auth *entry;
+
+ TAILQ_FOREACH(entry, serverauth, auth_entry) {
+ if (strcmp(entry->auth_htpasswd, auth->auth_htpasswd) == 0)
+ return (entry);
+ }
+
+ if ((entry = calloc(1, sizeof(*entry))) == NULL)
+ return (NULL);
+
+ memcpy(entry, auth, sizeof(*entry));
+
+ TAILQ_INSERT_TAIL(serverauth, entry, auth_entry);
+
+ return (entry);
+}
+
+struct auth *
+auth_byid(struct serverauth *serverauth, u_int32_t id)
+{
+ struct auth *auth;
+
+ TAILQ_FOREACH(auth, serverauth, auth_entry) {
+ if (auth->auth_id == id)
+ return (auth);
+ }
+
+ return (NULL);
+}
+
+void
+auth_free(struct serverauth *serverauth, struct auth *auth)
+{
+ TAILQ_REMOVE(serverauth, auth, auth_entry);
+}
diff --git a/httpd/httpd.conf.5 b/httpd/httpd.conf.5
new file mode 100644
index 0000000..acfb57a
--- /dev/null
+++ b/httpd/httpd.conf.5
@@ -0,0 +1,533 @@
+.\" $OpenBSD: httpd.conf.5,v 1.54 2015/03/06 05:10:18 reyk Exp $
+.\"
+.\" Copyright (c) 2014, 2015 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: March 6 2015 $
+.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 four 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.
+.It Sy Types
+Media types and extensions.
+.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.
+If
+.Sq *
+is given as an address,
+it will be used as an alias for
+.Ar 0.0.0.0
+to listen on all IPv4 addresses.
+Likewise,
+.Sq ::
+can be used to listen on all IPv6 addresses.
+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 directory ,
+.Ic log ,
+or
+.Ic root ) .
+Macros are not expanded inside quotes.
+.Pp
+For example:
+.Bd -literal -offset indent
+ext_ip="10.0.0.1"
+server "default" {
+ listen on $ext_ip port 80
+}
+.Ed
+.Sh GLOBAL CONFIGURATION
+Here are the settings that can be set globally:
+.Bl -tag -width Ds
+.It Ic chroot Ar directory
+Set the
+.Xr chroot 2
+directory.
+If not specified, it defaults to
+.Pa /var/www ,
+the home directory of the www user.
+.It Ic logdir Ar directory
+Specifies the full path of the directory in which log files will be written.
+If not specified, it defaults to
+.Pa /logs
+within the
+.Xr chroot 2
+directory.
+.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
+Each
+.Ic server
+must have a
+.Ar name
+and include one or more lines of the following syntax:
+.Bl -tag -width Ds
+.It Ic alias Ar name
+Specify an additional alias
+.Ar name
+for this server.
+.It Oo Ic no Oc Ic authenticate Oo Ar realm Oc Ic with Pa htpasswd
+Authenticate a remote user for
+.Ar realm
+by checking the credentials against the user authentication file
+.Pa htpasswd .
+The file name is relative to the
+.Ic chroot
+and must be readable by the www user.
+Use the
+.Ic no authenticate
+directive to disable authentication in a location.
+.It Ic block drop
+Drop the connection without sending an error page.
+.It Ic block Op Ic return Ar code Op uri
+Close the connection and send an error page.
+If the optional return code is not specified,
+.Xr httpd 8
+denies access with a
+.Sq 403 Forbidden
+response.
+The optional
+.Ar uri
+argument can be used with return codes in the 3xx range to send a
+.Sq Location:
+header for redirection to a specified URI.
+.Pp
+The
+.Ar url
+may contain predefined macros that will be expanded at runtime:
+.Pp
+.Bl -tag -width $DOCUMENT_URI -offset indent -compact
+.It Ic $DOCUMENT_URI
+The request path.
+.It Ic $QUERY_STRING
+The optional query string of the request.
+.It Ic $REMOTE_ADDR
+The IP address of the connected client.
+.It Ic $REMOTE_PORT
+The TCP source port of the connected client.
+.It Ic $REMOTE_USER
+The remote user for HTTP authentication.
+.It Ic $REQUEST_URI
+The request path and optional query string.
+.It Ic $SERVER_ADDR
+The configured IP address of the server.
+.It Ic $SERVER_PORT
+The configured TCP server port of the server.
+.It Ic $SERVER_NAME
+The name of the server.
+.El
+.It Ic connection Ar option
+Set the specified options and limits for HTTP connections.
+Valid options are:
+.Bl -tag -width Ds
+.It Ic max request body Ar number
+Set the maximum body size in bytes that the client can send to the server.
+The default value is 1048576 bytes (1M).
+.It Ic max requests Ar number
+Set the maximum number of requests per persistent HTTP connection.
+Persistent connections are negotiated using the Keep-Alive header in
+HTTP/1.0 and enabled by default in HTTP/1.1.
+The default maximum number of requests per connection is 100.
+.It Ic timeout Ar seconds
+Specify the inactivity timeout in seconds for accepted sessions.
+The default timeout is 600 seconds (10 minutes).
+The maximum is 2147483647 seconds (68 years).
+.El
+.It Ic directory Ar option
+Set the specified options when serving or accessing directories.
+Valid options are:
+.Bl -tag -width Ds
+.It Oo Ic no Oc Ic auto index
+If no index file is found, automatically generate a directory listing.
+This is disabled by default.
+.It Ic index Ar string
+Set the directory index file.
+If not specified, it defaults to
+.Pa index.html .
+.It Ic no index
+Disable the directory index.
+.Xr httpd 8
+will neither display nor generate a directory index.
+.El
+.It Oo Ic no Oc Ic fastcgi Op Ic socket Ar socket
+Enable FastCGI instead of serving files.
+The
+.Ar socket
+is a local path name within the
+.Xr chroot 2
+root directory of
+.Xr httpd 8
+and defaults to
+.Pa /run/slowcgi.sock .
+.It Ic listen on Ar address Oo Ic tls Oc Ic port Ar number
+Set the listen address and port.
+This statement can be specified multiple times.
+.It Ic location Ar path Brq ...
+Specify server configuration rules for a specific location.
+The
+.Ar path
+argument will be matched against the request path with shell globbing rules.
+A location section may include most of the server configuration rules
+except
+.Ic connection ,
+.Ic listen on ,
+.Ic location
+and
+.Ic tcp .
+.It Oo Ic no Oc Ic log Op Ar option
+Set the specified logging options.
+Logging is enabled by default using the standard
+.Ic access
+and
+.Ic error
+log files,
+but can be changed per server or location.
+Use the
+.Ic no log
+directive to disable logging of any requests.
+Valid options are:
+.Bl -tag -width Ds
+.It Ic access Ar name
+Set the
+.Ar name
+of the access log file relative to the log directory.
+If not specified, it defaults to
+.Pa access.log .
+.It Ic error Ar name
+Set the
+.Ar name
+of the error log file relative to the log directory.
+If not specified, it defaults to
+.Pa error.log .
+.It Ic style Ar style
+Set the logging style.
+The
+.Ar style
+can be
+.Cm common ,
+.Cm combined
+or
+.Cm connection .
+The styles
+.Cm common
+and
+.Cm combined
+write a log entry after each request similar to the standard Apache
+and nginx access log formats.
+The style
+.Cm connection
+writes a summarized log entry after each connection,
+that can have multiple requests,
+similar to the format that is used by
+.Xr relayd 8 .
+If not specified, the default is
+.Cm common .
+.It Oo Ic no Oc Ic syslog
+Enable or disable logging to
+.Xr syslog 3
+instead of the log files.
+.El
+.It Ic pass
+Disable any previous
+.Ic block
+in a location.
+.It Ic root Ar option
+Configure the document root and options for the request path.
+Valid options are:
+.Bl -tag -width Ds
+.It Ar directory
+Set the document root of the server.
+The
+.Ar directory
+is a pathname within the
+.Xr chroot 2
+root directory of
+.Nm httpd .
+If not specified, it defaults to
+.Pa /htdocs .
+.It Ic strip Ar number
+Strip
+.Ar number
+path components from the beginning of the request path before looking
+up the stripped-down path at the document root.
+.El
+.It Ic tcp Ar option
+Enable or disable the specified TCP/IP options; see
+.Xr tcp 4
+and
+.Xr ip 4
+for more information about the options.
+Valid options are:
+.Bl -tag -width Ds
+.It Ic backlog Ar number
+Set the maximum length the queue of pending connections may grow to.
+The backlog option is 10 by default and is limited by the
+.Va kern.somaxconn
+.Xr sysctl 8
+variable.
+.It Ic ip minttl Ar number
+This option for the underlying IP connection may be used to discard packets
+with a TTL lower than the specified value.
+This can be used to implement the
+Generalized TTL Security Mechanism (GTSM)
+according to RFC 5082.
+.It Ic ip ttl Ar number
+Change the default time-to-live value in the IP headers.
+.It Oo Ic no Oc Ic nodelay
+Enable the TCP NODELAY option for this connection.
+This is recommended to avoid delays in the data stream.
+.It Oo Ic no Oc Ic sack
+Use selective acknowledgements for this connection.
+.It Ic socket buffer Ar number
+Set the socket-level buffer size for input and output for this
+connection.
+This will affect the TCP window size.
+.El
+.It Ic tls Ar option
+Set the TLS configuration for the server.
+These options are only used if TLS has been enabled via the listen directive.
+Valid options are:
+.Bl -tag -width Ds
+.It Ic certificate Ar file
+Specify the certificate to use for this server.
+The
+.Ar file
+should contain a PEM encoded certificate.
+.It Ic ciphers Ar string
+Specify the TLS cipher string.
+If not specified, the default value
+.Qq HIGH:!aNULL
+will be used (strong crypto cipher suites without anonymous DH).
+See the CIPHERS section of
+.Xr openssl 1
+for information about SSL/TLS cipher suites and preference lists.
+.It Ic dhe Ar params
+Specify the DHE parameters to use for DHE cipher suites.
+Valid parameter values are none, legacy and auto.
+For legacy a fixed key length of 1024 bits is used, whereas for auto the key
+length is determined automatically.
+The default is none, which disables DHE cipher suites.
+.It Ic ecdhe Ar curve
+Specify the ECDHE curve to use for ECDHE cipher suites.
+Valid parameter values are none, auto and the short name of any known curve.
+The default is auto.
+.It Ic key Ar file
+Specify the private key to use for this server.
+The
+.Ar file
+should contain a PEM encoded private key and reside outside of the
+.Xr chroot 2
+root directory of
+.Nm httpd .
+.It Ic protocols Ar string
+Specify the TLS protocols to enable for this server.
+If not specified, the default value
+.Qq all
+will be used (all available protocols).
+Refer to the
+.Xr tls_config_parse_protocols 3
+function for other valid protocol string values.
+.El
+.El
+.Sh TYPES
+Configure the supported media types.
+.Xr httpd 8
+will set the
+.Ar Content-Type
+of the response header based on the file extension listed in the
+.Ic types
+section.
+If not specified,
+.Xr httpd 8
+will use built-in media types for
+.Ar text/css ,
+.Ar text/html ,
+.Ar text/plain ,
+.Ar image/gif ,
+.Ar image/png ,
+.Ar image/jpeg ,
+and
+.Ar application/javascript .
+.Pp
+The
+.Ic types
+section must include one or more lines of the following syntax:
+.Bl -tag -width Ds
+.It Ar type/subtype Ar name Op Ar name ...
+Set the media
+.Ar type
+and
+.Ar subtype
+to the specified extension
+.Ar name .
+One or more names can be specified per line.
+Each line may end with an optional semicolon.
+.It Ic include Ar file
+Include types definitions from an external file, for example
+.Pa /usr/share/misc/mime.types .
+.El
+.Sh EXAMPLES
+The following example will start one server that is pre-forked two
+times and is listening on all local IP addresses.
+It additionally defines some media types overriding the defaults.
+.Bd -literal -offset indent
+prefork 2
+
+server "default" {
+ listen on * port 80
+}
+
+types {
+ text/css css
+ text/html htm html
+ text/txt txt
+ image/gif gif
+ image/jpeg jpg jpeg
+ image/png png
+ application/javascript js
+ application/xml xml
+}
+.Ed
+.Pp
+The server can also be configured to only listen on the primary IP
+address of the network interface that is a member of the
+.Qq egress
+group.
+.Bd -literal -offset indent
+server "default" {
+ listen on egress port 80
+}
+.Ed
+.Pp
+Multiple servers can be configured to support hosting of different domains.
+If the same address is repeated multiple times in the
+.Ic listen on
+statement,
+the server will be matched based on the requested host name.
+.Bd -literal -offset indent
+server "www.example.com" {
+ alias "example.com"
+ listen on * port 80
+ listen on * tls port 443
+ root "/htdocs/www.example.com"
+}
+
+server "www.a.example.com" {
+ listen on 203.0.113.1 port 80
+ root "/htdocs/www.a.example.com"
+}
+
+server "www.b.example.com" {
+ listen on 203.0.113.1 port 80
+ root "/htdocs/www.b.example.com"
+}
+
+server "intranet.example.com" {
+ listen on 10.0.0.1 port 80
+ root "/htdocs/intranet.example.com"
+}
+.Ed
+.Pp
+Simple redirections can be configured with the
+.Ic block
+directive:
+.Bd -literal -offset indent
+server "example.com" {
+ listen on 10.0.0.1 port 80
+ block return 301 "http://www.example.com/"
+}
+
+server "www.example.com" {
+ listen on 10.0.0.1 port 80
+}
+.Ed
+.Pp
+The syntax of the types section is also compatible with the format used by nginx,
+so it is possible to include its
+.Pa mime.types
+file directly:
+.Bd -literal -offset indent
+include "/etc/nginx/mime.types"
+.Ed
+.Sh SEE ALSO
+.Xr htpasswd 1 ,
+.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/httpd/httpd.h b/httpd/httpd.h
new file mode 100644
index 0000000..28e02a5
--- /dev/null
+++ b/httpd/httpd.h
@@ -0,0 +1,688 @@
+/* $OpenBSD: httpd.h,v 1.81 2015/02/23 18:43:18 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 - 2015 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/types.h>
+#include <sys/socket.h>
+#include <sys/queue.h>
+#include <sys/tree.h>
+#include <sys/time.h>
+
+#include <net/if.h>
+
+#include <stdarg.h>
+#include <limits.h>
+#include <event.h>
+#include <imsg.h>
+#include <tls.h>
+
+#define CONF_FILE "/etc/httpd.conf"
+#define HTTPD_SOCKET "/var/run/httpd.sock"
+#define HTTPD_USER "www"
+#define HTTPD_SERVERNAME "OpenBSD httpd"
+#define HTTPD_DOCROOT "/htdocs"
+#define HTTPD_INDEX "index.html"
+#define HTTPD_FCGI_SOCKET "/run/slowcgi.sock"
+#define HTTPD_LOGROOT "/logs"
+#define HTTPD_ACCESS_LOG "access.log"
+#define HTTPD_ERROR_LOG "error.log"
+#define HTTPD_TLS_CERT "/etc/ssl/server.crt"
+#define HTTPD_TLS_KEY "/etc/ssl/private/server.key"
+#define HTTPD_TLS_CIPHERS "HIGH:!aNULL"
+#define HTTPD_TLS_DHE_PARAMS "none"
+#define HTTPD_TLS_ECDHE_CURVE "auto"
+#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_MAXREQUESTS 100 /* max requests per connection */
+#define SERVER_MAXREQUESTBODY 1048576 /* 1M */
+#define SERVER_BACKLOG 10
+#define SERVER_OUTOF_FD_RETRIES 5
+
+#define MEDIATYPE_NAMEMAX 128 /* file name extension */
+#define MEDIATYPE_TYPEMAX 64 /* length of type/subtype */
+
+#define CONFIG_RELOAD 0x00
+#define CONFIG_MEDIA 0x01
+#define CONFIG_SERVERS 0x02
+#define CONFIG_AUTH 0x04
+#define CONFIG_ALL 0xff
+
+#define FCGI_CONTENT_SIZE 65535
+
+enum httpchunk {
+ TOREAD_UNLIMITED = -1,
+ TOREAD_HTTP_HEADER = -2,
+ TOREAD_HTTP_CHUNK_LENGTH = -3,
+ TOREAD_HTTP_CHUNK_TRAILER = -4,
+ TOREAD_HTTP_NONE = -5
+};
+
+#if DEBUG
+#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;
+ int prefixlen;
+ 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_CTL_REOPEN,
+ IMSG_CFG_SERVER,
+ IMSG_CFG_MEDIA,
+ IMSG_CFG_AUTH,
+ IMSG_CFG_DONE,
+ IMSG_LOG_ACCESS,
+ IMSG_LOG_ERROR,
+ IMSG_LOG_OPEN
+};
+
+enum privsep_procid {
+ PROC_ALL = -1,
+ PROC_PARENT = 0,
+ PROC_SERVER,
+ PROC_LOGGER,
+ PROC_MAX
+} privsep_process;
+
+/* Attach the control socket to the following process */
+#define PROC_CONTROL PROC_LOGGER
+
+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;
+ struct event ps_evsigusr1;
+
+ 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;
+};
+
+enum fcgistate {
+ FCGI_READ_HEADER,
+ FCGI_READ_CONTENT,
+ FCGI_READ_PADDING
+};
+
+struct client {
+ u_int32_t clt_id;
+ pid_t clt_pid;
+ void *clt_srv;
+ void *clt_srv_conf;
+ u_int32_t clt_srv_id;
+ struct sockaddr_storage clt_srv_ss;
+
+ int clt_s;
+ in_port_t clt_port;
+ struct sockaddr_storage clt_ss;
+ struct bufferevent *clt_bev;
+ char *clt_buf;
+ size_t clt_buflen;
+ struct evbuffer *clt_output;
+ struct event clt_ev;
+ void *clt_descreq;
+ void *clt_descresp;
+ int clt_sndbufsiz;
+
+ int clt_fd;
+ struct tls *clt_tls_ctx;
+ struct bufferevent *clt_srvbev;
+
+ off_t clt_toread;
+ size_t clt_headerlen;
+ u_int clt_persist;
+ int clt_line;
+ int clt_done;
+ int clt_chunk;
+ int clt_inflight;
+ enum fcgistate clt_fcgi_state;
+ int clt_fcgi_toread;
+ int clt_fcgi_padding_len;
+ int clt_fcgi_type;
+ int clt_fcgi_chunked;
+ int clt_fcgi_end;
+ char *clt_remote_user;
+ struct evbuffer *clt_srvevb;
+
+ 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);
+
+#define SRVFLAG_INDEX 0x00000001
+#define SRVFLAG_NO_INDEX 0x00000002
+#define SRVFLAG_AUTO_INDEX 0x00000004
+#define SRVFLAG_NO_AUTO_INDEX 0x00000008
+#define SRVFLAG_ROOT 0x00000010
+#define SRVFLAG_LOCATION 0x00000020
+#define SRVFLAG_FCGI 0x00000040
+#define SRVFLAG_NO_FCGI 0x00000080
+#define SRVFLAG_LOG 0x00000100
+#define SRVFLAG_NO_LOG 0x00000200
+#define SRVFLAG_SOCKET 0x00000400
+#define SRVFLAG_SYSLOG 0x00000800
+#define SRVFLAG_NO_SYSLOG 0x00001000
+#define SRVFLAG_TLS 0x00002000
+#define SRVFLAG_ACCESS_LOG 0x00004000
+#define SRVFLAG_ERROR_LOG 0x00008000
+#define SRVFLAG_AUTH 0x00010000
+#define SRVFLAG_NO_AUTH 0x00020000
+#define SRVFLAG_BLOCK 0x00040000
+#define SRVFLAG_NO_BLOCK 0x00080000
+
+#define SRVFLAG_BITS \
+ "\10\01INDEX\02NO_INDEX\03AUTO_INDEX\04NO_AUTO_INDEX" \
+ "\05ROOT\06LOCATION\07FCGI\10NO_FCGI\11LOG\12NO_LOG\13SOCKET" \
+ "\14SYSLOG\15NO_SYSLOG\16TLS\17ACCESS_LOG\20ERROR_LOG" \
+ "\21AUTH\22NO_AUTH\23BLOCK\24NO_BLOCK"
+
+#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 log_format {
+ LOG_FORMAT_COMMON,
+ LOG_FORMAT_COMBINED,
+ LOG_FORMAT_CONNECTION
+};
+
+struct log_file {
+ char log_name[NAME_MAX];
+ int log_fd;
+ u_int32_t log_id;
+ TAILQ_ENTRY(log_file) log_entry;
+};
+TAILQ_HEAD(log_files, log_file) log_files;
+
+struct auth {
+ char auth_htpasswd[PATH_MAX];
+ u_int32_t auth_id;
+ TAILQ_ENTRY(auth) auth_entry;
+};
+TAILQ_HEAD(serverauth, auth);
+
+struct server_config {
+ u_int32_t id;
+ u_int32_t parent_id;
+ char name[HOST_NAME_MAX+1];
+ char location[NAME_MAX];
+ char index[NAME_MAX];
+ char root[PATH_MAX];
+ char socket[PATH_MAX];
+ char accesslog[NAME_MAX];
+ char errorlog[NAME_MAX];
+
+ in_port_t port;
+ struct sockaddr_storage ss;
+ int prefixlen;
+ struct timeval timeout;
+ u_int32_t maxrequests;
+ size_t maxrequestbody;
+
+ u_int8_t *tls_cert;
+ size_t tls_cert_len;
+ char *tls_cert_file;
+ char tls_ciphers[NAME_MAX];
+ char tls_dhe_params[NAME_MAX];
+ char tls_ecdhe_curve[NAME_MAX];
+ u_int8_t *tls_key;
+ size_t tls_key_len;
+ char *tls_key_file;
+ u_int32_t tls_protocols;
+
+ u_int32_t flags;
+ int strip;
+ u_int8_t tcpflags;
+ int tcpbufsiz;
+ int tcpbacklog;
+ u_int8_t tcpipttl;
+ u_int8_t tcpipminttl;
+
+ enum log_format logformat;
+ struct log_file *logaccess;
+ struct log_file *logerror;
+
+ char auth_realm[NAME_MAX];
+ u_int32_t auth_id;
+ struct auth *auth;
+
+ int return_code;
+ char *return_uri;
+ off_t return_uri_len;
+
+ TAILQ_ENTRY(server_config) entry;
+};
+TAILQ_HEAD(serverhosts, server_config);
+
+struct server {
+ TAILQ_ENTRY(server) srv_entry;
+ struct server_config srv_conf;
+ struct serverhosts srv_hosts;
+
+ int srv_s;
+ struct event srv_ev;
+ struct event srv_evt;
+
+ struct tls *srv_tls_ctx;
+ struct tls_config *srv_tls_config;
+
+ struct client_tree srv_clients;
+};
+TAILQ_HEAD(serverlist, server);
+
+struct media_type {
+ char media_name[MEDIATYPE_NAMEMAX];
+ char media_type[MEDIATYPE_TYPEMAX];
+ char media_subtype[MEDIATYPE_TYPEMAX];
+ char *media_encoding;
+ RB_ENTRY(media_type) media_entry;
+};
+RB_HEAD(mediatypes, media_type);
+
+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;
+ int sc_paused;
+ char *sc_chroot;
+ char *sc_logdir;
+
+ struct serverlist *sc_servers;
+ struct mediatypes *sc_mediatypes;
+ struct serverauth *sc_auth;
+
+ struct privsep *sc_ps;
+ int sc_reload;
+};
+
+#define HTTPD_OPT_VERBOSE 0x01
+#define HTTPD_OPT_NOACTION 0x04
+
+/* 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_tls_load_keypair(struct server *);
+int server_privinit(struct server *);
+void server_purge(struct server *);
+void serverconfig_free(struct server_config *);
+void serverconfig_reset(struct server_config *);
+int server_socket_af(struct sockaddr_storage *, in_port_t);
+in_port_t
+ server_socket_getport(struct sockaddr_storage *);
+int server_socket_connect(struct sockaddr_storage *, in_port_t,
+ struct server_config *);
+void server_write(struct bufferevent *, void *);
+void server_read(struct bufferevent *, void *);
+void server_error(struct bufferevent *, short, void *);
+void server_log(struct client *, const char *);
+void server_sendlog(struct server_config *, int, const char *, ...)
+ __attribute__((__format__ (printf, 3, 4)));
+void server_close(struct client *, const char *);
+void server_dump(struct client *, const void *, size_t);
+int server_client_cmp(struct client *, struct client *);
+int server_bufferevent_printf(struct client *, const char *, ...)
+ __attribute__((__format__ (printf, 2, 3)));
+int server_bufferevent_print(struct client *, const char *);
+int server_bufferevent_write_buffer(struct client *,
+ struct evbuffer *);
+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);
+void server_inflight_dec(struct client *, const char *);
+struct server *
+ server_byaddr(struct sockaddr *, in_port_t);
+struct server_config *
+ serverconfig_byid(u_int32_t);
+int server_foreach(int (*)(struct server *,
+ struct server_config *, void *), void *);
+
+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_http(struct client *clt, struct kv *, void *);
+int server_headers(struct client *, void *,
+ int (*)(struct client *, struct kv *, void *), void *);
+int server_writeresponse_http(struct client *);
+int server_response_http(struct client *, u_int, struct media_type *,
+ size_t, time_t);
+void server_reset_http(struct client *);
+void server_close_http(struct client *);
+int server_response(struct httpd *, struct client *);
+const char *
+ server_root_strip(const char *, int);
+struct server_config *
+ server_getlocation(struct client *, const char *);
+const char *
+ server_http_host(struct sockaddr_storage *, char *, size_t);
+char *server_http_parsehost(char *, char *, size_t, int *);
+ssize_t server_http_time(time_t, char *, size_t);
+int server_log_http(struct client *, u_int, size_t);
+
+/* server_file.c */
+int server_file(struct httpd *, struct client *);
+void server_file_error(struct bufferevent *, short, void *);
+
+/* server_fcgi.c */
+int server_fcgi(struct httpd *, struct client *);
+int fcgi_add_stdin(struct client *, struct evbuffer *);
+
+/* httpd.c */
+void event_again(struct event *, int, short,
+ void (*)(int, short, void *),
+ struct timeval *, struct timeval *, void *);
+int expand_string(char *, size_t, const char *, const char *);
+const char *url_decode(char *);
+char *url_encode(const char *);
+const char *canonicalize_host(const char *, char *, size_t);
+const char *canonicalize_path(const char *, char *, size_t);
+size_t path_info(char *);
+char *escape_html(const char *);
+void imsg_event_add(struct imsgev *);
+int imsg_compose_event(struct imsgev *, u_int16_t, u_int32_t,
+ pid_t, int, void *, u_int16_t);
+void socket_rlimit(int);
+char *evbuffer_getline(struct evbuffer *);
+char *get_string(u_int8_t *, size_t);
+void *get_data(u_int8_t *, size_t);
+int sockaddr_cmp(struct sockaddr *, struct sockaddr *, int);
+struct in6_addr *prefixlen2mask6(u_int8_t, u_int32_t *);
+u_int32_t prefixlen2mask(u_int8_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 *);
+struct media_type
+ *media_add(struct mediatypes *, struct media_type *);
+void media_delete(struct mediatypes *, struct media_type *);
+void media_purge(struct mediatypes *);
+struct media_type *
+ media_find(struct mediatypes *, char *);
+int media_cmp(struct media_type *, struct media_type *);
+RB_PROTOTYPE(kvtree, kv, kv_node, kv_cmp);
+RB_PROTOTYPE(mediatypes, media_type, media_entry, media_cmp);
+struct auth *auth_add(struct serverauth *, struct auth *);
+struct auth *auth_byid(struct serverauth *, u_int32_t);
+void auth_free(struct serverauth *, struct auth *);
+
+/* 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 logit(int, const char *, ...) __attribute__((__format__ (printf, 2, 3)));
+void vlog(int, const char *, va_list) __attribute__((__format__ (printf, 2, 0)));
+__dead void fatal(const char *);
+__dead void fatalx(const char *);
+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 *);
+int config_setmedia(struct httpd *, struct media_type *);
+int config_getmedia(struct httpd *, struct imsg *);
+int config_setauth(struct httpd *, struct auth *);
+int config_getauth(struct httpd *, struct imsg *);
+
+/* logger.c */
+pid_t logger(struct privsep *, struct privsep_proc *);
+int logger_open_priv(struct imsg *);
+
+#endif /* _HTTPD_H */
diff --git a/httpd/log.c b/httpd/log.c
new file mode 100644
index 0000000..a5dfc6b
--- /dev/null
+++ b/httpd/log.c
@@ -0,0 +1,242 @@
+/* $OpenBSD: log.c,v 1.5 2015/01/21 22:21:05 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/socket.h>
+#include <sys/time.h>
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+#include <netdb.h>
+#include <ctype.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/httpd/logger.c b/httpd/logger.c
new file mode 100644
index 0000000..b4e4404
--- /dev/null
+++ b/httpd/logger.c
@@ -0,0 +1,312 @@
+/* $OpenBSD: logger.c,v 1.11 2015/02/08 00:00:59 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/param.h> /* nitems */
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <imsg.h>
+
+#include "httpd.h"
+
+int logger_dispatch_parent(int, struct privsep_proc *,
+ struct imsg *);
+int logger_dispatch_server(int, struct privsep_proc *,
+ struct imsg *);
+void logger_shutdown(void);
+void logger_close(void);
+struct log_file *logger_open_file(const char *);
+int logger_open_fd(struct imsg *);
+int logger_open(struct server *, struct server_config *, void *);
+void logger_init(struct privsep *, struct privsep_proc *p, void *);
+int logger_start(void);
+int logger_log(struct imsg *);
+
+static struct httpd *env = NULL;
+int proc_id;
+static u_int32_t last_log_id = 0;
+
+static struct privsep_proc procs[] = {
+ { "parent", PROC_PARENT, logger_dispatch_parent },
+ { "server", PROC_SERVER, logger_dispatch_server }
+};
+
+pid_t
+logger(struct privsep *ps, struct privsep_proc *p)
+{
+ env = ps->ps_env;
+ return (proc_run(ps, p, procs, nitems(procs), logger_init, NULL));
+}
+
+void
+logger_shutdown(void)
+{
+ logger_close();
+ config_purge(env, CONFIG_ALL);
+}
+
+void
+logger_init(struct privsep *ps, struct privsep_proc *p, void *arg)
+{
+ 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 = logger_shutdown;
+
+ TAILQ_INIT(&log_files);
+}
+
+void
+logger_close(void)
+{
+ struct log_file *log, *next;
+
+ TAILQ_FOREACH_SAFE(log, &log_files, log_entry, next) {
+ if (log->log_fd != -1) {
+ close(log->log_fd);
+ log->log_fd = -1;
+ }
+ TAILQ_REMOVE(&log_files, log, log_entry);
+ }
+}
+
+struct log_file *
+logger_open_file(const char *name)
+{
+ struct log_file *log;
+ struct iovec iov[2];
+
+ if ((log = calloc(1, sizeof(*log))) == NULL) {
+ log_warn("failed to allocate log %s", name);
+ return (NULL);
+ }
+
+ log->log_id = ++last_log_id;
+ (void)strlcpy(log->log_name, name, sizeof(log->log_name));
+
+ /* The file will be opened by the parent process */
+ log->log_fd = -1;
+
+ iov[0].iov_base = &log->log_id;
+ iov[0].iov_len = sizeof(log->log_id);
+ iov[1].iov_base = log->log_name;
+ iov[1].iov_len = strlen(log->log_name) + 1;
+
+ proc_composev_imsg(env->sc_ps, PROC_PARENT, -1, IMSG_LOG_OPEN, -1,
+ iov, 2);
+
+ TAILQ_INSERT_TAIL(&log_files, log, log_entry);
+
+ return (log);
+}
+
+int
+logger_open_fd(struct imsg *imsg)
+{
+ struct log_file *log;
+ u_int32_t id;
+
+ IMSG_SIZE_CHECK(imsg, &id);
+ memcpy(&id, imsg->data, sizeof(id));
+
+ TAILQ_FOREACH(log, &log_files, log_entry) {
+ if (log->log_id == id) {
+ DPRINTF("%s: received log fd %d, file %s",
+ __func__, imsg->fd, log->log_name);
+ log->log_fd = imsg->fd;
+ return (0);
+ }
+ }
+
+ return (-1);
+}
+
+int
+logger_open_priv(struct imsg *imsg)
+{
+ char path[PATH_MAX];
+ char name[NAME_MAX], *p;
+ u_int32_t id;
+ size_t len;
+ int fd;
+
+ /* called from the priviled process */
+ IMSG_SIZE_CHECK(imsg, &id);
+ memcpy(&id, imsg->data, sizeof(id));
+ p = (char *)imsg->data + sizeof(id);
+
+ if ((size_t)snprintf(name, sizeof(name), "/%s", p) >= sizeof(name))
+ return (-1);
+ if ((len = strlcpy(path, env->sc_logdir, sizeof(path)))
+ >= sizeof(path))
+ return (-1);
+
+ p = path + len;
+ len = sizeof(path) - len;
+
+ if (canonicalize_path(name, p, len) == NULL) {
+ log_warnx("invalid log name");
+ return (-1);
+ }
+
+ if ((fd = open(path, O_WRONLY|O_APPEND|O_CREAT, 0644)) == -1) {
+ log_warn("failed to open %s", path);
+ return (-1);
+ }
+
+ proc_compose_imsg(env->sc_ps, PROC_LOGGER, -1, IMSG_LOG_OPEN, fd,
+ &id, sizeof(id));
+
+ DPRINTF("%s: opened log file %s, fd %d", __func__, path, fd);
+
+ return (0);
+}
+
+int
+logger_open(struct server *srv, struct server_config *srv_conf, void *arg)
+{
+ struct log_file *log, *logfile = NULL, *errfile = NULL;
+
+ if (srv_conf->flags & SRVFLAG_SYSLOG)
+ return (0);
+
+ /* disassociate */
+ srv_conf->logaccess = srv_conf->logerror = NULL;
+
+ TAILQ_FOREACH(log, &log_files, log_entry) {
+ if (strcmp(log->log_name, srv_conf->accesslog) == 0)
+ logfile = log;
+ if (strcmp(log->log_name, srv_conf->errorlog) == 0)
+ errfile = log;
+ }
+
+ if (logfile == NULL) {
+ if ((srv_conf->logaccess =
+ logger_open_file(srv_conf->accesslog)) == NULL)
+ return (-1);
+ } else
+ srv_conf->logaccess = logfile;
+
+ if (errfile == NULL) {
+ if ((srv_conf->logerror =
+ logger_open_file(srv_conf->errorlog)) == NULL)
+ return (-1);
+ } else
+ srv_conf->logerror = errfile;
+
+ return (0);
+}
+
+int
+logger_start(void)
+{
+ logger_close();
+ if (server_foreach(logger_open, NULL) == -1)
+ fatalx("failed to open log files");
+ return (0);
+}
+
+int
+logger_log(struct imsg *imsg)
+{
+ char *logline;
+ u_int32_t id;
+ struct server_config *srv_conf;
+ struct log_file *log;
+
+ IMSG_SIZE_CHECK(imsg, &id);
+ memcpy(&id, imsg->data, sizeof(id));
+
+ if ((srv_conf = serverconfig_byid(id)) == NULL)
+ fatalx("invalid logging requestr");
+
+ if (imsg->hdr.type == IMSG_LOG_ACCESS)
+ log = srv_conf->logaccess;
+ else
+ log = srv_conf->logerror;
+
+ if (log == NULL || log->log_fd == -1) {
+ log_warnx("log file %s not opened", log ? log->log_name : "");
+ return (0);
+ }
+
+ /* XXX get_string() would sanitize the string, but add a malloc */
+ logline = (char *)imsg->data + sizeof(id);
+
+ /* For debug output */
+ log_debug("%s", logline);
+
+ if (dprintf(log->log_fd, "%s\n", logline) == -1) {
+ if (logger_start() == -1)
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+logger_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:
+ case IMSG_CTL_REOPEN:
+ logger_start();
+ break;
+ case IMSG_CTL_RESET:
+ config_getreset(env, imsg);
+ break;
+ case IMSG_LOG_OPEN:
+ return (logger_open_fd(imsg));
+ default:
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+logger_dispatch_server(int fd, struct privsep_proc *p, struct imsg *imsg)
+{
+ switch (imsg->hdr.type) {
+ case IMSG_LOG_ACCESS:
+ case IMSG_LOG_ERROR:
+ logger_log(imsg);
+ break;
+ default:
+ return (-1);
+ }
+
+ return (0);
+}
diff --git a/httpd/parse.y b/httpd/parse.y
new file mode 100644
index 0000000..7e48aba
--- /dev/null
+++ b/httpd/parse.y
@@ -0,0 +1,2062 @@
+/* $OpenBSD: parse.y,v 1.65 2015/02/12 04:40:23 jsing Exp $ */
+
+/*
+ * Copyright (c) 2007 - 2015 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/tree.h>
+#include <sys/ioctl.h>
+#include <sys/sockio.h>
+#include <sys/time.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <ctype.h>
+#include <unistd.h>
+#include <err.h>
+#include <errno.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 "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 *, ...)
+ __attribute__((__format__ (printf, 1, 2)))
+ __attribute__((__nonnull__ (1)));
+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;
+uint32_t last_auth_id = 0;
+
+static struct server *srv = NULL, *parentsrv = NULL;
+static struct server_config *srv_conf = NULL;
+struct serverlist servers;
+struct media_type media;
+
+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 *);
+struct server *server_inherit(struct server *, const char *,
+ struct server_config *);
+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 auth auth;
+ struct {
+ struct sockaddr_storage ss;
+ char name[HOST_NAME_MAX+1];
+ } addr;
+ } v;
+ int lineno;
+} YYSTYPE;
+
+%}
+
+%token ACCESS ALIAS AUTO BACKLOG BODY BUFFER CERTIFICATE CHROOT CIPHERS COMMON
+%token COMBINED CONNECTION DHE DIRECTORY ECDHE ERR FCGI INDEX IP KEY LISTEN
+%token LOCATION LOG LOGDIR MAXIMUM NO NODELAY ON PORT PREFORK PROTOCOLS
+%token REQUEST REQUESTS ROOT SACK SERVER SOCKET STRIP STYLE SYSLOG TCP TIMEOUT
+%token TLS TYPES
+%token ERROR INCLUDE AUTHENTICATE WITH BLOCK DROP RETURN PASS
+%token <v.string> STRING
+%token <v.number> NUMBER
+%type <v.port> port
+%type <v.number> opttls
+%type <v.tv> timeout
+%type <v.string> numberstring optstring
+%type <v.auth> authopts
+
+%%
+
+grammar : /* empty */
+ | grammar include '\n'
+ | grammar '\n'
+ | grammar varset '\n'
+ | grammar main '\n'
+ | grammar server '\n'
+ | grammar types '\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);
+ }
+ ;
+
+opttls : /*empty*/ { $$ = 0; }
+ | TLS { $$ = 1; }
+ ;
+
+main : PREFORK NUMBER {
+ if (loadcfg)
+ break;
+ if ($2 <= 0 || $2 > SERVER_MAXPROC) {
+ yyerror("invalid number of preforked "
+ "servers: %lld", $2);
+ YYERROR;
+ }
+ conf->sc_prefork_server = $2;
+ }
+ | CHROOT STRING {
+ conf->sc_chroot = $2;
+ }
+ | LOGDIR STRING {
+ conf->sc_logdir = $2;
+ }
+ ;
+
+server : SERVER STRING {
+ struct server *s;
+
+ if (!loadcfg) {
+ free($2);
+ YYACCEPT;
+ }
+
+ 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);
+
+ strlcpy(s->srv_conf.root, HTTPD_DOCROOT,
+ sizeof(s->srv_conf.root));
+ strlcpy(s->srv_conf.index, HTTPD_INDEX,
+ sizeof(s->srv_conf.index));
+ strlcpy(s->srv_conf.accesslog, HTTPD_ACCESS_LOG,
+ sizeof(s->srv_conf.accesslog));
+ strlcpy(s->srv_conf.errorlog, HTTPD_ERROR_LOG,
+ sizeof(s->srv_conf.errorlog));
+ s->srv_conf.id = ++last_server_id;
+ s->srv_conf.parent_id = s->srv_conf.id;
+ s->srv_s = -1;
+ s->srv_conf.timeout.tv_sec = SERVER_TIMEOUT;
+ s->srv_conf.maxrequests = SERVER_MAXREQUESTS;
+ s->srv_conf.maxrequestbody = SERVER_MAXREQUESTBODY;
+ s->srv_conf.flags |= SRVFLAG_LOG;
+ s->srv_conf.logformat = LOG_FORMAT_COMMON;
+ s->srv_conf.tls_protocols = TLS_PROTOCOLS_ALL;
+ if ((s->srv_conf.tls_cert_file =
+ strdup(HTTPD_TLS_CERT)) == NULL)
+ fatal("out of memory");
+ if ((s->srv_conf.tls_key_file =
+ strdup(HTTPD_TLS_KEY)) == NULL)
+ fatal("out of memory");
+ strlcpy(s->srv_conf.tls_ciphers,
+ HTTPD_TLS_CIPHERS,
+ sizeof(s->srv_conf.tls_ciphers));
+ strlcpy(s->srv_conf.tls_dhe_params,
+ HTTPD_TLS_DHE_PARAMS,
+ sizeof(s->srv_conf.tls_dhe_params));
+ strlcpy(s->srv_conf.tls_ecdhe_curve,
+ HTTPD_TLS_ECDHE_CURVE,
+ sizeof(s->srv_conf.tls_ecdhe_curve));
+
+ if (last_server_id == INT_MAX) {
+ yyerror("too many servers defined");
+ free(s);
+ YYERROR;
+ }
+ srv = s;
+ srv_conf = &srv->srv_conf;
+
+ SPLAY_INIT(&srv->srv_clients);
+ TAILQ_INIT(&srv->srv_hosts);
+
+ TAILQ_INSERT_TAIL(&srv->srv_hosts, srv_conf, entry);
+ } '{' optnl serveropts_l '}' {
+ struct server *s = NULL, *sn;
+ struct server_config *a, *b;
+
+ srv_conf = &srv->srv_conf;
+
+ TAILQ_FOREACH(s, conf->sc_servers, srv_entry) {
+ if ((s->srv_conf.flags &
+ SRVFLAG_LOCATION) == 0 &&
+ strcmp(s->srv_conf.name,
+ srv->srv_conf.name) == 0 &&
+ s->srv_conf.port == srv->srv_conf.port &&
+ sockaddr_cmp(
+ (struct sockaddr *)&s->srv_conf.ss,
+ (struct sockaddr *)&srv->srv_conf.ss,
+ s->srv_conf.prefixlen) == 0)
+ break;
+ }
+ if (s != NULL) {
+ yyerror("server \"%s\" defined twice",
+ srv->srv_conf.name);
+ serverconfig_free(srv_conf);
+ free(srv);
+ YYABORT;
+ }
+
+ if (srv->srv_conf.ss.ss_family == AF_UNSPEC) {
+ yyerror("listen address not specified");
+ serverconfig_free(srv_conf);
+ free(srv);
+ YYERROR;
+ }
+
+ if ((srv->srv_conf.flags & SRVFLAG_TLS) &&
+ srv->srv_conf.tls_protocols == 0) {
+ yyerror("no TLS protocols");
+ free(srv);
+ YYERROR;
+ }
+
+ if (server_tls_load_keypair(srv) == -1) {
+ yyerror("failed to load public/private keys "
+ "for server %s", srv->srv_conf.name);
+ serverconfig_free(srv_conf);
+ free(srv);
+ YYERROR;
+ }
+
+ DPRINTF("adding server \"%s[%u]\"",
+ srv->srv_conf.name, srv->srv_conf.id);
+
+ TAILQ_INSERT_TAIL(conf->sc_servers, srv, srv_entry);
+
+ /*
+ * Add aliases and additional listen addresses as
+ * individual servers.
+ */
+ TAILQ_FOREACH(a, &srv->srv_hosts, entry) {
+ /* listen address */
+ if (a->ss.ss_family == AF_UNSPEC)
+ continue;
+ TAILQ_FOREACH(b, &srv->srv_hosts, entry) {
+ /* alias name */
+ if (*b->name == '\0' ||
+ (b == &srv->srv_conf && b == a))
+ continue;
+
+ if ((sn = server_inherit(srv,
+ b->name, a)) == NULL) {
+ serverconfig_free(srv_conf);
+ free(srv);
+ YYABORT;
+ }
+
+ DPRINTF("adding server \"%s[%u]\"",
+ sn->srv_conf.name, sn->srv_conf.id);
+
+ TAILQ_INSERT_TAIL(conf->sc_servers,
+ sn, srv_entry);
+ }
+ }
+
+ /* Remove temporary aliases */
+ TAILQ_FOREACH_SAFE(a, &srv->srv_hosts, entry, b) {
+ TAILQ_REMOVE(&srv->srv_hosts, a, entry);
+ if (a == &srv->srv_conf)
+ continue;
+ serverconfig_free(a);
+ free(a);
+ }
+
+ srv = NULL;
+ srv_conf = NULL;
+ }
+ ;
+
+serveropts_l : serveropts_l serveroptsl nl
+ | serveroptsl optnl
+ ;
+
+serveroptsl : LISTEN ON STRING opttls port {
+ struct addresslist al;
+ struct address *h;
+ struct server_config *s_conf, *alias = NULL;
+
+ if (parentsrv != NULL) {
+ yyerror("listen %s inside location", $3);
+ free($3);
+ YYERROR;
+ }
+
+ if (srv->srv_conf.ss.ss_family != AF_UNSPEC) {
+ if ((alias = calloc(1,
+ sizeof(*alias))) == NULL)
+ fatal("out of memory");
+
+ /* Add as an alias */
+ s_conf = alias;
+ } else
+ s_conf = &srv->srv_conf;
+
+ TAILQ_INIT(&al);
+ if (host($3, &al, 1, &$5, NULL, -1) <= 0) {
+ yyerror("invalid listen ip: %s", $3);
+ free($3);
+ YYERROR;
+ }
+ free($3);
+ h = TAILQ_FIRST(&al);
+ memcpy(&s_conf->ss, &h->ss, sizeof(s_conf->ss));
+ s_conf->port = h->port.val[0];
+ s_conf->prefixlen = h->prefixlen;
+ host_free(&al);
+
+ if ($4) {
+ s_conf->flags |= SRVFLAG_TLS;
+ }
+
+ if (alias != NULL) {
+ TAILQ_INSERT_TAIL(&srv->srv_hosts,
+ alias, entry);
+ }
+ }
+ | ALIAS STRING {
+ struct server_config *alias;
+
+ if (parentsrv != NULL) {
+ yyerror("alias inside location");
+ free($2);
+ YYERROR;
+ }
+
+ if ((alias = calloc(1, sizeof(*alias))) == NULL)
+ fatal("out of memory");
+
+ if (strlcpy(alias->name, $2, sizeof(alias->name)) >=
+ sizeof(alias->name)) {
+ yyerror("server alias truncated");
+ free($2);
+ free(alias);
+ YYERROR;
+ }
+ free($2);
+
+ TAILQ_INSERT_TAIL(&srv->srv_hosts, alias, entry);
+ }
+ | tcpip {
+ if (parentsrv != NULL) {
+ yyerror("tcp flags inside location");
+ YYERROR;
+ }
+ }
+ | connection {
+ if (parentsrv != NULL) {
+ yyerror("connection options inside location");
+ YYERROR;
+ }
+ }
+ | tls {
+ if (parentsrv != NULL) {
+ yyerror("tls configuration inside location");
+ YYERROR;
+ }
+ }
+ | root
+ | directory
+ | logformat
+ | fastcgi
+ | authenticate
+ | filter
+ | LOCATION STRING {
+ struct server *s;
+
+ if (srv->srv_conf.ss.ss_family == AF_UNSPEC) {
+ yyerror("listen address not specified");
+ free($2);
+ YYERROR;
+ }
+
+ if (parentsrv != NULL) {
+ yyerror("location %s inside location", $2);
+ free($2);
+ YYERROR;
+ }
+
+ if (!loadcfg) {
+ free($2);
+ YYACCEPT;
+ }
+
+ if ((s = calloc(1, sizeof (*s))) == NULL)
+ fatal("out of memory");
+
+ if (strlcpy(s->srv_conf.location, $2,
+ sizeof(s->srv_conf.location)) >=
+ sizeof(s->srv_conf.location)) {
+ yyerror("server location truncated");
+ free($2);
+ free(s);
+ YYERROR;
+ }
+ free($2);
+
+ if (strlcpy(s->srv_conf.name, srv->srv_conf.name,
+ sizeof(s->srv_conf.name)) >=
+ sizeof(s->srv_conf.name)) {
+ yyerror("server name truncated");
+ free(s);
+ YYERROR;
+ }
+
+ s->srv_conf.id = ++last_server_id;
+ /* A location entry uses the parent id */
+ s->srv_conf.parent_id = srv->srv_conf.id;
+ s->srv_conf.flags = SRVFLAG_LOCATION;
+ s->srv_s = -1;
+ memcpy(&s->srv_conf.ss, &srv->srv_conf.ss,
+ sizeof(s->srv_conf.ss));
+ s->srv_conf.port = srv->srv_conf.port;
+ s->srv_conf.prefixlen = srv->srv_conf.prefixlen;
+
+ if (last_server_id == INT_MAX) {
+ yyerror("too many servers/locations defined");
+ free(s);
+ YYERROR;
+ }
+ parentsrv = srv;
+ srv = s;
+ srv_conf = &srv->srv_conf;
+ SPLAY_INIT(&srv->srv_clients);
+ } '{' optnl serveropts_l '}' {
+ struct server *s = NULL;
+
+ TAILQ_FOREACH(s, conf->sc_servers, srv_entry) {
+ if ((s->srv_conf.flags & SRVFLAG_LOCATION) &&
+ s->srv_conf.id == srv_conf->id &&
+ strcmp(s->srv_conf.location,
+ srv_conf->location) == 0)
+ break;
+ }
+ if (s != NULL) {
+ yyerror("location \"%s\" defined twice",
+ srv->srv_conf.location);
+ serverconfig_free(srv_conf);
+ free(srv);
+ YYABORT;
+ }
+
+ DPRINTF("adding location \"%s\" for \"%s[%u]\"",
+ srv->srv_conf.location,
+ srv->srv_conf.name, srv->srv_conf.id);
+
+ TAILQ_INSERT_TAIL(conf->sc_servers, srv, srv_entry);
+
+ srv = parentsrv;
+ srv_conf = &parentsrv->srv_conf;
+ parentsrv = NULL;
+ }
+ | include
+ ;
+
+fastcgi : NO FCGI {
+ srv_conf->flags &= ~SRVFLAG_FCGI;
+ srv_conf->flags |= SRVFLAG_NO_FCGI;
+ }
+ | FCGI {
+ srv_conf->flags &= ~SRVFLAG_NO_FCGI;
+ srv_conf->flags |= SRVFLAG_FCGI;
+ }
+ | FCGI {
+ srv_conf->flags &= ~SRVFLAG_NO_FCGI;
+ srv_conf->flags |= SRVFLAG_FCGI;
+ } '{' optnl fcgiflags_l '}'
+ | FCGI {
+ srv_conf->flags &= ~SRVFLAG_NO_FCGI;
+ srv_conf->flags |= SRVFLAG_FCGI;
+ } fcgiflags
+ ;
+
+fcgiflags_l : fcgiflags optcommanl fcgiflags_l
+ | fcgiflags optnl
+ ;
+
+fcgiflags : SOCKET STRING {
+ if (strlcpy(srv_conf->socket, $2,
+ sizeof(srv_conf->socket)) >=
+ sizeof(srv_conf->socket)) {
+ yyerror("fastcgi socket too long");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ srv_conf->flags |= SRVFLAG_SOCKET;
+ }
+ ;
+
+connection : CONNECTION '{' optnl conflags_l '}'
+ | CONNECTION conflags
+ ;
+
+conflags_l : conflags optcommanl conflags_l
+ | conflags optnl
+ ;
+
+conflags : TIMEOUT timeout {
+ memcpy(&srv_conf->timeout, &$2,
+ sizeof(struct timeval));
+ }
+ | MAXIMUM REQUESTS NUMBER {
+ srv_conf->maxrequests = $3;
+ }
+ | MAXIMUM REQUEST BODY NUMBER {
+ srv_conf->maxrequestbody = $4;
+ }
+ ;
+
+tls : TLS '{' optnl tlsopts_l '}'
+ | TLS tlsopts
+ ;
+
+tlsopts_l : tlsopts optcommanl tlsopts_l
+ | tlsopts optnl
+ ;
+
+tlsopts : CERTIFICATE STRING {
+ free(srv_conf->tls_cert_file);
+ if ((srv_conf->tls_cert_file = strdup($2)) == NULL)
+ fatal("out of memory");
+ free($2);
+ }
+ | KEY STRING {
+ free(srv_conf->tls_key_file);
+ if ((srv_conf->tls_key_file = strdup($2)) == NULL)
+ fatal("out of memory");
+ free($2);
+ }
+ | CIPHERS STRING {
+ if (strlcpy(srv_conf->tls_ciphers, $2,
+ sizeof(srv_conf->tls_ciphers)) >=
+ sizeof(srv_conf->tls_ciphers)) {
+ yyerror("ciphers too long");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ | DHE STRING {
+ if (strlcpy(srv_conf->tls_dhe_params, $2,
+ sizeof(srv_conf->tls_dhe_params)) >=
+ sizeof(srv_conf->tls_dhe_params)) {
+ yyerror("dhe too long");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ | ECDHE STRING {
+ if (strlcpy(srv_conf->tls_ecdhe_curve, $2,
+ sizeof(srv_conf->tls_ecdhe_curve)) >=
+ sizeof(srv_conf->tls_ecdhe_curve)) {
+ yyerror("ecdhe too long");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ | PROTOCOLS STRING {
+ if (tls_config_parse_protocols(
+ &srv_conf->tls_protocols, $2) != 0) {
+ yyerror("invalid TLS protocols");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ ;
+
+root : ROOT rootflags
+ | ROOT '{' optnl rootflags_l '}'
+ ;
+
+rootflags_l : rootflags optcommanl rootflags_l
+ | rootflags optnl
+ ;
+
+rootflags : STRING {
+ if (strlcpy(srv->srv_conf.root, $1,
+ sizeof(srv->srv_conf.root)) >=
+ sizeof(srv->srv_conf.root)) {
+ yyerror("document root too long");
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ srv->srv_conf.flags |= SRVFLAG_ROOT;
+ }
+ | STRIP NUMBER {
+ if ($2 < 0 || $2 > INT_MAX) {
+ yyerror("invalid strip number");
+ YYERROR;
+ }
+ srv->srv_conf.strip = $2;
+ }
+ ;
+
+authenticate : NO AUTHENTICATE {
+ srv->srv_conf.flags |= SRVFLAG_NO_AUTH;
+ }
+ | AUTHENTICATE authopts {
+ struct auth *auth;
+
+ if ((auth = auth_add(conf->sc_auth, &$2)) == NULL) {
+ yyerror("failed to add auth");
+ YYERROR;
+ }
+
+ if (auth->auth_id == 0) {
+ /* New htpasswd, get new Id */
+ auth->auth_id = ++last_auth_id;
+ if (last_auth_id == INT_MAX) {
+ yyerror("too many auth ids defined");
+ auth_free(conf->sc_auth, auth);
+ YYERROR;
+ }
+ }
+
+ srv->srv_conf.auth_id = auth->auth_id;
+ srv->srv_conf.flags |= SRVFLAG_AUTH;
+ }
+ ;
+
+authopts : STRING WITH STRING {
+ if (strlcpy(srv->srv_conf.auth_realm, $1,
+ sizeof(srv->srv_conf.auth_realm)) >=
+ sizeof(srv->srv_conf.auth_realm)) {
+ yyerror("basic auth realm name too long");
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ if (strlcpy($$.auth_htpasswd, $3,
+ sizeof($$.auth_htpasswd)) >=
+ sizeof($$.auth_htpasswd)) {
+ yyerror("password file name too long");
+ free($3);
+ YYERROR;
+ }
+ free($3);
+
+ }
+ | WITH STRING {
+ if (strlcpy($$.auth_htpasswd, $2,
+ sizeof($$.auth_htpasswd)) >=
+ sizeof($$.auth_htpasswd)) {
+ yyerror("password file name too long");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ };
+
+directory : DIRECTORY dirflags
+ | DIRECTORY '{' optnl dirflags_l '}'
+ ;
+
+dirflags_l : dirflags optcommanl dirflags_l
+ | dirflags optnl
+ ;
+
+dirflags : INDEX STRING {
+ if (strlcpy(srv_conf->index, $2,
+ sizeof(srv_conf->index)) >=
+ sizeof(srv_conf->index)) {
+ yyerror("index file too long");
+ free($2);
+ YYERROR;
+ }
+ srv_conf->flags &= ~SRVFLAG_NO_INDEX;
+ srv_conf->flags |= SRVFLAG_INDEX;
+ free($2);
+ }
+ | NO INDEX {
+ srv_conf->flags &= ~SRVFLAG_INDEX;
+ srv_conf->flags |= SRVFLAG_NO_INDEX;
+ }
+ | AUTO INDEX {
+ srv_conf->flags &= ~SRVFLAG_NO_AUTO_INDEX;
+ srv_conf->flags |= SRVFLAG_AUTO_INDEX;
+ }
+ | NO AUTO INDEX {
+ srv_conf->flags &= ~SRVFLAG_AUTO_INDEX;
+ srv_conf->flags |= SRVFLAG_NO_AUTO_INDEX;
+ }
+ ;
+
+
+logformat : LOG logflags
+ | LOG '{' optnl logflags_l '}'
+ | NO LOG {
+ srv_conf->flags &= ~SRVFLAG_LOG;
+ srv_conf->flags |= SRVFLAG_NO_LOG;
+ }
+ ;
+
+logflags_l : logflags optcommanl logflags_l
+ | logflags optnl
+ ;
+
+logflags : STYLE logstyle
+ | SYSLOG {
+ srv_conf->flags &= ~SRVFLAG_NO_SYSLOG;
+ srv_conf->flags |= SRVFLAG_SYSLOG;
+ }
+ | NO SYSLOG {
+ srv_conf->flags &= ~SRVFLAG_SYSLOG;
+ srv_conf->flags |= SRVFLAG_NO_SYSLOG;
+ }
+ | ACCESS STRING {
+ if (strlcpy(srv_conf->accesslog, $2,
+ sizeof(srv_conf->accesslog)) >=
+ sizeof(srv_conf->accesslog)) {
+ yyerror("access log name too long");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ srv_conf->flags |= SRVFLAG_ACCESS_LOG;
+ }
+ | ERR STRING {
+ if (strlcpy(srv_conf->errorlog, $2,
+ sizeof(srv_conf->errorlog)) >=
+ sizeof(srv_conf->errorlog)) {
+ yyerror("error log name too long");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ srv_conf->flags |= SRVFLAG_ERROR_LOG;
+ }
+ ;
+
+logstyle : COMMON {
+ srv_conf->flags &= ~SRVFLAG_NO_LOG;
+ srv_conf->flags |= SRVFLAG_LOG;
+ srv_conf->logformat = LOG_FORMAT_COMMON;
+ }
+ | COMBINED {
+ srv_conf->flags &= ~SRVFLAG_NO_LOG;
+ srv_conf->flags |= SRVFLAG_LOG;
+ srv_conf->logformat = LOG_FORMAT_COMBINED;
+ }
+ | CONNECTION {
+ srv_conf->flags &= ~SRVFLAG_NO_LOG;
+ srv_conf->flags |= SRVFLAG_LOG;
+ srv_conf->logformat = LOG_FORMAT_CONNECTION;
+ }
+ ;
+
+filter : block RETURN NUMBER optstring {
+ if ($3 <= 0 || server_httperror_byid($3) == NULL) {
+ yyerror("invalid return code: %lld", $3);
+ free($4);
+ YYERROR;
+ }
+ srv_conf->return_code = $3;
+
+ if ($4 != NULL) {
+ /* Only for 3xx redirection headers */
+ if ($3 < 300 || $3 > 399) {
+ yyerror("invalid return code for "
+ "location URI");
+ free($4);
+ YYERROR;
+ }
+ srv_conf->return_uri = $4;
+ srv_conf->return_uri_len = strlen($4) + 1;
+ }
+ }
+ | block DROP {
+ /* No return code, silently drop the connection */
+ srv_conf->return_code = 0;
+ }
+ | block {
+ /* Forbidden */
+ srv_conf->return_code = 403;
+ }
+ | PASS {
+ srv_conf->flags &= ~SRVFLAG_BLOCK;
+ srv_conf->flags |= SRVFLAG_NO_BLOCK;
+ }
+ ;
+
+block : BLOCK {
+ srv_conf->flags &= ~SRVFLAG_NO_BLOCK;
+ srv_conf->flags |= SRVFLAG_BLOCK;
+ }
+ ;
+
+optstring : /* empty */ { $$ = NULL; }
+ | STRING { $$ = $1; }
+ ;
+
+tcpip : TCP '{' optnl tcpflags_l '}'
+ | TCP tcpflags
+ ;
+
+tcpflags_l : tcpflags optcommanl tcpflags_l
+ | tcpflags optnl
+ ;
+
+tcpflags : SACK { srv_conf->tcpflags |= TCPFLAG_SACK; }
+ | NO SACK { srv_conf->tcpflags |= TCPFLAG_NSACK; }
+ | NODELAY {
+ srv_conf->tcpflags |= TCPFLAG_NODELAY;
+ }
+ | NO NODELAY {
+ srv_conf->tcpflags |= TCPFLAG_NNODELAY;
+ }
+ | BACKLOG NUMBER {
+ if ($2 < 0 || $2 > SERVER_MAX_CLIENTS) {
+ yyerror("invalid backlog: %lld", $2);
+ YYERROR;
+ }
+ srv_conf->tcpbacklog = $2;
+ }
+ | SOCKET BUFFER NUMBER {
+ srv_conf->tcpflags |= TCPFLAG_BUFSIZ;
+ if ((srv_conf->tcpbufsiz = $3) < 0) {
+ yyerror("invalid socket buffer size: %lld", $3);
+ YYERROR;
+ }
+ }
+ | IP STRING NUMBER {
+ if ($3 < 0) {
+ yyerror("invalid ttl: %lld", $3);
+ free($2);
+ YYERROR;
+ }
+ if (strcasecmp("ttl", $2) == 0) {
+ srv_conf->tcpflags |= TCPFLAG_IPTTL;
+ srv_conf->tcpipttl = $3;
+ } else if (strcasecmp("minttl", $2) == 0) {
+ srv_conf->tcpflags |= TCPFLAG_IPMINTTL;
+ srv_conf->tcpipminttl = $3;
+ } else {
+ yyerror("invalid TCP/IP flag: %s", $2);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ ;
+
+types : TYPES '{' optnl mediaopts_l '}'
+ ;
+
+mediaopts_l : mediaopts_l mediaoptsl nl
+ | mediaoptsl nl
+ ;
+
+mediaoptsl : STRING '/' STRING {
+ if (strlcpy(media.media_type, $1,
+ sizeof(media.media_type)) >=
+ sizeof(media.media_type) ||
+ strlcpy(media.media_subtype, $3,
+ sizeof(media.media_subtype)) >=
+ sizeof(media.media_subtype)) {
+ yyerror("media type too long");
+ free($1);
+ free($3);
+ YYERROR;
+ }
+ free($1);
+ free($3);
+ } medianames_l optsemicolon
+ | include
+ ;
+
+medianames_l : medianames_l medianamesl
+ | medianamesl
+ ;
+
+medianamesl : numberstring {
+ if (strlcpy(media.media_name, $1,
+ sizeof(media.media_name)) >=
+ sizeof(media.media_name)) {
+ yyerror("media name too long");
+ free($1);
+ YYERROR;
+ }
+ free($1);
+
+ if (!loadcfg)
+ break;
+
+ if (media_add(conf->sc_mediatypes, &media) == NULL) {
+ yyerror("failed to add media type");
+ YYERROR;
+ }
+ }
+ ;
+
+port : PORT NUMBER {
+ if ($2 <= 0 || $2 >= (int)USHRT_MAX) {
+ yyerror("invalid port: %lld", $2);
+ YYERROR;
+ }
+ $$.val[0] = htons($2);
+ }
+ | PORT STRING {
+ int val;
+
+ if ((val = getservice($2)) == -1) {
+ yyerror("invalid port: %s", $2);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+
+ $$.val[0] = val;
+ }
+ ;
+
+timeout : NUMBER
+ {
+ if ($1 < 0) {
+ yyerror("invalid timeout: %lld", $1);
+ YYERROR;
+ }
+ $$.tv_sec = $1;
+ $$.tv_usec = 0;
+ }
+ ;
+
+numberstring : NUMBER {
+ char *s;
+ if (asprintf(&s, "%lld", $1) == -1) {
+ yyerror("asprintf: number");
+ YYERROR;
+ }
+ $$ = s;
+ }
+ | STRING
+ ;
+
+optsemicolon : ';'
+ |
+ ;
+
+optnl : '\n' optnl
+ |
+ ;
+
+optcommanl : ',' optnl
+ | nl
+ ;
+
+nl : '\n' optnl
+ ;
+
+%%
+
+struct keywords {
+ const char *k_name;
+ int k_val;
+};
+
+int
+yyerror(const char *fmt, ...)
+{
+ va_list ap;
+ char *msg;
+
+ file->errors++;
+ va_start(ap, fmt);
+ if (vasprintf(&msg, fmt, ap) == -1)
+ fatalx("yyerror vasprintf");
+ va_end(ap);
+ logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
+ free(msg);
+ 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[] = {
+ { "access", ACCESS },
+ { "alias", ALIAS },
+ { "authenticate", AUTHENTICATE},
+ { "auto", AUTO },
+ { "backlog", BACKLOG },
+ { "block", BLOCK },
+ { "body", BODY },
+ { "buffer", BUFFER },
+ { "certificate", CERTIFICATE },
+ { "chroot", CHROOT },
+ { "ciphers", CIPHERS },
+ { "combined", COMBINED },
+ { "common", COMMON },
+ { "connection", CONNECTION },
+ { "dhe", DHE },
+ { "directory", DIRECTORY },
+ { "drop", DROP },
+ { "ecdhe", ECDHE },
+ { "error", ERR },
+ { "fastcgi", FCGI },
+ { "include", INCLUDE },
+ { "index", INDEX },
+ { "ip", IP },
+ { "key", KEY },
+ { "listen", LISTEN },
+ { "location", LOCATION },
+ { "log", LOG },
+ { "logdir", LOGDIR },
+ { "max", MAXIMUM },
+ { "no", NO },
+ { "nodelay", NODELAY },
+ { "on", ON },
+ { "pass", PASS },
+ { "port", PORT },
+ { "prefork", PREFORK },
+ { "protocols", PROTOCOLS },
+ { "request", REQUEST },
+ { "requests", REQUESTS },
+ { "return", RETURN },
+ { "root", ROOT },
+ { "sack", SACK },
+ { "server", SERVER },
+ { "socket", SOCKET },
+ { "strip", STRIP },
+ { "style", STYLE },
+ { "syslog", SYSLOG },
+ { "tcp", TCP },
+ { "timeout", TIMEOUT },
+ { "tls", TLS },
+ { "types", TYPES },
+ { "with", WITH }
+ };
+ 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;
+ } else if (c == '\0') {
+ yyerror("syntax error");
+ return (findeol());
+ }
+ 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 != ';' && x != '/'))
+
+ if (isalnum(c) || 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;
+ struct http_mediatype mediatypes[] = MEDIA_TYPES;
+ struct media_type m;
+ int i;
+
+ conf = x_conf;
+ conf->sc_flags = 0;
+
+ loadcfg = 1;
+ errors = 0;
+ last_server_id = 0;
+ last_auth_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++;
+ }
+
+ if (RB_EMPTY(conf->sc_mediatypes)) {
+ /* Add default media types */
+ for (i = 0; mediatypes[i].media_name != NULL; i++) {
+ (void)strlcpy(m.media_name, mediatypes[i].media_name,
+ sizeof(m.media_name));
+ (void)strlcpy(m.media_type, mediatypes[i].media_type,
+ sizeof(m.media_type));
+ (void)strlcpy(m.media_subtype,
+ mediatypes[i].media_subtype,
+ sizeof(m.media_subtype));
+ m.media_encoding = NULL;
+
+ if (media_add(conf->sc_mediatypes, &m) == NULL) {
+ log_warnx("failed to add default media \"%s\"",
+ m.media_name);
+ 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;
+ if (sain->sin_addr.s_addr == INADDR_ANY)
+ h->prefixlen = 0; /* 0.0.0.0 address */
+ else
+ h->prefixlen = -1; /* host address */
+ 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;
+ if (memcmp(&sa_in6->sin6_addr, &in6addr_any,
+ sizeof(sa_in6->sin6_addr)) == 0)
+ h->prefixlen = 0; /* any address */
+ else
+ h->prefixlen = -1; /* host address */
+ 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 */
+ hints.ai_flags = AI_ADDRCONFIG;
+ 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;
+ h->prefixlen = -1; /* host address */
+
+ 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;
+ h->prefixlen = -1; /* host address */
+
+ 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;
+
+ if (strcmp("*", s) == 0)
+ s = "0.0.0.0";
+
+ 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);
+ }
+}
+
+struct server *
+server_inherit(struct server *src, const char *name,
+ struct server_config *addr)
+{
+ struct server *dst, *s, *dstl;
+
+ if ((dst = calloc(1, sizeof(*dst))) == NULL)
+ fatal("out of memory");
+
+ /* Copy the source server and assign a new Id */
+ memcpy(&dst->srv_conf, &src->srv_conf, sizeof(dst->srv_conf));
+ if ((dst->srv_conf.tls_cert_file =
+ strdup(src->srv_conf.tls_cert_file)) == NULL)
+ fatal("out of memory");
+ if ((dst->srv_conf.tls_key_file =
+ strdup(src->srv_conf.tls_key_file)) == NULL)
+ fatal("out of memory");
+ dst->srv_conf.tls_cert = NULL;
+ dst->srv_conf.tls_key = NULL;
+
+ if (src->srv_conf.return_uri != NULL &&
+ (dst->srv_conf.return_uri =
+ strdup(src->srv_conf.return_uri)) == NULL)
+ fatal("out of memory");
+
+ dst->srv_conf.id = ++last_server_id;
+ dst->srv_conf.parent_id = dst->srv_conf.id;
+ dst->srv_s = -1;
+
+ if (last_server_id == INT_MAX) {
+ yyerror("too many servers defined");
+ serverconfig_free(&dst->srv_conf);
+ free(dst);
+ return (NULL);
+ }
+
+ /* Now set alias and listen address */
+ strlcpy(dst->srv_conf.name, name, sizeof(dst->srv_conf.name));
+ memcpy(&dst->srv_conf.ss, &addr->ss, sizeof(dst->srv_conf.ss));
+ dst->srv_conf.port = addr->port;
+ dst->srv_conf.prefixlen = addr->prefixlen;
+ if (addr->flags & SRVFLAG_TLS)
+ dst->srv_conf.flags |= SRVFLAG_TLS;
+ else
+ dst->srv_conf.flags &= ~SRVFLAG_TLS;
+
+ if (server_tls_load_keypair(dst) == -1) {
+ yyerror("failed to load public/private keys "
+ "for server %s", dst->srv_conf.name);
+ serverconfig_free(&dst->srv_conf);
+ free(dst);
+ return (NULL);
+ }
+
+ /* Check if the new server already exists */
+ TAILQ_FOREACH(s, conf->sc_servers, srv_entry) {
+ if ((s->srv_conf.flags &
+ SRVFLAG_LOCATION) == 0 &&
+ strcmp(s->srv_conf.name,
+ dst->srv_conf.name) == 0 &&
+ s->srv_conf.port == dst->srv_conf.port &&
+ sockaddr_cmp(
+ (struct sockaddr *)&s->srv_conf.ss,
+ (struct sockaddr *)&dst->srv_conf.ss,
+ s->srv_conf.prefixlen) == 0)
+ break;
+ }
+ if (s != NULL) {
+ yyerror("server \"%s\" defined twice",
+ dst->srv_conf.name);
+ serverconfig_free(&dst->srv_conf);
+ free(dst);
+ return (NULL);
+ }
+
+ /* Copy all the locations of the source server */
+ TAILQ_FOREACH(s, conf->sc_servers, srv_entry) {
+ if (!(s->srv_conf.flags & SRVFLAG_LOCATION &&
+ s->srv_conf.parent_id == src->srv_conf.parent_id))
+ continue;
+
+ if ((dstl = calloc(1, sizeof(*dstl))) == NULL)
+ fatal("out of memory");
+
+ memcpy(&dstl->srv_conf, &s->srv_conf, sizeof(dstl->srv_conf));
+ strlcpy(dstl->srv_conf.name, name, sizeof(dstl->srv_conf.name));
+
+ /* Copy the new Id and listen address */
+ dstl->srv_conf.id = ++last_server_id;
+ dstl->srv_conf.parent_id = dst->srv_conf.id;
+ memcpy(&dstl->srv_conf.ss, &addr->ss,
+ sizeof(dstl->srv_conf.ss));
+ dstl->srv_conf.port = addr->port;
+ dstl->srv_conf.prefixlen = addr->prefixlen;
+ dstl->srv_s = -1;
+
+ DPRINTF("adding location \"%s\" for \"%s[%u]\"",
+ dstl->srv_conf.location,
+ dstl->srv_conf.name, dstl->srv_conf.id);
+
+ TAILQ_INSERT_TAIL(conf->sc_servers, dstl, srv_entry);
+ }
+
+ return (dst);
+}
+
+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/httpd/proc.c b/httpd/proc.c
new file mode 100644
index 0000000..34d65c0
--- /dev/null
+++ b/httpd/proc.c
@@ -0,0 +1,622 @@
+/* $OpenBSD: proc.c,v 1.8 2015/01/21 22:21:05 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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <pwd.h>
+#include <event.h>
+#include <imsg.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:
+ case SIGUSR1:
+ /* 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 */
+ setpgid(0, 0);
+ 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_set(&ps->ps_evsigusr1, SIGUSR1, 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);
+ signal_add(&ps->ps_evsigusr1, 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/httpd/server.c b/httpd/server.c
new file mode 100644
index 0000000..813f945
--- /dev/null
+++ b/httpd/server.c
@@ -0,0 +1,1221 @@
+/* $OpenBSD: server.c,v 1.60 2015/02/23 09:52:28 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 - 2015 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/param.h> /* nitems */
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/tree.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <event.h>
+#include <imsg.h>
+#include <tls.h>
+
+#include "httpd.h"
+
+#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b))
+
+int server_dispatch_parent(int, struct privsep_proc *,
+ struct imsg *);
+int server_dispatch_logger(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_config *, int, int);
+int server_socket_listen(struct sockaddr_storage *, in_port_t,
+ struct server_config *);
+
+int server_tls_init(struct server *);
+void server_tls_readcb(int, short, void *);
+void server_tls_writecb(int, short, void *);
+
+void server_accept(int, short, void *);
+void server_accept_tls(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 },
+ { "logger", PROC_LOGGER, server_dispatch_logger }
+};
+
+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)
+{
+ struct server *s;
+
+ if (srv->srv_conf.flags & SRVFLAG_LOCATION)
+ return (0);
+
+ log_debug("%s: adding server %s", __func__, srv->srv_conf.name);
+
+ /*
+ * There's no need to open a new socket if a server with the
+ * same address already exists.
+ */
+ TAILQ_FOREACH(s, env->sc_servers, srv_entry) {
+ if (s != srv && s->srv_s != -1 &&
+ s->srv_conf.port == srv->srv_conf.port &&
+ sockaddr_cmp((struct sockaddr *)&s->srv_conf.ss,
+ (struct sockaddr *)&srv->srv_conf.ss,
+ s->srv_conf.prefixlen) == 0)
+ return (0);
+ }
+
+ /* Open listening socket in the privileged process */
+ if ((srv->srv_s = server_socket_listen(&srv->srv_conf.ss,
+ srv->srv_conf.port, &srv->srv_conf)) == -1)
+ return (-1);
+
+ return (0);
+}
+
+int
+server_tls_load_keypair(struct server *srv)
+{
+ if ((srv->srv_conf.flags & SRVFLAG_TLS) == 0)
+ return (0);
+
+ if ((srv->srv_conf.tls_cert = tls_load_file(
+ srv->srv_conf.tls_cert_file, &srv->srv_conf.tls_cert_len,
+ NULL)) == NULL)
+ return (-1);
+ log_debug("%s: using certificate %s", __func__,
+ srv->srv_conf.tls_cert_file);
+
+ /* XXX allow to specify password for encrypted key */
+ if ((srv->srv_conf.tls_key = tls_load_file(
+ srv->srv_conf.tls_key_file, &srv->srv_conf.tls_key_len,
+ NULL)) == NULL)
+ return (-1);
+ log_debug("%s: using private key %s", __func__,
+ srv->srv_conf.tls_key_file);
+
+ return (0);
+}
+
+int
+server_tls_init(struct server *srv)
+{
+ if ((srv->srv_conf.flags & SRVFLAG_TLS) == 0)
+ return (0);
+
+ log_debug("%s: setting up TLS for %s", __func__, srv->srv_conf.name);
+
+ if (tls_init() != 0) {
+ log_warn("%s: failed to initialise tls", __func__);
+ return (-1);
+ }
+ if ((srv->srv_tls_config = tls_config_new()) == NULL) {
+ log_warn("%s: failed to get tls config", __func__);
+ return (-1);
+ }
+ if ((srv->srv_tls_ctx = tls_server()) == NULL) {
+ log_warn("%s: failed to get tls server", __func__);
+ return (-1);
+ }
+
+ tls_config_set_protocols(srv->srv_tls_config,
+ srv->srv_conf.tls_protocols);
+
+ if (tls_config_set_ciphers(srv->srv_tls_config,
+ srv->srv_conf.tls_ciphers) != 0) {
+ log_warn("%s: failed to set tls ciphers", __func__);
+ return (-1);
+ }
+ if (tls_config_set_dheparams(srv->srv_tls_config,
+ srv->srv_conf.tls_dhe_params) != 0) {
+ log_warn("%s: failed to set tls dhe params", __func__);
+ return (-1);
+ }
+ if (tls_config_set_ecdhecurve(srv->srv_tls_config,
+ srv->srv_conf.tls_ecdhe_curve) != 0) {
+ log_warn("%s: failed to set tls ecdhe curve", __func__);
+ return (-1);
+ }
+
+ if (tls_config_set_cert_mem(srv->srv_tls_config,
+ srv->srv_conf.tls_cert, srv->srv_conf.tls_cert_len) != 0) {
+ log_warn("%s: failed to set tls cert", __func__);
+ return (-1);
+ }
+ if (tls_config_set_key_mem(srv->srv_tls_config,
+ srv->srv_conf.tls_key, srv->srv_conf.tls_key_len) != 0) {
+ log_warn("%s: failed to set tls key", __func__);
+ return (-1);
+ }
+
+ if (tls_configure(srv->srv_tls_ctx, srv->srv_tls_config) != 0) {
+ log_warn("%s: failed to configure TLS - %s", __func__,
+ tls_error(srv->srv_tls_ctx));
+ return (-1);
+ }
+
+ /* We're now done with the public/private key... */
+ tls_config_clear_keys(srv->srv_tls_config);
+ explicit_bzero(srv->srv_conf.tls_cert, srv->srv_conf.tls_cert_len);
+ explicit_bzero(srv->srv_conf.tls_key, srv->srv_conf.tls_key_len);
+ free(srv->srv_conf.tls_cert);
+ free(srv->srv_conf.tls_key);
+ srv->srv_conf.tls_cert = NULL;
+ srv->srv_conf.tls_key = NULL;
+ srv->srv_conf.tls_cert_len = 0;
+ srv->srv_conf.tls_key_len = 0;
+
+ 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_tls_init(srv);
+ 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);
+ }
+}
+
+void
+server_purge(struct server *srv)
+{
+ struct client *clt;
+ struct server_config *srv_conf;
+
+ /* shutdown and remove server */
+ if (event_initialized(&srv->srv_ev))
+ event_del(&srv->srv_ev);
+ if (evtimer_initialized(&srv->srv_evt))
+ evtimer_del(&srv->srv_evt);
+
+ if (srv->srv_s != -1)
+ close(srv->srv_s);
+ TAILQ_REMOVE(env->sc_servers, srv, srv_entry);
+
+ /* cleanup sessions */
+ while ((clt =
+ SPLAY_ROOT(&srv->srv_clients)) != NULL)
+ server_close(clt, NULL);
+
+ /* cleanup hosts */
+ while ((srv_conf =
+ TAILQ_FIRST(&srv->srv_hosts)) != NULL) {
+ TAILQ_REMOVE(&srv->srv_hosts, srv_conf, entry);
+
+ /* It might point to our own "default" entry */
+ if (srv_conf != &srv->srv_conf) {
+ serverconfig_free(srv_conf);
+ free(srv_conf);
+ }
+ }
+
+ tls_config_free(srv->srv_tls_config);
+ tls_free(srv->srv_tls_ctx);
+
+ free(srv);
+}
+
+void
+serverconfig_free(struct server_config *srv_conf)
+{
+ free(srv_conf->return_uri);
+ free(srv_conf->tls_cert_file);
+ free(srv_conf->tls_cert);
+ free(srv_conf->tls_key_file);
+ free(srv_conf->tls_key);
+}
+
+void
+serverconfig_reset(struct server_config *srv_conf)
+{
+ srv_conf->tls_cert_file = srv_conf->tls_key_file = NULL;
+ srv_conf->tls_cert = srv_conf->tls_key = NULL;
+ srv_conf->return_uri = NULL;
+ srv_conf->auth = NULL;
+}
+
+struct server *
+server_byaddr(struct sockaddr *addr, in_port_t port)
+{
+ struct server *srv;
+
+ TAILQ_FOREACH(srv, env->sc_servers, srv_entry) {
+ if (port == srv->srv_conf.port &&
+ sockaddr_cmp((struct sockaddr *)&srv->srv_conf.ss,
+ addr, srv->srv_conf.prefixlen) == 0)
+ return (srv);
+ }
+
+ return (NULL);
+}
+
+struct server_config *
+serverconfig_byid(u_int32_t id)
+{
+ struct server *srv;
+ struct server_config *srv_conf;
+
+ TAILQ_FOREACH(srv, env->sc_servers, srv_entry) {
+ if (srv->srv_conf.id == id)
+ return (&srv->srv_conf);
+ TAILQ_FOREACH(srv_conf, &srv->srv_hosts, entry) {
+ if (srv_conf->id == id)
+ return (srv_conf);
+ }
+ }
+
+ return (NULL);
+}
+
+int
+server_foreach(int (*srv_cb)(struct server *,
+ struct server_config *, void *), void *arg)
+{
+ struct server *srv;
+ struct server_config *srv_conf;
+
+ TAILQ_FOREACH(srv, env->sc_servers, srv_entry) {
+ if ((srv_cb)(srv, &srv->srv_conf, arg) == -1)
+ return (-1);
+ TAILQ_FOREACH(srv_conf, &srv->srv_hosts, entry) {
+ if ((srv_cb)(srv, srv_conf, arg) == -1)
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+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_config *srv_conf, 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_conf->tcpflags & TCPFLAG_BUFSIZ) {
+ val = srv_conf->tcpbufsiz;
+ if (setsockopt(s, SOL_SOCKET, SO_RCVBUF,
+ &val, sizeof(val)) == -1)
+ goto bad;
+ val = srv_conf->tcpbufsiz;
+ if (setsockopt(s, SOL_SOCKET, SO_SNDBUF,
+ &val, sizeof(val)) == -1)
+ goto bad;
+ }
+
+ /*
+ * IP options
+ */
+ if (srv_conf->tcpflags & TCPFLAG_IPTTL) {
+ val = (int)srv_conf->tcpipttl;
+ if (setsockopt(s, IPPROTO_IP, IP_TTL,
+ &val, sizeof(val)) == -1)
+ goto bad;
+ }
+ if (srv_conf->tcpflags & TCPFLAG_IPMINTTL) {
+ val = (int)srv_conf->tcpipminttl;
+ if (setsockopt(s, IPPROTO_IP, IP_MINTTL,
+ &val, sizeof(val)) == -1)
+ goto bad;
+ }
+
+ /*
+ * TCP options
+ */
+ if (srv_conf->tcpflags & (TCPFLAG_NODELAY|TCPFLAG_NNODELAY)) {
+ if (srv_conf->tcpflags & TCPFLAG_NNODELAY)
+ val = 0;
+ else
+ val = 1;
+ if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY,
+ &val, sizeof(val)) == -1)
+ goto bad;
+ }
+ if (srv_conf->tcpflags & (TCPFLAG_SACK|TCPFLAG_NSACK)) {
+ if (srv_conf->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_config *srv_conf)
+{
+ int s;
+
+ if ((s = server_socket(ss, port, srv_conf, -1, 1)) == -1)
+ return (-1);
+
+ if (bind(s, (struct sockaddr *)ss, ss->ss_len) == -1)
+ goto bad;
+ if (listen(s, srv_conf->tcpbacklog) == -1)
+ goto bad;
+
+ return (s);
+
+ bad:
+ close(s);
+ return (-1);
+}
+
+int
+server_socket_connect(struct sockaddr_storage *ss, in_port_t port,
+ struct server_config *srv_conf)
+{
+ int s;
+
+ if ((s = server_socket(ss, port, srv_conf, -1, 0)) == -1)
+ return (-1);
+
+ if (connect(s, (struct sockaddr *)ss, ss->ss_len) == -1) {
+ if (errno != EINPROGRESS)
+ goto bad;
+ }
+
+ return (s);
+
+ bad:
+ close(s);
+ return (-1);
+}
+
+void
+server_tls_readcb(int fd, short event, void *arg)
+{
+ struct bufferevent *bufev = arg;
+ struct client *clt = bufev->cbarg;
+ char rbuf[IBUF_READ_SIZE];
+ int what = EVBUFFER_READ;
+ int howmuch = IBUF_READ_SIZE;
+ int ret;
+ size_t len;
+
+ if (event == EV_TIMEOUT) {
+ what |= EVBUFFER_TIMEOUT;
+ goto err;
+ }
+
+ if (bufev->wm_read.high != 0)
+ howmuch = MINIMUM(sizeof(rbuf), bufev->wm_read.high);
+
+ ret = tls_read(clt->clt_tls_ctx, rbuf, howmuch, &len);
+ if (ret == TLS_READ_AGAIN || ret == TLS_WRITE_AGAIN) {
+ goto retry;
+ } else if (ret != 0) {
+ what |= EVBUFFER_ERROR;
+ goto err;
+ }
+
+ if (evbuffer_add(bufev->input, rbuf, len) == -1) {
+ what |= EVBUFFER_ERROR;
+ goto err;
+ }
+
+ server_bufferevent_add(&bufev->ev_read, bufev->timeout_read);
+
+ len = EVBUFFER_LENGTH(bufev->input);
+ if (bufev->wm_read.low != 0 && len < bufev->wm_read.low)
+ return;
+ if (bufev->wm_read.high != 0 && len > bufev->wm_read.high) {
+ struct evbuffer *buf = bufev->input;
+ event_del(&bufev->ev_read);
+ evbuffer_setcb(buf, bufferevent_read_pressure_cb, bufev);
+ return;
+ }
+
+ if (bufev->readcb != NULL)
+ (*bufev->readcb)(bufev, bufev->cbarg);
+ return;
+
+ retry:
+ server_bufferevent_add(&bufev->ev_read, bufev->timeout_read);
+ return;
+
+ err:
+ (*bufev->errorcb)(bufev, what, bufev->cbarg);
+}
+
+void
+server_tls_writecb(int fd, short event, void *arg)
+{
+ struct bufferevent *bufev = arg;
+ struct client *clt = bufev->cbarg;
+ int ret;
+ short what = EVBUFFER_WRITE;
+ size_t len;
+
+ if (event == EV_TIMEOUT) {
+ what |= EVBUFFER_TIMEOUT;
+ goto err;
+ }
+
+ if (EVBUFFER_LENGTH(bufev->output)) {
+ if (clt->clt_buf == NULL) {
+ clt->clt_buflen = EVBUFFER_LENGTH(bufev->output);
+ if ((clt->clt_buf = malloc(clt->clt_buflen)) == NULL) {
+ what |= EVBUFFER_ERROR;
+ goto err;
+ }
+ bcopy(EVBUFFER_DATA(bufev->output),
+ clt->clt_buf, clt->clt_buflen);
+ }
+ ret = tls_write(clt->clt_tls_ctx, clt->clt_buf,
+ clt->clt_buflen, &len);
+ if (ret == TLS_READ_AGAIN || ret == TLS_WRITE_AGAIN) {
+ goto retry;
+ } else if (ret != 0) {
+ what |= EVBUFFER_ERROR;
+ goto err;
+ }
+ evbuffer_drain(bufev->output, len);
+ }
+ if (clt->clt_buf != NULL) {
+ free(clt->clt_buf);
+ clt->clt_buf = NULL;
+ clt->clt_buflen = 0;
+ }
+
+ if (EVBUFFER_LENGTH(bufev->output) != 0)
+ server_bufferevent_add(&bufev->ev_write, bufev->timeout_write);
+
+ if (bufev->writecb != NULL &&
+ EVBUFFER_LENGTH(bufev->output) <= bufev->wm_write.low)
+ (*bufev->writecb)(bufev, bufev->cbarg);
+ return;
+
+ retry:
+ if (clt->clt_buflen != 0)
+ server_bufferevent_add(&bufev->ev_write, bufev->timeout_write);
+ return;
+
+ err:
+ if (clt->clt_buf != NULL) {
+ free(clt->clt_buf);
+ clt->clt_buf = NULL;
+ clt->clt_buflen = 0;
+ }
+ (*bufev->errorcb)(bufev, what, bufev->cbarg);
+}
+
+void
+server_input(struct client *clt)
+{
+ struct server_config *srv_conf = clt->clt_srv_conf;
+ evbuffercb inrd = server_read;
+ evbuffercb inwr = server_write;
+ socklen_t slen;
+
+ 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;
+
+ slen = sizeof(clt->clt_sndbufsiz);
+ if (getsockopt(clt->clt_s, SOL_SOCKET, SO_SNDBUF,
+ &clt->clt_sndbufsiz, &slen) == -1) {
+ server_close(clt, "failed to get send buffer size");
+ return;
+ }
+
+ /*
+ * 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;
+ }
+
+ if (srv_conf->flags & SRVFLAG_TLS) {
+ event_set(&clt->clt_bev->ev_read, clt->clt_s, EV_READ,
+ server_tls_readcb, clt->clt_bev);
+ event_set(&clt->clt_bev->ev_write, clt->clt_s, EV_WRITE,
+ server_tls_writecb, clt->clt_bev);
+ }
+
+ /* Adjust write watermark to the socket buffer output size */
+ bufferevent_setwatermark(clt->clt_bev, EV_WRITE,
+ clt->clt_sndbufsiz, 0);
+ /* Read at most amount of data that fits in one fcgi record. */
+ bufferevent_setwatermark(clt->clt_bev, EV_READ, 0, FCGI_CONTENT_SIZE);
+
+ bufferevent_settimeout(clt->clt_bev,
+ srv_conf->timeout.tv_sec, 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;
+ struct evbuffer *dst = EVBUFFER_OUTPUT(bev);
+
+ if (EVBUFFER_LENGTH(dst) == 0 &&
+ clt->clt_toread == TOREAD_HTTP_NONE)
+ goto done;
+
+ getmonotime(&clt->clt_tv_last);
+
+ if (clt->clt_done)
+ goto done;
+
+ bufferevent_enable(bev, EV_READ);
+ return;
+ done:
+ (*bev->errorcb)(bev, EVBUFFER_WRITE|EVBUFFER_EOF, bev->cbarg);
+ return;
+}
+
+void
+server_dump(struct client *clt, const void *buf, size_t len)
+{
+ size_t outlen;
+
+ 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 (clt->clt_tls_ctx != NULL)
+ (void)tls_write(clt->clt_tls_ctx, buf, len, &outlen);
+ else
+ (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;
+ return;
+ done:
+ (*bev->errorcb)(bev, EVBUFFER_READ|EVBUFFER_EOF, bev->cbarg);
+ return;
+ fail:
+ server_close(clt, strerror(errno));
+}
+
+void
+server_error(struct bufferevent *bev, short error, void *arg)
+{
+ struct client *clt = arg;
+ struct evbuffer *dst;
+
+ if (error & EVBUFFER_TIMEOUT) {
+ server_close(clt, "buffer event timeout");
+ return;
+ }
+ if (error & EVBUFFER_ERROR) {
+ if (errno == EFBIG) {
+ bufferevent_enable(bev, EV_READ);
+ return;
+ }
+ server_close(clt, "buffer event error");
+ return;
+ }
+ if (error & (EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF)) {
+ bufferevent_disable(bev, EV_READ|EV_WRITE);
+
+ clt->clt_done = 1;
+
+ dst = EVBUFFER_OUTPUT(clt->clt_bev);
+ if (EVBUFFER_LENGTH(dst)) {
+ /* Finish writing all data first */
+ bufferevent_enable(clt->clt_bev, EV_WRITE);
+ return;
+ }
+
+ server_close(clt, "done");
+ return;
+ }
+ server_close(clt, "unknown 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_srv = srv;
+ clt->clt_srv_conf = &srv->srv_conf;
+ clt->clt_id = ++server_cltid;
+ clt->clt_srv_id = srv->srv_conf.id;
+ clt->clt_pid = getpid();
+ clt->clt_inflight = 1;
+
+ /* get local address */
+ slen = sizeof(clt->clt_srv_ss);
+ if (getsockname(s, (struct sockaddr *)&clt->clt_srv_ss,
+ &slen) == -1) {
+ server_close(clt, "listen address lookup failed");
+ return;
+ }
+
+ /* get client address */
+ memcpy(&clt->clt_ss, &ss, sizeof(clt->clt_ss));
+
+ /* get ports */
+ 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;
+ }
+
+ 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;
+ }
+
+ if (srv->srv_conf.flags & SRVFLAG_TLS) {
+ event_again(&clt->clt_ev, clt->clt_s, EV_TIMEOUT|EV_READ,
+ server_accept_tls, &clt->clt_tv_start,
+ &srv->srv_conf.timeout, clt);
+ 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_dec(clt, __func__);
+ }
+}
+
+void
+server_accept_tls(int fd, short event, void *arg)
+{
+ struct client *clt = (struct client *)arg;
+ struct server *srv = (struct server *)clt->clt_srv;
+ int ret;
+
+ if (event == EV_TIMEOUT) {
+ server_close(clt, "TLS accept timeout");
+ return;
+ }
+
+ if (srv->srv_tls_ctx == NULL)
+ fatalx("NULL tls context");
+
+ ret = tls_accept_socket(srv->srv_tls_ctx, &clt->clt_tls_ctx,
+ clt->clt_s);
+ if (ret == TLS_READ_AGAIN) {
+ event_again(&clt->clt_ev, clt->clt_s, EV_TIMEOUT|EV_READ,
+ server_accept_tls, &clt->clt_tv_start,
+ &srv->srv_conf.timeout, clt);
+ } else if (ret == TLS_WRITE_AGAIN) {
+ event_again(&clt->clt_ev, clt->clt_s, EV_TIMEOUT|EV_WRITE,
+ server_accept_tls, &clt->clt_tv_start,
+ &srv->srv_conf.timeout, clt);
+ } else if (ret != 0) {
+ log_warnx("%s: TLS accept failed - %s", __func__,
+ tls_error(srv->srv_tls_ctx));
+ return;
+ }
+
+ server_input(clt);
+ return;
+}
+
+void
+server_inflight_dec(struct client *clt, const char *why)
+{
+ if (clt != NULL) {
+ /* the flight already left inflight mode. */
+ if (clt->clt_inflight == 0)
+ return;
+ clt->clt_inflight = 0;
+ }
+
+ /* the file was never opened, thus this was an inflight client. */
+ server_inflight--;
+ DPRINTF("%s: inflight decremented, now %d, %s",
+ __func__, server_inflight, why);
+}
+
+void
+server_sendlog(struct server_config *srv_conf, int cmd, const char *emsg, ...)
+{
+ va_list ap;
+ char *msg;
+ int ret;
+ struct iovec iov[2];
+
+ if (srv_conf->flags & SRVFLAG_SYSLOG) {
+ va_start(ap, emsg);
+ if (cmd == IMSG_LOG_ACCESS)
+ vlog(LOG_INFO, emsg, ap);
+ else
+ vlog(LOG_DEBUG, emsg, ap);
+ va_end(ap);
+ return;
+ }
+
+ va_start(ap, emsg);
+ ret = vasprintf(&msg, emsg, ap);
+ va_end(ap);
+ if (ret == -1) {
+ log_warn("%s: vasprintf", __func__);
+ return;
+ }
+
+ iov[0].iov_base = &srv_conf->id;
+ iov[0].iov_len = sizeof(srv_conf->id);
+ iov[1].iov_base = msg;
+ iov[1].iov_len = strlen(msg) + 1;
+
+ proc_composev_imsg(env->sc_ps, PROC_LOGGER, -1, cmd, -1, iov, 2);
+}
+
+void
+server_log(struct client *clt, const char *msg)
+{
+ char ibuf[HOST_NAME_MAX+1], obuf[HOST_NAME_MAX+1];
+ struct server_config *srv_conf = clt->clt_srv_conf;
+ char *ptr = NULL;
+ int debug_cmd = -1;
+ extern int verbose;
+
+ switch (srv_conf->logformat) {
+ case LOG_FORMAT_CONNECTION:
+ debug_cmd = IMSG_LOG_ACCESS;
+ break;
+ default:
+ if (verbose > 1)
+ debug_cmd = IMSG_LOG_ERROR;
+ if (EVBUFFER_LENGTH(clt->clt_log)) {
+ while ((ptr =
+ evbuffer_readline(clt->clt_log)) != NULL) {
+ server_sendlog(srv_conf,
+ IMSG_LOG_ACCESS, "%s", ptr);
+ free(ptr);
+ }
+ }
+ break;
+ }
+
+ if (debug_cmd != -1 && msg != NULL) {
+ memset(ibuf, 0, sizeof(ibuf));
+ memset(obuf, 0, sizeof(obuf));
+ (void)print_host(&clt->clt_ss, ibuf, sizeof(ibuf));
+ (void)server_http_host(&clt->clt_srv_ss, obuf, sizeof(obuf));
+ if (EVBUFFER_LENGTH(clt->clt_log) &&
+ evbuffer_add_printf(clt->clt_log, "\n") != -1)
+ ptr = evbuffer_readline(clt->clt_log);
+ server_sendlog(srv_conf, debug_cmd, "server %s, "
+ "client %d (%d active), %s:%u -> %s, "
+ "%s%s%s", srv_conf->name, clt->clt_id, server_clients,
+ ibuf, ntohs(clt->clt_port), obuf, msg,
+ ptr == NULL ? "" : ",", ptr == NULL ? "" : ptr);
+ if (ptr != NULL)
+ free(ptr);
+ }
+}
+
+void
+server_close(struct client *clt, const char *msg)
+{
+ struct server *srv = clt->clt_srv;
+
+ SPLAY_REMOVE(client_tree, &srv->srv_clients, clt);
+
+ /* free the HTTP descriptors incl. headers */
+ server_close_http(clt);
+
+ event_del(&clt->clt_ev);
+ if (clt->clt_bev != NULL)
+ bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE);
+ if (clt->clt_srvbev != NULL)
+ bufferevent_disable(clt->clt_srvbev, EV_READ|EV_WRITE);
+
+ server_log(clt, msg);
+
+ if (clt->clt_bev != NULL)
+ bufferevent_free(clt->clt_bev);
+ if (clt->clt_output != NULL)
+ evbuffer_free(clt->clt_output);
+ if (clt->clt_srvevb != NULL)
+ evbuffer_free(clt->clt_srvevb);
+
+ if (clt->clt_srvbev != NULL)
+ bufferevent_free(clt->clt_srvbev);
+ if (clt->clt_fd != -1)
+ close(clt->clt_fd);
+ if (clt->clt_s != -1)
+ close(clt->clt_s);
+
+ if (clt->clt_tls_ctx != NULL)
+ tls_close(clt->clt_tls_ctx);
+ tls_free(clt->clt_tls_ctx);
+
+ server_inflight_dec(clt, __func__);
+
+ 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_MEDIA:
+ config_getmedia(env, imsg);
+ break;
+ case IMSG_CFG_AUTH:
+ config_getauth(env, imsg);
+ break;
+ 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_dispatch_logger(int fd, struct privsep_proc *p, struct imsg *imsg)
+{
+ switch (imsg->hdr.type) {
+ 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_printf(struct client *clt, const char *fmt, ...)
+{
+ int ret;
+ va_list ap;
+ char *str;
+
+ va_start(ap, fmt);
+ ret = vasprintf(&str, fmt, ap);
+ va_end(ap);
+
+ if (ret == -1)
+ return (ret);
+
+ ret = server_bufferevent_print(clt, str);
+ free(str);
+
+ return (ret);
+}
+
+int
+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/httpd/server_fcgi.c b/httpd/server_fcgi.c
new file mode 100644
index 0000000..33603a0
--- /dev/null
+++ b/httpd/server_fcgi.c
@@ -0,0 +1,729 @@
+/* $OpenBSD: server_fcgi.c,v 1.52 2015/02/23 19:22:43 chrisz Exp $ */
+
+/*
+ * Copyright (c) 2014 Florian Obser <florian@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/time.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <limits.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <time.h>
+#include <ctype.h>
+#include <event.h>
+
+#include "httpd.h"
+#include "http.h"
+
+#define FCGI_PADDING_SIZE 255
+#define FCGI_RECORD_SIZE \
+ (sizeof(struct fcgi_record_header) + FCGI_CONTENT_SIZE + FCGI_PADDING_SIZE)
+
+#define FCGI_BEGIN_REQUEST 1
+#define FCGI_ABORT_REQUEST 2
+#define FCGI_END_REQUEST 3
+#define FCGI_PARAMS 4
+#define FCGI_STDIN 5
+#define FCGI_STDOUT 6
+#define FCGI_STDERR 7
+#define FCGI_DATA 8
+#define FCGI_GET_VALUES 9
+#define FCGI_GET_VALUES_RESULT 10
+#define FCGI_UNKNOWN_TYPE 11
+#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
+
+#define FCGI_RESPONDER 1
+
+struct fcgi_record_header {
+ uint8_t version;
+ uint8_t type;
+ uint16_t id;
+ uint16_t content_len;
+ uint8_t padding_len;
+ uint8_t reserved;
+} __packed;
+
+struct fcgi_begin_request_body {
+ uint16_t role;
+ uint8_t flags;
+ uint8_t reserved[5];
+} __packed;
+
+struct server_fcgi_param {
+ int total_len;
+ uint8_t buf[FCGI_RECORD_SIZE];
+};
+
+int server_fcgi_header(struct client *, u_int);
+void server_fcgi_read(struct bufferevent *, void *);
+int server_fcgi_writeheader(struct client *, struct kv *, void *);
+int server_fcgi_writechunk(struct client *);
+int server_fcgi_getheaders(struct client *);
+int fcgi_add_param(struct server_fcgi_param *, const char *, const char *,
+ struct client *);
+
+int
+server_fcgi(struct httpd *env, struct client *clt)
+{
+ struct server_fcgi_param param;
+ struct server_config *srv_conf = clt->clt_srv_conf;
+ struct http_descriptor *desc = clt->clt_descreq;
+ struct fcgi_record_header *h;
+ struct fcgi_begin_request_body *begin;
+ char hbuf[HOST_NAME_MAX+1];
+ size_t scriptlen;
+ int pathlen;
+ int fd = -1, ret;
+ const char *stripped, *p, *alias, *errstr = NULL;
+ char *str, *script = NULL;
+
+ if (srv_conf->socket[0] == ':') {
+ struct sockaddr_storage ss;
+ in_port_t port;
+
+ p = srv_conf->socket + 1;
+
+ port = strtonum(p, 0, 0xffff, &errstr);
+ if (errstr != NULL) {
+ log_warn("%s: strtonum %s, %s", __func__, p, errstr);
+ goto fail;
+ }
+ memset(&ss, 0, sizeof(ss));
+ ss.ss_family = AF_INET;
+ ((struct sockaddr_in *)
+ &ss)->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ port = htons(port);
+
+ if ((fd = server_socket_connect(&ss, port, srv_conf)) == -1)
+ goto fail;
+ } else {
+ struct sockaddr_un sun;
+ size_t len;
+
+ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
+ goto fail;
+
+ memset(&sun, 0, sizeof(sun));
+ sun.sun_family = AF_UNIX;
+ len = strlcpy(sun.sun_path,
+ srv_conf->socket, sizeof(sun.sun_path));
+ if (len >= sizeof(sun.sun_path)) {
+ errstr = "socket path to long";
+ goto fail;
+ }
+ sun.sun_len = len;
+
+ if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1)
+ goto fail;
+ }
+
+ socket_set_blockmode(fd, BM_NONBLOCK);
+
+ memset(hbuf, 0, sizeof(hbuf));
+ clt->clt_fcgi_state = FCGI_READ_HEADER;
+ clt->clt_fcgi_toread = sizeof(struct fcgi_record_header);
+
+ if (clt->clt_srvevb != NULL)
+ evbuffer_free(clt->clt_srvevb);
+
+ clt->clt_srvevb = evbuffer_new();
+ if (clt->clt_srvevb == NULL) {
+ errstr = "failed to allocate evbuffer";
+ goto fail;
+ }
+
+ clt->clt_fd = fd;
+ if (clt->clt_srvbev != NULL)
+ bufferevent_free(clt->clt_srvbev);
+
+ clt->clt_srvbev = bufferevent_new(fd, server_fcgi_read,
+ NULL, server_file_error, clt);
+ if (clt->clt_srvbev == NULL) {
+ errstr = "failed to allocate fcgi buffer event";
+ goto fail;
+ }
+
+ memset(&param, 0, sizeof(param));
+
+ h = (struct fcgi_record_header *)&param.buf;
+ h->version = 1;
+ h->type = FCGI_BEGIN_REQUEST;
+ h->id = htons(1);
+ h->content_len = htons(sizeof(struct fcgi_begin_request_body));
+ h->padding_len = 0;
+
+ begin = (struct fcgi_begin_request_body *)&param.buf[sizeof(struct
+ fcgi_record_header)];
+ begin->role = htons(FCGI_RESPONDER);
+
+ bufferevent_write(clt->clt_srvbev, &param.buf,
+ sizeof(struct fcgi_record_header) +
+ sizeof(struct fcgi_begin_request_body));
+
+ h->type = FCGI_PARAMS;
+ h->content_len = param.total_len = 0;
+
+ alias = desc->http_path_alias != NULL
+ ? desc->http_path_alias
+ : desc->http_path;
+
+ stripped = server_root_strip(alias, srv_conf->strip);
+ if ((pathlen = asprintf(&script, "%s%s", srv_conf->root, stripped))
+ == -1) {
+ errstr = "failed to get script name";
+ goto fail;
+ }
+
+ scriptlen = path_info(script);
+ /*
+ * no part of root should show up in PATH_INFO.
+ * therefore scriptlen should be >= strlen(root)
+ */
+ if (scriptlen < strlen(srv_conf->root))
+ scriptlen = strlen(srv_conf->root);
+ if ((int)scriptlen < pathlen) {
+ if (fcgi_add_param(&param, "PATH_INFO",
+ script + scriptlen, clt) == -1) {
+ errstr = "failed to encode param";
+ goto fail;
+ }
+ script[scriptlen] = '\0';
+ }
+
+ /*
+ * calculate length of http SCRIPT_NAME:
+ * add length of stripped prefix,
+ * subtract length of prepended local root
+ */
+ scriptlen += (stripped - alias) - strlen(srv_conf->root);
+ if ((str = strndup(alias, scriptlen)) == NULL)
+ goto fail;
+ ret = fcgi_add_param(&param, "SCRIPT_NAME", str, clt);
+ free(str);
+ if (ret == -1) {
+ errstr = "failed to encode param";
+ goto fail;
+ }
+ if (fcgi_add_param(&param, "SCRIPT_FILENAME", script, clt) == -1) {
+ errstr = "failed to encode param";
+ goto fail;
+ }
+
+ if (desc->http_query)
+ if (fcgi_add_param(&param, "QUERY_STRING", desc->http_query,
+ clt) == -1) {
+ errstr = "failed to encode param";
+ goto fail;
+ }
+
+ if (fcgi_add_param(&param, "DOCUMENT_ROOT", srv_conf->root,
+ clt) == -1) {
+ errstr = "failed to encode param";
+ goto fail;
+ }
+ if (fcgi_add_param(&param, "DOCUMENT_URI", alias,
+ clt) == -1) {
+ errstr = "failed to encode param";
+ goto fail;
+ }
+ if (fcgi_add_param(&param, "GATEWAY_INTERFACE", "CGI/1.1",
+ clt) == -1) {
+ errstr = "failed to encode param";
+ goto fail;
+ }
+
+ if (srv_conf->flags & SRVFLAG_AUTH) {
+ if (fcgi_add_param(&param, "REMOTE_USER",
+ clt->clt_remote_user, clt) == -1) {
+ errstr = "failed to encode param";
+ goto fail;
+ }
+ }
+
+ /* Add HTTP_* headers */
+ if (server_headers(clt, desc, server_fcgi_writeheader, &param) == -1) {
+ errstr = "failed to encode param";
+ goto fail;
+ }
+
+ if (srv_conf->flags & SRVFLAG_TLS)
+ if (fcgi_add_param(&param, "HTTPS", "on", clt) == -1) {
+ errstr = "failed to encode param";
+ goto fail;
+ }
+
+ (void)print_host(&clt->clt_ss, hbuf, sizeof(hbuf));
+ if (fcgi_add_param(&param, "REMOTE_ADDR", hbuf, clt) == -1) {
+ errstr = "failed to encode param";
+ goto fail;
+ }
+
+ (void)snprintf(hbuf, sizeof(hbuf), "%d", ntohs(clt->clt_port));
+ if (fcgi_add_param(&param, "REMOTE_PORT", hbuf, clt) == -1) {
+ errstr = "failed to encode param";
+ goto fail;
+ }
+
+ if (fcgi_add_param(&param, "REQUEST_METHOD",
+ server_httpmethod_byid(desc->http_method), clt) == -1) {
+ errstr = "failed to encode param";
+ goto fail;
+ }
+
+ if (!desc->http_query) {
+ if (fcgi_add_param(&param, "REQUEST_URI", desc->http_path,
+ clt) == -1) {
+ errstr = "failed to encode param";
+ goto fail;
+ }
+ } else if (asprintf(&str, "%s?%s", desc->http_path,
+ desc->http_query) != -1) {
+ ret = fcgi_add_param(&param, "REQUEST_URI", str, clt);
+ free(str);
+ if (ret == -1) {
+ errstr = "failed to encode param";
+ goto fail;
+ }
+ }
+
+ (void)print_host(&clt->clt_srv_ss, hbuf, sizeof(hbuf));
+ if (fcgi_add_param(&param, "SERVER_ADDR", hbuf, clt) == -1) {
+ errstr = "failed to encode param";
+ goto fail;
+ }
+
+ (void)snprintf(hbuf, sizeof(hbuf), "%d",
+ ntohs(server_socket_getport(&clt->clt_srv_ss)));
+ if (fcgi_add_param(&param, "SERVER_PORT", hbuf, clt) == -1) {
+ errstr = "failed to encode param";
+ goto fail;
+ }
+
+ if (fcgi_add_param(&param, "SERVER_NAME", srv_conf->name,
+ clt) == -1) {
+ errstr = "failed to encode param";
+ goto fail;
+ }
+
+ if (fcgi_add_param(&param, "SERVER_PROTOCOL", desc->http_version,
+ clt) == -1) {
+ errstr = "failed to encode param";
+ goto fail;
+ }
+
+ if (fcgi_add_param(&param, "SERVER_SOFTWARE", HTTPD_SERVERNAME,
+ clt) == -1) {
+ errstr = "failed to encode param";
+ goto fail;
+ }
+
+ if (param.total_len != 0) { /* send last params record */
+ bufferevent_write(clt->clt_srvbev, &param.buf,
+ sizeof(struct fcgi_record_header) +
+ ntohs(h->content_len));
+ }
+
+ /* send "no more params" message */
+ h->content_len = 0;
+ bufferevent_write(clt->clt_srvbev, &param.buf,
+ sizeof(struct fcgi_record_header));
+
+ bufferevent_settimeout(clt->clt_srvbev,
+ srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec);
+ bufferevent_enable(clt->clt_srvbev, EV_READ|EV_WRITE);
+ if (clt->clt_toread != 0) {
+ server_read_httpcontent(clt->clt_bev, clt);
+ bufferevent_enable(clt->clt_bev, EV_READ);
+ } else {
+ bufferevent_disable(clt->clt_bev, EV_READ);
+ fcgi_add_stdin(clt, NULL);
+ }
+
+ if (strcmp(desc->http_version, "HTTP/1.1") == 0) {
+ clt->clt_fcgi_chunked = 1;
+ } else {
+ /* HTTP/1.0 does not support chunked encoding */
+ clt->clt_fcgi_chunked = 0;
+ clt->clt_persist = 0;
+ }
+ clt->clt_fcgi_end = 0;
+ clt->clt_done = 0;
+
+ free(script);
+ return (0);
+ fail:
+ free(script);
+ if (errstr == NULL)
+ errstr = strerror(errno);
+ server_abort_http(clt, 500, errstr);
+ return (-1);
+}
+
+int
+fcgi_add_stdin(struct client *clt, struct evbuffer *evbuf)
+{
+ struct fcgi_record_header h;
+
+ memset(&h, 0, sizeof(h));
+ h.version = 1;
+ h.type = FCGI_STDIN;
+ h.id = htons(1);
+ h.padding_len = 0;
+
+ if (evbuf == NULL) {
+ h.content_len = 0;
+ return bufferevent_write(clt->clt_srvbev, &h,
+ sizeof(struct fcgi_record_header));
+ } else {
+ h.content_len = htons(EVBUFFER_LENGTH(evbuf));
+ if (bufferevent_write(clt->clt_srvbev, &h,
+ sizeof(struct fcgi_record_header)) == -1)
+ return -1;
+ return bufferevent_write_buffer(clt->clt_srvbev, evbuf);
+ }
+ return (0);
+}
+
+int
+fcgi_add_param(struct server_fcgi_param *p, const char *key,
+ const char *val, struct client *clt)
+{
+ struct fcgi_record_header *h;
+ int len = 0;
+ int key_len = strlen(key);
+ int val_len = strlen(val);
+ uint8_t *param;
+
+ len += key_len + val_len;
+ len += key_len > 127 ? 4 : 1;
+ len += val_len > 127 ? 4 : 1;
+
+ DPRINTF("%s: %s[%d] => %s[%d], total_len: %d", __func__, key, key_len,
+ val, val_len, p->total_len);
+
+ if (len > FCGI_CONTENT_SIZE)
+ return (-1);
+
+ if (p->total_len + len > FCGI_CONTENT_SIZE) {
+ bufferevent_write(clt->clt_srvbev, p->buf,
+ sizeof(struct fcgi_record_header) + p->total_len);
+ p->total_len = 0;
+ }
+
+ h = (struct fcgi_record_header *)p->buf;
+ param = p->buf + sizeof(*h) + p->total_len;
+
+ if (key_len > 127) {
+ *param++ = ((key_len >> 24) & 0xff) | 0x80;
+ *param++ = ((key_len >> 16) & 0xff);
+ *param++ = ((key_len >> 8) & 0xff);
+ *param++ = (key_len & 0xff);
+ } else
+ *param++ = key_len;
+
+ if (val_len > 127) {
+ *param++ = ((val_len >> 24) & 0xff) | 0x80;
+ *param++ = ((val_len >> 16) & 0xff);
+ *param++ = ((val_len >> 8) & 0xff);
+ *param++ = (val_len & 0xff);
+ } else
+ *param++ = val_len;
+
+ memcpy(param, key, key_len);
+ param += key_len;
+ memcpy(param, val, val_len);
+
+ p->total_len += len;
+
+ h->content_len = htons(p->total_len);
+ return (0);
+}
+
+void
+server_fcgi_read(struct bufferevent *bev, void *arg)
+{
+ uint8_t buf[FCGI_RECORD_SIZE];
+ struct client *clt = (struct client *) arg;
+ struct fcgi_record_header *h;
+ size_t len;
+ char *ptr;
+
+ do {
+ len = bufferevent_read(bev, buf, clt->clt_fcgi_toread);
+ /* XXX error handling */
+ evbuffer_add(clt->clt_srvevb, buf, len);
+ clt->clt_fcgi_toread -= len;
+ DPRINTF("%s: len: %lu toread: %d state: %d type: %d",
+ __func__, len, clt->clt_fcgi_toread,
+ clt->clt_fcgi_state, clt->clt_fcgi_type);
+
+ if (clt->clt_fcgi_toread != 0)
+ return;
+
+ switch (clt->clt_fcgi_state) {
+ case FCGI_READ_HEADER:
+ clt->clt_fcgi_state = FCGI_READ_CONTENT;
+ h = (struct fcgi_record_header *)
+ EVBUFFER_DATA(clt->clt_srvevb);
+ DPRINTF("%s: record header: version %d type %d id %d "
+ "content len %d padding %d", __func__,
+ h->version, h->type, ntohs(h->id),
+ ntohs(h->content_len), h->padding_len);
+ clt->clt_fcgi_type = h->type;
+ clt->clt_fcgi_toread = ntohs(h->content_len);
+ clt->clt_fcgi_padding_len = h->padding_len;
+ evbuffer_drain(clt->clt_srvevb,
+ EVBUFFER_LENGTH(clt->clt_srvevb));
+ if (clt->clt_fcgi_toread != 0)
+ break;
+ else if (clt->clt_fcgi_type == FCGI_STDOUT &&
+ !clt->clt_chunk) {
+ server_abort_http(clt, 500, "empty stdout");
+ return;
+ }
+
+ /* fallthrough if content_len == 0 */
+ case FCGI_READ_CONTENT:
+ switch (clt->clt_fcgi_type) {
+ case FCGI_STDERR:
+ if (EVBUFFER_LENGTH(clt->clt_srvevb) > 0 &&
+ (ptr = get_string(
+ EVBUFFER_DATA(clt->clt_srvevb),
+ EVBUFFER_LENGTH(clt->clt_srvevb)))
+ != NULL) {
+ server_sendlog(clt->clt_srv_conf,
+ IMSG_LOG_ERROR, "%s", ptr);
+ free(ptr);
+ }
+ break;
+ case FCGI_STDOUT:
+ if (++clt->clt_chunk == 1) {
+ if (server_fcgi_header(clt,
+ server_fcgi_getheaders(clt))
+ == -1) {
+ server_abort_http(clt, 500,
+ "malformed fcgi headers");
+ return;
+ }
+ if (!EVBUFFER_LENGTH(clt->clt_srvevb))
+ break;
+ }
+ /* FALLTHROUGH */
+ case FCGI_END_REQUEST:
+ if (server_fcgi_writechunk(clt) == -1) {
+ server_abort_http(clt, 500,
+ "encoding error");
+ return;
+ }
+ break;
+ }
+ evbuffer_drain(clt->clt_srvevb,
+ EVBUFFER_LENGTH(clt->clt_srvevb));
+ if (!clt->clt_fcgi_padding_len) {
+ clt->clt_fcgi_state = FCGI_READ_HEADER;
+ clt->clt_fcgi_toread =
+ sizeof(struct fcgi_record_header);
+ } else {
+ clt->clt_fcgi_state = FCGI_READ_PADDING;
+ clt->clt_fcgi_toread =
+ clt->clt_fcgi_padding_len;
+ }
+ break;
+ case FCGI_READ_PADDING:
+ evbuffer_drain(clt->clt_srvevb,
+ EVBUFFER_LENGTH(clt->clt_srvevb));
+ clt->clt_fcgi_state = FCGI_READ_HEADER;
+ clt->clt_fcgi_toread =
+ sizeof(struct fcgi_record_header);
+ break;
+ }
+ } while (len > 0);
+}
+
+int
+server_fcgi_header(struct client *clt, u_int code)
+{
+ struct http_descriptor *desc = clt->clt_descreq;
+ struct http_descriptor *resp = clt->clt_descresp;
+ const char *error;
+ char tmbuf[32];
+ struct kv *kv, key;
+
+ if (desc == NULL || (error = server_httperror_byid(code)) == NULL)
+ return (-1);
+
+ if (server_log_http(clt, code, 0) == -1)
+ return (-1);
+
+ /* Add error codes */
+ if (kv_setkey(&resp->http_pathquery, "%lu", code) == -1 ||
+ kv_set(&resp->http_pathquery, "%s", error) == -1)
+ return (-1);
+
+ /* Add headers */
+ if (kv_add(&resp->http_headers, "Server", HTTPD_SERVERNAME) == NULL)
+ return (-1);
+
+ /* Set chunked encoding */
+ if (clt->clt_fcgi_chunked) {
+ /* XXX Should we keep and handle Content-Length instead? */
+ key.kv_key = "Content-Length";
+ if ((kv = kv_find(&resp->http_headers, &key)) != NULL)
+ kv_delete(&resp->http_headers, kv);
+
+ /*
+ * XXX What if the FastCGI added some kind of Transfer-Encoding?
+ * XXX like gzip, deflate or even "chunked"?
+ */
+ if (kv_add(&resp->http_headers,
+ "Transfer-Encoding", "chunked") == NULL)
+ return (-1);
+ }
+
+ /* Is it a persistent connection? */
+ if (clt->clt_persist) {
+ if (kv_add(&resp->http_headers,
+ "Connection", "keep-alive") == NULL)
+ return (-1);
+ } else if (kv_add(&resp->http_headers, "Connection", "close") == NULL)
+ return (-1);
+
+ /* Date header is mandatory and should be added as late as possible */
+ if (server_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0 ||
+ kv_add(&resp->http_headers, "Date", tmbuf) == NULL)
+ return (-1);
+
+ /* Write initial header (fcgi might append more) */
+ if (server_writeresponse_http(clt) == -1 ||
+ server_bufferevent_print(clt, "\r\n") == -1 ||
+ server_headers(clt, resp, server_writeheader_http, NULL) == -1 ||
+ server_bufferevent_print(clt, "\r\n") == -1)
+ return (-1);
+
+ return (0);
+}
+
+int
+server_fcgi_writeheader(struct client *clt, struct kv *hdr, void *arg)
+{
+ struct server_fcgi_param *param = arg;
+ char *val, *name, *p;
+ const char *key;
+ int ret;
+
+ 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;
+
+ val = hdr->kv_value;
+
+ if (strcasecmp(key, "Content-Length") == 0 ||
+ strcasecmp(key, "Content-Type") == 0) {
+ if ((name = strdup(key)) == NULL)
+ return (-1);
+ } else {
+ if (asprintf(&name, "HTTP_%s", key) == -1)
+ return (-1);
+ }
+
+ for (p = name; *p != '\0'; p++) {
+ if (isalpha((unsigned char)*p))
+ *p = toupper((unsigned char)*p);
+ else
+ *p = '_';
+ }
+
+ ret = fcgi_add_param(param, name, val, clt);
+ free(name);
+
+ return (ret);
+}
+
+int
+server_fcgi_writechunk(struct client *clt)
+{
+ struct evbuffer *evb = clt->clt_srvevb;
+ size_t len;
+
+ if (clt->clt_fcgi_type == FCGI_END_REQUEST) {
+ len = 0;
+ } else
+ len = EVBUFFER_LENGTH(evb);
+
+ if (clt->clt_fcgi_chunked) {
+ /* If len is 0, make sure to write the end marker only once */
+ if (len == 0 && clt->clt_fcgi_end++)
+ return (0);
+ if (server_bufferevent_printf(clt, "%zx\r\n", len) == -1 ||
+ server_bufferevent_write_chunk(clt, evb, len) == -1 ||
+ server_bufferevent_print(clt, "\r\n") == -1)
+ return (-1);
+ } else if (len)
+ return (server_bufferevent_write_buffer(clt, evb));
+
+ return (0);
+}
+
+int
+server_fcgi_getheaders(struct client *clt)
+{
+ struct http_descriptor *resp = clt->clt_descresp;
+ struct evbuffer *evb = clt->clt_srvevb;
+ int code = 200;
+ char *line, *key, *value;
+ const char *errstr;
+
+ while ((line = evbuffer_getline(evb)) != NULL && *line != '\0') {
+ key = line;
+
+ if ((value = strchr(key, ':')) == NULL)
+ break;
+ if (*value == ':') {
+ *value++ = '\0';
+ value += strspn(value, " \t");
+ } else {
+ *value++ = '\0';
+ }
+
+ DPRINTF("%s: %s: %s", __func__, key, value);
+
+ if (strcasecmp("Status", key) == 0) {
+ value[strcspn(value, " \t")] = '\0';
+ code = (int)strtonum(value, 100, 600, &errstr);
+ if (errstr != NULL || server_httperror_byid(
+ code) == NULL)
+ code = 200;
+ } else {
+ (void)kv_add(&resp->http_headers, key, value);
+ }
+ free(line);
+ }
+
+ return (code);
+}
diff --git a/httpd/server_file.c b/httpd/server_file.c
new file mode 100644
index 0000000..f697504
--- /dev/null
+++ b/httpd/server_file.c
@@ -0,0 +1,469 @@
+/* $OpenBSD: server_file.c,v 1.51 2015/02/12 10:05:29 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 - 2015 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/time.h>
+#include <sys/stat.h>
+
+#include <limits.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <dirent.h>
+#include <time.h>
+#include <event.h>
+
+#include "httpd.h"
+#include "http.h"
+
+#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b))
+#define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b))
+
+int server_file_access(struct httpd *, struct client *, char *, size_t);
+int server_file_request(struct httpd *, struct client *, char *,
+ struct stat *);
+int server_file_index(struct httpd *, struct client *, struct stat *);
+int server_file_method(struct client *);
+
+int
+server_file_access(struct httpd *env, struct client *clt,
+ char *path, size_t len)
+{
+ struct http_descriptor *desc = clt->clt_descreq;
+ struct server_config *srv_conf = clt->clt_srv_conf;
+ struct stat st;
+ char *newpath;
+ int ret;
+
+ errno = 0;
+
+ if (access(path, R_OK) == -1) {
+ goto fail;
+ } else if (stat(path, &st) == -1) {
+ goto fail;
+ } else if (S_ISDIR(st.st_mode)) {
+ /* Deny access if directory indexing is disabled */
+ if (srv_conf->flags & SRVFLAG_NO_INDEX) {
+ errno = EACCES;
+ goto fail;
+ }
+
+ if (desc->http_path_alias != NULL) {
+ /* Recursion - the index "file" is a directory? */
+ errno = EINVAL;
+ goto fail;
+ }
+
+ /* Redirect to path with trailing "/" */
+ if (path[strlen(path) - 1] != '/') {
+ if (asprintf(&newpath, "http%s://%s%s/",
+ srv_conf->flags & SRVFLAG_TLS ? "s" : "",
+ desc->http_host, desc->http_path) == -1)
+ return (500);
+ /* Path alias will be used for the redirection */
+ desc->http_path_alias = newpath;
+
+ /* Indicate that the file has been moved */
+ return (301);
+ }
+
+ /* Append the default index file to the location */
+ if (asprintf(&newpath, "%s%s", desc->http_path,
+ srv_conf->index) == -1)
+ return (500);
+ desc->http_path_alias = newpath;
+ if (server_getlocation(clt, newpath) != srv_conf) {
+ /* The location has changed */
+ return (server_file(env, clt));
+ }
+
+ /* Otherwise append the default index file to the path */
+ if (strlcat(path, srv_conf->index, len) >= len) {
+ errno = EACCES;
+ goto fail;
+ }
+
+ ret = server_file_access(env, clt, path, len);
+ if (ret == 404) {
+ /*
+ * Index file not found; fail if auto-indexing is
+ * not enabled, otherwise return success but
+ * indicate directory with S_ISDIR of the previous
+ * stat.
+ */
+ if ((srv_conf->flags & SRVFLAG_AUTO_INDEX) == 0) {
+ errno = EACCES;
+ goto fail;
+ }
+
+ return (server_file_index(env, clt, &st));
+ }
+ return (ret);
+ } else if (!S_ISREG(st.st_mode)) {
+ /* Don't follow symlinks and ignore special files */
+ errno = EACCES;
+ goto fail;
+ }
+
+ return (server_file_request(env, clt, path, &st));
+
+ fail:
+ switch (errno) {
+ case ENOENT:
+ case ENOTDIR:
+ return (404);
+ case EACCES:
+ return (403);
+ default:
+ return (500);
+ }
+
+ /* NOTREACHED */
+}
+
+int
+server_file(struct httpd *env, struct client *clt)
+{
+ struct http_descriptor *desc = clt->clt_descreq;
+ struct server_config *srv_conf = clt->clt_srv_conf;
+ char path[PATH_MAX];
+ const char *stripped, *errstr = NULL;
+ int ret = 500;
+
+ if (srv_conf->flags & SRVFLAG_FCGI)
+ return (server_fcgi(env, clt));
+
+ /* Request path is already canonicalized */
+ stripped = server_root_strip(
+ desc->http_path_alias != NULL ?
+ desc->http_path_alias : desc->http_path,
+ srv_conf->strip);
+ if ((size_t)snprintf(path, sizeof(path), "%s%s",
+ srv_conf->root, stripped) >= sizeof(path)) {
+ errstr = desc->http_path;
+ goto abort;
+ }
+
+ /* Returns HTTP status code on error */
+ if ((ret = server_file_access(env, clt, path, sizeof(path))) > 0) {
+ errstr = desc->http_path_alias != NULL ?
+ desc->http_path_alias : desc->http_path;
+ goto abort;
+ }
+
+ return (ret);
+
+ abort:
+ if (errstr == NULL)
+ errstr = strerror(errno);
+ server_abort_http(clt, ret, errstr);
+ return (-1);
+}
+
+int
+server_file_method(struct client *clt)
+{
+ struct http_descriptor *desc = clt->clt_descreq;
+
+ switch (desc->http_method) {
+ case HTTP_METHOD_GET:
+ case HTTP_METHOD_HEAD:
+ return (0);
+ default:
+ /* Other methods are not allowed */
+ errno = EACCES;
+ return (405);
+ }
+ /* NOTREACHED */
+}
+
+int
+server_file_request(struct httpd *env, struct client *clt, char *path,
+ struct stat *st)
+{
+ struct server_config *srv_conf = clt->clt_srv_conf;
+ struct media_type *media;
+ const char *errstr = NULL;
+ int fd = -1, ret, code = 500;
+
+ if ((ret = server_file_method(clt)) != 0) {
+ code = ret;
+ goto abort;
+ }
+
+ /* Now open the file, should be readable or we have another problem */
+ if ((fd = open(path, O_RDONLY)) == -1)
+ goto abort;
+
+ media = media_find(env->sc_mediatypes, path);
+ ret = server_response_http(clt, 200, media, st->st_size,
+ MINIMUM(time(NULL), st->st_mtim.tv_sec));
+ switch (ret) {
+ case -1:
+ goto fail;
+ case 0:
+ /* Connection is already finished */
+ close(fd);
+ goto done;
+ default:
+ break;
+ }
+
+ clt->clt_fd = fd;
+ if (clt->clt_srvbev != NULL)
+ bufferevent_free(clt->clt_srvbev);
+
+ clt->clt_srvbev = bufferevent_new(clt->clt_fd, server_read,
+ server_write, server_file_error, clt);
+ if (clt->clt_srvbev == NULL) {
+ errstr = "failed to allocate file buffer event";
+ goto fail;
+ }
+
+ /* Adjust read watermark to the socket output buffer size */
+ bufferevent_setwatermark(clt->clt_srvbev, EV_READ, 0,
+ clt->clt_sndbufsiz);
+
+ bufferevent_settimeout(clt->clt_srvbev,
+ srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec);
+ bufferevent_enable(clt->clt_srvbev, EV_READ);
+ bufferevent_disable(clt->clt_bev, EV_READ);
+
+ done:
+ server_reset_http(clt);
+ return (0);
+ fail:
+ bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE);
+ bufferevent_free(clt->clt_bev);
+ clt->clt_bev = NULL;
+ abort:
+ if (errstr == NULL)
+ errstr = strerror(errno);
+ server_abort_http(clt, code, errstr);
+ return (-1);
+}
+
+int
+server_file_index(struct httpd *env, struct client *clt, struct stat *st)
+{
+ char path[PATH_MAX];
+ char tmstr[21];
+ struct http_descriptor *desc = clt->clt_descreq;
+ struct server_config *srv_conf = clt->clt_srv_conf;
+ struct dirent **namelist, *dp;
+ int namesize, i, ret, fd = -1, namewidth, skip;
+ int code = 500;
+ struct evbuffer *evb = NULL;
+ struct media_type *media;
+ const char *stripped, *style;
+ char *escapeduri, *escapedhtml, *escapedpath;
+ struct tm tm;
+ time_t t, dir_mtime;
+
+ if ((ret = server_file_method(clt)) != 0) {
+ code = ret;
+ goto abort;
+ }
+
+ /* Request path is already canonicalized */
+ stripped = server_root_strip(desc->http_path, srv_conf->strip);
+ if ((size_t)snprintf(path, sizeof(path), "%s%s",
+ srv_conf->root, stripped) >= sizeof(path))
+ goto abort;
+
+ /* Now open the file, should be readable or we have another problem */
+ if ((fd = open(path, O_RDONLY)) == -1)
+ goto abort;
+
+ /* Save last modification time */
+ dir_mtime = MINIMUM(time(NULL), st->st_mtim.tv_sec);
+
+ if ((evb = evbuffer_new()) == NULL)
+ goto abort;
+
+ if ((namesize = scandir(path, &namelist, NULL, alphasort)) == -1)
+ goto abort;
+
+ /* Indicate failure but continue going through the list */
+ skip = 0;
+
+ if ((escapedpath = escape_html(desc->http_path)) == NULL)
+ goto fail;
+
+ /* A CSS stylesheet allows minimal customization by the user */
+ style = "body { background-color: white; color: black; font-family: "
+ "sans-serif; }\nhr { border: 0; border-bottom: 1px dashed; }\n";
+ /* Generate simple HTML index document */
+ if (evbuffer_add_printf(evb,
+ "<!DOCTYPE html>\n"
+ "<html>\n"
+ "<head>\n"
+ "<title>Index of %s</title>\n"
+ "<style type=\"text/css\"><!--\n%s\n--></style>\n"
+ "</head>\n"
+ "<body>\n"
+ "<h1>Index of %s</h1>\n"
+ "<hr>\n<pre>\n",
+ escapedpath, style, escapedpath) == -1)
+ skip = 1;
+
+ free(escapedpath);
+
+ for (i = 0; i < namesize; i++) {
+ dp = namelist[i];
+
+ if (skip ||
+ fstatat(fd, dp->d_name, st, 0) == -1) {
+ free(dp);
+ continue;
+ }
+
+ t = st->st_mtime;
+ localtime_r(&t, &tm);
+ strftime(tmstr, sizeof(tmstr), "%d-%h-%Y %R", &tm);
+ namewidth = 51 - strlen(dp->d_name);
+
+ if ((escapeduri = url_encode(dp->d_name)) == NULL)
+ goto fail;
+ if ((escapedhtml = escape_html(dp->d_name)) == NULL)
+ goto fail;
+
+ if (dp->d_name[0] == '.' &&
+ !(dp->d_name[1] == '.' && dp->d_name[2] == '\0')) {
+ /* ignore hidden files starting with a dot */
+ } else if (S_ISDIR(st->st_mode)) {
+ namewidth -= 1; /* trailing slash */
+ if (evbuffer_add_printf(evb,
+ "<a href=\"%s\">%s/</a>%*s%s%20s\n",
+ escapeduri, escapedhtml,
+ MAXIMUM(namewidth, 0), " ", tmstr, "-") == -1)
+ skip = 1;
+ } else if (S_ISREG(st->st_mode)) {
+ if (evbuffer_add_printf(evb,
+ "<a href=\"%s\">%s</a>%*s%s%20llu\n",
+ escapeduri, escapedhtml,
+ MAXIMUM(namewidth, 0), " ",
+ tmstr, st->st_size) == -1)
+ skip = 1;
+ }
+ free(escapeduri);
+ free(escapedhtml);
+ free(dp);
+ }
+ free(namelist);
+
+ if (skip ||
+ evbuffer_add_printf(evb,
+ "</pre>\n<hr>\n</body>\n</html>\n") == -1)
+ goto abort;
+
+ close(fd);
+ fd = -1;
+
+ media = media_find(env->sc_mediatypes, "index.html");
+ ret = server_response_http(clt, 200, media, EVBUFFER_LENGTH(evb),
+ dir_mtime);
+ switch (ret) {
+ case -1:
+ goto fail;
+ case 0:
+ /* Connection is already finished */
+ evbuffer_free(evb);
+ goto done;
+ default:
+ break;
+ }
+
+ if (server_bufferevent_write_buffer(clt, evb) == -1)
+ goto fail;
+ evbuffer_free(evb);
+ evb = NULL;
+
+ bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE);
+ if (clt->clt_persist)
+ clt->clt_toread = TOREAD_HTTP_HEADER;
+ else
+ clt->clt_toread = TOREAD_HTTP_NONE;
+ clt->clt_done = 0;
+
+ done:
+ server_reset_http(clt);
+ return (0);
+ fail:
+ bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE);
+ bufferevent_free(clt->clt_bev);
+ clt->clt_bev = NULL;
+ abort:
+ if (fd != -1)
+ close(fd);
+ if (evb != NULL)
+ evbuffer_free(evb);
+ server_abort_http(clt, code, desc->http_path);
+ return (-1);
+}
+
+void
+server_file_error(struct bufferevent *bev, short error, void *arg)
+{
+ struct client *clt = arg;
+ struct evbuffer *dst;
+
+ if (error & EVBUFFER_TIMEOUT) {
+ server_close(clt, "buffer event timeout");
+ return;
+ }
+ if (error & EVBUFFER_ERROR) {
+ if (errno == EFBIG) {
+ bufferevent_enable(bev, EV_READ);
+ return;
+ }
+ server_close(clt, "buffer event error");
+ return;
+ }
+ if (error & (EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF)) {
+ bufferevent_disable(bev, EV_READ|EV_WRITE);
+
+ clt->clt_done = 1;
+
+ if (clt->clt_persist) {
+ /* Close input file and wait for next HTTP request */
+ if (clt->clt_fd != -1)
+ close(clt->clt_fd);
+ clt->clt_fd = -1;
+ clt->clt_toread = TOREAD_HTTP_HEADER;
+ server_reset_http(clt);
+ bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE);
+ return;
+ }
+
+ dst = EVBUFFER_OUTPUT(clt->clt_bev);
+ if (EVBUFFER_LENGTH(dst)) {
+ /* Finish writing all data first */
+ bufferevent_enable(clt->clt_bev, EV_WRITE);
+ return;
+ }
+
+ server_close(clt, "done");
+ return;
+ }
+ server_close(clt, "unknown event error");
+ return;
+}
diff --git a/httpd/server_http.c b/httpd/server_http.c
new file mode 100644
index 0000000..b63fc22
--- /dev/null
+++ b/httpd/server_http.c
@@ -0,0 +1,1425 @@
+/* $OpenBSD: server_http.c,v 1.75 2015/02/23 18:43:18 reyk Exp $ */
+
+/*
+ * Copyright (c) 2006 - 2015 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/tree.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+#include <stdio.h>
+#include <time.h>
+#include <resolv.h>
+#include <event.h>
+#include <fnmatch.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 *);
+int server_http_authenticate(struct server_config *,
+ struct client *);
+char *server_expand_http(struct client *, const char *,
+ char *, size_t);
+
+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_descreq = desc;
+
+ if ((desc = calloc(1, sizeof(*desc))) == NULL) {
+ /* req will be cleaned up later */
+ return (-1);
+ }
+ RB_INIT(&desc->http_headers);
+ clt->clt_descresp = desc;
+
+ return (0);
+}
+
+void
+server_httpdesc_free(struct http_descriptor *desc)
+{
+ if (desc == NULL)
+ return;
+ if (desc->http_path != NULL) {
+ free(desc->http_path);
+ desc->http_path = NULL;
+ }
+ if (desc->http_path_alias != NULL) {
+ free(desc->http_path_alias);
+ desc->http_path_alias = NULL;
+ }
+ if (desc->http_query != NULL) {
+ free(desc->http_query);
+ desc->http_query = NULL;
+ }
+ if (desc->http_version != NULL) {
+ free(desc->http_version);
+ desc->http_version = NULL;
+ }
+ if (desc->http_host != NULL) {
+ free(desc->http_host);
+ desc->http_host = NULL;
+ }
+ kv_purge(&desc->http_headers);
+ desc->http_lastheader = NULL;
+ desc->http_method = 0;
+ desc->http_chunked = 0;
+}
+
+int
+server_http_authenticate(struct server_config *srv_conf, struct client *clt)
+{
+ char decoded[1024];
+ FILE *fp = NULL;
+ struct http_descriptor *desc = clt->clt_descreq;
+ struct auth *auth = srv_conf->auth;
+ struct kv *ba, key;
+ size_t linesize = 0;
+ ssize_t linelen;
+ int ret = -1;
+ char *line = NULL, *user = NULL, *pass = NULL;
+ char *clt_user = NULL, *clt_pass = NULL;
+
+ memset(decoded, 0, sizeof(decoded));
+ key.kv_key = "Authorization";
+
+ if ((ba = kv_find(&desc->http_headers, &key)) == NULL ||
+ ba->kv_value == NULL)
+ goto done;
+
+ if (strncmp(ba->kv_value, "Basic ", strlen("Basic ")) != 0)
+ goto done;
+
+ if (b64_pton(strchr(ba->kv_value, ' ') + 1, (u_int8_t *)decoded,
+ sizeof(decoded)) <= 0)
+ goto done;
+
+ if ((clt_pass = strchr(decoded, ':')) == NULL)
+ goto done;
+
+ clt_user = decoded;
+ *clt_pass++ = '\0';
+ if ((clt->clt_remote_user = strdup(clt_user)) == NULL)
+ goto done;
+
+ if (clt_pass == NULL)
+ goto done;
+
+ if ((fp = fopen(auth->auth_htpasswd, "r")) == NULL)
+ goto done;
+
+ while ((linelen = getline(&line, &linesize, fp)) != -1) {
+ if (line[linelen - 1] == '\n')
+ line[linelen - 1] = '\0';
+ user = line;
+ pass = strchr(line, ':');
+
+ if (pass == NULL) {
+ explicit_bzero(line, linelen);
+ continue;
+ }
+
+ *pass++ = '\0';
+
+ if (strcmp(clt_user, user) != 0) {
+ explicit_bzero(line, linelen);
+ continue;
+ }
+
+ if (crypt_checkpass(clt_pass, pass) == 0) {
+ explicit_bzero(line, linelen);
+ ret = 0;
+ break;
+ }
+ }
+done:
+ if (fp != NULL)
+ fclose(fp);
+
+ if (ba != NULL && ba->kv_value != NULL) {
+ explicit_bzero(ba->kv_value, strlen(ba->kv_value));
+ explicit_bzero(decoded, sizeof(decoded));
+ }
+
+ return (ret);
+}
+
+void
+server_read_http(struct bufferevent *bev, void *arg)
+{
+ struct client *clt = arg;
+ struct server_config *srv_conf = clt->clt_srv_conf;
+ struct http_descriptor *desc = clt->clt_descreq;
+ 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) {
+ server_abort_http(clt, 413, "request too large");
+ goto abort;
+ }
+
+ /*
+ * 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) {
+ server_abort_http(clt, 400, "malformed");
+ goto abort;
+ }
+
+ /* Append line to the last header, if present */
+ if (kv_extend(&desc->http_headers,
+ desc->http_lastheader, line) == NULL)
+ 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) {
+ server_abort_http(clt, 400, "malformed");
+ goto abort;
+ }
+
+ /*
+ * Decode request path and query
+ */
+ desc->http_path = strdup(value);
+ if (desc->http_path == NULL)
+ goto fail;
+
+ desc->http_version = strchr(desc->http_path, ' ');
+ if (desc->http_version == NULL)
+ goto fail;
+
+ *desc->http_version++ = '\0';
+ desc->http_query = strchr(desc->http_path, '?');
+ if (desc->http_query != NULL)
+ *desc->http_query++ = '\0';
+
+ /*
+ * Have to allocate the strings because they could
+ * be changed independently by the filters later.
+ */
+ if ((desc->http_version =
+ strdup(desc->http_version)) == NULL)
+ goto fail;
+
+ if (desc->http_query != NULL &&
+ (desc->http_query =
+ strdup(desc->http_query)) == NULL)
+ 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 ((size_t)clt->clt_toread >
+ srv_conf->maxrequestbody) {
+ server_abort_http(clt, 413, NULL);
+ 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)
+ 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:
+ /* WebDAV methods */
+ case HTTP_METHOD_COPY:
+ clt->clt_toread = 0;
+ break;
+ case HTTP_METHOD_POST:
+ case HTTP_METHOD_PUT:
+ case HTTP_METHOD_RESPONSE:
+ /* WebDAV methods */
+ case HTTP_METHOD_PROPFIND:
+ case HTTP_METHOD_PROPPATCH:
+ case HTTP_METHOD_MKCOL:
+ case HTTP_METHOD_LOCK:
+ case HTTP_METHOD_UNLOCK:
+ case HTTP_METHOD_VERSION_CONTROL:
+ case HTTP_METHOD_REPORT:
+ case HTTP_METHOD_CHECKOUT:
+ case HTTP_METHOD_CHECKIN:
+ case HTTP_METHOD_UNCHECKOUT:
+ case HTTP_METHOD_MKWORKSPACE:
+ case HTTP_METHOD_UPDATE:
+ case HTTP_METHOD_LABEL:
+ case HTTP_METHOD_MERGE:
+ case HTTP_METHOD_BASELINE_CONTROL:
+ case HTTP_METHOD_MKACTIVITY:
+ case HTTP_METHOD_ORDERPATCH:
+ case HTTP_METHOD_ACL:
+ case HTTP_METHOD_MKREDIRECTREF:
+ case HTTP_METHOD_UPDATEREDIRECTREF:
+ case HTTP_METHOD_SEARCH:
+ case HTTP_METHOD_PATCH:
+ /* HTTP request payload */
+ if (clt->clt_toread > 0)
+ bev->readcb = server_read_httpcontent;
+
+ /* Single-pass HTTP body */
+ if (clt->clt_toread < 0) {
+ clt->clt_toread = TOREAD_UNLIMITED;
+ bev->readcb = server_read;
+ }
+ break;
+ default:
+ server_abort_http(clt, 405, "method not allowed");
+ return;
+ }
+ 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)
+ bufferevent_disable(bev, EV_READ);
+ server_response(env, clt);
+ return;
+ }
+ 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));
+ 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 (fcgi_add_stdin(clt, src) == -1)
+ goto fail;
+ clt->clt_toread = 0;
+ } else {
+ if (fcgi_add_stdin(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) {
+ fcgi_add_stdin(clt, NULL);
+ clt->clt_toread = TOREAD_HTTP_HEADER;
+ bufferevent_disable(bev, EV_READ);
+ bev->readcb = server_read_http;
+ return;
+ }
+ if (clt->clt_done)
+ goto done;
+ if (bev->readcb != server_read_httpcontent)
+ bev->readcb(bev, arg);
+
+ return;
+ done:
+ 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 server *srv = clt->clt_srv;
+
+ server_log(clt, NULL);
+
+ server_httpdesc_free(clt->clt_descreq);
+ server_httpdesc_free(clt->clt_descresp);
+ clt->clt_headerlen = 0;
+ clt->clt_line = 0;
+ clt->clt_done = 0;
+ clt->clt_chunk = 0;
+ free(clt->clt_remote_user);
+ clt->clt_remote_user = NULL;
+ clt->clt_bev->readcb = server_read_http;
+ clt->clt_srv_conf = &srv->srv_conf;
+}
+
+ssize_t
+server_http_time(time_t t, char *tmbuf, size_t len)
+{
+ struct tm tm;
+
+ /* New HTTP/1.1 RFC 7231 prefers IMF-fixdate from RFC 5322 */
+ if (t == -1 || gmtime_r(&t, &tm) == NULL)
+ return (-1);
+ else
+ return (strftime(tmbuf, len, "%a, %d %h %Y %T %Z", &tm));
+}
+
+const char *
+server_http_host(struct sockaddr_storage *ss, char *buf, size_t len)
+{
+ char hbuf[HOST_NAME_MAX+1];
+ in_port_t port;
+
+ if (print_host(ss, buf, len) == NULL)
+ return (NULL);
+
+ port = ntohs(server_socket_getport(ss));
+ if (port == HTTP_PORT)
+ return (buf);
+
+ switch (ss->ss_family) {
+ case AF_INET:
+ if ((size_t)snprintf(hbuf, sizeof(hbuf),
+ "%s:%u", buf, port) >= sizeof(hbuf))
+ return (NULL);
+ break;
+ case AF_INET6:
+ if ((size_t)snprintf(hbuf, sizeof(hbuf),
+ "[%s]:%u", buf, port) >= sizeof(hbuf))
+ return (NULL);
+ break;
+ }
+
+ if (strlcpy(buf, hbuf, len) >= len)
+ return (NULL);
+
+ return (buf);
+}
+
+char *
+server_http_parsehost(char *host, char *buf, size_t len, int *portval)
+{
+ char *start, *end, *port;
+ const char *errstr = NULL;
+
+ if (strlcpy(buf, host, len) >= len) {
+ log_debug("%s: host name too long", __func__);
+ return (NULL);
+ }
+
+ start = buf;
+ end = port = NULL;
+
+ if (*start == '[' && (end = strchr(start, ']')) != NULL) {
+ /* Address enclosed in [] with port, eg. [2001:db8::1]:80 */
+ start++;
+ *end++ = '\0';
+ if ((port = strchr(end, ':')) == NULL || *port == '\0')
+ port = NULL;
+ else
+ port++;
+ memmove(buf, start, strlen(start) + 1);
+ } else if ((end = strchr(start, ':')) != NULL) {
+ /* Name or address with port, eg. www.example.com:80 */
+ *end++ = '\0';
+ port = end;
+ } else {
+ /* Name or address with default port, eg. www.example.com */
+ port = NULL;
+ }
+
+ if (port != NULL) {
+ /* Save the requested port */
+ *portval = strtonum(port, 0, 0xffff, &errstr);
+ if (errstr != NULL) {
+ log_debug("%s: invalid port: %s", __func__,
+ strerror(errno));
+ return (NULL);
+ }
+ *portval = htons(*portval);
+ } else {
+ /* Port not given, indicate the default port */
+ *portval = -1;
+ }
+
+ return (start);
+}
+
+void
+server_abort_http(struct client *clt, u_int code, const char *msg)
+{
+ struct server *srv = clt->clt_srv;
+ struct server_config *srv_conf = &srv->srv_conf;
+ struct bufferevent *bev = clt->clt_bev;
+ struct http_descriptor *desc = clt->clt_descreq;
+ const char *httperr = NULL, *style;
+ char *httpmsg, *body = NULL, *extraheader = NULL;
+ char tmbuf[32], hbuf[128];
+ char buf[IBUF_READ_SIZE], *ptr = NULL;
+ int bodylen;
+
+ if (code == 0) {
+ server_close(clt, "dropped");
+ return;
+ }
+
+ if ((httperr = server_httperror_byid(code)) == NULL)
+ httperr = "Unknown Error";
+
+ if (bev == NULL)
+ goto done;
+
+ if (server_log_http(clt, code, 0) == -1)
+ goto done;
+
+ /* Some system information */
+ if (print_host(&srv_conf->ss, hbuf, sizeof(hbuf)) == NULL)
+ goto done;
+
+ if (server_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0)
+ goto done;
+
+ /* Do not send details of the Internal Server Error */
+ switch (code) {
+ case 301:
+ case 302:
+ case 303:
+ if (msg == NULL)
+ break;
+ memset(buf, 0, sizeof(buf));
+ if ((ptr = server_expand_http(clt, msg,
+ buf, sizeof(buf))) == NULL)
+ goto done;
+ if ((ptr = url_encode(ptr)) == NULL)
+ goto done;
+ if (asprintf(&extraheader, "Location: %s\r\n", ptr) == -1) {
+ code = 500;
+ extraheader = NULL;
+ }
+ msg = ptr;
+ break;
+ case 401:
+ if (asprintf(&extraheader,
+ "WWW-Authenticate: Basic realm=\"%s\"\r\n", msg) == -1) {
+ code = 500;
+ extraheader = NULL;
+ }
+ break;
+ default:
+ /*
+ * Do not send details of the error. Traditionally,
+ * web servers responsed with the request path on 40x
+ * errors which could be abused to inject JavaScript etc.
+ * Instead of sanitizing the path here, we just don't
+ * reprint it.
+ */
+ break;
+ }
+
+ /* A CSS stylesheet allows minimal customization by the user */
+ style = "body { background-color: white; color: black; font-family: "
+ "'Comic Sans MS', 'Chalkboard SE', 'Comic Neue', sans-serif; }\n"
+ "hr { border: 0; border-bottom: 1px dashed; }\n";
+
+ /* Generate simple HTML error document */
+ if ((bodylen = asprintf(&body,
+ "<!DOCTYPE html>\n"
+ "<html>\n"
+ "<head>\n"
+ "<title>%03d %s</title>\n"
+ "<style type=\"text/css\"><!--\n%s\n--></style>\n"
+ "</head>\n"
+ "<body>\n"
+ "<h1>%03d %s</h1>\n"
+ "<hr>\n<address>%s</address>\n"
+ "</body>\n"
+ "</html>\n",
+ code, httperr, style, code, httperr, HTTPD_SERVERNAME)) == -1)
+ goto done;
+
+ /* Add basic HTTP headers */
+ 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"
+ "Content-Length: %d\r\n"
+ "%s"
+ "\r\n"
+ "%s",
+ code, httperr, tmbuf, HTTPD_SERVERNAME, bodylen,
+ extraheader == NULL ? "" : extraheader,
+ desc->http_method == HTTP_METHOD_HEAD ? "" : body) == -1)
+ goto done;
+
+ /* Dump the message without checking for success */
+ server_dump(clt, httpmsg, strlen(httpmsg));
+ free(httpmsg);
+
+ done:
+ free(body);
+ free(extraheader);
+ if (msg == NULL)
+ msg = "\"\"";
+ if (asprintf(&httpmsg, "%s (%03d %s)", msg, code, httperr) == -1) {
+ server_close(clt, msg);
+ } else {
+ server_close(clt, httpmsg);
+ free(httpmsg);
+ }
+ free(ptr);
+}
+
+void
+server_close_http(struct client *clt)
+{
+ struct http_descriptor *desc;
+
+ desc = clt->clt_descreq;
+ server_httpdesc_free(desc);
+ free(desc);
+ clt->clt_descreq = NULL;
+
+ desc = clt->clt_descresp;
+ server_httpdesc_free(desc);
+ free(desc);
+ clt->clt_descresp = NULL;
+ free(clt->clt_remote_user);
+ clt->clt_remote_user = NULL;
+}
+
+char *
+server_expand_http(struct client *clt, const char *val, char *buf,
+ size_t len)
+{
+ struct http_descriptor *desc = clt->clt_descreq;
+ struct server_config *srv_conf = clt->clt_srv_conf;
+ char ibuf[128], *str;
+
+ if (strlcpy(buf, val, len) >= len)
+ return (NULL);
+
+ if (strstr(val, "$DOCUMENT_URI") != NULL) {
+ if (expand_string(buf, len, "$DOCUMENT_URI",
+ desc->http_path) != 0)
+ return (NULL);
+ }
+ if (strstr(val, "$QUERY_STRING") != NULL) {
+ if (expand_string(buf, len, "$QUERY_STRING",
+ desc->http_query == NULL ? "" :
+ desc->http_query) != 0)
+ return (NULL);
+ }
+ if (strstr(val, "$REMOTE_") != NULL) {
+ if (strstr(val, "$REMOTE_ADDR") != NULL) {
+ if (print_host(&clt->clt_ss,
+ ibuf, sizeof(ibuf)) == NULL)
+ return (NULL);
+ if (expand_string(buf, len,
+ "$REMOTE_ADDR", ibuf) != 0)
+ return (NULL);
+ }
+ if (strstr(val, "$REMOTE_PORT") != NULL) {
+ snprintf(ibuf, sizeof(ibuf),
+ "%u", ntohs(clt->clt_port));
+ if (expand_string(buf, len,
+ "$REMOTE_PORT", ibuf) != 0)
+ return (NULL);
+ }
+ if (strstr(val, "$REMOTE_USER") != NULL) {
+ if ((srv_conf->flags & SRVFLAG_AUTH) &&
+ clt->clt_remote_user != NULL)
+ str = clt->clt_remote_user;
+ else
+ str = "";
+ if (expand_string(buf, len,
+ "$REMOTE_USER", str) != 0)
+ return (NULL);
+ }
+ }
+ if (strstr(val, "$REQUEST_URI") != NULL) {
+ if (desc->http_query == NULL) {
+ if ((str = strdup(desc->http_path)) == NULL)
+ return (NULL);
+ } else if (asprintf(&str, "%s?%s",
+ desc->http_path, desc->http_query) == -1)
+ return (NULL);
+ if (expand_string(buf, len, "$REQUEST_URI", str) != 0) {
+ free(str);
+ return (NULL);
+ }
+ free(str);
+ }
+ if (strstr(val, "$SERVER_") != NULL) {
+ if (strstr(val, "$SERVER_ADDR") != NULL) {
+ if (print_host(&srv_conf->ss,
+ ibuf, sizeof(ibuf)) == NULL)
+ return (NULL);
+ if (expand_string(buf, len,
+ "$SERVER_ADDR", ibuf) != 0)
+ return (NULL);
+ }
+ if (strstr(val, "$SERVER_PORT") != NULL) {
+ snprintf(ibuf, sizeof(ibuf), "%u",
+ ntohs(srv_conf->port));
+ if (expand_string(buf, len,
+ "$SERVER_PORT", ibuf) != 0)
+ return (NULL);
+ }
+ if (strstr(val, "$SERVER_NAME") != NULL) {
+ if (expand_string(buf, len,
+ "$SERVER_NAME", srv_conf->name) != 0)
+ return (NULL);
+ }
+ }
+
+ return (buf);
+}
+
+int
+server_response(struct httpd *httpd, struct client *clt)
+{
+ char path[PATH_MAX];
+ char hostname[HOST_NAME_MAX+1];
+ struct http_descriptor *desc = clt->clt_descreq;
+ struct http_descriptor *resp = clt->clt_descresp;
+ struct server *srv = clt->clt_srv;
+ struct server_config *srv_conf = &srv->srv_conf;
+ struct kv *kv, key, *host;
+ int portval = -1;
+ char *hostval;
+
+ /* Canonicalize the request path */
+ if (desc->http_path == NULL ||
+ url_decode(desc->http_path) == NULL ||
+ canonicalize_path(desc->http_path, path, sizeof(path)) == NULL)
+ goto fail;
+ free(desc->http_path);
+ if ((desc->http_path = strdup(path)) == NULL)
+ goto fail;
+
+ key.kv_key = "Host";
+ if ((host = kv_find(&desc->http_headers, &key)) != NULL &&
+ host->kv_value == NULL)
+ host = NULL;
+
+ if (strcmp(desc->http_version, "HTTP/1.1") == 0) {
+ /* Host header is mandatory */
+ if (host == NULL)
+ goto fail;
+
+ /* Is the connection persistent? */
+ key.kv_key = "Connection";
+ if ((kv = kv_find(&desc->http_headers, &key)) != NULL &&
+ strcasecmp("close", kv->kv_value) == 0)
+ clt->clt_persist = 0;
+ else
+ clt->clt_persist++;
+ } else {
+ /* Is the connection persistent? */
+ key.kv_key = "Connection";
+ if ((kv = kv_find(&desc->http_headers, &key)) != NULL &&
+ strcasecmp("keep-alive", kv->kv_value) == 0)
+ clt->clt_persist++;
+ else
+ clt->clt_persist = 0;
+ }
+
+ if (clt->clt_persist >= srv_conf->maxrequests)
+ clt->clt_persist = 0;
+
+ /*
+ * Do we have a Host header and matching configuration?
+ * XXX the Host can also appear in the URL path.
+ */
+ if (host != NULL) {
+ if ((hostval = server_http_parsehost(host->kv_value,
+ hostname, sizeof(hostname), &portval)) == NULL)
+ goto fail;
+
+ TAILQ_FOREACH(srv_conf, &srv->srv_hosts, entry) {
+#ifdef DEBUG
+ if ((srv_conf->flags & SRVFLAG_LOCATION) == 0) {
+ DPRINTF("%s: virtual host \"%s:%u\""
+ " host \"%s\" (\"%s\")",
+ __func__, srv_conf->name,
+ ntohs(srv_conf->port), host->kv_value,
+ hostname);
+ }
+#endif
+ if ((srv_conf->flags & SRVFLAG_LOCATION) == 0 &&
+ fnmatch(srv_conf->name, hostname,
+ FNM_CASEFOLD) == 0 &&
+ (portval == -1 ||
+ (portval != -1 && portval == srv_conf->port))) {
+ /* Replace host configuration */
+ clt->clt_srv_conf = srv_conf;
+ srv_conf = NULL;
+ break;
+ }
+ }
+ }
+
+ if (srv_conf != NULL) {
+ /* Use the actual server IP address */
+ if (server_http_host(&clt->clt_srv_ss, hostname,
+ sizeof(hostname)) == NULL)
+ goto fail;
+ } else {
+ /* Host header was valid and found */
+ if (strlcpy(hostname, host->kv_value, sizeof(hostname)) >=
+ sizeof(hostname))
+ goto fail;
+ srv_conf = clt->clt_srv_conf;
+ }
+
+ if ((desc->http_host = strdup(hostname)) == NULL)
+ goto fail;
+
+ /* Now fill in the mandatory parts of the response descriptor */
+ resp->http_method = desc->http_method;
+ if ((resp->http_version = strdup(desc->http_version)) == NULL)
+ goto fail;
+
+ /* Now search for the location */
+ srv_conf = server_getlocation(clt, desc->http_path);
+
+ if (srv_conf->flags & SRVFLAG_BLOCK) {
+ server_abort_http(clt, srv_conf->return_code,
+ srv_conf->return_uri);
+ return (-1);
+ } else if (srv_conf->flags & SRVFLAG_AUTH &&
+ server_http_authenticate(srv_conf, clt) == -1) {
+ server_abort_http(clt, 401, srv_conf->auth_realm);
+ return (-1);
+ } else
+ return (server_file(httpd, clt));
+ fail:
+ server_abort_http(clt, 400, "bad request");
+ return (-1);
+}
+
+const char *
+server_root_strip(const char *path, int n)
+{
+ const char *p;
+
+ /* Strip strip leading directories. Leading '/' is ignored. */
+ for (; n > 0 && *path != '\0'; n--)
+ if ((p = strchr(++path, '/')) == NULL)
+ path = strchr(path, '\0');
+ else
+ path = p;
+
+ return (path);
+}
+
+struct server_config *
+server_getlocation(struct client *clt, const char *path)
+{
+ struct server *srv = clt->clt_srv;
+ struct server_config *srv_conf = clt->clt_srv_conf, *location;
+
+ /* Now search for the location */
+ TAILQ_FOREACH(location, &srv->srv_hosts, entry) {
+#ifdef DEBUG
+ if (location->flags & SRVFLAG_LOCATION) {
+ DPRINTF("%s: location \"%s\" path \"%s\"",
+ __func__, location->location, path);
+ }
+#endif
+ if ((location->flags & SRVFLAG_LOCATION) &&
+ location->parent_id == srv_conf->parent_id &&
+ fnmatch(location->location, path, FNM_CASEFOLD) == 0) {
+ /* Replace host configuration */
+ clt->clt_srv_conf = srv_conf = location;
+ break;
+ }
+ }
+
+ return (srv_conf);
+}
+
+int
+server_response_http(struct client *clt, u_int code,
+ struct media_type *media, size_t size, time_t mtime)
+{
+ struct http_descriptor *desc = clt->clt_descreq;
+ struct http_descriptor *resp = clt->clt_descresp;
+ const char *error;
+ struct kv *ct, *cl;
+ char tmbuf[32];
+
+ if (desc == NULL || (error = server_httperror_byid(code)) == NULL)
+ return (-1);
+
+ if (server_log_http(clt, code, size) == -1)
+ return (-1);
+
+ /* Add error codes */
+ if (kv_setkey(&resp->http_pathquery, "%lu", code) == -1 ||
+ kv_set(&resp->http_pathquery, "%s", error) == -1)
+ return (-1);
+
+ /* Add headers */
+ if (kv_add(&resp->http_headers, "Server", HTTPD_SERVERNAME) == NULL)
+ return (-1);
+
+ /* Is it a persistent connection? */
+ if (clt->clt_persist) {
+ if (kv_add(&resp->http_headers,
+ "Connection", "keep-alive") == NULL)
+ return (-1);
+ } else if (kv_add(&resp->http_headers, "Connection", "close") == NULL)
+ return (-1);
+
+ /* Set media type */
+ if ((ct = kv_add(&resp->http_headers, "Content-Type", NULL)) == NULL ||
+ kv_set(ct, "%s/%s",
+ media == NULL ? "application" : media->media_type,
+ media == NULL ? "octet-stream" : media->media_subtype) == -1)
+ return (-1);
+
+ /* Set content length, if specified */
+ if ((cl =
+ kv_add(&resp->http_headers, "Content-Length", NULL)) == NULL ||
+ kv_set(cl, "%ld", size) == -1)
+ return (-1);
+
+ /* Set last modification time */
+ if (server_http_time(mtime, tmbuf, sizeof(tmbuf)) <= 0 ||
+ kv_add(&resp->http_headers, "Last-Modified", tmbuf) == NULL)
+ return (-1);
+
+ /* Date header is mandatory and should be added as late as possible */
+ if (server_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0 ||
+ kv_add(&resp->http_headers, "Date", tmbuf) == NULL)
+ return (-1);
+
+ /* Write completed header */
+ if (server_writeresponse_http(clt) == -1 ||
+ server_bufferevent_print(clt, "\r\n") == -1 ||
+ server_headers(clt, resp, server_writeheader_http, NULL) == -1 ||
+ server_bufferevent_print(clt, "\r\n") == -1)
+ return (-1);
+
+ if (size == 0 || resp->http_method == HTTP_METHOD_HEAD) {
+ bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE);
+ if (clt->clt_persist)
+ clt->clt_toread = TOREAD_HTTP_HEADER;
+ else
+ clt->clt_toread = TOREAD_HTTP_NONE;
+ clt->clt_done = 0;
+ return (0);
+ }
+
+ return (1);
+}
+
+int
+server_writeresponse_http(struct client *clt)
+{
+ struct http_descriptor *desc = clt->clt_descresp;
+
+ 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_http(struct client *clt, struct kv *hdr, void *arg)
+{
+ 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;
+ 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_headers(struct client *clt, void *descp,
+ int (*hdr_cb)(struct client *, struct kv *, void *), void *arg)
+{
+ struct kv *hdr, *kv;
+ struct http_descriptor *desc = descp;
+
+ RB_FOREACH(hdr, kvtree, &desc->http_headers) {
+ if ((hdr_cb)(clt, hdr, arg) == -1)
+ return (-1);
+ TAILQ_FOREACH(kv, &hdr->kv_children, kv_entry) {
+ if ((hdr_cb)(clt, kv, arg) == -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;
+
+ 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 = "<UNKNOWN>";
+ 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;
+
+ /*
+ * RFC 2616 section 5.1.1 says that the method is case
+ * sensitive so we don't do a strcasecmp here.
+ */
+ return (strcmp(ma->method_name, mb->method_name));
+}
+
+const char *
+server_httperror_byid(u_int id)
+{
+ struct http_error error, *res;
+
+ /* Set up key */
+ error.error_code = (int)id;
+
+ if ((res = bsearch(&error, http_errors,
+ sizeof(http_errors) / sizeof(http_errors[0]) - 1,
+ sizeof(http_errors[0]), server_httperror_cmp)) != NULL)
+ return (res->error_name);
+
+ return (NULL);
+}
+
+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);
+}
+
+int
+server_log_http(struct client *clt, u_int code, size_t len)
+{
+ static char tstamp[64];
+ static char ip[INET6_ADDRSTRLEN];
+ time_t t;
+ struct kv key, *agent, *referrer;
+ struct tm *tm;
+ struct server_config *srv_conf;
+ struct http_descriptor *desc;
+
+ if ((srv_conf = clt->clt_srv_conf) == NULL)
+ return (-1);
+ if ((srv_conf->flags & SRVFLAG_LOG) == 0)
+ return (0);
+ if ((desc = clt->clt_descreq) == NULL)
+ return (-1);
+
+ if ((t = time(NULL)) == -1)
+ return (-1);
+ if ((tm = localtime(&t)) == NULL)
+ return (-1);
+ if (strftime(tstamp, sizeof(tstamp), "%d/%b/%Y:%H:%M:%S %z", tm) == 0)
+ return (-1);
+
+ if (print_host(&clt->clt_ss, ip, sizeof(ip)) == NULL)
+ return (-1);
+
+ /*
+ * For details on common log format, see:
+ * https://httpd.apache.org/docs/current/mod/mod_log_config.html
+ *
+ * httpd's format is similar to these Apache LogFormats:
+ * "%v %h %l %u %t \"%r\" %>s %B"
+ * "%v %h %l %u %t \"%r\" %>s %B \"%{Referer}i\" \"%{User-agent}i\""
+ */
+ switch (srv_conf->logformat) {
+ case LOG_FORMAT_COMMON:
+ if (evbuffer_add_printf(clt->clt_log,
+ "%s %s - %s [%s] \"%s %s%s%s%s%s\" %03d %zu\n",
+ srv_conf->name, ip, clt->clt_remote_user == NULL ? "-" :
+ clt->clt_remote_user, tstamp,
+ server_httpmethod_byid(desc->http_method),
+ desc->http_path == NULL ? "" : desc->http_path,
+ desc->http_query == NULL ? "" : "?",
+ desc->http_query == NULL ? "" : desc->http_query,
+ desc->http_version == NULL ? "" : " ",
+ desc->http_version == NULL ? "" : desc->http_version,
+ code, len) == -1)
+ return (-1);
+ break;
+
+ case LOG_FORMAT_COMBINED:
+ key.kv_key = "Referer"; /* sic */
+ if ((referrer = kv_find(&desc->http_headers, &key)) != NULL &&
+ referrer->kv_value == NULL)
+ referrer = NULL;
+
+ key.kv_key = "User-Agent";
+ if ((agent = kv_find(&desc->http_headers, &key)) != NULL &&
+ agent->kv_value == NULL)
+ agent = NULL;
+
+ if (evbuffer_add_printf(clt->clt_log,
+ "%s %s - %s [%s] \"%s %s%s%s%s%s\""
+ " %03d %zu \"%s\" \"%s\"\n",
+ srv_conf->name, ip, clt->clt_remote_user == NULL ? "-" :
+ clt->clt_remote_user, tstamp,
+ server_httpmethod_byid(desc->http_method),
+ desc->http_path == NULL ? "" : desc->http_path,
+ desc->http_query == NULL ? "" : "?",
+ desc->http_query == NULL ? "" : desc->http_query,
+ desc->http_version == NULL ? "" : " ",
+ desc->http_version == NULL ? "" : desc->http_version,
+ code, len,
+ referrer == NULL ? "" : referrer->kv_value,
+ agent == NULL ? "" : agent->kv_value) == -1)
+ return (-1);
+ break;
+
+ case LOG_FORMAT_CONNECTION:
+ if (evbuffer_add_printf(clt->clt_log, " [%s]",
+ desc->http_path == NULL ? "" : desc->http_path) == -1)
+ return (-1);
+ break;
+ }
+
+ return (0);
+}