diff options
author | Reyk Floeter <reyk@esdenera.com> | 2015-03-07 21:15:37 +0100 |
---|---|---|
committer | Reyk Floeter <reyk@esdenera.com> | 2015-03-07 21:15:37 +0100 |
commit | 7fdb196d97b9af7ed5e238ce8dca1ed5ad21ebbe (patch) | |
tree | 580763c74a99af82056996f27fa2277554b13d71 /httpd | |
parent | fa2f6dd5e2c33c291f059a90713e585abfc310ef (diff) | |
download | httpd-7fdb196d97b9af7ed5e238ce8dca1ed5ad21ebbe.tar.gz httpd-7fdb196d97b9af7ed5e238ce8dca1ed5ad21ebbe.zip |
Move files the git repository
Diffstat (limited to 'httpd')
-rw-r--r-- | httpd/Makefile | 19 | ||||
-rw-r--r-- | httpd/config.c | 589 | ||||
-rw-r--r-- | httpd/control.c | 334 | ||||
-rw-r--r-- | httpd/http.h | 253 | ||||
-rw-r--r-- | httpd/httpd.8 | 102 | ||||
-rw-r--r-- | httpd/httpd.c | 1281 | ||||
-rw-r--r-- | httpd/httpd.conf.5 | 533 | ||||
-rw-r--r-- | httpd/httpd.h | 688 | ||||
-rw-r--r-- | httpd/log.c | 242 | ||||
-rw-r--r-- | httpd/logger.c | 312 | ||||
-rw-r--r-- | httpd/parse.y | 2062 | ||||
-rw-r--r-- | httpd/proc.c | 622 | ||||
-rw-r--r-- | httpd/server.c | 1221 | ||||
-rw-r--r-- | httpd/server_fcgi.c | 729 | ||||
-rw-r--r-- | httpd/server_file.c | 469 | ||||
-rw-r--r-- | httpd/server_http.c | 1425 |
16 files changed, 10881 insertions, 0 deletions
diff --git a/httpd/Makefile b/httpd/Makefile new file mode 100644 index 0000000..885ad42 --- /dev/null +++ b/httpd/Makefile @@ -0,0 +1,19 @@ +# $OpenBSD: Makefile,v 1.27 2015/02/23 10:39:10 reyk Exp $ + +PROG= httpd +SRCS= parse.y +SRCS+= config.c control.c httpd.c log.c logger.c proc.c +SRCS+= server.c server_http.c server_file.c server_fcgi.c +MAN= httpd.8 httpd.conf.5 + +LDADD= -levent -ltls -lssl -lcrypto -lutil +DPADD= ${LIBEVENT} ${LIBTLS} ${LIBSSL} ${LIBCRYPTO} ${LIBUTIL} +#DEBUG= -g -DDEBUG=3 -O0 +CFLAGS+= -Wall -I${.CURDIR} +CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith +CFLAGS+= -Wsign-compare +CLEANFILES+= y.tab.h + +.include <bsd.prog.mk> diff --git a/httpd/config.c b/httpd/config.c new file mode 100644 index 0000000..2dc9159 --- /dev/null +++ b/httpd/config.c @@ -0,0 +1,589 @@ +/* $OpenBSD: config.c,v 1.36 2015/02/23 11:48:41 reyk Exp $ */ + +/* + * Copyright (c) 2011 - 2015 Reyk Floeter <reyk@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/time.h> +#include <sys/uio.h> + +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <imsg.h> + +#include "httpd.h" + +int config_getserver_config(struct httpd *, struct server *, + struct imsg *); +int config_getserver_auth(struct httpd *, struct server_config *); + +int +config_init(struct httpd *env) +{ + struct privsep *ps = env->sc_ps; + u_int what; + + /* Global configuration */ + if (privsep_process == PROC_PARENT) { + env->sc_prefork_server = SERVER_NUMPROC; + + ps->ps_what[PROC_PARENT] = CONFIG_ALL; + ps->ps_what[PROC_SERVER] = + CONFIG_SERVERS|CONFIG_MEDIA|CONFIG_AUTH; + ps->ps_what[PROC_LOGGER] = CONFIG_SERVERS; + } + + /* Other configuration */ + what = ps->ps_what[privsep_process]; + + if (what & CONFIG_SERVERS) { + if ((env->sc_servers = + calloc(1, sizeof(*env->sc_servers))) == NULL) + return (-1); + TAILQ_INIT(env->sc_servers); + } + + if (what & CONFIG_MEDIA) { + if ((env->sc_mediatypes = + calloc(1, sizeof(*env->sc_mediatypes))) == NULL) + return (-1); + RB_INIT(env->sc_mediatypes); + } + + if (what & CONFIG_AUTH) { + if ((env->sc_auth = + calloc(1, sizeof(*env->sc_auth))) == NULL) + return (-1); + TAILQ_INIT(env->sc_auth); + } + + return (0); +} + +void +config_purge(struct httpd *env, u_int reset) +{ + struct privsep *ps = env->sc_ps; + struct server *srv; + struct auth *auth; + u_int what; + + what = ps->ps_what[privsep_process] & reset; + + if (what & CONFIG_SERVERS && env->sc_servers != NULL) { + while ((srv = TAILQ_FIRST(env->sc_servers)) != NULL) + server_purge(srv); + } + + if (what & CONFIG_MEDIA && env->sc_mediatypes != NULL) + media_purge(env->sc_mediatypes); + + if (what & CONFIG_AUTH && env->sc_auth != NULL) { + while ((auth = TAILQ_FIRST(env->sc_auth)) != NULL) { + auth_free(env->sc_auth, auth); + free(auth); + } + } +} + +int +config_setreset(struct httpd *env, u_int reset) +{ + struct privsep *ps = env->sc_ps; + int id; + + for (id = 0; id < PROC_MAX; id++) { + if ((reset & ps->ps_what[id]) == 0 || + id == privsep_process) + continue; + proc_compose_imsg(ps, id, -1, IMSG_CTL_RESET, -1, + &reset, sizeof(reset)); + } + + return (0); +} + +int +config_getreset(struct httpd *env, struct imsg *imsg) +{ + u_int mode; + + IMSG_SIZE_CHECK(imsg, &mode); + memcpy(&mode, imsg->data, sizeof(mode)); + + config_purge(env, mode); + + return (0); +} + +int +config_getcfg(struct httpd *env, struct imsg *imsg) +{ + struct privsep *ps = env->sc_ps; + struct ctl_flags cf; + u_int what; + + if (IMSG_DATA_SIZE(imsg) != sizeof(cf)) + return (0); /* ignore */ + + /* Update runtime flags */ + memcpy(&cf, imsg->data, sizeof(cf)); + env->sc_opts = cf.cf_opts; + env->sc_flags = cf.cf_flags; + + what = ps->ps_what[privsep_process]; + + if (privsep_process != PROC_PARENT) + proc_compose_imsg(env->sc_ps, PROC_PARENT, -1, + IMSG_CFG_DONE, -1, NULL, 0); + + return (0); +} + +int +config_setserver(struct httpd *env, struct server *srv) +{ + struct privsep *ps = env->sc_ps; + struct server_config s; + int id; + int fd, n, m; + struct iovec iov[6]; + size_t c; + u_int what; + + /* opens listening sockets etc. */ + if (server_privinit(srv) == -1) + return (-1); + + for (id = 0; id < PROC_MAX; id++) { + what = ps->ps_what[id]; + + if ((what & CONFIG_SERVERS) == 0 || id == privsep_process) + continue; + + DPRINTF("%s: sending %s \"%s[%u]\" to %s fd %d", __func__, + (srv->srv_conf.flags & SRVFLAG_LOCATION) ? + "location" : "server", + srv->srv_conf.name, srv->srv_conf.id, + ps->ps_title[id], srv->srv_s); + + memcpy(&s, &srv->srv_conf, sizeof(s)); + + c = 0; + iov[c].iov_base = &s; + iov[c++].iov_len = sizeof(s); + if (srv->srv_conf.return_uri_len != 0) { + iov[c].iov_base = srv->srv_conf.return_uri; + iov[c++].iov_len = srv->srv_conf.return_uri_len; + } + if (srv->srv_conf.tls_cert_len != 0) { + iov[c].iov_base = srv->srv_conf.tls_cert; + iov[c++].iov_len = srv->srv_conf.tls_cert_len; + } + if (srv->srv_conf.tls_key_len != 0) { + iov[c].iov_base = srv->srv_conf.tls_key; + iov[c++].iov_len = srv->srv_conf.tls_key_len; + } + + if (id == PROC_SERVER && + (srv->srv_conf.flags & SRVFLAG_LOCATION) == 0) { + /* XXX imsg code will close the fd after 1st call */ + n = -1; + proc_range(ps, id, &n, &m); + for (n = 0; n < m; n++) { + if (srv->srv_s == -1) + fd = -1; + else if ((fd = dup(srv->srv_s)) == -1) + return (-1); + proc_composev_imsg(ps, id, n, + IMSG_CFG_SERVER, fd, iov, c); + } + } else { + proc_composev_imsg(ps, id, -1, IMSG_CFG_SERVER, -1, + iov, c); + } + } + + return (0); +} + +int +config_getserver_auth(struct httpd *env, struct server_config *srv_conf) +{ + struct privsep *ps = env->sc_ps; + + if ((ps->ps_what[privsep_process] & CONFIG_AUTH) == 0 || + (srv_conf->flags & SRVFLAG_AUTH) == 0) + return (0); + + if ((srv_conf->auth = auth_byid(env->sc_auth, + srv_conf->auth_id)) == NULL) + return (-1); + + return (0); +} + +int +config_getserver_config(struct httpd *env, struct server *srv, + struct imsg *imsg) +{ +#ifdef DEBUG + struct privsep *ps = env->sc_ps; +#endif + struct server_config *srv_conf, *parent; + u_int8_t *p = imsg->data; + u_int f; + size_t s; + + if ((srv_conf = calloc(1, sizeof(*srv_conf))) == NULL) + return (-1); + + IMSG_SIZE_CHECK(imsg, srv_conf); + memcpy(srv_conf, p, sizeof(*srv_conf)); + s = sizeof(*srv_conf); + + /* Reset these variables to avoid free'ing invalid pointers */ + serverconfig_reset(srv_conf); + + TAILQ_FOREACH(parent, &srv->srv_hosts, entry) { + if (strcmp(parent->name, srv_conf->name) == 0) + break; + } + if (parent == NULL) + parent = &srv->srv_conf; + + if (config_getserver_auth(env, srv_conf) != 0) + goto fail; + + /* + * Get variable-length values for the virtual host. The tls_* ones + * aren't needed in the virtual hosts unless we implement SNI. + */ + if (srv_conf->return_uri_len != 0) { + if ((srv_conf->return_uri = get_data(p + s, + srv_conf->return_uri_len)) == NULL) + goto fail; + s += srv_conf->return_uri_len; + } + + if (srv_conf->flags & SRVFLAG_LOCATION) { + /* Inherit configuration from the parent */ + f = SRVFLAG_INDEX|SRVFLAG_NO_INDEX; + if ((srv_conf->flags & f) == 0) { + srv_conf->flags |= parent->flags & f; + (void)strlcpy(srv_conf->index, parent->index, + sizeof(srv_conf->index)); + } + + f = SRVFLAG_AUTO_INDEX|SRVFLAG_NO_AUTO_INDEX; + if ((srv_conf->flags & f) == 0) + srv_conf->flags |= parent->flags & f; + + f = SRVFLAG_SOCKET|SRVFLAG_FCGI; + if ((srv_conf->flags & f) == SRVFLAG_FCGI) { + srv_conf->flags |= f; + (void)strlcpy(srv_conf->socket, HTTPD_FCGI_SOCKET, + sizeof(srv_conf->socket)); + } + + f = SRVFLAG_ROOT; + if ((srv_conf->flags & f) == 0) { + srv_conf->flags |= parent->flags & f; + (void)strlcpy(srv_conf->root, parent->root, + sizeof(srv_conf->root)); + } + + f = SRVFLAG_FCGI|SRVFLAG_NO_FCGI; + if ((srv_conf->flags & f) == 0) + srv_conf->flags |= parent->flags & f; + + f = SRVFLAG_LOG|SRVFLAG_NO_LOG; + if ((srv_conf->flags & f) == 0) { + srv_conf->flags |= parent->flags & f; + srv_conf->logformat = parent->logformat; + } + + f = SRVFLAG_SYSLOG|SRVFLAG_NO_SYSLOG; + if ((srv_conf->flags & f) == 0) + srv_conf->flags |= parent->flags & f; + + f = SRVFLAG_AUTH|SRVFLAG_NO_AUTH; + if ((srv_conf->flags & f) == 0) { + srv_conf->flags |= parent->flags & f; + srv_conf->auth = parent->auth; + srv_conf->auth_id = parent->auth_id; + (void)strlcpy(srv_conf->auth_realm, + parent->auth_realm, + sizeof(srv_conf->auth_realm)); + } + + f = SRVFLAG_TLS; + srv_conf->flags |= parent->flags & f; + + f = SRVFLAG_ACCESS_LOG; + if ((srv_conf->flags & f) == 0) { + srv_conf->flags |= parent->flags & f; + (void)strlcpy(srv_conf->accesslog, + parent->accesslog, + sizeof(srv_conf->accesslog)); + } + + f = SRVFLAG_ERROR_LOG; + if ((srv_conf->flags & f) == 0) { + srv_conf->flags |= parent->flags & f; + (void)strlcpy(srv_conf->errorlog, + parent->errorlog, + sizeof(srv_conf->errorlog)); + } + + f = SRVFLAG_BLOCK|SRVFLAG_NO_BLOCK; + if ((srv_conf->flags & f) == 0) { + free(srv_conf->return_uri); + srv_conf->flags |= parent->flags & f; + srv_conf->return_code = parent->return_code; + srv_conf->return_uri_len = parent->return_uri_len; + if (srv_conf->return_uri_len && + (srv_conf->return_uri = + strdup(parent->return_uri)) == NULL) + goto fail; + } + + memcpy(&srv_conf->timeout, &parent->timeout, + sizeof(srv_conf->timeout)); + srv_conf->maxrequests = parent->maxrequests; + srv_conf->maxrequestbody = parent->maxrequestbody; + + DPRINTF("%s: %s %d location \"%s\", " + "parent \"%s[%u]\", flags: %s", + __func__, ps->ps_title[privsep_process], ps->ps_instance, + srv_conf->location, parent->name, parent->id, + printb_flags(srv_conf->flags, SRVFLAG_BITS)); + } else { + /* Add a new "virtual" server */ + DPRINTF("%s: %s %d server \"%s[%u]\", parent \"%s[%u]\", " + "flags: %s", __func__, + ps->ps_title[privsep_process], ps->ps_instance, + srv_conf->name, srv_conf->id, parent->name, parent->id, + printb_flags(srv_conf->flags, SRVFLAG_BITS)); + } + + TAILQ_INSERT_TAIL(&srv->srv_hosts, srv_conf, entry); + + return (0); + + fail: + serverconfig_free(srv_conf); + free(srv_conf); + return (-1); +} + +int +config_getserver(struct httpd *env, struct imsg *imsg) +{ +#ifdef DEBUG + struct privsep *ps = env->sc_ps; +#endif + struct server *srv = NULL; + struct server_config srv_conf; + u_int8_t *p = imsg->data; + size_t s; + + IMSG_SIZE_CHECK(imsg, &srv_conf); + memcpy(&srv_conf, p, sizeof(srv_conf)); + s = sizeof(srv_conf); + + /* Reset these variables to avoid free'ing invalid pointers */ + serverconfig_reset(&srv_conf); + + if ((IMSG_DATA_SIZE(imsg) - s) < + (srv_conf.tls_cert_len + srv_conf.tls_key_len + + srv_conf.return_uri_len)) { + log_debug("%s: invalid message length", __func__); + goto fail; + } + + /* Check if server with matching listening socket already exists */ + if ((srv = server_byaddr((struct sockaddr *) + &srv_conf.ss, srv_conf.port)) != NULL) { + /* Add "host" to existing listening server */ + if (imsg->fd != -1) { + if (srv->srv_s == -1) + srv->srv_s = imsg->fd; + else + close(imsg->fd); + } + return (config_getserver_config(env, srv, imsg)); + } + + if (srv_conf.flags & SRVFLAG_LOCATION) + fatalx("invalid location"); + + /* Otherwise create a new server */ + if ((srv = calloc(1, sizeof(*srv))) == NULL) + goto fail; + + memcpy(&srv->srv_conf, &srv_conf, sizeof(srv->srv_conf)); + srv->srv_s = imsg->fd; + + if (config_getserver_auth(env, &srv->srv_conf) != 0) + goto fail; + + SPLAY_INIT(&srv->srv_clients); + TAILQ_INIT(&srv->srv_hosts); + + TAILQ_INSERT_TAIL(&srv->srv_hosts, &srv->srv_conf, entry); + TAILQ_INSERT_TAIL(env->sc_servers, srv, srv_entry); + + DPRINTF("%s: %s %d configuration \"%s[%u]\", flags: %s", __func__, + ps->ps_title[privsep_process], ps->ps_instance, + srv->srv_conf.name, srv->srv_conf.id, + printb_flags(srv->srv_conf.flags, SRVFLAG_BITS)); + + /* + * Get all variable-length values for the parent server. + */ + if (srv->srv_conf.return_uri_len != 0) { + if ((srv->srv_conf.return_uri = get_data(p + s, + srv->srv_conf.return_uri_len)) == NULL) + goto fail; + s += srv->srv_conf.return_uri_len; + } + if (srv->srv_conf.tls_cert_len != 0) { + if ((srv->srv_conf.tls_cert = get_data(p + s, + srv->srv_conf.tls_cert_len)) == NULL) + goto fail; + s += srv->srv_conf.tls_cert_len; + } + if (srv->srv_conf.tls_key_len != 0) { + if ((srv->srv_conf.tls_key = get_data(p + s, + srv->srv_conf.tls_key_len)) == NULL) + goto fail; + s += srv->srv_conf.tls_key_len; + } + + return (0); + + fail: + if (imsg->fd != -1) + close(imsg->fd); + if (srv != NULL) { + free(srv->srv_conf.tls_cert); + free(srv->srv_conf.tls_key); + } + free(srv); + + return (-1); +} + +int +config_setmedia(struct httpd *env, struct media_type *media) +{ + struct privsep *ps = env->sc_ps; + int id; + u_int what; + + for (id = 0; id < PROC_MAX; id++) { + what = ps->ps_what[id]; + + if ((what & CONFIG_MEDIA) == 0 || id == privsep_process) + continue; + + DPRINTF("%s: sending media \"%s\" to %s", __func__, + media->media_name, ps->ps_title[id]); + + proc_compose_imsg(ps, id, -1, IMSG_CFG_MEDIA, -1, + media, sizeof(*media)); + } + + return (0); +} + +int +config_getmedia(struct httpd *env, struct imsg *imsg) +{ +#ifdef DEBUG + struct privsep *ps = env->sc_ps; +#endif + struct media_type media; + u_int8_t *p = imsg->data; + + IMSG_SIZE_CHECK(imsg, &media); + memcpy(&media, p, sizeof(media)); + + if (media_add(env->sc_mediatypes, &media) == NULL) { + log_debug("%s: failed to add media \"%s\"", + __func__, media.media_name); + return (-1); + } + + DPRINTF("%s: %s %d received media \"%s\"", __func__, + ps->ps_title[privsep_process], ps->ps_instance, + media.media_name); + + return (0); +} + +int +config_setauth(struct httpd *env, struct auth *auth) +{ + struct privsep *ps = env->sc_ps; + int id; + u_int what; + + for (id = 0; id < PROC_MAX; id++) { + what = ps->ps_what[id]; + + if ((what & CONFIG_AUTH) == 0 || id == privsep_process) + continue; + + DPRINTF("%s: sending auth \"%s[%u]\" to %s", __func__, + auth->auth_htpasswd, auth->auth_id, ps->ps_title[id]); + + proc_compose_imsg(ps, id, -1, IMSG_CFG_AUTH, -1, + auth, sizeof(*auth)); + } + + return (0); +} + +int +config_getauth(struct httpd *env, struct imsg *imsg) +{ +#ifdef DEBUG + struct privsep *ps = env->sc_ps; +#endif + struct auth auth; + u_int8_t *p = imsg->data; + + IMSG_SIZE_CHECK(imsg, &auth); + memcpy(&auth, p, sizeof(auth)); + + if (auth_add(env->sc_auth, &auth) == NULL) { + log_debug("%s: failed to add auth \"%s[%u]\"", + __func__, auth.auth_htpasswd, auth.auth_id); + return (-1); + } + + DPRINTF("%s: %s %d received auth \"%s[%u]\"", __func__, + ps->ps_title[privsep_process], ps->ps_instance, + auth.auth_htpasswd, auth.auth_id); + + return (0); +} diff --git a/httpd/control.c b/httpd/control.c new file mode 100644 index 0000000..fe60375 --- /dev/null +++ b/httpd/control.c @@ -0,0 +1,334 @@ +/* $OpenBSD: control.c,v 1.6 2015/01/21 22:21:05 reyk Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/queue.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/un.h> + +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <imsg.h> + +#include "httpd.h" + +#define CONTROL_BACKLOG 5 + +struct ctl_connlist ctl_conns; + +void control_accept(int, short, void *); +void control_close(int, struct control_sock *); + +int +control_init(struct privsep *ps, struct control_sock *cs) +{ + struct httpd *env = ps->ps_env; + struct sockaddr_un sun; + int fd; + mode_t old_umask, mode; + + if (cs->cs_name == NULL) + return (0); + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + log_warn("%s: socket", __func__); + return (-1); + } + + sun.sun_family = AF_UNIX; + if (strlcpy(sun.sun_path, cs->cs_name, + sizeof(sun.sun_path)) >= sizeof(sun.sun_path)) { + log_warn("%s: %s name too long", __func__, cs->cs_name); + close(fd); + return (-1); + } + + if (unlink(cs->cs_name) == -1) + if (errno != ENOENT) { + log_warn("%s: unlink %s", __func__, cs->cs_name); + close(fd); + return (-1); + } + + if (cs->cs_restricted) { + old_umask = umask(S_IXUSR|S_IXGRP|S_IXOTH); + mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH; + } else { + old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); + mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP; + } + + if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) { + log_warn("%s: bind: %s", __func__, cs->cs_name); + close(fd); + (void)umask(old_umask); + return (-1); + } + (void)umask(old_umask); + + if (chmod(cs->cs_name, mode) == -1) { + log_warn("%s: chmod", __func__); + close(fd); + (void)unlink(cs->cs_name); + return (-1); + } + + socket_set_blockmode(fd, BM_NONBLOCK); + cs->cs_fd = fd; + cs->cs_env = env; + + return (0); +} + +int +control_listen(struct control_sock *cs) +{ + if (cs->cs_name == NULL) + return (0); + + if (listen(cs->cs_fd, CONTROL_BACKLOG) == -1) { + log_warn("%s: listen", __func__); + return (-1); + } + + event_set(&cs->cs_ev, cs->cs_fd, EV_READ, + control_accept, cs); + event_add(&cs->cs_ev, NULL); + evtimer_set(&cs->cs_evt, control_accept, cs); + + return (0); +} + +void +control_cleanup(struct control_sock *cs) +{ + if (cs->cs_name == NULL) + return; + event_del(&cs->cs_ev); + event_del(&cs->cs_evt); + (void)unlink(cs->cs_name); +} + +/* ARGSUSED */ +void +control_accept(int listenfd, short event, void *arg) +{ + int connfd; + socklen_t len; + struct sockaddr_un sun; + struct ctl_conn *c; + struct control_sock *cs = arg; + + event_add(&cs->cs_ev, NULL); + if ((event & EV_TIMEOUT)) + return; + + len = sizeof(sun); + if ((connfd = accept(listenfd, + (struct sockaddr *)&sun, &len)) == -1) { + /* + * Pause accept if we are out of file descriptors, or + * libevent will haunt us here too. + */ + if (errno == ENFILE || errno == EMFILE) { + struct timeval evtpause = { 1, 0 }; + + event_del(&cs->cs_ev); + evtimer_add(&cs->cs_evt, &evtpause); + } else if (errno != EWOULDBLOCK && errno != EINTR && + errno != ECONNABORTED) + log_warn("%s: accept", __func__); + return; + } + + socket_set_blockmode(connfd, BM_NONBLOCK); + + if ((c = calloc(1, sizeof(struct ctl_conn))) == NULL) { + close(connfd); + log_warn("%s: calloc", __func__); + return; + } + + imsg_init(&c->iev.ibuf, connfd); + c->iev.handler = control_dispatch_imsg; + c->iev.events = EV_READ; + c->iev.data = cs; /* proc.c cheats (reuses the handler) */ + event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events, + c->iev.handler, cs); + event_add(&c->iev.ev, NULL); + + TAILQ_INSERT_TAIL(&ctl_conns, c, entry); +} + +struct ctl_conn * +control_connbyfd(int fd) +{ + struct ctl_conn *c; + + for (c = TAILQ_FIRST(&ctl_conns); c != NULL && c->iev.ibuf.fd != fd; + c = TAILQ_NEXT(c, entry)) + ; /* nothing */ + + return (c); +} + +void +control_close(int fd, struct control_sock *cs) +{ + struct ctl_conn *c; + + if ((c = control_connbyfd(fd)) == NULL) { + log_warn("%s: fd %d not found", __func__, fd); + return; + } + + msgbuf_clear(&c->iev.ibuf.w); + TAILQ_REMOVE(&ctl_conns, c, entry); + + event_del(&c->iev.ev); + close(c->iev.ibuf.fd); + + /* Some file descriptors are available again. */ + if (evtimer_pending(&cs->cs_evt, NULL)) { + evtimer_del(&cs->cs_evt); + event_add(&cs->cs_ev, NULL); + } + + free(c); +} + +/* ARGSUSED */ +void +control_dispatch_imsg(int fd, short event, void *arg) +{ + struct control_sock *cs = arg; + struct ctl_conn *c; + struct imsg imsg; + int n; + int verbose; + struct httpd *env = cs->cs_env; + + if ((c = control_connbyfd(fd)) == NULL) { + log_warn("%s: fd %d not found", __func__, fd); + return; + } + + if (event & EV_READ) { + if ((n = imsg_read(&c->iev.ibuf)) == -1 || n == 0) { + control_close(fd, cs); + return; + } + } + + if (event & EV_WRITE) { + if (msgbuf_write(&c->iev.ibuf.w) <= 0 && errno != EAGAIN) { + control_close(fd, cs); + return; + } + } + + for (;;) { + if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) { + control_close(fd, cs); + return; + } + + if (n == 0) + break; + + if (c->waiting) { + log_debug("%s: unexpected imsg %d", + __func__, imsg.hdr.type); + imsg_free(&imsg); + control_close(fd, cs); + return; + } + + switch (imsg.hdr.type) { + case IMSG_CTL_SHUTDOWN: + case IMSG_CTL_RELOAD: + case IMSG_CTL_REOPEN: + proc_forward_imsg(env->sc_ps, &imsg, PROC_PARENT, -1); + break; + case IMSG_CTL_NOTIFY: + if (c->flags & CTL_CONN_NOTIFY) { + log_debug("%s: " + "client requested notify more than once", + __func__); + imsg_compose_event(&c->iev, IMSG_CTL_FAIL, + 0, 0, -1, NULL, 0); + break; + } + c->flags |= CTL_CONN_NOTIFY; + break; + case IMSG_CTL_VERBOSE: + IMSG_SIZE_CHECK(&imsg, &verbose); + + memcpy(&verbose, imsg.data, sizeof(verbose)); + + proc_forward_imsg(env->sc_ps, &imsg, PROC_PARENT, -1); + proc_forward_imsg(env->sc_ps, &imsg, PROC_SERVER, -1); + + memcpy(imsg.data, &verbose, sizeof(verbose)); + control_imsg_forward(&imsg); + log_verbose(verbose); + break; + default: + log_debug("%s: error handling imsg %d", + __func__, imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + + imsg_event_add(&c->iev); +} + +void +control_imsg_forward(struct imsg *imsg) +{ + struct ctl_conn *c; + + TAILQ_FOREACH(c, &ctl_conns, entry) + if (c->flags & CTL_CONN_NOTIFY) + imsg_compose_event(&c->iev, imsg->hdr.type, + 0, imsg->hdr.pid, -1, imsg->data, + imsg->hdr.len - IMSG_HEADER_SIZE); +} + +void +socket_set_blockmode(int fd, enum blockmodes bm) +{ + int flags; + + if ((flags = fcntl(fd, F_GETFL, 0)) == -1) + fatal("fcntl F_GETFL"); + + if (bm == BM_NONBLOCK) + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + + if ((flags = fcntl(fd, F_SETFL, flags)) == -1) + fatal("fcntl F_SETFL"); +} diff --git a/httpd/http.h b/httpd/http.h new file mode 100644 index 0000000..00cf235 --- /dev/null +++ b/httpd/http.h @@ -0,0 +1,253 @@ +/* $OpenBSD: http.h,v 1.12 2015/02/11 12:52:01 florian Exp $ */ + +/* + * Copyright (c) 2012 - 2015 Reyk Floeter <reyk@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _HTTP_H +#define _HTTP_H + +#define HTTP_PORT 80 +#define HTTPS_PORT 443 + +enum httpmethod { + HTTP_METHOD_NONE = 0, + + /* HTTP/1.1, RFC 7231 */ + HTTP_METHOD_GET, + HTTP_METHOD_HEAD, + HTTP_METHOD_POST, + HTTP_METHOD_PUT, + HTTP_METHOD_DELETE, + HTTP_METHOD_OPTIONS, + HTTP_METHOD_TRACE, + HTTP_METHOD_CONNECT, + + /* WebDAV, RFC 4918 */ + HTTP_METHOD_PROPFIND, + HTTP_METHOD_PROPPATCH, + HTTP_METHOD_MKCOL, + HTTP_METHOD_COPY, + HTTP_METHOD_MOVE, + HTTP_METHOD_LOCK, + HTTP_METHOD_UNLOCK, + + /* WebDAV Versioning Extension, RFC 3253 */ + HTTP_METHOD_VERSION_CONTROL, + HTTP_METHOD_REPORT, + HTTP_METHOD_CHECKOUT, + HTTP_METHOD_CHECKIN, + HTTP_METHOD_UNCHECKOUT, + HTTP_METHOD_MKWORKSPACE, + HTTP_METHOD_UPDATE, + HTTP_METHOD_LABEL, + HTTP_METHOD_MERGE, + HTTP_METHOD_BASELINE_CONTROL, + HTTP_METHOD_MKACTIVITY, + + /* WebDAV Ordered Collections, RFC 3648 */ + HTTP_METHOD_ORDERPATCH, + + /* WebDAV Access Control, RFC 3744 */ + HTTP_METHOD_ACL, + + /* WebDAV Redirect Reference Resources, RFC 4437 */ + HTTP_METHOD_MKREDIRECTREF, + HTTP_METHOD_UPDATEREDIRECTREF, + + /* WebDAV Search, RFC 5323 */ + HTTP_METHOD_SEARCH, + + /* PATCH, RFC 5789 */ + HTTP_METHOD_PATCH, + + /* Server response (internal value) */ + HTTP_METHOD_RESPONSE +}; + +struct http_method { + enum httpmethod method_id; + const char *method_name; +}; +#define HTTP_METHODS { \ + { HTTP_METHOD_GET, "GET" }, \ + { HTTP_METHOD_HEAD, "HEAD" }, \ + { HTTP_METHOD_POST, "POST" }, \ + { HTTP_METHOD_PUT, "PUT" }, \ + { HTTP_METHOD_DELETE, "DELETE" }, \ + { HTTP_METHOD_OPTIONS, "OPTIONS" }, \ + { HTTP_METHOD_TRACE, "TRACE" }, \ + { HTTP_METHOD_CONNECT, "CONNECT" }, \ + { HTTP_METHOD_PROPFIND, "PROPFIND" }, \ + { HTTP_METHOD_PROPPATCH, "PROPPATCH" }, \ + { HTTP_METHOD_MKCOL, "MKCOL" }, \ + { HTTP_METHOD_COPY, "COPY" }, \ + { HTTP_METHOD_MOVE, "MOVE" }, \ + { HTTP_METHOD_LOCK, "LOCK" }, \ + { HTTP_METHOD_UNLOCK, "UNLOCK" }, \ + { HTTP_METHOD_VERSION_CONTROL, "VERSION-CONTROL" }, \ + { HTTP_METHOD_REPORT, "REPORT" }, \ + { HTTP_METHOD_CHECKOUT, "CHECKOUT" }, \ + { HTTP_METHOD_CHECKIN, "CHECKIN" }, \ + { HTTP_METHOD_UNCHECKOUT, "UNCHECKOUT" }, \ + { HTTP_METHOD_MKWORKSPACE, "MKWORKSPACE" }, \ + { HTTP_METHOD_UPDATE, "UPDATE" }, \ + { HTTP_METHOD_LABEL, "LABEL" }, \ + { HTTP_METHOD_MERGE, "MERGE" }, \ + { HTTP_METHOD_BASELINE_CONTROL, "BASELINE-CONTROL" }, \ + { HTTP_METHOD_MKACTIVITY, "MKACTIVITY" }, \ + { HTTP_METHOD_ORDERPATCH, "ORDERPATCH" }, \ + { HTTP_METHOD_ACL, "ACL" }, \ + { HTTP_METHOD_MKREDIRECTREF, "MKREDIRECTREF" }, \ + { HTTP_METHOD_UPDATEREDIRECTREF, "UPDATEREDIRECTREF" }, \ + { HTTP_METHOD_SEARCH, "SEARCH" }, \ + { HTTP_METHOD_PATCH, "PATCH" }, \ + { HTTP_METHOD_NONE, NULL } \ +} + +struct http_error { + int error_code; + const char *error_name; +}; + +/* + * HTTP status codes based on IANA assignments (2014-06-11 version): + * https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml + * plus legacy (306) and non-standard (420). + */ +#define HTTP_ERRORS { \ + { 100, "Continue" }, \ + { 101, "Switching Protocols" }, \ + { 102, "Processing" }, \ + /* 103-199 unassigned */ \ + { 200, "OK" }, \ + { 201, "Created" }, \ + { 202, "Accepted" }, \ + { 203, "Non-Authoritative Information" }, \ + { 204, "No Content" }, \ + { 205, "Reset Content" }, \ + { 206, "Partial Content" }, \ + { 207, "Multi-Status" }, \ + { 208, "Already Reported" }, \ + /* 209-225 unassigned */ \ + { 226, "IM Used" }, \ + /* 227-299 unassigned */ \ + { 300, "Multiple Choices" }, \ + { 301, "Moved Permanently" }, \ + { 302, "Found" }, \ + { 303, "See Other" }, \ + { 304, "Not Modified" }, \ + { 305, "Use Proxy" }, \ + { 306, "Switch Proxy" }, \ + { 307, "Temporary Redirect" }, \ + { 308, "Permanent Redirect" }, \ + /* 309-399 unassigned */ \ + { 400, "Bad Request" }, \ + { 401, "Unauthorized" }, \ + { 402, "Payment Required" }, \ + { 403, "Forbidden" }, \ + { 404, "Not Found" }, \ + { 405, "Method Not Allowed" }, \ + { 406, "Not Acceptable" }, \ + { 407, "Proxy Authentication Required" }, \ + { 408, "Request Timeout" }, \ + { 409, "Conflict" }, \ + { 410, "Gone" }, \ + { 411, "Length Required" }, \ + { 412, "Precondition Failed" }, \ + { 413, "Payload Too Large" }, \ + { 414, "URI Too Long" }, \ + { 415, "Unsupported Media Type" }, \ + { 416, "Range Not Satisfiable" }, \ + { 417, "Expectation Failed" }, \ + { 418, "I'm a teapot" }, \ + /* 419-421 unassigned */ \ + { 420, "Enhance Your Calm" }, \ + { 422, "Unprocessable Entity" }, \ + { 423, "Locked" }, \ + { 424, "Failed Dependency" }, \ + /* 425 unassigned */ \ + { 426, "Upgrade Required" }, \ + /* 427 unassigned */ \ + { 428, "Precondition Required" }, \ + { 429, "Too Many Requests" }, \ + /* 430 unassigned */ \ + { 431, "Request Header Fields Too Large" }, \ + /* 432-450 unassigned */ \ + { 451, "Unavailable For Legal Reasons" }, \ + /* 452-499 unassigned */ \ + { 500, "Internal Server Error" }, \ + { 501, "Not Implemented" }, \ + { 502, "Bad Gateway" }, \ + { 503, "Service Unavailable" }, \ + { 504, "Gateway Timeout" }, \ + { 505, "HTTP Version Not Supported" }, \ + { 506, "Variant Also Negotiates" }, \ + { 507, "Insufficient Storage" }, \ + { 508, "Loop Detected" }, \ + /* 509 unassigned */ \ + { 510, "Not Extended" }, \ + { 511, "Network Authentication Required" }, \ + /* 512-599 unassigned */ \ + { 0, NULL } \ +} + +struct http_mediatype { + char *media_name; + char *media_type; + char *media_subtype; +}; +/* + * Some default media types based on (2014-08-04 version): + * https://www.iana.org/assignments/media-types/media-types.xhtml + */ +#define MEDIA_TYPES { \ + { "css", "text", "css" }, \ + { "html", "text", "html" }, \ + { "txt", "text", "plain" }, \ + { "gif", "image", "gif" }, \ + { "jpeg", "image", "jpeg" }, \ + { "jpg", "image", "jpeg" }, \ + { "png", "image", "png" }, \ + { "svg", "image", "svg+xml" }, \ + { "js", "application", "javascript" }, \ + { NULL } \ +} + +/* Used during runtime */ +struct http_descriptor { + struct kv http_pathquery; + struct kv http_matchquery; +#define http_path http_pathquery.kv_key +#define http_query http_pathquery.kv_value +#define http_rescode http_pathquery.kv_key +#define http_resmesg http_pathquery.kv_value +#define query_key http_matchquery.kv_key +#define query_val http_matchquery.kv_value + + char *http_host; + enum httpmethod http_method; + int http_chunked; + char *http_version; + + /* Rewritten path remains NULL if not used */ + char *http_path_alias; + + /* A tree of headers and attached lists for repeated headers. */ + struct kv *http_lastheader; + struct kvtree http_headers; +}; + +#endif /* _HTTP_H */ diff --git a/httpd/httpd.8 b/httpd/httpd.8 new file mode 100644 index 0000000..eb35096 --- /dev/null +++ b/httpd/httpd.8 @@ -0,0 +1,102 @@ +.\" $OpenBSD: httpd.8,v 1.50 2015/02/24 07:56:06 bentley Exp $ +.\" +.\" Copyright (c) 2014 Reyk Floeter <reyk@openbsd.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: February 24 2015 $ +.Dt HTTPD 8 +.Os +.Sh NAME +.Nm httpd +.Nd HTTP daemon +.Sh SYNOPSIS +.Nm +.Op Fl dnv +.Op Fl D Ar macro Ns = Ns Ar value +.Op Fl f Ar file +.Sh DESCRIPTION +The +.Nm +daemon is an HTTP server with FastCGI and TLS support. +.Pp +The FastCGI implementation has optional socket support. +.Nm +can log to +.Xr syslog 3 +or per-server files with several standard formats. +.Pp +.Nm +rereads its configuration file when it receives +.Dv SIGHUP +and reopens log files when it receives +.Dv SIGUSR1 . +.Pp +The options are as follows: +.Bl -tag -width Dssmacro=value +.It Fl D Ar macro Ns = Ns Ar value +Set a +.Ar macro +to a +.Ar value . +Macros can be referenced in the configuration files. +.It Fl d +Debug mode. +Create one server and don't detach or become a daemon. +This allows for easy monitoring of +.Nm . +.It Fl f Ar file +Specifies the configuration file. +The default is +.Pa /etc/httpd.conf . +.It Fl n +Check that the configuration is valid, but don't start any servers. +.It Fl v +Verbose mode. +Multiple +.Fl v +options increases the verbosity. +.El +.Sh FILES +.Bl -tag -width "/etc/ssl/private/server.key" -compact +.It Pa /etc/httpd.conf +Default configuration file. +.It Pa /etc/ssl/private/server.key +Default SSL/TLS server key. +.It Pa /etc/ssl/server.crt +Default SSL/TLS server certificate. +.It Pa /var/run/httpd.sock +.Ux Ns -domain +socket used for communication with +.Nm . +.It Pa /var/www/logs/access.log +Default access log file. +.It Pa /var/www/logs/error.log +Default error log file. +.El +.Sh SEE ALSO +.Xr httpd.conf 5 +.Sh HISTORY +The +.Nm +program first appeared in +.Ox 5.6 . +.Nm +is based on +.Xr relayd 8 . +.Sh AUTHORS +.An -nosplit +The +.Nm +program was written by +.An Reyk Floeter Aq Mt reyk@openbsd.org . diff --git a/httpd/httpd.c b/httpd/httpd.c new file mode 100644 index 0000000..c6f183f --- /dev/null +++ b/httpd/httpd.c @@ -0,0 +1,1281 @@ +/* $OpenBSD: httpd.c,v 1.35 2015/02/23 18:43:18 reyk Exp $ */ + +/* + * Copyright (c) 2014 Reyk Floeter <reyk@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/param.h> /* nitems */ +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/resource.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <signal.h> +#include <getopt.h> +#include <fnmatch.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <unistd.h> +#include <ctype.h> +#include <pwd.h> + +#include "httpd.h" + +#define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) + +__dead void usage(void); + +int parent_configure(struct httpd *); +void parent_configure_done(struct httpd *); +void parent_reload(struct httpd *, u_int, const char *); +void parent_reopen(struct httpd *); +void parent_sig_handler(int, short, void *); +void parent_shutdown(struct httpd *); +int parent_dispatch_server(int, struct privsep_proc *, + struct imsg *); +int parent_dispatch_logger(int, struct privsep_proc *, + struct imsg *); + +struct httpd *httpd_env; + +static struct privsep_proc procs[] = { + { "server", PROC_SERVER, parent_dispatch_server, server }, + { "logger", PROC_LOGGER, parent_dispatch_logger, logger } +}; + +void +parent_sig_handler(int sig, short event, void *arg) +{ + struct privsep *ps = arg; + int die = 0, status, fail, id; + pid_t pid; + char *cause; + + switch (sig) { + case SIGTERM: + case SIGINT: + die = 1; + /* FALLTHROUGH */ + case SIGCHLD: + do { + pid = waitpid(WAIT_ANY, &status, WNOHANG); + if (pid <= 0) + continue; + + fail = 0; + if (WIFSIGNALED(status)) { + fail = 1; + asprintf(&cause, "terminated; signal %d", + WTERMSIG(status)); + } else if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != 0) { + fail = 1; + asprintf(&cause, "exited abnormally"); + } else + asprintf(&cause, "exited okay"); + } else + fatalx("unexpected cause of SIGCHLD"); + + die = 1; + + for (id = 0; id < PROC_MAX; id++) + if (pid == ps->ps_pid[id]) { + if (fail) + log_warnx("lost child: %s %s", + ps->ps_title[id], cause); + break; + } + + free(cause); + } while (pid > 0 || (pid == -1 && errno == EINTR)); + + if (die) + parent_shutdown(ps->ps_env); + break; + case SIGHUP: + log_info("%s: reload requested with SIGHUP", __func__); + + /* + * This is safe because libevent uses async signal handlers + * that run in the event loop and not in signal context. + */ + parent_reload(ps->ps_env, CONFIG_RELOAD, NULL); + break; + case SIGPIPE: + /* ignore */ + break; + case SIGUSR1: + log_info("%s: reopen requested with SIGUSR1", __func__); + + parent_reopen(ps->ps_env); + break; + default: + fatalx("unexpected signal"); + } +} + +__dead void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s [-dnv] [-D macro=value] [-f file]\n", + __progname); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + int c; + unsigned int proc; + int debug = 0, verbose = 0; + u_int32_t opts = 0; + struct httpd *env; + struct privsep *ps; + const char *conffile = CONF_FILE; + + while ((c = getopt(argc, argv, "dD:nf:v")) != -1) { + switch (c) { + case 'd': + debug = 2; + break; + case 'D': + if (cmdline_symset(optarg) < 0) + log_warnx("could not parse macro definition %s", + optarg); + break; + case 'n': + debug = 2; + opts |= HTTPD_OPT_NOACTION; + break; + case 'f': + conffile = optarg; + break; + case 'v': + verbose++; + opts |= HTTPD_OPT_VERBOSE; + break; + default: + usage(); + } + } + + log_init(debug ? debug : 1); /* log to stderr until daemonized */ + + argc -= optind; + if (argc > 0) + usage(); + + if ((env = calloc(1, sizeof(*env))) == NULL || + (ps = calloc(1, sizeof(*ps))) == NULL) + exit(1); + + httpd_env = env; + env->sc_ps = ps; + ps->ps_env = env; + TAILQ_INIT(&ps->ps_rcsocks); + env->sc_conffile = conffile; + env->sc_opts = opts; + + if (parse_config(env->sc_conffile, env) == -1) + exit(1); + + if (geteuid()) + errx(1, "need root privileges"); + + if ((ps->ps_pw = getpwnam(HTTPD_USER)) == NULL) + errx(1, "unknown user %s", HTTPD_USER); + + /* Configure the control socket */ + ps->ps_csock.cs_name = HTTPD_SOCKET; + + log_init(debug); + log_verbose(verbose); + + if (!debug && daemon(1, 0) == -1) + err(1, "failed to daemonize"); + + if (env->sc_opts & HTTPD_OPT_NOACTION) + ps->ps_noaction = 1; + else + log_info("startup"); + + ps->ps_instances[PROC_SERVER] = env->sc_prefork_server; + ps->ps_ninstances = env->sc_prefork_server; + + if (env->sc_chroot == NULL) + env->sc_chroot = ps->ps_pw->pw_dir; + for (proc = 0; proc < nitems(procs); proc++) + procs[proc].p_chroot = env->sc_chroot; + + if (env->sc_logdir == NULL) { + if (asprintf(&env->sc_logdir, "%s%s", env->sc_chroot, + HTTPD_LOGROOT) == -1) + errx(1, "malloc failed"); + } + + proc_init(ps, procs, nitems(procs)); + + setproctitle("parent"); + + event_init(); + + signal_set(&ps->ps_evsigint, SIGINT, parent_sig_handler, ps); + signal_set(&ps->ps_evsigterm, SIGTERM, parent_sig_handler, ps); + signal_set(&ps->ps_evsigchld, SIGCHLD, parent_sig_handler, ps); + signal_set(&ps->ps_evsighup, SIGHUP, parent_sig_handler, ps); + signal_set(&ps->ps_evsigpipe, SIGPIPE, parent_sig_handler, ps); + signal_set(&ps->ps_evsigusr1, SIGUSR1, parent_sig_handler, ps); + + signal_add(&ps->ps_evsigint, NULL); + signal_add(&ps->ps_evsigterm, NULL); + signal_add(&ps->ps_evsigchld, NULL); + signal_add(&ps->ps_evsighup, NULL); + signal_add(&ps->ps_evsigpipe, NULL); + signal_add(&ps->ps_evsigusr1, NULL); + + proc_listen(ps, procs, nitems(procs)); + + if (load_config(env->sc_conffile, env) == -1) { + proc_kill(env->sc_ps); + exit(1); + } + + if (env->sc_opts & HTTPD_OPT_NOACTION) { + fprintf(stderr, "configuration OK\n"); + proc_kill(env->sc_ps); + exit(0); + } + + if (parent_configure(env) == -1) + fatalx("configuration failed"); + + event_dispatch(); + + parent_shutdown(env); + /* NOTREACHED */ + + return (0); +} + +int +parent_configure(struct httpd *env) +{ + int id; + struct ctl_flags cf; + int ret = -1; + struct server *srv; + struct media_type *media; + struct auth *auth; + + RB_FOREACH(media, mediatypes, env->sc_mediatypes) { + if (config_setmedia(env, media) == -1) + fatal("send media"); + } + + TAILQ_FOREACH(auth, env->sc_auth, auth_entry) { + if (config_setauth(env, auth) == -1) + fatal("send auth"); + } + + /* First send the servers... */ + TAILQ_FOREACH(srv, env->sc_servers, srv_entry) { + if (srv->srv_conf.flags & SRVFLAG_LOCATION) + continue; + if (config_setserver(env, srv) == -1) + fatal("send server"); + } + /* ...and now send the locations */ + TAILQ_FOREACH(srv, env->sc_servers, srv_entry) { + if ((srv->srv_conf.flags & SRVFLAG_LOCATION) == 0) + continue; + if (config_setserver(env, srv) == -1) + fatal("send location"); + } + + /* The servers need to reload their config. */ + env->sc_reload = env->sc_prefork_server + 1; + + for (id = 0; id < PROC_MAX; id++) { + if (id == privsep_process) + continue; + cf.cf_opts = env->sc_opts; + cf.cf_flags = env->sc_flags; + + proc_compose_imsg(env->sc_ps, id, -1, IMSG_CFG_DONE, -1, + &cf, sizeof(cf)); + } + + ret = 0; + + config_purge(env, CONFIG_ALL); + return (ret); +} + +void +parent_reload(struct httpd *env, u_int reset, const char *filename) +{ + if (env->sc_reload) { + log_debug("%s: already in progress: %d pending", + __func__, env->sc_reload); + return; + } + + /* Switch back to the default config file */ + if (filename == NULL || *filename == '\0') + filename = env->sc_conffile; + + log_debug("%s: level %d config file %s", __func__, reset, filename); + + config_purge(env, CONFIG_ALL); + + if (reset == CONFIG_RELOAD) { + if (load_config(filename, env) == -1) { + log_debug("%s: failed to load config file %s", + __func__, filename); + } + + config_setreset(env, CONFIG_ALL); + + if (parent_configure(env) == -1) { + log_debug("%s: failed to commit config from %s", + __func__, filename); + } + } else + config_setreset(env, reset); +} + +void +parent_reopen(struct httpd *env) +{ + proc_compose_imsg(env->sc_ps, PROC_LOGGER, -1, IMSG_CTL_REOPEN, + -1, NULL, 0); +} + +void +parent_configure_done(struct httpd *env) +{ + int id; + + if (env->sc_reload == 0) { + log_warnx("%s: configuration already finished", __func__); + return; + } + + env->sc_reload--; + if (env->sc_reload == 0) { + for (id = 0; id < PROC_MAX; id++) { + if (id == privsep_process) + continue; + + proc_compose_imsg(env->sc_ps, id, -1, IMSG_CTL_START, + -1, NULL, 0); + } + } +} + +void +parent_shutdown(struct httpd *env) +{ + config_purge(env, CONFIG_ALL); + + proc_kill(env->sc_ps); + control_cleanup(&env->sc_ps->ps_csock); + + free(env->sc_ps); + free(env); + + log_info("parent terminating, pid %d", getpid()); + + exit(0); +} + +int +parent_dispatch_server(int fd, struct privsep_proc *p, struct imsg *imsg) +{ + struct httpd *env = p->p_env; + + switch (imsg->hdr.type) { + case IMSG_CFG_DONE: + parent_configure_done(env); + break; + default: + return (-1); + } + + return (0); +} + +int +parent_dispatch_logger(int fd, struct privsep_proc *p, struct imsg *imsg) +{ + struct httpd *env = p->p_env; + u_int v; + char *str = NULL; + + switch (imsg->hdr.type) { + case IMSG_CTL_RESET: + IMSG_SIZE_CHECK(imsg, &v); + memcpy(&v, imsg->data, sizeof(v)); + parent_reload(env, v, NULL); + break; + case IMSG_CTL_RELOAD: + if (IMSG_DATA_SIZE(imsg) > 0) + str = get_string(imsg->data, IMSG_DATA_SIZE(imsg)); + parent_reload(env, CONFIG_RELOAD, str); + if (str != NULL) + free(str); + break; + case IMSG_CTL_SHUTDOWN: + parent_shutdown(env); + break; + case IMSG_CTL_REOPEN: + parent_reopen(env); + break; + case IMSG_CFG_DONE: + parent_configure_done(env); + break; + case IMSG_LOG_OPEN: + if (logger_open_priv(imsg) == -1) + fatalx("failed to open log file"); + break; + default: + return (-1); + } + + return (0); +} + +/* + * Utility functions + */ + +void +event_again(struct event *ev, int fd, short event, + void (*fn)(int, short, void *), + struct timeval *start, struct timeval *end, void *arg) +{ + struct timeval tv_next, tv_now, tv; + + getmonotime(&tv_now); + memcpy(&tv_next, end, sizeof(tv_next)); + timersub(&tv_now, start, &tv_now); + timersub(&tv_next, &tv_now, &tv_next); + + memset(&tv, 0, sizeof(tv)); + if (timercmp(&tv_next, &tv, >)) + memcpy(&tv, &tv_next, sizeof(tv)); + + event_del(ev); + event_set(ev, fd, event, fn, arg); + event_add(ev, &tv); +} + +int +expand_string(char *label, size_t len, const char *srch, const char *repl) +{ + char *tmp; + char *p, *q; + + if ((tmp = calloc(1, len)) == NULL) { + log_debug("%s: calloc", __func__); + return (-1); + } + p = q = label; + while ((q = strstr(p, srch)) != NULL) { + *q = '\0'; + if ((strlcat(tmp, p, len) >= len) || + (strlcat(tmp, repl, len) >= len)) { + log_debug("%s: string too long", __func__); + free(tmp); + return (-1); + } + q += strlen(srch); + p = q; + } + if (strlcat(tmp, p, len) >= len) { + log_debug("%s: string too long", __func__); + free(tmp); + return (-1); + } + (void)strlcpy(label, tmp, len); /* always fits */ + free(tmp); + + return (0); +} + +const char * +canonicalize_host(const char *host, char *name, size_t len) +{ + struct sockaddr_in sin4; + struct sockaddr_in6 sin6; + size_t i, j; + size_t plen; + char c; + + if (len < 2) + goto fail; + + /* + * Canonicalize an IPv4/6 address + */ + if (inet_pton(AF_INET, host, &sin4) == 1) + return (inet_ntop(AF_INET, &sin4, name, len)); + if (inet_pton(AF_INET6, host, &sin6) == 1) + return (inet_ntop(AF_INET6, &sin6, name, len)); + + /* + * Canonicalize a hostname + */ + + /* 1. remove repeated dots and convert upper case to lower case */ + plen = strlen(host); + memset(name, 0, len); + for (i = j = 0; i < plen; i++) { + if (j >= (len - 1)) + goto fail; + c = tolower(host[i]); + if ((c == '.') && (j == 0 || name[j - 1] == '.')) + continue; + name[j++] = c; + } + + /* 2. remove trailing dots */ + for (i = j; i > 0; i--) { + if (name[i - 1] != '.') + break; + name[i - 1] = '\0'; + j--; + } + if (j <= 0) + goto fail; + + return (name); + + fail: + errno = EINVAL; + return (NULL); +} + +const char * +url_decode(char *url) +{ + char *p, *q; + char hex[3]; + u_long x; + + hex[2] = '\0'; + p = q = url; + + while (*p != '\0') { + switch (*p) { + case '%': + /* Encoding character is followed by two hex chars */ + if (!(isxdigit(p[1]) && isxdigit(p[2]))) + return (NULL); + + hex[0] = p[1]; + hex[1] = p[2]; + + /* + * We don't have to validate "hex" because it is + * guaranteed to include two hex chars followed by nul. + */ + x = strtoul(hex, NULL, 16); + *q = (char)x; + p += 2; + break; + default: + *q = *p; + break; + } + p++; + q++; + } + *q = '\0'; + + return (url); +} + +const char * +canonicalize_path(const char *input, char *path, size_t len) +{ + const char *i; + char *p, *start, *end; + + /* assuming input starts with '/' and is nul-terminated */ + i = input; + p = path; + + if (*input != '/' || len < 3) + return (NULL); + + start = p; + end = p + (len - 1); + + while (*i != '\0') { + /* Detect truncation */ + if (p >= end) + return (NULL); + + /* 1. check for special path elements */ + if (i[0] == '/') { + if (i[1] == '/') { + /* a) skip repeating '//' slashes */ + while (i[1] == '/') + i++; + continue; + } else if (i[1] == '.' && i[2] == '.' && + (i[3] == '/' || i[3] == '\0')) { + /* b) revert '..' to previous directory */ + i += 3; + while (p > start && *p != '/') + p--; + *p = '\0'; + continue; + } else if (i[1] == '.' && + (i[2] == '/' || i[2] == '\0')) { + /* c) skip unnecessary '.' current dir */ + i += 2; + continue; + } + } + + /* 2. copy any other characters */ + *p++ = *i; + i++; + } + if (p == start) + *p++ = '/'; + *p++ = '\0'; + + return (path); +} + +size_t +path_info(char *path) +{ + char *p, *start, *end, ch; + struct stat st; + int ret; + + start = path; + end = start + strlen(path); + + for (p = end; p > start; p--) { + /* Scan every path component from the end and at each '/' */ + if (p < end && *p != '/') + continue; + + /* Temporarily cut the path component out */ + ch = *p; + *p = '\0'; + ret = stat(path, &st); + *p = ch; + + /* Break if the initial path component was found */ + if (ret == 0) + break; + } + + return (p - start); +} + +char * +url_encode(const char *src) +{ + static char hex[] = "0123456789ABCDEF"; + char *dp, *dst; + unsigned char c; + + /* We need 3 times the memory if every letter is encoded. */ + if ((dst = calloc(3, strlen(src) + 1)) == NULL) + return (NULL); + + for (dp = dst; *src != 0; src++) { + c = (unsigned char) *src; + if (c == ' ' || c == '#' || c == '%' || c == '?' || c == '"' || + c == '&' || c == '<' || c <= 0x1f || c >= 0x7f) { + *dp++ = '%'; + *dp++ = hex[c >> 4]; + *dp++ = hex[c & 0x0f]; + } else + *dp++ = *src; + } + return (dst); +} + +char* +escape_html(const char* src) +{ + char *dp, *dst; + + /* We need 5 times the memory if every letter is "<" or ">". */ + if ((dst = calloc(5, strlen(src) + 1)) == NULL) + return NULL; + + for (dp = dst; *src != 0; src++) { + if (*src == '<') { + *dp++ = '&'; + *dp++ = 'l'; + *dp++ = 't'; + *dp++ = ';'; + } else if (*src == '>') { + *dp++ = '&'; + *dp++ = 'g'; + *dp++ = 't'; + *dp++ = ';'; + } else if (*src == '&') { + *dp++ = '&'; + *dp++ = 'a'; + *dp++ = 'm'; + *dp++ = 'p'; + *dp++ = ';'; + } else + *dp++ = *src; + } + return (dst); +} + +void +socket_rlimit(int maxfd) +{ + struct rlimit rl; + + if (getrlimit(RLIMIT_NOFILE, &rl) == -1) + fatal("socket_rlimit: failed to get resource limit"); + log_debug("%s: max open files %llu", __func__, rl.rlim_max); + + /* + * Allow the maximum number of open file descriptors for this + * login class (which should be the class "daemon" by default). + */ + if (maxfd == -1) + rl.rlim_cur = rl.rlim_max; + else + rl.rlim_cur = MAXIMUM(rl.rlim_max, (rlim_t)maxfd); + if (setrlimit(RLIMIT_NOFILE, &rl) == -1) + fatal("socket_rlimit: failed to set resource limit"); +} + +char * +evbuffer_getline(struct evbuffer *evb) +{ + u_int8_t *ptr = EVBUFFER_DATA(evb); + size_t len = EVBUFFER_LENGTH(evb); + char *str; + size_t i; + + /* Safe version of evbuffer_readline() */ + if ((str = get_string(ptr, len)) == NULL) + return (NULL); + + for (i = 0; str[i] != '\0'; i++) { + if (str[i] == '\r' || str[i] == '\n') + break; + } + + if (i == len) { + free(str); + return (NULL); + } + + str[i] = '\0'; + + if ((i + 1) < len) { + if (ptr[i] == '\r' && ptr[i + 1] == '\n') + i++; + } + + evbuffer_drain(evb, ++i); + + return (str); +} + +char * +get_string(u_int8_t *ptr, size_t len) +{ + size_t i; + char *str; + + for (i = 0; i < len; i++) + if (!(isprint(ptr[i]) || isspace(ptr[i]))) + break; + + if ((str = calloc(1, i + 1)) == NULL) + return (NULL); + memcpy(str, ptr, i); + + return (str); +} + +void * +get_data(u_int8_t *ptr, size_t len) +{ + u_int8_t *data; + + if ((data = calloc(1, len)) == NULL) + return (NULL); + memcpy(data, ptr, len); + + return (data); +} + +int +sockaddr_cmp(struct sockaddr *a, struct sockaddr *b, int prefixlen) +{ + struct sockaddr_in *a4, *b4; + struct sockaddr_in6 *a6, *b6; + u_int32_t av[4], bv[4], mv[4]; + + if (a->sa_family == AF_UNSPEC || b->sa_family == AF_UNSPEC) + return (0); + else if (a->sa_family > b->sa_family) + return (1); + else if (a->sa_family < b->sa_family) + return (-1); + + if (prefixlen == -1) + memset(&mv, 0xff, sizeof(mv)); + + switch (a->sa_family) { + case AF_INET: + a4 = (struct sockaddr_in *)a; + b4 = (struct sockaddr_in *)b; + + av[0] = a4->sin_addr.s_addr; + bv[0] = b4->sin_addr.s_addr; + if (prefixlen != -1) + mv[0] = prefixlen2mask(prefixlen); + + if ((av[0] & mv[0]) > (bv[0] & mv[0])) + return (1); + if ((av[0] & mv[0]) < (bv[0] & mv[0])) + return (-1); + break; + case AF_INET6: + a6 = (struct sockaddr_in6 *)a; + b6 = (struct sockaddr_in6 *)b; + + memcpy(&av, &a6->sin6_addr.s6_addr, 16); + memcpy(&bv, &b6->sin6_addr.s6_addr, 16); + if (prefixlen != -1) + prefixlen2mask6(prefixlen, mv); + + if ((av[3] & mv[3]) > (bv[3] & mv[3])) + return (1); + if ((av[3] & mv[3]) < (bv[3] & mv[3])) + return (-1); + if ((av[2] & mv[2]) > (bv[2] & mv[2])) + return (1); + if ((av[2] & mv[2]) < (bv[2] & mv[2])) + return (-1); + if ((av[1] & mv[1]) > (bv[1] & mv[1])) + return (1); + if ((av[1] & mv[1]) < (bv[1] & mv[1])) + return (-1); + if ((av[0] & mv[0]) > (bv[0] & mv[0])) + return (1); + if ((av[0] & mv[0]) < (bv[0] & mv[0])) + return (-1); + break; + } + + return (0); +} + +u_int32_t +prefixlen2mask(u_int8_t prefixlen) +{ + if (prefixlen == 0) + return (0); + + if (prefixlen > 32) + prefixlen = 32; + + return (htonl(0xffffffff << (32 - prefixlen))); +} + +struct in6_addr * +prefixlen2mask6(u_int8_t prefixlen, u_int32_t *mask) +{ + static struct in6_addr s6; + int i; + + if (prefixlen > 128) + prefixlen = 128; + + memset(&s6, 0, sizeof(s6)); + for (i = 0; i < prefixlen / 8; i++) + s6.s6_addr[i] = 0xff; + i = prefixlen % 8; + if (i) + s6.s6_addr[prefixlen / 8] = 0xff00 >> i; + + memcpy(mask, &s6, sizeof(s6)); + + return (&s6); +} + +int +accept_reserve(int sockfd, struct sockaddr *addr, socklen_t *addrlen, + int reserve, volatile int *counter) +{ + int ret; + if (getdtablecount() + reserve + + *counter >= getdtablesize()) { + errno = EMFILE; + return (-1); + } + + if ((ret = accept(sockfd, addr, addrlen)) > -1) { + (*counter)++; + DPRINTF("%s: inflight incremented, now %d",__func__, *counter); + } + return (ret); +} + +struct kv * +kv_add(struct kvtree *keys, char *key, char *value) +{ + struct kv *kv, *oldkv; + + if (key == NULL) + return (NULL); + if ((kv = calloc(1, sizeof(*kv))) == NULL) + return (NULL); + if ((kv->kv_key = strdup(key)) == NULL) { + free(kv); + return (NULL); + } + if (value != NULL && + (kv->kv_value = strdup(value)) == NULL) { + free(kv->kv_key); + free(kv); + return (NULL); + } + TAILQ_INIT(&kv->kv_children); + + if ((oldkv = RB_INSERT(kvtree, keys, kv)) != NULL) { + TAILQ_INSERT_TAIL(&oldkv->kv_children, kv, kv_entry); + kv->kv_parent = oldkv; + } + + return (kv); +} + +int +kv_set(struct kv *kv, char *fmt, ...) +{ + va_list ap; + char *value = NULL; + struct kv *ckv; + + va_start(ap, fmt); + if (vasprintf(&value, fmt, ap) == -1) + return (-1); + va_end(ap); + + /* Remove all children */ + while ((ckv = TAILQ_FIRST(&kv->kv_children)) != NULL) { + TAILQ_REMOVE(&kv->kv_children, ckv, kv_entry); + kv_free(ckv); + free(ckv); + } + + /* Set the new value */ + if (kv->kv_value != NULL) + free(kv->kv_value); + kv->kv_value = value; + + return (0); +} + +int +kv_setkey(struct kv *kv, char *fmt, ...) +{ + va_list ap; + char *key = NULL; + + va_start(ap, fmt); + if (vasprintf(&key, fmt, ap) == -1) + return (-1); + va_end(ap); + + if (kv->kv_key != NULL) + free(kv->kv_key); + kv->kv_key = key; + + return (0); +} + +void +kv_delete(struct kvtree *keys, struct kv *kv) +{ + struct kv *ckv; + + RB_REMOVE(kvtree, keys, kv); + + /* Remove all children */ + while ((ckv = TAILQ_FIRST(&kv->kv_children)) != NULL) { + TAILQ_REMOVE(&kv->kv_children, ckv, kv_entry); + kv_free(ckv); + free(ckv); + } + + kv_free(kv); + free(kv); +} + +struct kv * +kv_extend(struct kvtree *keys, struct kv *kv, char *value) +{ + char *newvalue; + + if (kv == NULL) { + return (NULL); + } else if (kv->kv_value != NULL) { + if (asprintf(&newvalue, "%s%s", kv->kv_value, value) == -1) + return (NULL); + + free(kv->kv_value); + kv->kv_value = newvalue; + } else if ((kv->kv_value = strdup(value)) == NULL) + return (NULL); + + return (kv); +} + +void +kv_purge(struct kvtree *keys) +{ + struct kv *kv; + + while ((kv = RB_MIN(kvtree, keys)) != NULL) + kv_delete(keys, kv); +} + +void +kv_free(struct kv *kv) +{ + if (kv->kv_key != NULL) { + free(kv->kv_key); + } + kv->kv_key = NULL; + if (kv->kv_value != NULL) { + free(kv->kv_value); + } + kv->kv_value = NULL; + memset(kv, 0, sizeof(*kv)); +} + +struct kv * +kv_inherit(struct kv *dst, struct kv *src) +{ + memset(dst, 0, sizeof(*dst)); + memcpy(dst, src, sizeof(*dst)); + TAILQ_INIT(&dst->kv_children); + + if (src->kv_key != NULL) { + if ((dst->kv_key = strdup(src->kv_key)) == NULL) { + kv_free(dst); + return (NULL); + } + } + if (src->kv_value != NULL) { + if ((dst->kv_value = strdup(src->kv_value)) == NULL) { + kv_free(dst); + return (NULL); + } + } + + return (dst); +} + +int +kv_log(struct evbuffer *log, struct kv *kv) +{ + char *msg; + + if (log == NULL) + return (0); + if (asprintf(&msg, " [%s%s%s]", + kv->kv_key == NULL ? "(unknown)" : kv->kv_key, + kv->kv_value == NULL ? "" : ": ", + kv->kv_value == NULL ? "" : kv->kv_value) == -1) + return (-1); + if (evbuffer_add(log, msg, strlen(msg)) == -1) { + free(msg); + return (-1); + } + free(msg); + + return (0); +} + +struct kv * +kv_find(struct kvtree *keys, struct kv *kv) +{ + struct kv *match; + const char *key; + + if (kv->kv_flags & KV_FLAG_GLOBBING) { + /* Test header key using shell globbing rules */ + key = kv->kv_key == NULL ? "" : kv->kv_key; + RB_FOREACH(match, kvtree, keys) { + if (fnmatch(key, match->kv_key, FNM_CASEFOLD) == 0) + break; + } + } else { + /* Fast tree-based lookup only works without globbing */ + match = RB_FIND(kvtree, keys, kv); + } + + return (match); +} + +int +kv_cmp(struct kv *a, struct kv *b) +{ + return (strcasecmp(a->kv_key, b->kv_key)); +} + +RB_GENERATE(kvtree, kv, kv_node, kv_cmp); + +struct media_type * +media_add(struct mediatypes *types, struct media_type *media) +{ + struct media_type *entry; + + if ((entry = RB_FIND(mediatypes, types, media)) != NULL) { + log_debug("%s: duplicated entry for \"%s\"", __func__, + media->media_name); + return (NULL); + } + + if ((entry = malloc(sizeof(*media))) == NULL) + return (NULL); + + memcpy(entry, media, sizeof(*entry)); + if (media->media_encoding != NULL && + (entry->media_encoding = strdup(media->media_encoding)) == NULL) { + free(entry); + return (NULL); + } + RB_INSERT(mediatypes, types, entry); + + return (entry); +} + +void +media_delete(struct mediatypes *types, struct media_type *media) +{ + RB_REMOVE(mediatypes, types, media); + if (media->media_encoding != NULL) + free(media->media_encoding); + free(media); +} + +void +media_purge(struct mediatypes *types) +{ + struct media_type *media; + + while ((media = RB_MIN(mediatypes, types)) != NULL) + media_delete(types, media); +} + +struct media_type * +media_find(struct mediatypes *types, char *file) +{ + struct media_type *match, media; + char *p; + + /* Last component of the file name */ + p = strchr(file, '\0'); + while (p > file && p[-1] != '.' && p[-1] != '/') + p--; + if (*p == '\0') + return (NULL); + + if (strlcpy(media.media_name, p, + sizeof(media.media_name)) >= + sizeof(media.media_name)) { + return (NULL); + } + + /* Find media type by extension name */ + match = RB_FIND(mediatypes, types, &media); + + return (match); +} + +int +media_cmp(struct media_type *a, struct media_type *b) +{ + return (strcasecmp(a->media_name, b->media_name)); +} + +RB_GENERATE(mediatypes, media_type, media_entry, media_cmp); + +struct auth * +auth_add(struct serverauth *serverauth, struct auth *auth) +{ + struct auth *entry; + + TAILQ_FOREACH(entry, serverauth, auth_entry) { + if (strcmp(entry->auth_htpasswd, auth->auth_htpasswd) == 0) + return (entry); + } + + if ((entry = calloc(1, sizeof(*entry))) == NULL) + return (NULL); + + memcpy(entry, auth, sizeof(*entry)); + + TAILQ_INSERT_TAIL(serverauth, entry, auth_entry); + + return (entry); +} + +struct auth * +auth_byid(struct serverauth *serverauth, u_int32_t id) +{ + struct auth *auth; + + TAILQ_FOREACH(auth, serverauth, auth_entry) { + if (auth->auth_id == id) + return (auth); + } + + return (NULL); +} + +void +auth_free(struct serverauth *serverauth, struct auth *auth) +{ + TAILQ_REMOVE(serverauth, auth, auth_entry); +} diff --git a/httpd/httpd.conf.5 b/httpd/httpd.conf.5 new file mode 100644 index 0000000..acfb57a --- /dev/null +++ b/httpd/httpd.conf.5 @@ -0,0 +1,533 @@ +.\" $OpenBSD: httpd.conf.5,v 1.54 2015/03/06 05:10:18 reyk Exp $ +.\" +.\" Copyright (c) 2014, 2015 Reyk Floeter <reyk@openbsd.org> +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: March 6 2015 $ +.Dt HTTPD.CONF 5 +.Os +.Sh NAME +.Nm httpd.conf +.Nd HTTP daemon configuration file +.Sh DESCRIPTION +.Nm +is the configuration file for the HTTP daemon, +.Xr httpd 8 . +.Sh SECTIONS +.Nm +is divided into four main sections: +.Bl -tag -width xxxx +.It Sy Macros +User-defined variables may be defined and used later, simplifying the +configuration file. +.It Sy Global Configuration +Global settings for +.Xr httpd 8 . +.It Sy Servers +Listening HTTP web servers. +.It Sy Types +Media types and extensions. +.El +.Pp +Within the sections, +a host +.Ar address +can be specified by IPv4 address, IPv6 address, interface name, +interface group, or DNS hostname. +If the address is an interface name, +.Xr httpd 8 +will look up the first IPv4 address and any other IPv4 and IPv6 +addresses of the specified network interface. +If +.Sq * +is given as an address, +it will be used as an alias for +.Ar 0.0.0.0 +to listen on all IPv4 addresses. +Likewise, +.Sq :: +can be used to listen on all IPv6 addresses. +A +.Ar port +can be specified by number or name. +The port name to number mappings are found in the file +.Pa /etc/services ; +see +.Xr services 5 +for details. +.Pp +The current line can be extended over multiple lines using a backslash +.Pq Sq \e . +Comments can be put anywhere in the file using a hash mark +.Pq Sq # , +and extend to the end of the current line. +Care should be taken when commenting out multi-line text: +the comment is effective until the end of the entire block. +.Pp +Argument names not beginning with a letter, digit, or underscore +must be quoted. +.Pp +Additional configuration files can be included with the +.Ic include +keyword, for example: +.Bd -literal -offset indent +include "/etc/httpd.conf.local" +.Ed +.Sh MACROS +Macros can be defined that will later be expanded in context. +Macro names must start with a letter, digit, or underscore, +and may contain any of those characters. +Macro names may not be reserved words (for example, +.Ic directory , +.Ic log , +or +.Ic root ) . +Macros are not expanded inside quotes. +.Pp +For example: +.Bd -literal -offset indent +ext_ip="10.0.0.1" +server "default" { + listen on $ext_ip port 80 +} +.Ed +.Sh GLOBAL CONFIGURATION +Here are the settings that can be set globally: +.Bl -tag -width Ds +.It Ic chroot Ar directory +Set the +.Xr chroot 2 +directory. +If not specified, it defaults to +.Pa /var/www , +the home directory of the www user. +.It Ic logdir Ar directory +Specifies the full path of the directory in which log files will be written. +If not specified, it defaults to +.Pa /logs +within the +.Xr chroot 2 +directory. +.It Ic prefork Ar number +Run the specified number of server processes. +This increases the performance and prevents delays when connecting +to a server. +.Xr httpd 8 +runs 3 server processes by default. +.El +.Sh SERVERS +The configured web servers. +.Pp +Each +.Ic server +must have a +.Ar name +and include one or more lines of the following syntax: +.Bl -tag -width Ds +.It Ic alias Ar name +Specify an additional alias +.Ar name +for this server. +.It Oo Ic no Oc Ic authenticate Oo Ar realm Oc Ic with Pa htpasswd +Authenticate a remote user for +.Ar realm +by checking the credentials against the user authentication file +.Pa htpasswd . +The file name is relative to the +.Ic chroot +and must be readable by the www user. +Use the +.Ic no authenticate +directive to disable authentication in a location. +.It Ic block drop +Drop the connection without sending an error page. +.It Ic block Op Ic return Ar code Op uri +Close the connection and send an error page. +If the optional return code is not specified, +.Xr httpd 8 +denies access with a +.Sq 403 Forbidden +response. +The optional +.Ar uri +argument can be used with return codes in the 3xx range to send a +.Sq Location: +header for redirection to a specified URI. +.Pp +The +.Ar url +may contain predefined macros that will be expanded at runtime: +.Pp +.Bl -tag -width $DOCUMENT_URI -offset indent -compact +.It Ic $DOCUMENT_URI +The request path. +.It Ic $QUERY_STRING +The optional query string of the request. +.It Ic $REMOTE_ADDR +The IP address of the connected client. +.It Ic $REMOTE_PORT +The TCP source port of the connected client. +.It Ic $REMOTE_USER +The remote user for HTTP authentication. +.It Ic $REQUEST_URI +The request path and optional query string. +.It Ic $SERVER_ADDR +The configured IP address of the server. +.It Ic $SERVER_PORT +The configured TCP server port of the server. +.It Ic $SERVER_NAME +The name of the server. +.El +.It Ic connection Ar option +Set the specified options and limits for HTTP connections. +Valid options are: +.Bl -tag -width Ds +.It Ic max request body Ar number +Set the maximum body size in bytes that the client can send to the server. +The default value is 1048576 bytes (1M). +.It Ic max requests Ar number +Set the maximum number of requests per persistent HTTP connection. +Persistent connections are negotiated using the Keep-Alive header in +HTTP/1.0 and enabled by default in HTTP/1.1. +The default maximum number of requests per connection is 100. +.It Ic timeout Ar seconds +Specify the inactivity timeout in seconds for accepted sessions. +The default timeout is 600 seconds (10 minutes). +The maximum is 2147483647 seconds (68 years). +.El +.It Ic directory Ar option +Set the specified options when serving or accessing directories. +Valid options are: +.Bl -tag -width Ds +.It Oo Ic no Oc Ic auto index +If no index file is found, automatically generate a directory listing. +This is disabled by default. +.It Ic index Ar string +Set the directory index file. +If not specified, it defaults to +.Pa index.html . +.It Ic no index +Disable the directory index. +.Xr httpd 8 +will neither display nor generate a directory index. +.El +.It Oo Ic no Oc Ic fastcgi Op Ic socket Ar socket +Enable FastCGI instead of serving files. +The +.Ar socket +is a local path name within the +.Xr chroot 2 +root directory of +.Xr httpd 8 +and defaults to +.Pa /run/slowcgi.sock . +.It Ic listen on Ar address Oo Ic tls Oc Ic port Ar number +Set the listen address and port. +This statement can be specified multiple times. +.It Ic location Ar path Brq ... +Specify server configuration rules for a specific location. +The +.Ar path +argument will be matched against the request path with shell globbing rules. +A location section may include most of the server configuration rules +except +.Ic connection , +.Ic listen on , +.Ic location +and +.Ic tcp . +.It Oo Ic no Oc Ic log Op Ar option +Set the specified logging options. +Logging is enabled by default using the standard +.Ic access +and +.Ic error +log files, +but can be changed per server or location. +Use the +.Ic no log +directive to disable logging of any requests. +Valid options are: +.Bl -tag -width Ds +.It Ic access Ar name +Set the +.Ar name +of the access log file relative to the log directory. +If not specified, it defaults to +.Pa access.log . +.It Ic error Ar name +Set the +.Ar name +of the error log file relative to the log directory. +If not specified, it defaults to +.Pa error.log . +.It Ic style Ar style +Set the logging style. +The +.Ar style +can be +.Cm common , +.Cm combined +or +.Cm connection . +The styles +.Cm common +and +.Cm combined +write a log entry after each request similar to the standard Apache +and nginx access log formats. +The style +.Cm connection +writes a summarized log entry after each connection, +that can have multiple requests, +similar to the format that is used by +.Xr relayd 8 . +If not specified, the default is +.Cm common . +.It Oo Ic no Oc Ic syslog +Enable or disable logging to +.Xr syslog 3 +instead of the log files. +.El +.It Ic pass +Disable any previous +.Ic block +in a location. +.It Ic root Ar option +Configure the document root and options for the request path. +Valid options are: +.Bl -tag -width Ds +.It Ar directory +Set the document root of the server. +The +.Ar directory +is a pathname within the +.Xr chroot 2 +root directory of +.Nm httpd . +If not specified, it defaults to +.Pa /htdocs . +.It Ic strip Ar number +Strip +.Ar number +path components from the beginning of the request path before looking +up the stripped-down path at the document root. +.El +.It Ic tcp Ar option +Enable or disable the specified TCP/IP options; see +.Xr tcp 4 +and +.Xr ip 4 +for more information about the options. +Valid options are: +.Bl -tag -width Ds +.It Ic backlog Ar number +Set the maximum length the queue of pending connections may grow to. +The backlog option is 10 by default and is limited by the +.Va kern.somaxconn +.Xr sysctl 8 +variable. +.It Ic ip minttl Ar number +This option for the underlying IP connection may be used to discard packets +with a TTL lower than the specified value. +This can be used to implement the +Generalized TTL Security Mechanism (GTSM) +according to RFC 5082. +.It Ic ip ttl Ar number +Change the default time-to-live value in the IP headers. +.It Oo Ic no Oc Ic nodelay +Enable the TCP NODELAY option for this connection. +This is recommended to avoid delays in the data stream. +.It Oo Ic no Oc Ic sack +Use selective acknowledgements for this connection. +.It Ic socket buffer Ar number +Set the socket-level buffer size for input and output for this +connection. +This will affect the TCP window size. +.El +.It Ic tls Ar option +Set the TLS configuration for the server. +These options are only used if TLS has been enabled via the listen directive. +Valid options are: +.Bl -tag -width Ds +.It Ic certificate Ar file +Specify the certificate to use for this server. +The +.Ar file +should contain a PEM encoded certificate. +.It Ic ciphers Ar string +Specify the TLS cipher string. +If not specified, the default value +.Qq HIGH:!aNULL +will be used (strong crypto cipher suites without anonymous DH). +See the CIPHERS section of +.Xr openssl 1 +for information about SSL/TLS cipher suites and preference lists. +.It Ic dhe Ar params +Specify the DHE parameters to use for DHE cipher suites. +Valid parameter values are none, legacy and auto. +For legacy a fixed key length of 1024 bits is used, whereas for auto the key +length is determined automatically. +The default is none, which disables DHE cipher suites. +.It Ic ecdhe Ar curve +Specify the ECDHE curve to use for ECDHE cipher suites. +Valid parameter values are none, auto and the short name of any known curve. +The default is auto. +.It Ic key Ar file +Specify the private key to use for this server. +The +.Ar file +should contain a PEM encoded private key and reside outside of the +.Xr chroot 2 +root directory of +.Nm httpd . +.It Ic protocols Ar string +Specify the TLS protocols to enable for this server. +If not specified, the default value +.Qq all +will be used (all available protocols). +Refer to the +.Xr tls_config_parse_protocols 3 +function for other valid protocol string values. +.El +.El +.Sh TYPES +Configure the supported media types. +.Xr httpd 8 +will set the +.Ar Content-Type +of the response header based on the file extension listed in the +.Ic types +section. +If not specified, +.Xr httpd 8 +will use built-in media types for +.Ar text/css , +.Ar text/html , +.Ar text/plain , +.Ar image/gif , +.Ar image/png , +.Ar image/jpeg , +and +.Ar application/javascript . +.Pp +The +.Ic types +section must include one or more lines of the following syntax: +.Bl -tag -width Ds +.It Ar type/subtype Ar name Op Ar name ... +Set the media +.Ar type +and +.Ar subtype +to the specified extension +.Ar name . +One or more names can be specified per line. +Each line may end with an optional semicolon. +.It Ic include Ar file +Include types definitions from an external file, for example +.Pa /usr/share/misc/mime.types . +.El +.Sh EXAMPLES +The following example will start one server that is pre-forked two +times and is listening on all local IP addresses. +It additionally defines some media types overriding the defaults. +.Bd -literal -offset indent +prefork 2 + +server "default" { + listen on * port 80 +} + +types { + text/css css + text/html htm html + text/txt txt + image/gif gif + image/jpeg jpg jpeg + image/png png + application/javascript js + application/xml xml +} +.Ed +.Pp +The server can also be configured to only listen on the primary IP +address of the network interface that is a member of the +.Qq egress +group. +.Bd -literal -offset indent +server "default" { + listen on egress port 80 +} +.Ed +.Pp +Multiple servers can be configured to support hosting of different domains. +If the same address is repeated multiple times in the +.Ic listen on +statement, +the server will be matched based on the requested host name. +.Bd -literal -offset indent +server "www.example.com" { + alias "example.com" + listen on * port 80 + listen on * tls port 443 + root "/htdocs/www.example.com" +} + +server "www.a.example.com" { + listen on 203.0.113.1 port 80 + root "/htdocs/www.a.example.com" +} + +server "www.b.example.com" { + listen on 203.0.113.1 port 80 + root "/htdocs/www.b.example.com" +} + +server "intranet.example.com" { + listen on 10.0.0.1 port 80 + root "/htdocs/intranet.example.com" +} +.Ed +.Pp +Simple redirections can be configured with the +.Ic block +directive: +.Bd -literal -offset indent +server "example.com" { + listen on 10.0.0.1 port 80 + block return 301 "http://www.example.com/" +} + +server "www.example.com" { + listen on 10.0.0.1 port 80 +} +.Ed +.Pp +The syntax of the types section is also compatible with the format used by nginx, +so it is possible to include its +.Pa mime.types +file directly: +.Bd -literal -offset indent +include "/etc/nginx/mime.types" +.Ed +.Sh SEE ALSO +.Xr htpasswd 1 , +.Xr httpd 8 +.Sh AUTHORS +.An -nosplit +The +.Xr httpd 8 +program was written by +.An Reyk Floeter Aq Mt reyk@openbsd.org . diff --git a/httpd/httpd.h b/httpd/httpd.h new file mode 100644 index 0000000..28e02a5 --- /dev/null +++ b/httpd/httpd.h @@ -0,0 +1,688 @@ +/* $OpenBSD: httpd.h,v 1.81 2015/02/23 18:43:18 reyk Exp $ */ + +/* + * Copyright (c) 2006 - 2015 Reyk Floeter <reyk@openbsd.org> + * Copyright (c) 2006, 2007 Pierre-Yves Ritschard <pyr@openbsd.org> + * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _HTTPD_H +#define _HTTPD_H + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/time.h> + +#include <net/if.h> + +#include <stdarg.h> +#include <limits.h> +#include <event.h> +#include <imsg.h> +#include <tls.h> + +#define CONF_FILE "/etc/httpd.conf" +#define HTTPD_SOCKET "/var/run/httpd.sock" +#define HTTPD_USER "www" +#define HTTPD_SERVERNAME "OpenBSD httpd" +#define HTTPD_DOCROOT "/htdocs" +#define HTTPD_INDEX "index.html" +#define HTTPD_FCGI_SOCKET "/run/slowcgi.sock" +#define HTTPD_LOGROOT "/logs" +#define HTTPD_ACCESS_LOG "access.log" +#define HTTPD_ERROR_LOG "error.log" +#define HTTPD_TLS_CERT "/etc/ssl/server.crt" +#define HTTPD_TLS_KEY "/etc/ssl/private/server.key" +#define HTTPD_TLS_CIPHERS "HIGH:!aNULL" +#define HTTPD_TLS_DHE_PARAMS "none" +#define HTTPD_TLS_ECDHE_CURVE "auto" +#define FD_RESERVE 5 + +#define SERVER_MAX_CLIENTS 1024 +#define SERVER_TIMEOUT 600 +#define SERVER_CACHESIZE -1 /* use default size */ +#define SERVER_NUMPROC 3 +#define SERVER_MAXPROC 32 +#define SERVER_MAXHEADERLENGTH 8192 +#define SERVER_MAXREQUESTS 100 /* max requests per connection */ +#define SERVER_MAXREQUESTBODY 1048576 /* 1M */ +#define SERVER_BACKLOG 10 +#define SERVER_OUTOF_FD_RETRIES 5 + +#define MEDIATYPE_NAMEMAX 128 /* file name extension */ +#define MEDIATYPE_TYPEMAX 64 /* length of type/subtype */ + +#define CONFIG_RELOAD 0x00 +#define CONFIG_MEDIA 0x01 +#define CONFIG_SERVERS 0x02 +#define CONFIG_AUTH 0x04 +#define CONFIG_ALL 0xff + +#define FCGI_CONTENT_SIZE 65535 + +enum httpchunk { + TOREAD_UNLIMITED = -1, + TOREAD_HTTP_HEADER = -2, + TOREAD_HTTP_CHUNK_LENGTH = -3, + TOREAD_HTTP_CHUNK_TRAILER = -4, + TOREAD_HTTP_NONE = -5 +}; + +#if DEBUG +#define DPRINTF log_debug +#else +#define DPRINTF(x...) do {} while(0) +#endif + +struct ctl_flags { + u_int8_t cf_opts; + u_int32_t cf_flags; +}; + +enum key_type { + KEY_TYPE_NONE = 0, + KEY_TYPE_COOKIE, + KEY_TYPE_HEADER, + KEY_TYPE_PATH, + KEY_TYPE_QUERY, + KEY_TYPE_URL, + KEY_TYPE_MAX +}; + +TAILQ_HEAD(kvlist, kv); +RB_HEAD(kvtree, kv); + +struct kv { + char *kv_key; + char *kv_value; + + enum key_type kv_type; + +#define KV_FLAG_INVALID 0x01 +#define KV_FLAG_GLOBBING 0x02 + u_int8_t kv_flags; + + struct kvlist kv_children; + struct kv *kv_parent; + TAILQ_ENTRY(kv) kv_entry; + + RB_ENTRY(kv) kv_node; +}; + +struct portrange { + in_port_t val[2]; + u_int8_t op; +}; + +struct address { + struct sockaddr_storage ss; + int ipproto; + int prefixlen; + struct portrange port; + char ifname[IFNAMSIZ]; + TAILQ_ENTRY(address) entry; +}; +TAILQ_HEAD(addresslist, address); + +/* initially control.h */ +struct control_sock { + const char *cs_name; + struct event cs_ev; + struct event cs_evt; + int cs_fd; + int cs_restricted; + void *cs_env; + + TAILQ_ENTRY(control_sock) cs_entry; +}; +TAILQ_HEAD(control_socks, control_sock); + +struct { + struct event ev; + int fd; +} control_state; + +enum blockmodes { + BM_NORMAL, + BM_NONBLOCK +}; + +struct imsgev { + struct imsgbuf ibuf; + void (*handler)(int, short, void *); + struct event ev; + struct privsep_proc *proc; + void *data; + short events; +}; + +#define IMSG_SIZE_CHECK(imsg, p) do { \ + if (IMSG_DATA_SIZE(imsg) < sizeof(*p)) \ + fatalx("bad length imsg received"); \ +} while (0) +#define IMSG_DATA_SIZE(imsg) ((imsg)->hdr.len - IMSG_HEADER_SIZE) + +struct ctl_conn { + TAILQ_ENTRY(ctl_conn) entry; + u_int8_t flags; + u_int waiting; +#define CTL_CONN_NOTIFY 0x01 + struct imsgev iev; + +}; +TAILQ_HEAD(ctl_connlist, ctl_conn); + +enum imsg_type { + IMSG_NONE, + IMSG_CTL_OK, + IMSG_CTL_FAIL, + IMSG_CTL_VERBOSE, + IMSG_CTL_RESET, + IMSG_CTL_SHUTDOWN, + IMSG_CTL_RELOAD, + IMSG_CTL_NOTIFY, + IMSG_CTL_END, + IMSG_CTL_START, + IMSG_CTL_REOPEN, + IMSG_CFG_SERVER, + IMSG_CFG_MEDIA, + IMSG_CFG_AUTH, + IMSG_CFG_DONE, + IMSG_LOG_ACCESS, + IMSG_LOG_ERROR, + IMSG_LOG_OPEN +}; + +enum privsep_procid { + PROC_ALL = -1, + PROC_PARENT = 0, + PROC_SERVER, + PROC_LOGGER, + PROC_MAX +} privsep_process; + +/* Attach the control socket to the following process */ +#define PROC_CONTROL PROC_LOGGER + +struct privsep_pipes { + int *pp_pipes[PROC_MAX]; +}; + +struct privsep { + struct privsep_pipes *ps_pipes[PROC_MAX]; + struct privsep_pipes *ps_pp; + + struct imsgev *ps_ievs[PROC_MAX]; + const char *ps_title[PROC_MAX]; + pid_t ps_pid[PROC_MAX]; + u_int8_t ps_what[PROC_MAX]; + + u_int ps_instances[PROC_MAX]; + u_int ps_ninstances; + u_int ps_instance; + + struct control_sock ps_csock; + struct control_socks ps_rcsocks; + + /* Event and signal handlers */ + struct event ps_evsigint; + struct event ps_evsigterm; + struct event ps_evsigchld; + struct event ps_evsighup; + struct event ps_evsigpipe; + struct event ps_evsigusr1; + + int ps_noaction; + struct passwd *ps_pw; + struct httpd *ps_env; +}; + +struct privsep_proc { + const char *p_title; + enum privsep_procid p_id; + int (*p_cb)(int, struct privsep_proc *, + struct imsg *); + pid_t (*p_init)(struct privsep *, + struct privsep_proc *); + void (*p_shutdown)(void); + u_int p_instance; + const char *p_chroot; + struct privsep *p_ps; + struct httpd *p_env; +}; + +enum fcgistate { + FCGI_READ_HEADER, + FCGI_READ_CONTENT, + FCGI_READ_PADDING +}; + +struct client { + u_int32_t clt_id; + pid_t clt_pid; + void *clt_srv; + void *clt_srv_conf; + u_int32_t clt_srv_id; + struct sockaddr_storage clt_srv_ss; + + int clt_s; + in_port_t clt_port; + struct sockaddr_storage clt_ss; + struct bufferevent *clt_bev; + char *clt_buf; + size_t clt_buflen; + struct evbuffer *clt_output; + struct event clt_ev; + void *clt_descreq; + void *clt_descresp; + int clt_sndbufsiz; + + int clt_fd; + struct tls *clt_tls_ctx; + struct bufferevent *clt_srvbev; + + off_t clt_toread; + size_t clt_headerlen; + u_int clt_persist; + int clt_line; + int clt_done; + int clt_chunk; + int clt_inflight; + enum fcgistate clt_fcgi_state; + int clt_fcgi_toread; + int clt_fcgi_padding_len; + int clt_fcgi_type; + int clt_fcgi_chunked; + int clt_fcgi_end; + char *clt_remote_user; + struct evbuffer *clt_srvevb; + + struct evbuffer *clt_log; + struct timeval clt_timeout; + struct timeval clt_tv_start; + struct timeval clt_tv_last; + struct event clt_inflightevt; + + SPLAY_ENTRY(client) clt_nodes; +}; +SPLAY_HEAD(client_tree, client); + +#define SRVFLAG_INDEX 0x00000001 +#define SRVFLAG_NO_INDEX 0x00000002 +#define SRVFLAG_AUTO_INDEX 0x00000004 +#define SRVFLAG_NO_AUTO_INDEX 0x00000008 +#define SRVFLAG_ROOT 0x00000010 +#define SRVFLAG_LOCATION 0x00000020 +#define SRVFLAG_FCGI 0x00000040 +#define SRVFLAG_NO_FCGI 0x00000080 +#define SRVFLAG_LOG 0x00000100 +#define SRVFLAG_NO_LOG 0x00000200 +#define SRVFLAG_SOCKET 0x00000400 +#define SRVFLAG_SYSLOG 0x00000800 +#define SRVFLAG_NO_SYSLOG 0x00001000 +#define SRVFLAG_TLS 0x00002000 +#define SRVFLAG_ACCESS_LOG 0x00004000 +#define SRVFLAG_ERROR_LOG 0x00008000 +#define SRVFLAG_AUTH 0x00010000 +#define SRVFLAG_NO_AUTH 0x00020000 +#define SRVFLAG_BLOCK 0x00040000 +#define SRVFLAG_NO_BLOCK 0x00080000 + +#define SRVFLAG_BITS \ + "\10\01INDEX\02NO_INDEX\03AUTO_INDEX\04NO_AUTO_INDEX" \ + "\05ROOT\06LOCATION\07FCGI\10NO_FCGI\11LOG\12NO_LOG\13SOCKET" \ + "\14SYSLOG\15NO_SYSLOG\16TLS\17ACCESS_LOG\20ERROR_LOG" \ + "\21AUTH\22NO_AUTH\23BLOCK\24NO_BLOCK" + +#define TCPFLAG_NODELAY 0x01 +#define TCPFLAG_NNODELAY 0x02 +#define TCPFLAG_SACK 0x04 +#define TCPFLAG_NSACK 0x08 +#define TCPFLAG_BUFSIZ 0x10 +#define TCPFLAG_IPTTL 0x20 +#define TCPFLAG_IPMINTTL 0x40 +#define TCPFLAG_NSPLICE 0x80 +#define TCPFLAG_DEFAULT 0x00 + +#define TCPFLAG_BITS \ + "\10\01NODELAY\02NO_NODELAY\03SACK\04NO_SACK" \ + "\05SOCKET_BUFFER_SIZE\06IP_TTL\07IP_MINTTL\10NO_SPLICE" + +enum log_format { + LOG_FORMAT_COMMON, + LOG_FORMAT_COMBINED, + LOG_FORMAT_CONNECTION +}; + +struct log_file { + char log_name[NAME_MAX]; + int log_fd; + u_int32_t log_id; + TAILQ_ENTRY(log_file) log_entry; +}; +TAILQ_HEAD(log_files, log_file) log_files; + +struct auth { + char auth_htpasswd[PATH_MAX]; + u_int32_t auth_id; + TAILQ_ENTRY(auth) auth_entry; +}; +TAILQ_HEAD(serverauth, auth); + +struct server_config { + u_int32_t id; + u_int32_t parent_id; + char name[HOST_NAME_MAX+1]; + char location[NAME_MAX]; + char index[NAME_MAX]; + char root[PATH_MAX]; + char socket[PATH_MAX]; + char accesslog[NAME_MAX]; + char errorlog[NAME_MAX]; + + in_port_t port; + struct sockaddr_storage ss; + int prefixlen; + struct timeval timeout; + u_int32_t maxrequests; + size_t maxrequestbody; + + u_int8_t *tls_cert; + size_t tls_cert_len; + char *tls_cert_file; + char tls_ciphers[NAME_MAX]; + char tls_dhe_params[NAME_MAX]; + char tls_ecdhe_curve[NAME_MAX]; + u_int8_t *tls_key; + size_t tls_key_len; + char *tls_key_file; + u_int32_t tls_protocols; + + u_int32_t flags; + int strip; + u_int8_t tcpflags; + int tcpbufsiz; + int tcpbacklog; + u_int8_t tcpipttl; + u_int8_t tcpipminttl; + + enum log_format logformat; + struct log_file *logaccess; + struct log_file *logerror; + + char auth_realm[NAME_MAX]; + u_int32_t auth_id; + struct auth *auth; + + int return_code; + char *return_uri; + off_t return_uri_len; + + TAILQ_ENTRY(server_config) entry; +}; +TAILQ_HEAD(serverhosts, server_config); + +struct server { + TAILQ_ENTRY(server) srv_entry; + struct server_config srv_conf; + struct serverhosts srv_hosts; + + int srv_s; + struct event srv_ev; + struct event srv_evt; + + struct tls *srv_tls_ctx; + struct tls_config *srv_tls_config; + + struct client_tree srv_clients; +}; +TAILQ_HEAD(serverlist, server); + +struct media_type { + char media_name[MEDIATYPE_NAMEMAX]; + char media_type[MEDIATYPE_TYPEMAX]; + char media_subtype[MEDIATYPE_TYPEMAX]; + char *media_encoding; + RB_ENTRY(media_type) media_entry; +}; +RB_HEAD(mediatypes, media_type); + +struct httpd { + u_int8_t sc_opts; + u_int32_t sc_flags; + const char *sc_conffile; + struct event sc_ev; + u_int16_t sc_prefork_server; + u_int16_t sc_id; + int sc_paused; + char *sc_chroot; + char *sc_logdir; + + struct serverlist *sc_servers; + struct mediatypes *sc_mediatypes; + struct serverauth *sc_auth; + + struct privsep *sc_ps; + int sc_reload; +}; + +#define HTTPD_OPT_VERBOSE 0x01 +#define HTTPD_OPT_NOACTION 0x04 + +/* control.c */ +int control_init(struct privsep *, struct control_sock *); +int control_listen(struct control_sock *); +void control_cleanup(struct control_sock *); +void control_dispatch_imsg(int, short, void *); +void control_imsg_forward(struct imsg *); +struct ctl_conn * + control_connbyfd(int); +void socket_set_blockmode(int, enum blockmodes); + +extern struct ctl_connlist ctl_conns; + +/* parse.y */ +int parse_config(const char *, struct httpd *); +int load_config(const char *, struct httpd *); +int cmdline_symset(char *); + +/* server.c */ +pid_t server(struct privsep *, struct privsep_proc *); +int server_tls_load_keypair(struct server *); +int server_privinit(struct server *); +void server_purge(struct server *); +void serverconfig_free(struct server_config *); +void serverconfig_reset(struct server_config *); +int server_socket_af(struct sockaddr_storage *, in_port_t); +in_port_t + server_socket_getport(struct sockaddr_storage *); +int server_socket_connect(struct sockaddr_storage *, in_port_t, + struct server_config *); +void server_write(struct bufferevent *, void *); +void server_read(struct bufferevent *, void *); +void server_error(struct bufferevent *, short, void *); +void server_log(struct client *, const char *); +void server_sendlog(struct server_config *, int, const char *, ...) + __attribute__((__format__ (printf, 3, 4))); +void server_close(struct client *, const char *); +void server_dump(struct client *, const void *, size_t); +int server_client_cmp(struct client *, struct client *); +int server_bufferevent_printf(struct client *, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +int server_bufferevent_print(struct client *, const char *); +int server_bufferevent_write_buffer(struct client *, + struct evbuffer *); +int server_bufferevent_write_chunk(struct client *, + struct evbuffer *, size_t); +int server_bufferevent_add(struct event *, int); +int server_bufferevent_write(struct client *, void *, size_t); +void server_inflight_dec(struct client *, const char *); +struct server * + server_byaddr(struct sockaddr *, in_port_t); +struct server_config * + serverconfig_byid(u_int32_t); +int server_foreach(int (*)(struct server *, + struct server_config *, void *), void *); + +SPLAY_PROTOTYPE(client_tree, client, clt_nodes, server_client_cmp); + +/* server_http.c */ +void server_http_init(struct server *); +void server_http(struct httpd *); +int server_httpdesc_init(struct client *); +void server_read_http(struct bufferevent *, void *); +void server_abort_http(struct client *, u_int, const char *); +u_int server_httpmethod_byname(const char *); +const char + *server_httpmethod_byid(u_int); +const char + *server_httperror_byid(u_int); +void server_read_httpcontent(struct bufferevent *, void *); +void server_read_httpchunks(struct bufferevent *, void *); +int server_writeheader_http(struct client *clt, struct kv *, void *); +int server_headers(struct client *, void *, + int (*)(struct client *, struct kv *, void *), void *); +int server_writeresponse_http(struct client *); +int server_response_http(struct client *, u_int, struct media_type *, + size_t, time_t); +void server_reset_http(struct client *); +void server_close_http(struct client *); +int server_response(struct httpd *, struct client *); +const char * + server_root_strip(const char *, int); +struct server_config * + server_getlocation(struct client *, const char *); +const char * + server_http_host(struct sockaddr_storage *, char *, size_t); +char *server_http_parsehost(char *, char *, size_t, int *); +ssize_t server_http_time(time_t, char *, size_t); +int server_log_http(struct client *, u_int, size_t); + +/* server_file.c */ +int server_file(struct httpd *, struct client *); +void server_file_error(struct bufferevent *, short, void *); + +/* server_fcgi.c */ +int server_fcgi(struct httpd *, struct client *); +int fcgi_add_stdin(struct client *, struct evbuffer *); + +/* httpd.c */ +void event_again(struct event *, int, short, + void (*)(int, short, void *), + struct timeval *, struct timeval *, void *); +int expand_string(char *, size_t, const char *, const char *); +const char *url_decode(char *); +char *url_encode(const char *); +const char *canonicalize_host(const char *, char *, size_t); +const char *canonicalize_path(const char *, char *, size_t); +size_t path_info(char *); +char *escape_html(const char *); +void imsg_event_add(struct imsgev *); +int imsg_compose_event(struct imsgev *, u_int16_t, u_int32_t, + pid_t, int, void *, u_int16_t); +void socket_rlimit(int); +char *evbuffer_getline(struct evbuffer *); +char *get_string(u_int8_t *, size_t); +void *get_data(u_int8_t *, size_t); +int sockaddr_cmp(struct sockaddr *, struct sockaddr *, int); +struct in6_addr *prefixlen2mask6(u_int8_t, u_int32_t *); +u_int32_t prefixlen2mask(u_int8_t); +int accept_reserve(int, struct sockaddr *, socklen_t *, int, + volatile int *); +struct kv *kv_add(struct kvtree *, char *, char *); +int kv_set(struct kv *, char *, ...); +int kv_setkey(struct kv *, char *, ...); +void kv_delete(struct kvtree *, struct kv *); +struct kv *kv_extend(struct kvtree *, struct kv *, char *); +void kv_purge(struct kvtree *); +void kv_free(struct kv *); +struct kv *kv_inherit(struct kv *, struct kv *); +int kv_log(struct evbuffer *, struct kv *); +struct kv *kv_find(struct kvtree *, struct kv *); +int kv_cmp(struct kv *, struct kv *); +struct media_type + *media_add(struct mediatypes *, struct media_type *); +void media_delete(struct mediatypes *, struct media_type *); +void media_purge(struct mediatypes *); +struct media_type * + media_find(struct mediatypes *, char *); +int media_cmp(struct media_type *, struct media_type *); +RB_PROTOTYPE(kvtree, kv, kv_node, kv_cmp); +RB_PROTOTYPE(mediatypes, media_type, media_entry, media_cmp); +struct auth *auth_add(struct serverauth *, struct auth *); +struct auth *auth_byid(struct serverauth *, u_int32_t); +void auth_free(struct serverauth *, struct auth *); + +/* log.c */ +void log_init(int); +void log_verbose(int); +void log_warn(const char *, ...) __attribute__((__format__ (printf, 1, 2))); +void log_warnx(const char *, ...) __attribute__((__format__ (printf, 1, 2))); +void log_info(const char *, ...) __attribute__((__format__ (printf, 1, 2))); +void log_debug(const char *, ...) __attribute__((__format__ (printf, 1, 2))); +void logit(int, const char *, ...) __attribute__((__format__ (printf, 2, 3))); +void vlog(int, const char *, va_list) __attribute__((__format__ (printf, 2, 0))); +__dead void fatal(const char *); +__dead void fatalx(const char *); +const char *print_host(struct sockaddr_storage *, char *, size_t); +const char *print_time(struct timeval *, struct timeval *, char *, size_t); +const char *printb_flags(const u_int32_t, const char *); +void getmonotime(struct timeval *); + +/* proc.c */ +void proc_init(struct privsep *, struct privsep_proc *, u_int); +void proc_kill(struct privsep *); +void proc_listen(struct privsep *, struct privsep_proc *, size_t); +void proc_dispatch(int, short event, void *); +pid_t proc_run(struct privsep *, struct privsep_proc *, + struct privsep_proc *, u_int, + void (*)(struct privsep *, struct privsep_proc *, void *), void *); +void proc_range(struct privsep *, enum privsep_procid, int *, int *); +int proc_compose_imsg(struct privsep *, enum privsep_procid, int, + u_int16_t, int, void *, u_int16_t); +int proc_composev_imsg(struct privsep *, enum privsep_procid, int, + u_int16_t, int, const struct iovec *, int); +int proc_forward_imsg(struct privsep *, struct imsg *, + enum privsep_procid, int); +struct imsgbuf * + proc_ibuf(struct privsep *, enum privsep_procid, int); +struct imsgev * + proc_iev(struct privsep *, enum privsep_procid, int); +void imsg_event_add(struct imsgev *); +int imsg_compose_event(struct imsgev *, u_int16_t, u_int32_t, + pid_t, int, void *, u_int16_t); +int imsg_composev_event(struct imsgev *, u_int16_t, u_int32_t, + pid_t, int, const struct iovec *, int); + +/* config.c */ +int config_init(struct httpd *); +void config_purge(struct httpd *, u_int); +int config_setreset(struct httpd *, u_int); +int config_getreset(struct httpd *, struct imsg *); +int config_getcfg(struct httpd *, struct imsg *); +int config_setserver(struct httpd *, struct server *); +int config_getserver(struct httpd *, struct imsg *); +int config_setmedia(struct httpd *, struct media_type *); +int config_getmedia(struct httpd *, struct imsg *); +int config_setauth(struct httpd *, struct auth *); +int config_getauth(struct httpd *, struct imsg *); + +/* logger.c */ +pid_t logger(struct privsep *, struct privsep_proc *); +int logger_open_priv(struct imsg *); + +#endif /* _HTTPD_H */ diff --git a/httpd/log.c b/httpd/log.c new file mode 100644 index 0000000..a5dfc6b --- /dev/null +++ b/httpd/log.c @@ -0,0 +1,242 @@ +/* $OpenBSD: log.c,v 1.5 2015/01/21 22:21:05 reyk Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/time.h> + +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <time.h> +#include <netdb.h> +#include <ctype.h> + +#include "httpd.h" + +int debug; +int verbose; + +void vlog(int, const char *, va_list) + __attribute__((__format__ (printf, 2, 0))); +void logit(int, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); + +void +log_init(int n_debug) +{ + extern char *__progname; + + debug = n_debug; + verbose = n_debug; + + if (!debug) + openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON); + + tzset(); +} + +void +log_verbose(int v) +{ + verbose = v; +} + +void +logit(int pri, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vlog(pri, fmt, ap); + va_end(ap); +} + +void +vlog(int pri, const char *fmt, va_list ap) +{ + char *nfmt; + + if (debug) { + /* best effort in out of mem situations */ + if (asprintf(&nfmt, "%s\n", fmt) == -1) { + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } else { + vfprintf(stderr, nfmt, ap); + free(nfmt); + } + fflush(stderr); + } else + vsyslog(pri, fmt, ap); +} + + +void +log_warn(const char *emsg, ...) +{ + char *nfmt; + va_list ap; + + /* best effort to even work in out of memory situations */ + if (emsg == NULL) + logit(LOG_CRIT, "%s", strerror(errno)); + else { + va_start(ap, emsg); + + if (asprintf(&nfmt, "%s: %s", emsg, strerror(errno)) == -1) { + /* we tried it... */ + vlog(LOG_CRIT, emsg, ap); + logit(LOG_CRIT, "%s", strerror(errno)); + } else { + vlog(LOG_CRIT, nfmt, ap); + free(nfmt); + } + va_end(ap); + } +} + +void +log_warnx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_CRIT, emsg, ap); + va_end(ap); +} + +void +log_info(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_INFO, emsg, ap); + va_end(ap); +} + +void +log_debug(const char *emsg, ...) +{ + va_list ap; + + if (verbose > 1) { + va_start(ap, emsg); + vlog(LOG_DEBUG, emsg, ap); + va_end(ap); + } +} + +void +fatal(const char *emsg) +{ + if (emsg == NULL) + logit(LOG_CRIT, "fatal: %s", strerror(errno)); + else + if (errno) + logit(LOG_CRIT, "fatal: %s: %s", + emsg, strerror(errno)); + else + logit(LOG_CRIT, "fatal: %s", emsg); + + exit(1); +} + +void +fatalx(const char *emsg) +{ + errno = 0; + fatal(emsg); +} + +const char * +print_host(struct sockaddr_storage *ss, char *buf, size_t len) +{ + if (getnameinfo((struct sockaddr *)ss, ss->ss_len, + buf, len, NULL, 0, NI_NUMERICHOST) != 0) { + buf[0] = '\0'; + return (NULL); + } + return (buf); +} + +const char * +print_time(struct timeval *a, struct timeval *b, char *buf, size_t len) +{ + struct timeval tv; + u_long h, sec, min; + + timerclear(&tv); + timersub(a, b, &tv); + sec = tv.tv_sec % 60; + min = tv.tv_sec / 60 % 60; + h = tv.tv_sec / 60 / 60; + + snprintf(buf, len, "%.2lu:%.2lu:%.2lu", h, min, sec); + return (buf); +} + +const char * +printb_flags(const u_int32_t v, const char *bits) +{ + static char buf[2][BUFSIZ]; + static int idx = 0; + int i, any = 0; + char c, *p, *r; + + p = r = buf[++idx % 2]; + memset(p, 0, BUFSIZ); + + if (bits) { + bits++; + while ((i = *bits++)) { + if (v & (1 << (i - 1))) { + if (any) { + *p++ = ','; + *p++ = ' '; + } + any = 1; + for (; (c = *bits) > 32; bits++) { + if (c == '_') + *p++ = ' '; + else + *p++ = tolower((u_char)c); + } + } else + for (; *bits > 32; bits++) + ; + } + } + + return (r); +} + +void +getmonotime(struct timeval *tv) +{ + struct timespec ts; + + if (clock_gettime(CLOCK_MONOTONIC, &ts)) + fatal("clock_gettime"); + + TIMESPEC_TO_TIMEVAL(tv, &ts); +} diff --git a/httpd/logger.c b/httpd/logger.c new file mode 100644 index 0000000..b4e4404 --- /dev/null +++ b/httpd/logger.c @@ -0,0 +1,312 @@ +/* $OpenBSD: logger.c,v 1.11 2015/02/08 00:00:59 reyk Exp $ */ + +/* + * Copyright (c) 2014 Reyk Floeter <reyk@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/param.h> /* nitems */ +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/uio.h> + +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <imsg.h> + +#include "httpd.h" + +int logger_dispatch_parent(int, struct privsep_proc *, + struct imsg *); +int logger_dispatch_server(int, struct privsep_proc *, + struct imsg *); +void logger_shutdown(void); +void logger_close(void); +struct log_file *logger_open_file(const char *); +int logger_open_fd(struct imsg *); +int logger_open(struct server *, struct server_config *, void *); +void logger_init(struct privsep *, struct privsep_proc *p, void *); +int logger_start(void); +int logger_log(struct imsg *); + +static struct httpd *env = NULL; +int proc_id; +static u_int32_t last_log_id = 0; + +static struct privsep_proc procs[] = { + { "parent", PROC_PARENT, logger_dispatch_parent }, + { "server", PROC_SERVER, logger_dispatch_server } +}; + +pid_t +logger(struct privsep *ps, struct privsep_proc *p) +{ + env = ps->ps_env; + return (proc_run(ps, p, procs, nitems(procs), logger_init, NULL)); +} + +void +logger_shutdown(void) +{ + logger_close(); + config_purge(env, CONFIG_ALL); +} + +void +logger_init(struct privsep *ps, struct privsep_proc *p, void *arg) +{ + if (config_init(ps->ps_env) == -1) + fatal("failed to initialize configuration"); + + /* Set to current prefork id */ + proc_id = p->p_instance; + + /* We use a custom shutdown callback */ + p->p_shutdown = logger_shutdown; + + TAILQ_INIT(&log_files); +} + +void +logger_close(void) +{ + struct log_file *log, *next; + + TAILQ_FOREACH_SAFE(log, &log_files, log_entry, next) { + if (log->log_fd != -1) { + close(log->log_fd); + log->log_fd = -1; + } + TAILQ_REMOVE(&log_files, log, log_entry); + } +} + +struct log_file * +logger_open_file(const char *name) +{ + struct log_file *log; + struct iovec iov[2]; + + if ((log = calloc(1, sizeof(*log))) == NULL) { + log_warn("failed to allocate log %s", name); + return (NULL); + } + + log->log_id = ++last_log_id; + (void)strlcpy(log->log_name, name, sizeof(log->log_name)); + + /* The file will be opened by the parent process */ + log->log_fd = -1; + + iov[0].iov_base = &log->log_id; + iov[0].iov_len = sizeof(log->log_id); + iov[1].iov_base = log->log_name; + iov[1].iov_len = strlen(log->log_name) + 1; + + proc_composev_imsg(env->sc_ps, PROC_PARENT, -1, IMSG_LOG_OPEN, -1, + iov, 2); + + TAILQ_INSERT_TAIL(&log_files, log, log_entry); + + return (log); +} + +int +logger_open_fd(struct imsg *imsg) +{ + struct log_file *log; + u_int32_t id; + + IMSG_SIZE_CHECK(imsg, &id); + memcpy(&id, imsg->data, sizeof(id)); + + TAILQ_FOREACH(log, &log_files, log_entry) { + if (log->log_id == id) { + DPRINTF("%s: received log fd %d, file %s", + __func__, imsg->fd, log->log_name); + log->log_fd = imsg->fd; + return (0); + } + } + + return (-1); +} + +int +logger_open_priv(struct imsg *imsg) +{ + char path[PATH_MAX]; + char name[NAME_MAX], *p; + u_int32_t id; + size_t len; + int fd; + + /* called from the priviled process */ + IMSG_SIZE_CHECK(imsg, &id); + memcpy(&id, imsg->data, sizeof(id)); + p = (char *)imsg->data + sizeof(id); + + if ((size_t)snprintf(name, sizeof(name), "/%s", p) >= sizeof(name)) + return (-1); + if ((len = strlcpy(path, env->sc_logdir, sizeof(path))) + >= sizeof(path)) + return (-1); + + p = path + len; + len = sizeof(path) - len; + + if (canonicalize_path(name, p, len) == NULL) { + log_warnx("invalid log name"); + return (-1); + } + + if ((fd = open(path, O_WRONLY|O_APPEND|O_CREAT, 0644)) == -1) { + log_warn("failed to open %s", path); + return (-1); + } + + proc_compose_imsg(env->sc_ps, PROC_LOGGER, -1, IMSG_LOG_OPEN, fd, + &id, sizeof(id)); + + DPRINTF("%s: opened log file %s, fd %d", __func__, path, fd); + + return (0); +} + +int +logger_open(struct server *srv, struct server_config *srv_conf, void *arg) +{ + struct log_file *log, *logfile = NULL, *errfile = NULL; + + if (srv_conf->flags & SRVFLAG_SYSLOG) + return (0); + + /* disassociate */ + srv_conf->logaccess = srv_conf->logerror = NULL; + + TAILQ_FOREACH(log, &log_files, log_entry) { + if (strcmp(log->log_name, srv_conf->accesslog) == 0) + logfile = log; + if (strcmp(log->log_name, srv_conf->errorlog) == 0) + errfile = log; + } + + if (logfile == NULL) { + if ((srv_conf->logaccess = + logger_open_file(srv_conf->accesslog)) == NULL) + return (-1); + } else + srv_conf->logaccess = logfile; + + if (errfile == NULL) { + if ((srv_conf->logerror = + logger_open_file(srv_conf->errorlog)) == NULL) + return (-1); + } else + srv_conf->logerror = errfile; + + return (0); +} + +int +logger_start(void) +{ + logger_close(); + if (server_foreach(logger_open, NULL) == -1) + fatalx("failed to open log files"); + return (0); +} + +int +logger_log(struct imsg *imsg) +{ + char *logline; + u_int32_t id; + struct server_config *srv_conf; + struct log_file *log; + + IMSG_SIZE_CHECK(imsg, &id); + memcpy(&id, imsg->data, sizeof(id)); + + if ((srv_conf = serverconfig_byid(id)) == NULL) + fatalx("invalid logging requestr"); + + if (imsg->hdr.type == IMSG_LOG_ACCESS) + log = srv_conf->logaccess; + else + log = srv_conf->logerror; + + if (log == NULL || log->log_fd == -1) { + log_warnx("log file %s not opened", log ? log->log_name : ""); + return (0); + } + + /* XXX get_string() would sanitize the string, but add a malloc */ + logline = (char *)imsg->data + sizeof(id); + + /* For debug output */ + log_debug("%s", logline); + + if (dprintf(log->log_fd, "%s\n", logline) == -1) { + if (logger_start() == -1) + return (-1); + } + + return (0); +} + +int +logger_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg) +{ + switch (imsg->hdr.type) { + case IMSG_CFG_SERVER: + config_getserver(env, imsg); + break; + case IMSG_CFG_DONE: + config_getcfg(env, imsg); + break; + case IMSG_CTL_START: + case IMSG_CTL_REOPEN: + logger_start(); + break; + case IMSG_CTL_RESET: + config_getreset(env, imsg); + break; + case IMSG_LOG_OPEN: + return (logger_open_fd(imsg)); + default: + return (-1); + } + + return (0); +} + +int +logger_dispatch_server(int fd, struct privsep_proc *p, struct imsg *imsg) +{ + switch (imsg->hdr.type) { + case IMSG_LOG_ACCESS: + case IMSG_LOG_ERROR: + logger_log(imsg); + break; + default: + return (-1); + } + + return (0); +} diff --git a/httpd/parse.y b/httpd/parse.y new file mode 100644 index 0000000..7e48aba --- /dev/null +++ b/httpd/parse.y @@ -0,0 +1,2062 @@ +/* $OpenBSD: parse.y,v 1.65 2015/02/12 04:40:23 jsing Exp $ */ + +/* + * Copyright (c) 2007 - 2015 Reyk Floeter <reyk@openbsd.org> + * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> + * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@openbsd.org> + * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org> + * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org> + * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org> + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +%{ +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/ioctl.h> +#include <sys/sockio.h> +#include <sys/time.h> + +#include <net/if.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <ctype.h> +#include <unistd.h> +#include <err.h> +#include <errno.h> +#include <limits.h> +#include <stdint.h> +#include <stdarg.h> +#include <stdio.h> +#include <netdb.h> +#include <string.h> +#include <ifaddrs.h> +#include <syslog.h> + +#include "httpd.h" +#include "http.h" + +TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); +static struct file { + TAILQ_ENTRY(file) entry; + FILE *stream; + char *name; + int lineno; + int errors; +} *file, *topfile; +struct file *pushfile(const char *, int); +int popfile(void); +int check_file_secrecy(int, const char *); +int yyparse(void); +int yylex(void); +int yyerror(const char *, ...) + __attribute__((__format__ (printf, 1, 2))) + __attribute__((__nonnull__ (1))); +int kw_cmp(const void *, const void *); +int lookup(char *); +int lgetc(int); +int lungetc(int); +int findeol(void); + +TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); +struct sym { + TAILQ_ENTRY(sym) entry; + int used; + int persist; + char *nam; + char *val; +}; +int symset(const char *, const char *, int); +char *symget(const char *); + +struct httpd *conf = NULL; +static int errors = 0; +static int loadcfg = 0; +uint32_t last_server_id = 0; +uint32_t last_auth_id = 0; + +static struct server *srv = NULL, *parentsrv = NULL; +static struct server_config *srv_conf = NULL; +struct serverlist servers; +struct media_type media; + +struct address *host_v4(const char *); +struct address *host_v6(const char *); +int host_dns(const char *, struct addresslist *, + int, struct portrange *, const char *, int); +int host_if(const char *, struct addresslist *, + int, struct portrange *, const char *, int); +int host(const char *, struct addresslist *, + int, struct portrange *, const char *, int); +void host_free(struct addresslist *); +struct server *server_inherit(struct server *, const char *, + struct server_config *); +int getservice(char *); +int is_if_in_group(const char *, const char *); + +typedef struct { + union { + int64_t number; + char *string; + struct timeval tv; + struct portrange port; + struct auth auth; + struct { + struct sockaddr_storage ss; + char name[HOST_NAME_MAX+1]; + } addr; + } v; + int lineno; +} YYSTYPE; + +%} + +%token ACCESS ALIAS AUTO BACKLOG BODY BUFFER CERTIFICATE CHROOT CIPHERS COMMON +%token COMBINED CONNECTION DHE DIRECTORY ECDHE ERR FCGI INDEX IP KEY LISTEN +%token LOCATION LOG LOGDIR MAXIMUM NO NODELAY ON PORT PREFORK PROTOCOLS +%token REQUEST REQUESTS ROOT SACK SERVER SOCKET STRIP STYLE SYSLOG TCP TIMEOUT +%token TLS TYPES +%token ERROR INCLUDE AUTHENTICATE WITH BLOCK DROP RETURN PASS +%token <v.string> STRING +%token <v.number> NUMBER +%type <v.port> port +%type <v.number> opttls +%type <v.tv> timeout +%type <v.string> numberstring optstring +%type <v.auth> authopts + +%% + +grammar : /* empty */ + | grammar include '\n' + | grammar '\n' + | grammar varset '\n' + | grammar main '\n' + | grammar server '\n' + | grammar types '\n' + | grammar error '\n' { file->errors++; } + ; + +include : INCLUDE STRING { + struct file *nfile; + + if ((nfile = pushfile($2, 0)) == NULL) { + yyerror("failed to include file %s", $2); + free($2); + YYERROR; + } + free($2); + + file = nfile; + lungetc('\n'); + } + ; + +varset : STRING '=' STRING { + if (symset($1, $3, 0) == -1) + fatal("cannot store variable"); + free($1); + free($3); + } + ; + +opttls : /*empty*/ { $$ = 0; } + | TLS { $$ = 1; } + ; + +main : PREFORK NUMBER { + if (loadcfg) + break; + if ($2 <= 0 || $2 > SERVER_MAXPROC) { + yyerror("invalid number of preforked " + "servers: %lld", $2); + YYERROR; + } + conf->sc_prefork_server = $2; + } + | CHROOT STRING { + conf->sc_chroot = $2; + } + | LOGDIR STRING { + conf->sc_logdir = $2; + } + ; + +server : SERVER STRING { + struct server *s; + + if (!loadcfg) { + free($2); + YYACCEPT; + } + + if ((s = calloc(1, sizeof (*s))) == NULL) + fatal("out of memory"); + + if (strlcpy(s->srv_conf.name, $2, + sizeof(s->srv_conf.name)) >= + sizeof(s->srv_conf.name)) { + yyerror("server name truncated"); + free($2); + free(s); + YYERROR; + } + free($2); + + strlcpy(s->srv_conf.root, HTTPD_DOCROOT, + sizeof(s->srv_conf.root)); + strlcpy(s->srv_conf.index, HTTPD_INDEX, + sizeof(s->srv_conf.index)); + strlcpy(s->srv_conf.accesslog, HTTPD_ACCESS_LOG, + sizeof(s->srv_conf.accesslog)); + strlcpy(s->srv_conf.errorlog, HTTPD_ERROR_LOG, + sizeof(s->srv_conf.errorlog)); + s->srv_conf.id = ++last_server_id; + s->srv_conf.parent_id = s->srv_conf.id; + s->srv_s = -1; + s->srv_conf.timeout.tv_sec = SERVER_TIMEOUT; + s->srv_conf.maxrequests = SERVER_MAXREQUESTS; + s->srv_conf.maxrequestbody = SERVER_MAXREQUESTBODY; + s->srv_conf.flags |= SRVFLAG_LOG; + s->srv_conf.logformat = LOG_FORMAT_COMMON; + s->srv_conf.tls_protocols = TLS_PROTOCOLS_ALL; + if ((s->srv_conf.tls_cert_file = + strdup(HTTPD_TLS_CERT)) == NULL) + fatal("out of memory"); + if ((s->srv_conf.tls_key_file = + strdup(HTTPD_TLS_KEY)) == NULL) + fatal("out of memory"); + strlcpy(s->srv_conf.tls_ciphers, + HTTPD_TLS_CIPHERS, + sizeof(s->srv_conf.tls_ciphers)); + strlcpy(s->srv_conf.tls_dhe_params, + HTTPD_TLS_DHE_PARAMS, + sizeof(s->srv_conf.tls_dhe_params)); + strlcpy(s->srv_conf.tls_ecdhe_curve, + HTTPD_TLS_ECDHE_CURVE, + sizeof(s->srv_conf.tls_ecdhe_curve)); + + if (last_server_id == INT_MAX) { + yyerror("too many servers defined"); + free(s); + YYERROR; + } + srv = s; + srv_conf = &srv->srv_conf; + + SPLAY_INIT(&srv->srv_clients); + TAILQ_INIT(&srv->srv_hosts); + + TAILQ_INSERT_TAIL(&srv->srv_hosts, srv_conf, entry); + } '{' optnl serveropts_l '}' { + struct server *s = NULL, *sn; + struct server_config *a, *b; + + srv_conf = &srv->srv_conf; + + TAILQ_FOREACH(s, conf->sc_servers, srv_entry) { + if ((s->srv_conf.flags & + SRVFLAG_LOCATION) == 0 && + strcmp(s->srv_conf.name, + srv->srv_conf.name) == 0 && + s->srv_conf.port == srv->srv_conf.port && + sockaddr_cmp( + (struct sockaddr *)&s->srv_conf.ss, + (struct sockaddr *)&srv->srv_conf.ss, + s->srv_conf.prefixlen) == 0) + break; + } + if (s != NULL) { + yyerror("server \"%s\" defined twice", + srv->srv_conf.name); + serverconfig_free(srv_conf); + free(srv); + YYABORT; + } + + if (srv->srv_conf.ss.ss_family == AF_UNSPEC) { + yyerror("listen address not specified"); + serverconfig_free(srv_conf); + free(srv); + YYERROR; + } + + if ((srv->srv_conf.flags & SRVFLAG_TLS) && + srv->srv_conf.tls_protocols == 0) { + yyerror("no TLS protocols"); + free(srv); + YYERROR; + } + + if (server_tls_load_keypair(srv) == -1) { + yyerror("failed to load public/private keys " + "for server %s", srv->srv_conf.name); + serverconfig_free(srv_conf); + free(srv); + YYERROR; + } + + DPRINTF("adding server \"%s[%u]\"", + srv->srv_conf.name, srv->srv_conf.id); + + TAILQ_INSERT_TAIL(conf->sc_servers, srv, srv_entry); + + /* + * Add aliases and additional listen addresses as + * individual servers. + */ + TAILQ_FOREACH(a, &srv->srv_hosts, entry) { + /* listen address */ + if (a->ss.ss_family == AF_UNSPEC) + continue; + TAILQ_FOREACH(b, &srv->srv_hosts, entry) { + /* alias name */ + if (*b->name == '\0' || + (b == &srv->srv_conf && b == a)) + continue; + + if ((sn = server_inherit(srv, + b->name, a)) == NULL) { + serverconfig_free(srv_conf); + free(srv); + YYABORT; + } + + DPRINTF("adding server \"%s[%u]\"", + sn->srv_conf.name, sn->srv_conf.id); + + TAILQ_INSERT_TAIL(conf->sc_servers, + sn, srv_entry); + } + } + + /* Remove temporary aliases */ + TAILQ_FOREACH_SAFE(a, &srv->srv_hosts, entry, b) { + TAILQ_REMOVE(&srv->srv_hosts, a, entry); + if (a == &srv->srv_conf) + continue; + serverconfig_free(a); + free(a); + } + + srv = NULL; + srv_conf = NULL; + } + ; + +serveropts_l : serveropts_l serveroptsl nl + | serveroptsl optnl + ; + +serveroptsl : LISTEN ON STRING opttls port { + struct addresslist al; + struct address *h; + struct server_config *s_conf, *alias = NULL; + + if (parentsrv != NULL) { + yyerror("listen %s inside location", $3); + free($3); + YYERROR; + } + + if (srv->srv_conf.ss.ss_family != AF_UNSPEC) { + if ((alias = calloc(1, + sizeof(*alias))) == NULL) + fatal("out of memory"); + + /* Add as an alias */ + s_conf = alias; + } else + s_conf = &srv->srv_conf; + + TAILQ_INIT(&al); + if (host($3, &al, 1, &$5, NULL, -1) <= 0) { + yyerror("invalid listen ip: %s", $3); + free($3); + YYERROR; + } + free($3); + h = TAILQ_FIRST(&al); + memcpy(&s_conf->ss, &h->ss, sizeof(s_conf->ss)); + s_conf->port = h->port.val[0]; + s_conf->prefixlen = h->prefixlen; + host_free(&al); + + if ($4) { + s_conf->flags |= SRVFLAG_TLS; + } + + if (alias != NULL) { + TAILQ_INSERT_TAIL(&srv->srv_hosts, + alias, entry); + } + } + | ALIAS STRING { + struct server_config *alias; + + if (parentsrv != NULL) { + yyerror("alias inside location"); + free($2); + YYERROR; + } + + if ((alias = calloc(1, sizeof(*alias))) == NULL) + fatal("out of memory"); + + if (strlcpy(alias->name, $2, sizeof(alias->name)) >= + sizeof(alias->name)) { + yyerror("server alias truncated"); + free($2); + free(alias); + YYERROR; + } + free($2); + + TAILQ_INSERT_TAIL(&srv->srv_hosts, alias, entry); + } + | tcpip { + if (parentsrv != NULL) { + yyerror("tcp flags inside location"); + YYERROR; + } + } + | connection { + if (parentsrv != NULL) { + yyerror("connection options inside location"); + YYERROR; + } + } + | tls { + if (parentsrv != NULL) { + yyerror("tls configuration inside location"); + YYERROR; + } + } + | root + | directory + | logformat + | fastcgi + | authenticate + | filter + | LOCATION STRING { + struct server *s; + + if (srv->srv_conf.ss.ss_family == AF_UNSPEC) { + yyerror("listen address not specified"); + free($2); + YYERROR; + } + + if (parentsrv != NULL) { + yyerror("location %s inside location", $2); + free($2); + YYERROR; + } + + if (!loadcfg) { + free($2); + YYACCEPT; + } + + if ((s = calloc(1, sizeof (*s))) == NULL) + fatal("out of memory"); + + if (strlcpy(s->srv_conf.location, $2, + sizeof(s->srv_conf.location)) >= + sizeof(s->srv_conf.location)) { + yyerror("server location truncated"); + free($2); + free(s); + YYERROR; + } + free($2); + + if (strlcpy(s->srv_conf.name, srv->srv_conf.name, + sizeof(s->srv_conf.name)) >= + sizeof(s->srv_conf.name)) { + yyerror("server name truncated"); + free(s); + YYERROR; + } + + s->srv_conf.id = ++last_server_id; + /* A location entry uses the parent id */ + s->srv_conf.parent_id = srv->srv_conf.id; + s->srv_conf.flags = SRVFLAG_LOCATION; + s->srv_s = -1; + memcpy(&s->srv_conf.ss, &srv->srv_conf.ss, + sizeof(s->srv_conf.ss)); + s->srv_conf.port = srv->srv_conf.port; + s->srv_conf.prefixlen = srv->srv_conf.prefixlen; + + if (last_server_id == INT_MAX) { + yyerror("too many servers/locations defined"); + free(s); + YYERROR; + } + parentsrv = srv; + srv = s; + srv_conf = &srv->srv_conf; + SPLAY_INIT(&srv->srv_clients); + } '{' optnl serveropts_l '}' { + struct server *s = NULL; + + TAILQ_FOREACH(s, conf->sc_servers, srv_entry) { + if ((s->srv_conf.flags & SRVFLAG_LOCATION) && + s->srv_conf.id == srv_conf->id && + strcmp(s->srv_conf.location, + srv_conf->location) == 0) + break; + } + if (s != NULL) { + yyerror("location \"%s\" defined twice", + srv->srv_conf.location); + serverconfig_free(srv_conf); + free(srv); + YYABORT; + } + + DPRINTF("adding location \"%s\" for \"%s[%u]\"", + srv->srv_conf.location, + srv->srv_conf.name, srv->srv_conf.id); + + TAILQ_INSERT_TAIL(conf->sc_servers, srv, srv_entry); + + srv = parentsrv; + srv_conf = &parentsrv->srv_conf; + parentsrv = NULL; + } + | include + ; + +fastcgi : NO FCGI { + srv_conf->flags &= ~SRVFLAG_FCGI; + srv_conf->flags |= SRVFLAG_NO_FCGI; + } + | FCGI { + srv_conf->flags &= ~SRVFLAG_NO_FCGI; + srv_conf->flags |= SRVFLAG_FCGI; + } + | FCGI { + srv_conf->flags &= ~SRVFLAG_NO_FCGI; + srv_conf->flags |= SRVFLAG_FCGI; + } '{' optnl fcgiflags_l '}' + | FCGI { + srv_conf->flags &= ~SRVFLAG_NO_FCGI; + srv_conf->flags |= SRVFLAG_FCGI; + } fcgiflags + ; + +fcgiflags_l : fcgiflags optcommanl fcgiflags_l + | fcgiflags optnl + ; + +fcgiflags : SOCKET STRING { + if (strlcpy(srv_conf->socket, $2, + sizeof(srv_conf->socket)) >= + sizeof(srv_conf->socket)) { + yyerror("fastcgi socket too long"); + free($2); + YYERROR; + } + free($2); + srv_conf->flags |= SRVFLAG_SOCKET; + } + ; + +connection : CONNECTION '{' optnl conflags_l '}' + | CONNECTION conflags + ; + +conflags_l : conflags optcommanl conflags_l + | conflags optnl + ; + +conflags : TIMEOUT timeout { + memcpy(&srv_conf->timeout, &$2, + sizeof(struct timeval)); + } + | MAXIMUM REQUESTS NUMBER { + srv_conf->maxrequests = $3; + } + | MAXIMUM REQUEST BODY NUMBER { + srv_conf->maxrequestbody = $4; + } + ; + +tls : TLS '{' optnl tlsopts_l '}' + | TLS tlsopts + ; + +tlsopts_l : tlsopts optcommanl tlsopts_l + | tlsopts optnl + ; + +tlsopts : CERTIFICATE STRING { + free(srv_conf->tls_cert_file); + if ((srv_conf->tls_cert_file = strdup($2)) == NULL) + fatal("out of memory"); + free($2); + } + | KEY STRING { + free(srv_conf->tls_key_file); + if ((srv_conf->tls_key_file = strdup($2)) == NULL) + fatal("out of memory"); + free($2); + } + | CIPHERS STRING { + if (strlcpy(srv_conf->tls_ciphers, $2, + sizeof(srv_conf->tls_ciphers)) >= + sizeof(srv_conf->tls_ciphers)) { + yyerror("ciphers too long"); + free($2); + YYERROR; + } + free($2); + } + | DHE STRING { + if (strlcpy(srv_conf->tls_dhe_params, $2, + sizeof(srv_conf->tls_dhe_params)) >= + sizeof(srv_conf->tls_dhe_params)) { + yyerror("dhe too long"); + free($2); + YYERROR; + } + free($2); + } + | ECDHE STRING { + if (strlcpy(srv_conf->tls_ecdhe_curve, $2, + sizeof(srv_conf->tls_ecdhe_curve)) >= + sizeof(srv_conf->tls_ecdhe_curve)) { + yyerror("ecdhe too long"); + free($2); + YYERROR; + } + free($2); + } + | PROTOCOLS STRING { + if (tls_config_parse_protocols( + &srv_conf->tls_protocols, $2) != 0) { + yyerror("invalid TLS protocols"); + free($2); + YYERROR; + } + free($2); + } + ; + +root : ROOT rootflags + | ROOT '{' optnl rootflags_l '}' + ; + +rootflags_l : rootflags optcommanl rootflags_l + | rootflags optnl + ; + +rootflags : STRING { + if (strlcpy(srv->srv_conf.root, $1, + sizeof(srv->srv_conf.root)) >= + sizeof(srv->srv_conf.root)) { + yyerror("document root too long"); + free($1); + YYERROR; + } + free($1); + srv->srv_conf.flags |= SRVFLAG_ROOT; + } + | STRIP NUMBER { + if ($2 < 0 || $2 > INT_MAX) { + yyerror("invalid strip number"); + YYERROR; + } + srv->srv_conf.strip = $2; + } + ; + +authenticate : NO AUTHENTICATE { + srv->srv_conf.flags |= SRVFLAG_NO_AUTH; + } + | AUTHENTICATE authopts { + struct auth *auth; + + if ((auth = auth_add(conf->sc_auth, &$2)) == NULL) { + yyerror("failed to add auth"); + YYERROR; + } + + if (auth->auth_id == 0) { + /* New htpasswd, get new Id */ + auth->auth_id = ++last_auth_id; + if (last_auth_id == INT_MAX) { + yyerror("too many auth ids defined"); + auth_free(conf->sc_auth, auth); + YYERROR; + } + } + + srv->srv_conf.auth_id = auth->auth_id; + srv->srv_conf.flags |= SRVFLAG_AUTH; + } + ; + +authopts : STRING WITH STRING { + if (strlcpy(srv->srv_conf.auth_realm, $1, + sizeof(srv->srv_conf.auth_realm)) >= + sizeof(srv->srv_conf.auth_realm)) { + yyerror("basic auth realm name too long"); + free($1); + YYERROR; + } + free($1); + if (strlcpy($$.auth_htpasswd, $3, + sizeof($$.auth_htpasswd)) >= + sizeof($$.auth_htpasswd)) { + yyerror("password file name too long"); + free($3); + YYERROR; + } + free($3); + + } + | WITH STRING { + if (strlcpy($$.auth_htpasswd, $2, + sizeof($$.auth_htpasswd)) >= + sizeof($$.auth_htpasswd)) { + yyerror("password file name too long"); + free($2); + YYERROR; + } + free($2); + }; + +directory : DIRECTORY dirflags + | DIRECTORY '{' optnl dirflags_l '}' + ; + +dirflags_l : dirflags optcommanl dirflags_l + | dirflags optnl + ; + +dirflags : INDEX STRING { + if (strlcpy(srv_conf->index, $2, + sizeof(srv_conf->index)) >= + sizeof(srv_conf->index)) { + yyerror("index file too long"); + free($2); + YYERROR; + } + srv_conf->flags &= ~SRVFLAG_NO_INDEX; + srv_conf->flags |= SRVFLAG_INDEX; + free($2); + } + | NO INDEX { + srv_conf->flags &= ~SRVFLAG_INDEX; + srv_conf->flags |= SRVFLAG_NO_INDEX; + } + | AUTO INDEX { + srv_conf->flags &= ~SRVFLAG_NO_AUTO_INDEX; + srv_conf->flags |= SRVFLAG_AUTO_INDEX; + } + | NO AUTO INDEX { + srv_conf->flags &= ~SRVFLAG_AUTO_INDEX; + srv_conf->flags |= SRVFLAG_NO_AUTO_INDEX; + } + ; + + +logformat : LOG logflags + | LOG '{' optnl logflags_l '}' + | NO LOG { + srv_conf->flags &= ~SRVFLAG_LOG; + srv_conf->flags |= SRVFLAG_NO_LOG; + } + ; + +logflags_l : logflags optcommanl logflags_l + | logflags optnl + ; + +logflags : STYLE logstyle + | SYSLOG { + srv_conf->flags &= ~SRVFLAG_NO_SYSLOG; + srv_conf->flags |= SRVFLAG_SYSLOG; + } + | NO SYSLOG { + srv_conf->flags &= ~SRVFLAG_SYSLOG; + srv_conf->flags |= SRVFLAG_NO_SYSLOG; + } + | ACCESS STRING { + if (strlcpy(srv_conf->accesslog, $2, + sizeof(srv_conf->accesslog)) >= + sizeof(srv_conf->accesslog)) { + yyerror("access log name too long"); + free($2); + YYERROR; + } + free($2); + srv_conf->flags |= SRVFLAG_ACCESS_LOG; + } + | ERR STRING { + if (strlcpy(srv_conf->errorlog, $2, + sizeof(srv_conf->errorlog)) >= + sizeof(srv_conf->errorlog)) { + yyerror("error log name too long"); + free($2); + YYERROR; + } + free($2); + srv_conf->flags |= SRVFLAG_ERROR_LOG; + } + ; + +logstyle : COMMON { + srv_conf->flags &= ~SRVFLAG_NO_LOG; + srv_conf->flags |= SRVFLAG_LOG; + srv_conf->logformat = LOG_FORMAT_COMMON; + } + | COMBINED { + srv_conf->flags &= ~SRVFLAG_NO_LOG; + srv_conf->flags |= SRVFLAG_LOG; + srv_conf->logformat = LOG_FORMAT_COMBINED; + } + | CONNECTION { + srv_conf->flags &= ~SRVFLAG_NO_LOG; + srv_conf->flags |= SRVFLAG_LOG; + srv_conf->logformat = LOG_FORMAT_CONNECTION; + } + ; + +filter : block RETURN NUMBER optstring { + if ($3 <= 0 || server_httperror_byid($3) == NULL) { + yyerror("invalid return code: %lld", $3); + free($4); + YYERROR; + } + srv_conf->return_code = $3; + + if ($4 != NULL) { + /* Only for 3xx redirection headers */ + if ($3 < 300 || $3 > 399) { + yyerror("invalid return code for " + "location URI"); + free($4); + YYERROR; + } + srv_conf->return_uri = $4; + srv_conf->return_uri_len = strlen($4) + 1; + } + } + | block DROP { + /* No return code, silently drop the connection */ + srv_conf->return_code = 0; + } + | block { + /* Forbidden */ + srv_conf->return_code = 403; + } + | PASS { + srv_conf->flags &= ~SRVFLAG_BLOCK; + srv_conf->flags |= SRVFLAG_NO_BLOCK; + } + ; + +block : BLOCK { + srv_conf->flags &= ~SRVFLAG_NO_BLOCK; + srv_conf->flags |= SRVFLAG_BLOCK; + } + ; + +optstring : /* empty */ { $$ = NULL; } + | STRING { $$ = $1; } + ; + +tcpip : TCP '{' optnl tcpflags_l '}' + | TCP tcpflags + ; + +tcpflags_l : tcpflags optcommanl tcpflags_l + | tcpflags optnl + ; + +tcpflags : SACK { srv_conf->tcpflags |= TCPFLAG_SACK; } + | NO SACK { srv_conf->tcpflags |= TCPFLAG_NSACK; } + | NODELAY { + srv_conf->tcpflags |= TCPFLAG_NODELAY; + } + | NO NODELAY { + srv_conf->tcpflags |= TCPFLAG_NNODELAY; + } + | BACKLOG NUMBER { + if ($2 < 0 || $2 > SERVER_MAX_CLIENTS) { + yyerror("invalid backlog: %lld", $2); + YYERROR; + } + srv_conf->tcpbacklog = $2; + } + | SOCKET BUFFER NUMBER { + srv_conf->tcpflags |= TCPFLAG_BUFSIZ; + if ((srv_conf->tcpbufsiz = $3) < 0) { + yyerror("invalid socket buffer size: %lld", $3); + YYERROR; + } + } + | IP STRING NUMBER { + if ($3 < 0) { + yyerror("invalid ttl: %lld", $3); + free($2); + YYERROR; + } + if (strcasecmp("ttl", $2) == 0) { + srv_conf->tcpflags |= TCPFLAG_IPTTL; + srv_conf->tcpipttl = $3; + } else if (strcasecmp("minttl", $2) == 0) { + srv_conf->tcpflags |= TCPFLAG_IPMINTTL; + srv_conf->tcpipminttl = $3; + } else { + yyerror("invalid TCP/IP flag: %s", $2); + free($2); + YYERROR; + } + free($2); + } + ; + +types : TYPES '{' optnl mediaopts_l '}' + ; + +mediaopts_l : mediaopts_l mediaoptsl nl + | mediaoptsl nl + ; + +mediaoptsl : STRING '/' STRING { + if (strlcpy(media.media_type, $1, + sizeof(media.media_type)) >= + sizeof(media.media_type) || + strlcpy(media.media_subtype, $3, + sizeof(media.media_subtype)) >= + sizeof(media.media_subtype)) { + yyerror("media type too long"); + free($1); + free($3); + YYERROR; + } + free($1); + free($3); + } medianames_l optsemicolon + | include + ; + +medianames_l : medianames_l medianamesl + | medianamesl + ; + +medianamesl : numberstring { + if (strlcpy(media.media_name, $1, + sizeof(media.media_name)) >= + sizeof(media.media_name)) { + yyerror("media name too long"); + free($1); + YYERROR; + } + free($1); + + if (!loadcfg) + break; + + if (media_add(conf->sc_mediatypes, &media) == NULL) { + yyerror("failed to add media type"); + YYERROR; + } + } + ; + +port : PORT NUMBER { + if ($2 <= 0 || $2 >= (int)USHRT_MAX) { + yyerror("invalid port: %lld", $2); + YYERROR; + } + $$.val[0] = htons($2); + } + | PORT STRING { + int val; + + if ((val = getservice($2)) == -1) { + yyerror("invalid port: %s", $2); + free($2); + YYERROR; + } + free($2); + + $$.val[0] = val; + } + ; + +timeout : NUMBER + { + if ($1 < 0) { + yyerror("invalid timeout: %lld", $1); + YYERROR; + } + $$.tv_sec = $1; + $$.tv_usec = 0; + } + ; + +numberstring : NUMBER { + char *s; + if (asprintf(&s, "%lld", $1) == -1) { + yyerror("asprintf: number"); + YYERROR; + } + $$ = s; + } + | STRING + ; + +optsemicolon : ';' + | + ; + +optnl : '\n' optnl + | + ; + +optcommanl : ',' optnl + | nl + ; + +nl : '\n' optnl + ; + +%% + +struct keywords { + const char *k_name; + int k_val; +}; + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + char *msg; + + file->errors++; + va_start(ap, fmt); + if (vasprintf(&msg, fmt, ap) == -1) + fatalx("yyerror vasprintf"); + va_end(ap); + logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg); + free(msg); + return (0); +} + +int +kw_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct keywords *)e)->k_name)); +} + +int +lookup(char *s) +{ + /* this has to be sorted always */ + static const struct keywords keywords[] = { + { "access", ACCESS }, + { "alias", ALIAS }, + { "authenticate", AUTHENTICATE}, + { "auto", AUTO }, + { "backlog", BACKLOG }, + { "block", BLOCK }, + { "body", BODY }, + { "buffer", BUFFER }, + { "certificate", CERTIFICATE }, + { "chroot", CHROOT }, + { "ciphers", CIPHERS }, + { "combined", COMBINED }, + { "common", COMMON }, + { "connection", CONNECTION }, + { "dhe", DHE }, + { "directory", DIRECTORY }, + { "drop", DROP }, + { "ecdhe", ECDHE }, + { "error", ERR }, + { "fastcgi", FCGI }, + { "include", INCLUDE }, + { "index", INDEX }, + { "ip", IP }, + { "key", KEY }, + { "listen", LISTEN }, + { "location", LOCATION }, + { "log", LOG }, + { "logdir", LOGDIR }, + { "max", MAXIMUM }, + { "no", NO }, + { "nodelay", NODELAY }, + { "on", ON }, + { "pass", PASS }, + { "port", PORT }, + { "prefork", PREFORK }, + { "protocols", PROTOCOLS }, + { "request", REQUEST }, + { "requests", REQUESTS }, + { "return", RETURN }, + { "root", ROOT }, + { "sack", SACK }, + { "server", SERVER }, + { "socket", SOCKET }, + { "strip", STRIP }, + { "style", STYLE }, + { "syslog", SYSLOG }, + { "tcp", TCP }, + { "timeout", TIMEOUT }, + { "tls", TLS }, + { "types", TYPES }, + { "with", WITH } + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) + return (p->k_val); + else + return (STRING); +} + +#define MAXPUSHBACK 128 + +u_char *parsebuf; +int parseindex; +u_char pushback_buffer[MAXPUSHBACK]; +int pushback_index = 0; + +int +lgetc(int quotec) +{ + int c, next; + + if (parsebuf) { + /* Read character from the parsebuffer instead of input. */ + if (parseindex >= 0) { + c = parsebuf[parseindex++]; + if (c != '\0') + return (c); + parsebuf = NULL; + } else + parseindex++; + } + + if (pushback_index) + return (pushback_buffer[--pushback_index]); + + if (quotec) { + if ((c = getc(file->stream)) == EOF) { + yyerror("reached end of file while parsing " + "quoted string"); + if (file == topfile || popfile() == EOF) + return (EOF); + return (quotec); + } + return (c); + } + + while ((c = getc(file->stream)) == '\\') { + next = getc(file->stream); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = file->lineno; + file->lineno++; + } + + while (c == EOF) { + if (file == topfile || popfile() == EOF) + return (EOF); + c = getc(file->stream); + } + return (c); +} + +int +lungetc(int c) +{ + if (c == EOF) + return (EOF); + if (parsebuf) { + parseindex--; + if (parseindex >= 0) + return (c); + } + if (pushback_index < MAXPUSHBACK-1) + return (pushback_buffer[pushback_index++] = c); + else + return (EOF); +} + +int +findeol(void) +{ + int c; + + parsebuf = NULL; + + /* skip to either EOF or the first real EOL */ + while (1) { + if (pushback_index) + c = pushback_buffer[--pushback_index]; + else + c = lgetc(0); + if (c == '\n') { + file->lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} + +int +yylex(void) +{ + u_char buf[8096]; + u_char *p, *val; + int quotec, next, c; + int token; + +top: + p = buf; + while ((c = lgetc(0)) == ' ' || c == '\t') + ; /* nothing */ + + yylval.lineno = file->lineno; + if (c == '#') + while ((c = lgetc(0)) != '\n' && c != EOF) + ; /* nothing */ + if (c == '$' && parsebuf == NULL) { + while (1) { + if ((c = lgetc(0)) == EOF) + return (0); + + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + if (isalnum(c) || c == '_') { + *p++ = c; + continue; + } + *p = '\0'; + lungetc(c); + break; + } + val = symget(buf); + if (val == NULL) { + yyerror("macro '%s' not defined", buf); + return (findeol()); + } + parsebuf = val; + parseindex = 0; + goto top; + } + + switch (c) { + case '\'': + case '"': + quotec = c; + while (1) { + if ((c = lgetc(quotec)) == EOF) + return (0); + if (c == '\n') { + file->lineno++; + continue; + } else if (c == '\\') { + if ((next = lgetc(quotec)) == EOF) + return (0); + if (next == quotec || c == ' ' || c == '\t') + c = next; + else if (next == '\n') { + file->lineno++; + continue; + } else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } else if (c == '\0') { + yyerror("syntax error"); + return (findeol()); + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + *p++ = c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + err(1, "yylex: strdup"); + return (STRING); + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && isdigit(c)); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + const char *errstr = NULL; + + *p = '\0'; + yylval.v.number = strtonum(buf, LLONG_MIN, + LLONG_MAX, &errstr); + if (errstr) { + yyerror("\"%s\" invalid number: %s", + buf, errstr); + return (findeol()); + } + return (NUMBER); + } else { +nodigits: + while (p > buf + 1) + lungetc(*--p); + c = *--p; + if (c == '-') + return (c); + } + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && x != '<' && x != '>' && \ + x != '!' && x != '=' && x != '#' && \ + x != ',' && x != ';' && x != '/')) + + if (isalnum(c) || c == ':' || c == '_' || c == '*') { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup(buf)) == STRING) + if ((yylval.v.string = strdup(buf)) == NULL) + err(1, "yylex: strdup"); + return (token); + } + if (c == '\n') { + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +int +check_file_secrecy(int fd, const char *fname) +{ + struct stat st; + + if (fstat(fd, &st)) { + log_warn("cannot stat %s", fname); + return (-1); + } + if (st.st_uid != 0 && st.st_uid != getuid()) { + log_warnx("%s: owner not root or current user", fname); + return (-1); + } + if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) { + log_warnx("%s: group writable or world read/writable", fname); + return (-1); + } + return (0); +} + +struct file * +pushfile(const char *name, int secret) +{ + struct file *nfile; + + if ((nfile = calloc(1, sizeof(struct file))) == NULL) { + log_warn("%s: malloc", __func__); + return (NULL); + } + if ((nfile->name = strdup(name)) == NULL) { + log_warn("%s: malloc", __func__); + free(nfile); + return (NULL); + } + if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { + log_warn("%s: %s", __func__, nfile->name); + free(nfile->name); + free(nfile); + return (NULL); + } else if (secret && + check_file_secrecy(fileno(nfile->stream), nfile->name)) { + fclose(nfile->stream); + free(nfile->name); + free(nfile); + return (NULL); + } + nfile->lineno = 1; + TAILQ_INSERT_TAIL(&files, nfile, entry); + return (nfile); +} + +int +popfile(void) +{ + struct file *prev; + + if ((prev = TAILQ_PREV(file, files, entry)) != NULL) + prev->errors += file->errors; + + TAILQ_REMOVE(&files, file, entry); + fclose(file->stream); + free(file->name); + free(file); + file = prev; + return (file ? 0 : EOF); +} + +int +parse_config(const char *filename, struct httpd *x_conf) +{ + struct sym *sym, *next; + + conf = x_conf; + if (config_init(conf) == -1) { + log_warn("%s: cannot initialize configuration", __func__); + return (-1); + } + + errors = 0; + + if ((file = pushfile(filename, 0)) == NULL) + return (-1); + + topfile = file; + setservent(1); + + yyparse(); + errors = file->errors; + popfile(); + + endservent(); + endprotoent(); + + /* Free macros */ + for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) { + next = TAILQ_NEXT(sym, entry); + if (!sym->persist) { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + + return (errors ? -1 : 0); +} + +int +load_config(const char *filename, struct httpd *x_conf) +{ + struct sym *sym, *next; + struct http_mediatype mediatypes[] = MEDIA_TYPES; + struct media_type m; + int i; + + conf = x_conf; + conf->sc_flags = 0; + + loadcfg = 1; + errors = 0; + last_server_id = 0; + last_auth_id = 0; + + srv = NULL; + + if ((file = pushfile(filename, 0)) == NULL) + return (-1); + + topfile = file; + setservent(1); + + yyparse(); + errors = file->errors; + popfile(); + + endservent(); + endprotoent(); + + /* Free macros and check which have not been used. */ + for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) { + next = TAILQ_NEXT(sym, entry); + if ((conf->sc_opts & HTTPD_OPT_VERBOSE) && !sym->used) + fprintf(stderr, "warning: macro '%s' not " + "used\n", sym->nam); + if (!sym->persist) { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + + if (TAILQ_EMPTY(conf->sc_servers)) { + log_warnx("no actions, nothing to do"); + errors++; + } + + if (RB_EMPTY(conf->sc_mediatypes)) { + /* Add default media types */ + for (i = 0; mediatypes[i].media_name != NULL; i++) { + (void)strlcpy(m.media_name, mediatypes[i].media_name, + sizeof(m.media_name)); + (void)strlcpy(m.media_type, mediatypes[i].media_type, + sizeof(m.media_type)); + (void)strlcpy(m.media_subtype, + mediatypes[i].media_subtype, + sizeof(m.media_subtype)); + m.media_encoding = NULL; + + if (media_add(conf->sc_mediatypes, &m) == NULL) { + log_warnx("failed to add default media \"%s\"", + m.media_name); + errors++; + } + } + } + + return (errors ? -1 : 0); +} + +int +symset(const char *nam, const char *val, int persist) +{ + struct sym *sym; + + for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam); + sym = TAILQ_NEXT(sym, entry)) + ; /* nothing */ + + if (sym != NULL) { + if (sym->persist == 1) + return (0); + else { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + if ((sym = calloc(1, sizeof(*sym))) == NULL) + return (-1); + + sym->nam = strdup(nam); + if (sym->nam == NULL) { + free(sym); + return (-1); + } + sym->val = strdup(val); + if (sym->val == NULL) { + free(sym->nam); + free(sym); + return (-1); + } + sym->used = 0; + sym->persist = persist; + TAILQ_INSERT_TAIL(&symhead, sym, entry); + return (0); +} + +int +cmdline_symset(char *s) +{ + char *sym, *val; + int ret; + size_t len; + + if ((val = strrchr(s, '=')) == NULL) + return (-1); + + len = strlen(s) - strlen(val) + 1; + if ((sym = malloc(len)) == NULL) + errx(1, "cmdline_symset: malloc"); + + (void)strlcpy(sym, s, len); + + ret = symset(sym, val + 1, 1); + free(sym); + + return (ret); +} + +char * +symget(const char *nam) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) + if (strcmp(nam, sym->nam) == 0) { + sym->used = 1; + return (sym->val); + } + return (NULL); +} + +struct address * +host_v4(const char *s) +{ + struct in_addr ina; + struct sockaddr_in *sain; + struct address *h; + + memset(&ina, 0, sizeof(ina)); + if (inet_pton(AF_INET, s, &ina) != 1) + return (NULL); + + if ((h = calloc(1, sizeof(*h))) == NULL) + fatal(NULL); + sain = (struct sockaddr_in *)&h->ss; + sain->sin_len = sizeof(struct sockaddr_in); + sain->sin_family = AF_INET; + sain->sin_addr.s_addr = ina.s_addr; + if (sain->sin_addr.s_addr == INADDR_ANY) + h->prefixlen = 0; /* 0.0.0.0 address */ + else + h->prefixlen = -1; /* host address */ + return (h); +} + +struct address * +host_v6(const char *s) +{ + struct addrinfo hints, *res; + struct sockaddr_in6 *sa_in6; + struct address *h = NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_DGRAM; /* dummy */ + hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo(s, "0", &hints, &res) == 0) { + if ((h = calloc(1, sizeof(*h))) == NULL) + fatal(NULL); + sa_in6 = (struct sockaddr_in6 *)&h->ss; + sa_in6->sin6_len = sizeof(struct sockaddr_in6); + sa_in6->sin6_family = AF_INET6; + memcpy(&sa_in6->sin6_addr, + &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, + sizeof(sa_in6->sin6_addr)); + sa_in6->sin6_scope_id = + ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id; + if (memcmp(&sa_in6->sin6_addr, &in6addr_any, + sizeof(sa_in6->sin6_addr)) == 0) + h->prefixlen = 0; /* any address */ + else + h->prefixlen = -1; /* host address */ + freeaddrinfo(res); + } + + return (h); +} + +int +host_dns(const char *s, struct addresslist *al, int max, + struct portrange *port, const char *ifname, int ipproto) +{ + struct addrinfo hints, *res0, *res; + int error, cnt = 0; + struct sockaddr_in *sain; + struct sockaddr_in6 *sin6; + struct address *h; + + if ((cnt = host_if(s, al, max, port, ifname, ipproto)) != 0) + return (cnt); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; /* DUMMY */ + hints.ai_flags = AI_ADDRCONFIG; + error = getaddrinfo(s, NULL, &hints, &res0); + if (error == EAI_AGAIN || error == EAI_NODATA || error == EAI_NONAME) + return (0); + if (error) { + log_warnx("%s: could not parse \"%s\": %s", __func__, s, + gai_strerror(error)); + return (-1); + } + + for (res = res0; res && cnt < max; res = res->ai_next) { + if (res->ai_family != AF_INET && + res->ai_family != AF_INET6) + continue; + if ((h = calloc(1, sizeof(*h))) == NULL) + fatal(NULL); + + if (port != NULL) + memcpy(&h->port, port, sizeof(h->port)); + if (ifname != NULL) { + if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >= + sizeof(h->ifname)) + log_warnx("%s: interface name truncated", + __func__); + freeaddrinfo(res0); + free(h); + return (-1); + } + if (ipproto != -1) + h->ipproto = ipproto; + h->ss.ss_family = res->ai_family; + h->prefixlen = -1; /* host address */ + + if (res->ai_family == AF_INET) { + sain = (struct sockaddr_in *)&h->ss; + sain->sin_len = sizeof(struct sockaddr_in); + sain->sin_addr.s_addr = ((struct sockaddr_in *) + res->ai_addr)->sin_addr.s_addr; + } else { + sin6 = (struct sockaddr_in6 *)&h->ss; + sin6->sin6_len = sizeof(struct sockaddr_in6); + memcpy(&sin6->sin6_addr, &((struct sockaddr_in6 *) + res->ai_addr)->sin6_addr, sizeof(struct in6_addr)); + } + + TAILQ_INSERT_HEAD(al, h, entry); + cnt++; + } + if (cnt == max && res) { + log_warnx("%s: %s resolves to more than %d hosts", __func__, + s, max); + } + freeaddrinfo(res0); + return (cnt); +} + +int +host_if(const char *s, struct addresslist *al, int max, + struct portrange *port, const char *ifname, int ipproto) +{ + struct ifaddrs *ifap, *p; + struct sockaddr_in *sain; + struct sockaddr_in6 *sin6; + struct address *h; + int cnt = 0, af; + + if (getifaddrs(&ifap) == -1) + fatal("getifaddrs"); + + /* First search for IPv4 addresses */ + af = AF_INET; + + nextaf: + for (p = ifap; p != NULL && cnt < max; p = p->ifa_next) { + if (p->ifa_addr->sa_family != af || + (strcmp(s, p->ifa_name) != 0 && + !is_if_in_group(p->ifa_name, s))) + continue; + if ((h = calloc(1, sizeof(*h))) == NULL) + fatal("calloc"); + + if (port != NULL) + memcpy(&h->port, port, sizeof(h->port)); + if (ifname != NULL) { + if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >= + sizeof(h->ifname)) + log_warnx("%s: interface name truncated", + __func__); + freeifaddrs(ifap); + return (-1); + } + if (ipproto != -1) + h->ipproto = ipproto; + h->ss.ss_family = af; + h->prefixlen = -1; /* host address */ + + if (af == AF_INET) { + sain = (struct sockaddr_in *)&h->ss; + sain->sin_len = sizeof(struct sockaddr_in); + sain->sin_addr.s_addr = ((struct sockaddr_in *) + p->ifa_addr)->sin_addr.s_addr; + } else { + sin6 = (struct sockaddr_in6 *)&h->ss; + sin6->sin6_len = sizeof(struct sockaddr_in6); + memcpy(&sin6->sin6_addr, &((struct sockaddr_in6 *) + p->ifa_addr)->sin6_addr, sizeof(struct in6_addr)); + sin6->sin6_scope_id = ((struct sockaddr_in6 *) + p->ifa_addr)->sin6_scope_id; + } + + TAILQ_INSERT_HEAD(al, h, entry); + cnt++; + } + if (af == AF_INET) { + /* Next search for IPv6 addresses */ + af = AF_INET6; + goto nextaf; + } + + if (cnt > max) { + log_warnx("%s: %s resolves to more than %d hosts", __func__, + s, max); + } + freeifaddrs(ifap); + return (cnt); +} + +int +host(const char *s, struct addresslist *al, int max, + struct portrange *port, const char *ifname, int ipproto) +{ + struct address *h; + + if (strcmp("*", s) == 0) + s = "0.0.0.0"; + + h = host_v4(s); + + /* IPv6 address? */ + if (h == NULL) + h = host_v6(s); + + if (h != NULL) { + if (port != NULL) + memcpy(&h->port, port, sizeof(h->port)); + if (ifname != NULL) { + if (strlcpy(h->ifname, ifname, sizeof(h->ifname)) >= + sizeof(h->ifname)) { + log_warnx("%s: interface name truncated", + __func__); + free(h); + return (-1); + } + } + if (ipproto != -1) + h->ipproto = ipproto; + + TAILQ_INSERT_HEAD(al, h, entry); + return (1); + } + + return (host_dns(s, al, max, port, ifname, ipproto)); +} + +void +host_free(struct addresslist *al) +{ + struct address *h; + + while ((h = TAILQ_FIRST(al)) != NULL) { + TAILQ_REMOVE(al, h, entry); + free(h); + } +} + +struct server * +server_inherit(struct server *src, const char *name, + struct server_config *addr) +{ + struct server *dst, *s, *dstl; + + if ((dst = calloc(1, sizeof(*dst))) == NULL) + fatal("out of memory"); + + /* Copy the source server and assign a new Id */ + memcpy(&dst->srv_conf, &src->srv_conf, sizeof(dst->srv_conf)); + if ((dst->srv_conf.tls_cert_file = + strdup(src->srv_conf.tls_cert_file)) == NULL) + fatal("out of memory"); + if ((dst->srv_conf.tls_key_file = + strdup(src->srv_conf.tls_key_file)) == NULL) + fatal("out of memory"); + dst->srv_conf.tls_cert = NULL; + dst->srv_conf.tls_key = NULL; + + if (src->srv_conf.return_uri != NULL && + (dst->srv_conf.return_uri = + strdup(src->srv_conf.return_uri)) == NULL) + fatal("out of memory"); + + dst->srv_conf.id = ++last_server_id; + dst->srv_conf.parent_id = dst->srv_conf.id; + dst->srv_s = -1; + + if (last_server_id == INT_MAX) { + yyerror("too many servers defined"); + serverconfig_free(&dst->srv_conf); + free(dst); + return (NULL); + } + + /* Now set alias and listen address */ + strlcpy(dst->srv_conf.name, name, sizeof(dst->srv_conf.name)); + memcpy(&dst->srv_conf.ss, &addr->ss, sizeof(dst->srv_conf.ss)); + dst->srv_conf.port = addr->port; + dst->srv_conf.prefixlen = addr->prefixlen; + if (addr->flags & SRVFLAG_TLS) + dst->srv_conf.flags |= SRVFLAG_TLS; + else + dst->srv_conf.flags &= ~SRVFLAG_TLS; + + if (server_tls_load_keypair(dst) == -1) { + yyerror("failed to load public/private keys " + "for server %s", dst->srv_conf.name); + serverconfig_free(&dst->srv_conf); + free(dst); + return (NULL); + } + + /* Check if the new server already exists */ + TAILQ_FOREACH(s, conf->sc_servers, srv_entry) { + if ((s->srv_conf.flags & + SRVFLAG_LOCATION) == 0 && + strcmp(s->srv_conf.name, + dst->srv_conf.name) == 0 && + s->srv_conf.port == dst->srv_conf.port && + sockaddr_cmp( + (struct sockaddr *)&s->srv_conf.ss, + (struct sockaddr *)&dst->srv_conf.ss, + s->srv_conf.prefixlen) == 0) + break; + } + if (s != NULL) { + yyerror("server \"%s\" defined twice", + dst->srv_conf.name); + serverconfig_free(&dst->srv_conf); + free(dst); + return (NULL); + } + + /* Copy all the locations of the source server */ + TAILQ_FOREACH(s, conf->sc_servers, srv_entry) { + if (!(s->srv_conf.flags & SRVFLAG_LOCATION && + s->srv_conf.parent_id == src->srv_conf.parent_id)) + continue; + + if ((dstl = calloc(1, sizeof(*dstl))) == NULL) + fatal("out of memory"); + + memcpy(&dstl->srv_conf, &s->srv_conf, sizeof(dstl->srv_conf)); + strlcpy(dstl->srv_conf.name, name, sizeof(dstl->srv_conf.name)); + + /* Copy the new Id and listen address */ + dstl->srv_conf.id = ++last_server_id; + dstl->srv_conf.parent_id = dst->srv_conf.id; + memcpy(&dstl->srv_conf.ss, &addr->ss, + sizeof(dstl->srv_conf.ss)); + dstl->srv_conf.port = addr->port; + dstl->srv_conf.prefixlen = addr->prefixlen; + dstl->srv_s = -1; + + DPRINTF("adding location \"%s\" for \"%s[%u]\"", + dstl->srv_conf.location, + dstl->srv_conf.name, dstl->srv_conf.id); + + TAILQ_INSERT_TAIL(conf->sc_servers, dstl, srv_entry); + } + + return (dst); +} + +int +getservice(char *n) +{ + struct servent *s; + const char *errstr; + long long llval; + + llval = strtonum(n, 0, UINT16_MAX, &errstr); + if (errstr) { + s = getservbyname(n, "tcp"); + if (s == NULL) + s = getservbyname(n, "udp"); + if (s == NULL) { + yyerror("unknown port %s", n); + return (-1); + } + return (s->s_port); + } + + return (htons((u_short)llval)); +} + +int +is_if_in_group(const char *ifname, const char *groupname) +{ + unsigned int len; + struct ifgroupreq ifgr; + struct ifg_req *ifg; + int s; + int ret = 0; + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + err(1, "socket"); + + memset(&ifgr, 0, sizeof(ifgr)); + if (strlcpy(ifgr.ifgr_name, ifname, IFNAMSIZ) >= IFNAMSIZ) + err(1, "IFNAMSIZ"); + if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1) { + if (errno == EINVAL || errno == ENOTTY) + goto end; + err(1, "SIOCGIFGROUP"); + } + + len = ifgr.ifgr_len; + ifgr.ifgr_groups = + (struct ifg_req *)calloc(len / sizeof(struct ifg_req), + sizeof(struct ifg_req)); + if (ifgr.ifgr_groups == NULL) + err(1, "getifgroups"); + if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1) + err(1, "SIOCGIFGROUP"); + + ifg = ifgr.ifgr_groups; + for (; ifg && len >= sizeof(struct ifg_req); ifg++) { + len -= sizeof(struct ifg_req); + if (strcmp(ifg->ifgrq_group, groupname) == 0) { + ret = 1; + break; + } + } + free(ifgr.ifgr_groups); + +end: + close(s); + return (ret); +} diff --git a/httpd/proc.c b/httpd/proc.c new file mode 100644 index 0000000..34d65c0 --- /dev/null +++ b/httpd/proc.c @@ -0,0 +1,622 @@ +/* $OpenBSD: proc.c,v 1.8 2015/01/21 22:21:05 reyk Exp $ */ + +/* + * Copyright (c) 2010 - 2014 Reyk Floeter <reyk@openbsd.org> + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/wait.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <signal.h> +#include <pwd.h> +#include <event.h> +#include <imsg.h> + +#include "httpd.h" + +void proc_open(struct privsep *, struct privsep_proc *, + struct privsep_proc *, size_t); +void proc_close(struct privsep *); +int proc_ispeer(struct privsep_proc *, u_int, enum privsep_procid); +void proc_shutdown(struct privsep_proc *); +void proc_sig_handler(int, short, void *); +void proc_range(struct privsep *, enum privsep_procid, int *, int *); + +int +proc_ispeer(struct privsep_proc *procs, u_int nproc, enum privsep_procid type) +{ + u_int i; + + for (i = 0; i < nproc; i++) + if (procs[i].p_id == type) + return (1); + return (0); +} + +void +proc_init(struct privsep *ps, struct privsep_proc *procs, u_int nproc) +{ + u_int i, j, src, dst; + struct privsep_pipes *pp; + + /* + * Allocate pipes for all process instances (incl. parent) + * + * - ps->ps_pipes: N:M mapping + * N source processes connected to M destination processes: + * [src][instances][dst][instances], for example + * [PROC_RELAY][3][PROC_CA][3] + * + * - ps->ps_pp: per-process 1:M part of ps->ps_pipes + * Each process instance has a destination array of socketpair fds: + * [dst][instances], for example + * [PROC_PARENT][0] + */ + for (src = 0; src < PROC_MAX; src++) { + /* Allocate destination array for each process */ + if ((ps->ps_pipes[src] = calloc(ps->ps_ninstances, + sizeof(struct privsep_pipes))) == NULL) + fatal("proc_init: calloc"); + + for (i = 0; i < ps->ps_ninstances; i++) { + pp = &ps->ps_pipes[src][i]; + + for (dst = 0; dst < PROC_MAX; dst++) { + /* Allocate maximum fd integers */ + if ((pp->pp_pipes[dst] = + calloc(ps->ps_ninstances, + sizeof(int))) == NULL) + fatal("proc_init: calloc"); + + /* Mark fd as unused */ + for (j = 0; j < ps->ps_ninstances; j++) + pp->pp_pipes[dst][j] = -1; + } + } + } + + /* + * Setup and run the parent and its children + */ + privsep_process = PROC_PARENT; + ps->ps_instances[PROC_PARENT] = 1; + ps->ps_title[PROC_PARENT] = "parent"; + ps->ps_pid[PROC_PARENT] = getpid(); + ps->ps_pp = &ps->ps_pipes[privsep_process][0]; + + for (i = 0; i < nproc; i++) { + /* Default to 1 process instance */ + if (ps->ps_instances[procs[i].p_id] < 1) + ps->ps_instances[procs[i].p_id] = 1; + ps->ps_title[procs[i].p_id] = procs[i].p_title; + } + + proc_open(ps, NULL, procs, nproc); + + /* Engage! */ + for (i = 0; i < nproc; i++) + ps->ps_pid[procs[i].p_id] = (*procs[i].p_init)(ps, &procs[i]); +} + +void +proc_kill(struct privsep *ps) +{ + pid_t pid; + u_int i; + + if (privsep_process != PROC_PARENT) + return; + + for (i = 0; i < PROC_MAX; i++) { + if (ps->ps_pid[i] == 0) + continue; + killpg(ps->ps_pid[i], SIGTERM); + } + + do { + pid = waitpid(WAIT_ANY, NULL, 0); + } while (pid != -1 || (pid == -1 && errno == EINTR)); + + proc_close(ps); +} + +void +proc_open(struct privsep *ps, struct privsep_proc *p, + struct privsep_proc *procs, size_t nproc) +{ + struct privsep_pipes *pa, *pb; + int fds[2]; + u_int i, j, src, proc; + + if (p == NULL) + src = privsep_process; /* parent */ + else + src = p->p_id; + + /* + * Open socket pairs for our peers + */ + for (proc = 0; proc < nproc; proc++) { + procs[proc].p_ps = ps; + procs[proc].p_env = ps->ps_env; + + for (i = 0; i < ps->ps_instances[src]; i++) { + for (j = 0; j < ps->ps_instances[procs[proc].p_id]; + j++) { + pa = &ps->ps_pipes[src][i]; + pb = &ps->ps_pipes[procs[proc].p_id][j]; + + /* Check if fds are already set by peer */ + if (pa->pp_pipes[procs[proc].p_id][j] != -1) + continue; + + if (socketpair(AF_UNIX, SOCK_STREAM, + PF_UNSPEC, fds) == -1) + fatal("socketpair"); + + socket_set_blockmode(fds[0], BM_NONBLOCK); + socket_set_blockmode(fds[1], BM_NONBLOCK); + + pa->pp_pipes[procs[proc].p_id][j] = fds[0]; + pb->pp_pipes[src][i] = fds[1]; + } + } + } +} + +void +proc_listen(struct privsep *ps, struct privsep_proc *procs, size_t nproc) +{ + u_int i, dst, src, n, m; + struct privsep_pipes *pp; + + /* + * Close unused pipes + */ + for (src = 0; src < PROC_MAX; src++) { + for (n = 0; n < ps->ps_instances[src]; n++) { + /* Ingore current process */ + if (src == (u_int)privsep_process && + n == ps->ps_instance) + continue; + + pp = &ps->ps_pipes[src][n]; + + for (dst = 0; dst < PROC_MAX; dst++) { + if (src == dst) + continue; + for (m = 0; m < ps->ps_instances[dst]; m++) { + if (pp->pp_pipes[dst][m] == -1) + continue; + + /* Close and invalidate fd */ + close(pp->pp_pipes[dst][m]); + pp->pp_pipes[dst][m] = -1; + } + } + } + } + + src = privsep_process; + ps->ps_pp = pp = &ps->ps_pipes[src][ps->ps_instance]; + + /* + * Listen on appropriate pipes + */ + for (i = 0; i < nproc; i++) { + dst = procs[i].p_id; + + if (src == dst) + fatal("proc_listen: cannot peer with oneself"); + + if ((ps->ps_ievs[dst] = calloc(ps->ps_instances[dst], + sizeof(struct imsgev))) == NULL) + fatal("proc_open"); + + for (n = 0; n < ps->ps_instances[dst]; n++) { + if (pp->pp_pipes[dst][n] == -1) + continue; + + imsg_init(&(ps->ps_ievs[dst][n].ibuf), + pp->pp_pipes[dst][n]); + ps->ps_ievs[dst][n].handler = proc_dispatch; + ps->ps_ievs[dst][n].events = EV_READ; + ps->ps_ievs[dst][n].proc = &procs[i]; + ps->ps_ievs[dst][n].data = &ps->ps_ievs[dst][n]; + procs[i].p_instance = n; + + event_set(&(ps->ps_ievs[dst][n].ev), + ps->ps_ievs[dst][n].ibuf.fd, + ps->ps_ievs[dst][n].events, + ps->ps_ievs[dst][n].handler, + ps->ps_ievs[dst][n].data); + event_add(&(ps->ps_ievs[dst][n].ev), NULL); + } + } +} + +void +proc_close(struct privsep *ps) +{ + u_int dst, n; + struct privsep_pipes *pp; + + if (ps == NULL) + return; + + pp = ps->ps_pp; + + for (dst = 0; dst < PROC_MAX; dst++) { + if (ps->ps_ievs[dst] == NULL) + continue; + + for (n = 0; n < ps->ps_instances[dst]; n++) { + if (pp->pp_pipes[dst][n] == -1) + continue; + + /* Cancel the fd, close and invalidate the fd */ + event_del(&(ps->ps_ievs[dst][n].ev)); + imsg_clear(&(ps->ps_ievs[dst][n].ibuf)); + close(pp->pp_pipes[dst][n]); + pp->pp_pipes[dst][n] = -1; + } + free(ps->ps_ievs[dst]); + } +} + +void +proc_shutdown(struct privsep_proc *p) +{ + struct privsep *ps = p->p_ps; + + if (p->p_id == PROC_CONTROL && ps) + control_cleanup(&ps->ps_csock); + + if (p->p_shutdown != NULL) + (*p->p_shutdown)(); + + proc_close(ps); + + log_info("%s exiting, pid %d", p->p_title, getpid()); + + _exit(0); +} + +void +proc_sig_handler(int sig, short event, void *arg) +{ + struct privsep_proc *p = arg; + + switch (sig) { + case SIGINT: + case SIGTERM: + proc_shutdown(p); + break; + case SIGCHLD: + case SIGHUP: + case SIGPIPE: + case SIGUSR1: + /* ignore */ + break; + default: + fatalx("proc_sig_handler: unexpected signal"); + /* NOTREACHED */ + } +} + +pid_t +proc_run(struct privsep *ps, struct privsep_proc *p, + struct privsep_proc *procs, u_int nproc, + void (*init)(struct privsep *, struct privsep_proc *, void *), void *arg) +{ + pid_t pid; + struct passwd *pw; + const char *root; + struct control_sock *rcs; + u_int n; + + if (ps->ps_noaction) + return (0); + + proc_open(ps, p, procs, nproc); + + /* Fork child handlers */ + switch (pid = fork()) { + case -1: + fatal("proc_run: cannot fork"); + case 0: + /* Set the process group of the current process */ + setpgid(0, 0); + break; + default: + return (pid); + } + + pw = ps->ps_pw; + + if (p->p_id == PROC_CONTROL && ps->ps_instance == 0) { + if (control_init(ps, &ps->ps_csock) == -1) + fatalx(p->p_title); + TAILQ_FOREACH(rcs, &ps->ps_rcsocks, cs_entry) + if (control_init(ps, rcs) == -1) + fatalx(p->p_title); + } + + /* Change root directory */ + if (p->p_chroot != NULL) + root = p->p_chroot; + else + root = pw->pw_dir; + + if (chroot(root) == -1) + fatal("proc_run: chroot"); + if (chdir("/") == -1) + fatal("proc_run: chdir(\"/\")"); + + privsep_process = p->p_id; + + setproctitle("%s", p->p_title); + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("proc_run: cannot drop privileges"); + + /* Fork child handlers */ + for (n = 1; n < ps->ps_instances[p->p_id]; n++) { + if (fork() == 0) { + ps->ps_instance = p->p_instance = n; + break; + } + } + +#ifdef DEBUG + log_debug("%s: %s %d/%d, pid %d", __func__, p->p_title, + ps->ps_instance + 1, ps->ps_instances[p->p_id], getpid()); +#endif + + event_init(); + + signal_set(&ps->ps_evsigint, SIGINT, proc_sig_handler, p); + signal_set(&ps->ps_evsigterm, SIGTERM, proc_sig_handler, p); + signal_set(&ps->ps_evsigchld, SIGCHLD, proc_sig_handler, p); + signal_set(&ps->ps_evsighup, SIGHUP, proc_sig_handler, p); + signal_set(&ps->ps_evsigpipe, SIGPIPE, proc_sig_handler, p); + signal_set(&ps->ps_evsigusr1, SIGUSR1, proc_sig_handler, p); + + signal_add(&ps->ps_evsigint, NULL); + signal_add(&ps->ps_evsigterm, NULL); + signal_add(&ps->ps_evsigchld, NULL); + signal_add(&ps->ps_evsighup, NULL); + signal_add(&ps->ps_evsigpipe, NULL); + signal_add(&ps->ps_evsigusr1, NULL); + + proc_listen(ps, procs, nproc); + + if (p->p_id == PROC_CONTROL && ps->ps_instance == 0) { + TAILQ_INIT(&ctl_conns); + if (control_listen(&ps->ps_csock) == -1) + fatalx(p->p_title); + TAILQ_FOREACH(rcs, &ps->ps_rcsocks, cs_entry) + if (control_listen(rcs) == -1) + fatalx(p->p_title); + } + + if (init != NULL) + init(ps, p, arg); + + event_dispatch(); + + proc_shutdown(p); + + return (0); +} + +void +proc_dispatch(int fd, short event, void *arg) +{ + struct imsgev *iev = arg; + struct privsep_proc *p = iev->proc; + struct privsep *ps = p->p_ps; + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + int verbose; + const char *title; + + title = ps->ps_title[privsep_process]; + ibuf = &iev->ibuf; + + if (event & EV_READ) { + if ((n = imsg_read(ibuf)) == -1) + fatal(title); + if (n == 0) { + /* this pipe is dead, so remove the event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + return; + } + } + + if (event & EV_WRITE) { + if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN) + fatal(title); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal(title); + if (n == 0) + break; + +#if DEBUG > 1 + log_debug("%s: %s %d got imsg %d from %s %d", + __func__, title, ps->ps_instance + 1, + imsg.hdr.type, p->p_title, p->p_instance); +#endif + + /* + * Check the message with the program callback + */ + if ((p->p_cb)(fd, p, &imsg) == 0) { + /* Message was handled by the callback, continue */ + imsg_free(&imsg); + continue; + } + + /* + * Generic message handling + */ + switch (imsg.hdr.type) { + case IMSG_CTL_VERBOSE: + IMSG_SIZE_CHECK(&imsg, &verbose); + memcpy(&verbose, imsg.data, sizeof(verbose)); + log_verbose(verbose); + break; + default: + log_warnx("%s: %s %d got invalid imsg %d from %s %d", + __func__, title, ps->ps_instance + 1, + imsg.hdr.type, p->p_title, p->p_instance); + fatalx(title); + } + imsg_free(&imsg); + } + imsg_event_add(iev); +} + +/* + * imsg helper functions + */ + +void +imsg_event_add(struct imsgev *iev) +{ + if (iev->handler == NULL) { + imsg_flush(&iev->ibuf); + return; + } + + iev->events = EV_READ; + if (iev->ibuf.w.queued) + iev->events |= EV_WRITE; + + event_del(&iev->ev); + event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev->data); + event_add(&iev->ev, NULL); +} + +int +imsg_compose_event(struct imsgev *iev, u_int16_t type, u_int32_t peerid, + pid_t pid, int fd, void *data, u_int16_t datalen) +{ + int ret; + + if ((ret = imsg_compose(&iev->ibuf, type, peerid, + pid, fd, data, datalen)) == -1) + return (ret); + imsg_event_add(iev); + return (ret); +} + +int +imsg_composev_event(struct imsgev *iev, u_int16_t type, u_int32_t peerid, + pid_t pid, int fd, const struct iovec *iov, int iovcnt) +{ + int ret; + + if ((ret = imsg_composev(&iev->ibuf, type, peerid, + pid, fd, iov, iovcnt)) == -1) + return (ret); + imsg_event_add(iev); + return (ret); +} + +void +proc_range(struct privsep *ps, enum privsep_procid id, int *n, int *m) +{ + if (*n == -1) { + /* Use a range of all target instances */ + *n = 0; + *m = ps->ps_instances[id]; + } else { + /* Use only a single slot of the specified peer process */ + *m = *n + 1; + } +} + +int +proc_compose_imsg(struct privsep *ps, enum privsep_procid id, int n, + u_int16_t type, int fd, void *data, u_int16_t datalen) +{ + int m; + + proc_range(ps, id, &n, &m); + for (; n < m; n++) { + if (imsg_compose_event(&ps->ps_ievs[id][n], + type, -1, 0, fd, data, datalen) == -1) + return (-1); + } + + return (0); +} + +int +proc_composev_imsg(struct privsep *ps, enum privsep_procid id, int n, + u_int16_t type, int fd, const struct iovec *iov, int iovcnt) +{ + int m; + + proc_range(ps, id, &n, &m); + for (; n < m; n++) + if (imsg_composev_event(&ps->ps_ievs[id][n], + type, -1, 0, fd, iov, iovcnt) == -1) + return (-1); + + return (0); +} + +int +proc_forward_imsg(struct privsep *ps, struct imsg *imsg, + enum privsep_procid id, int n) +{ + return (proc_compose_imsg(ps, id, n, imsg->hdr.type, + imsg->fd, imsg->data, IMSG_DATA_SIZE(imsg))); +} + +struct imsgbuf * +proc_ibuf(struct privsep *ps, enum privsep_procid id, int n) +{ + int m; + + proc_range(ps, id, &n, &m); + return (&ps->ps_ievs[id][n].ibuf); +} + +struct imsgev * +proc_iev(struct privsep *ps, enum privsep_procid id, int n) +{ + int m; + + proc_range(ps, id, &n, &m); + return (&ps->ps_ievs[id][n]); +} diff --git a/httpd/server.c b/httpd/server.c new file mode 100644 index 0000000..813f945 --- /dev/null +++ b/httpd/server.c @@ -0,0 +1,1221 @@ +/* $OpenBSD: server.c,v 1.60 2015/02/23 09:52:28 reyk Exp $ */ + +/* + * Copyright (c) 2006 - 2015 Reyk Floeter <reyk@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/param.h> /* nitems */ +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <sys/tree.h> + +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <limits.h> +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <event.h> +#include <imsg.h> +#include <tls.h> + +#include "httpd.h" + +#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) + +int server_dispatch_parent(int, struct privsep_proc *, + struct imsg *); +int server_dispatch_logger(int, struct privsep_proc *, + struct imsg *); +void server_shutdown(void); + +void server_init(struct privsep *, struct privsep_proc *p, void *); +void server_launch(void); +int server_socket(struct sockaddr_storage *, in_port_t, + struct server_config *, int, int); +int server_socket_listen(struct sockaddr_storage *, in_port_t, + struct server_config *); + +int server_tls_init(struct server *); +void server_tls_readcb(int, short, void *); +void server_tls_writecb(int, short, void *); + +void server_accept(int, short, void *); +void server_accept_tls(int, short, void *); +void server_input(struct client *); + +extern void bufferevent_read_pressure_cb(struct evbuffer *, size_t, + size_t, void *); + +volatile int server_clients; +volatile int server_inflight = 0; +u_int32_t server_cltid; + +static struct httpd *env = NULL; +int proc_id; + +static struct privsep_proc procs[] = { + { "parent", PROC_PARENT, server_dispatch_parent }, + { "logger", PROC_LOGGER, server_dispatch_logger } +}; + +pid_t +server(struct privsep *ps, struct privsep_proc *p) +{ + pid_t pid; + env = ps->ps_env; + pid = proc_run(ps, p, procs, nitems(procs), server_init, NULL); + server_http(env); + return (pid); +} + +void +server_shutdown(void) +{ + config_purge(env, CONFIG_ALL); + usleep(200); /* XXX server needs to shutdown last */ +} + +int +server_privinit(struct server *srv) +{ + struct server *s; + + if (srv->srv_conf.flags & SRVFLAG_LOCATION) + return (0); + + log_debug("%s: adding server %s", __func__, srv->srv_conf.name); + + /* + * There's no need to open a new socket if a server with the + * same address already exists. + */ + TAILQ_FOREACH(s, env->sc_servers, srv_entry) { + if (s != srv && s->srv_s != -1 && + s->srv_conf.port == srv->srv_conf.port && + sockaddr_cmp((struct sockaddr *)&s->srv_conf.ss, + (struct sockaddr *)&srv->srv_conf.ss, + s->srv_conf.prefixlen) == 0) + return (0); + } + + /* Open listening socket in the privileged process */ + if ((srv->srv_s = server_socket_listen(&srv->srv_conf.ss, + srv->srv_conf.port, &srv->srv_conf)) == -1) + return (-1); + + return (0); +} + +int +server_tls_load_keypair(struct server *srv) +{ + if ((srv->srv_conf.flags & SRVFLAG_TLS) == 0) + return (0); + + if ((srv->srv_conf.tls_cert = tls_load_file( + srv->srv_conf.tls_cert_file, &srv->srv_conf.tls_cert_len, + NULL)) == NULL) + return (-1); + log_debug("%s: using certificate %s", __func__, + srv->srv_conf.tls_cert_file); + + /* XXX allow to specify password for encrypted key */ + if ((srv->srv_conf.tls_key = tls_load_file( + srv->srv_conf.tls_key_file, &srv->srv_conf.tls_key_len, + NULL)) == NULL) + return (-1); + log_debug("%s: using private key %s", __func__, + srv->srv_conf.tls_key_file); + + return (0); +} + +int +server_tls_init(struct server *srv) +{ + if ((srv->srv_conf.flags & SRVFLAG_TLS) == 0) + return (0); + + log_debug("%s: setting up TLS for %s", __func__, srv->srv_conf.name); + + if (tls_init() != 0) { + log_warn("%s: failed to initialise tls", __func__); + return (-1); + } + if ((srv->srv_tls_config = tls_config_new()) == NULL) { + log_warn("%s: failed to get tls config", __func__); + return (-1); + } + if ((srv->srv_tls_ctx = tls_server()) == NULL) { + log_warn("%s: failed to get tls server", __func__); + return (-1); + } + + tls_config_set_protocols(srv->srv_tls_config, + srv->srv_conf.tls_protocols); + + if (tls_config_set_ciphers(srv->srv_tls_config, + srv->srv_conf.tls_ciphers) != 0) { + log_warn("%s: failed to set tls ciphers", __func__); + return (-1); + } + if (tls_config_set_dheparams(srv->srv_tls_config, + srv->srv_conf.tls_dhe_params) != 0) { + log_warn("%s: failed to set tls dhe params", __func__); + return (-1); + } + if (tls_config_set_ecdhecurve(srv->srv_tls_config, + srv->srv_conf.tls_ecdhe_curve) != 0) { + log_warn("%s: failed to set tls ecdhe curve", __func__); + return (-1); + } + + if (tls_config_set_cert_mem(srv->srv_tls_config, + srv->srv_conf.tls_cert, srv->srv_conf.tls_cert_len) != 0) { + log_warn("%s: failed to set tls cert", __func__); + return (-1); + } + if (tls_config_set_key_mem(srv->srv_tls_config, + srv->srv_conf.tls_key, srv->srv_conf.tls_key_len) != 0) { + log_warn("%s: failed to set tls key", __func__); + return (-1); + } + + if (tls_configure(srv->srv_tls_ctx, srv->srv_tls_config) != 0) { + log_warn("%s: failed to configure TLS - %s", __func__, + tls_error(srv->srv_tls_ctx)); + return (-1); + } + + /* We're now done with the public/private key... */ + tls_config_clear_keys(srv->srv_tls_config); + explicit_bzero(srv->srv_conf.tls_cert, srv->srv_conf.tls_cert_len); + explicit_bzero(srv->srv_conf.tls_key, srv->srv_conf.tls_key_len); + free(srv->srv_conf.tls_cert); + free(srv->srv_conf.tls_key); + srv->srv_conf.tls_cert = NULL; + srv->srv_conf.tls_key = NULL; + srv->srv_conf.tls_cert_len = 0; + srv->srv_conf.tls_key_len = 0; + + return (0); +} + +void +server_init(struct privsep *ps, struct privsep_proc *p, void *arg) +{ + server_http(ps->ps_env); + + if (config_init(ps->ps_env) == -1) + fatal("failed to initialize configuration"); + + /* Set to current prefork id */ + proc_id = p->p_instance; + + /* We use a custom shutdown callback */ + p->p_shutdown = server_shutdown; + + /* Unlimited file descriptors (use system limits) */ + socket_rlimit(-1); + +#if 0 + /* Schedule statistics timer */ + evtimer_set(&env->sc_statev, server_statistics, NULL); + memcpy(&tv, &env->sc_statinterval, sizeof(tv)); + evtimer_add(&env->sc_statev, &tv); +#endif +} + +void +server_launch(void) +{ + struct server *srv; + + TAILQ_FOREACH(srv, env->sc_servers, srv_entry) { + server_tls_init(srv); + server_http_init(srv); + + log_debug("%s: running server %s", __func__, + srv->srv_conf.name); + + event_set(&srv->srv_ev, srv->srv_s, EV_READ, + server_accept, srv); + event_add(&srv->srv_ev, NULL); + evtimer_set(&srv->srv_evt, server_accept, srv); + } +} + +void +server_purge(struct server *srv) +{ + struct client *clt; + struct server_config *srv_conf; + + /* shutdown and remove server */ + if (event_initialized(&srv->srv_ev)) + event_del(&srv->srv_ev); + if (evtimer_initialized(&srv->srv_evt)) + evtimer_del(&srv->srv_evt); + + if (srv->srv_s != -1) + close(srv->srv_s); + TAILQ_REMOVE(env->sc_servers, srv, srv_entry); + + /* cleanup sessions */ + while ((clt = + SPLAY_ROOT(&srv->srv_clients)) != NULL) + server_close(clt, NULL); + + /* cleanup hosts */ + while ((srv_conf = + TAILQ_FIRST(&srv->srv_hosts)) != NULL) { + TAILQ_REMOVE(&srv->srv_hosts, srv_conf, entry); + + /* It might point to our own "default" entry */ + if (srv_conf != &srv->srv_conf) { + serverconfig_free(srv_conf); + free(srv_conf); + } + } + + tls_config_free(srv->srv_tls_config); + tls_free(srv->srv_tls_ctx); + + free(srv); +} + +void +serverconfig_free(struct server_config *srv_conf) +{ + free(srv_conf->return_uri); + free(srv_conf->tls_cert_file); + free(srv_conf->tls_cert); + free(srv_conf->tls_key_file); + free(srv_conf->tls_key); +} + +void +serverconfig_reset(struct server_config *srv_conf) +{ + srv_conf->tls_cert_file = srv_conf->tls_key_file = NULL; + srv_conf->tls_cert = srv_conf->tls_key = NULL; + srv_conf->return_uri = NULL; + srv_conf->auth = NULL; +} + +struct server * +server_byaddr(struct sockaddr *addr, in_port_t port) +{ + struct server *srv; + + TAILQ_FOREACH(srv, env->sc_servers, srv_entry) { + if (port == srv->srv_conf.port && + sockaddr_cmp((struct sockaddr *)&srv->srv_conf.ss, + addr, srv->srv_conf.prefixlen) == 0) + return (srv); + } + + return (NULL); +} + +struct server_config * +serverconfig_byid(u_int32_t id) +{ + struct server *srv; + struct server_config *srv_conf; + + TAILQ_FOREACH(srv, env->sc_servers, srv_entry) { + if (srv->srv_conf.id == id) + return (&srv->srv_conf); + TAILQ_FOREACH(srv_conf, &srv->srv_hosts, entry) { + if (srv_conf->id == id) + return (srv_conf); + } + } + + return (NULL); +} + +int +server_foreach(int (*srv_cb)(struct server *, + struct server_config *, void *), void *arg) +{ + struct server *srv; + struct server_config *srv_conf; + + TAILQ_FOREACH(srv, env->sc_servers, srv_entry) { + if ((srv_cb)(srv, &srv->srv_conf, arg) == -1) + return (-1); + TAILQ_FOREACH(srv_conf, &srv->srv_hosts, entry) { + if ((srv_cb)(srv, srv_conf, arg) == -1) + return (-1); + } + } + + return (0); +} + +int +server_socket_af(struct sockaddr_storage *ss, in_port_t port) +{ + switch (ss->ss_family) { + case AF_INET: + ((struct sockaddr_in *)ss)->sin_port = port; + ((struct sockaddr_in *)ss)->sin_len = + sizeof(struct sockaddr_in); + break; + case AF_INET6: + ((struct sockaddr_in6 *)ss)->sin6_port = port; + ((struct sockaddr_in6 *)ss)->sin6_len = + sizeof(struct sockaddr_in6); + break; + default: + return (-1); + } + + return (0); +} + +in_port_t +server_socket_getport(struct sockaddr_storage *ss) +{ + switch (ss->ss_family) { + case AF_INET: + return (((struct sockaddr_in *)ss)->sin_port); + case AF_INET6: + return (((struct sockaddr_in6 *)ss)->sin6_port); + default: + return (0); + } + + /* NOTREACHED */ + return (0); +} + +int +server_socket(struct sockaddr_storage *ss, in_port_t port, + struct server_config *srv_conf, int fd, int reuseport) +{ + struct linger lng; + int s = -1, val; + + if (server_socket_af(ss, port) == -1) + goto bad; + + s = fd == -1 ? socket(ss->ss_family, SOCK_STREAM, IPPROTO_TCP) : fd; + if (s == -1) + goto bad; + + /* + * Socket options + */ + memset(&lng, 0, sizeof(lng)); + if (setsockopt(s, SOL_SOCKET, SO_LINGER, &lng, sizeof(lng)) == -1) + goto bad; + if (reuseport) { + val = 1; + if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &val, + sizeof(int)) == -1) + goto bad; + } + if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) + goto bad; + if (srv_conf->tcpflags & TCPFLAG_BUFSIZ) { + val = srv_conf->tcpbufsiz; + if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, + &val, sizeof(val)) == -1) + goto bad; + val = srv_conf->tcpbufsiz; + if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, + &val, sizeof(val)) == -1) + goto bad; + } + + /* + * IP options + */ + if (srv_conf->tcpflags & TCPFLAG_IPTTL) { + val = (int)srv_conf->tcpipttl; + if (setsockopt(s, IPPROTO_IP, IP_TTL, + &val, sizeof(val)) == -1) + goto bad; + } + if (srv_conf->tcpflags & TCPFLAG_IPMINTTL) { + val = (int)srv_conf->tcpipminttl; + if (setsockopt(s, IPPROTO_IP, IP_MINTTL, + &val, sizeof(val)) == -1) + goto bad; + } + + /* + * TCP options + */ + if (srv_conf->tcpflags & (TCPFLAG_NODELAY|TCPFLAG_NNODELAY)) { + if (srv_conf->tcpflags & TCPFLAG_NNODELAY) + val = 0; + else + val = 1; + if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, + &val, sizeof(val)) == -1) + goto bad; + } + if (srv_conf->tcpflags & (TCPFLAG_SACK|TCPFLAG_NSACK)) { + if (srv_conf->tcpflags & TCPFLAG_NSACK) + val = 0; + else + val = 1; + if (setsockopt(s, IPPROTO_TCP, TCP_SACK_ENABLE, + &val, sizeof(val)) == -1) + goto bad; + } + + return (s); + + bad: + if (s != -1) + close(s); + return (-1); +} + +int +server_socket_listen(struct sockaddr_storage *ss, in_port_t port, + struct server_config *srv_conf) +{ + int s; + + if ((s = server_socket(ss, port, srv_conf, -1, 1)) == -1) + return (-1); + + if (bind(s, (struct sockaddr *)ss, ss->ss_len) == -1) + goto bad; + if (listen(s, srv_conf->tcpbacklog) == -1) + goto bad; + + return (s); + + bad: + close(s); + return (-1); +} + +int +server_socket_connect(struct sockaddr_storage *ss, in_port_t port, + struct server_config *srv_conf) +{ + int s; + + if ((s = server_socket(ss, port, srv_conf, -1, 0)) == -1) + return (-1); + + if (connect(s, (struct sockaddr *)ss, ss->ss_len) == -1) { + if (errno != EINPROGRESS) + goto bad; + } + + return (s); + + bad: + close(s); + return (-1); +} + +void +server_tls_readcb(int fd, short event, void *arg) +{ + struct bufferevent *bufev = arg; + struct client *clt = bufev->cbarg; + char rbuf[IBUF_READ_SIZE]; + int what = EVBUFFER_READ; + int howmuch = IBUF_READ_SIZE; + int ret; + size_t len; + + if (event == EV_TIMEOUT) { + what |= EVBUFFER_TIMEOUT; + goto err; + } + + if (bufev->wm_read.high != 0) + howmuch = MINIMUM(sizeof(rbuf), bufev->wm_read.high); + + ret = tls_read(clt->clt_tls_ctx, rbuf, howmuch, &len); + if (ret == TLS_READ_AGAIN || ret == TLS_WRITE_AGAIN) { + goto retry; + } else if (ret != 0) { + what |= EVBUFFER_ERROR; + goto err; + } + + if (evbuffer_add(bufev->input, rbuf, len) == -1) { + what |= EVBUFFER_ERROR; + goto err; + } + + server_bufferevent_add(&bufev->ev_read, bufev->timeout_read); + + len = EVBUFFER_LENGTH(bufev->input); + if (bufev->wm_read.low != 0 && len < bufev->wm_read.low) + return; + if (bufev->wm_read.high != 0 && len > bufev->wm_read.high) { + struct evbuffer *buf = bufev->input; + event_del(&bufev->ev_read); + evbuffer_setcb(buf, bufferevent_read_pressure_cb, bufev); + return; + } + + if (bufev->readcb != NULL) + (*bufev->readcb)(bufev, bufev->cbarg); + return; + + retry: + server_bufferevent_add(&bufev->ev_read, bufev->timeout_read); + return; + + err: + (*bufev->errorcb)(bufev, what, bufev->cbarg); +} + +void +server_tls_writecb(int fd, short event, void *arg) +{ + struct bufferevent *bufev = arg; + struct client *clt = bufev->cbarg; + int ret; + short what = EVBUFFER_WRITE; + size_t len; + + if (event == EV_TIMEOUT) { + what |= EVBUFFER_TIMEOUT; + goto err; + } + + if (EVBUFFER_LENGTH(bufev->output)) { + if (clt->clt_buf == NULL) { + clt->clt_buflen = EVBUFFER_LENGTH(bufev->output); + if ((clt->clt_buf = malloc(clt->clt_buflen)) == NULL) { + what |= EVBUFFER_ERROR; + goto err; + } + bcopy(EVBUFFER_DATA(bufev->output), + clt->clt_buf, clt->clt_buflen); + } + ret = tls_write(clt->clt_tls_ctx, clt->clt_buf, + clt->clt_buflen, &len); + if (ret == TLS_READ_AGAIN || ret == TLS_WRITE_AGAIN) { + goto retry; + } else if (ret != 0) { + what |= EVBUFFER_ERROR; + goto err; + } + evbuffer_drain(bufev->output, len); + } + if (clt->clt_buf != NULL) { + free(clt->clt_buf); + clt->clt_buf = NULL; + clt->clt_buflen = 0; + } + + if (EVBUFFER_LENGTH(bufev->output) != 0) + server_bufferevent_add(&bufev->ev_write, bufev->timeout_write); + + if (bufev->writecb != NULL && + EVBUFFER_LENGTH(bufev->output) <= bufev->wm_write.low) + (*bufev->writecb)(bufev, bufev->cbarg); + return; + + retry: + if (clt->clt_buflen != 0) + server_bufferevent_add(&bufev->ev_write, bufev->timeout_write); + return; + + err: + if (clt->clt_buf != NULL) { + free(clt->clt_buf); + clt->clt_buf = NULL; + clt->clt_buflen = 0; + } + (*bufev->errorcb)(bufev, what, bufev->cbarg); +} + +void +server_input(struct client *clt) +{ + struct server_config *srv_conf = clt->clt_srv_conf; + evbuffercb inrd = server_read; + evbuffercb inwr = server_write; + socklen_t slen; + + if (server_httpdesc_init(clt) == -1) { + server_close(clt, "failed to allocate http descriptor"); + return; + } + + clt->clt_toread = TOREAD_HTTP_HEADER; + inrd = server_read_http; + + slen = sizeof(clt->clt_sndbufsiz); + if (getsockopt(clt->clt_s, SOL_SOCKET, SO_SNDBUF, + &clt->clt_sndbufsiz, &slen) == -1) { + server_close(clt, "failed to get send buffer size"); + return; + } + + /* + * Client <-> Server + */ + clt->clt_bev = bufferevent_new(clt->clt_s, inrd, inwr, + server_error, clt); + if (clt->clt_bev == NULL) { + server_close(clt, "failed to allocate input buffer event"); + return; + } + + if (srv_conf->flags & SRVFLAG_TLS) { + event_set(&clt->clt_bev->ev_read, clt->clt_s, EV_READ, + server_tls_readcb, clt->clt_bev); + event_set(&clt->clt_bev->ev_write, clt->clt_s, EV_WRITE, + server_tls_writecb, clt->clt_bev); + } + + /* Adjust write watermark to the socket buffer output size */ + bufferevent_setwatermark(clt->clt_bev, EV_WRITE, + clt->clt_sndbufsiz, 0); + /* Read at most amount of data that fits in one fcgi record. */ + bufferevent_setwatermark(clt->clt_bev, EV_READ, 0, FCGI_CONTENT_SIZE); + + bufferevent_settimeout(clt->clt_bev, + srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec); + bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); +} + +void +server_write(struct bufferevent *bev, void *arg) +{ + struct client *clt = arg; + struct evbuffer *dst = EVBUFFER_OUTPUT(bev); + + if (EVBUFFER_LENGTH(dst) == 0 && + clt->clt_toread == TOREAD_HTTP_NONE) + goto done; + + getmonotime(&clt->clt_tv_last); + + if (clt->clt_done) + goto done; + + bufferevent_enable(bev, EV_READ); + return; + done: + (*bev->errorcb)(bev, EVBUFFER_WRITE|EVBUFFER_EOF, bev->cbarg); + return; +} + +void +server_dump(struct client *clt, const void *buf, size_t len) +{ + size_t outlen; + + if (!len) + return; + + /* + * This function will dump the specified message directly + * to the underlying client, without waiting for success + * of non-blocking events etc. This is useful to print an + * error message before gracefully closing the client. + */ + if (clt->clt_tls_ctx != NULL) + (void)tls_write(clt->clt_tls_ctx, buf, len, &outlen); + else + (void)write(clt->clt_s, buf, len); +} + +void +server_read(struct bufferevent *bev, void *arg) +{ + struct client *clt = arg; + struct evbuffer *src = EVBUFFER_INPUT(bev); + + getmonotime(&clt->clt_tv_last); + + if (!EVBUFFER_LENGTH(src)) + return; + if (server_bufferevent_write_buffer(clt, src) == -1) + goto fail; + if (clt->clt_done) + goto done; + return; + done: + (*bev->errorcb)(bev, EVBUFFER_READ|EVBUFFER_EOF, bev->cbarg); + return; + fail: + server_close(clt, strerror(errno)); +} + +void +server_error(struct bufferevent *bev, short error, void *arg) +{ + struct client *clt = arg; + struct evbuffer *dst; + + if (error & EVBUFFER_TIMEOUT) { + server_close(clt, "buffer event timeout"); + return; + } + if (error & EVBUFFER_ERROR) { + if (errno == EFBIG) { + bufferevent_enable(bev, EV_READ); + return; + } + server_close(clt, "buffer event error"); + return; + } + if (error & (EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF)) { + bufferevent_disable(bev, EV_READ|EV_WRITE); + + clt->clt_done = 1; + + dst = EVBUFFER_OUTPUT(clt->clt_bev); + if (EVBUFFER_LENGTH(dst)) { + /* Finish writing all data first */ + bufferevent_enable(clt->clt_bev, EV_WRITE); + return; + } + + server_close(clt, "done"); + return; + } + server_close(clt, "unknown event error"); + return; +} + +void +server_accept(int fd, short event, void *arg) +{ + struct server *srv = arg; + struct client *clt = NULL; + socklen_t slen; + struct sockaddr_storage ss; + int s = -1; + + event_add(&srv->srv_ev, NULL); + if ((event & EV_TIMEOUT)) + return; + + slen = sizeof(ss); + if ((s = accept_reserve(fd, (struct sockaddr *)&ss, + &slen, FD_RESERVE, &server_inflight)) == -1) { + /* + * Pause accept if we are out of file descriptors, or + * libevent will haunt us here too. + */ + if (errno == ENFILE || errno == EMFILE) { + struct timeval evtpause = { 1, 0 }; + + event_del(&srv->srv_ev); + evtimer_add(&srv->srv_evt, &evtpause); + log_debug("%s: deferring connections", __func__); + } + return; + } + if (server_clients >= SERVER_MAX_CLIENTS) + goto err; + + if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) + goto err; + + if ((clt = calloc(1, sizeof(*clt))) == NULL) + goto err; + + clt->clt_s = s; + clt->clt_fd = -1; + clt->clt_toread = TOREAD_UNLIMITED; + clt->clt_srv = srv; + clt->clt_srv_conf = &srv->srv_conf; + clt->clt_id = ++server_cltid; + clt->clt_srv_id = srv->srv_conf.id; + clt->clt_pid = getpid(); + clt->clt_inflight = 1; + + /* get local address */ + slen = sizeof(clt->clt_srv_ss); + if (getsockname(s, (struct sockaddr *)&clt->clt_srv_ss, + &slen) == -1) { + server_close(clt, "listen address lookup failed"); + return; + } + + /* get client address */ + memcpy(&clt->clt_ss, &ss, sizeof(clt->clt_ss)); + + /* get ports */ + switch (ss.ss_family) { + case AF_INET: + clt->clt_port = ((struct sockaddr_in *)&ss)->sin_port; + break; + case AF_INET6: + clt->clt_port = ((struct sockaddr_in6 *)&ss)->sin6_port; + break; + } + + getmonotime(&clt->clt_tv_start); + memcpy(&clt->clt_tv_last, &clt->clt_tv_start, sizeof(clt->clt_tv_last)); + + server_clients++; + SPLAY_INSERT(client_tree, &srv->srv_clients, clt); + + /* Increment the per-relay client counter */ + //srv->srv_stats[proc_id].last++; + + /* Pre-allocate output buffer */ + clt->clt_output = evbuffer_new(); + if (clt->clt_output == NULL) { + server_close(clt, "failed to allocate output buffer"); + return; + } + + /* Pre-allocate log buffer */ + clt->clt_log = evbuffer_new(); + if (clt->clt_log == NULL) { + server_close(clt, "failed to allocate log buffer"); + return; + } + + if (srv->srv_conf.flags & SRVFLAG_TLS) { + event_again(&clt->clt_ev, clt->clt_s, EV_TIMEOUT|EV_READ, + server_accept_tls, &clt->clt_tv_start, + &srv->srv_conf.timeout, clt); + return; + } + + server_input(clt); + return; + + err: + if (s != -1) { + close(s); + if (clt != NULL) + free(clt); + /* + * the client struct was not completly set up, but still + * counted as an inflight client. account for this. + */ + server_inflight_dec(clt, __func__); + } +} + +void +server_accept_tls(int fd, short event, void *arg) +{ + struct client *clt = (struct client *)arg; + struct server *srv = (struct server *)clt->clt_srv; + int ret; + + if (event == EV_TIMEOUT) { + server_close(clt, "TLS accept timeout"); + return; + } + + if (srv->srv_tls_ctx == NULL) + fatalx("NULL tls context"); + + ret = tls_accept_socket(srv->srv_tls_ctx, &clt->clt_tls_ctx, + clt->clt_s); + if (ret == TLS_READ_AGAIN) { + event_again(&clt->clt_ev, clt->clt_s, EV_TIMEOUT|EV_READ, + server_accept_tls, &clt->clt_tv_start, + &srv->srv_conf.timeout, clt); + } else if (ret == TLS_WRITE_AGAIN) { + event_again(&clt->clt_ev, clt->clt_s, EV_TIMEOUT|EV_WRITE, + server_accept_tls, &clt->clt_tv_start, + &srv->srv_conf.timeout, clt); + } else if (ret != 0) { + log_warnx("%s: TLS accept failed - %s", __func__, + tls_error(srv->srv_tls_ctx)); + return; + } + + server_input(clt); + return; +} + +void +server_inflight_dec(struct client *clt, const char *why) +{ + if (clt != NULL) { + /* the flight already left inflight mode. */ + if (clt->clt_inflight == 0) + return; + clt->clt_inflight = 0; + } + + /* the file was never opened, thus this was an inflight client. */ + server_inflight--; + DPRINTF("%s: inflight decremented, now %d, %s", + __func__, server_inflight, why); +} + +void +server_sendlog(struct server_config *srv_conf, int cmd, const char *emsg, ...) +{ + va_list ap; + char *msg; + int ret; + struct iovec iov[2]; + + if (srv_conf->flags & SRVFLAG_SYSLOG) { + va_start(ap, emsg); + if (cmd == IMSG_LOG_ACCESS) + vlog(LOG_INFO, emsg, ap); + else + vlog(LOG_DEBUG, emsg, ap); + va_end(ap); + return; + } + + va_start(ap, emsg); + ret = vasprintf(&msg, emsg, ap); + va_end(ap); + if (ret == -1) { + log_warn("%s: vasprintf", __func__); + return; + } + + iov[0].iov_base = &srv_conf->id; + iov[0].iov_len = sizeof(srv_conf->id); + iov[1].iov_base = msg; + iov[1].iov_len = strlen(msg) + 1; + + proc_composev_imsg(env->sc_ps, PROC_LOGGER, -1, cmd, -1, iov, 2); +} + +void +server_log(struct client *clt, const char *msg) +{ + char ibuf[HOST_NAME_MAX+1], obuf[HOST_NAME_MAX+1]; + struct server_config *srv_conf = clt->clt_srv_conf; + char *ptr = NULL; + int debug_cmd = -1; + extern int verbose; + + switch (srv_conf->logformat) { + case LOG_FORMAT_CONNECTION: + debug_cmd = IMSG_LOG_ACCESS; + break; + default: + if (verbose > 1) + debug_cmd = IMSG_LOG_ERROR; + if (EVBUFFER_LENGTH(clt->clt_log)) { + while ((ptr = + evbuffer_readline(clt->clt_log)) != NULL) { + server_sendlog(srv_conf, + IMSG_LOG_ACCESS, "%s", ptr); + free(ptr); + } + } + break; + } + + if (debug_cmd != -1 && msg != NULL) { + memset(ibuf, 0, sizeof(ibuf)); + memset(obuf, 0, sizeof(obuf)); + (void)print_host(&clt->clt_ss, ibuf, sizeof(ibuf)); + (void)server_http_host(&clt->clt_srv_ss, obuf, sizeof(obuf)); + if (EVBUFFER_LENGTH(clt->clt_log) && + evbuffer_add_printf(clt->clt_log, "\n") != -1) + ptr = evbuffer_readline(clt->clt_log); + server_sendlog(srv_conf, debug_cmd, "server %s, " + "client %d (%d active), %s:%u -> %s, " + "%s%s%s", srv_conf->name, clt->clt_id, server_clients, + ibuf, ntohs(clt->clt_port), obuf, msg, + ptr == NULL ? "" : ",", ptr == NULL ? "" : ptr); + if (ptr != NULL) + free(ptr); + } +} + +void +server_close(struct client *clt, const char *msg) +{ + struct server *srv = clt->clt_srv; + + SPLAY_REMOVE(client_tree, &srv->srv_clients, clt); + + /* free the HTTP descriptors incl. headers */ + server_close_http(clt); + + event_del(&clt->clt_ev); + if (clt->clt_bev != NULL) + bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); + if (clt->clt_srvbev != NULL) + bufferevent_disable(clt->clt_srvbev, EV_READ|EV_WRITE); + + server_log(clt, msg); + + if (clt->clt_bev != NULL) + bufferevent_free(clt->clt_bev); + if (clt->clt_output != NULL) + evbuffer_free(clt->clt_output); + if (clt->clt_srvevb != NULL) + evbuffer_free(clt->clt_srvevb); + + if (clt->clt_srvbev != NULL) + bufferevent_free(clt->clt_srvbev); + if (clt->clt_fd != -1) + close(clt->clt_fd); + if (clt->clt_s != -1) + close(clt->clt_s); + + if (clt->clt_tls_ctx != NULL) + tls_close(clt->clt_tls_ctx); + tls_free(clt->clt_tls_ctx); + + server_inflight_dec(clt, __func__); + + if (clt->clt_log != NULL) + evbuffer_free(clt->clt_log); + + free(clt); + server_clients--; +} + +int +server_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg) +{ + switch (imsg->hdr.type) { + case IMSG_CFG_MEDIA: + config_getmedia(env, imsg); + break; + case IMSG_CFG_AUTH: + config_getauth(env, imsg); + break; + case IMSG_CFG_SERVER: + config_getserver(env, imsg); + break; + case IMSG_CFG_DONE: + config_getcfg(env, imsg); + break; + case IMSG_CTL_START: + server_launch(); + break; + case IMSG_CTL_RESET: + config_getreset(env, imsg); + break; + default: + return (-1); + } + + return (0); +} + +int +server_dispatch_logger(int fd, struct privsep_proc *p, struct imsg *imsg) +{ + switch (imsg->hdr.type) { + default: + return (-1); + } + + return (0); +} + +int +server_bufferevent_add(struct event *ev, int timeout) +{ + struct timeval tv, *ptv = NULL; + + if (timeout) { + timerclear(&tv); + tv.tv_sec = timeout; + ptv = &tv; + } + + return (event_add(ev, ptv)); +} + +int +server_bufferevent_printf(struct client *clt, const char *fmt, ...) +{ + int ret; + va_list ap; + char *str; + + va_start(ap, fmt); + ret = vasprintf(&str, fmt, ap); + va_end(ap); + + if (ret == -1) + return (ret); + + ret = server_bufferevent_print(clt, str); + free(str); + + return (ret); +} + +int +server_bufferevent_print(struct client *clt, const char *str) +{ + if (clt->clt_bev == NULL) + return (evbuffer_add(clt->clt_output, str, strlen(str))); + return (bufferevent_write(clt->clt_bev, str, strlen(str))); +} + +int +server_bufferevent_write_buffer(struct client *clt, struct evbuffer *buf) +{ + if (clt->clt_bev == NULL) + return (evbuffer_add_buffer(clt->clt_output, buf)); + return (bufferevent_write_buffer(clt->clt_bev, buf)); +} + +int +server_bufferevent_write_chunk(struct client *clt, + struct evbuffer *buf, size_t size) +{ + int ret; + ret = server_bufferevent_write(clt, buf->buffer, size); + if (ret != -1) + evbuffer_drain(buf, size); + return (ret); +} + +int +server_bufferevent_write(struct client *clt, void *data, size_t size) +{ + if (clt->clt_bev == NULL) + return (evbuffer_add(clt->clt_output, data, size)); + return (bufferevent_write(clt->clt_bev, data, size)); +} + +int +server_client_cmp(struct client *a, struct client *b) +{ + return ((int)a->clt_id - b->clt_id); +} + +SPLAY_GENERATE(client_tree, client, clt_nodes, server_client_cmp); diff --git a/httpd/server_fcgi.c b/httpd/server_fcgi.c new file mode 100644 index 0000000..33603a0 --- /dev/null +++ b/httpd/server_fcgi.c @@ -0,0 +1,729 @@ +/* $OpenBSD: server_fcgi.c,v 1.52 2015/02/23 19:22:43 chrisz Exp $ */ + +/* + * Copyright (c) 2014 Florian Obser <florian@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <limits.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <time.h> +#include <ctype.h> +#include <event.h> + +#include "httpd.h" +#include "http.h" + +#define FCGI_PADDING_SIZE 255 +#define FCGI_RECORD_SIZE \ + (sizeof(struct fcgi_record_header) + FCGI_CONTENT_SIZE + FCGI_PADDING_SIZE) + +#define FCGI_BEGIN_REQUEST 1 +#define FCGI_ABORT_REQUEST 2 +#define FCGI_END_REQUEST 3 +#define FCGI_PARAMS 4 +#define FCGI_STDIN 5 +#define FCGI_STDOUT 6 +#define FCGI_STDERR 7 +#define FCGI_DATA 8 +#define FCGI_GET_VALUES 9 +#define FCGI_GET_VALUES_RESULT 10 +#define FCGI_UNKNOWN_TYPE 11 +#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE) + +#define FCGI_RESPONDER 1 + +struct fcgi_record_header { + uint8_t version; + uint8_t type; + uint16_t id; + uint16_t content_len; + uint8_t padding_len; + uint8_t reserved; +} __packed; + +struct fcgi_begin_request_body { + uint16_t role; + uint8_t flags; + uint8_t reserved[5]; +} __packed; + +struct server_fcgi_param { + int total_len; + uint8_t buf[FCGI_RECORD_SIZE]; +}; + +int server_fcgi_header(struct client *, u_int); +void server_fcgi_read(struct bufferevent *, void *); +int server_fcgi_writeheader(struct client *, struct kv *, void *); +int server_fcgi_writechunk(struct client *); +int server_fcgi_getheaders(struct client *); +int fcgi_add_param(struct server_fcgi_param *, const char *, const char *, + struct client *); + +int +server_fcgi(struct httpd *env, struct client *clt) +{ + struct server_fcgi_param param; + struct server_config *srv_conf = clt->clt_srv_conf; + struct http_descriptor *desc = clt->clt_descreq; + struct fcgi_record_header *h; + struct fcgi_begin_request_body *begin; + char hbuf[HOST_NAME_MAX+1]; + size_t scriptlen; + int pathlen; + int fd = -1, ret; + const char *stripped, *p, *alias, *errstr = NULL; + char *str, *script = NULL; + + if (srv_conf->socket[0] == ':') { + struct sockaddr_storage ss; + in_port_t port; + + p = srv_conf->socket + 1; + + port = strtonum(p, 0, 0xffff, &errstr); + if (errstr != NULL) { + log_warn("%s: strtonum %s, %s", __func__, p, errstr); + goto fail; + } + memset(&ss, 0, sizeof(ss)); + ss.ss_family = AF_INET; + ((struct sockaddr_in *) + &ss)->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + port = htons(port); + + if ((fd = server_socket_connect(&ss, port, srv_conf)) == -1) + goto fail; + } else { + struct sockaddr_un sun; + size_t len; + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + goto fail; + + memset(&sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; + len = strlcpy(sun.sun_path, + srv_conf->socket, sizeof(sun.sun_path)); + if (len >= sizeof(sun.sun_path)) { + errstr = "socket path to long"; + goto fail; + } + sun.sun_len = len; + + if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) + goto fail; + } + + socket_set_blockmode(fd, BM_NONBLOCK); + + memset(hbuf, 0, sizeof(hbuf)); + clt->clt_fcgi_state = FCGI_READ_HEADER; + clt->clt_fcgi_toread = sizeof(struct fcgi_record_header); + + if (clt->clt_srvevb != NULL) + evbuffer_free(clt->clt_srvevb); + + clt->clt_srvevb = evbuffer_new(); + if (clt->clt_srvevb == NULL) { + errstr = "failed to allocate evbuffer"; + goto fail; + } + + clt->clt_fd = fd; + if (clt->clt_srvbev != NULL) + bufferevent_free(clt->clt_srvbev); + + clt->clt_srvbev = bufferevent_new(fd, server_fcgi_read, + NULL, server_file_error, clt); + if (clt->clt_srvbev == NULL) { + errstr = "failed to allocate fcgi buffer event"; + goto fail; + } + + memset(¶m, 0, sizeof(param)); + + h = (struct fcgi_record_header *)¶m.buf; + h->version = 1; + h->type = FCGI_BEGIN_REQUEST; + h->id = htons(1); + h->content_len = htons(sizeof(struct fcgi_begin_request_body)); + h->padding_len = 0; + + begin = (struct fcgi_begin_request_body *)¶m.buf[sizeof(struct + fcgi_record_header)]; + begin->role = htons(FCGI_RESPONDER); + + bufferevent_write(clt->clt_srvbev, ¶m.buf, + sizeof(struct fcgi_record_header) + + sizeof(struct fcgi_begin_request_body)); + + h->type = FCGI_PARAMS; + h->content_len = param.total_len = 0; + + alias = desc->http_path_alias != NULL + ? desc->http_path_alias + : desc->http_path; + + stripped = server_root_strip(alias, srv_conf->strip); + if ((pathlen = asprintf(&script, "%s%s", srv_conf->root, stripped)) + == -1) { + errstr = "failed to get script name"; + goto fail; + } + + scriptlen = path_info(script); + /* + * no part of root should show up in PATH_INFO. + * therefore scriptlen should be >= strlen(root) + */ + if (scriptlen < strlen(srv_conf->root)) + scriptlen = strlen(srv_conf->root); + if ((int)scriptlen < pathlen) { + if (fcgi_add_param(¶m, "PATH_INFO", + script + scriptlen, clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + script[scriptlen] = '\0'; + } + + /* + * calculate length of http SCRIPT_NAME: + * add length of stripped prefix, + * subtract length of prepended local root + */ + scriptlen += (stripped - alias) - strlen(srv_conf->root); + if ((str = strndup(alias, scriptlen)) == NULL) + goto fail; + ret = fcgi_add_param(¶m, "SCRIPT_NAME", str, clt); + free(str); + if (ret == -1) { + errstr = "failed to encode param"; + goto fail; + } + if (fcgi_add_param(¶m, "SCRIPT_FILENAME", script, clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + if (desc->http_query) + if (fcgi_add_param(¶m, "QUERY_STRING", desc->http_query, + clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + if (fcgi_add_param(¶m, "DOCUMENT_ROOT", srv_conf->root, + clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + if (fcgi_add_param(¶m, "DOCUMENT_URI", alias, + clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + if (fcgi_add_param(¶m, "GATEWAY_INTERFACE", "CGI/1.1", + clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + if (srv_conf->flags & SRVFLAG_AUTH) { + if (fcgi_add_param(¶m, "REMOTE_USER", + clt->clt_remote_user, clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + } + + /* Add HTTP_* headers */ + if (server_headers(clt, desc, server_fcgi_writeheader, ¶m) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + if (srv_conf->flags & SRVFLAG_TLS) + if (fcgi_add_param(¶m, "HTTPS", "on", clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + (void)print_host(&clt->clt_ss, hbuf, sizeof(hbuf)); + if (fcgi_add_param(¶m, "REMOTE_ADDR", hbuf, clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + (void)snprintf(hbuf, sizeof(hbuf), "%d", ntohs(clt->clt_port)); + if (fcgi_add_param(¶m, "REMOTE_PORT", hbuf, clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + if (fcgi_add_param(¶m, "REQUEST_METHOD", + server_httpmethod_byid(desc->http_method), clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + if (!desc->http_query) { + if (fcgi_add_param(¶m, "REQUEST_URI", desc->http_path, + clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + } else if (asprintf(&str, "%s?%s", desc->http_path, + desc->http_query) != -1) { + ret = fcgi_add_param(¶m, "REQUEST_URI", str, clt); + free(str); + if (ret == -1) { + errstr = "failed to encode param"; + goto fail; + } + } + + (void)print_host(&clt->clt_srv_ss, hbuf, sizeof(hbuf)); + if (fcgi_add_param(¶m, "SERVER_ADDR", hbuf, clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + (void)snprintf(hbuf, sizeof(hbuf), "%d", + ntohs(server_socket_getport(&clt->clt_srv_ss))); + if (fcgi_add_param(¶m, "SERVER_PORT", hbuf, clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + if (fcgi_add_param(¶m, "SERVER_NAME", srv_conf->name, + clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + if (fcgi_add_param(¶m, "SERVER_PROTOCOL", desc->http_version, + clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + if (fcgi_add_param(¶m, "SERVER_SOFTWARE", HTTPD_SERVERNAME, + clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + if (param.total_len != 0) { /* send last params record */ + bufferevent_write(clt->clt_srvbev, ¶m.buf, + sizeof(struct fcgi_record_header) + + ntohs(h->content_len)); + } + + /* send "no more params" message */ + h->content_len = 0; + bufferevent_write(clt->clt_srvbev, ¶m.buf, + sizeof(struct fcgi_record_header)); + + bufferevent_settimeout(clt->clt_srvbev, + srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec); + bufferevent_enable(clt->clt_srvbev, EV_READ|EV_WRITE); + if (clt->clt_toread != 0) { + server_read_httpcontent(clt->clt_bev, clt); + bufferevent_enable(clt->clt_bev, EV_READ); + } else { + bufferevent_disable(clt->clt_bev, EV_READ); + fcgi_add_stdin(clt, NULL); + } + + if (strcmp(desc->http_version, "HTTP/1.1") == 0) { + clt->clt_fcgi_chunked = 1; + } else { + /* HTTP/1.0 does not support chunked encoding */ + clt->clt_fcgi_chunked = 0; + clt->clt_persist = 0; + } + clt->clt_fcgi_end = 0; + clt->clt_done = 0; + + free(script); + return (0); + fail: + free(script); + if (errstr == NULL) + errstr = strerror(errno); + server_abort_http(clt, 500, errstr); + return (-1); +} + +int +fcgi_add_stdin(struct client *clt, struct evbuffer *evbuf) +{ + struct fcgi_record_header h; + + memset(&h, 0, sizeof(h)); + h.version = 1; + h.type = FCGI_STDIN; + h.id = htons(1); + h.padding_len = 0; + + if (evbuf == NULL) { + h.content_len = 0; + return bufferevent_write(clt->clt_srvbev, &h, + sizeof(struct fcgi_record_header)); + } else { + h.content_len = htons(EVBUFFER_LENGTH(evbuf)); + if (bufferevent_write(clt->clt_srvbev, &h, + sizeof(struct fcgi_record_header)) == -1) + return -1; + return bufferevent_write_buffer(clt->clt_srvbev, evbuf); + } + return (0); +} + +int +fcgi_add_param(struct server_fcgi_param *p, const char *key, + const char *val, struct client *clt) +{ + struct fcgi_record_header *h; + int len = 0; + int key_len = strlen(key); + int val_len = strlen(val); + uint8_t *param; + + len += key_len + val_len; + len += key_len > 127 ? 4 : 1; + len += val_len > 127 ? 4 : 1; + + DPRINTF("%s: %s[%d] => %s[%d], total_len: %d", __func__, key, key_len, + val, val_len, p->total_len); + + if (len > FCGI_CONTENT_SIZE) + return (-1); + + if (p->total_len + len > FCGI_CONTENT_SIZE) { + bufferevent_write(clt->clt_srvbev, p->buf, + sizeof(struct fcgi_record_header) + p->total_len); + p->total_len = 0; + } + + h = (struct fcgi_record_header *)p->buf; + param = p->buf + sizeof(*h) + p->total_len; + + if (key_len > 127) { + *param++ = ((key_len >> 24) & 0xff) | 0x80; + *param++ = ((key_len >> 16) & 0xff); + *param++ = ((key_len >> 8) & 0xff); + *param++ = (key_len & 0xff); + } else + *param++ = key_len; + + if (val_len > 127) { + *param++ = ((val_len >> 24) & 0xff) | 0x80; + *param++ = ((val_len >> 16) & 0xff); + *param++ = ((val_len >> 8) & 0xff); + *param++ = (val_len & 0xff); + } else + *param++ = val_len; + + memcpy(param, key, key_len); + param += key_len; + memcpy(param, val, val_len); + + p->total_len += len; + + h->content_len = htons(p->total_len); + return (0); +} + +void +server_fcgi_read(struct bufferevent *bev, void *arg) +{ + uint8_t buf[FCGI_RECORD_SIZE]; + struct client *clt = (struct client *) arg; + struct fcgi_record_header *h; + size_t len; + char *ptr; + + do { + len = bufferevent_read(bev, buf, clt->clt_fcgi_toread); + /* XXX error handling */ + evbuffer_add(clt->clt_srvevb, buf, len); + clt->clt_fcgi_toread -= len; + DPRINTF("%s: len: %lu toread: %d state: %d type: %d", + __func__, len, clt->clt_fcgi_toread, + clt->clt_fcgi_state, clt->clt_fcgi_type); + + if (clt->clt_fcgi_toread != 0) + return; + + switch (clt->clt_fcgi_state) { + case FCGI_READ_HEADER: + clt->clt_fcgi_state = FCGI_READ_CONTENT; + h = (struct fcgi_record_header *) + EVBUFFER_DATA(clt->clt_srvevb); + DPRINTF("%s: record header: version %d type %d id %d " + "content len %d padding %d", __func__, + h->version, h->type, ntohs(h->id), + ntohs(h->content_len), h->padding_len); + clt->clt_fcgi_type = h->type; + clt->clt_fcgi_toread = ntohs(h->content_len); + clt->clt_fcgi_padding_len = h->padding_len; + evbuffer_drain(clt->clt_srvevb, + EVBUFFER_LENGTH(clt->clt_srvevb)); + if (clt->clt_fcgi_toread != 0) + break; + else if (clt->clt_fcgi_type == FCGI_STDOUT && + !clt->clt_chunk) { + server_abort_http(clt, 500, "empty stdout"); + return; + } + + /* fallthrough if content_len == 0 */ + case FCGI_READ_CONTENT: + switch (clt->clt_fcgi_type) { + case FCGI_STDERR: + if (EVBUFFER_LENGTH(clt->clt_srvevb) > 0 && + (ptr = get_string( + EVBUFFER_DATA(clt->clt_srvevb), + EVBUFFER_LENGTH(clt->clt_srvevb))) + != NULL) { + server_sendlog(clt->clt_srv_conf, + IMSG_LOG_ERROR, "%s", ptr); + free(ptr); + } + break; + case FCGI_STDOUT: + if (++clt->clt_chunk == 1) { + if (server_fcgi_header(clt, + server_fcgi_getheaders(clt)) + == -1) { + server_abort_http(clt, 500, + "malformed fcgi headers"); + return; + } + if (!EVBUFFER_LENGTH(clt->clt_srvevb)) + break; + } + /* FALLTHROUGH */ + case FCGI_END_REQUEST: + if (server_fcgi_writechunk(clt) == -1) { + server_abort_http(clt, 500, + "encoding error"); + return; + } + break; + } + evbuffer_drain(clt->clt_srvevb, + EVBUFFER_LENGTH(clt->clt_srvevb)); + if (!clt->clt_fcgi_padding_len) { + clt->clt_fcgi_state = FCGI_READ_HEADER; + clt->clt_fcgi_toread = + sizeof(struct fcgi_record_header); + } else { + clt->clt_fcgi_state = FCGI_READ_PADDING; + clt->clt_fcgi_toread = + clt->clt_fcgi_padding_len; + } + break; + case FCGI_READ_PADDING: + evbuffer_drain(clt->clt_srvevb, + EVBUFFER_LENGTH(clt->clt_srvevb)); + clt->clt_fcgi_state = FCGI_READ_HEADER; + clt->clt_fcgi_toread = + sizeof(struct fcgi_record_header); + break; + } + } while (len > 0); +} + +int +server_fcgi_header(struct client *clt, u_int code) +{ + struct http_descriptor *desc = clt->clt_descreq; + struct http_descriptor *resp = clt->clt_descresp; + const char *error; + char tmbuf[32]; + struct kv *kv, key; + + if (desc == NULL || (error = server_httperror_byid(code)) == NULL) + return (-1); + + if (server_log_http(clt, code, 0) == -1) + return (-1); + + /* Add error codes */ + if (kv_setkey(&resp->http_pathquery, "%lu", code) == -1 || + kv_set(&resp->http_pathquery, "%s", error) == -1) + return (-1); + + /* Add headers */ + if (kv_add(&resp->http_headers, "Server", HTTPD_SERVERNAME) == NULL) + return (-1); + + /* Set chunked encoding */ + if (clt->clt_fcgi_chunked) { + /* XXX Should we keep and handle Content-Length instead? */ + key.kv_key = "Content-Length"; + if ((kv = kv_find(&resp->http_headers, &key)) != NULL) + kv_delete(&resp->http_headers, kv); + + /* + * XXX What if the FastCGI added some kind of Transfer-Encoding? + * XXX like gzip, deflate or even "chunked"? + */ + if (kv_add(&resp->http_headers, + "Transfer-Encoding", "chunked") == NULL) + return (-1); + } + + /* Is it a persistent connection? */ + if (clt->clt_persist) { + if (kv_add(&resp->http_headers, + "Connection", "keep-alive") == NULL) + return (-1); + } else if (kv_add(&resp->http_headers, "Connection", "close") == NULL) + return (-1); + + /* Date header is mandatory and should be added as late as possible */ + if (server_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0 || + kv_add(&resp->http_headers, "Date", tmbuf) == NULL) + return (-1); + + /* Write initial header (fcgi might append more) */ + if (server_writeresponse_http(clt) == -1 || + server_bufferevent_print(clt, "\r\n") == -1 || + server_headers(clt, resp, server_writeheader_http, NULL) == -1 || + server_bufferevent_print(clt, "\r\n") == -1) + return (-1); + + return (0); +} + +int +server_fcgi_writeheader(struct client *clt, struct kv *hdr, void *arg) +{ + struct server_fcgi_param *param = arg; + char *val, *name, *p; + const char *key; + int ret; + + if (hdr->kv_flags & KV_FLAG_INVALID) + return (0); + + /* The key might have been updated in the parent */ + if (hdr->kv_parent != NULL && hdr->kv_parent->kv_key != NULL) + key = hdr->kv_parent->kv_key; + else + key = hdr->kv_key; + + val = hdr->kv_value; + + if (strcasecmp(key, "Content-Length") == 0 || + strcasecmp(key, "Content-Type") == 0) { + if ((name = strdup(key)) == NULL) + return (-1); + } else { + if (asprintf(&name, "HTTP_%s", key) == -1) + return (-1); + } + + for (p = name; *p != '\0'; p++) { + if (isalpha((unsigned char)*p)) + *p = toupper((unsigned char)*p); + else + *p = '_'; + } + + ret = fcgi_add_param(param, name, val, clt); + free(name); + + return (ret); +} + +int +server_fcgi_writechunk(struct client *clt) +{ + struct evbuffer *evb = clt->clt_srvevb; + size_t len; + + if (clt->clt_fcgi_type == FCGI_END_REQUEST) { + len = 0; + } else + len = EVBUFFER_LENGTH(evb); + + if (clt->clt_fcgi_chunked) { + /* If len is 0, make sure to write the end marker only once */ + if (len == 0 && clt->clt_fcgi_end++) + return (0); + if (server_bufferevent_printf(clt, "%zx\r\n", len) == -1 || + server_bufferevent_write_chunk(clt, evb, len) == -1 || + server_bufferevent_print(clt, "\r\n") == -1) + return (-1); + } else if (len) + return (server_bufferevent_write_buffer(clt, evb)); + + return (0); +} + +int +server_fcgi_getheaders(struct client *clt) +{ + struct http_descriptor *resp = clt->clt_descresp; + struct evbuffer *evb = clt->clt_srvevb; + int code = 200; + char *line, *key, *value; + const char *errstr; + + while ((line = evbuffer_getline(evb)) != NULL && *line != '\0') { + key = line; + + if ((value = strchr(key, ':')) == NULL) + break; + if (*value == ':') { + *value++ = '\0'; + value += strspn(value, " \t"); + } else { + *value++ = '\0'; + } + + DPRINTF("%s: %s: %s", __func__, key, value); + + if (strcasecmp("Status", key) == 0) { + value[strcspn(value, " \t")] = '\0'; + code = (int)strtonum(value, 100, 600, &errstr); + if (errstr != NULL || server_httperror_byid( + code) == NULL) + code = 200; + } else { + (void)kv_add(&resp->http_headers, key, value); + } + free(line); + } + + return (code); +} diff --git a/httpd/server_file.c b/httpd/server_file.c new file mode 100644 index 0000000..f697504 --- /dev/null +++ b/httpd/server_file.c @@ -0,0 +1,469 @@ +/* $OpenBSD: server_file.c,v 1.51 2015/02/12 10:05:29 reyk Exp $ */ + +/* + * Copyright (c) 2006 - 2015 Reyk Floeter <reyk@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> + +#include <limits.h> +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdio.h> +#include <dirent.h> +#include <time.h> +#include <event.h> + +#include "httpd.h" +#include "http.h" + +#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) +#define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) + +int server_file_access(struct httpd *, struct client *, char *, size_t); +int server_file_request(struct httpd *, struct client *, char *, + struct stat *); +int server_file_index(struct httpd *, struct client *, struct stat *); +int server_file_method(struct client *); + +int +server_file_access(struct httpd *env, struct client *clt, + char *path, size_t len) +{ + struct http_descriptor *desc = clt->clt_descreq; + struct server_config *srv_conf = clt->clt_srv_conf; + struct stat st; + char *newpath; + int ret; + + errno = 0; + + if (access(path, R_OK) == -1) { + goto fail; + } else if (stat(path, &st) == -1) { + goto fail; + } else if (S_ISDIR(st.st_mode)) { + /* Deny access if directory indexing is disabled */ + if (srv_conf->flags & SRVFLAG_NO_INDEX) { + errno = EACCES; + goto fail; + } + + if (desc->http_path_alias != NULL) { + /* Recursion - the index "file" is a directory? */ + errno = EINVAL; + goto fail; + } + + /* Redirect to path with trailing "/" */ + if (path[strlen(path) - 1] != '/') { + if (asprintf(&newpath, "http%s://%s%s/", + srv_conf->flags & SRVFLAG_TLS ? "s" : "", + desc->http_host, desc->http_path) == -1) + return (500); + /* Path alias will be used for the redirection */ + desc->http_path_alias = newpath; + + /* Indicate that the file has been moved */ + return (301); + } + + /* Append the default index file to the location */ + if (asprintf(&newpath, "%s%s", desc->http_path, + srv_conf->index) == -1) + return (500); + desc->http_path_alias = newpath; + if (server_getlocation(clt, newpath) != srv_conf) { + /* The location has changed */ + return (server_file(env, clt)); + } + + /* Otherwise append the default index file to the path */ + if (strlcat(path, srv_conf->index, len) >= len) { + errno = EACCES; + goto fail; + } + + ret = server_file_access(env, clt, path, len); + if (ret == 404) { + /* + * Index file not found; fail if auto-indexing is + * not enabled, otherwise return success but + * indicate directory with S_ISDIR of the previous + * stat. + */ + if ((srv_conf->flags & SRVFLAG_AUTO_INDEX) == 0) { + errno = EACCES; + goto fail; + } + + return (server_file_index(env, clt, &st)); + } + return (ret); + } else if (!S_ISREG(st.st_mode)) { + /* Don't follow symlinks and ignore special files */ + errno = EACCES; + goto fail; + } + + return (server_file_request(env, clt, path, &st)); + + fail: + switch (errno) { + case ENOENT: + case ENOTDIR: + return (404); + case EACCES: + return (403); + default: + return (500); + } + + /* NOTREACHED */ +} + +int +server_file(struct httpd *env, struct client *clt) +{ + struct http_descriptor *desc = clt->clt_descreq; + struct server_config *srv_conf = clt->clt_srv_conf; + char path[PATH_MAX]; + const char *stripped, *errstr = NULL; + int ret = 500; + + if (srv_conf->flags & SRVFLAG_FCGI) + return (server_fcgi(env, clt)); + + /* Request path is already canonicalized */ + stripped = server_root_strip( + desc->http_path_alias != NULL ? + desc->http_path_alias : desc->http_path, + srv_conf->strip); + if ((size_t)snprintf(path, sizeof(path), "%s%s", + srv_conf->root, stripped) >= sizeof(path)) { + errstr = desc->http_path; + goto abort; + } + + /* Returns HTTP status code on error */ + if ((ret = server_file_access(env, clt, path, sizeof(path))) > 0) { + errstr = desc->http_path_alias != NULL ? + desc->http_path_alias : desc->http_path; + goto abort; + } + + return (ret); + + abort: + if (errstr == NULL) + errstr = strerror(errno); + server_abort_http(clt, ret, errstr); + return (-1); +} + +int +server_file_method(struct client *clt) +{ + struct http_descriptor *desc = clt->clt_descreq; + + switch (desc->http_method) { + case HTTP_METHOD_GET: + case HTTP_METHOD_HEAD: + return (0); + default: + /* Other methods are not allowed */ + errno = EACCES; + return (405); + } + /* NOTREACHED */ +} + +int +server_file_request(struct httpd *env, struct client *clt, char *path, + struct stat *st) +{ + struct server_config *srv_conf = clt->clt_srv_conf; + struct media_type *media; + const char *errstr = NULL; + int fd = -1, ret, code = 500; + + if ((ret = server_file_method(clt)) != 0) { + code = ret; + goto abort; + } + + /* Now open the file, should be readable or we have another problem */ + if ((fd = open(path, O_RDONLY)) == -1) + goto abort; + + media = media_find(env->sc_mediatypes, path); + ret = server_response_http(clt, 200, media, st->st_size, + MINIMUM(time(NULL), st->st_mtim.tv_sec)); + switch (ret) { + case -1: + goto fail; + case 0: + /* Connection is already finished */ + close(fd); + goto done; + default: + break; + } + + clt->clt_fd = fd; + if (clt->clt_srvbev != NULL) + bufferevent_free(clt->clt_srvbev); + + clt->clt_srvbev = bufferevent_new(clt->clt_fd, server_read, + server_write, server_file_error, clt); + if (clt->clt_srvbev == NULL) { + errstr = "failed to allocate file buffer event"; + goto fail; + } + + /* Adjust read watermark to the socket output buffer size */ + bufferevent_setwatermark(clt->clt_srvbev, EV_READ, 0, + clt->clt_sndbufsiz); + + bufferevent_settimeout(clt->clt_srvbev, + srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec); + bufferevent_enable(clt->clt_srvbev, EV_READ); + bufferevent_disable(clt->clt_bev, EV_READ); + + done: + server_reset_http(clt); + return (0); + fail: + bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); + bufferevent_free(clt->clt_bev); + clt->clt_bev = NULL; + abort: + if (errstr == NULL) + errstr = strerror(errno); + server_abort_http(clt, code, errstr); + return (-1); +} + +int +server_file_index(struct httpd *env, struct client *clt, struct stat *st) +{ + char path[PATH_MAX]; + char tmstr[21]; + struct http_descriptor *desc = clt->clt_descreq; + struct server_config *srv_conf = clt->clt_srv_conf; + struct dirent **namelist, *dp; + int namesize, i, ret, fd = -1, namewidth, skip; + int code = 500; + struct evbuffer *evb = NULL; + struct media_type *media; + const char *stripped, *style; + char *escapeduri, *escapedhtml, *escapedpath; + struct tm tm; + time_t t, dir_mtime; + + if ((ret = server_file_method(clt)) != 0) { + code = ret; + goto abort; + } + + /* Request path is already canonicalized */ + stripped = server_root_strip(desc->http_path, srv_conf->strip); + if ((size_t)snprintf(path, sizeof(path), "%s%s", + srv_conf->root, stripped) >= sizeof(path)) + goto abort; + + /* Now open the file, should be readable or we have another problem */ + if ((fd = open(path, O_RDONLY)) == -1) + goto abort; + + /* Save last modification time */ + dir_mtime = MINIMUM(time(NULL), st->st_mtim.tv_sec); + + if ((evb = evbuffer_new()) == NULL) + goto abort; + + if ((namesize = scandir(path, &namelist, NULL, alphasort)) == -1) + goto abort; + + /* Indicate failure but continue going through the list */ + skip = 0; + + if ((escapedpath = escape_html(desc->http_path)) == NULL) + goto fail; + + /* A CSS stylesheet allows minimal customization by the user */ + style = "body { background-color: white; color: black; font-family: " + "sans-serif; }\nhr { border: 0; border-bottom: 1px dashed; }\n"; + /* Generate simple HTML index document */ + if (evbuffer_add_printf(evb, + "<!DOCTYPE html>\n" + "<html>\n" + "<head>\n" + "<title>Index of %s</title>\n" + "<style type=\"text/css\"><!--\n%s\n--></style>\n" + "</head>\n" + "<body>\n" + "<h1>Index of %s</h1>\n" + "<hr>\n<pre>\n", + escapedpath, style, escapedpath) == -1) + skip = 1; + + free(escapedpath); + + for (i = 0; i < namesize; i++) { + dp = namelist[i]; + + if (skip || + fstatat(fd, dp->d_name, st, 0) == -1) { + free(dp); + continue; + } + + t = st->st_mtime; + localtime_r(&t, &tm); + strftime(tmstr, sizeof(tmstr), "%d-%h-%Y %R", &tm); + namewidth = 51 - strlen(dp->d_name); + + if ((escapeduri = url_encode(dp->d_name)) == NULL) + goto fail; + if ((escapedhtml = escape_html(dp->d_name)) == NULL) + goto fail; + + if (dp->d_name[0] == '.' && + !(dp->d_name[1] == '.' && dp->d_name[2] == '\0')) { + /* ignore hidden files starting with a dot */ + } else if (S_ISDIR(st->st_mode)) { + namewidth -= 1; /* trailing slash */ + if (evbuffer_add_printf(evb, + "<a href=\"%s\">%s/</a>%*s%s%20s\n", + escapeduri, escapedhtml, + MAXIMUM(namewidth, 0), " ", tmstr, "-") == -1) + skip = 1; + } else if (S_ISREG(st->st_mode)) { + if (evbuffer_add_printf(evb, + "<a href=\"%s\">%s</a>%*s%s%20llu\n", + escapeduri, escapedhtml, + MAXIMUM(namewidth, 0), " ", + tmstr, st->st_size) == -1) + skip = 1; + } + free(escapeduri); + free(escapedhtml); + free(dp); + } + free(namelist); + + if (skip || + evbuffer_add_printf(evb, + "</pre>\n<hr>\n</body>\n</html>\n") == -1) + goto abort; + + close(fd); + fd = -1; + + media = media_find(env->sc_mediatypes, "index.html"); + ret = server_response_http(clt, 200, media, EVBUFFER_LENGTH(evb), + dir_mtime); + switch (ret) { + case -1: + goto fail; + case 0: + /* Connection is already finished */ + evbuffer_free(evb); + goto done; + default: + break; + } + + if (server_bufferevent_write_buffer(clt, evb) == -1) + goto fail; + evbuffer_free(evb); + evb = NULL; + + bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); + if (clt->clt_persist) + clt->clt_toread = TOREAD_HTTP_HEADER; + else + clt->clt_toread = TOREAD_HTTP_NONE; + clt->clt_done = 0; + + done: + server_reset_http(clt); + return (0); + fail: + bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); + bufferevent_free(clt->clt_bev); + clt->clt_bev = NULL; + abort: + if (fd != -1) + close(fd); + if (evb != NULL) + evbuffer_free(evb); + server_abort_http(clt, code, desc->http_path); + return (-1); +} + +void +server_file_error(struct bufferevent *bev, short error, void *arg) +{ + struct client *clt = arg; + struct evbuffer *dst; + + if (error & EVBUFFER_TIMEOUT) { + server_close(clt, "buffer event timeout"); + return; + } + if (error & EVBUFFER_ERROR) { + if (errno == EFBIG) { + bufferevent_enable(bev, EV_READ); + return; + } + server_close(clt, "buffer event error"); + return; + } + if (error & (EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF)) { + bufferevent_disable(bev, EV_READ|EV_WRITE); + + clt->clt_done = 1; + + if (clt->clt_persist) { + /* Close input file and wait for next HTTP request */ + if (clt->clt_fd != -1) + close(clt->clt_fd); + clt->clt_fd = -1; + clt->clt_toread = TOREAD_HTTP_HEADER; + server_reset_http(clt); + bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); + return; + } + + dst = EVBUFFER_OUTPUT(clt->clt_bev); + if (EVBUFFER_LENGTH(dst)) { + /* Finish writing all data first */ + bufferevent_enable(clt->clt_bev, EV_WRITE); + return; + } + + server_close(clt, "done"); + return; + } + server_close(clt, "unknown event error"); + return; +} diff --git a/httpd/server_http.c b/httpd/server_http.c new file mode 100644 index 0000000..b63fc22 --- /dev/null +++ b/httpd/server_http.c @@ -0,0 +1,1425 @@ +/* $OpenBSD: server_http.c,v 1.75 2015/02/23 18:43:18 reyk Exp $ */ + +/* + * Copyright (c) 2006 - 2015 Reyk Floeter <reyk@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/tree.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> +#include <stdio.h> +#include <time.h> +#include <resolv.h> +#include <event.h> +#include <fnmatch.h> + +#include "httpd.h" +#include "http.h" + +static int server_httpmethod_cmp(const void *, const void *); +static int server_httperror_cmp(const void *, const void *); +void server_httpdesc_free(struct http_descriptor *); +int server_http_authenticate(struct server_config *, + struct client *); +char *server_expand_http(struct client *, const char *, + char *, size_t); + +static struct httpd *env = NULL; + +static struct http_method http_methods[] = HTTP_METHODS; +static struct http_error http_errors[] = HTTP_ERRORS; + +void +server_http(struct httpd *x_env) +{ + if (x_env != NULL) + env = x_env; + + DPRINTF("%s: sorting lookup tables, pid %d", __func__, getpid()); + + /* Sort the HTTP lookup arrays */ + qsort(http_methods, sizeof(http_methods) / + sizeof(http_methods[0]) - 1, + sizeof(http_methods[0]), server_httpmethod_cmp); + qsort(http_errors, sizeof(http_errors) / + sizeof(http_errors[0]) - 1, + sizeof(http_errors[0]), server_httperror_cmp); +} + +void +server_http_init(struct server *srv) +{ + /* nothing */ +} + +int +server_httpdesc_init(struct client *clt) +{ + struct http_descriptor *desc; + + if ((desc = calloc(1, sizeof(*desc))) == NULL) + return (-1); + RB_INIT(&desc->http_headers); + clt->clt_descreq = desc; + + if ((desc = calloc(1, sizeof(*desc))) == NULL) { + /* req will be cleaned up later */ + return (-1); + } + RB_INIT(&desc->http_headers); + clt->clt_descresp = desc; + + return (0); +} + +void +server_httpdesc_free(struct http_descriptor *desc) +{ + if (desc == NULL) + return; + if (desc->http_path != NULL) { + free(desc->http_path); + desc->http_path = NULL; + } + if (desc->http_path_alias != NULL) { + free(desc->http_path_alias); + desc->http_path_alias = NULL; + } + if (desc->http_query != NULL) { + free(desc->http_query); + desc->http_query = NULL; + } + if (desc->http_version != NULL) { + free(desc->http_version); + desc->http_version = NULL; + } + if (desc->http_host != NULL) { + free(desc->http_host); + desc->http_host = NULL; + } + kv_purge(&desc->http_headers); + desc->http_lastheader = NULL; + desc->http_method = 0; + desc->http_chunked = 0; +} + +int +server_http_authenticate(struct server_config *srv_conf, struct client *clt) +{ + char decoded[1024]; + FILE *fp = NULL; + struct http_descriptor *desc = clt->clt_descreq; + struct auth *auth = srv_conf->auth; + struct kv *ba, key; + size_t linesize = 0; + ssize_t linelen; + int ret = -1; + char *line = NULL, *user = NULL, *pass = NULL; + char *clt_user = NULL, *clt_pass = NULL; + + memset(decoded, 0, sizeof(decoded)); + key.kv_key = "Authorization"; + + if ((ba = kv_find(&desc->http_headers, &key)) == NULL || + ba->kv_value == NULL) + goto done; + + if (strncmp(ba->kv_value, "Basic ", strlen("Basic ")) != 0) + goto done; + + if (b64_pton(strchr(ba->kv_value, ' ') + 1, (u_int8_t *)decoded, + sizeof(decoded)) <= 0) + goto done; + + if ((clt_pass = strchr(decoded, ':')) == NULL) + goto done; + + clt_user = decoded; + *clt_pass++ = '\0'; + if ((clt->clt_remote_user = strdup(clt_user)) == NULL) + goto done; + + if (clt_pass == NULL) + goto done; + + if ((fp = fopen(auth->auth_htpasswd, "r")) == NULL) + goto done; + + while ((linelen = getline(&line, &linesize, fp)) != -1) { + if (line[linelen - 1] == '\n') + line[linelen - 1] = '\0'; + user = line; + pass = strchr(line, ':'); + + if (pass == NULL) { + explicit_bzero(line, linelen); + continue; + } + + *pass++ = '\0'; + + if (strcmp(clt_user, user) != 0) { + explicit_bzero(line, linelen); + continue; + } + + if (crypt_checkpass(clt_pass, pass) == 0) { + explicit_bzero(line, linelen); + ret = 0; + break; + } + } +done: + if (fp != NULL) + fclose(fp); + + if (ba != NULL && ba->kv_value != NULL) { + explicit_bzero(ba->kv_value, strlen(ba->kv_value)); + explicit_bzero(decoded, sizeof(decoded)); + } + + return (ret); +} + +void +server_read_http(struct bufferevent *bev, void *arg) +{ + struct client *clt = arg; + struct server_config *srv_conf = clt->clt_srv_conf; + struct http_descriptor *desc = clt->clt_descreq; + struct evbuffer *src = EVBUFFER_INPUT(bev); + char *line = NULL, *key, *value; + const char *errstr; + size_t size, linelen; + struct kv *hdr = NULL; + + getmonotime(&clt->clt_tv_last); + + size = EVBUFFER_LENGTH(src); + DPRINTF("%s: session %d: size %lu, to read %lld", + __func__, clt->clt_id, size, clt->clt_toread); + if (!size) { + clt->clt_toread = TOREAD_HTTP_HEADER; + goto done; + } + + while (!clt->clt_done && (line = evbuffer_readline(src)) != NULL) { + linelen = strlen(line); + + /* + * An empty line indicates the end of the request. + * libevent already stripped the \r\n for us. + */ + if (!linelen) { + clt->clt_done = 1; + free(line); + break; + } + key = line; + + /* Limit the total header length minus \r\n */ + clt->clt_headerlen += linelen; + if (clt->clt_headerlen > SERVER_MAXHEADERLENGTH) { + server_abort_http(clt, 413, "request too large"); + goto abort; + } + + /* + * The first line is the GET/POST/PUT/... request, + * subsequent lines are HTTP headers. + */ + if (++clt->clt_line == 1) + value = strchr(key, ' '); + else if (*key == ' ' || *key == '\t') + /* Multiline headers wrap with a space or tab */ + value = NULL; + else + value = strchr(key, ':'); + if (value == NULL) { + if (clt->clt_line == 1) { + server_abort_http(clt, 400, "malformed"); + goto abort; + } + + /* Append line to the last header, if present */ + if (kv_extend(&desc->http_headers, + desc->http_lastheader, line) == NULL) + goto fail; + + free(line); + continue; + } + if (*value == ':') { + *value++ = '\0'; + value += strspn(value, " \t\r\n"); + } else { + *value++ = '\0'; + } + + DPRINTF("%s: session %d: header '%s: %s'", __func__, + clt->clt_id, key, value); + + /* + * Identify and handle specific HTTP request methods + */ + if (clt->clt_line == 1) { + if ((desc->http_method = server_httpmethod_byname(key)) + == HTTP_METHOD_NONE) { + server_abort_http(clt, 400, "malformed"); + goto abort; + } + + /* + * Decode request path and query + */ + desc->http_path = strdup(value); + if (desc->http_path == NULL) + goto fail; + + desc->http_version = strchr(desc->http_path, ' '); + if (desc->http_version == NULL) + goto fail; + + *desc->http_version++ = '\0'; + desc->http_query = strchr(desc->http_path, '?'); + if (desc->http_query != NULL) + *desc->http_query++ = '\0'; + + /* + * Have to allocate the strings because they could + * be changed independently by the filters later. + */ + if ((desc->http_version = + strdup(desc->http_version)) == NULL) + goto fail; + + if (desc->http_query != NULL && + (desc->http_query = + strdup(desc->http_query)) == NULL) + goto fail; + + } else if (desc->http_method != HTTP_METHOD_NONE && + strcasecmp("Content-Length", key) == 0) { + if (desc->http_method == HTTP_METHOD_TRACE || + desc->http_method == HTTP_METHOD_CONNECT) { + /* + * These method should not have a body + * and thus no Content-Length header. + */ + server_abort_http(clt, 400, "malformed"); + goto abort; + } + + /* + * Need to read data from the client after the + * HTTP header. + * XXX What about non-standard clients not using + * the carriage return? And some browsers seem to + * include the line length in the content-length. + */ + clt->clt_toread = strtonum(value, 0, LLONG_MAX, + &errstr); + if (errstr) { + server_abort_http(clt, 500, errstr); + goto abort; + } + if ((size_t)clt->clt_toread > + srv_conf->maxrequestbody) { + server_abort_http(clt, 413, NULL); + goto abort; + } + } + + if (strcasecmp("Transfer-Encoding", key) == 0 && + strcasecmp("chunked", value) == 0) + desc->http_chunked = 1; + + if (clt->clt_line != 1) { + if ((hdr = kv_add(&desc->http_headers, key, + value)) == NULL) + goto fail; + + desc->http_lastheader = hdr; + } + + free(line); + } + if (clt->clt_done) { + if (desc->http_method == HTTP_METHOD_NONE) { + server_abort_http(clt, 406, "no method"); + return; + } + + switch (desc->http_method) { + case HTTP_METHOD_CONNECT: + /* Data stream */ + clt->clt_toread = TOREAD_UNLIMITED; + bev->readcb = server_read; + break; + case HTTP_METHOD_DELETE: + case HTTP_METHOD_GET: + case HTTP_METHOD_HEAD: + case HTTP_METHOD_OPTIONS: + /* WebDAV methods */ + case HTTP_METHOD_COPY: + clt->clt_toread = 0; + break; + case HTTP_METHOD_POST: + case HTTP_METHOD_PUT: + case HTTP_METHOD_RESPONSE: + /* WebDAV methods */ + case HTTP_METHOD_PROPFIND: + case HTTP_METHOD_PROPPATCH: + case HTTP_METHOD_MKCOL: + case HTTP_METHOD_LOCK: + case HTTP_METHOD_UNLOCK: + case HTTP_METHOD_VERSION_CONTROL: + case HTTP_METHOD_REPORT: + case HTTP_METHOD_CHECKOUT: + case HTTP_METHOD_CHECKIN: + case HTTP_METHOD_UNCHECKOUT: + case HTTP_METHOD_MKWORKSPACE: + case HTTP_METHOD_UPDATE: + case HTTP_METHOD_LABEL: + case HTTP_METHOD_MERGE: + case HTTP_METHOD_BASELINE_CONTROL: + case HTTP_METHOD_MKACTIVITY: + case HTTP_METHOD_ORDERPATCH: + case HTTP_METHOD_ACL: + case HTTP_METHOD_MKREDIRECTREF: + case HTTP_METHOD_UPDATEREDIRECTREF: + case HTTP_METHOD_SEARCH: + case HTTP_METHOD_PATCH: + /* HTTP request payload */ + if (clt->clt_toread > 0) + bev->readcb = server_read_httpcontent; + + /* Single-pass HTTP body */ + if (clt->clt_toread < 0) { + clt->clt_toread = TOREAD_UNLIMITED; + bev->readcb = server_read; + } + break; + default: + server_abort_http(clt, 405, "method not allowed"); + return; + } + if (desc->http_chunked) { + /* Chunked transfer encoding */ + clt->clt_toread = TOREAD_HTTP_CHUNK_LENGTH; + bev->readcb = server_read_httpchunks; + } + + done: + if (clt->clt_toread != 0) + bufferevent_disable(bev, EV_READ); + server_response(env, clt); + return; + } + if (clt->clt_done) { + server_close(clt, "done"); + return; + } + if (EVBUFFER_LENGTH(src) && bev->readcb != server_read_http) + bev->readcb(bev, arg); + bufferevent_enable(bev, EV_READ); + return; + fail: + server_abort_http(clt, 500, strerror(errno)); + abort: + free(line); +} + +void +server_read_httpcontent(struct bufferevent *bev, void *arg) +{ + struct client *clt = arg; + struct evbuffer *src = EVBUFFER_INPUT(bev); + size_t size; + + getmonotime(&clt->clt_tv_last); + + size = EVBUFFER_LENGTH(src); + DPRINTF("%s: session %d: size %lu, to read %lld", __func__, + clt->clt_id, size, clt->clt_toread); + if (!size) + return; + + if (clt->clt_toread > 0) { + /* Read content data */ + if ((off_t)size > clt->clt_toread) { + size = clt->clt_toread; + if (fcgi_add_stdin(clt, src) == -1) + goto fail; + clt->clt_toread = 0; + } else { + if (fcgi_add_stdin(clt, src) == -1) + goto fail; + clt->clt_toread -= size; + } + DPRINTF("%s: done, size %lu, to read %lld", __func__, + size, clt->clt_toread); + } + if (clt->clt_toread == 0) { + fcgi_add_stdin(clt, NULL); + clt->clt_toread = TOREAD_HTTP_HEADER; + bufferevent_disable(bev, EV_READ); + bev->readcb = server_read_http; + return; + } + if (clt->clt_done) + goto done; + if (bev->readcb != server_read_httpcontent) + bev->readcb(bev, arg); + + return; + done: + return; + fail: + server_close(clt, strerror(errno)); +} + +void +server_read_httpchunks(struct bufferevent *bev, void *arg) +{ + struct client *clt = arg; + struct evbuffer *src = EVBUFFER_INPUT(bev); + char *line; + long long llval; + size_t size; + + getmonotime(&clt->clt_tv_last); + + size = EVBUFFER_LENGTH(src); + DPRINTF("%s: session %d: size %lu, to read %lld", __func__, + clt->clt_id, size, clt->clt_toread); + if (!size) + return; + + if (clt->clt_toread > 0) { + /* Read chunk data */ + if ((off_t)size > clt->clt_toread) { + size = clt->clt_toread; + if (server_bufferevent_write_chunk(clt, src, size) + == -1) + goto fail; + clt->clt_toread = 0; + } else { + if (server_bufferevent_write_buffer(clt, src) == -1) + goto fail; + clt->clt_toread -= size; + } + DPRINTF("%s: done, size %lu, to read %lld", __func__, + size, clt->clt_toread); + } + switch (clt->clt_toread) { + case TOREAD_HTTP_CHUNK_LENGTH: + line = evbuffer_readline(src); + if (line == NULL) { + /* Ignore empty line, continue */ + bufferevent_enable(bev, EV_READ); + return; + } + if (strlen(line) == 0) { + free(line); + goto next; + } + + /* + * Read prepended chunk size in hex, ignore the trailer. + * The returned signed value must not be negative. + */ + if (sscanf(line, "%llx", &llval) != 1 || llval < 0) { + free(line); + server_close(clt, "invalid chunk size"); + return; + } + + if (server_bufferevent_print(clt, line) == -1 || + server_bufferevent_print(clt, "\r\n") == -1) { + free(line); + goto fail; + } + free(line); + + if ((clt->clt_toread = llval) == 0) { + DPRINTF("%s: last chunk", __func__); + clt->clt_toread = TOREAD_HTTP_CHUNK_TRAILER; + } + break; + case TOREAD_HTTP_CHUNK_TRAILER: + /* Last chunk is 0 bytes followed by trailer and empty line */ + line = evbuffer_readline(src); + if (line == NULL) { + /* Ignore empty line, continue */ + bufferevent_enable(bev, EV_READ); + return; + } + if (server_bufferevent_print(clt, line) == -1 || + server_bufferevent_print(clt, "\r\n") == -1) { + free(line); + goto fail; + } + if (strlen(line) == 0) { + /* Switch to HTTP header mode */ + clt->clt_toread = TOREAD_HTTP_HEADER; + bev->readcb = server_read_http; + } + free(line); + break; + case 0: + /* Chunk is terminated by an empty newline */ + line = evbuffer_readline(src); + if (line != NULL) + free(line); + if (server_bufferevent_print(clt, "\r\n") == -1) + goto fail; + clt->clt_toread = TOREAD_HTTP_CHUNK_LENGTH; + break; + } + + next: + if (clt->clt_done) + goto done; + if (EVBUFFER_LENGTH(src)) + bev->readcb(bev, arg); + bufferevent_enable(bev, EV_READ); + return; + + done: + server_close(clt, "last http chunk read (done)"); + return; + fail: + server_close(clt, strerror(errno)); +} + +void +server_reset_http(struct client *clt) +{ + struct server *srv = clt->clt_srv; + + server_log(clt, NULL); + + server_httpdesc_free(clt->clt_descreq); + server_httpdesc_free(clt->clt_descresp); + clt->clt_headerlen = 0; + clt->clt_line = 0; + clt->clt_done = 0; + clt->clt_chunk = 0; + free(clt->clt_remote_user); + clt->clt_remote_user = NULL; + clt->clt_bev->readcb = server_read_http; + clt->clt_srv_conf = &srv->srv_conf; +} + +ssize_t +server_http_time(time_t t, char *tmbuf, size_t len) +{ + struct tm tm; + + /* New HTTP/1.1 RFC 7231 prefers IMF-fixdate from RFC 5322 */ + if (t == -1 || gmtime_r(&t, &tm) == NULL) + return (-1); + else + return (strftime(tmbuf, len, "%a, %d %h %Y %T %Z", &tm)); +} + +const char * +server_http_host(struct sockaddr_storage *ss, char *buf, size_t len) +{ + char hbuf[HOST_NAME_MAX+1]; + in_port_t port; + + if (print_host(ss, buf, len) == NULL) + return (NULL); + + port = ntohs(server_socket_getport(ss)); + if (port == HTTP_PORT) + return (buf); + + switch (ss->ss_family) { + case AF_INET: + if ((size_t)snprintf(hbuf, sizeof(hbuf), + "%s:%u", buf, port) >= sizeof(hbuf)) + return (NULL); + break; + case AF_INET6: + if ((size_t)snprintf(hbuf, sizeof(hbuf), + "[%s]:%u", buf, port) >= sizeof(hbuf)) + return (NULL); + break; + } + + if (strlcpy(buf, hbuf, len) >= len) + return (NULL); + + return (buf); +} + +char * +server_http_parsehost(char *host, char *buf, size_t len, int *portval) +{ + char *start, *end, *port; + const char *errstr = NULL; + + if (strlcpy(buf, host, len) >= len) { + log_debug("%s: host name too long", __func__); + return (NULL); + } + + start = buf; + end = port = NULL; + + if (*start == '[' && (end = strchr(start, ']')) != NULL) { + /* Address enclosed in [] with port, eg. [2001:db8::1]:80 */ + start++; + *end++ = '\0'; + if ((port = strchr(end, ':')) == NULL || *port == '\0') + port = NULL; + else + port++; + memmove(buf, start, strlen(start) + 1); + } else if ((end = strchr(start, ':')) != NULL) { + /* Name or address with port, eg. www.example.com:80 */ + *end++ = '\0'; + port = end; + } else { + /* Name or address with default port, eg. www.example.com */ + port = NULL; + } + + if (port != NULL) { + /* Save the requested port */ + *portval = strtonum(port, 0, 0xffff, &errstr); + if (errstr != NULL) { + log_debug("%s: invalid port: %s", __func__, + strerror(errno)); + return (NULL); + } + *portval = htons(*portval); + } else { + /* Port not given, indicate the default port */ + *portval = -1; + } + + return (start); +} + +void +server_abort_http(struct client *clt, u_int code, const char *msg) +{ + struct server *srv = clt->clt_srv; + struct server_config *srv_conf = &srv->srv_conf; + struct bufferevent *bev = clt->clt_bev; + struct http_descriptor *desc = clt->clt_descreq; + const char *httperr = NULL, *style; + char *httpmsg, *body = NULL, *extraheader = NULL; + char tmbuf[32], hbuf[128]; + char buf[IBUF_READ_SIZE], *ptr = NULL; + int bodylen; + + if (code == 0) { + server_close(clt, "dropped"); + return; + } + + if ((httperr = server_httperror_byid(code)) == NULL) + httperr = "Unknown Error"; + + if (bev == NULL) + goto done; + + if (server_log_http(clt, code, 0) == -1) + goto done; + + /* Some system information */ + if (print_host(&srv_conf->ss, hbuf, sizeof(hbuf)) == NULL) + goto done; + + if (server_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0) + goto done; + + /* Do not send details of the Internal Server Error */ + switch (code) { + case 301: + case 302: + case 303: + if (msg == NULL) + break; + memset(buf, 0, sizeof(buf)); + if ((ptr = server_expand_http(clt, msg, + buf, sizeof(buf))) == NULL) + goto done; + if ((ptr = url_encode(ptr)) == NULL) + goto done; + if (asprintf(&extraheader, "Location: %s\r\n", ptr) == -1) { + code = 500; + extraheader = NULL; + } + msg = ptr; + break; + case 401: + if (asprintf(&extraheader, + "WWW-Authenticate: Basic realm=\"%s\"\r\n", msg) == -1) { + code = 500; + extraheader = NULL; + } + break; + default: + /* + * Do not send details of the error. Traditionally, + * web servers responsed with the request path on 40x + * errors which could be abused to inject JavaScript etc. + * Instead of sanitizing the path here, we just don't + * reprint it. + */ + break; + } + + /* A CSS stylesheet allows minimal customization by the user */ + style = "body { background-color: white; color: black; font-family: " + "'Comic Sans MS', 'Chalkboard SE', 'Comic Neue', sans-serif; }\n" + "hr { border: 0; border-bottom: 1px dashed; }\n"; + + /* Generate simple HTML error document */ + if ((bodylen = asprintf(&body, + "<!DOCTYPE html>\n" + "<html>\n" + "<head>\n" + "<title>%03d %s</title>\n" + "<style type=\"text/css\"><!--\n%s\n--></style>\n" + "</head>\n" + "<body>\n" + "<h1>%03d %s</h1>\n" + "<hr>\n<address>%s</address>\n" + "</body>\n" + "</html>\n", + code, httperr, style, code, httperr, HTTPD_SERVERNAME)) == -1) + goto done; + + /* Add basic HTTP headers */ + if (asprintf(&httpmsg, + "HTTP/1.0 %03d %s\r\n" + "Date: %s\r\n" + "Server: %s\r\n" + "Connection: close\r\n" + "Content-Type: text/html\r\n" + "Content-Length: %d\r\n" + "%s" + "\r\n" + "%s", + code, httperr, tmbuf, HTTPD_SERVERNAME, bodylen, + extraheader == NULL ? "" : extraheader, + desc->http_method == HTTP_METHOD_HEAD ? "" : body) == -1) + goto done; + + /* Dump the message without checking for success */ + server_dump(clt, httpmsg, strlen(httpmsg)); + free(httpmsg); + + done: + free(body); + free(extraheader); + if (msg == NULL) + msg = "\"\""; + if (asprintf(&httpmsg, "%s (%03d %s)", msg, code, httperr) == -1) { + server_close(clt, msg); + } else { + server_close(clt, httpmsg); + free(httpmsg); + } + free(ptr); +} + +void +server_close_http(struct client *clt) +{ + struct http_descriptor *desc; + + desc = clt->clt_descreq; + server_httpdesc_free(desc); + free(desc); + clt->clt_descreq = NULL; + + desc = clt->clt_descresp; + server_httpdesc_free(desc); + free(desc); + clt->clt_descresp = NULL; + free(clt->clt_remote_user); + clt->clt_remote_user = NULL; +} + +char * +server_expand_http(struct client *clt, const char *val, char *buf, + size_t len) +{ + struct http_descriptor *desc = clt->clt_descreq; + struct server_config *srv_conf = clt->clt_srv_conf; + char ibuf[128], *str; + + if (strlcpy(buf, val, len) >= len) + return (NULL); + + if (strstr(val, "$DOCUMENT_URI") != NULL) { + if (expand_string(buf, len, "$DOCUMENT_URI", + desc->http_path) != 0) + return (NULL); + } + if (strstr(val, "$QUERY_STRING") != NULL) { + if (expand_string(buf, len, "$QUERY_STRING", + desc->http_query == NULL ? "" : + desc->http_query) != 0) + return (NULL); + } + if (strstr(val, "$REMOTE_") != NULL) { + if (strstr(val, "$REMOTE_ADDR") != NULL) { + if (print_host(&clt->clt_ss, + ibuf, sizeof(ibuf)) == NULL) + return (NULL); + if (expand_string(buf, len, + "$REMOTE_ADDR", ibuf) != 0) + return (NULL); + } + if (strstr(val, "$REMOTE_PORT") != NULL) { + snprintf(ibuf, sizeof(ibuf), + "%u", ntohs(clt->clt_port)); + if (expand_string(buf, len, + "$REMOTE_PORT", ibuf) != 0) + return (NULL); + } + if (strstr(val, "$REMOTE_USER") != NULL) { + if ((srv_conf->flags & SRVFLAG_AUTH) && + clt->clt_remote_user != NULL) + str = clt->clt_remote_user; + else + str = ""; + if (expand_string(buf, len, + "$REMOTE_USER", str) != 0) + return (NULL); + } + } + if (strstr(val, "$REQUEST_URI") != NULL) { + if (desc->http_query == NULL) { + if ((str = strdup(desc->http_path)) == NULL) + return (NULL); + } else if (asprintf(&str, "%s?%s", + desc->http_path, desc->http_query) == -1) + return (NULL); + if (expand_string(buf, len, "$REQUEST_URI", str) != 0) { + free(str); + return (NULL); + } + free(str); + } + if (strstr(val, "$SERVER_") != NULL) { + if (strstr(val, "$SERVER_ADDR") != NULL) { + if (print_host(&srv_conf->ss, + ibuf, sizeof(ibuf)) == NULL) + return (NULL); + if (expand_string(buf, len, + "$SERVER_ADDR", ibuf) != 0) + return (NULL); + } + if (strstr(val, "$SERVER_PORT") != NULL) { + snprintf(ibuf, sizeof(ibuf), "%u", + ntohs(srv_conf->port)); + if (expand_string(buf, len, + "$SERVER_PORT", ibuf) != 0) + return (NULL); + } + if (strstr(val, "$SERVER_NAME") != NULL) { + if (expand_string(buf, len, + "$SERVER_NAME", srv_conf->name) != 0) + return (NULL); + } + } + + return (buf); +} + +int +server_response(struct httpd *httpd, struct client *clt) +{ + char path[PATH_MAX]; + char hostname[HOST_NAME_MAX+1]; + struct http_descriptor *desc = clt->clt_descreq; + struct http_descriptor *resp = clt->clt_descresp; + struct server *srv = clt->clt_srv; + struct server_config *srv_conf = &srv->srv_conf; + struct kv *kv, key, *host; + int portval = -1; + char *hostval; + + /* Canonicalize the request path */ + if (desc->http_path == NULL || + url_decode(desc->http_path) == NULL || + canonicalize_path(desc->http_path, path, sizeof(path)) == NULL) + goto fail; + free(desc->http_path); + if ((desc->http_path = strdup(path)) == NULL) + goto fail; + + key.kv_key = "Host"; + if ((host = kv_find(&desc->http_headers, &key)) != NULL && + host->kv_value == NULL) + host = NULL; + + if (strcmp(desc->http_version, "HTTP/1.1") == 0) { + /* Host header is mandatory */ + if (host == NULL) + goto fail; + + /* Is the connection persistent? */ + key.kv_key = "Connection"; + if ((kv = kv_find(&desc->http_headers, &key)) != NULL && + strcasecmp("close", kv->kv_value) == 0) + clt->clt_persist = 0; + else + clt->clt_persist++; + } else { + /* Is the connection persistent? */ + key.kv_key = "Connection"; + if ((kv = kv_find(&desc->http_headers, &key)) != NULL && + strcasecmp("keep-alive", kv->kv_value) == 0) + clt->clt_persist++; + else + clt->clt_persist = 0; + } + + if (clt->clt_persist >= srv_conf->maxrequests) + clt->clt_persist = 0; + + /* + * Do we have a Host header and matching configuration? + * XXX the Host can also appear in the URL path. + */ + if (host != NULL) { + if ((hostval = server_http_parsehost(host->kv_value, + hostname, sizeof(hostname), &portval)) == NULL) + goto fail; + + TAILQ_FOREACH(srv_conf, &srv->srv_hosts, entry) { +#ifdef DEBUG + if ((srv_conf->flags & SRVFLAG_LOCATION) == 0) { + DPRINTF("%s: virtual host \"%s:%u\"" + " host \"%s\" (\"%s\")", + __func__, srv_conf->name, + ntohs(srv_conf->port), host->kv_value, + hostname); + } +#endif + if ((srv_conf->flags & SRVFLAG_LOCATION) == 0 && + fnmatch(srv_conf->name, hostname, + FNM_CASEFOLD) == 0 && + (portval == -1 || + (portval != -1 && portval == srv_conf->port))) { + /* Replace host configuration */ + clt->clt_srv_conf = srv_conf; + srv_conf = NULL; + break; + } + } + } + + if (srv_conf != NULL) { + /* Use the actual server IP address */ + if (server_http_host(&clt->clt_srv_ss, hostname, + sizeof(hostname)) == NULL) + goto fail; + } else { + /* Host header was valid and found */ + if (strlcpy(hostname, host->kv_value, sizeof(hostname)) >= + sizeof(hostname)) + goto fail; + srv_conf = clt->clt_srv_conf; + } + + if ((desc->http_host = strdup(hostname)) == NULL) + goto fail; + + /* Now fill in the mandatory parts of the response descriptor */ + resp->http_method = desc->http_method; + if ((resp->http_version = strdup(desc->http_version)) == NULL) + goto fail; + + /* Now search for the location */ + srv_conf = server_getlocation(clt, desc->http_path); + + if (srv_conf->flags & SRVFLAG_BLOCK) { + server_abort_http(clt, srv_conf->return_code, + srv_conf->return_uri); + return (-1); + } else if (srv_conf->flags & SRVFLAG_AUTH && + server_http_authenticate(srv_conf, clt) == -1) { + server_abort_http(clt, 401, srv_conf->auth_realm); + return (-1); + } else + return (server_file(httpd, clt)); + fail: + server_abort_http(clt, 400, "bad request"); + return (-1); +} + +const char * +server_root_strip(const char *path, int n) +{ + const char *p; + + /* Strip strip leading directories. Leading '/' is ignored. */ + for (; n > 0 && *path != '\0'; n--) + if ((p = strchr(++path, '/')) == NULL) + path = strchr(path, '\0'); + else + path = p; + + return (path); +} + +struct server_config * +server_getlocation(struct client *clt, const char *path) +{ + struct server *srv = clt->clt_srv; + struct server_config *srv_conf = clt->clt_srv_conf, *location; + + /* Now search for the location */ + TAILQ_FOREACH(location, &srv->srv_hosts, entry) { +#ifdef DEBUG + if (location->flags & SRVFLAG_LOCATION) { + DPRINTF("%s: location \"%s\" path \"%s\"", + __func__, location->location, path); + } +#endif + if ((location->flags & SRVFLAG_LOCATION) && + location->parent_id == srv_conf->parent_id && + fnmatch(location->location, path, FNM_CASEFOLD) == 0) { + /* Replace host configuration */ + clt->clt_srv_conf = srv_conf = location; + break; + } + } + + return (srv_conf); +} + +int +server_response_http(struct client *clt, u_int code, + struct media_type *media, size_t size, time_t mtime) +{ + struct http_descriptor *desc = clt->clt_descreq; + struct http_descriptor *resp = clt->clt_descresp; + const char *error; + struct kv *ct, *cl; + char tmbuf[32]; + + if (desc == NULL || (error = server_httperror_byid(code)) == NULL) + return (-1); + + if (server_log_http(clt, code, size) == -1) + return (-1); + + /* Add error codes */ + if (kv_setkey(&resp->http_pathquery, "%lu", code) == -1 || + kv_set(&resp->http_pathquery, "%s", error) == -1) + return (-1); + + /* Add headers */ + if (kv_add(&resp->http_headers, "Server", HTTPD_SERVERNAME) == NULL) + return (-1); + + /* Is it a persistent connection? */ + if (clt->clt_persist) { + if (kv_add(&resp->http_headers, + "Connection", "keep-alive") == NULL) + return (-1); + } else if (kv_add(&resp->http_headers, "Connection", "close") == NULL) + return (-1); + + /* Set media type */ + if ((ct = kv_add(&resp->http_headers, "Content-Type", NULL)) == NULL || + kv_set(ct, "%s/%s", + media == NULL ? "application" : media->media_type, + media == NULL ? "octet-stream" : media->media_subtype) == -1) + return (-1); + + /* Set content length, if specified */ + if ((cl = + kv_add(&resp->http_headers, "Content-Length", NULL)) == NULL || + kv_set(cl, "%ld", size) == -1) + return (-1); + + /* Set last modification time */ + if (server_http_time(mtime, tmbuf, sizeof(tmbuf)) <= 0 || + kv_add(&resp->http_headers, "Last-Modified", tmbuf) == NULL) + return (-1); + + /* Date header is mandatory and should be added as late as possible */ + if (server_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0 || + kv_add(&resp->http_headers, "Date", tmbuf) == NULL) + return (-1); + + /* Write completed header */ + if (server_writeresponse_http(clt) == -1 || + server_bufferevent_print(clt, "\r\n") == -1 || + server_headers(clt, resp, server_writeheader_http, NULL) == -1 || + server_bufferevent_print(clt, "\r\n") == -1) + return (-1); + + if (size == 0 || resp->http_method == HTTP_METHOD_HEAD) { + bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); + if (clt->clt_persist) + clt->clt_toread = TOREAD_HTTP_HEADER; + else + clt->clt_toread = TOREAD_HTTP_NONE; + clt->clt_done = 0; + return (0); + } + + return (1); +} + +int +server_writeresponse_http(struct client *clt) +{ + struct http_descriptor *desc = clt->clt_descresp; + + DPRINTF("version: %s rescode: %s resmsg: %s", desc->http_version, + desc->http_rescode, desc->http_resmesg); + + if (server_bufferevent_print(clt, desc->http_version) == -1 || + server_bufferevent_print(clt, " ") == -1 || + server_bufferevent_print(clt, desc->http_rescode) == -1 || + server_bufferevent_print(clt, " ") == -1 || + server_bufferevent_print(clt, desc->http_resmesg) == -1) + return (-1); + + return (0); +} + +int +server_writeheader_http(struct client *clt, struct kv *hdr, void *arg) +{ + char *ptr; + const char *key; + + if (hdr->kv_flags & KV_FLAG_INVALID) + return (0); + + /* The key might have been updated in the parent */ + if (hdr->kv_parent != NULL && hdr->kv_parent->kv_key != NULL) + key = hdr->kv_parent->kv_key; + else + key = hdr->kv_key; + + ptr = hdr->kv_value; + if (server_bufferevent_print(clt, key) == -1 || + (ptr != NULL && + (server_bufferevent_print(clt, ": ") == -1 || + server_bufferevent_print(clt, ptr) == -1 || + server_bufferevent_print(clt, "\r\n") == -1))) + return (-1); + DPRINTF("%s: %s: %s", __func__, key, + hdr->kv_value == NULL ? "" : hdr->kv_value); + + return (0); +} + +int +server_headers(struct client *clt, void *descp, + int (*hdr_cb)(struct client *, struct kv *, void *), void *arg) +{ + struct kv *hdr, *kv; + struct http_descriptor *desc = descp; + + RB_FOREACH(hdr, kvtree, &desc->http_headers) { + if ((hdr_cb)(clt, hdr, arg) == -1) + return (-1); + TAILQ_FOREACH(kv, &hdr->kv_children, kv_entry) { + if ((hdr_cb)(clt, kv, arg) == -1) + return (-1); + } + } + + return (0); +} + +enum httpmethod +server_httpmethod_byname(const char *name) +{ + enum httpmethod id = HTTP_METHOD_NONE; + struct http_method method, *res = NULL; + + /* Set up key */ + method.method_name = name; + + if ((res = bsearch(&method, http_methods, + sizeof(http_methods) / sizeof(http_methods[0]) - 1, + sizeof(http_methods[0]), server_httpmethod_cmp)) != NULL) + id = res->method_id; + + return (id); +} + +const char * +server_httpmethod_byid(u_int id) +{ + const char *name = "<UNKNOWN>"; + int i; + + for (i = 0; http_methods[i].method_name != NULL; i++) { + if (http_methods[i].method_id == id) { + name = http_methods[i].method_name; + break; + } + } + + return (name); +} + +static int +server_httpmethod_cmp(const void *a, const void *b) +{ + const struct http_method *ma = a; + const struct http_method *mb = b; + + /* + * RFC 2616 section 5.1.1 says that the method is case + * sensitive so we don't do a strcasecmp here. + */ + return (strcmp(ma->method_name, mb->method_name)); +} + +const char * +server_httperror_byid(u_int id) +{ + struct http_error error, *res; + + /* Set up key */ + error.error_code = (int)id; + + if ((res = bsearch(&error, http_errors, + sizeof(http_errors) / sizeof(http_errors[0]) - 1, + sizeof(http_errors[0]), server_httperror_cmp)) != NULL) + return (res->error_name); + + return (NULL); +} + +static int +server_httperror_cmp(const void *a, const void *b) +{ + const struct http_error *ea = a; + const struct http_error *eb = b; + return (ea->error_code - eb->error_code); +} + +int +server_log_http(struct client *clt, u_int code, size_t len) +{ + static char tstamp[64]; + static char ip[INET6_ADDRSTRLEN]; + time_t t; + struct kv key, *agent, *referrer; + struct tm *tm; + struct server_config *srv_conf; + struct http_descriptor *desc; + + if ((srv_conf = clt->clt_srv_conf) == NULL) + return (-1); + if ((srv_conf->flags & SRVFLAG_LOG) == 0) + return (0); + if ((desc = clt->clt_descreq) == NULL) + return (-1); + + if ((t = time(NULL)) == -1) + return (-1); + if ((tm = localtime(&t)) == NULL) + return (-1); + if (strftime(tstamp, sizeof(tstamp), "%d/%b/%Y:%H:%M:%S %z", tm) == 0) + return (-1); + + if (print_host(&clt->clt_ss, ip, sizeof(ip)) == NULL) + return (-1); + + /* + * For details on common log format, see: + * https://httpd.apache.org/docs/current/mod/mod_log_config.html + * + * httpd's format is similar to these Apache LogFormats: + * "%v %h %l %u %t \"%r\" %>s %B" + * "%v %h %l %u %t \"%r\" %>s %B \"%{Referer}i\" \"%{User-agent}i\"" + */ + switch (srv_conf->logformat) { + case LOG_FORMAT_COMMON: + if (evbuffer_add_printf(clt->clt_log, + "%s %s - %s [%s] \"%s %s%s%s%s%s\" %03d %zu\n", + srv_conf->name, ip, clt->clt_remote_user == NULL ? "-" : + clt->clt_remote_user, tstamp, + server_httpmethod_byid(desc->http_method), + desc->http_path == NULL ? "" : desc->http_path, + desc->http_query == NULL ? "" : "?", + desc->http_query == NULL ? "" : desc->http_query, + desc->http_version == NULL ? "" : " ", + desc->http_version == NULL ? "" : desc->http_version, + code, len) == -1) + return (-1); + break; + + case LOG_FORMAT_COMBINED: + key.kv_key = "Referer"; /* sic */ + if ((referrer = kv_find(&desc->http_headers, &key)) != NULL && + referrer->kv_value == NULL) + referrer = NULL; + + key.kv_key = "User-Agent"; + if ((agent = kv_find(&desc->http_headers, &key)) != NULL && + agent->kv_value == NULL) + agent = NULL; + + if (evbuffer_add_printf(clt->clt_log, + "%s %s - %s [%s] \"%s %s%s%s%s%s\"" + " %03d %zu \"%s\" \"%s\"\n", + srv_conf->name, ip, clt->clt_remote_user == NULL ? "-" : + clt->clt_remote_user, tstamp, + server_httpmethod_byid(desc->http_method), + desc->http_path == NULL ? "" : desc->http_path, + desc->http_query == NULL ? "" : "?", + desc->http_query == NULL ? "" : desc->http_query, + desc->http_version == NULL ? "" : " ", + desc->http_version == NULL ? "" : desc->http_version, + code, len, + referrer == NULL ? "" : referrer->kv_value, + agent == NULL ? "" : agent->kv_value) == -1) + return (-1); + break; + + case LOG_FORMAT_CONNECTION: + if (evbuffer_add_printf(clt->clt_log, " [%s]", + desc->http_path == NULL ? "" : desc->http_path) == -1) + return (-1); + break; + } + + return (0); +} |