aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorReyk Floeter <reyk@esdenera.com>2014-07-12 19:00:52 +0200
committerReyk Floeter <reyk@esdenera.com>2014-07-12 19:00:52 +0200
commit3d88bcbb432cda12794bc4e0aac180e5489901ff (patch)
treea98a20f0806c3e5476bc64798d503f70442c4a97
downloadhttpd-3d88bcbb432cda12794bc4e0aac180e5489901ff.tar.gz
httpd-3d88bcbb432cda12794bc4e0aac180e5489901ff.zip
Import httpd experiment based on relayd.
-rw-r--r--.gitignore2
-rw-r--r--Makefile18
-rw-r--r--config.c147
-rw-r--r--control.c337
-rw-r--r--http.h122
-rw-r--r--httpd.849
-rw-r--r--httpd.c527
-rw-r--r--httpd.conf.51567
-rw-r--r--httpd.h395
-rw-r--r--log.c251
-rw-r--r--parse.y1019
-rw-r--r--proc.c627
-rw-r--r--server.c636
-rw-r--r--server_http.c1736
14 files changed, 7433 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..67f3c82
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+.cvsignore
+CVS
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..066c649
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,18 @@
+# $OpenBSD$
+
+PROG= httpd
+SRCS= parse.y
+SRCS+= config.c control.c httpd.c log.c proc.c server.c
+MAN= httpd.8 httpd.conf.5
+
+LDADD= -levent -lssl -lcrypto -lutil
+DPADD= ${LIBEVENT} ${LIBSSL} ${LIBCRYPTO} ${LIBUTIL}
+
+CFLAGS+= -Wall -I${.CURDIR} -Werror
+CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes
+CFLAGS+= -Wmissing-declarations
+CFLAGS+= -Wshadow -Wpointer-arith
+CFLAGS+= -Wsign-compare
+CLEANFILES+= y.tab.h
+
+.include <bsd.prog.mk>
diff --git a/config.c b/config.c
new file mode 100644
index 0000000..8a64c4b
--- /dev/null
+++ b/config.c
@@ -0,0 +1,147 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2011 - 2014 Reyk Floeter <reyk@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+
+#include <net/if.h>
+#include <net/pfvar.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <net/route.h>
+
+#include <ctype.h>
+#include <unistd.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <ifaddrs.h>
+
+#include <openssl/ssl.h>
+
+#include "httpd.h"
+
+int
+config_init(struct httpd *env)
+{
+ struct privsep *ps = env->sc_ps;
+ u_int what;
+
+ /* Global configuration */
+ if (privsep_process == PROC_PARENT) {
+ env->sc_prefork_server = SERVER_NUMPROC;
+
+ ps->ps_what[PROC_PARENT] = CONFIG_ALL;
+ ps->ps_what[PROC_SERVER] = CONFIG_SERVERS;
+ }
+
+ /* Other configuration */
+ what = ps->ps_what[privsep_process];
+
+ if (what & CONFIG_SERVERS) {
+#if 0
+ if ((env->sc_servers =
+ calloc(1, sizeof(*env->sc_relays))) == NULL)
+ return (-1);
+ TAILQ_INIT(env->sc_relays);
+ if ((env->sc_pkeys =
+ calloc(1, sizeof(*env->sc_pkeys))) == NULL)
+ return (-1);
+ TAILQ_INIT(env->sc_pkeys);
+#endif
+ }
+
+ return (0);
+}
+
+void
+config_purge(struct httpd *env, u_int reset)
+{
+ struct privsep *ps = env->sc_ps;
+ struct server *srv;
+ u_int what;
+
+ what = ps->ps_what[privsep_process] & reset;
+
+ if (what & CONFIG_SERVERS && env->sc_servers != NULL) {
+ while ((srv = TAILQ_FIRST(env->sc_servers)) != NULL) {
+ TAILQ_REMOVE(env->sc_servers, srv, srv_entry);
+ free(srv);
+ }
+ }
+}
+
+int
+config_setreset(struct httpd *env, u_int reset)
+{
+ struct privsep *ps = env->sc_ps;
+ int id;
+
+ for (id = 0; id < PROC_MAX; id++) {
+ if ((reset & ps->ps_what[id]) == 0 ||
+ id == privsep_process)
+ continue;
+ proc_compose_imsg(ps, id, -1, IMSG_CTL_RESET, -1,
+ &reset, sizeof(reset));
+ }
+
+ return (0);
+}
+
+int
+config_getreset(struct httpd *env, struct imsg *imsg)
+{
+ u_int mode;
+
+ IMSG_SIZE_CHECK(imsg, &mode);
+ memcpy(&mode, imsg->data, sizeof(mode));
+
+ config_purge(env, mode);
+
+ return (0);
+}
+
+int
+config_getcfg(struct httpd *env, struct imsg *imsg)
+{
+ struct privsep *ps = env->sc_ps;
+ struct ctl_flags cf;
+ u_int what;
+
+ if (IMSG_DATA_SIZE(imsg) != sizeof(cf))
+ return (0); /* ignore */
+
+ /* Update runtime flags */
+ memcpy(&cf, imsg->data, sizeof(cf));
+ env->sc_opts = cf.cf_opts;
+ env->sc_flags = cf.cf_flags;
+
+ what = ps->ps_what[privsep_process];
+
+ return (0);
+}
diff --git a/control.c b/control.c
new file mode 100644
index 0000000..20e0bea
--- /dev/null
+++ b/control.c
@@ -0,0 +1,337 @@
+/* $OpenBSD: control.c,v 1.46 2014/07/11 16:39:10 krw Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/queue.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <net/if.h>
+
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include <openssl/ssl.h>
+
+#include "httpd.h"
+
+#define CONTROL_BACKLOG 5
+
+struct ctl_connlist ctl_conns;
+
+void control_accept(int, short, void *);
+void control_close(int, struct control_sock *);
+
+int
+control_init(struct privsep *ps, struct control_sock *cs)
+{
+ struct httpd *env = ps->ps_env;
+ struct sockaddr_un sun;
+ int fd;
+ mode_t old_umask, mode;
+
+ if (cs->cs_name == NULL)
+ return (0);
+
+ if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+ log_warn("%s: socket", __func__);
+ return (-1);
+ }
+
+ sun.sun_family = AF_UNIX;
+ if (strlcpy(sun.sun_path, cs->cs_name,
+ sizeof(sun.sun_path)) >= sizeof(sun.sun_path)) {
+ log_warn("%s: %s name too long", __func__, cs->cs_name);
+ close(fd);
+ return (-1);
+ }
+
+ if (unlink(cs->cs_name) == -1)
+ if (errno != ENOENT) {
+ log_warn("%s: unlink %s", __func__, cs->cs_name);
+ close(fd);
+ return (-1);
+ }
+
+ if (cs->cs_restricted) {
+ old_umask = umask(S_IXUSR|S_IXGRP|S_IXOTH);
+ mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
+ } else {
+ old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
+ mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP;
+ }
+
+ if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
+ log_warn("%s: bind: %s", __func__, cs->cs_name);
+ close(fd);
+ (void)umask(old_umask);
+ return (-1);
+ }
+ (void)umask(old_umask);
+
+ if (chmod(cs->cs_name, mode) == -1) {
+ log_warn("%s: chmod", __func__);
+ close(fd);
+ (void)unlink(cs->cs_name);
+ return (-1);
+ }
+
+ socket_set_blockmode(fd, BM_NONBLOCK);
+ cs->cs_fd = fd;
+ cs->cs_env = env;
+
+ return (0);
+}
+
+int
+control_listen(struct control_sock *cs)
+{
+ if (cs->cs_name == NULL)
+ return (0);
+
+ if (listen(cs->cs_fd, CONTROL_BACKLOG) == -1) {
+ log_warn("%s: listen", __func__);
+ return (-1);
+ }
+
+ event_set(&cs->cs_ev, cs->cs_fd, EV_READ,
+ control_accept, cs);
+ event_add(&cs->cs_ev, NULL);
+ evtimer_set(&cs->cs_evt, control_accept, cs);
+
+ return (0);
+}
+
+void
+control_cleanup(struct control_sock *cs)
+{
+ if (cs->cs_name == NULL)
+ return;
+ event_del(&cs->cs_ev);
+ event_del(&cs->cs_evt);
+ (void)unlink(cs->cs_name);
+}
+
+/* ARGSUSED */
+void
+control_accept(int listenfd, short event, void *arg)
+{
+ int connfd;
+ socklen_t len;
+ struct sockaddr_un sun;
+ struct ctl_conn *c;
+ struct control_sock *cs = arg;
+
+ event_add(&cs->cs_ev, NULL);
+ if ((event & EV_TIMEOUT))
+ return;
+
+ len = sizeof(sun);
+ if ((connfd = accept(listenfd,
+ (struct sockaddr *)&sun, &len)) == -1) {
+ /*
+ * Pause accept if we are out of file descriptors, or
+ * libevent will haunt us here too.
+ */
+ if (errno == ENFILE || errno == EMFILE) {
+ struct timeval evtpause = { 1, 0 };
+
+ event_del(&cs->cs_ev);
+ evtimer_add(&cs->cs_evt, &evtpause);
+ } else if (errno != EWOULDBLOCK && errno != EINTR &&
+ errno != ECONNABORTED)
+ log_warn("%s: accept", __func__);
+ return;
+ }
+
+ socket_set_blockmode(connfd, BM_NONBLOCK);
+
+ if ((c = calloc(1, sizeof(struct ctl_conn))) == NULL) {
+ close(connfd);
+ log_warn("%s: calloc", __func__);
+ return;
+ }
+
+ imsg_init(&c->iev.ibuf, connfd);
+ c->iev.handler = control_dispatch_imsg;
+ c->iev.events = EV_READ;
+ c->iev.data = cs; /* proc.c cheats (reuses the handler) */
+ event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events,
+ c->iev.handler, cs);
+ event_add(&c->iev.ev, NULL);
+
+ TAILQ_INSERT_TAIL(&ctl_conns, c, entry);
+}
+
+struct ctl_conn *
+control_connbyfd(int fd)
+{
+ struct ctl_conn *c;
+
+ for (c = TAILQ_FIRST(&ctl_conns); c != NULL && c->iev.ibuf.fd != fd;
+ c = TAILQ_NEXT(c, entry))
+ ; /* nothing */
+
+ return (c);
+}
+
+void
+control_close(int fd, struct control_sock *cs)
+{
+ struct ctl_conn *c;
+
+ if ((c = control_connbyfd(fd)) == NULL) {
+ log_warn("%s: fd %d not found", __func__, fd);
+ return;
+ }
+
+ msgbuf_clear(&c->iev.ibuf.w);
+ TAILQ_REMOVE(&ctl_conns, c, entry);
+
+ event_del(&c->iev.ev);
+ close(c->iev.ibuf.fd);
+
+ /* Some file descriptors are available again. */
+ if (evtimer_pending(&cs->cs_evt, NULL)) {
+ evtimer_del(&cs->cs_evt);
+ event_add(&cs->cs_ev, NULL);
+ }
+
+ free(c);
+}
+
+/* ARGSUSED */
+void
+control_dispatch_imsg(int fd, short event, void *arg)
+{
+ struct control_sock *cs = arg;
+ struct ctl_conn *c;
+ struct imsg imsg;
+ int n;
+ int verbose;
+ struct httpd *env = cs->cs_env;
+
+ if ((c = control_connbyfd(fd)) == NULL) {
+ log_warn("%s: fd %d not found", __func__, fd);
+ return;
+ }
+
+ if (event & EV_READ) {
+ if ((n = imsg_read(&c->iev.ibuf)) == -1 || n == 0) {
+ control_close(fd, cs);
+ return;
+ }
+ }
+
+ if (event & EV_WRITE) {
+ if (msgbuf_write(&c->iev.ibuf.w) <= 0 && errno != EAGAIN) {
+ control_close(fd, cs);
+ return;
+ }
+ }
+
+ for (;;) {
+ if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) {
+ control_close(fd, cs);
+ return;
+ }
+
+ if (n == 0)
+ break;
+
+ if (c->waiting) {
+ log_debug("%s: unexpected imsg %d",
+ __func__, imsg.hdr.type);
+ imsg_free(&imsg);
+ control_close(fd, cs);
+ return;
+ }
+
+ switch (imsg.hdr.type) {
+ case IMSG_CTL_SHUTDOWN:
+ case IMSG_CTL_RELOAD:
+ proc_forward_imsg(env->sc_ps, &imsg, PROC_PARENT, -1);
+ break;
+ case IMSG_CTL_NOTIFY:
+ if (c->flags & CTL_CONN_NOTIFY) {
+ log_debug("%s: "
+ "client requested notify more than once",
+ __func__);
+ imsg_compose_event(&c->iev, IMSG_CTL_FAIL,
+ 0, 0, -1, NULL, 0);
+ break;
+ }
+ c->flags |= CTL_CONN_NOTIFY;
+ break;
+ case IMSG_CTL_VERBOSE:
+ IMSG_SIZE_CHECK(&imsg, &verbose);
+
+ memcpy(&verbose, imsg.data, sizeof(verbose));
+
+ proc_forward_imsg(env->sc_ps, &imsg, PROC_PARENT, -1);
+ proc_forward_imsg(env->sc_ps, &imsg, PROC_SERVER, -1);
+
+ memcpy(imsg.data, &verbose, sizeof(verbose));
+ control_imsg_forward(&imsg);
+ log_verbose(verbose);
+ break;
+ default:
+ log_debug("%s: error handling imsg %d",
+ __func__, imsg.hdr.type);
+ break;
+ }
+ imsg_free(&imsg);
+ }
+
+ imsg_event_add(&c->iev);
+}
+
+void
+control_imsg_forward(struct imsg *imsg)
+{
+ struct ctl_conn *c;
+
+ TAILQ_FOREACH(c, &ctl_conns, entry)
+ if (c->flags & CTL_CONN_NOTIFY)
+ imsg_compose_event(&c->iev, imsg->hdr.type,
+ 0, imsg->hdr.pid, -1, imsg->data,
+ imsg->hdr.len - IMSG_HEADER_SIZE);
+}
+
+void
+socket_set_blockmode(int fd, enum blockmodes bm)
+{
+ int flags;
+
+ if ((flags = fcntl(fd, F_GETFL, 0)) == -1)
+ fatal("fcntl F_GETFL");
+
+ if (bm == BM_NONBLOCK)
+ flags |= O_NONBLOCK;
+ else
+ flags &= ~O_NONBLOCK;
+
+ if ((flags = fcntl(fd, F_SETFL, flags)) == -1)
+ fatal("fcntl F_SETFL");
+}
diff --git a/http.h b/http.h
new file mode 100644
index 0000000..625359a
--- /dev/null
+++ b/http.h
@@ -0,0 +1,122 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2012 - 2014 Reyk Floeter <reyk@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _HTTPD_HTTP_H
+#define _HTTPD_HTTP_H
+
+enum httpmethod {
+ HTTP_METHOD_NONE = 0,
+
+ /* HTTP/1.1, RFC 2616 */
+ HTTP_METHOD_GET,
+ HTTP_METHOD_HEAD,
+ HTTP_METHOD_POST,
+ HTTP_METHOD_PUT,
+ HTTP_METHOD_DELETE,
+ HTTP_METHOD_OPTIONS,
+ HTTP_METHOD_TRACE,
+ HTTP_METHOD_CONNECT,
+
+ /* WebDAV, RFC 4918 */
+ HTTP_METHOD_PROPFIND,
+ HTTP_METHOD_PROPPATCH,
+ HTTP_METHOD_MKCOL,
+ HTTP_METHOD_COPY,
+ HTTP_METHOD_MOVE,
+ HTTP_METHOD_LOCK,
+ HTTP_METHOD_UNLOCK,
+
+ /* PATCH, RFC 5789 */
+ HTTP_METHOD_PATCH,
+
+ /* Server response (internal value) */
+ HTTP_METHOD_RESPONSE
+};
+
+struct http_method {
+ enum httpmethod method_id;
+ const char *method_name;
+};
+#define HTTP_METHODS { \
+ { HTTP_METHOD_GET, "GET" }, \
+ { HTTP_METHOD_HEAD, "HEAD" }, \
+ { HTTP_METHOD_POST, "POST" }, \
+ { HTTP_METHOD_PUT, "PUT" }, \
+ { HTTP_METHOD_DELETE, "DELETE" }, \
+ { HTTP_METHOD_OPTIONS, "OPTIONS" }, \
+ { HTTP_METHOD_TRACE, "TRACE" }, \
+ { HTTP_METHOD_CONNECT, "CONNECT" }, \
+ { HTTP_METHOD_PROPFIND, "PROPFIND" }, \
+ { HTTP_METHOD_PROPPATCH, "PROPPATCH" }, \
+ { HTTP_METHOD_MKCOL, "MKCOL" }, \
+ { HTTP_METHOD_COPY, "COPY" }, \
+ { HTTP_METHOD_MOVE, "MOVE" }, \
+ { HTTP_METHOD_LOCK, "LOCK" }, \
+ { HTTP_METHOD_UNLOCK, "UNLOCK" }, \
+ { HTTP_METHOD_PATCH, "PATCH" }, \
+ { HTTP_METHOD_NONE, NULL } \
+}
+
+struct http_error {
+ int error_code;
+ const char *error_name;
+};
+#define HTTP_ERRORS { \
+ { 100, "Continue" }, \
+ { 101, "Switching Protocols" }, \
+ { 200, "OK" }, \
+ { 201, "Created" }, \
+ { 202, "Accepted" }, \
+ { 203, "Non-Authorative Information" }, \
+ { 204, "No Content" }, \
+ { 205, "Reset Content" }, \
+ { 206, "Partial Content" }, \
+ { 300, "Multiple Choices" }, \
+ { 301, "Moved Permanently" }, \
+ { 302, "Moved Temporarily" }, \
+ { 303, "See Other" }, \
+ { 304, "Not Modified" }, \
+ { 307, "Temporary Redirect" }, \
+ { 400, "Bad Request" }, \
+ { 401, "Unauthorized" }, \
+ { 402, "Payment Required" }, \
+ { 403, "Forbidden" }, \
+ { 404, "Not Found" }, \
+ { 405, "Method Not Allowed" }, \
+ { 406, "Not Acceptable" }, \
+ { 407, "Proxy Authentication Required" }, \
+ { 408, "Request Timeout" }, \
+ { 409, "Conflict" }, \
+ { 410, "Gone" }, \
+ { 411, "Length Required" }, \
+ { 412, "Precondition Failed" }, \
+ { 413, "Request Entity Too Large" }, \
+ { 414, "Request-URL Too Long" }, \
+ { 415, "Unsupported Media Type" }, \
+ { 416, "Requested Range Not Satisfiable" }, \
+ { 417, "Expectation Failed" }, \
+ { 500, "Internal Server Error" }, \
+ { 501, "Not Implemented" }, \
+ { 502, "Bad Gateway" }, \
+ { 503, "Service Unavailable" }, \
+ { 504, "Gateway Timeout" }, \
+ { 505, "HTTP Version Not Supported" }, \
+ { 0, NULL } \
+}
+
+#endif /* _HTTPD_HTTP_H */
diff --git a/httpd.8 b/httpd.8
new file mode 100644
index 0000000..2c5457d
--- /dev/null
+++ b/httpd.8
@@ -0,0 +1,49 @@
+.\" $OpenBSD$
+.\"
+.\" Copyright (c) 2014 Reyk Floeter <reyk@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: July 12 2014 $
+.Dt HTTPD 8
+.Os
+.Sh NAME
+.Nm httpd
+.Nd HTTP daemon
+.Sh SYNOPSIS
+.Nm
+.Op Fl dnv
+.Op Fl D Ar macro Ns = Ns Ar value
+.Op Fl f Ar file
+.Sh DESCRIPTION
+.Nm
+is an HTTP server that serves static files.
+.El
+.Sh FILES
+.Bl -tag -width "/var/run/httpd.sockXX" -compact
+.It /etc/httpd.conf
+Default configuration file.
+.It /var/run/httpd.sock
+.Ux Ns -domain
+socket used for communication with
+.Xr httpctl 8 .
+.El
+.Sh SEE ALSO
+.Xr httpd.conf 5 ,
+.Xr httpctl 8
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+program was written by
+.An Reyk Floeter Aq Mt reyk@openbsd.org .
diff --git a/httpd.c b/httpd.c
new file mode 100644
index 0000000..73ab565
--- /dev/null
+++ b/httpd.c
@@ -0,0 +1,527 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2014 Reyk Floeter <reyk@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/resource.h>
+#include <sys/hash.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <fnmatch.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <sha1.h>
+#include <md5.h>
+
+#include <openssl/ssl.h>
+
+#include "httpd.h"
+
+__dead void usage(void);
+
+int parent_configure(struct httpd *);
+void parent_configure_done(struct httpd *);
+void parent_reload(struct httpd *, u_int, const char *);
+void parent_sig_handler(int, short, void *);
+void parent_shutdown(struct httpd *);
+int parent_dispatch_server(int, struct privsep_proc *,
+ struct imsg *);
+
+struct httpd *httpd_env;
+
+static struct privsep_proc procs[] = {
+ { "server", PROC_SERVER, parent_dispatch_server, server }
+};
+
+void
+parent_sig_handler(int sig, short event, void *arg)
+{
+ struct privsep *ps = arg;
+ int die = 0, status, fail, id;
+ pid_t pid;
+ char *cause;
+
+ switch (sig) {
+ case SIGTERM:
+ case SIGINT:
+ die = 1;
+ /* FALLTHROUGH */
+ case SIGCHLD:
+ do {
+ pid = waitpid(WAIT_ANY, &status, WNOHANG);
+ if (pid <= 0)
+ continue;
+
+ fail = 0;
+ if (WIFSIGNALED(status)) {
+ fail = 1;
+ asprintf(&cause, "terminated; signal %d",
+ WTERMSIG(status));
+ } else if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status) != 0) {
+ fail = 1;
+ asprintf(&cause, "exited abnormally");
+ } else
+ asprintf(&cause, "exited okay");
+ } else
+ fatalx("unexpected cause of SIGCHLD");
+
+ die = 1;
+
+ for (id = 0; id < PROC_MAX; id++)
+ if (pid == ps->ps_pid[id]) {
+ if (fail)
+ log_warnx("lost child: %s %s",
+ ps->ps_title[id], cause);
+ break;
+ }
+
+ free(cause);
+ } while (pid > 0 || (pid == -1 && errno == EINTR));
+
+ if (die)
+ parent_shutdown(ps->ps_env);
+ break;
+ case SIGHUP:
+ log_info("%s: reload requested with SIGHUP", __func__);
+
+ /*
+ * This is safe because libevent uses async signal handlers
+ * that run in the event loop and not in signal context.
+ */
+ parent_reload(ps->ps_env, CONFIG_RELOAD, NULL);
+ break;
+ case SIGPIPE:
+ /* ignore */
+ break;
+ default:
+ fatalx("unexpected signal");
+ }
+}
+
+__dead void
+usage(void)
+{
+ extern char *__progname;
+
+ fprintf(stderr, "usage: %s [-dnv] [-D macro=value] [-f file]\n",
+ __progname);
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int c;
+ int debug = 0, verbose = 0;
+ u_int32_t opts = 0;
+ struct httpd *env;
+ struct privsep *ps;
+ const char *conffile = CONF_FILE;
+
+ while ((c = getopt(argc, argv, "dD:nf:v")) != -1) {
+ switch (c) {
+ case 'd':
+ debug = 2;
+ break;
+ case 'D':
+ if (cmdline_symset(optarg) < 0)
+ log_warnx("could not parse macro definition %s",
+ optarg);
+ break;
+ case 'n':
+ debug = 2;
+ opts |= HTTPD_OPT_NOACTION;
+ break;
+ case 'f':
+ conffile = optarg;
+ break;
+ case 'v':
+ verbose++;
+ opts |= HTTPD_OPT_VERBOSE;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ log_init(debug ? debug : 1); /* log to stderr until daemonized */
+
+ argc -= optind;
+ if (argc > 0)
+ usage();
+
+ if ((env = calloc(1, sizeof(*env))) == NULL ||
+ (ps = calloc(1, sizeof(*ps))) == NULL)
+ exit(1);
+
+ httpd_env = env;
+ env->sc_ps = ps;
+ ps->ps_env = env;
+ TAILQ_INIT(&ps->ps_rcsocks);
+ env->sc_conffile = conffile;
+ env->sc_opts = opts;
+
+ if (parse_config(env->sc_conffile, env) == -1)
+ exit(1);
+
+ if (debug)
+ env->sc_opts |= HTTPD_OPT_LOGUPDATE;
+
+ if (geteuid())
+ errx(1, "need root privileges");
+
+ if ((ps->ps_pw = getpwnam(HTTPD_USER)) == NULL)
+ errx(1, "unknown user %s", HTTPD_USER);
+
+ /* Configure the control socket */
+ ps->ps_csock.cs_name = HTTPD_SOCKET;
+
+ log_init(debug);
+ log_verbose(verbose);
+
+ if (!debug && daemon(1, 0) == -1)
+ err(1, "failed to daemonize");
+
+ if (env->sc_opts & HTTPD_OPT_NOACTION)
+ ps->ps_noaction = 1;
+ else
+ log_info("startup");
+
+ ps->ps_instances[PROC_SERVER] = env->sc_prefork_server;
+ ps->ps_ninstances = env->sc_prefork_server;
+
+ proc_init(ps, procs, nitems(procs));
+
+ setproctitle("parent");
+
+ event_init();
+
+ signal_set(&ps->ps_evsigint, SIGINT, parent_sig_handler, ps);
+ signal_set(&ps->ps_evsigterm, SIGTERM, parent_sig_handler, ps);
+ signal_set(&ps->ps_evsigchld, SIGCHLD, parent_sig_handler, ps);
+ signal_set(&ps->ps_evsighup, SIGHUP, parent_sig_handler, ps);
+ signal_set(&ps->ps_evsigpipe, SIGPIPE, parent_sig_handler, ps);
+
+ signal_add(&ps->ps_evsigint, NULL);
+ signal_add(&ps->ps_evsigterm, NULL);
+ signal_add(&ps->ps_evsigchld, NULL);
+ signal_add(&ps->ps_evsighup, NULL);
+ signal_add(&ps->ps_evsigpipe, NULL);
+
+ proc_listen(ps, procs, nitems(procs));
+
+ if (load_config(env->sc_conffile, env) == -1) {
+ proc_kill(env->sc_ps);
+ exit(1);
+ }
+
+ if (env->sc_opts & HTTPD_OPT_NOACTION) {
+ fprintf(stderr, "configuration OK\n");
+ proc_kill(env->sc_ps);
+ exit(0);
+ }
+
+ if (parent_configure(env) == -1)
+ fatalx("configuration failed");
+
+ event_dispatch();
+
+ parent_shutdown(env);
+ /* NOTREACHED */
+
+ return (0);
+}
+
+int
+parent_configure(struct httpd *env)
+{
+ int id;
+ struct ctl_flags cf;
+ int ret = -1;
+
+#if 0
+ TAILQ_FOREACH(srv, env->sc_server, entry)
+ config_setserver(env, srv);
+#endif
+
+ /* The servers need to reload their config. */
+ env->sc_reload = env->sc_prefork_server;
+
+ for (id = 0; id < PROC_MAX; id++) {
+ if (id == privsep_process)
+ continue;
+ cf.cf_opts = env->sc_opts;
+ cf.cf_flags = env->sc_flags;
+
+ proc_compose_imsg(env->sc_ps, id, -1, IMSG_CFG_DONE, -1,
+ &cf, sizeof(cf));
+ }
+
+ ret = 0;
+
+ config_purge(env, CONFIG_ALL & ~CONFIG_SERVERS);
+ return (ret);
+}
+
+void
+parent_reload(struct httpd *env, u_int reset, const char *filename)
+{
+ if (env->sc_reload) {
+ log_debug("%s: already in progress: %d pending",
+ __func__, env->sc_reload);
+ return;
+ }
+
+ /* Switch back to the default config file */
+ if (filename == NULL || *filename == '\0')
+ filename = env->sc_conffile;
+
+ log_debug("%s: level %d config file %s", __func__, reset, filename);
+
+ config_purge(env, CONFIG_ALL);
+
+ if (reset == CONFIG_RELOAD) {
+ if (load_config(filename, env) == -1) {
+ log_debug("%s: failed to load config file %s",
+ __func__, filename);
+ }
+
+ config_setreset(env, CONFIG_ALL);
+
+ if (parent_configure(env) == -1) {
+ log_debug("%s: failed to commit config from %s",
+ __func__, filename);
+ }
+ } else
+ config_setreset(env, reset);
+}
+
+void
+parent_configure_done(struct httpd *env)
+{
+ int id;
+
+ if (env->sc_reload == 0) {
+ log_warnx("%s: configuration already finished", __func__);
+ return;
+ }
+
+ env->sc_reload--;
+ if (env->sc_reload == 0) {
+ for (id = 0; id < PROC_MAX; id++) {
+ if (id == privsep_process)
+ continue;
+
+ proc_compose_imsg(env->sc_ps, id, -1, IMSG_CTL_START,
+ -1, NULL, 0);
+ }
+ }
+}
+
+void
+parent_shutdown(struct httpd *env)
+{
+ config_purge(env, CONFIG_ALL);
+
+ proc_kill(env->sc_ps);
+ control_cleanup(&env->sc_ps->ps_csock);
+
+ free(env->sc_ps);
+ free(env);
+
+ log_info("parent terminating, pid %d", getpid());
+
+ exit(0);
+}
+
+int
+parent_dispatch_server(int fd, struct privsep_proc *p, struct imsg *imsg)
+{
+ struct httpd *env = p->p_env;
+
+ switch (imsg->hdr.type) {
+ case IMSG_CFG_DONE:
+ parent_configure_done(env);
+ break;
+ default:
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * Utility functions
+ */
+
+void
+event_again(struct event *ev, int fd, short event,
+ void (*fn)(int, short, void *),
+ struct timeval *start, struct timeval *end, void *arg)
+{
+ struct timeval tv_next, tv_now, tv;
+
+ getmonotime(&tv_now);
+ bcopy(end, &tv_next, sizeof(tv_next));
+ timersub(&tv_now, start, &tv_now);
+ timersub(&tv_next, &tv_now, &tv_next);
+
+ bzero(&tv, sizeof(tv));
+ if (timercmp(&tv_next, &tv, >))
+ bcopy(&tv_next, &tv, sizeof(tv));
+
+ event_del(ev);
+ event_set(ev, fd, event, fn, arg);
+ event_add(ev, &tv);
+}
+
+const char *
+canonicalize_host(const char *host, char *name, size_t len)
+{
+ struct sockaddr_in sin4;
+ struct sockaddr_in6 sin6;
+ u_int i, j;
+ size_t plen;
+ char c;
+
+ if (len < 2)
+ goto fail;
+
+ /*
+ * Canonicalize an IPv4/6 address
+ */
+ if (inet_pton(AF_INET, host, &sin4) == 1)
+ return (inet_ntop(AF_INET, &sin4, name, len));
+ if (inet_pton(AF_INET6, host, &sin6) == 1)
+ return (inet_ntop(AF_INET6, &sin6, name, len));
+
+ /*
+ * Canonicalize a hostname
+ */
+
+ /* 1. remove repeated dots and convert upper case to lower case */
+ plen = strlen(host);
+ bzero(name, len);
+ for (i = j = 0; i < plen; i++) {
+ if (j >= (len - 1))
+ goto fail;
+ c = tolower(host[i]);
+ if ((c == '.') && (j == 0 || name[j - 1] == '.'))
+ continue;
+ name[j++] = c;
+ }
+
+ /* 2. remove trailing dots */
+ for (i = j; i > 0; i--) {
+ if (name[i - 1] != '.')
+ break;
+ name[i - 1] = '\0';
+ j--;
+ }
+ if (j <= 0)
+ goto fail;
+
+ return (name);
+
+ fail:
+ errno = EINVAL;
+ return (NULL);
+}
+
+void
+socket_rlimit(int maxfd)
+{
+ struct rlimit rl;
+
+ if (getrlimit(RLIMIT_NOFILE, &rl) == -1)
+ fatal("socket_rlimit: failed to get resource limit");
+ log_debug("%s: max open files %llu", __func__, rl.rlim_max);
+
+ /*
+ * Allow the maximum number of open file descriptors for this
+ * login class (which should be the class "daemon" by default).
+ */
+ if (maxfd == -1)
+ rl.rlim_cur = rl.rlim_max;
+ else
+ rl.rlim_cur = MAX(rl.rlim_max, (rlim_t)maxfd);
+ if (setrlimit(RLIMIT_NOFILE, &rl) == -1)
+ fatal("socket_rlimit: failed to set resource limit");
+}
+
+char *
+get_string(u_int8_t *ptr, size_t len)
+{
+ size_t i;
+ char *str;
+
+ for (i = 0; i < len; i++)
+ if (!(isprint(ptr[i]) || isspace(ptr[i])))
+ break;
+
+ if ((str = calloc(1, i + 1)) == NULL)
+ return (NULL);
+ memcpy(str, ptr, i);
+
+ return (str);
+}
+
+void *
+get_data(u_int8_t *ptr, size_t len)
+{
+ u_int8_t *data;
+
+ if ((data = calloc(1, len)) == NULL)
+ return (NULL);
+ memcpy(data, ptr, len);
+
+ return (data);
+}
+
+int
+accept_reserve(int sockfd, struct sockaddr *addr, socklen_t *addrlen,
+ int reserve, volatile int *counter)
+{
+ int ret;
+ if (getdtablecount() + reserve +
+ *counter >= getdtablesize()) {
+ errno = EMFILE;
+ return (-1);
+ }
+
+ if ((ret = accept(sockfd, addr, addrlen)) > -1) {
+ (*counter)++;
+ DPRINTF("%s: inflight incremented, now %d",__func__, *counter);
+ }
+ return (ret);
+}
diff --git a/httpd.conf.5 b/httpd.conf.5
new file mode 100644
index 0000000..24f823f
--- /dev/null
+++ b/httpd.conf.5
@@ -0,0 +1,1567 @@
+.\" $OpenBSD: relayd.conf.5,v 1.147 2014/07/11 16:59:38 reyk Exp $
+.\"
+.\" Copyright (c) 2006 - 2014 Reyk Floeter <reyk@openbsd.org>
+.\" Copyright (c) 2006, 2007 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.
+.\"
+.Dd $Mdocdate: July 11 2014 $
+.Dt RELAYD.CONF 5
+.Os
+.Sh NAME
+.Nm relayd.conf
+.Nd relay daemon configuration file
+.Sh DESCRIPTION
+.Nm
+is the configuration file for the relay daemon,
+.Xr relayd 8 .
+.Sh SECTIONS
+.Nm
+is divided into seven 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 relayd 8 .
+Do note that the config file allows global settings to be added after
+defining tables in the config file, but those tables will use the
+built-in defaults instead of the global settings below them.
+.It Sy Tables
+Table definitions describe a list of hosts,
+in a similar fashion to
+.Xr pf 4
+tables.
+They are used for relay, redirection, and router target selection with
+the described options and health checking on the host they contain.
+.It Sy Redirections
+Redirections are translated to
+.Xr pf 4
+rdr-to rules for stateful forwarding to a target host from a
+health-checked table on layer 3.
+.It Sy Relays
+Relays allow application layer load balancing, SSL acceleration, and
+general purpose TCP proxying on layer 7.
+.It Sy Protocols
+Protocols are predefined settings and filter rules for relays.
+.It Sy Routers
+Routers are used to insert routes with health-checked gateways for
+(WAN) link balancing.
+.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 relayd 8
+will look up the first IPv4 address and any other IPv4 and IPv6
+addresses of the specified network interface.
+A
+.Ar port
+can be specified by number or name.
+The port name to number mappings are found in the file
+.Pa /etc/services ;
+see
+.Xr services 5
+for details.
+.Pp
+The current line can be extended over multiple lines using a backslash
+.Pq Sq \e .
+Comments can be put anywhere in the file using a hash mark
+.Pq Sq # ,
+and extend to the end of the current line.
+Care should be taken when commenting out multi-line text:
+the comment is effective until the end of the entire block.
+.Pp
+Argument names not beginning with a letter, digit, or underscore
+must be quoted.
+.Pp
+Additional configuration files can be included with the
+.Ic include
+keyword, for example:
+.Bd -literal -offset indent
+include "/etc/relayd.conf.local"
+.Ed
+.Sh MACROS
+Macros can be defined that will later be expanded in context.
+Macro names must start with a letter, digit, or underscore,
+and may contain any of those characters.
+Macro names may not be reserved words (for example,
+.Ic table ,
+.Ic relay ,
+or
+.Ic timeout ) .
+Macros are not expanded inside quotes.
+.Pp
+For example:
+.Bd -literal -offset indent
+www1="10.0.0.1"
+www2="10.0.0.2"
+table \*(Ltwebhosts\*(Gt {
+ $www1
+ $www2
+}
+.Ed
+.Sh GLOBAL CONFIGURATION
+Here are the settings that can be set globally:
+.Bl -tag -width Ds
+.It Ic interval Ar number
+Set the interval in seconds at which the hosts will be checked.
+The default interval is 10 seconds.
+.It Xo
+.Ic log
+.Pq Ic updates Ns | Ns Ic all
+.Xc
+Log state notifications after completed host checks.
+Either only log the
+.Ic updates
+to new states or log
+.Ic all
+state notifications, even if the state didn't change.
+The host state can be
+.Ar up
+(the health check completed successfully),
+.Ar down
+(the host is down or didn't match the check criteria),
+or
+.Ar unknown
+(the host is disabled or has not been checked yet).
+.It Ic prefork Ar number
+When using relays, run the specified number of processes to handle
+relayed connections.
+This increases the performance and prevents delays when connecting
+to a relay.
+.Xr relayd 8
+runs 3 relay processes by default and every process will handle
+all configured relays.
+.It Ic snmp Oo Ic trap Oc Op Qq Ar path
+Send an SNMP trap when the state of a host changes.
+.Xr relayd 8
+will try to connect to
+.Xr snmpd 8
+over the AgentX SNMP socket specified by
+.Ar path
+and request it send a trap to the registered trap receivers.
+If
+.Ar path
+is not specified, a default path of
+.Ar /var/run/agentx.sock
+will be used.
+See
+.Xr snmpd.conf 5
+for more information about SNMP configuration.
+.It Ic timeout Ar number
+Set the global timeout in milliseconds for checks.
+This can be overridden by the timeout value in the table definitions.
+The default interval is 200 milliseconds and it must not exceed the
+global interval.
+Please note that the default value is optimized for checks within the
+same collision domain \(en use a higher timeout, such as 1000 milliseconds,
+for checks of hosts in other subnets.
+If this option is to be set, it should be placed before overrides in tables.
+.El
+.Sh TABLES
+Tables are used to group a set of hosts as the target for redirections
+or relays; they will be mapped to a
+.Xr pf 4
+table for redirections.
+Tables may be defined with the following attribute:
+.Bl -tag -width disable
+.It Ic disable
+Start the table disabled \(en no hosts will be checked in this table.
+The table can be later enabled through
+.Xr relayctl 8 .
+.El
+.Pp
+Each table must contain at least one host
+.Ar address ;
+multiple hosts are separated by newline, comma, or whitespace.
+Host entries may be defined with the following attributes:
+.Bl -tag -width retry
+.It Ic ip ttl Ar number
+Change the default time-to-live value in the IP headers for host checks.
+.It Ic parent Ar number
+The optional parent option inherits the state from a parent
+host with the specified identifier.
+The check will be skipped for this host and copied from the parent host.
+This can be used to prevent multiple checks on hosts with multiple IP
+addresses for the same service.
+The host identifiers are sequentially assigned to the configured hosts
+starting with 1; it can be shown with the
+.Xr relayctl 8
+.Ic show summary
+commands.
+.It Ic priority Ar number
+Change the route priority used when adding a route.
+If not specified, the kernel will set a priority of 8 (RTP_STATIC).
+In ordinary use, a fallback route should be added statically with a very
+high (e.g. 52) priority.
+Unused in all other modes.
+.It Ic retry Ar number
+The optional retry option adds a tolerance for failed host checks;
+the check will be retried for
+.Ar number
+more times before setting the host state to down.
+If this table is used by a relay, it will also specify the number of
+retries for outgoing connection attempts.
+.El
+.Pp
+For example:
+.Bd -literal -offset indent
+table \*(Ltservice\*(Gt { 192.168.1.1, 192.168.1.2, 192.168.2.3 }
+table \*(Ltfallback\*(Gt disable { 10.1.5.1 retry 2 }
+
+redirect "www" {
+ listen on www.example.com port 80
+ forward to \*(Ltservice\*(Gt check http "/" code 200
+ forward to \*(Ltfallback\*(Gt check http "/" code 200
+}
+.Ed
+.Pp
+Tables are used by
+.Ic forward to
+directives in redirections or relays with a set of general options,
+health-checking rules, and timings;
+see the
+.Sx REDIRECTIONS
+and
+.Sx RELAYS
+sections for more information about the forward context.
+Table specific configuration directives are described below.
+Multiple options can be appended to
+.Ic forward to
+directives, separated by whitespaces.
+.Pp
+The following options will configure the health-checking method for
+the table, and is mandatory for redirections:
+.Bl -tag -width Ds
+.It Xo
+.Ic check http Ar path
+.Op Ic host Ar hostname
+.Ic code Ar number
+.Xc
+For each host in the table, verify that retrieving the URL
+.Ar path
+gives the HTTP return code
+.Ar number .
+If
+.Ar hostname
+is specified, it is used as the
+.Dq Host:
+header to query a specific hostname at the target host.
+To validate the HTTP return code, use this shell command:
+.Bd -literal -offset indent
+$ echo -n "HEAD <path> HTTP/1.0\er\en\er\en" | \e
+ nc <host> <port> | head -n1
+.Ed
+.Pp
+This prints the status header including the actual return code:
+.Bd -literal -offset indent
+HTTP/1.1 200 OK
+.Ed
+.It Xo
+.Ic check https Ar path
+.Op Ic host Ar hostname
+.Ic code Ar number
+.Xc
+This has the same effect as above but wraps the HTTP request in SSL.
+.It Xo
+.Ic check http Ar path
+.Op Ic host Ar hostname
+.Ic digest Ar string
+.Xc
+For each host in the table, verify that retrieving the URL
+.Ar path
+produces non-binary content whose message digest matches the defined string.
+The algorithm used is determined by the string length of the
+.Ar digest
+argument, either SHA1 (40 characters) or MD5 (32 characters).
+If
+.Ar hostname
+is specified, it is used as the
+.Dq Host:
+header to query a specific hostname at the target host.
+The digest does not take the HTTP headers into account.
+Do not specify a binary object (such as a graphic) as the target of the
+request, as
+.Nm
+expects the data returned to be a string.
+To compute the digest, use this simple command:
+.Bd -literal -offset indent
+$ ftp -o - http://host[:port]/path | sha1
+.Ed
+.Pp
+This gives a digest that can be used as-is in a digest statement:
+.Bd -literal -offset indent
+a9993e36476816aba3e25717850c26c9cd0d89d
+.Ed
+.It Xo
+.Ic check https Ar path
+.Op Ic host Ar hostname
+.Ic digest Ar string
+.Xc
+This has the same effect as above but wraps the HTTP request in SSL.
+.It Ic check icmp
+Ping hosts in this table to determine whether they are up or not.
+This method will automatically use ICMP or ICMPV6 depending on the
+address family of each host.
+.It Ic check script Ar path
+Execute an external program to check the host state.
+The program will be executed for each host by specifying the hostname
+on the command line:
+.Bd -literal -offset indent
+/usr/local/bin/checkload.pl front-www1.private.example.com
+.Ed
+.Pp
+.Xr relayd 8
+expects a positive return value on success and zero on failure.
+Note that the script will be executed with the privileges of the
+.Qq _relayd
+user and terminated after
+.Ar timeout
+milliseconds.
+.It Xo
+.Ic check send
+.Ar data
+.Ic expect
+.Ar pattern
+.Op Ic ssl
+.Xc
+For each host in the table, a TCP connection is established on the
+port specified, then
+.Ar data
+is sent.
+Incoming data is then read and is expected to match against
+.Ar pattern
+using shell globbing rules.
+If
+.Ar data
+is an empty string or
+.Ic nothing
+then nothing is sent on the connection and data is immediately
+read.
+This can be useful with protocols that output a banner like
+SMTP, NNTP, and FTP.
+If the
+.Ic ssl
+keyword is present,
+the transaction will occur in an SSL tunnel.
+.It Ic check ssl
+Perform a complete SSL handshake with each host to check their availability.
+.It Ic check tcp
+Use a simple TCP connect to check that hosts are up.
+.El
+.Pp
+The following general table options are available:
+.Bl -tag -width Ds
+.It Ic demote Ar group
+Enable the per-table
+.Xr carp 4
+demotion option.
+This will increment the carp demotion counter for the
+specified interface group if all hosts in the table are down.
+For more information on interface groups,
+see the
+.Ic group
+keyword in
+.Xr ifconfig 8 .
+.It Ic interval Ar number
+Override the global interval and specify one for this table.
+It must be a multiple of the global interval.
+.It Ic timeout Ar number
+Set the timeout in milliseconds for each host that is checked using
+TCP as the transport.
+This will override the global timeout, which is 200 milliseconds by default.
+.El
+.Pp
+The following options will set the scheduling algorithm to select a
+host from the specified table:
+.Bl -tag -width Ds
+.It Ic mode hash
+Balances the outgoing connections across the active hosts based on the
+hashed name of the relay, the hashed name of the table, and the IP
+address and port of the relay.
+Additional input can be fed into the
+hash by looking at HTTP headers and GET variables;
+see the
+.Sx PROTOCOLS
+section below.
+This mode is only supported by relays.
+.It Ic mode least-states
+Forward each outgoing connection to the active host with the least
+active
+.Xr pf 4
+states.
+This mode is only supported by redirections.
+.It Ic mode loadbalance
+Balances the outgoing connections across the active hosts based on the
+hashed name of the relay, the hashed name of the table, the source IP
+address of the client, and the IP address and port of the relay.
+This mode is only supported by relays.
+.It Ic mode random
+Distributes the outgoing connections randomly through all active hosts.
+This mode is only supported by relays.
+.It Ic mode roundrobin
+Distributes the outgoing connections using a round-robin scheduler
+through all active hosts.
+This is the default mode and will be used if no option has been specified.
+This mode is supported by redirections and relays.
+.It Ic mode source-hash
+Balances the outgoing connections across the active hosts based on the
+hashed name of the redirection or relay, the hashed name of the table,
+and the source IP address of the client.
+This mode is only supported by relays.
+.El
+.Sh REDIRECTIONS
+Redirections represent a
+.Xr pf 4
+rdr-to rule.
+They are used for stateful redirections to the hosts in the specified
+tables.
+.Xr pf 4
+rewrites the target IP addresses and ports of the incoming
+connections, operating on layer 3.
+The configuration directives that are valid in the
+.Ic redirect
+context are described below:
+.Bl -tag -width Ds
+.It Ic disable
+The redirection is initially disabled.
+It can be later enabled through
+.Xr relayctl 8 .
+.It Xo
+.Ic forward to
+.Aq Ar table
+.Op Ic port Ar number
+.Ar options ...
+.Xc
+Specify the tables of target hosts to be used; see the
+.Sx TABLES
+section above for information about table options.
+If the
+.Ic port
+option is not specified, the first port from the
+.Ic listen on
+directive will be used.
+This directive can be specified twice \(en the second entry will be used
+as the backup table if all hosts in the main table are down.
+At least one entry for the main table is mandatory.
+.It Xo
+.Ic listen on Ar address
+.Op ip-proto
+.Ic port Ar port
+.Op Ic interface Ar name
+.Xc
+Specify an
+.Ar address
+and a
+.Ar port
+to listen on.
+.Xr pf 4
+will redirect incoming connections for the specified target to the
+hosts in the main or backup table.
+The
+.Ar port
+argument can optionally specify a port range instead of a single port;
+the format is
+.Ar min-port : Ns Ar max-port .
+The optional argument
+.Ar ip-proto
+can be used to specify an IP protocol like
+.Ar tcp
+or
+.Ar udp ;
+it defaults to
+.Ar tcp .
+The rule can be optionally restricted to a given interface name.
+.It Xo
+.Ic route to
+.Aq Ar table
+.Op Ic port Ar number
+.Ar options ...
+.Xc
+Like the
+.Ic forward to
+directive, but directly routes the packets to the target host without
+modifying the target address using a
+.Xr pf 4
+route-to rule.
+This can be used for
+.Dq direct server return
+to force the target host to respond via a different gateway.
+Note that hosts have to accept sessions for the same address as
+the gateway, which is typically done by configuring a loopback
+interface on the host with this address.
+.It Ic session timeout Ar seconds
+Specify the inactivity timeout in seconds for established redirections.
+The default timeout is 600 seconds (10 minutes).
+The maximum is 2147483647 seconds (68 years).
+.It Ic sticky-address
+This has the same effect as specifying sticky-address
+for an rdr-to rule in
+.Xr pf.conf 5 .
+It will ensure that multiple connections from the same source are
+mapped to the same redirection address.
+.It Xo
+.Op Ic match
+.Ic pftag Ar name
+.Xc
+Automatically tag packets passing through the
+.Xr pf 4
+rdr-to rule with the name supplied.
+This allows simpler filter rules.
+The optional
+.Ic match
+keyword will change the default rule action from
+.Ar pass in quick
+to
+.Ar match in
+to allow further evaluation in the pf ruleset using the
+.Ar tagged name
+rule option.
+.El
+.Sh RELAYS
+Relays will forward traffic between a client and a target server.
+In contrast to redirections and IP forwarding in the network stack, a
+relay will accept incoming connections from remote clients as a
+server, open an outgoing connection to a target host, and forward
+any traffic between the target host and the remote client,
+operating on layer 7.
+A relay is also called an application layer gateway or layer 7 proxy.
+.Pp
+The main purpose of a relay is to provide advanced load balancing
+functionality based on specified protocol characteristics, such as
+HTTP headers, to provide SSL acceleration and to allow
+basic handling of the underlying application protocol.
+.Pp
+The
+.Ic relay
+configuration directives are described below:
+.Bl -tag -width Ds
+.It Ic disable
+Start the relay but immediately close any accepted connections.
+.It Xo
+.Op Ic transparent
+.Ic forward
+.Op Ic with ssl
+.Ic to
+.Ar address
+.Op Ic port Ar port
+.Ar options ...
+.Xc
+Specify the address and port of the target host to connect to.
+If the
+.Ic port
+option is not specified, the port from the
+.Ic listen on
+directive will be used.
+Use the
+.Ic transparent
+keyword to enable fully-transparent mode; the source address of the
+client will be retained in this case.
+.Pp
+The
+.Ic with ssl
+directive enables client-side SSL mode to connect to the remote host.
+Verification of server certificates can be enabled by setting the
+.Ic ca file
+option in the protocol section.
+.Pp
+The following options may be specified for forward directives:
+.Bl -tag -width Ds
+.It Ic retry Ar number
+The optional host
+.Ic retry
+option will be used as a tolerance for failed
+host connections; the connection will be retried for
+.Ar number
+more times.
+.It Ic inet
+If the requested destination is an IPv6 address,
+.Xr relayd 8
+will forward the connection to an IPv4 address which is determined by
+the last 4 octets of the original IPv6 destination.
+For example, if the original IPv6 destination address is
+2001:db8:7395:ffff::a01:101, the session is relayed to the IPv4
+address 10.1.1.1 (a01:101).
+.It Ic inet6 Ar address-prefix
+If the requested destination is an IPv4 address,
+.Xr relayd 8
+will forward the connection to an IPv6 address which is determined by
+setting the last 4 octets of the specified IPv6
+.Ar address-prefix
+to the 4 octets of the original IPv4 destination.
+For example, if the original IPv4 destination address is 10.1.1.1 and
+the specified address prefix is 2001:db8:7395:ffff::, the session is
+relayed to the IPv6 address 2001:db8:7395:ffff::a01:101.
+.El
+.It Xo
+.Ic forward to
+.Aq Ar table
+.Op Ic port Ar port
+.Ar options ...
+.Xc
+Like the previous directive, but connect to a host from the specified
+table; see the
+.Sx TABLES
+section above for information about table options.
+This directive can be specified multiple times \(en subsequent entries
+will be used as the backup table if all hosts in the previous table
+are down.
+At least one entry for the main table is mandatory.
+.It Xo
+.Ic forward to
+.Ic destination
+.Ar options ...
+.Xc
+When redirecting connections with a divert-to rule in
+.Xr pf.conf 5
+to a relay listening on localhost, this directive will
+look up the real destination address of the intended target host,
+allowing the relay to be run as a transparent proxy.
+If an additional
+.Ic forward to
+directive to a specified address or table is present,
+it will be used as a backup if the lookup failed.
+.It Xo
+.Ic forward to
+.Ic nat lookup
+.Ar options ...
+.Xc
+Like the previous directive, but for redirections with rdr-to in
+.Xr pf.conf 5 .
+.It Xo
+.Ic listen on Ar address
+.Op Ic port Ar port
+.Op Ic ssl
+.Xc
+Specify the address and port for the relay to listen on.
+The relay will accept incoming connections to the specified address.
+If the
+.Ic port
+option is not specified, the port from the
+.Ic listen on
+directive will be used.
+.Pp
+If the
+.Ic ssl
+keyword is present, the relay will accept connections using the
+encrypted SSL protocol.
+The relay will attempt to look up a private key in
+.Pa /etc/ssl/private/address:port.key
+and a public certificate in
+.Pa /etc/ssl/address:port.crt ,
+where
+.Ar address
+is the specified IP address and
+.Ar port
+is the specified port that the relay listens on.
+If these files are not present, the relay will continue to look in
+.Pa /etc/ssl/private/address.key
+and
+.Pa /etc/ssl/address.crt .
+See
+.Xr ssl 8
+for details about SSL server certificates.
+.It Ic protocol Ar name
+Use the specified protocol definition for the relay.
+The generic TCP protocol options will be used by default;
+see the
+.Sx PROTOCOLS
+section below.
+.It Ic session 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
+.Sh SSL RELAYS
+In addition to plain TCP,
+.Xr relayd 8
+supports the Secure Sockets Layer (SSL) and Transport Layer Security
+(TLS) cryptographic protocols for authenticated and encrypted relays.
+TLS is the successor of the original SSL protocol but the term SSL can
+refer to either of the protocols in
+.Nm .
+.Xr relayd 8
+can operate as an SSL client or server to offer a variety of options
+for different use cases related to SSL.
+.Bl -tag -width Ds
+.It Ic SSL client
+When configuring the relay
+.Ic forward
+statements with the
+.Ic with ssl
+directive,
+.Xr relayd 8
+will enable client-side SSL to connect to the remote host.
+This is commonly used for SSL tunneling and transparent encapsulation
+of plain TCP connections.
+See the
+.Ic forward to
+description in the
+.Sx RELAYS
+section for more details.
+.It Ic SSL server
+When specifying the
+.Ic ssl
+keyword in the relay
+.Ic listen
+statements,
+.Xr relayd 8
+will accept connections from clients as an SSL server.
+This mode is also known as
+.Dq SSL acceleration .
+See the
+.Ic listen on
+description in the
+.Sx RELAYS
+section for more details.
+.It Ic SSL client and server
+When combining both modes, SSL server and client,
+.Xr relayd 8
+can filter SSL connections as a man-in-the-middle.
+This combined mode is also called
+.Dq SSL inspection .
+The configuration requires additional X.509 certificate settings;
+see the
+.Ic ca key
+description in the
+.Sx PROTOCOLS
+section for more details.
+.El
+.Pp
+When configured for
+.Dq SSL inspection
+mode,
+.Xr relayd 8
+will listen for incoming connections which have been diverted to the
+local socket by PF.
+Before accepting and negotiating the incoming SSL connection as a
+server, it will look up the original destination address on the
+diverted socket, and pre-connect to the target server as an SSL client
+to obtain the remote SSL certificate.
+It will update or patch the obtained SSL certificate by replacing the
+included public key with its local server key because it doesn't have
+the private key of the remote server certificate.
+It also updates the X.509 issuer name to the local CA subject name and
+signs the certificate with its local CA key.
+This way it keeps all the other X.509 attributes that are already
+present in the server certificate, including the "green bar" extended
+validation attributes.
+Now it finally accepts the SSL connection from the diverted client
+using the updated certificate and continues to handle the connection
+and to connect to the remote server.
+.Sh PROTOCOLS
+Protocols are templates defining settings and rules for relays.
+They allow setting generic TCP options, SSL settings, and rules
+for the selected application layer protocol.
+.Pp
+The protocol directive is available for a number of different
+application layer protocols.
+There is no generic handler for UDP-based protocols because it is a
+stateless datagram-based protocol which has to look into the
+application layer protocol to find any possible state information.
+.Bl -tag -width Ds
+.It Ic dns protocol
+(UDP)
+Domain Name System (DNS) protocol.
+The requested IDs in the DNS header will be used to match the state.
+.Xr relayd 8
+replaces these IDs with random values to compensate for
+predictable values generated by some hosts.
+.It Ic http protocol
+Handle the HyperText Transfer Protocol
+(HTTP, or "HTTPS" if encapsulated in an SSL tunnel).
+.It Xo
+.Op Ic tcp
+.Ic protocol
+.Xc
+Generic handler for TCP-based protocols.
+This is the default.
+.El
+.Pp
+The available configuration directives are described below:
+.Bl -tag -width Ds
+.It Xo
+.Pq Ic block Ns | Ns Ic pass Ns | Ns Ic match
+.Op Ar rule
+.Xc
+Specify one or more rules to filter connections based on their
+network or application layer headers;
+see the
+.Sx FILTER RULES
+section for more details.
+.It Ic return error Op Ar option
+Return an error response to the client if an internal operation or the
+forward connection to the client failed.
+By default, the connection will be silently dropped.
+The effect of this option depends on the protocol: HTTP will send an
+error header and page to the client before closing the connection.
+Additional valid options are:
+.Bl -tag -width Ds
+.It Ic style Ar string
+Specify a Cascading Style Sheet (CSS) to be used for the returned
+HTTP error pages, for example:
+.Bd -literal -offset indent
+body { background: #a00000; color: white; }
+.Ed
+.El
+.It Ic ssl Ar option
+Set the SSL options and session settings.
+This is only used if SSL is enabled in the relay.
+Valid options are:
+.Bl -tag -width Ds
+.It Ic ca cert Ar path
+Specify a CA certificate for SSL inspection.
+For more information, see the
+.Ic ca key
+option below.
+.It Ic ca file Ar path
+This option enables CA verification in SSL client mode.
+The daemon will load the CA (Certificate Authority) certificates from
+the specified path to verify the server certificates.
+.Ox
+provides a default CA bundle in
+.Pa /etc/ssl/cert.pem .
+.It Ic ca key Ar path Ic password Ar password
+Specify a CA key for SSL inspection.
+The
+.Ar password
+argument will specify the password to decrypt the CA key
+(typically an RSA key).
+This option will enable SSL inspection if the following conditions
+are true:
+.Pp
+.Bl -bullet -compact -offset indent
+.It
+SSL client mode is enabled by the
+.Ic listen
+directive:
+.Ic listen on ... ssl .
+.It
+SSL server mode and divert lookups are enabled by the
+.Ic forward
+directive:
+.Ic forward with ssl to destination .
+.It
+The
+.Ic ca cert
+option is specified.
+.It
+The
+.Ic ca key
+option is specified.
+.El
+.It Ic ciphers Ar string
+Set the string defining the SSL cipher suite.
+If not specified, the default value
+.Ar HIGH:!aNULL
+will be used (strong crypto cipher suites without anonymous DH).
+See the CIPHERS section of
+.Xr openssl 1
+for information about SSL cipher suites and preference lists.
+.It Oo Ic no Oc Ic cipher-server-preference
+Prefer the server's cipher list over the client's preferences when
+choosing a cipher for the connection;
+disabled by default.
+.It Oo Ic no Oc Ic client-renegotiation
+Allow client-initiated renegotiation;
+enabled by default.
+Disable to mitigate a potential DoS risk.
+.It Ic ecdh Op Ic curve Ar name
+Set a named curve to use when generating EC keys for ECDHE-based
+cipher suites with Perfect Forward Security (PFS).
+If the curve
+.Ar name
+is not specified, the default curve
+.Ar prime256v1
+will be used.
+ECDHE is enabled by default.
+.It Ic no ecdh
+Disable ECDHE support.
+.It Ic edh Op Ic params Ar maximum
+Enable EDH-based cipher suites with Perfect Forward Security (PFS) for
+older clients that do not support ECDHE.
+If the
+.Ar maximum
+length of the DH params for EDH is not specified, the default value of
+.Ar 1024
+bits will be used.
+Other possible values are numbers between 1024 and 8192, including
+.Ar 1024 ,
+.Ar 1536 ,
+.Ar 2048 ,
+.Ar 4096 ,
+or
+.Ar 8192 .
+Values higher than 1024 bits can cause incompatibilities with older
+SSL clients.
+.It Ic no edh
+Disable EDH support.
+This is the default.
+.It Ic session cache Ar value
+Set the maximum size of the SSL session cache.
+If the
+.Ar value
+is zero, the default size defined by the SSL library will be used.
+A positive number will set the maximum size in bytes and the keyword
+.Ic disable
+will disable the SSL session cache.
+.It Xo
+.Op Ic no
+.Ic sslv2
+.Xc
+Enable the SSLv2 protocol;
+disabled by default.
+.It Xo
+.Op Ic no
+.Ic sslv3
+.Xc
+Disable the SSLv3 protocol;
+enabled by default.
+.It Xo
+.Op Ic no
+.Ic tlsv1
+.Xc
+Disable the TLSv1/SSLv3.1 protocol;
+enabled by default.
+.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
+.Ic 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
+.Ar 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 Xo
+.Op Ic no
+.Ic nodelay
+.Xc
+Enable the TCP NODELAY option for this connection.
+This is recommended to avoid delays in the relayed data stream,
+e.g. for SSH connections.
+.It Xo
+.Op Ic no
+.Ic sack
+.Xc
+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.
+.It Xo
+.Op Ic no
+.Ic splice
+.Xc
+Use socket splicing for zero-copy data transfer.
+This option is enabled by default.
+.El
+.El
+.Sh FILTER RULES
+Relays have the ability to filter connections based
+on their network or application layer headers.
+Filter rules apply options to connections based on the specified
+filter parameters.
+.Pp
+For each connection that is processed by a relay, the filter rules are
+evaluated in sequential order, from first to last.
+For
+.Ar block
+and
+.Ar pass ,
+the last matching rule decides what action is taken;
+if no rule matches the connection, the default action is to establish
+the connection without any additional action.
+For
+.Ar match ,
+rules are evaluated every time they match;
+the pass/block state of a connection remains unchanged.
+.Pp
+The filter action may be one of the following:
+.Bl -tag -width Ds
+.It Ic block
+The connection is blocked.
+If a
+.Ic block
+rule matches a new connection attempt, it will not be established.
+.Ic block
+rules can also trigger for existing connections after evaluating
+application layer parameters;
+any connection of the relay session will be instantly dropped.
+.It Ic match
+The connection is matched.
+This action does not alter the connection state, but allows
+additional parameters to the connection.
+.It Ic pass
+The connection is passed;
+.Xr relayd 8
+will continue to process the relay session normally.
+.El
+.Pp
+These filter parameters can be used in the rules:
+.Bl -tag -width Ds
+.It Ic request No or Ic response
+A relay session always consists of two connections:
+the
+.Ic request ,
+a client initiating a new connection to a server via the relay,
+and the
+.Ic response ,
+the server accepting the connection.
+Depending on the protocol,
+an established session can be purely request/response-based (like
+HTTP), exchange data in a bidirectional way (like arbitrary TCP
+sessions), or just contain a single datagram and an optional response
+(like UDP-based protocols).
+But the client always
+.Ar requests
+to communicate with a remote peer; the server.
+.It Ic quick
+If a connection is matched by a rule with the
+.Ic quick
+option set,
+the rule is considered to be the last matching rule and any further
+evaluation is skipped.
+.It Ic inet No or Ic inet6
+Only match connections with the specified address family,
+either of type IPv4 or IPv6.
+.\" XXX .It Ic from
+.\" XXX .It Ic to
+.It Ic label Ar string
+The label will be printed as part of the error message if the
+.Ic return error
+option is set and may contain HTML tags, for example:
+.Bd -literal -offset indent
+block request url digest 5c1e03f58f8ce0b457474ffb371fd1ef \e
+ label "\*(Lta href='http://example.com/adv.pl?id=7359'\*(Gt\e
+ Advisory provided by example.com\*(Lt/a\*(Gt"
+.Ed
+.It Ic no Ar parameter
+Reset a sticky parameter that was previously set by a matching rule.
+The
+.Ar parameter
+is a keyword that can be either
+.Ic label
+or
+.Ic tag .
+.It Ic tag Ar string
+Add a "sticky" tag to connections matching this filter rule.
+Tags can be used to filter the connection by further rules using the
+.Ic tagged
+option.
+Only one tag is assigned per connection;
+the tag will be replaced if the connection is already tagged.
+.It Ic tagged Ar string
+Match the connection if it is already tagged with a given tag by a
+previous rule.
+.El
+.Pp
+The following parameters are available when using the
+.Ic http
+protocol:
+.Bl -tag -width Ds
+.It Ic method Ar NAME
+Match the HTTP request method.
+The method is specified by
+.Ar name
+and can be either
+.Ic CONNECT ,
+.Ic COPY ,
+.Ic DELETE ,
+.Ic GET ,
+.Ic HEAD ,
+.Ic LOCK ,
+.Ic MKCOL ,
+.Ic MOVE ,
+.Ic OPTIONS ,
+.Ic PATCH ,
+.Ic POST ,
+.Ic PROPFIND ,
+.Ic PROPPATCH ,
+.Ic PUT ,
+.Ic TRACE ,
+or
+.Ic UNLOCK .
+.It Xo
+.Ar type Ar option
+.Oo Oo Ic digest Oc
+.Pq Ar key Ns | Ns Ic file Ar path
+.Oo Ic value Ar value Oc Oc
+.Xc
+Match a specified HTTP header entity and an optional
+.Ic key
+and
+.Ic value .
+An
+.Ic option
+can be specified to modify the matched entity or to trigger an event.
+The entity is extracted from the HTTP request or response header and
+can be either of
+.Ar type
+.Ic cookie ,
+.Ic header ,
+.Ic path ,
+.Ic query ,
+or
+.Ic url .
+.Pp
+Instead of a single
+.Ar key ,
+multiple keys can be loaded from a
+.Ic file
+specified by
+.Ar path
+that contains one key per line.
+Lines will be stripped at the first whitespace or newline character
+and any empty lines or lines beginning with a hash mark (`#') will be
+ignored.
+.Pp
+If the
+.Ic digest
+keyword is specified,
+compare the message digest of the key against the defined string.
+The algorithm used is determined by the string length of the
+.Ar key
+argument, either SHA1 (40 characters) or MD5 (32 characters).
+To compute the digest,
+for example for a
+.Ic url ,
+use this simple command:
+.Bd -literal -offset indent
+$ echo -n "example.com/path/?args" | sha1
+.Ed
+.El
+.Pp
+.Bq Ar type
+may be one of:
+.Bl -tag -width Ds
+.It Ic cookie Ar option Oo Ar key Oo Ic value Ar value Oc Oc
+Look up the entity as a value in the Cookie header.
+This type is only available with the direction
+.Ic request .
+.It Ic header Ar option Oo Ar key Oo Ic value Ar value Oc Oc
+Look up the entity in the application protocol headers, like HTTP
+headers in
+.Ic http
+mode.
+.It Ic path Ar option Oo Ar key Oo Ic value Ar value Oc Oc
+Look up the entity as a value in the URL path when using the
+.Ic http
+protocol.
+This type is only available with the direction
+.Ic request .
+The
+.Ar key
+will match the path of the requested URL without the hostname
+and query and the value will match the complete query,
+for example:
+.Bd -literal -offset indent
+block path "/index.html"
+block path "/cgi-bin/t.cgi" value "foo=bar*"
+.Ed
+.It Ic query Ar option Oo Ar key Oo Ic value Ar value Oc Oc
+Look up the entity as a query variable in the URL when using the
+.Ic http
+protocol.
+This type is only available with the direction
+.Ic request ,
+for example:
+.Bd -literal -offset indent
+# Will match /cgi-bin/example.pl?foo=bar&ok=yes
+request query expect "bar" from "foo"
+.Ed
+.It Ic url Ar option Oo Oo Ic digest Oc Ar key Oo Ic value Ar value Oc Oc
+Look up the entity as a URL suffix/prefix expression consisting of a
+canonicalized hostname without port or suffix and a path name or
+prefix when using the
+.Ic http
+protocol.
+This type is only available with the direction
+.Ic request ,
+for example:
+.Bd -literal -offset indent
+block url "example.com/index.html"
+block url "example.com/test.cgi?val=1"
+.Ed
+.Pp
+.Xr relayd 8
+will match the full URL and different possible suffix/prefix
+combinations by stripping subdomains and path components (up to 5
+levels), and the query string.
+For example, the following
+lookups will be done for
+.Ar http://www.example.com:81/1/2/3/4/5.html?query=yes :
+.Bd -literal -offset indent
+www.example.com/1/2/3/4/5.html?query=yes
+www.example.com/1/2/3/4/5.html
+www.example.com/
+www.example.com/1/
+www.example.com/1/2/
+www.example.com/1/2/3/
+example.com/1/2/3/4/5.html?query=yes
+example.com/1/2/3/4/5.html
+example.com/
+example.com/1/
+example.com/1/2/
+example.com/1/2/3/
+.Ed
+.El
+.Pp
+.Bq Ar option
+may be one of:
+.Bl -tag -width Ds
+.It Ic append
+Append the specified
+.Ar value
+to a protocol entity with the selected
+.Ar key
+name.
+If it does not exist, it will be created with the new value.
+.Pp
+The value string may contain predefined macros that will be expanded
+at runtime:
+.Pp
+.Bl -tag -width $SERVER_ADDR -offset indent -compact
+.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 $SERVER_ADDR
+The configured IP address of the relay.
+.It Ic $SERVER_PORT
+The configured TCP server port of the relay.
+.It Ic $SERVER_NAME
+The server software name of
+.Xr relayd 8 .
+.It Ic $TIMEOUT
+The configured session timeout of the relay.
+.El
+.It Ic hash
+Feed the
+.Ar value
+of the selected entity into the load balancing hash to select the
+target host.
+See the
+.Ic table
+keyword in the
+.Sx RELAYS
+section above.
+.It Ic log
+Log the
+.Ar key
+name and the
+.Ar value
+of the entity.
+.It Ic remove
+Remove the entity with the selected
+.Ar key
+name.
+.It Ic set
+Like the
+.Ic append
+directive above, but change the contents of the specified entity.
+If
+.Ar key
+does not exist in the request, it will be created with the new
+.Ar value .
+.Pp
+The
+.Ar value
+string
+may contain predefined macros that will be expanded at runtime,
+as detailed for the
+.Ic append
+directive above.
+.El
+.Sh ROUTERS
+Routers represent routing table entries in the kernel forwarding
+database, see
+.Xr route 4 ,
+and a table of associated gateways.
+They are used to dynamically insert or remove routes with gateways
+based on their availability and health-check results.
+A router can include multiple network statements and a single forward
+statement with a table of one or more gateways.
+All entries in a single router directive must match the same address
+family, either IPv4 or IPv6.
+.Pp
+The kernel supports multipath routing when multiple gateways exist to
+the same destination address.
+The multipath routing behaviour can be changed globally using the
+.Xr sysctl 8
+variables
+.Va net.inet.ip.multipath
+and
+.Va net.inet6.ip6.multipath .
+With the default setting of 0,
+the first route selected will be used for subsequent packets to that
+destination regardless of source.
+Setting it to 1 will enable load balancing based on the packet source
+address across gateways; multiple routes with the same priority are
+used equally.
+The kernel will also check the link state of the related network
+interface and try a different route if it is not active.
+.Pp
+The configuration directives that are valid in the
+.Ic routers
+context are described below:
+.Bl -tag -width Ds
+.It Xo
+.Ic forward to
+.Aq Ar table
+.Ic port Ar number
+.Ar options ...
+.Xc
+Specify the table of target gateways to be used; see the
+.Sx TABLES
+section above for information about table options.
+This entry is mandatory and must be specified once.
+.It Xo
+.Ic route
+.Ar address Ns Li / Ns Ar prefix
+.Xc
+Specify the network address and prefix length of a route destination
+that is reachable via the active gateways.
+This entry must be specified at least once in a router directive.
+.It Ic rtable Ar id
+Add the routes to the kernel routing table with the specified
+.Ar id .
+.It Ic rtlabel Ar label
+Add the routes with the specified
+.Ar label
+to the kernel routing table.
+.El
+.Sh FILES
+.Bl -tag -width Ds -compact
+.It Pa /etc/relayd.conf
+.Xr relayd 8
+configuration file.
+.Pp
+.It Pa /etc/services
+Service name database.
+.Pp
+.It Pa /etc/ssl/address.crt
+.It Pa /etc/ssl/address:port.crt
+.It Pa /etc/ssl/private/address.key
+.It Pa /etc/ssl/private/address:port.key
+Location of the relay SSL server certificates, where
+.Ar address
+is the configured IP address
+and
+.Ar port
+is the configured port number of the relay.
+.Pp
+.It Pa /etc/ssl/cert.pem
+Default location of the CA bundle that can be used with
+.Xr relayd 8 .
+.El
+.Sh EXAMPLES
+This configuration file would create a redirection service
+.Dq www
+which load balances four hosts
+and falls back to one host containing a
+.Dq sorry page :
+.Bd -literal -offset indent
+www1=front-www1.private.example.com
+www2=front-www2.private.example.com
+www3=front-www3.private.example.com
+www4=front-www4.private.example.com
+
+interval 5
+
+table \*(Ltphphosts\*(Gt { $www1, $www2, $www3, $www4 }
+table \*(Ltsorryhost\*(Gt disable { sorryhost.private.example.com }
+
+redirect "www" {
+ listen on www.example.com port 8080 interface trunk0
+ listen on www6.example.com port 80 interface trunk0
+
+ pftag REDIRECTED
+
+ forward to \*(Ltphphosts\*(Gt port 8080 timeout 300 \e
+ check http "/" digest "630aa3c2f..."
+ forward to \*(Ltsorryhost\*(Gt port 8080 timeout 300 check icmp
+}
+.Ed
+.Pp
+It is possible to specify multiple listen directives with different IP
+protocols in a single redirection configuration:
+.Bd -literal -offset indent
+redirect "dns" {
+ listen on dns.example.com tcp port 53
+ listen on dns.example.com udp port 53
+
+ forward to \*(Ltdnshosts\*(Gt port 53 check tcp
+}
+.Ed
+.Pp
+The following configuration would add a relay to forward
+secure HTTPS connections to a pool of HTTP webservers
+using the
+.Ic loadbalance
+mode (SSL acceleration and layer 7 load balancing).
+The HTTP protocol definition will add two HTTP headers containing
+address information of the client and the server, set the
+.Dq Keep-Alive
+header value to the configured session timeout,
+and include the
+.Dq sessid
+variable in the hash to calculate the target host:
+.Bd -literal -offset indent
+http protocol "http_ssl" {
+ match header append "X-Forwarded-For" \e
+ value "$REMOTE_ADDR"
+ match header append "X-Forwarded-By" \e
+ value "$REMOTE_ADDR:$SERVER_PORT"
+ match header set "Keep-Alive" value "$TIMEOUT"
+
+ match query hash "sessid"
+ match hash "sessid"
+
+ pass
+ block path "/cgi-bin/index.cgi" value "*command=*"
+
+ ssl { sslv2, ciphers "MEDIUM:HIGH" }
+}
+
+relay "sslaccel" {
+ listen on www.example.com port 443 ssl
+ protocol "http_ssl"
+ forward to \*(Ltphphosts\*(Gt port 8080 mode loadbalance check tcp
+}
+.Ed
+.Pp
+The second relay example will accept incoming connections to port
+2222 and forward them to a remote SSH server.
+The TCP
+.Ic nodelay
+option will allow a
+.Dq smooth
+SSH session without delays between keystrokes or displayed output on
+the terminal:
+.Bd -literal -offset indent
+protocol "myssh" {
+ tcp { nodelay, socket buffer 65536 }
+}
+
+relay "sshforward" {
+ listen on www.example.com port 2222
+ protocol "myssh"
+ forward to shell.example.com port 22
+}
+.Ed
+.Pp
+The following relay example will configure
+.Dq SSL inspection
+as described in the
+.Sx SSL RELAYS
+section.
+To start, first generate a new local CA key and certificate:
+.Bd -literal -offset indent
+# openssl req -x509 -days 365 -newkey rsa:2048 \e
+ -keyout /etc/ssl/private/ca.key -out /etc/ssl/ca.crt
+.Ed
+.Pp
+An SSL server key and self-signed cert for 127.0.0.1 are also required;
+see
+.Ic listen on
+in the
+.Sx RELAYS
+section for more details about certificate locations.
+Configure the packet filter with a matching divert rule in
+.Xr pf.conf 5 :
+.Bd -literal -offset indent
+# Divert incoming HTTPS traffic to relayd
+pass in on vlan1 inet proto tcp to port 443 \e
+ divert-to localhost port 8443
+.Ed
+.Pp
+And finally configure the SSL inspection in
+.Nm :
+.Bd -literal -offset indent
+http protocol httpfilter {
+ return error
+
+ pass
+ match label "Prohibited!"
+ block url "social.network.example.com/"
+
+ # New configuration directives for SSL Interception
+ ssl ca key "/etc/ssl/private/ca.key" password "password123"
+ ssl ca cert "/etc/ssl/ca.crt"
+}
+
+relay sslinspect {
+ listen on 127.0.0.1 port 8443 ssl
+ protocol httpfilter
+ forward with ssl to destination
+}
+.Ed
+.Pp
+The next simple router configuration example can be used to run
+redundant, health-checked WAN links:
+.Bd -literal -offset indent
+table \*(Ltgateways\*(Gt { $gw1 ip ttl 1, $gw2 ip ttl 1 }
+router "uplinks" {
+ route 0.0.0.0/0
+ forward to \*(Ltgateways\*(Gt check icmp
+}
+.Ed
+.Sh SEE ALSO
+.Xr relayctl 8 ,
+.Xr relayd 8 ,
+.Xr snmpd 8 ,
+.Xr ssl 8
+.Sh HISTORY
+The
+.Nm
+file format, formerly known as
+.Ic hoststated.conf ,
+first appeared in
+.Ox 4.1 .
+It was renamed to
+.Nm
+in
+.Ox 4.3 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Xr relayd 8
+program was written by
+.An Pierre-Yves Ritschard Aq Mt pyr@openbsd.org
+and
+.An Reyk Floeter Aq Mt reyk@openbsd.org .
+.Sh CAVEATS
+.Xr relayd 8
+Verification of SSL server certificates is based on a static CA bundle
+and
+.Xr relayd 8
+currently does not support CRLs (Certificate Revocation Lists).
diff --git a/httpd.h b/httpd.h
new file mode 100644
index 0000000..f871583
--- /dev/null
+++ b/httpd.h
@@ -0,0 +1,395 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2006 - 2014 Reyk Floeter <reyk@openbsd.org>
+ * Copyright (c) 2006, 2007 Pierre-Yves Ritschard <pyr@openbsd.org>
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _HTTPD_H
+#define _HTTPD_H
+
+#include <sys/tree.h>
+
+#include <sys/param.h> /* MAXHOSTNAMELEN */
+#include <limits.h>
+#include <imsg.h>
+
+#define CONF_FILE "/etc/httpd.conf"
+#define HTTPD_SOCKET "/var/run/httpd.sock"
+#define HTTPD_USER "www"
+#define HTTPD_SERVERNAME "OpenBSD httpd"
+#define FD_RESERVE 5
+
+#define SERVER_MAX_CLIENTS 1024
+#define SERVER_TIMEOUT 600
+#define SERVER_CACHESIZE -1 /* use default size */
+#define SERVER_NUMPROC 3
+#define SERVER_MAXPROC 32
+#define SERVER_MAXHEADERLENGTH 8192
+#define SERVER_BACKLOG 10
+#define SERVER_OUTOF_FD_RETRIES 5
+
+#define CONFIG_RELOAD 0x00
+#define CONFIG_SERVERS 0x01
+#define CONFIG_ALL 0xff
+
+#define TCPFLAG_NODELAY 0x01
+#define TCPFLAG_NNODELAY 0x02
+#define TCPFLAG_SACK 0x04
+#define TCPFLAG_NSACK 0x08
+#define TCPFLAG_BUFSIZ 0x10
+#define TCPFLAG_IPTTL 0x20
+#define TCPFLAG_IPMINTTL 0x40
+#define TCPFLAG_NSPLICE 0x80
+#define TCPFLAG_DEFAULT 0x00
+
+#define TCPFLAG_BITS \
+ "\10\01NODELAY\02NO_NODELAY\03SACK\04NO_SACK" \
+ "\05SOCKET_BUFFER_SIZE\06IP_TTL\07IP_MINTTL\10NO_SPLICE"
+
+enum httpchunk {
+ TOREAD_UNLIMITED = -1,
+ TOREAD_HTTP_HEADER = -2,
+ TOREAD_HTTP_CHUNK_LENGTH = -3,
+ TOREAD_HTTP_CHUNK_TRAILER = -4
+};
+
+#if DEBUG > 1
+#define DPRINTF log_debug
+#define DEBUG_CERT 1
+#else
+#define DPRINTF(x...) do {} while(0)
+#endif
+
+struct ctl_flags {
+ u_int8_t cf_opts;
+ u_int32_t cf_flags;
+};
+
+struct portrange {
+ in_port_t val[2];
+ u_int8_t op;
+};
+
+struct address {
+ struct sockaddr_storage ss;
+ int ipproto;
+ struct portrange port;
+ char ifname[IFNAMSIZ];
+ TAILQ_ENTRY(address) entry;
+};
+TAILQ_HEAD(addresslist, address);
+
+/* initially control.h */
+struct control_sock {
+ const char *cs_name;
+ struct event cs_ev;
+ struct event cs_evt;
+ int cs_fd;
+ int cs_restricted;
+ void *cs_env;
+
+ TAILQ_ENTRY(control_sock) cs_entry;
+};
+TAILQ_HEAD(control_socks, control_sock);
+
+struct {
+ struct event ev;
+ int fd;
+} control_state;
+
+enum blockmodes {
+ BM_NORMAL,
+ BM_NONBLOCK
+};
+
+struct imsgev {
+ struct imsgbuf ibuf;
+ void (*handler)(int, short, void *);
+ struct event ev;
+ struct privsep_proc *proc;
+ void *data;
+ short events;
+};
+
+#define IMSG_SIZE_CHECK(imsg, p) do { \
+ if (IMSG_DATA_SIZE(imsg) < sizeof(*p)) \
+ fatalx("bad length imsg received"); \
+} while (0)
+#define IMSG_DATA_SIZE(imsg) ((imsg)->hdr.len - IMSG_HEADER_SIZE)
+
+struct ctl_conn {
+ TAILQ_ENTRY(ctl_conn) entry;
+ u_int8_t flags;
+ u_int waiting;
+#define CTL_CONN_NOTIFY 0x01
+ struct imsgev iev;
+
+};
+TAILQ_HEAD(ctl_connlist, ctl_conn);
+
+enum imsg_type {
+ IMSG_NONE,
+ IMSG_CTL_OK,
+ IMSG_CTL_FAIL,
+ IMSG_CTL_VERBOSE,
+ IMSG_CTL_RESET,
+ IMSG_CTL_SHUTDOWN,
+ IMSG_CTL_RELOAD,
+ IMSG_CTL_NOTIFY,
+ IMSG_CTL_END,
+ IMSG_CTL_START,
+ IMSG_CFG_DONE
+};
+
+enum privsep_procid {
+ PROC_ALL = -1,
+ PROC_PARENT = 0,
+ PROC_SERVER,
+ PROC_MAX
+} privsep_process;
+
+/* Attach the control socket to the following process */
+#define PROC_CONTROL PROC_PARENT
+
+struct privsep_pipes {
+ int *pp_pipes[PROC_MAX];
+};
+
+struct privsep {
+ struct privsep_pipes *ps_pipes[PROC_MAX];
+ struct privsep_pipes *ps_pp;
+
+ struct imsgev *ps_ievs[PROC_MAX];
+ const char *ps_title[PROC_MAX];
+ pid_t ps_pid[PROC_MAX];
+ u_int8_t ps_what[PROC_MAX];
+
+ u_int ps_instances[PROC_MAX];
+ u_int ps_ninstances;
+ u_int ps_instance;
+
+ struct control_sock ps_csock;
+ struct control_socks ps_rcsocks;
+
+ /* Event and signal handlers */
+ struct event ps_evsigint;
+ struct event ps_evsigterm;
+ struct event ps_evsigchld;
+ struct event ps_evsighup;
+ struct event ps_evsigpipe;
+
+ int ps_noaction;
+ struct passwd *ps_pw;
+ struct httpd *ps_env;
+};
+
+struct privsep_proc {
+ const char *p_title;
+ enum privsep_procid p_id;
+ int (*p_cb)(int, struct privsep_proc *,
+ struct imsg *);
+ pid_t (*p_init)(struct privsep *,
+ struct privsep_proc *);
+ void (*p_shutdown)(void);
+ u_int p_instance;
+ const char *p_chroot;
+ struct privsep *p_ps;
+ struct httpd *p_env;
+};
+
+struct client {
+ u_int32_t clt_id;
+ pid_t clt_pid;
+ void *clt_server;
+ u_int32_t clt_serverid;
+
+ int clt_s;
+ in_port_t clt_port;
+ struct sockaddr_storage clt_ss;
+ struct bufferevent *clt_bev;
+ struct evbuffer *clt_output;
+ struct event clt_ev;
+
+ off_t clt_toread;
+ int clt_line;
+ size_t clt_headerlen;
+ int clt_done;
+
+ struct evbuffer *clt_log;
+ struct timeval clt_timeout;
+ struct timeval clt_tv_start;
+ struct timeval clt_tv_last;
+ struct event clt_inflightevt;
+
+ SPLAY_ENTRY(client) clt_nodes;
+};
+SPLAY_HEAD(client_tree, client);
+
+struct server_config {
+ u_int32_t id;
+ u_int32_t flags;
+ char name[MAXHOSTNAMELEN];
+ in_port_t port;
+ struct sockaddr_storage ss;
+ struct timeval timeout;
+};
+
+struct server {
+ TAILQ_ENTRY(server) srv_entry;
+ struct server_config srv_conf;
+
+ u_int8_t srv_tcpflags;
+ int srv_tcpbufsiz;
+ int srv_tcpbacklog;
+ u_int8_t srv_tcpipttl;
+ u_int8_t srv_tcpipminttl;
+
+ int srv_s;
+ in_port_t src_port;
+ struct sockaddr_storage srv_ss;
+ struct bufferevent *srv_bev;
+ int srv_dsts;
+ struct bufferevent *srv_dstbev;
+
+ struct event srv_ev;
+ struct event srv_evt;
+
+ struct client_tree srv_clients;
+};
+TAILQ_HEAD(serverlist, server);
+
+struct httpd {
+ u_int8_t sc_opts;
+ u_int32_t sc_flags;
+ const char *sc_conffile;
+ struct event sc_ev;
+ u_int16_t sc_prefork_server;
+ u_int16_t sc_id;
+
+ struct serverlist *sc_servers;
+
+ struct privsep *sc_ps;
+ int sc_reload;
+};
+
+#define HTTPD_OPT_VERBOSE 0x01
+#define HTTPD_OPT_NOACTION 0x04
+#define HTTPD_OPT_LOGUPDATE 0x08
+#define HTTPD_OPT_LOGNOTIFY 0x10
+#define HTTPD_OPT_LOGALL 0x18
+
+/* control.c */
+int control_init(struct privsep *, struct control_sock *);
+int control_listen(struct control_sock *);
+void control_cleanup(struct control_sock *);
+void control_dispatch_imsg(int, short, void *);
+void control_imsg_forward(struct imsg *);
+struct ctl_conn *
+ control_connbyfd(int);
+void socket_set_blockmode(int, enum blockmodes);
+
+extern struct ctl_connlist ctl_conns;
+
+/* parse.y */
+int parse_config(const char *, struct httpd *);
+int load_config(const char *, struct httpd *);
+int cmdline_symset(char *);
+
+/* server.c */
+pid_t server(struct privsep *, struct privsep_proc *);
+int server_privinit(struct server *);
+int server_socket_af(struct sockaddr_storage *, in_port_t);
+in_port_t
+ server_socket_getport(struct sockaddr_storage *);
+void server_write(struct bufferevent *, void *);
+void server_read(struct bufferevent *, void *);
+void server_error(struct bufferevent *, short, void *);
+void server_close(struct client *, const char *);
+void server_dump(struct client *, const void *, size_t);
+int server_client_cmp(struct client *, struct client *);
+int server_bufferevent_print(struct client *, const char *);
+int server_bufferevent_write_buffer(struct client *,
+ struct evbuffer *);
+int server_bufferevent_write_chunk(struct client *,
+ struct evbuffer *, size_t);
+int server_bufferevent_add(struct event *, int);
+int server_bufferevent_write(struct client *, void *, size_t);
+
+SPLAY_PROTOTYPE(client_tree, client, clt_nodes, server_client_cmp);
+
+/* httpd.c */
+void event_again(struct event *, int, short,
+ void (*)(int, short, void *),
+ struct timeval *, struct timeval *, void *);
+const char *canonicalize_host(const char *, char *, size_t);
+void imsg_event_add(struct imsgev *);
+int imsg_compose_event(struct imsgev *, u_int16_t, u_int32_t,
+ pid_t, int, void *, u_int16_t);
+void socket_rlimit(int);
+char *get_string(u_int8_t *, size_t);
+void *get_data(u_int8_t *, size_t);
+int accept_reserve(int, struct sockaddr *, socklen_t *, int,
+ volatile int *);
+
+/* log.c */
+void log_init(int);
+void log_verbose(int);
+void log_warn(const char *, ...) __attribute__((__format__ (printf, 1, 2)));
+void log_warnx(const char *, ...) __attribute__((__format__ (printf, 1, 2)));
+void log_info(const char *, ...) __attribute__((__format__ (printf, 1, 2)));
+void log_debug(const char *, ...) __attribute__((__format__ (printf, 1, 2)));
+void vlog(int, const char *, va_list) __attribute__((__format__ (printf, 2, 0)));
+__dead void fatal(const char *);
+__dead void fatalx(const char *);
+const char *print_host(struct sockaddr_storage *, char *, size_t);
+const char *print_time(struct timeval *, struct timeval *, char *, size_t);
+const char *printb_flags(const u_int32_t, const char *);
+void getmonotime(struct timeval *);
+
+/* proc.c */
+void proc_init(struct privsep *, struct privsep_proc *, u_int);
+void proc_kill(struct privsep *);
+void proc_listen(struct privsep *, struct privsep_proc *, size_t);
+void proc_dispatch(int, short event, void *);
+pid_t proc_run(struct privsep *, struct privsep_proc *,
+ struct privsep_proc *, u_int,
+ void (*)(struct privsep *, struct privsep_proc *, void *), void *);
+void proc_range(struct privsep *, enum privsep_procid, int *, int *);
+int proc_compose_imsg(struct privsep *, enum privsep_procid, int,
+ u_int16_t, int, void *, u_int16_t);
+int proc_composev_imsg(struct privsep *, enum privsep_procid, int,
+ u_int16_t, int, const struct iovec *, int);
+int proc_forward_imsg(struct privsep *, struct imsg *,
+ enum privsep_procid, int);
+struct imsgbuf *
+ proc_ibuf(struct privsep *, enum privsep_procid, int);
+struct imsgev *
+ proc_iev(struct privsep *, enum privsep_procid, int);
+void imsg_event_add(struct imsgev *);
+int imsg_compose_event(struct imsgev *, u_int16_t, u_int32_t,
+ pid_t, int, void *, u_int16_t);
+int imsg_composev_event(struct imsgev *, u_int16_t, u_int32_t,
+ pid_t, int, const struct iovec *, int);
+
+/* config.c */
+int config_init(struct httpd *);
+void config_purge(struct httpd *, u_int);
+int config_setreset(struct httpd *, u_int);
+int config_getreset(struct httpd *, struct imsg *);
+int config_getcfg(struct httpd *, struct imsg *);
+
+#endif /* _HTTPD_H */
diff --git a/log.c b/log.c
new file mode 100644
index 0000000..9e1adaf
--- /dev/null
+++ b/log.c
@@ -0,0 +1,251 @@
+/* $OpenBSD: log.c,v 1.22 2014/04/18 16:13:02 reyk Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/tree.h>
+
+#include <net/if.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <event.h>
+#include <netdb.h>
+#include <ctype.h>
+
+#include <openssl/ssl.h>
+
+#include "httpd.h"
+
+int debug;
+int verbose;
+
+void vlog(int, const char *, va_list)
+ __attribute__((__format__ (printf, 2, 0)));
+void logit(int, const char *, ...)
+ __attribute__((__format__ (printf, 2, 3)));
+
+void
+log_init(int n_debug)
+{
+ extern char *__progname;
+
+ debug = n_debug;
+ verbose = n_debug;
+
+ if (!debug)
+ openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON);
+
+ tzset();
+}
+
+void
+log_verbose(int v)
+{
+ verbose = v;
+}
+
+void
+logit(int pri, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vlog(pri, fmt, ap);
+ va_end(ap);
+}
+
+void
+vlog(int pri, const char *fmt, va_list ap)
+{
+ char *nfmt;
+
+ if (debug) {
+ /* best effort in out of mem situations */
+ if (asprintf(&nfmt, "%s\n", fmt) == -1) {
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ } else {
+ vfprintf(stderr, nfmt, ap);
+ free(nfmt);
+ }
+ fflush(stderr);
+ } else
+ vsyslog(pri, fmt, ap);
+}
+
+
+void
+log_warn(const char *emsg, ...)
+{
+ char *nfmt;
+ va_list ap;
+
+ /* best effort to even work in out of memory situations */
+ if (emsg == NULL)
+ logit(LOG_CRIT, "%s", strerror(errno));
+ else {
+ va_start(ap, emsg);
+
+ if (asprintf(&nfmt, "%s: %s", emsg, strerror(errno)) == -1) {
+ /* we tried it... */
+ vlog(LOG_CRIT, emsg, ap);
+ logit(LOG_CRIT, "%s", strerror(errno));
+ } else {
+ vlog(LOG_CRIT, nfmt, ap);
+ free(nfmt);
+ }
+ va_end(ap);
+ }
+}
+
+void
+log_warnx(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_CRIT, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_info(const char *emsg, ...)
+{
+ va_list ap;
+
+ va_start(ap, emsg);
+ vlog(LOG_INFO, emsg, ap);
+ va_end(ap);
+}
+
+void
+log_debug(const char *emsg, ...)
+{
+ va_list ap;
+
+ if (verbose > 1) {
+ va_start(ap, emsg);
+ vlog(LOG_DEBUG, emsg, ap);
+ va_end(ap);
+ }
+}
+
+void
+fatal(const char *emsg)
+{
+ if (emsg == NULL)
+ logit(LOG_CRIT, "fatal: %s", strerror(errno));
+ else
+ if (errno)
+ logit(LOG_CRIT, "fatal: %s: %s",
+ emsg, strerror(errno));
+ else
+ logit(LOG_CRIT, "fatal: %s", emsg);
+
+ exit(1);
+}
+
+void
+fatalx(const char *emsg)
+{
+ errno = 0;
+ fatal(emsg);
+}
+
+const char *
+print_host(struct sockaddr_storage *ss, char *buf, size_t len)
+{
+ if (getnameinfo((struct sockaddr *)ss, ss->ss_len,
+ buf, len, NULL, 0, NI_NUMERICHOST) != 0) {
+ buf[0] = '\0';
+ return (NULL);
+ }
+ return (buf);
+}
+
+const char *
+print_time(struct timeval *a, struct timeval *b, char *buf, size_t len)
+{
+ struct timeval tv;
+ u_long h, sec, min;
+
+ timerclear(&tv);
+ timersub(a, b, &tv);
+ sec = tv.tv_sec % 60;
+ min = tv.tv_sec / 60 % 60;
+ h = tv.tv_sec / 60 / 60;
+
+ snprintf(buf, len, "%.2lu:%.2lu:%.2lu", h, min, sec);
+ return (buf);
+}
+
+const char *
+printb_flags(const u_int32_t v, const char *bits)
+{
+ static char buf[2][BUFSIZ];
+ static int idx = 0;
+ int i, any = 0;
+ char c, *p, *r;
+
+ p = r = buf[++idx % 2];
+ bzero(p, 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/parse.y b/parse.y
new file mode 100644
index 0000000..7c2dfa5
--- /dev/null
+++ b/parse.y
@@ -0,0 +1,1019 @@
+/* $OpenBSD: parse.y,v 1.187 2014/07/11 17:35:16 reyk Exp $ */
+
+/*
+ * Copyright (c) 2007 - 2014 Reyk Floeter <reyk@openbsd.org>
+ * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org>
+ * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org>
+ * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
+ * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
+ * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
+ * Copyright (c) 2001 Markus Friedl. All rights reserved.
+ * Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
+ * Copyright (c) 2001 Theo de Raadt. All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+%{
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/queue.h>
+#include <sys/ioctl.h>
+#include <sys/hash.h>
+
+#include <net/if.h>
+#include <net/pfvar.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <net/route.h>
+
+#include <ctype.h>
+#include <unistd.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <netdb.h>
+#include <string.h>
+#include <ifaddrs.h>
+#include <syslog.h>
+
+#include <openssl/ssl.h>
+
+#include "httpd.h"
+#include "http.h"
+
+TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
+static struct file {
+ TAILQ_ENTRY(file) entry;
+ FILE *stream;
+ char *name;
+ int lineno;
+ int errors;
+} *file, *topfile;
+struct file *pushfile(const char *, int);
+int popfile(void);
+int check_file_secrecy(int, const char *);
+int yyparse(void);
+int yylex(void);
+int yyerror(const char *, ...);
+int kw_cmp(const void *, const void *);
+int lookup(char *);
+int lgetc(int);
+int lungetc(int);
+int findeol(void);
+
+TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
+struct sym {
+ TAILQ_ENTRY(sym) entry;
+ int used;
+ int persist;
+ char *nam;
+ char *val;
+};
+int symset(const char *, const char *, int);
+char *symget(const char *);
+
+struct httpd *conf = NULL;
+static int errors = 0;
+static int loadcfg = 0;
+uint32_t last_server_id = 0;
+
+static struct server *srv = NULL;
+struct serverlist servers;
+
+struct address *host_v4(const char *);
+struct address *host_v6(const char *);
+int host_dns(const char *, struct addresslist *,
+ int, struct portrange *, const char *, int);
+int host_if(const char *, struct addresslist *,
+ int, struct portrange *, const char *, int);
+int host(const char *, struct addresslist *,
+ int, struct portrange *, const char *, int);
+void host_free(struct addresslist *);
+int getservice(char *);
+int is_if_in_group(const char *, const char *);
+
+typedef struct {
+ union {
+ int64_t number;
+ char *string;
+ struct timeval tv;
+ struct portrange port;
+ struct {
+ struct sockaddr_storage ss;
+ char name[MAXHOSTNAMELEN];
+ } addr;
+ } v;
+ int lineno;
+} YYSTYPE;
+
+%}
+
+%token ALL PORT LISTEN PREFORK SERVER ERROR INCLUDE LOG VERBOSE
+%token UPDATES INCLUDE
+%token <v.string> STRING
+%token <v.number> NUMBER
+%type <v.number> loglevel
+
+%%
+
+grammar : /* empty */
+ | grammar include '\n'
+ | grammar '\n'
+ | grammar varset '\n'
+ | grammar main '\n'
+ | grammar server '\n'
+ | grammar error '\n' { file->errors++; }
+ ;
+
+include : INCLUDE STRING {
+ struct file *nfile;
+
+ if ((nfile = pushfile($2, 0)) == NULL) {
+ yyerror("failed to include file %s", $2);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+
+ file = nfile;
+ lungetc('\n');
+ }
+ ;
+
+varset : STRING '=' STRING {
+ if (symset($1, $3, 0) == -1)
+ fatal("cannot store variable");
+ free($1);
+ free($3);
+ }
+ ;
+
+main : LOG loglevel {
+ if (loadcfg)
+ break;
+ conf->sc_opts |= $2;
+ }
+ | PREFORK NUMBER {
+ if (loadcfg)
+ break;
+ if ($2 <= 0 || $2 > SERVER_MAXPROC) {
+ yyerror("invalid number of preforked "
+ "servers: %d", $2);
+ YYERROR;
+ }
+ conf->sc_prefork_server = $2;
+ }
+ ;
+
+server : SERVER STRING {
+ free($2);
+ }
+ ;
+
+loglevel : UPDATES { $$ = HTTPD_OPT_LOGUPDATE; }
+ | ALL { $$ = HTTPD_OPT_LOGALL; }
+ ;
+
+comma : ','
+ | nl
+ | /* empty */
+ ;
+
+optnl : '\n' optnl
+ |
+ ;
+
+nl : '\n' optnl
+ ;
+
+%%
+
+struct keywords {
+ const char *k_name;
+ int k_val;
+};
+
+int
+yyerror(const char *fmt, ...)
+{
+ va_list ap;
+ char *nfmt;
+
+ file->errors++;
+ va_start(ap, fmt);
+ if (asprintf(&nfmt, "%s:%d: %s", file->name, yylval.lineno, fmt) == -1)
+ fatalx("yyerror asprintf");
+ vlog(LOG_CRIT, nfmt, ap);
+ va_end(ap);
+ free(nfmt);
+ return (0);
+}
+
+int
+kw_cmp(const void *k, const void *e)
+{
+ return (strcmp(k, ((const struct keywords *)e)->k_name));
+}
+
+int
+lookup(char *s)
+{
+ /* this has to be sorted always */
+ static const struct keywords keywords[] = {
+ { "all", ALL },
+ { "include", INCLUDE },
+ { "listen", LISTEN },
+ { "log", LOG },
+ { "port", PORT },
+ { "prefork", PREFORK },
+ { "server", SERVER },
+ { "updates", UPDATES }
+ };
+ const struct keywords *p;
+
+ p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
+ sizeof(keywords[0]), kw_cmp);
+
+ if (p)
+ return (p->k_val);
+ else
+ return (STRING);
+}
+
+#define MAXPUSHBACK 128
+
+u_char *parsebuf;
+int parseindex;
+u_char pushback_buffer[MAXPUSHBACK];
+int pushback_index = 0;
+
+int
+lgetc(int quotec)
+{
+ int c, next;
+
+ if (parsebuf) {
+ /* Read character from the parsebuffer instead of input. */
+ if (parseindex >= 0) {
+ c = parsebuf[parseindex++];
+ if (c != '\0')
+ return (c);
+ parsebuf = NULL;
+ } else
+ parseindex++;
+ }
+
+ if (pushback_index)
+ return (pushback_buffer[--pushback_index]);
+
+ if (quotec) {
+ if ((c = getc(file->stream)) == EOF) {
+ yyerror("reached end of file while parsing "
+ "quoted string");
+ if (file == topfile || popfile() == EOF)
+ return (EOF);
+ return (quotec);
+ }
+ return (c);
+ }
+
+ while ((c = getc(file->stream)) == '\\') {
+ next = getc(file->stream);
+ if (next != '\n') {
+ c = next;
+ break;
+ }
+ yylval.lineno = file->lineno;
+ file->lineno++;
+ }
+
+ while (c == EOF) {
+ if (file == topfile || popfile() == EOF)
+ return (EOF);
+ c = getc(file->stream);
+ }
+ return (c);
+}
+
+int
+lungetc(int c)
+{
+ if (c == EOF)
+ return (EOF);
+ if (parsebuf) {
+ parseindex--;
+ if (parseindex >= 0)
+ return (c);
+ }
+ if (pushback_index < MAXPUSHBACK-1)
+ return (pushback_buffer[pushback_index++] = c);
+ else
+ return (EOF);
+}
+
+int
+findeol(void)
+{
+ int c;
+
+ parsebuf = NULL;
+
+ /* skip to either EOF or the first real EOL */
+ while (1) {
+ if (pushback_index)
+ c = pushback_buffer[--pushback_index];
+ else
+ c = lgetc(0);
+ if (c == '\n') {
+ file->lineno++;
+ break;
+ }
+ if (c == EOF)
+ break;
+ }
+ return (ERROR);
+}
+
+int
+yylex(void)
+{
+ u_char buf[8096];
+ u_char *p, *val;
+ int quotec, next, c;
+ int token;
+
+top:
+ p = buf;
+ while ((c = lgetc(0)) == ' ' || c == '\t')
+ ; /* nothing */
+
+ yylval.lineno = file->lineno;
+ if (c == '#')
+ while ((c = lgetc(0)) != '\n' && c != EOF)
+ ; /* nothing */
+ if (c == '$' && parsebuf == NULL) {
+ while (1) {
+ if ((c = lgetc(0)) == EOF)
+ return (0);
+
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ if (isalnum(c) || c == '_') {
+ *p++ = c;
+ continue;
+ }
+ *p = '\0';
+ lungetc(c);
+ break;
+ }
+ val = symget(buf);
+ if (val == NULL) {
+ yyerror("macro '%s' not defined", buf);
+ return (findeol());
+ }
+ parsebuf = val;
+ parseindex = 0;
+ goto top;
+ }
+
+ switch (c) {
+ case '\'':
+ case '"':
+ quotec = c;
+ while (1) {
+ if ((c = lgetc(quotec)) == EOF)
+ return (0);
+ if (c == '\n') {
+ file->lineno++;
+ continue;
+ } else if (c == '\\') {
+ if ((next = lgetc(quotec)) == EOF)
+ return (0);
+ if (next == quotec || c == ' ' || c == '\t')
+ c = next;
+ else if (next == '\n') {
+ file->lineno++;
+ continue;
+ } else
+ lungetc(next);
+ } else if (c == quotec) {
+ *p = '\0';
+ break;
+ }
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ *p++ = c;
+ }
+ yylval.v.string = strdup(buf);
+ if (yylval.v.string == NULL)
+ err(1, "yylex: strdup");
+ return (STRING);
+ }
+
+#define allowed_to_end_number(x) \
+ (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
+
+ if (c == '-' || isdigit(c)) {
+ do {
+ *p++ = c;
+ if ((unsigned)(p-buf) >= sizeof(buf)) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ } while ((c = lgetc(0)) != EOF && isdigit(c));
+ lungetc(c);
+ if (p == buf + 1 && buf[0] == '-')
+ goto nodigits;
+ if (c == EOF || allowed_to_end_number(c)) {
+ const char *errstr = NULL;
+
+ *p = '\0';
+ yylval.v.number = strtonum(buf, LLONG_MIN,
+ LLONG_MAX, &errstr);
+ if (errstr) {
+ yyerror("\"%s\" invalid number: %s",
+ buf, errstr);
+ return (findeol());
+ }
+ return (NUMBER);
+ } else {
+nodigits:
+ while (p > buf + 1)
+ lungetc(*--p);
+ c = *--p;
+ if (c == '-')
+ return (c);
+ }
+ }
+
+#define allowed_in_string(x) \
+ (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
+ x != '{' && x != '}' && x != '<' && x != '>' && \
+ x != '!' && x != '=' && x != '#' && \
+ x != ',' && x != '/'))
+
+ if (isalnum(c) || c == ':' || c == '_') {
+ do {
+ *p++ = c;
+ if ((unsigned)(p-buf) >= sizeof(buf)) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ } while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
+ lungetc(c);
+ *p = '\0';
+ if ((token = lookup(buf)) == STRING)
+ if ((yylval.v.string = strdup(buf)) == NULL)
+ err(1, "yylex: strdup");
+ return (token);
+ }
+ if (c == '\n') {
+ yylval.lineno = file->lineno;
+ file->lineno++;
+ }
+ if (c == EOF)
+ return (0);
+ return (c);
+}
+
+int
+check_file_secrecy(int fd, const char *fname)
+{
+ struct stat st;
+
+ if (fstat(fd, &st)) {
+ log_warn("cannot stat %s", fname);
+ return (-1);
+ }
+ if (st.st_uid != 0 && st.st_uid != getuid()) {
+ log_warnx("%s: owner not root or current user", fname);
+ return (-1);
+ }
+ if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
+ log_warnx("%s: group writable or world read/writable", fname);
+ return (-1);
+ }
+ return (0);
+}
+
+struct file *
+pushfile(const char *name, int secret)
+{
+ struct file *nfile;
+
+ if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
+ log_warn("%s: malloc", __func__);
+ return (NULL);
+ }
+ if ((nfile->name = strdup(name)) == NULL) {
+ log_warn("%s: malloc", __func__);
+ free(nfile);
+ return (NULL);
+ }
+ if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
+ log_warn("%s: %s", __func__, nfile->name);
+ free(nfile->name);
+ free(nfile);
+ return (NULL);
+ } else if (secret &&
+ check_file_secrecy(fileno(nfile->stream), nfile->name)) {
+ fclose(nfile->stream);
+ free(nfile->name);
+ free(nfile);
+ return (NULL);
+ }
+ nfile->lineno = 1;
+ TAILQ_INSERT_TAIL(&files, nfile, entry);
+ return (nfile);
+}
+
+int
+popfile(void)
+{
+ struct file *prev;
+
+ if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
+ prev->errors += file->errors;
+
+ TAILQ_REMOVE(&files, file, entry);
+ fclose(file->stream);
+ free(file->name);
+ free(file);
+ file = prev;
+ return (file ? 0 : EOF);
+}
+
+int
+parse_config(const char *filename, struct httpd *x_conf)
+{
+ struct sym *sym, *next;
+
+ conf = x_conf;
+ if (config_init(conf) == -1) {
+ log_warn("%s: cannot initialize configuration", __func__);
+ return (-1);
+ }
+
+ errors = 0;
+
+ if ((file = pushfile(filename, 0)) == NULL)
+ return (-1);
+
+ topfile = file;
+ setservent(1);
+
+ yyparse();
+ errors = file->errors;
+ popfile();
+
+ endservent();
+ endprotoent();
+
+ /* Free macros */
+ for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) {
+ next = TAILQ_NEXT(sym, entry);
+ if (!sym->persist) {
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entry);
+ free(sym);
+ }
+ }
+
+ return (errors ? -1 : 0);
+}
+
+int
+load_config(const char *filename, struct httpd *x_conf)
+{
+ struct sym *sym, *next;
+
+ conf = x_conf;
+ conf->sc_flags = 0;
+
+ loadcfg = 1;
+ errors = 0;
+ last_server_id = 0;
+
+ srv = NULL;
+
+ if ((file = pushfile(filename, 0)) == NULL)
+ return (-1);
+
+ topfile = file;
+ setservent(1);
+
+ yyparse();
+ errors = file->errors;
+ popfile();
+
+ endservent();
+ endprotoent();
+
+ /* Free macros and check which have not been used. */
+ for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) {
+ next = TAILQ_NEXT(sym, entry);
+ if ((conf->sc_opts & HTTPD_OPT_VERBOSE) && !sym->used)
+ fprintf(stderr, "warning: macro '%s' not "
+ "used\n", sym->nam);
+ if (!sym->persist) {
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entry);
+ free(sym);
+ }
+ }
+
+ if (TAILQ_EMPTY(conf->sc_servers)) {
+ log_warnx("no actions, nothing to do");
+ errors++;
+ }
+
+ return (errors ? -1 : 0);
+}
+
+int
+symset(const char *nam, const char *val, int persist)
+{
+ struct sym *sym;
+
+ for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam);
+ sym = TAILQ_NEXT(sym, entry))
+ ; /* nothing */
+
+ if (sym != NULL) {
+ if (sym->persist == 1)
+ return (0);
+ else {
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entry);
+ free(sym);
+ }
+ }
+ if ((sym = calloc(1, sizeof(*sym))) == NULL)
+ return (-1);
+
+ sym->nam = strdup(nam);
+ if (sym->nam == NULL) {
+ free(sym);
+ return (-1);
+ }
+ sym->val = strdup(val);
+ if (sym->val == NULL) {
+ free(sym->nam);
+ free(sym);
+ return (-1);
+ }
+ sym->used = 0;
+ sym->persist = persist;
+ TAILQ_INSERT_TAIL(&symhead, sym, entry);
+ return (0);
+}
+
+int
+cmdline_symset(char *s)
+{
+ char *sym, *val;
+ int ret;
+ size_t len;
+
+ if ((val = strrchr(s, '=')) == NULL)
+ return (-1);
+
+ len = strlen(s) - strlen(val) + 1;
+ if ((sym = malloc(len)) == NULL)
+ errx(1, "cmdline_symset: malloc");
+
+ (void)strlcpy(sym, s, len);
+
+ ret = symset(sym, val + 1, 1);
+ free(sym);
+
+ return (ret);
+}
+
+char *
+symget(const char *nam)
+{
+ struct sym *sym;
+
+ TAILQ_FOREACH(sym, &symhead, entry)
+ if (strcmp(nam, sym->nam) == 0) {
+ sym->used = 1;
+ return (sym->val);
+ }
+ return (NULL);
+}
+
+struct address *
+host_v4(const char *s)
+{
+ struct in_addr ina;
+ struct sockaddr_in *sain;
+ struct address *h;
+
+ bzero(&ina, sizeof(ina));
+ if (inet_pton(AF_INET, s, &ina) != 1)
+ return (NULL);
+
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(NULL);
+ sain = (struct sockaddr_in *)&h->ss;
+ sain->sin_len = sizeof(struct sockaddr_in);
+ sain->sin_family = AF_INET;
+ sain->sin_addr.s_addr = ina.s_addr;
+
+ return (h);
+}
+
+struct address *
+host_v6(const char *s)
+{
+ struct addrinfo hints, *res;
+ struct sockaddr_in6 *sa_in6;
+ struct address *h = NULL;
+
+ bzero(&hints, sizeof(hints));
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_DGRAM; /* dummy */
+ hints.ai_flags = AI_NUMERICHOST;
+ if (getaddrinfo(s, "0", &hints, &res) == 0) {
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(NULL);
+ sa_in6 = (struct sockaddr_in6 *)&h->ss;
+ sa_in6->sin6_len = sizeof(struct sockaddr_in6);
+ sa_in6->sin6_family = AF_INET6;
+ memcpy(&sa_in6->sin6_addr,
+ &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr,
+ sizeof(sa_in6->sin6_addr));
+ sa_in6->sin6_scope_id =
+ ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id;
+
+ freeaddrinfo(res);
+ }
+
+ return (h);
+}
+
+int
+host_dns(const char *s, struct addresslist *al, int max,
+ struct portrange *port, const char *ifname, int ipproto)
+{
+ struct addrinfo hints, *res0, *res;
+ int error, cnt = 0;
+ struct sockaddr_in *sain;
+ struct sockaddr_in6 *sin6;
+ struct address *h;
+
+ if ((cnt = host_if(s, al, max, port, ifname, ipproto)) != 0)
+ return (cnt);
+
+ bzero(&hints, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM; /* DUMMY */
+ error = getaddrinfo(s, NULL, &hints, &res0);
+ if (error == EAI_AGAIN || error == EAI_NODATA || error == EAI_NONAME)
+ return (0);
+ if (error) {
+ log_warnx("%s: could not parse \"%s\": %s", __func__, s,
+ gai_strerror(error));
+ return (-1);
+ }
+
+ for (res = res0; res && cnt < max; res = res->ai_next) {
+ if (res->ai_family != AF_INET &&
+ res->ai_family != AF_INET6)
+ continue;
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal(NULL);
+
+ if (port != NULL)
+ bcopy(port, &h->port, sizeof(h->port));
+ if (ifname != NULL) {
+ if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >=
+ sizeof(h->ifname))
+ log_warnx("%s: interface name truncated",
+ __func__);
+ freeaddrinfo(res0);
+ free(h);
+ return (-1);
+ }
+ if (ipproto != -1)
+ h->ipproto = ipproto;
+ h->ss.ss_family = res->ai_family;
+
+ if (res->ai_family == AF_INET) {
+ sain = (struct sockaddr_in *)&h->ss;
+ sain->sin_len = sizeof(struct sockaddr_in);
+ sain->sin_addr.s_addr = ((struct sockaddr_in *)
+ res->ai_addr)->sin_addr.s_addr;
+ } else {
+ sin6 = (struct sockaddr_in6 *)&h->ss;
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ memcpy(&sin6->sin6_addr, &((struct sockaddr_in6 *)
+ res->ai_addr)->sin6_addr, sizeof(struct in6_addr));
+ }
+
+ TAILQ_INSERT_HEAD(al, h, entry);
+ cnt++;
+ }
+ if (cnt == max && res) {
+ log_warnx("%s: %s resolves to more than %d hosts", __func__,
+ s, max);
+ }
+ freeaddrinfo(res0);
+ return (cnt);
+}
+
+int
+host_if(const char *s, struct addresslist *al, int max,
+ struct portrange *port, const char *ifname, int ipproto)
+{
+ struct ifaddrs *ifap, *p;
+ struct sockaddr_in *sain;
+ struct sockaddr_in6 *sin6;
+ struct address *h;
+ int cnt = 0, af;
+
+ if (getifaddrs(&ifap) == -1)
+ fatal("getifaddrs");
+
+ /* First search for IPv4 addresses */
+ af = AF_INET;
+
+ nextaf:
+ for (p = ifap; p != NULL && cnt < max; p = p->ifa_next) {
+ if (p->ifa_addr->sa_family != af ||
+ (strcmp(s, p->ifa_name) != 0 &&
+ !is_if_in_group(p->ifa_name, s)))
+ continue;
+ if ((h = calloc(1, sizeof(*h))) == NULL)
+ fatal("calloc");
+
+ if (port != NULL)
+ bcopy(port, &h->port, sizeof(h->port));
+ if (ifname != NULL) {
+ if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >=
+ sizeof(h->ifname))
+ log_warnx("%s: interface name truncated",
+ __func__);
+ freeifaddrs(ifap);
+ return (-1);
+ }
+ if (ipproto != -1)
+ h->ipproto = ipproto;
+ h->ss.ss_family = af;
+
+ if (af == AF_INET) {
+ sain = (struct sockaddr_in *)&h->ss;
+ sain->sin_len = sizeof(struct sockaddr_in);
+ sain->sin_addr.s_addr = ((struct sockaddr_in *)
+ p->ifa_addr)->sin_addr.s_addr;
+ } else {
+ sin6 = (struct sockaddr_in6 *)&h->ss;
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+ memcpy(&sin6->sin6_addr, &((struct sockaddr_in6 *)
+ p->ifa_addr)->sin6_addr, sizeof(struct in6_addr));
+ sin6->sin6_scope_id = ((struct sockaddr_in6 *)
+ p->ifa_addr)->sin6_scope_id;
+ }
+
+ TAILQ_INSERT_HEAD(al, h, entry);
+ cnt++;
+ }
+ if (af == AF_INET) {
+ /* Next search for IPv6 addresses */
+ af = AF_INET6;
+ goto nextaf;
+ }
+
+ if (cnt > max) {
+ log_warnx("%s: %s resolves to more than %d hosts", __func__,
+ s, max);
+ }
+ freeifaddrs(ifap);
+ return (cnt);
+}
+
+int
+host(const char *s, struct addresslist *al, int max,
+ struct portrange *port, const char *ifname, int ipproto)
+{
+ struct address *h;
+
+ h = host_v4(s);
+
+ /* IPv6 address? */
+ if (h == NULL)
+ h = host_v6(s);
+
+ if (h != NULL) {
+ if (port != NULL)
+ bcopy(port, &h->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));
+}
+
+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/proc.c b/proc.c
new file mode 100644
index 0000000..4be0b4f
--- /dev/null
+++ b/proc.c
@@ -0,0 +1,627 @@
+/* $OpenBSD: proc.c,v 1.15 2014/07/11 16:39:10 krw Exp $ */
+
+/*
+ * Copyright (c) 2010 - 2014 Reyk Floeter <reyk@openbsd.org>
+ * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/tree.h>
+
+#include <net/if.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <pwd.h>
+#include <event.h>
+
+#include <openssl/ssl.h>
+
+#include "httpd.h"
+
+void proc_open(struct privsep *, struct privsep_proc *,
+ struct privsep_proc *, size_t);
+void proc_close(struct privsep *);
+int proc_ispeer(struct privsep_proc *, u_int, enum privsep_procid);
+void proc_shutdown(struct privsep_proc *);
+void proc_sig_handler(int, short, void *);
+void proc_range(struct privsep *, enum privsep_procid, int *, int *);
+
+int
+proc_ispeer(struct privsep_proc *procs, u_int nproc, enum privsep_procid type)
+{
+ u_int i;
+
+ for (i = 0; i < nproc; i++)
+ if (procs[i].p_id == type)
+ return (1);
+ return (0);
+}
+
+void
+proc_init(struct privsep *ps, struct privsep_proc *procs, u_int nproc)
+{
+ u_int i, j, src, dst;
+ struct privsep_pipes *pp;
+
+ /*
+ * Allocate pipes for all process instances (incl. parent)
+ *
+ * - ps->ps_pipes: N:M mapping
+ * N source processes connected to M destination processes:
+ * [src][instances][dst][instances], for example
+ * [PROC_RELAY][3][PROC_CA][3]
+ *
+ * - ps->ps_pp: per-process 1:M part of ps->ps_pipes
+ * Each process instance has a destination array of socketpair fds:
+ * [dst][instances], for example
+ * [PROC_PARENT][0]
+ */
+ for (src = 0; src < PROC_MAX; src++) {
+ /* Allocate destination array for each process */
+ if ((ps->ps_pipes[src] = calloc(ps->ps_ninstances,
+ sizeof(struct privsep_pipes))) == NULL)
+ fatal("proc_init: calloc");
+
+ for (i = 0; i < ps->ps_ninstances; i++) {
+ pp = &ps->ps_pipes[src][i];
+
+ for (dst = 0; dst < PROC_MAX; dst++) {
+ /* Allocate maximum fd integers */
+ if ((pp->pp_pipes[dst] =
+ calloc(ps->ps_ninstances,
+ sizeof(int))) == NULL)
+ fatal("proc_init: calloc");
+
+ /* Mark fd as unused */
+ for (j = 0; j < ps->ps_ninstances; j++)
+ pp->pp_pipes[dst][j] = -1;
+ }
+ }
+ }
+
+ /*
+ * Setup and run the parent and its children
+ */
+ privsep_process = PROC_PARENT;
+ ps->ps_instances[PROC_PARENT] = 1;
+ ps->ps_title[PROC_PARENT] = "parent";
+ ps->ps_pid[PROC_PARENT] = getpid();
+ ps->ps_pp = &ps->ps_pipes[privsep_process][0];
+
+ for (i = 0; i < nproc; i++) {
+ /* Default to 1 process instance */
+ if (ps->ps_instances[procs[i].p_id] < 1)
+ ps->ps_instances[procs[i].p_id] = 1;
+ ps->ps_title[procs[i].p_id] = procs[i].p_title;
+ }
+
+ proc_open(ps, NULL, procs, nproc);
+
+ /* Engage! */
+ for (i = 0; i < nproc; i++)
+ ps->ps_pid[procs[i].p_id] = (*procs[i].p_init)(ps, &procs[i]);
+}
+
+void
+proc_kill(struct privsep *ps)
+{
+ pid_t pid;
+ u_int i;
+
+ if (privsep_process != PROC_PARENT)
+ return;
+
+ for (i = 0; i < PROC_MAX; i++) {
+ if (ps->ps_pid[i] == 0)
+ continue;
+ killpg(ps->ps_pid[i], SIGTERM);
+ }
+
+ do {
+ pid = waitpid(WAIT_ANY, NULL, 0);
+ } while (pid != -1 || (pid == -1 && errno == EINTR));
+
+ proc_close(ps);
+}
+
+void
+proc_open(struct privsep *ps, struct privsep_proc *p,
+ struct privsep_proc *procs, size_t nproc)
+{
+ struct privsep_pipes *pa, *pb;
+ int fds[2];
+ u_int i, j, src, proc;
+
+ if (p == NULL)
+ src = privsep_process; /* parent */
+ else
+ src = p->p_id;
+
+ /*
+ * Open socket pairs for our peers
+ */
+ for (proc = 0; proc < nproc; proc++) {
+ procs[proc].p_ps = ps;
+ procs[proc].p_env = ps->ps_env;
+
+ for (i = 0; i < ps->ps_instances[src]; i++) {
+ for (j = 0; j < ps->ps_instances[procs[proc].p_id];
+ j++) {
+ pa = &ps->ps_pipes[src][i];
+ pb = &ps->ps_pipes[procs[proc].p_id][j];
+
+ /* Check if fds are already set by peer */
+ if (pa->pp_pipes[procs[proc].p_id][j] != -1)
+ continue;
+
+ if (socketpair(AF_UNIX, SOCK_STREAM,
+ PF_UNSPEC, fds) == -1)
+ fatal("socketpair");
+
+ socket_set_blockmode(fds[0], BM_NONBLOCK);
+ socket_set_blockmode(fds[1], BM_NONBLOCK);
+
+ pa->pp_pipes[procs[proc].p_id][j] = fds[0];
+ pb->pp_pipes[src][i] = fds[1];
+ }
+ }
+ }
+}
+
+void
+proc_listen(struct privsep *ps, struct privsep_proc *procs, size_t nproc)
+{
+ u_int i, dst, src, n, m;
+ struct privsep_pipes *pp;
+
+ /*
+ * Close unused pipes
+ */
+ for (src = 0; src < PROC_MAX; src++) {
+ for (n = 0; n < ps->ps_instances[src]; n++) {
+ /* Ingore current process */
+ if (src == (u_int)privsep_process &&
+ n == ps->ps_instance)
+ continue;
+
+ pp = &ps->ps_pipes[src][n];
+
+ for (dst = 0; dst < PROC_MAX; dst++) {
+ if (src == dst)
+ continue;
+ for (m = 0; m < ps->ps_instances[dst]; m++) {
+ if (pp->pp_pipes[dst][m] == -1)
+ continue;
+
+ /* Close and invalidate fd */
+ close(pp->pp_pipes[dst][m]);
+ pp->pp_pipes[dst][m] = -1;
+ }
+ }
+ }
+ }
+
+ src = privsep_process;
+ ps->ps_pp = pp = &ps->ps_pipes[src][ps->ps_instance];
+
+ /*
+ * Listen on appropriate pipes
+ */
+ for (i = 0; i < nproc; i++) {
+ dst = procs[i].p_id;
+
+ if (src == dst)
+ fatal("proc_listen: cannot peer with oneself");
+
+ if ((ps->ps_ievs[dst] = calloc(ps->ps_instances[dst],
+ sizeof(struct imsgev))) == NULL)
+ fatal("proc_open");
+
+ for (n = 0; n < ps->ps_instances[dst]; n++) {
+ if (pp->pp_pipes[dst][n] == -1)
+ continue;
+
+ imsg_init(&(ps->ps_ievs[dst][n].ibuf),
+ pp->pp_pipes[dst][n]);
+ ps->ps_ievs[dst][n].handler = proc_dispatch;
+ ps->ps_ievs[dst][n].events = EV_READ;
+ ps->ps_ievs[dst][n].proc = &procs[i];
+ ps->ps_ievs[dst][n].data = &ps->ps_ievs[dst][n];
+ procs[i].p_instance = n;
+
+ event_set(&(ps->ps_ievs[dst][n].ev),
+ ps->ps_ievs[dst][n].ibuf.fd,
+ ps->ps_ievs[dst][n].events,
+ ps->ps_ievs[dst][n].handler,
+ ps->ps_ievs[dst][n].data);
+ event_add(&(ps->ps_ievs[dst][n].ev), NULL);
+ }
+ }
+}
+
+void
+proc_close(struct privsep *ps)
+{
+ u_int dst, n;
+ struct privsep_pipes *pp;
+
+ if (ps == NULL)
+ return;
+
+ pp = ps->ps_pp;
+
+ for (dst = 0; dst < PROC_MAX; dst++) {
+ if (ps->ps_ievs[dst] == NULL)
+ continue;
+
+ for (n = 0; n < ps->ps_instances[dst]; n++) {
+ if (pp->pp_pipes[dst][n] == -1)
+ continue;
+
+ /* Cancel the fd, close and invalidate the fd */
+ event_del(&(ps->ps_ievs[dst][n].ev));
+ imsg_clear(&(ps->ps_ievs[dst][n].ibuf));
+ close(pp->pp_pipes[dst][n]);
+ pp->pp_pipes[dst][n] = -1;
+ }
+ free(ps->ps_ievs[dst]);
+ }
+}
+
+void
+proc_shutdown(struct privsep_proc *p)
+{
+ struct privsep *ps = p->p_ps;
+
+ if (p->p_id == PROC_CONTROL && ps)
+ control_cleanup(&ps->ps_csock);
+
+ if (p->p_shutdown != NULL)
+ (*p->p_shutdown)();
+
+ proc_close(ps);
+
+ log_info("%s exiting, pid %d", p->p_title, getpid());
+
+ _exit(0);
+}
+
+void
+proc_sig_handler(int sig, short event, void *arg)
+{
+ struct privsep_proc *p = arg;
+
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ proc_shutdown(p);
+ break;
+ case SIGCHLD:
+ case SIGHUP:
+ case SIGPIPE:
+ /* ignore */
+ break;
+ default:
+ fatalx("proc_sig_handler: unexpected signal");
+ /* NOTREACHED */
+ }
+}
+
+pid_t
+proc_run(struct privsep *ps, struct privsep_proc *p,
+ struct privsep_proc *procs, u_int nproc,
+ void (*init)(struct privsep *, struct privsep_proc *, void *), void *arg)
+{
+ pid_t pid;
+ struct passwd *pw;
+ const char *root;
+ struct control_sock *rcs;
+ u_int n;
+
+ if (ps->ps_noaction)
+ return (0);
+
+ proc_open(ps, p, procs, nproc);
+
+ /* Fork child handlers */
+ switch (pid = fork()) {
+ case -1:
+ fatal("proc_run: cannot fork");
+ case 0:
+ /* Set the process group of the current process */
+ setpgrp(0, getpid());
+ break;
+ default:
+ return (pid);
+ }
+
+ pw = ps->ps_pw;
+
+ if (p->p_id == PROC_CONTROL && ps->ps_instance == 0) {
+ if (control_init(ps, &ps->ps_csock) == -1)
+ fatalx(p->p_title);
+ TAILQ_FOREACH(rcs, &ps->ps_rcsocks, cs_entry)
+ if (control_init(ps, rcs) == -1)
+ fatalx(p->p_title);
+ }
+
+ /* Change root directory */
+ if (p->p_chroot != NULL)
+ root = p->p_chroot;
+ else
+ root = pw->pw_dir;
+
+ if (chroot(root) == -1)
+ fatal("proc_run: chroot");
+ if (chdir("/") == -1)
+ fatal("proc_run: chdir(\"/\")");
+
+ privsep_process = p->p_id;
+
+ setproctitle("%s", p->p_title);
+
+ if (setgroups(1, &pw->pw_gid) ||
+ setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
+ setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
+ fatal("proc_run: cannot drop privileges");
+
+ /* Fork child handlers */
+ for (n = 1; n < ps->ps_instances[p->p_id]; n++) {
+ if (fork() == 0) {
+ ps->ps_instance = p->p_instance = n;
+ break;
+ }
+ }
+
+#ifdef DEBUG
+ log_debug("%s: %s %d/%d, pid %d", __func__, p->p_title,
+ ps->ps_instance + 1, ps->ps_instances[p->p_id], getpid());
+#endif
+
+ event_init();
+
+ signal_set(&ps->ps_evsigint, SIGINT, proc_sig_handler, p);
+ signal_set(&ps->ps_evsigterm, SIGTERM, proc_sig_handler, p);
+ signal_set(&ps->ps_evsigchld, SIGCHLD, proc_sig_handler, p);
+ signal_set(&ps->ps_evsighup, SIGHUP, proc_sig_handler, p);
+ signal_set(&ps->ps_evsigpipe, SIGPIPE, proc_sig_handler, p);
+
+ signal_add(&ps->ps_evsigint, NULL);
+ signal_add(&ps->ps_evsigterm, NULL);
+ signal_add(&ps->ps_evsigchld, NULL);
+ signal_add(&ps->ps_evsighup, NULL);
+ signal_add(&ps->ps_evsigpipe, NULL);
+
+ proc_listen(ps, procs, nproc);
+
+ if (p->p_id == PROC_CONTROL && ps->ps_instance == 0) {
+ TAILQ_INIT(&ctl_conns);
+ if (control_listen(&ps->ps_csock) == -1)
+ fatalx(p->p_title);
+ TAILQ_FOREACH(rcs, &ps->ps_rcsocks, cs_entry)
+ if (control_listen(rcs) == -1)
+ fatalx(p->p_title);
+ }
+
+ if (init != NULL)
+ init(ps, p, arg);
+
+ event_dispatch();
+
+ proc_shutdown(p);
+
+ return (0);
+}
+
+void
+proc_dispatch(int fd, short event, void *arg)
+{
+ struct imsgev *iev = arg;
+ struct privsep_proc *p = iev->proc;
+ struct privsep *ps = p->p_ps;
+ struct imsgbuf *ibuf;
+ struct imsg imsg;
+ ssize_t n;
+ int verbose;
+ const char *title;
+
+ title = ps->ps_title[privsep_process];
+ ibuf = &iev->ibuf;
+
+ if (event & EV_READ) {
+ if ((n = imsg_read(ibuf)) == -1)
+ fatal(title);
+ if (n == 0) {
+ /* this pipe is dead, so remove the event handler */
+ event_del(&iev->ev);
+ event_loopexit(NULL);
+ return;
+ }
+ }
+
+ if (event & EV_WRITE) {
+ if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN)
+ fatal(title);
+ }
+
+ for (;;) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal(title);
+ if (n == 0)
+ break;
+
+#if DEBUG > 1
+ log_debug("%s: %s %d got imsg %d from %s %d",
+ __func__, title, ps->ps_instance + 1,
+ imsg.hdr.type, p->p_title, p->p_instance);
+#endif
+
+ /*
+ * Check the message with the program callback
+ */
+ if ((p->p_cb)(fd, p, &imsg) == 0) {
+ /* Message was handled by the callback, continue */
+ imsg_free(&imsg);
+ continue;
+ }
+
+ /*
+ * Generic message handling
+ */
+ switch (imsg.hdr.type) {
+ case IMSG_CTL_VERBOSE:
+ IMSG_SIZE_CHECK(&imsg, &verbose);
+ memcpy(&verbose, imsg.data, sizeof(verbose));
+ log_verbose(verbose);
+ break;
+ default:
+ log_warnx("%s: %s %d got invalid imsg %d from %s %d",
+ __func__, title, ps->ps_instance + 1,
+ imsg.hdr.type, p->p_title, p->p_instance);
+ fatalx(title);
+ }
+ imsg_free(&imsg);
+ }
+ imsg_event_add(iev);
+}
+
+/*
+ * imsg helper functions
+ */
+
+void
+imsg_event_add(struct imsgev *iev)
+{
+ if (iev->handler == NULL) {
+ imsg_flush(&iev->ibuf);
+ return;
+ }
+
+ iev->events = EV_READ;
+ if (iev->ibuf.w.queued)
+ iev->events |= EV_WRITE;
+
+ event_del(&iev->ev);
+ event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev->data);
+ event_add(&iev->ev, NULL);
+}
+
+int
+imsg_compose_event(struct imsgev *iev, u_int16_t type, u_int32_t peerid,
+ pid_t pid, int fd, void *data, u_int16_t datalen)
+{
+ int ret;
+
+ if ((ret = imsg_compose(&iev->ibuf, type, peerid,
+ pid, fd, data, datalen)) == -1)
+ return (ret);
+ imsg_event_add(iev);
+ return (ret);
+}
+
+int
+imsg_composev_event(struct imsgev *iev, u_int16_t type, u_int32_t peerid,
+ pid_t pid, int fd, const struct iovec *iov, int iovcnt)
+{
+ int ret;
+
+ if ((ret = imsg_composev(&iev->ibuf, type, peerid,
+ pid, fd, iov, iovcnt)) == -1)
+ return (ret);
+ imsg_event_add(iev);
+ return (ret);
+}
+
+void
+proc_range(struct privsep *ps, enum privsep_procid id, int *n, int *m)
+{
+ if (*n == -1) {
+ /* Use a range of all target instances */
+ *n = 0;
+ *m = ps->ps_instances[id];
+ } else {
+ /* Use only a single slot of the specified peer process */
+ *m = *n + 1;
+ }
+}
+
+int
+proc_compose_imsg(struct privsep *ps, enum privsep_procid id, int n,
+ u_int16_t type, int fd, void *data, u_int16_t datalen)
+{
+ int m;
+
+ proc_range(ps, id, &n, &m);
+ for (; n < m; n++) {
+ if (imsg_compose_event(&ps->ps_ievs[id][n],
+ type, -1, 0, fd, data, datalen) == -1)
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+proc_composev_imsg(struct privsep *ps, enum privsep_procid id, int n,
+ u_int16_t type, int fd, const struct iovec *iov, int iovcnt)
+{
+ int m;
+
+ proc_range(ps, id, &n, &m);
+ for (; n < m; n++)
+ if (imsg_composev_event(&ps->ps_ievs[id][n],
+ type, -1, 0, fd, iov, iovcnt) == -1)
+ return (-1);
+
+ return (0);
+}
+
+int
+proc_forward_imsg(struct privsep *ps, struct imsg *imsg,
+ enum privsep_procid id, int n)
+{
+ return (proc_compose_imsg(ps, id, n, imsg->hdr.type,
+ imsg->fd, imsg->data, IMSG_DATA_SIZE(imsg)));
+}
+
+struct imsgbuf *
+proc_ibuf(struct privsep *ps, enum privsep_procid id, int n)
+{
+ int m;
+
+ proc_range(ps, id, &n, &m);
+ return (&ps->ps_ievs[id][n].ibuf);
+}
+
+struct imsgev *
+proc_iev(struct privsep *ps, enum privsep_procid id, int n)
+{
+ int m;
+
+ proc_range(ps, id, &n, &m);
+ return (&ps->ps_ievs[id][n]);
+}
diff --git a/server.c b/server.c
new file mode 100644
index 0000000..65912d8
--- /dev/null
+++ b/server.c
@@ -0,0 +1,636 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2006 - 2014 Reyk Floeter <reyk@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/tree.h>
+#include <sys/hash.h>
+
+#include <net/if.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <err.h>
+#include <pwd.h>
+#include <event.h>
+#include <fnmatch.h>
+
+#include <openssl/dh.h>
+#include <openssl/ssl.h>
+
+#include "httpd.h"
+
+int server_dispatch_parent(int, struct privsep_proc *,
+ struct imsg *);
+void server_shutdown(void);
+
+void server_init(struct privsep *, struct privsep_proc *p, void *);
+void server_launch(void);
+int server_socket(struct sockaddr_storage *, in_port_t,
+ struct server *, int, int);
+int server_socket_listen(struct sockaddr_storage *, in_port_t,
+ struct server *);
+
+void server_accept(int, short, void *);
+void server_input(struct client *);
+
+extern void bufferevent_read_pressure_cb(struct evbuffer *, size_t,
+ size_t, void *);
+
+volatile int server_clients;
+volatile int server_inflight = 0;
+u_int32_t server_cltid;
+
+static struct httpd *env = NULL;
+int proc_id;
+
+static struct privsep_proc procs[] = {
+ { "parent", PROC_PARENT, server_dispatch_parent }
+};
+
+pid_t
+server(struct privsep *ps, struct privsep_proc *p)
+{
+ pid_t pid;
+ env = ps->ps_env;
+ pid = proc_run(ps, p, procs, nitems(procs), server_init, NULL);
+// server_http(env);
+ return (pid);
+}
+
+void
+server_shutdown(void)
+{
+ config_purge(env, CONFIG_ALL);
+ usleep(200); /* XXX server needs to shutdown last */
+}
+
+int
+server_privinit(struct server *srv)
+{
+ log_debug("%s: adding server %s", __func__, srv->srv_conf.name);
+
+ if ((srv->srv_s = server_socket_listen(&srv->srv_conf.ss,
+ srv->srv_conf.port, srv)) == -1)
+ return (-1);
+
+ return (0);
+}
+
+void
+server_init(struct privsep *ps, struct privsep_proc *p, void *arg)
+{
+ 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);
+ bcopy(&env->sc_statinterval, &tv, sizeof(tv));
+ evtimer_add(&env->sc_statev, &tv);
+#endif
+}
+
+void
+server_launch(void)
+{
+ struct server *srv;
+
+ TAILQ_FOREACH(srv, env->sc_servers, srv_entry) {
+// server_http_init(srv);
+
+ log_debug("%s: running server %s", __func__,
+ srv->srv_conf.name);
+
+ event_set(&srv->srv_ev, srv->srv_s, EV_READ,
+ server_accept, srv);
+ event_add(&srv->srv_ev, NULL);
+ evtimer_set(&srv->srv_evt, server_accept, srv);
+ }
+}
+
+int
+server_socket_af(struct sockaddr_storage *ss, in_port_t port)
+{
+ switch (ss->ss_family) {
+ case AF_INET:
+ ((struct sockaddr_in *)ss)->sin_port = port;
+ ((struct sockaddr_in *)ss)->sin_len =
+ sizeof(struct sockaddr_in);
+ break;
+ case AF_INET6:
+ ((struct sockaddr_in6 *)ss)->sin6_port = port;
+ ((struct sockaddr_in6 *)ss)->sin6_len =
+ sizeof(struct sockaddr_in6);
+ break;
+ default:
+ return (-1);
+ }
+
+ return (0);
+}
+
+in_port_t
+server_socket_getport(struct sockaddr_storage *ss)
+{
+ switch (ss->ss_family) {
+ case AF_INET:
+ return (((struct sockaddr_in *)ss)->sin_port);
+ case AF_INET6:
+ return (((struct sockaddr_in6 *)ss)->sin6_port);
+ default:
+ return (0);
+ }
+
+ /* NOTREACHED */
+ return (0);
+}
+
+int
+server_socket(struct sockaddr_storage *ss, in_port_t port,
+ struct server *srv, int fd, int reuseport)
+{
+ struct linger lng;
+ int s = -1, val;
+
+ if (server_socket_af(ss, port) == -1)
+ goto bad;
+
+ s = fd == -1 ? socket(ss->ss_family, SOCK_STREAM, IPPROTO_TCP) : fd;
+ if (s == -1)
+ goto bad;
+
+ /*
+ * Socket options
+ */
+ bzero(&lng, sizeof(lng));
+ if (setsockopt(s, SOL_SOCKET, SO_LINGER, &lng, sizeof(lng)) == -1)
+ goto bad;
+ if (reuseport) {
+ val = 1;
+ if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &val,
+ sizeof(int)) == -1)
+ goto bad;
+ }
+ if (fcntl(s, F_SETFL, O_NONBLOCK) == -1)
+ goto bad;
+ if (srv->srv_tcpflags & TCPFLAG_BUFSIZ) {
+ val = srv->srv_tcpbufsiz;
+ if (setsockopt(s, SOL_SOCKET, SO_RCVBUF,
+ &val, sizeof(val)) == -1)
+ goto bad;
+ val = srv->srv_tcpbufsiz;
+ if (setsockopt(s, SOL_SOCKET, SO_SNDBUF,
+ &val, sizeof(val)) == -1)
+ goto bad;
+ }
+
+ /*
+ * IP options
+ */
+ if (srv->srv_tcpflags & TCPFLAG_IPTTL) {
+ val = (int)srv->srv_tcpipttl;
+ if (setsockopt(s, IPPROTO_IP, IP_TTL,
+ &val, sizeof(val)) == -1)
+ goto bad;
+ }
+ if (srv->srv_tcpflags & TCPFLAG_IPMINTTL) {
+ val = (int)srv->srv_tcpipminttl;
+ if (setsockopt(s, IPPROTO_IP, IP_MINTTL,
+ &val, sizeof(val)) == -1)
+ goto bad;
+ }
+
+ /*
+ * TCP options
+ */
+ if (srv->srv_tcpflags & (TCPFLAG_NODELAY|TCPFLAG_NNODELAY)) {
+ if (srv->srv_tcpflags & TCPFLAG_NNODELAY)
+ val = 0;
+ else
+ val = 1;
+ if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY,
+ &val, sizeof(val)) == -1)
+ goto bad;
+ }
+ if (srv->srv_tcpflags & (TCPFLAG_SACK|TCPFLAG_NSACK)) {
+ if (srv->srv_tcpflags & TCPFLAG_NSACK)
+ val = 0;
+ else
+ val = 1;
+ if (setsockopt(s, IPPROTO_TCP, TCP_SACK_ENABLE,
+ &val, sizeof(val)) == -1)
+ goto bad;
+ }
+
+ return (s);
+
+ bad:
+ if (s != -1)
+ close(s);
+ return (-1);
+}
+
+int
+server_socket_listen(struct sockaddr_storage *ss, in_port_t port,
+ struct server *srv)
+{
+ int s;
+
+ if ((s = server_socket(ss, port, srv, -1, 1)) == -1)
+ return (-1);
+
+ if (bind(s, (struct sockaddr *)ss, ss->ss_len) == -1)
+ goto bad;
+ if (listen(s, srv->srv_tcpbacklog) == -1)
+ goto bad;
+
+ return (s);
+
+ bad:
+ close(s);
+ return (-1);
+}
+
+void
+server_input(struct client *clt)
+{
+ struct server *srv = clt->clt_server;
+ evbuffercb inrd = server_read;
+ evbuffercb inwr = server_write;
+
+#if 0
+ if (server_httpdesc_init(&clt->clt_in) == -1) {
+ server_close(clt,
+ "failed to allocate http descriptor");
+ return;
+ }
+#endif
+ clt->clt_toread = TOREAD_HTTP_HEADER;
+// inrd = server_read_http;
+
+ /*
+ * Client <-> Server
+ */
+ clt->clt_bev = bufferevent_new(clt->clt_s, inrd, inwr,
+ server_error, clt);
+ if (clt->clt_bev == NULL) {
+ server_close(clt, "failed to allocate input buffer event");
+ return;
+ }
+
+ bufferevent_settimeout(clt->clt_bev,
+ srv->srv_conf.timeout.tv_sec, srv->srv_conf.timeout.tv_sec);
+ bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE);
+}
+
+void
+server_write(struct bufferevent *bev, void *arg)
+{
+ struct client *clt = arg;
+
+ getmonotime(&clt->clt_tv_last);
+
+ if (clt->clt_done)
+ goto done;
+ return;
+ done:
+ server_close(clt, "done");
+ return;
+}
+
+void
+server_dump(struct client *clt, const void *buf, size_t len)
+{
+ if (!len)
+ return;
+
+ /*
+ * This function will dump the specified message directly
+ * to the underlying client, without waiting for success
+ * of non-blocking events etc. This is useful to print an
+ * error message before gracefully closing the client.
+ */
+#if 0
+ if (cre->ssl != NULL)
+ (void)SSL_write(cre->ssl, buf, len);
+ else
+#endif
+ (void)write(clt->clt_s, buf, len);
+}
+
+void
+server_read(struct bufferevent *bev, void *arg)
+{
+ struct client *clt = arg;
+ struct evbuffer *src = EVBUFFER_INPUT(bev);
+
+ getmonotime(&clt->clt_tv_last);
+
+ if (!EVBUFFER_LENGTH(src))
+ return;
+ if (server_bufferevent_write_buffer(clt, src) == -1)
+ goto fail;
+ if (clt->clt_done)
+ goto done;
+ if (clt->clt_bev)
+ bufferevent_enable(clt->clt_bev, EV_READ);
+ return;
+ done:
+ server_close(clt, "done");
+ return;
+ fail:
+ server_close(clt, strerror(errno));
+}
+
+void
+server_error(struct bufferevent *bev, short error, void *arg)
+{
+ struct client *clt = arg;
+
+ if (error & EVBUFFER_TIMEOUT) {
+ server_close(clt, "buffer event timeout");
+ return;
+ }
+ if (error & EVBUFFER_ERROR && errno == EFBIG) {
+ bufferevent_enable(clt->clt_bev, EV_READ);
+ return;
+ }
+ if (error & (EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF)) {
+ bufferevent_disable(bev, EV_READ|EV_WRITE);
+
+ clt->clt_done = 1;
+
+ return;
+// server_close(con, "done");
+ }
+ server_close(clt, "buffer event error");
+ return;
+}
+
+void
+server_accept(int fd, short event, void *arg)
+{
+ struct server *srv = arg;
+ struct client *clt = NULL;
+ socklen_t slen;
+ struct sockaddr_storage ss;
+ int s = -1;
+
+ event_add(&srv->srv_ev, NULL);
+ if ((event & EV_TIMEOUT))
+ return;
+
+ slen = sizeof(ss);
+ if ((s = accept_reserve(fd, (struct sockaddr *)&ss,
+ &slen, FD_RESERVE, &server_inflight)) == -1) {
+ /*
+ * Pause accept if we are out of file descriptors, or
+ * libevent will haunt us here too.
+ */
+ if (errno == ENFILE || errno == EMFILE) {
+ struct timeval evtpause = { 1, 0 };
+
+ event_del(&srv->srv_ev);
+ evtimer_add(&srv->srv_evt, &evtpause);
+ log_debug("%s: deferring connections", __func__);
+ }
+ return;
+ }
+ if (server_clients >= SERVER_MAX_CLIENTS)
+ goto err;
+
+ if (fcntl(s, F_SETFL, O_NONBLOCK) == -1)
+ goto err;
+
+ if ((clt = calloc(1, sizeof(*clt))) == NULL)
+ goto err;
+
+ clt->clt_s = s;
+ clt->clt_toread = TOREAD_UNLIMITED;
+ clt->clt_server = srv;
+ clt->clt_id = ++server_cltid;
+ clt->clt_serverid = srv->srv_conf.id;
+ clt->clt_pid = getpid();
+ switch (ss.ss_family) {
+ case AF_INET:
+ clt->clt_port = ((struct sockaddr_in *)&ss)->sin_port;
+ break;
+ case AF_INET6:
+ clt->clt_port = ((struct sockaddr_in6 *)&ss)->sin6_port;
+ break;
+ }
+ memcpy(&clt->clt_ss, &ss, sizeof(clt->clt_ss));
+
+ getmonotime(&clt->clt_tv_start);
+ bcopy(&clt->clt_tv_start, &clt->clt_tv_last, sizeof(clt->clt_tv_last));
+
+ server_clients++;
+ SPLAY_INSERT(client_tree, &srv->srv_clients, clt);
+
+ /* Increment the per-relay client counter */
+ //srv->srv_stats[proc_id].last++;
+
+ /* Pre-allocate output buffer */
+ clt->clt_output = evbuffer_new();
+ if (clt->clt_output == NULL) {
+ server_close(clt, "failed to allocate output buffer");
+ return;
+ }
+
+ /* Pre-allocate log buffer */
+ clt->clt_log = evbuffer_new();
+ if (clt->clt_log == NULL) {
+ server_close(clt, "failed to allocate log buffer");
+ return;
+ }
+
+ server_input(clt);
+ return;
+
+ err:
+ if (s != -1) {
+ close(s);
+ if (clt != NULL)
+ free(clt);
+ /*
+ * the client struct was not completly set up, but still
+ * counted as an inflight client. account for this.
+ */
+ server_inflight--;
+ log_debug("%s: inflight decremented, now %d",
+ __func__, server_inflight);
+ }
+}
+
+void
+server_close(struct client *clt, const char *msg)
+{
+ char ibuf[128], obuf[128], *ptr = NULL;
+ struct server *srv = clt->clt_server;
+
+ SPLAY_REMOVE(client_tree, &srv->srv_clients, clt);
+
+ event_del(&clt->clt_ev);
+ if (clt->clt_bev != NULL)
+ bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE);
+
+ if ((env->sc_opts & HTTPD_OPT_LOGUPDATE) && msg != NULL) {
+ bzero(&ibuf, sizeof(ibuf));
+ bzero(&obuf, sizeof(obuf));
+ (void)print_host(&clt->clt_ss, ibuf, sizeof(ibuf));
+ (void)print_host(&srv->srv_ss, obuf, sizeof(obuf));
+ if (EVBUFFER_LENGTH(clt->clt_log) &&
+ evbuffer_add_printf(clt->clt_log, "\r\n") != -1)
+ ptr = evbuffer_readline(clt->clt_log);
+ log_info("server %s, "
+ "client %d (%d active), %s -> %s:%d, "
+ "%s%s%s", srv->srv_conf.name, clt->clt_id, server_clients,
+ ibuf, obuf, ntohs(clt->clt_port), msg,
+ ptr == NULL ? "" : ",", ptr == NULL ? "" : ptr);
+ if (ptr != NULL)
+ free(ptr);
+ }
+
+ if (clt->clt_bev != NULL)
+ bufferevent_free(clt->clt_bev);
+ else if (clt->clt_output != NULL)
+ evbuffer_free(clt->clt_output);
+ if (clt->clt_s != -1) {
+ close(clt->clt_s);
+ if (/* XXX */ -1) {
+ /*
+ * the output was never connected,
+ * thus this was an inflight client.
+ */
+ server_inflight--;
+ log_debug("%s: clients inflight decremented, now %d",
+ __func__, server_inflight);
+ }
+ }
+
+ if (clt->clt_log != NULL)
+ evbuffer_free(clt->clt_log);
+
+ free(clt);
+ server_clients--;
+}
+
+int
+server_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
+{
+ switch (imsg->hdr.type) {
+#if 0
+ case IMSG_CFG_SERVER:
+ config_getserver(env, imsg);
+ break;
+#endif
+ case IMSG_CFG_DONE:
+ config_getcfg(env, imsg);
+ break;
+ case IMSG_CTL_START:
+ server_launch();
+ break;
+ case IMSG_CTL_RESET:
+ config_getreset(env, imsg);
+ break;
+ default:
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+server_bufferevent_add(struct event *ev, int timeout)
+{
+ struct timeval tv, *ptv = NULL;
+
+ if (timeout) {
+ timerclear(&tv);
+ tv.tv_sec = timeout;
+ ptv = &tv;
+ }
+
+ return (event_add(ev, ptv));
+}
+
+int
+server_bufferevent_print(struct client *clt, const char *str)
+{
+ if (clt->clt_bev == NULL)
+ return (evbuffer_add(clt->clt_output, str, strlen(str)));
+ return (bufferevent_write(clt->clt_bev, str, strlen(str)));
+}
+
+int
+server_bufferevent_write_buffer(struct client *clt, struct evbuffer *buf)
+{
+ if (clt->clt_bev == NULL)
+ return (evbuffer_add_buffer(clt->clt_output, buf));
+ return (bufferevent_write_buffer(clt->clt_bev, buf));
+}
+
+int
+server_bufferevent_write_chunk(struct client *clt,
+ struct evbuffer *buf, size_t size)
+{
+ int ret;
+ ret = server_bufferevent_write(clt, buf->buffer, size);
+ if (ret != -1)
+ evbuffer_drain(buf, size);
+ return (ret);
+}
+
+int
+server_bufferevent_write(struct client *clt, void *data, size_t size)
+{
+ if (clt->clt_bev == NULL)
+ return (evbuffer_add(clt->clt_output, data, size));
+ return (bufferevent_write(clt->clt_bev, data, size));
+}
+
+int
+server_client_cmp(struct client *a, struct client *b)
+{
+ return ((int)a->clt_id - b->clt_id);
+}
+
+SPLAY_GENERATE(client_tree, client, clt_nodes, server_client_cmp);
diff --git a/server_http.c b/server_http.c
new file mode 100644
index 0000000..75e1521
--- /dev/null
+++ b/server_http.c
@@ -0,0 +1,1736 @@
+/* $OpenBSD: relay_http.c,v 1.25 2014/07/11 23:11:54 benno Exp $ */
+
+/*
+ * Copyright (c) 2006 - 2014 Reyk Floeter <reyk@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/tree.h>
+#include <sys/hash.h>
+
+#include <net/if.h>
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <err.h>
+#include <pwd.h>
+#include <event.h>
+#include <fnmatch.h>
+
+#include <openssl/ssl.h>
+
+#include "relayd.h"
+#include "http.h"
+
+static int _relay_lookup_url(struct ctl_relay_event *, char *, char *,
+ char *, struct kv *);
+int relay_lookup_url(struct ctl_relay_event *,
+ const char *, struct kv *);
+int relay_lookup_query(struct ctl_relay_event *, struct kv *);
+int relay_lookup_cookie(struct ctl_relay_event *, const char *,
+ struct kv *);
+void relay_read_httpcontent(struct bufferevent *, void *);
+void relay_read_httpchunks(struct bufferevent *, void *);
+char *relay_expand_http(struct ctl_relay_event *, char *,
+ char *, size_t);
+int relay_writeheader_kv(struct ctl_relay_event *, struct kv *);
+int relay_writeheader_http(struct ctl_relay_event *,
+ struct ctl_relay_event *);
+int relay_writerequest_http(struct ctl_relay_event *,
+ struct ctl_relay_event *);
+int relay_writeresponse_http(struct ctl_relay_event *,
+ struct ctl_relay_event *);
+void relay_reset_http(struct ctl_relay_event *);
+static int relay_httpmethod_cmp(const void *, const void *);
+static int relay_httperror_cmp(const void *, const void *);
+int relay_httpquery_test(struct ctl_relay_event *,
+ struct relay_rule *, struct kvlist *);
+int relay_httpheader_test(struct ctl_relay_event *,
+ struct relay_rule *, struct kvlist *);
+int relay_httppath_test(struct ctl_relay_event *,
+ struct relay_rule *, struct kvlist *);
+int relay_httpurl_test(struct ctl_relay_event *,
+ struct relay_rule *, struct kvlist *);
+int relay_httpcookie_test(struct ctl_relay_event *,
+ struct relay_rule *, struct kvlist *);
+int relay_apply_actions(struct ctl_relay_event *, struct kvlist *);
+int relay_match_actions(struct ctl_relay_event *,
+ struct relay_rule *, struct kvlist *, struct kvlist *);
+void relay_httpdesc_free(struct http_descriptor *);
+
+static struct relayd *env = NULL;
+
+static struct http_method http_methods[] = HTTP_METHODS;
+static struct http_error http_errors[] = HTTP_ERRORS;
+
+void
+relay_http(struct relayd *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]), relay_httpmethod_cmp);
+ qsort(http_errors, sizeof(http_errors) /
+ sizeof(http_errors[0]) - 1,
+ sizeof(http_errors[0]), relay_httperror_cmp);
+}
+
+void
+relay_http_init(struct relay *rlay)
+{
+ rlay->rl_proto->close = relay_close_http;
+
+ relay_http(NULL);
+
+ /* Calculate skip step for the filter rules (may take a while) */
+ relay_calc_skip_steps(&rlay->rl_proto->rules);
+}
+
+int
+relay_httpdesc_init(struct ctl_relay_event *cre)
+{
+ struct http_descriptor *desc;
+
+ if ((desc = calloc(1, sizeof(*desc))) == NULL)
+ return (-1);
+
+ RB_INIT(&desc->http_headers);
+ cre->desc = desc;
+
+ return (0);
+}
+
+void
+relay_httpdesc_free(struct http_descriptor *desc)
+{
+ if (desc->http_path != NULL) {
+ free(desc->http_path);
+ desc->http_path = NULL;
+ }
+ if (desc->http_query != NULL) {
+ free(desc->http_query);
+ desc->http_query = NULL;
+ }
+ if (desc->http_version != NULL) {
+ free(desc->http_version);
+ desc->http_version = NULL;
+ }
+ if (desc->query_key != NULL) {
+ free(desc->query_key);
+ desc->query_key = NULL;
+ }
+ if (desc->query_val != NULL) {
+ free(desc->query_val);
+ desc->query_val = NULL;
+ }
+ kv_purge(&desc->http_headers);
+}
+
+void
+relay_read_http(struct bufferevent *bev, void *arg)
+{
+ struct ctl_relay_event *cre = arg;
+ struct http_descriptor *desc = cre->desc;
+ struct rsession *con = cre->con;
+ struct relay *rlay = con->se_relay;
+ struct protocol *proto = rlay->rl_proto;
+ struct evbuffer *src = EVBUFFER_INPUT(bev);
+ char *line = NULL, *key, *value;
+ int action;
+ const char *errstr;
+ size_t size, linelen;
+ struct kv *hdr = NULL;
+
+ getmonotime(&con->se_tv_last);
+
+ size = EVBUFFER_LENGTH(src);
+ DPRINTF("%s: session %d: size %lu, to read %lld",
+ __func__, con->se_id, size, cre->toread);
+ if (!size) {
+ if (cre->dir == RELAY_DIR_RESPONSE)
+ return;
+ cre->toread = TOREAD_HTTP_HEADER;
+ goto done;
+ }
+
+ while (!cre->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) {
+ cre->done = 1;
+ free(line);
+ break;
+ }
+ key = line;
+
+ /* Limit the total header length minus \r\n */
+ cre->headerlen += linelen;
+ if (cre->headerlen > RELAY_MAXHEADERLENGTH) {
+ free(line);
+ relay_abort_http(con, 413, "request too large", 0);
+ return;
+ }
+
+ /*
+ * The first line is the GET/POST/PUT/... request,
+ * subsequent lines are HTTP headers.
+ */
+ if (++cre->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 (cre->line == 1) {
+ free(line);
+ relay_abort_http(con, 400, "malformed", 0);
+ return;
+ }
+
+ /* Append line to the last header, if present */
+ if (kv_extend(&desc->http_headers,
+ desc->http_lastheader, line) == NULL) {
+ free(line);
+ goto fail;
+ }
+
+ free(line);
+ continue;
+ }
+ if (*value == ':') {
+ *value++ = '\0';
+ value += strspn(value, " \t\r\n");
+ } else {
+ *value++ = '\0';
+ }
+
+ DPRINTF("%s: session %d: header '%s: %s'", __func__,
+ con->se_id, key, value);
+
+ /*
+ * Identify and handle specific HTTP request methods
+ */
+ if (cre->line == 1 && cre->dir == RELAY_DIR_RESPONSE) {
+ desc->http_method = HTTP_METHOD_RESPONSE;
+ /*
+ * Decode response path and query
+ */
+ desc->http_version = strdup(line);
+ if (desc->http_version == NULL) {
+ free(line);
+ goto fail;
+ }
+ desc->http_rescode = strdup(value);
+ if (desc->http_rescode == NULL) {
+ free(line);
+ goto fail;
+ }
+ desc->http_resmesg = strchr(desc->http_rescode, ' ');
+ if (desc->http_resmesg == NULL) {
+ free(line);
+ goto fail;
+ }
+ *desc->http_resmesg++ = '\0';
+ if ((desc->http_resmesg = strdup(desc->http_resmesg))
+ == NULL) {
+ free(line);
+ goto fail;
+ }
+ DPRINTF("http_version %s http_rescode %s "
+ "http_resmesg %s", desc->http_version,
+ desc->http_rescode, desc->http_resmesg);
+ goto lookup;
+ } else if (cre->line == 1 && cre->dir == RELAY_DIR_REQUEST) {
+ if ((desc->http_method = relay_httpmethod_byname(key))
+ == HTTP_METHOD_NONE)
+ goto fail;
+ /*
+ * Decode request path and query
+ */
+ desc->http_path = strdup(value);
+ if (desc->http_path == NULL) {
+ free(line);
+ goto fail;
+ }
+ desc->http_version = strchr(desc->http_path, ' ');
+ if (desc->http_version != NULL)
+ *desc->http_version++ = '\0';
+ desc->http_query = strchr(desc->http_path, '?');
+ if (desc->http_query != NULL)
+ *desc->http_query++ = '\0';
+
+ /*
+ * Have to allocate the strings because they could
+ * be changed independetly by the filters later.
+ */
+ if (desc->http_version != NULL &&
+ (desc->http_version =
+ strdup(desc->http_version)) == NULL) {
+ free(line);
+ goto fail;
+ }
+ if (desc->http_query != NULL &&
+ (desc->http_query =
+ strdup(desc->http_query)) == NULL) {
+ free(line);
+ goto fail;
+ }
+ } else if (desc->http_method != HTTP_METHOD_NONE &&
+ strcasecmp("Content-Length", key) == 0) {
+ if (desc->http_method == HTTP_METHOD_TRACE ||
+ desc->http_method == HTTP_METHOD_CONNECT) {
+ /*
+ * These method should not have a body
+ * and thus no Content-Length header.
+ */
+ relay_abort_http(con, 400, "malformed", 0);
+ 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.
+ */
+ cre->toread = strtonum(value, 0, LLONG_MAX,
+ &errstr);
+ if (errstr) {
+ relay_abort_http(con, 500, errstr, 0);
+ goto abort;
+ }
+ }
+ lookup:
+ if (strcasecmp("Transfer-Encoding", key) == 0 &&
+ strcasecmp("chunked", value) == 0)
+ desc->http_chunked = 1;
+
+ if (cre->line != 1) {
+ if ((hdr = kv_add(&desc->http_headers, key,
+ value)) == NULL) {
+ free(line);
+ goto fail;
+ }
+ desc->http_lastheader = hdr;
+ }
+
+ free(line);
+ }
+ if (cre->done) {
+ if (desc->http_method == HTTP_METHOD_NONE) {
+ relay_abort_http(con, 406, "no method", 0);
+ return;
+ }
+
+ action = relay_test(proto, cre);
+ if (action == RES_FAIL) {
+ relay_close(con, "filter rule failed");
+ return;
+ } else if (action != RES_PASS) {
+ relay_abort_http(con, 403, "Forbidden", con->se_label);
+ return;
+ }
+
+ switch (desc->http_method) {
+ case HTTP_METHOD_CONNECT:
+ /* Data stream */
+ cre->toread = TOREAD_UNLIMITED;
+ bev->readcb = relay_read;
+ break;
+ case HTTP_METHOD_DELETE:
+ case HTTP_METHOD_GET:
+ case HTTP_METHOD_HEAD:
+ case HTTP_METHOD_OPTIONS:
+ cre->toread = 0;
+ break;
+ case HTTP_METHOD_POST:
+ case HTTP_METHOD_PUT:
+ case HTTP_METHOD_RESPONSE:
+ /* HTTP request payload */
+ if (cre->toread > 0)
+ bev->readcb = relay_read_httpcontent;
+
+ /* Single-pass HTTP body */
+ if (cre->toread < 0) {
+ cre->toread = TOREAD_UNLIMITED;
+ bev->readcb = relay_read;
+ }
+ break;
+ default:
+ /* HTTP handler */
+ cre->toread = TOREAD_HTTP_HEADER;
+ bev->readcb = relay_read_http;
+ break;
+ }
+ if (desc->http_chunked) {
+ /* Chunked transfer encoding */
+ cre->toread = TOREAD_HTTP_CHUNK_LENGTH;
+ bev->readcb = relay_read_httpchunks;
+ }
+
+ if (cre->dir == RELAY_DIR_REQUEST) {
+ if (relay_writerequest_http(cre->dst, cre) == -1)
+ goto fail;
+ } else {
+ if (relay_writeresponse_http(cre->dst, cre) == -1)
+ goto fail;
+ }
+ if (relay_bufferevent_print(cre->dst, "\r\n") == -1 ||
+ relay_writeheader_http(cre->dst, cre) == -1 ||
+ relay_bufferevent_print(cre->dst, "\r\n") == -1)
+ goto fail;
+
+ relay_reset_http(cre);
+ done:
+ if (cre->dir == RELAY_DIR_REQUEST && cre->toread <= 0 &&
+ cre->dst->bev == NULL) {
+ if (rlay->rl_conf.fwdmode == FWD_TRANS) {
+ relay_bindanyreq(con, 0, IPPROTO_TCP);
+ return;
+ }
+ if (relay_connect(con) == -1)
+ relay_abort_http(con, 502, "session failed", 0);
+ return;
+ }
+ }
+ if (con->se_done) {
+ relay_close(con, "last http read (done)");
+ return;
+ }
+ if (EVBUFFER_LENGTH(src) && bev->readcb != relay_read_http)
+ bev->readcb(bev, arg);
+ bufferevent_enable(bev, EV_READ);
+ if (relay_splice(cre) == -1)
+ relay_close(con, strerror(errno));
+ return;
+ fail:
+ relay_abort_http(con, 500, strerror(errno), 0);
+ return;
+ abort:
+ free(line);
+}
+
+void
+relay_read_httpcontent(struct bufferevent *bev, void *arg)
+{
+ struct ctl_relay_event *cre = arg;
+ struct rsession *con = cre->con;
+ struct evbuffer *src = EVBUFFER_INPUT(bev);
+ size_t size;
+
+ getmonotime(&con->se_tv_last);
+
+ size = EVBUFFER_LENGTH(src);
+ DPRINTF("%s: session %d: size %lu, to read %lld", __func__,
+ con->se_id, size, cre->toread);
+ if (!size)
+ return;
+ if (relay_spliceadjust(cre) == -1)
+ goto fail;
+
+ if (cre->toread > 0) {
+ /* Read content data */
+ if ((off_t)size > cre->toread) {
+ size = cre->toread;
+ if (relay_bufferevent_write_chunk(cre->dst, src, size)
+ == -1)
+ goto fail;
+ cre->toread = 0;
+ } else {
+ if (relay_bufferevent_write_buffer(cre->dst, src) == -1)
+ goto fail;
+ cre->toread -= size;
+ }
+ DPRINTF("%s: done, size %lu, to read %lld", __func__,
+ size, cre->toread);
+ }
+ if (cre->toread == 0) {
+ cre->toread = TOREAD_HTTP_HEADER;
+ bev->readcb = relay_read_http;
+ }
+ if (con->se_done)
+ goto done;
+ if (bev->readcb != relay_read_httpcontent)
+ bev->readcb(bev, arg);
+ bufferevent_enable(bev, EV_READ);
+ return;
+ done:
+ relay_close(con, "last http content read");
+ return;
+ fail:
+ relay_close(con, strerror(errno));
+}
+
+void
+relay_read_httpchunks(struct bufferevent *bev, void *arg)
+{
+ struct ctl_relay_event *cre = arg;
+ struct rsession *con = cre->con;
+ struct evbuffer *src = EVBUFFER_INPUT(bev);
+ char *line;
+ long long llval;
+ size_t size;
+
+ getmonotime(&con->se_tv_last);
+
+ size = EVBUFFER_LENGTH(src);
+ DPRINTF("%s: session %d: size %lu, to read %lld", __func__,
+ con->se_id, size, cre->toread);
+ if (!size)
+ return;
+ if (relay_spliceadjust(cre) == -1)
+ goto fail;
+
+ if (cre->toread > 0) {
+ /* Read chunk data */
+ if ((off_t)size > cre->toread) {
+ size = cre->toread;
+ if (relay_bufferevent_write_chunk(cre->dst, src, size)
+ == -1)
+ goto fail;
+ cre->toread = 0;
+ } else {
+ if (relay_bufferevent_write_buffer(cre->dst, src) == -1)
+ goto fail;
+ cre->toread -= size;
+ }
+ DPRINTF("%s: done, size %lu, to read %lld", __func__,
+ size, cre->toread);
+ }
+ switch (cre->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);
+ relay_close(con, "invalid chunk size");
+ return;
+ }
+
+ if (relay_bufferevent_print(cre->dst, line) == -1 ||
+ relay_bufferevent_print(cre->dst, "\r\n") == -1) {
+ free(line);
+ goto fail;
+ }
+ free(line);
+
+ if ((cre->toread = llval) == 0) {
+ DPRINTF("%s: last chunk", __func__);
+ cre->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 (relay_bufferevent_print(cre->dst, line) == -1 ||
+ relay_bufferevent_print(cre->dst, "\r\n") == -1) {
+ free(line);
+ goto fail;
+ }
+ if (strlen(line) == 0) {
+ /* Switch to HTTP header mode */
+ cre->toread = TOREAD_HTTP_HEADER;
+ bev->readcb = relay_read_http;
+ }
+ free(line);
+ break;
+ case 0:
+ /* Chunk is terminated by an empty newline */
+ line = evbuffer_readline(src);
+ if (line != NULL)
+ free(line);
+ if (relay_bufferevent_print(cre->dst, "\r\n") == -1)
+ goto fail;
+ cre->toread = TOREAD_HTTP_CHUNK_LENGTH;
+ break;
+ }
+
+ next:
+ if (con->se_done)
+ goto done;
+ if (EVBUFFER_LENGTH(src))
+ bev->readcb(bev, arg);
+ bufferevent_enable(bev, EV_READ);
+ return;
+
+ done:
+ relay_close(con, "last http chunk read (done)");
+ return;
+ fail:
+ relay_close(con, strerror(errno));
+}
+
+void
+relay_reset_http(struct ctl_relay_event *cre)
+{
+ struct http_descriptor *desc = cre->desc;
+
+ relay_httpdesc_free(desc);
+ if (cre->buf != NULL) {
+ free(cre->buf);
+ cre->buf = NULL;
+ cre->buflen = 0;
+ }
+ desc->http_method = 0;
+ desc->http_chunked = 0;
+ cre->headerlen = 0;
+ cre->line = 0;
+ cre->done = 0;
+}
+
+static int
+_relay_lookup_url(struct ctl_relay_event *cre, char *host, char *path,
+ char *query, struct kv *kv)
+{
+ struct rsession *con = cre->con;
+ char *val, *md = NULL;
+ int ret = RES_FAIL;
+ const char *str = NULL;
+
+ if (asprintf(&val, "%s%s%s%s",
+ host, path,
+ query == NULL ? "" : "?",
+ query == NULL ? "" : query) == -1) {
+ relay_abort_http(con, 500, "failed to allocate URL", 0);
+ return (RES_FAIL);
+ }
+
+ switch (kv->kv_digest) {
+ case DIGEST_SHA1:
+ case DIGEST_MD5:
+ if ((md = digeststr(kv->kv_digest,
+ val, strlen(val), NULL)) == NULL) {
+ relay_abort_http(con, 500,
+ "failed to allocate digest", 0);
+ goto fail;
+ }
+ str = md;
+ break;
+ case DIGEST_NONE:
+ str = val;
+ break;
+ }
+
+ DPRINTF("%s: session %d: %s, %s: %d", __func__, con->se_id,
+ str, kv->kv_key, strcasecmp(kv->kv_key, str));
+
+ if (strcasecmp(kv->kv_key, str) == 0) {
+ ret = RES_DROP;
+ goto fail;
+ }
+
+ ret = RES_PASS;
+ fail:
+ if (md != NULL)
+ free(md);
+ free(val);
+ return (ret);
+}
+
+int
+relay_lookup_url(struct ctl_relay_event *cre, const char *host, struct kv *kv)
+{
+ struct rsession *con = cre->con;
+ struct http_descriptor *desc = (struct http_descriptor *)cre->desc;
+ int i, j, dots;
+ char *hi[RELAY_MAXLOOKUPLEVELS], *p, *pp, *c, ch;
+ char ph[MAXHOSTNAMELEN];
+ int ret;
+
+ if (desc->http_path == NULL)
+ return (RES_PASS);
+
+ /*
+ * This is an URL lookup algorithm inspired by
+ * http://code.google.com/apis/safebrowsing/
+ * developers_guide.html#PerformingLookups
+ */
+
+ DPRINTF("%s: session %d: host '%s', path '%s', query '%s'",
+ __func__, con->se_id, host, desc->http_path,
+ desc->http_query == NULL ? "" : desc->http_query);
+
+ if (canonicalize_host(host, ph, sizeof(ph)) == NULL) {
+ relay_abort_http(con, 400, "invalid host name", 0);
+ return (RES_FAIL);
+ }
+
+ bzero(hi, sizeof(hi));
+ for (dots = -1, i = strlen(ph) - 1; i > 0; i--) {
+ if (ph[i] == '.' && ++dots)
+ hi[dots - 1] = &ph[i + 1];
+ if (dots > (RELAY_MAXLOOKUPLEVELS - 2))
+ break;
+ }
+ if (dots == -1)
+ dots = 0;
+ hi[dots] = ph;
+
+ if ((pp = strdup(desc->http_path)) == NULL) {
+ relay_abort_http(con, 500, "failed to allocate path", 0);
+ return (RES_FAIL);
+ }
+ for (i = (RELAY_MAXLOOKUPLEVELS - 1); i >= 0; i--) {
+ if (hi[i] == NULL)
+ continue;
+
+ /* 1. complete path with query */
+ if (desc->http_query != NULL)
+ if ((ret = _relay_lookup_url(cre, hi[i],
+ pp, desc->http_query, kv)) != RES_PASS)
+ goto done;
+
+ /* 2. complete path without query */
+ if ((ret = _relay_lookup_url(cre, hi[i],
+ pp, NULL, kv)) != RES_PASS)
+ goto done;
+
+ /* 3. traverse path */
+ for (j = 0, p = strchr(pp, '/');
+ p != NULL; p = strchr(p, '/'), j++) {
+ if (j > (RELAY_MAXLOOKUPLEVELS - 2) || *(++p) == '\0')
+ break;
+ c = &pp[p - pp];
+ ch = *c;
+ *c = '\0';
+ if ((ret = _relay_lookup_url(cre, hi[i],
+ pp, NULL, kv)) != RES_PASS)
+ goto done;
+ *c = ch;
+ }
+ }
+
+ ret = RES_PASS;
+ done:
+ free(pp);
+ return (ret);
+}
+
+int
+relay_lookup_cookie(struct ctl_relay_event *cre, const char *str,
+ struct kv *kv)
+{
+ struct rsession *con = cre->con;
+ char *val, *ptr, *key, *value;
+ int ret;
+
+ if ((val = strdup(str)) == NULL) {
+ relay_abort_http(con, 500, "failed to allocate cookie", 0);
+ return (RES_FAIL);
+ }
+
+ for (ptr = val; ptr != NULL && strlen(ptr);) {
+ if (*ptr == ' ')
+ *ptr++ = '\0';
+ key = ptr;
+ if ((ptr = strchr(ptr, ';')) != NULL)
+ *ptr++ = '\0';
+ /*
+ * XXX We do not handle attributes
+ * ($Path, $Domain, or $Port)
+ */
+ if (*key == '$')
+ continue;
+
+ if ((value =
+ strchr(key, '=')) == NULL ||
+ strlen(value) < 1)
+ continue;
+ *value++ = '\0';
+ if (*value == '"')
+ *value++ = '\0';
+ if (value[strlen(value) - 1] == '"')
+ value[strlen(value) - 1] = '\0';
+
+ DPRINTF("%s: session %d: %s = %s, %s = %s : %d",
+ __func__, con->se_id,
+ key, value, kv->kv_key, kv->kv_value,
+ strcasecmp(kv->kv_key, key));
+
+ if (strcasecmp(kv->kv_key, key) == 0 &&
+ ((kv->kv_value == NULL) ||
+ (fnmatch(kv->kv_value, value,
+ FNM_CASEFOLD) != FNM_NOMATCH))) {
+ ret = RES_DROP;
+ goto done;
+ }
+ }
+
+ ret = RES_PASS;
+
+ done:
+ free(val);
+ return (ret);
+}
+
+int
+relay_lookup_query(struct ctl_relay_event *cre, struct kv *kv)
+{
+ struct http_descriptor *desc = cre->desc;
+ struct kv *match = &desc->http_matchquery;
+ char *val, *ptr, *tmpkey = NULL, *tmpval = NULL;
+ int ret = -1;
+
+ if (desc->http_query == NULL)
+ return (-1);
+ if ((val = strdup(desc->http_query)) == NULL) {
+ relay_abort_http(cre->con, 500, "failed to allocate query", 0);
+ return (-1);
+ }
+
+ ptr = val;
+ while (ptr != NULL && strlen(ptr)) {
+ tmpkey = ptr;
+ if ((ptr = strchr(ptr, '&')) != NULL)
+ *ptr++ = '\0';
+ if ((tmpval = strchr(tmpkey, '=')) == NULL || strlen(tmpval)
+ < 1)
+ continue;
+ *tmpval++ = '\0';
+
+ if (fnmatch(kv->kv_key, tmpkey, 0) != FNM_NOMATCH &&
+ (kv->kv_value == NULL || fnmatch(kv->kv_value, tmpval, 0)
+ != FNM_NOMATCH))
+ break;
+ else
+ tmpkey = NULL;
+ }
+
+ if (tmpkey == NULL || tmpval == NULL)
+ goto done;
+
+ match->kv_key = strdup(tmpkey);
+ if (match->kv_key == NULL)
+ goto done;
+ match->kv_value = strdup(tmpval);
+ if (match->kv_key == NULL)
+ goto done;
+ ret = 0;
+
+ done:
+ free(val);
+ return (ret);
+}
+
+
+void
+relay_abort_http(struct rsession *con, u_int code, const char *msg,
+ u_int16_t labelid)
+{
+ struct relay *rlay = con->se_relay;
+ struct bufferevent *bev = con->se_in.bev;
+ const char *httperr = NULL, *text = "";
+ char *httpmsg;
+ time_t t;
+ struct tm *lt;
+ char tmbuf[32], hbuf[128];
+ const char *style, *label = NULL;
+
+ if ((httperr = relay_httperror_byid(code)) == NULL)
+ httperr = "Unknown Error";
+
+ if (labelid != 0)
+ label = label_id2name(labelid);
+
+ /* In some cases this function may be called from generic places */
+ if (rlay->rl_proto->type != RELAY_PROTO_HTTP ||
+ (rlay->rl_proto->flags & F_RETURN) == 0) {
+ relay_close(con, msg);
+ return;
+ }
+
+ if (bev == NULL)
+ goto done;
+
+ /* Some system information */
+ if (print_host(&rlay->rl_conf.ss, hbuf, sizeof(hbuf)) == NULL)
+ goto done;
+
+ /* RFC 2616 "tolerates" asctime() */
+ time(&t);
+ lt = localtime(&t);
+ tmbuf[0] = '\0';
+ if (asctime_r(lt, tmbuf) != NULL)
+ tmbuf[strlen(tmbuf) - 1] = '\0'; /* skip final '\n' */
+
+ /* Do not send details of the Internal Server Error */
+ if (code != 500)
+ text = msg;
+
+ /* A CSS stylesheet allows minimal customization by the user */
+ if ((style = rlay->rl_proto->style) == NULL)
+ style = "body { background-color: #a00000; color: white; }";
+
+ /* Generate simple HTTP+HTML error document */
+ if (asprintf(&httpmsg,
+ "HTTP/1.0 %03d %s\r\n"
+ "Date: %s\r\n"
+ "Server: %s\r\n"
+ "Connection: close\r\n"
+ "Content-Type: text/html\r\n"
+ "\r\n"
+ "<!DOCTYPE HTML PUBLIC "
+ "\"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"
+ "<html>\n"
+ "<head>\n"
+ "<title>%03d %s</title>\n"
+ "<style type=\"text/css\"><!--\n%s\n--></style>\n"
+ "</head>\n"
+ "<body>\n"
+ "<h1>%s</h1>\n"
+ "<div id='m'>%s</div>\n"
+ "<div id='l'>%s</div>\n"
+ "<hr><address>%s at %s port %d</address>\n"
+ "</body>\n"
+ "</html>\n",
+ code, httperr, tmbuf, RELAYD_SERVERNAME,
+ code, httperr, style, httperr, text,
+ label == NULL ? "" : label,
+ RELAYD_SERVERNAME, hbuf, ntohs(rlay->rl_conf.port)) == -1)
+ goto done;
+
+ /* Dump the message without checking for success */
+ relay_dump(&con->se_in, httpmsg, strlen(httpmsg));
+ free(httpmsg);
+
+ done:
+ if (asprintf(&httpmsg, "%s (%03d %s)", msg, code, httperr) == -1)
+ relay_close(con, msg);
+ else {
+ relay_close(con, httpmsg);
+ free(httpmsg);
+ }
+}
+
+void
+relay_close_http(struct rsession *con)
+{
+ struct http_descriptor *desc[2] = {
+ con->se_in.desc, con->se_out.desc
+ };
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ if (desc[i] == NULL)
+ continue;
+ relay_httpdesc_free(desc[i]);
+ free(desc[i]);
+ }
+}
+
+char *
+relay_expand_http(struct ctl_relay_event *cre, char *val, char *buf,
+ size_t len)
+{
+ struct rsession *con = cre->con;
+ struct relay *rlay = con->se_relay;
+ char ibuf[128];
+
+ if (strlcpy(buf, val, len) >= len)
+ return (NULL);
+
+ if (strstr(val, "$REMOTE_") != NULL) {
+ if (strstr(val, "$REMOTE_ADDR") != NULL) {
+ if (print_host(&cre->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(cre->port));
+ if (expand_string(buf, len,
+ "$REMOTE_PORT", ibuf) != 0)
+ return (NULL);
+ }
+ }
+ if (strstr(val, "$SERVER_") != NULL) {
+ if (strstr(val, "$SERVER_ADDR") != NULL) {
+ if (print_host(&rlay->rl_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(rlay->rl_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", RELAYD_SERVERNAME) != 0)
+ return (NULL);
+ }
+ }
+ if (strstr(val, "$TIMEOUT") != NULL) {
+ snprintf(ibuf, sizeof(ibuf), "%lld",
+ (long long)rlay->rl_conf.timeout.tv_sec);
+ if (expand_string(buf, len, "$TIMEOUT", ibuf) != 0)
+ return (NULL);
+ }
+
+ return (buf);
+}
+
+int
+relay_writerequest_http(struct ctl_relay_event *dst,
+ struct ctl_relay_event *cre)
+{
+ struct http_descriptor *desc = (struct http_descriptor *)cre->desc;
+ const char *name = NULL;
+
+ if ((name = relay_httpmethod_byid(desc->http_method)) == NULL)
+ return (-1);
+
+ if (relay_bufferevent_print(dst, name) == -1 ||
+ relay_bufferevent_print(dst, " ") == -1 ||
+ relay_bufferevent_print(dst, desc->http_path) == -1 ||
+ (desc->http_query != NULL &&
+ (relay_bufferevent_print(dst, "?") == -1 ||
+ relay_bufferevent_print(dst, desc->http_query) == -1)) ||
+ relay_bufferevent_print(dst, " ") == -1 ||
+ relay_bufferevent_print(dst, desc->http_version) == -1)
+ return (-1);
+
+ return (0);
+}
+
+int
+relay_writeresponse_http(struct ctl_relay_event *dst,
+ struct ctl_relay_event *cre)
+{
+ struct http_descriptor *desc = (struct http_descriptor *)cre->desc;
+
+ DPRINTF("version: %s rescode: %s resmsg: %s", desc->http_version,
+ desc->http_rescode, desc->http_resmesg);
+
+ if (relay_bufferevent_print(dst, desc->http_version) == -1 ||
+ relay_bufferevent_print(dst, " ") == -1 ||
+ relay_bufferevent_print(dst, desc->http_rescode) == -1 ||
+ relay_bufferevent_print(dst, " ") == -1 ||
+ relay_bufferevent_print(dst, desc->http_resmesg) == -1)
+ return (-1);
+
+ return (0);
+}
+
+int
+relay_writeheader_kv(struct ctl_relay_event *dst, struct kv *hdr)
+{
+ char *ptr;
+ const char *key;
+
+ if (hdr->kv_flags & KV_FLAG_INVALID)
+ return (0);
+
+ /* The key might have been updated in the parent */
+ if (hdr->kv_parent != NULL && hdr->kv_parent->kv_key != NULL)
+ key = hdr->kv_parent->kv_key;
+ else
+ key = hdr->kv_key;
+
+ ptr = hdr->kv_value;
+ DPRINTF("%s: ptr %s", __func__, ptr);
+ if (relay_bufferevent_print(dst, key) == -1 ||
+ (ptr != NULL &&
+ (relay_bufferevent_print(dst, ": ") == -1 ||
+ relay_bufferevent_print(dst, ptr) == -1 ||
+ relay_bufferevent_print(dst, "\r\n") == -1)))
+ return (-1);
+ DPRINTF("%s: %s: %s", __func__, key,
+ hdr->kv_value == NULL ? "" : hdr->kv_value);
+
+ return (0);
+}
+
+int
+relay_writeheader_http(struct ctl_relay_event *dst, struct ctl_relay_event
+ *cre)
+{
+ struct kv *hdr, *kv;
+ struct http_descriptor *desc = (struct http_descriptor *)cre->desc;
+
+ RB_FOREACH(hdr, kvtree, &desc->http_headers) {
+ if (relay_writeheader_kv(dst, hdr) == -1)
+ return (-1);
+ TAILQ_FOREACH(kv, &hdr->kv_children, kv_entry) {
+ if (relay_writeheader_kv(dst, kv) == -1)
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+enum httpmethod
+relay_httpmethod_byname(const char *name)
+{
+ enum httpmethod id = HTTP_METHOD_NONE;
+ struct http_method method, *res = NULL;
+
+ /* Set up key */
+ method.method_name = name;
+
+ /*
+ * RFC 2616 section 5.1.1 says that the method is case
+ * sensitive so we don't do a strcasecmp here.
+ */
+ if ((res = bsearch(&method, http_methods,
+ sizeof(http_methods) / sizeof(http_methods[0]) - 1,
+ sizeof(http_methods[0]), relay_httpmethod_cmp)) != NULL)
+ id = res->method_id;
+
+ return (id);
+}
+
+const char *
+relay_httpmethod_byid(u_int id)
+{
+ const char *name = NULL;
+ int i;
+
+ for (i = 0; http_methods[i].method_name != NULL; i++) {
+ if (http_methods[i].method_id == id) {
+ name = http_methods[i].method_name;
+ break;
+ }
+ }
+
+ return (name);
+}
+
+static int
+relay_httpmethod_cmp(const void *a, const void *b)
+{
+ const struct http_method *ma = a;
+ const struct http_method *mb = b;
+ return (strcmp(ma->method_name, mb->method_name));
+}
+
+const char *
+relay_httperror_byid(u_int id)
+{
+ struct http_error error, *res = NULL;
+
+ /* Set up key */
+ error.error_code = (int)id;
+
+ res = bsearch(&error, http_errors,
+ sizeof(http_errors) / sizeof(http_errors[0]) - 1,
+ sizeof(http_errors[0]), relay_httperror_cmp);
+
+ return (res->error_name);
+}
+
+static int
+relay_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
+relay_httpquery_test(struct ctl_relay_event *cre, struct relay_rule *rule,
+ struct kvlist *actions)
+{
+ struct http_descriptor *desc = cre->desc;
+ struct kv *match = &desc->http_matchquery;
+ struct kv *kv = &rule->rule_kv[KEY_TYPE_QUERY];
+
+ if (cre->dir == RELAY_DIR_RESPONSE || kv->kv_type != KEY_TYPE_QUERY)
+ return (0);
+ else if (kv->kv_key == NULL)
+ return (0);
+ else if (relay_lookup_query(cre, kv))
+ return (-1);
+
+ relay_match(actions, kv, match, NULL);
+
+ return (0);
+}
+
+int
+relay_httpheader_test(struct ctl_relay_event *cre, struct relay_rule *rule,
+ struct kvlist *actions)
+{
+ struct http_descriptor *desc = cre->desc;
+ struct kv *kv = &rule->rule_kv[KEY_TYPE_HEADER];
+ struct kv *match;
+
+ if (kv->kv_type != KEY_TYPE_HEADER)
+ return (0);
+
+ match = kv_find(&desc->http_headers, kv);
+
+ if (kv->kv_option == KEY_OPTION_APPEND ||
+ kv->kv_option == KEY_OPTION_SET) {
+ /* header can be NULL and will be added later */
+ } else if (match == NULL) {
+ /* Fail if header doesn't exist */
+ return (-1);
+ }
+
+ relay_match(actions, kv, match, &desc->http_headers);
+
+ return (0);
+}
+
+int
+relay_httppath_test(struct ctl_relay_event *cre, struct relay_rule *rule,
+ struct kvlist *actions)
+{
+ struct http_descriptor *desc = cre->desc;
+ struct kv *kv = &rule->rule_kv[KEY_TYPE_PATH];
+ struct kv *match = &desc->http_pathquery;
+ const char *query;
+
+ if (cre->dir == RELAY_DIR_RESPONSE || kv->kv_type != KEY_TYPE_PATH)
+ return (0);
+ else if (kv->kv_key == NULL)
+ return (0);
+ else if (fnmatch(kv->kv_key, desc->http_path, 0) == FNM_NOMATCH)
+ return (-1);
+ else if (kv->kv_value != NULL && kv->kv_option == KEY_OPTION_NONE) {
+ query = desc->http_query == NULL ? "" : desc->http_query;
+ if (fnmatch(kv->kv_value, query, FNM_CASEFOLD) == FNM_NOMATCH)
+ return (-1);
+ }
+
+ relay_match(actions, kv, match, NULL);
+
+ return (0);
+}
+
+int
+relay_httpurl_test(struct ctl_relay_event *cre, struct relay_rule *rule,
+ struct kvlist *actions)
+{
+ struct http_descriptor *desc = cre->desc;
+ struct kv *host, key;
+ struct kv *kv = &rule->rule_kv[KEY_TYPE_URL];
+ struct kv *match = &desc->http_pathquery;
+
+ if (cre->dir == RELAY_DIR_RESPONSE || kv->kv_type != KEY_TYPE_URL ||
+ kv->kv_key == NULL)
+ return (0);
+
+ key.kv_key = "Host";
+ host = kv_find(&desc->http_headers, &key);
+
+ if (host == NULL || host->kv_value == NULL)
+ return (0);
+ else if (rule->rule_action != RULE_ACTION_BLOCK &&
+ kv->kv_option == KEY_OPTION_LOG &&
+ fnmatch(kv->kv_key, match->kv_key, FNM_CASEFOLD) != FNM_NOMATCH) {
+ /* fnmatch url only for logging */
+ } else if (relay_lookup_url(cre, host->kv_value, kv) != 0)
+ return (-1);
+
+ relay_match(actions, kv, match, NULL);
+
+ return (0);
+}
+
+int
+relay_httpcookie_test(struct ctl_relay_event *cre, struct relay_rule *rule,
+ struct kvlist *actions)
+{
+ struct http_descriptor *desc = cre->desc;
+ struct kv *kv = &rule->rule_kv[KEY_TYPE_COOKIE], key;
+ struct kv *match = NULL;
+
+ if (kv->kv_type != KEY_TYPE_COOKIE)
+ return (0);
+
+ switch (cre->dir) {
+ case RELAY_DIR_REQUEST:
+ key.kv_key = "Cookie";
+ break;
+ case RELAY_DIR_RESPONSE:
+ key.kv_key = "Set-Cookie";
+ break;
+ default:
+ return (0);
+ /* NOTREACHED */
+ break;
+ }
+
+ if (kv->kv_option == KEY_OPTION_APPEND ||
+ kv->kv_option == KEY_OPTION_SET) {
+ /* no cookie, can be NULL and will be added later */
+ } else {
+ match = kv_find(&desc->http_headers, &key);
+ if (match == NULL)
+ return (-1);
+ if (kv->kv_key == NULL || match->kv_value == NULL)
+ return (0);
+ else if (relay_lookup_cookie(cre, match->kv_value, kv) != 0)
+ return (-1);
+ }
+
+ relay_match(actions, kv, match, &desc->http_headers);
+
+ return (0);
+}
+
+int
+relay_match_actions(struct ctl_relay_event *cre, struct relay_rule *rule,
+ struct kvlist *matches, struct kvlist *actions)
+{
+ struct rsession *con = cre->con;
+ struct kv *kv;
+
+ /*
+ * Apply the following options instantly (action per match).
+ */
+ if (rule->rule_table != NULL)
+ con->se_table = rule->rule_table;
+
+ if (rule->rule_tag != 0)
+ con->se_tag = rule->rule_tag == -1 ? 0 : rule->rule_tag;
+
+ if (rule->rule_label != 0)
+ con->se_label = rule->rule_label == -1 ? 0 : rule->rule_label;
+
+ /*
+ * Apply the remaining options once after evaluation.
+ */
+ if (matches == NULL) {
+ /* 'pass' or 'block' rule */
+ TAILQ_FOREACH(kv, &rule->rule_kvlist, kv_rule_entry) {
+ TAILQ_INSERT_TAIL(actions, kv, kv_action_entry);
+ TAILQ_REMOVE(&rule->rule_kvlist, kv, kv_rule_entry);
+ }
+ } else {
+ /* 'match' rule */
+ TAILQ_FOREACH(kv, matches, kv_match_entry) {
+ TAILQ_INSERT_TAIL(actions, kv, kv_action_entry);
+ }
+ }
+
+ return (0);
+}
+
+int
+relay_apply_actions(struct ctl_relay_event *cre, struct kvlist *actions)
+{
+ struct rsession *con = cre->con;
+ struct http_descriptor *desc = cre->desc;
+ struct kv *host = NULL;
+ const char *value;
+ struct kv *kv, *match, *kp, *mp, kvcopy, matchcopy, key;
+ int addkv, ret;
+ char buf[IBUF_READ_SIZE], *ptr;
+
+ memset(&kvcopy, 0, sizeof(kvcopy));
+ memset(&matchcopy, 0, sizeof(matchcopy));
+
+ ret = -1;
+ kp = mp = NULL;
+ TAILQ_FOREACH(kv, actions, kv_action_entry) {
+ kp = NULL;
+ match = kv->kv_match;
+ addkv = 0;
+
+ /*
+ * Although marked as deleted, give a chance to non-critical
+ * actions, ie. log, to be performed
+ */
+ if (match != NULL && (match->kv_flags & KV_FLAG_INVALID))
+ goto matchdel;
+
+ switch (kv->kv_option) {
+ case KEY_OPTION_APPEND:
+ case KEY_OPTION_SET:
+ switch (kv->kv_type) {
+ case KEY_TYPE_PATH:
+ if (kv->kv_option == KEY_OPTION_APPEND) {
+ if (kv_setkey(match, "%s%s",
+ match->kv_key, kv->kv_key) == -1)
+ goto fail;
+ } else {
+ if (kv_setkey(match, "%s",
+ kv->kv_value) == -1)
+ goto fail;
+ }
+ break;
+ case KEY_TYPE_COOKIE:
+ kp = &kvcopy;
+ if (kv_inherit(kp, kv) == NULL)
+ goto fail;
+ if (kv_set(kp, "%s=%s;", kp->kv_key,
+ kp->kv_value) == -1)
+ goto fail;
+ if (kv_setkey(kp, "%s", cre->dir ==
+ RELAY_DIR_REQUEST ?
+ "Cookie" : "Set-Cookie") == -1)
+ goto fail;
+ /* FALLTHROUGH cookie is a header */
+ case KEY_TYPE_HEADER:
+ if (match == NULL) {
+ addkv = 1;
+ break;
+ }
+ if (match->kv_value == NULL ||
+ kv->kv_option == KEY_OPTION_SET) {
+ if (kv_set(match, "%s",
+ kv->kv_value) == -1)
+ goto fail;
+ } else {
+ if (kv_setkey(match, "%s,%s",
+ match->kv_key, kv->kv_key) == -1)
+ goto fail;
+ }
+ break;
+ default:
+ /* query, url not supported */
+ break;
+ }
+ break;
+ case KEY_OPTION_REMOVE:
+ switch (kv->kv_type) {
+ case KEY_TYPE_PATH:
+ if (kv_setkey(match, "/") == -1)
+ goto fail;
+ break;
+ case KEY_TYPE_COOKIE:
+ case KEY_TYPE_HEADER:
+ if (kv->kv_matchtree != NULL)
+ match->kv_flags |= KV_FLAG_INVALID;
+ else
+ kv_free(match);
+ match = kv->kv_match = NULL;
+ break;
+ default:
+ /* query and url not supported */
+ break;
+ }
+ break;
+ case KEY_OPTION_HASH:
+ switch (kv->kv_type) {
+ case KEY_TYPE_PATH:
+ value = match->kv_key;
+ break;
+ default:
+ value = match->kv_value;
+ break;
+ }
+ if (!con->se_hashkeyset)
+ con->se_hashkey = HASHINIT;
+ con->se_hashkey = hash32_str(value, con->se_hashkey);
+ con->se_hashkeyset = 1;
+ log_debug("%s: hashkey 0x%04x", __func__,
+ con->se_hashkey);
+ break;
+ case KEY_OPTION_LOG:
+ /* perform this later */
+ break;
+ default:
+ fatalx("relay_action: invalid action");
+ /* NOTREACHED */
+ }
+
+ /* from now on, reads from kp writes to kv */
+ if (kp == NULL)
+ kp = kv;
+ if (addkv && kv->kv_matchtree != NULL) {
+ /* Add new entry to the list (eg. new HTTP header) */
+ if ((match = kv_add(kv->kv_matchtree, kp->kv_key,
+ kp->kv_value)) == NULL)
+ goto fail;
+ match->kv_option = kp->kv_option;
+ match->kv_type = kp->kv_type;
+ kv->kv_match = match;
+ }
+ if (match != NULL && kp->kv_flags & KV_FLAG_MACRO) {
+ bzero(buf, sizeof(buf));
+ if ((ptr = relay_expand_http(cre, kp->kv_value, buf,
+ sizeof(buf))) == NULL)
+ goto fail;
+ if (kv_set(match, ptr) == -1)
+ goto fail;
+ }
+
+ matchdel:
+ switch(kv->kv_option) {
+ case KEY_OPTION_LOG:
+ if (match == NULL)
+ break;
+ mp = &matchcopy;
+ if (kv_inherit(mp, match) == NULL)
+ goto fail;
+ if (mp->kv_flags & KV_FLAG_INVALID) {
+ if (kv_set(mp, "%s (removed)",
+ mp->kv_value) == -1)
+ goto fail;
+ }
+ switch(kv->kv_type) {
+ case KEY_TYPE_URL:
+ key.kv_key = "Host";
+ host = kv_find(&desc->http_headers, &key);
+ switch (kv->kv_digest) {
+ case DIGEST_NONE:
+ if (host == NULL ||
+ host->kv_value == NULL)
+ break;
+ if (kv_setkey(mp, "%s%s",
+ host->kv_value, mp->kv_key) ==
+ -1)
+ goto fail;
+ break;
+ default:
+ if (kv_setkey(mp, "%s", kv->kv_key)
+ == -1)
+ goto fail;
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ if (kv_log(con->se_log, mp, con->se_label) == -1)
+ goto fail;
+ break;
+ default:
+ break;
+ }
+
+ /* actions applied, cleanup kv */
+ kv->kv_match = NULL;
+ kv->kv_matchtree = NULL;
+ TAILQ_REMOVE(actions, kv, kv_match_entry);
+
+ kv_free(&kvcopy);
+ kv_free(&matchcopy);
+ }
+
+ ret = 0;
+ fail:
+ kv_free(&kvcopy);
+ kv_free(&matchcopy);
+
+ return (ret);
+}
+
+#define RELAY_GET_SKIP_STEP(i) \
+ do { \
+ r = r->rule_skip[i]; \
+ DPRINTF("%s:%d: skip %d rules", __func__, __LINE__, i); \
+ } while (0)
+
+#define RELAY_GET_NEXT_STEP \
+ do { \
+ DPRINTF("%s:%d: next rule", __func__, __LINE__); \
+ goto nextrule; \
+ } while (0)
+
+int
+relay_test(struct protocol *proto, struct ctl_relay_event *cre)
+{
+ struct rsession *con;
+ struct http_descriptor *desc = cre->desc;
+ struct relay_rule *r = NULL, *rule = NULL;
+ u_int cnt = 0;
+ u_int action = RES_PASS;
+ struct kvlist actions, matches;
+ struct kv *kv;
+
+ con = cre->con;
+ TAILQ_INIT(&actions);
+
+ r = TAILQ_FIRST(&proto->rules);
+ while (r != NULL) {
+ cnt++;
+ TAILQ_INIT(&matches);
+ TAILQ_INIT(&r->rule_kvlist);
+ if (r->rule_dir && r->rule_dir != cre->dir)
+ RELAY_GET_SKIP_STEP(RULE_SKIP_DIR);
+ else if (proto->type != r->rule_proto)
+ RELAY_GET_SKIP_STEP(RULE_SKIP_PROTO);
+ else if (r->rule_af != AF_UNSPEC &&
+ (cre->ss.ss_family != r->rule_af ||
+ cre->dst->ss.ss_family != r->rule_af))
+ RELAY_GET_SKIP_STEP(RULE_SKIP_AF);
+ else if (RELAY_ADDR_CMP(&r->rule_src, &cre->ss) != 0)
+ RELAY_GET_SKIP_STEP(RULE_SKIP_SRC);
+ else if (RELAY_ADDR_CMP(&r->rule_dst, &cre->dst->ss) != 0)
+ RELAY_GET_SKIP_STEP(RULE_SKIP_DST);
+ else if (r->rule_method != HTTP_METHOD_NONE &&
+ (desc->http_method == HTTP_METHOD_RESPONSE ||
+ desc->http_method != r->rule_method))
+ RELAY_GET_SKIP_STEP(RULE_SKIP_METHOD);
+ else if (r->rule_tagged && con->se_tag != r->rule_tagged)
+ RELAY_GET_NEXT_STEP;
+ else if (relay_httpheader_test(cre, r, &matches) != 0)
+ RELAY_GET_NEXT_STEP;
+ else if (relay_httpquery_test(cre, r, &matches) != 0)
+ RELAY_GET_NEXT_STEP;
+ else if (relay_httppath_test(cre, r, &matches) != 0)
+ RELAY_GET_NEXT_STEP;
+ else if (relay_httpurl_test(cre, r, &matches) != 0)
+ RELAY_GET_NEXT_STEP;
+ else if (relay_httpcookie_test(cre, r, &matches) != 0)
+ RELAY_GET_NEXT_STEP;
+ else {
+ DPRINTF("%s: session %d: matched rule %d",
+ __func__, con->se_id, r->rule_id);
+
+ if (r->rule_action == RULE_ACTION_MATCH) {
+ if (relay_match_actions(cre, r, &matches,
+ &actions) != 0) {
+ /* Something bad happened, drop */
+ action = RES_DROP;
+ break;
+ }
+ RELAY_GET_NEXT_STEP;
+ } else if (r->rule_action == RULE_ACTION_BLOCK)
+ action = RES_DROP;
+ else if (r->rule_action == RULE_ACTION_PASS)
+ action = RES_PASS;
+
+ /* Rule matched */
+ rule = r;
+
+ /* Temporarily save actions */
+ TAILQ_FOREACH(kv, &matches, kv_match_entry) {
+ TAILQ_INSERT_TAIL(&rule->rule_kvlist,
+ kv, kv_rule_entry);
+ }
+
+ if (rule->rule_flags & RULE_FLAG_QUICK)
+ break;
+
+ nextrule:
+ /* Continue to find last matching policy */
+ r = TAILQ_NEXT(r, rule_entry);
+ }
+ }
+
+ if (rule != NULL &&
+ relay_match_actions(cre, rule, NULL, &actions) != 0) {
+ /* Something bad happened, drop */
+ action = RES_DROP;
+ }
+
+ if (relay_apply_actions(cre, &actions) != 0) {
+ /* Something bad happened, drop */
+ action = RES_DROP;
+ }
+
+ DPRINTF("%s: session %d: action %d", __func__,
+ con->se_id, action);
+
+ return (action);
+}
+
+#define RELAY_SET_SKIP_STEPS(i) \
+ do { \
+ while (head[i] != cur) { \
+ head[i]->rule_skip[i] = cur; \
+ head[i] = TAILQ_NEXT(head[i], rule_entry); \
+ } \
+ } while (0)
+
+/* This code is derived from pf_calc_skip_steps() from pf.c */
+void
+relay_calc_skip_steps(struct relay_rules *rules)
+{
+ struct relay_rule *head[RULE_SKIP_COUNT], *cur, *prev;
+ int i;
+
+ cur = TAILQ_FIRST(rules);
+ prev = cur;
+ for (i = 0; i < RULE_SKIP_COUNT; ++i)
+ head[i] = cur;
+ while (cur != NULL) {
+ if (cur->rule_dir != prev->rule_dir)
+ RELAY_SET_SKIP_STEPS(RULE_SKIP_DIR);
+ else if (cur->rule_proto != prev->rule_proto)
+ RELAY_SET_SKIP_STEPS(RULE_SKIP_PROTO);
+ else if (cur->rule_af != prev->rule_af)
+ RELAY_SET_SKIP_STEPS(RULE_SKIP_AF);
+ else if (RELAY_ADDR_NEQ(&cur->rule_src, &prev->rule_src))
+ RELAY_SET_SKIP_STEPS(RULE_SKIP_SRC);
+ else if (RELAY_ADDR_NEQ(&cur->rule_dst, &prev->rule_dst))
+ RELAY_SET_SKIP_STEPS(RULE_SKIP_DST);
+ else if (cur->rule_method != prev->rule_method)
+ RELAY_SET_SKIP_STEPS(RULE_SKIP_METHOD);
+
+ prev = cur;
+ cur = TAILQ_NEXT(cur, rule_entry);
+ }
+ for (i = 0; i < RULE_SKIP_COUNT; ++i)
+ RELAY_SET_SKIP_STEPS(i);
+}
+
+void
+relay_match(struct kvlist *actions, struct kv *kv, struct kv *match,
+ struct kvtree *matchtree)
+{
+ if (kv->kv_option != KEY_OPTION_NONE) {
+ kv->kv_match = match;
+ kv->kv_matchtree = matchtree;
+ TAILQ_INSERT_TAIL(actions, kv, kv_match_entry);
+ }
+}