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