From 7fdb196d97b9af7ed5e238ce8dca1ed5ad21ebbe Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Sat, 7 Mar 2015 21:15:37 +0100 Subject: Move files the git repository --- Makefile | 21 +- config.c | 589 --------------- control.c | 334 --------- http.h | 253 ------- httpd.8 | 102 --- httpd.c | 1281 -------------------------------- httpd.conf.5 | 533 ------------- httpd.h | 688 ----------------- httpd/Makefile | 19 + httpd/config.c | 589 +++++++++++++++ httpd/control.c | 334 +++++++++ httpd/http.h | 253 +++++++ httpd/httpd.8 | 102 +++ httpd/httpd.c | 1281 ++++++++++++++++++++++++++++++++ httpd/httpd.conf.5 | 533 +++++++++++++ httpd/httpd.h | 688 +++++++++++++++++ httpd/log.c | 242 ++++++ httpd/logger.c | 312 ++++++++ httpd/parse.y | 2062 +++++++++++++++++++++++++++++++++++++++++++++++++++ httpd/proc.c | 622 ++++++++++++++++ httpd/server.c | 1221 ++++++++++++++++++++++++++++++ httpd/server_fcgi.c | 729 ++++++++++++++++++ httpd/server_file.c | 469 ++++++++++++ httpd/server_http.c | 1425 +++++++++++++++++++++++++++++++++++ log.c | 242 ------ logger.c | 312 -------- parse.y | 2062 --------------------------------------------------- proc.c | 622 ---------------- server.c | 1221 ------------------------------ server_fcgi.c | 729 ------------------ server_file.c | 469 ------------ server_http.c | 1425 ----------------------------------- 32 files changed, 10884 insertions(+), 10880 deletions(-) delete mode 100644 config.c delete mode 100644 control.c delete mode 100644 http.h delete mode 100644 httpd.8 delete mode 100644 httpd.c delete mode 100644 httpd.conf.5 delete mode 100644 httpd.h create mode 100644 httpd/Makefile create mode 100644 httpd/config.c create mode 100644 httpd/control.c create mode 100644 httpd/http.h create mode 100644 httpd/httpd.8 create mode 100644 httpd/httpd.c create mode 100644 httpd/httpd.conf.5 create mode 100644 httpd/httpd.h create mode 100644 httpd/log.c create mode 100644 httpd/logger.c create mode 100644 httpd/parse.y create mode 100644 httpd/proc.c create mode 100644 httpd/server.c create mode 100644 httpd/server_fcgi.c create mode 100644 httpd/server_file.c create mode 100644 httpd/server_http.c delete mode 100644 log.c delete mode 100644 logger.c delete mode 100644 parse.y delete mode 100644 proc.c delete mode 100644 server.c delete mode 100644 server_fcgi.c delete mode 100644 server_file.c delete mode 100644 server_http.c diff --git a/Makefile b/Makefile index 885ad42..7ff4af3 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,4 @@ -# $OpenBSD: Makefile,v 1.27 2015/02/23 10:39:10 reyk Exp $ +SUBDIR= httpd +MAKE_FLAGS= BINDIR=/usr/sbin SUDO=sudo -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 +.include diff --git a/config.c b/config.c deleted file mode 100644 index 2dc9159..0000000 --- a/config.c +++ /dev/null @@ -1,589 +0,0 @@ -/* $OpenBSD: config.c,v 1.36 2015/02/23 11:48:41 reyk Exp $ */ - -/* - * Copyright (c) 2011 - 2015 Reyk Floeter - * - * 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 -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#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/control.c b/control.c deleted file mode 100644 index fe60375..0000000 --- a/control.c +++ /dev/null @@ -1,334 +0,0 @@ -/* $OpenBSD: control.c,v 1.6 2015/01/21 22:21:05 reyk Exp $ */ - -/* - * Copyright (c) 2003, 2004 Henning Brauer - * - * 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 -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#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/http.h b/http.h deleted file mode 100644 index 00cf235..0000000 --- a/http.h +++ /dev/null @@ -1,253 +0,0 @@ -/* $OpenBSD: http.h,v 1.12 2015/02/11 12:52:01 florian Exp $ */ - -/* - * Copyright (c) 2012 - 2015 Reyk Floeter - * - * 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.8 b/httpd.8 deleted file mode 100644 index eb35096..0000000 --- a/httpd.8 +++ /dev/null @@ -1,102 +0,0 @@ -.\" $OpenBSD: httpd.8,v 1.50 2015/02/24 07:56:06 bentley Exp $ -.\" -.\" Copyright (c) 2014 Reyk Floeter -.\" -.\" 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.c b/httpd.c deleted file mode 100644 index c6f183f..0000000 --- a/httpd.c +++ /dev/null @@ -1,1281 +0,0 @@ -/* $OpenBSD: httpd.c,v 1.35 2015/02/23 18:43:18 reyk Exp $ */ - -/* - * Copyright (c) 2014 Reyk Floeter - * - * 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 /* nitems */ -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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.conf.5 b/httpd.conf.5 deleted file mode 100644 index acfb57a..0000000 --- a/httpd.conf.5 +++ /dev/null @@ -1,533 +0,0 @@ -.\" $OpenBSD: httpd.conf.5,v 1.54 2015/03/06 05:10:18 reyk Exp $ -.\" -.\" Copyright (c) 2014, 2015 Reyk Floeter -.\" -.\" 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.h b/httpd.h deleted file mode 100644 index 28e02a5..0000000 --- a/httpd.h +++ /dev/null @@ -1,688 +0,0 @@ -/* $OpenBSD: httpd.h,v 1.81 2015/02/23 18:43:18 reyk Exp $ */ - -/* - * Copyright (c) 2006 - 2015 Reyk Floeter - * Copyright (c) 2006, 2007 Pierre-Yves Ritschard - * Copyright (c) 2003, 2004 Henning Brauer - * - * 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 -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -#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/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 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 + * + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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 + * + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 +.\" +.\" 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 + * + * 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 /* nitems */ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +.\" +.\" 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 + * Copyright (c) 2006, 2007 Pierre-Yves Ritschard + * Copyright (c) 2003, 2004 Henning Brauer + * + * 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 +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#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 + * + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 /* nitems */ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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 + * Copyright (c) 2008 Gilles Chehade + * Copyright (c) 2006 Pierre-Yves Ritschard + * Copyright (c) 2004, 2005 Esben Norby + * Copyright (c) 2004 Ryan McBride + * Copyright (c) 2002, 2003, 2004 Henning Brauer + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 STRING +%token NUMBER +%type port +%type opttls +%type timeout +%type numberstring optstring +%type 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 + * Copyright (c) 2008 Pierre-Yves Ritschard + * + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 /* nitems */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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, + "\n" + "\n" + "\n" + "Index of %s\n" + "\n" + "\n" + "\n" + "

Index of %s

\n" + "
\n
\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,
+			    "%s/%*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,
+			    "%s%*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,
+	    "
\n
\n\n\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 + * + * 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 +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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, + "\n" + "\n" + "\n" + "%03d %s\n" + "\n" + "\n" + "\n" + "

%03d %s

\n" + "
\n
%s
\n" + "\n" + "\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 = ""; + 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); +} diff --git a/log.c b/log.c deleted file mode 100644 index a5dfc6b..0000000 --- a/log.c +++ /dev/null @@ -1,242 +0,0 @@ -/* $OpenBSD: log.c,v 1.5 2015/01/21 22:21:05 reyk Exp $ */ - -/* - * Copyright (c) 2003, 2004 Henning Brauer - * - * 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 -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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/logger.c b/logger.c deleted file mode 100644 index b4e4404..0000000 --- a/logger.c +++ /dev/null @@ -1,312 +0,0 @@ -/* $OpenBSD: logger.c,v 1.11 2015/02/08 00:00:59 reyk Exp $ */ - -/* - * Copyright (c) 2014 Reyk Floeter - * - * 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 /* nitems */ -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#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/parse.y b/parse.y deleted file mode 100644 index 7e48aba..0000000 --- a/parse.y +++ /dev/null @@ -1,2062 +0,0 @@ -/* $OpenBSD: parse.y,v 1.65 2015/02/12 04:40:23 jsing Exp $ */ - -/* - * Copyright (c) 2007 - 2015 Reyk Floeter - * Copyright (c) 2008 Gilles Chehade - * Copyright (c) 2006 Pierre-Yves Ritschard - * Copyright (c) 2004, 2005 Esben Norby - * Copyright (c) 2004 Ryan McBride - * Copyright (c) 2002, 2003, 2004 Henning Brauer - * 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 -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 STRING -%token NUMBER -%type port -%type opttls -%type timeout -%type numberstring optstring -%type 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/proc.c b/proc.c deleted file mode 100644 index 34d65c0..0000000 --- a/proc.c +++ /dev/null @@ -1,622 +0,0 @@ -/* $OpenBSD: proc.c,v 1.8 2015/01/21 22:21:05 reyk Exp $ */ - -/* - * Copyright (c) 2010 - 2014 Reyk Floeter - * Copyright (c) 2008 Pierre-Yves Ritschard - * - * 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 -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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/server.c b/server.c deleted file mode 100644 index 813f945..0000000 --- a/server.c +++ /dev/null @@ -1,1221 +0,0 @@ -/* $OpenBSD: server.c,v 1.60 2015/02/23 09:52:28 reyk Exp $ */ - -/* - * Copyright (c) 2006 - 2015 Reyk Floeter - * - * 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 /* nitems */ -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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/server_fcgi.c b/server_fcgi.c deleted file mode 100644 index 33603a0..0000000 --- a/server_fcgi.c +++ /dev/null @@ -1,729 +0,0 @@ -/* $OpenBSD: server_fcgi.c,v 1.52 2015/02/23 19:22:43 chrisz Exp $ */ - -/* - * Copyright (c) 2014 Florian Obser - * - * 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 -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#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/server_file.c b/server_file.c deleted file mode 100644 index f697504..0000000 --- a/server_file.c +++ /dev/null @@ -1,469 +0,0 @@ -/* $OpenBSD: server_file.c,v 1.51 2015/02/12 10:05:29 reyk Exp $ */ - -/* - * Copyright (c) 2006 - 2015 Reyk Floeter - * - * 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 -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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, - "\n" - "\n" - "\n" - "Index of %s\n" - "\n" - "\n" - "\n" - "

Index of %s

\n" - "
\n
\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,
-			    "%s/%*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,
-			    "%s%*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,
-	    "
\n
\n\n\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/server_http.c b/server_http.c deleted file mode 100644 index b63fc22..0000000 --- a/server_http.c +++ /dev/null @@ -1,1425 +0,0 @@ -/* $OpenBSD: server_http.c,v 1.75 2015/02/23 18:43:18 reyk Exp $ */ - -/* - * Copyright (c) 2006 - 2015 Reyk Floeter - * - * 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 -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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, - "\n" - "\n" - "\n" - "%03d %s\n" - "\n" - "\n" - "\n" - "

%03d %s

\n" - "
\n
%s
\n" - "\n" - "\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 = ""; - 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); -} -- cgit v1.2.3-54-g00ecf