From ce95017bf16f7937013134fc5188900d3e2cd9c7 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Wed, 16 Aug 2017 09:27:36 +0200 Subject: sync --- httpd/Makefile | 6 +- httpd/config.c | 199 ++++++++---- httpd/control.c | 20 +- httpd/httpd.8 | 5 +- httpd/httpd.c | 150 +++++---- httpd/httpd.conf.5 | 48 ++- httpd/httpd.h | 146 ++++++--- httpd/log.c | 52 +-- httpd/logger.c | 29 +- httpd/parse.y | 97 ++++-- httpd/patterns.7 | 8 +- httpd/proc.c | 602 +++++++++++++++++++++++------------ httpd/server.c | 288 +++++++++++------ httpd/server_fcgi.c | 117 +++---- httpd/server_file.c | 171 +++++----- httpd/server_http.c | 157 ++++++++- regress/tests/Httpd.pm | 5 +- regress/tests/LICENSE | 2 +- regress/tests/Makefile | 34 +- regress/tests/README | 10 +- regress/tests/args-get-1048576.pl | 2 +- regress/tests/args-get-1073741824.pl | 2 +- regress/tests/args-get-512.pl | 2 +- regress/tests/args-get-slash.pl | 4 +- regress/tests/funcs.pl | 182 +++++------ 25 files changed, 1496 insertions(+), 842 deletions(-) diff --git a/httpd/Makefile b/httpd/Makefile index e01dec1..3766675 100644 --- a/httpd/Makefile +++ b/httpd/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.28 2015/06/23 15:23:14 reyk Exp $ +# $OpenBSD: Makefile,v 1.30 2017/07/03 22:21:47 espie Exp $ PROG= httpd SRCS= parse.y @@ -16,7 +16,7 @@ CFLAGS+= -Wall -I${.CURDIR} CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes CFLAGS+= -Wmissing-declarations CFLAGS+= -Wshadow -Wpointer-arith -CFLAGS+= -Wsign-compare -CLEANFILES+= y.tab.h +CFLAGS+= -Wsign-compare -Wcast-qual +YFLAGS= .include diff --git a/httpd/config.c b/httpd/config.c index 4f8ef4c..3c31c3d 100644 --- a/httpd/config.c +++ b/httpd/config.c @@ -1,4 +1,4 @@ -/* $OpenBSD: config.c,v 1.47 2016/08/15 14:14:55 jsing Exp $ */ +/* $OpenBSD: config.c,v 1.53 2017/07/19 17:36:25 jsing Exp $ */ /* * Copyright (c) 2011 - 2015 Reyk Floeter @@ -41,14 +41,13 @@ config_init(struct httpd *env) unsigned int what; /* Global configuration */ - if (privsep_process == PROC_PARENT) { + 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; - } + 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]; @@ -147,6 +146,7 @@ config_getcfg(struct httpd *env, struct imsg *imsg) memcpy(&cf, imsg->data, sizeof(cf)); env->sc_opts = cf.cf_opts; env->sc_flags = cf.cf_flags; + memcpy(env->sc_tls_sid, cf.cf_tls_sid, sizeof(env->sc_tls_sid)); what = ps->ps_what[privsep_process]; @@ -211,10 +211,18 @@ config_setserver(struct httpd *env, struct server *srv) __func__, srv->srv_conf.name); return (-1); } + + /* Prevent fd exhaustion in the parent. */ + if (proc_flush_imsg(ps, id, n) == -1) { + log_warn("%s: failed to flush " + "IMSG_CFG_SERVER imsg for `%s'", + __func__, srv->srv_conf.name); + return (-1); + } } /* Configure TLS if necessary. */ - config_settls(env, srv); + config_setserver_tls(env, srv); } else { if (proc_composev(ps, id, IMSG_CFG_SERVER, iov, c) != 0) { @@ -226,11 +234,21 @@ config_setserver(struct httpd *env, struct server *srv) } } + /* Close server socket early to prevent fd exhaustion in the parent. */ + if (srv->srv_s != -1) { + close(srv->srv_s); + srv->srv_s = -1; + } + + explicit_bzero(&srv->srv_conf.tls_ticket_key, + sizeof(srv->srv_conf.tls_ticket_key)); + return (0); } -int -config_settls(struct httpd *env, struct server *srv) +static int +config_settls(struct httpd *env, struct server *srv, enum tls_config_type type, + const char *label, uint8_t *data, size_t len) { struct privsep *ps = env->sc_ps; struct server_config *srv_conf = &srv->srv_conf; @@ -238,54 +256,65 @@ config_settls(struct httpd *env, struct server *srv) struct iovec iov[2]; size_t c; - if ((srv_conf->flags & SRVFLAG_TLS) == 0) + if (data == NULL || len == 0) return (0); - log_debug("%s: configuring tls for %s", __func__, srv_conf->name); + DPRINTF("%s: sending tls %s for \"%s[%u]\" to %s fd %d", __func__, + label, srv_conf->name, srv_conf->id, ps->ps_title[PROC_SERVER], + srv->srv_s); - if (srv_conf->tls_cert_len != 0) { - DPRINTF("%s: sending tls cert \"%s[%u]\" to %s fd %d", __func__, - srv_conf->name, srv_conf->id, ps->ps_title[PROC_SERVER], - srv->srv_s); + memset(&tls, 0, sizeof(tls)); + tls.id = srv_conf->id; + tls.tls_type = type; + tls.tls_len = len; + tls.tls_chunk_offset = 0; - memset(&tls, 0, sizeof(tls)); - tls.id = srv_conf->id; - tls.tls_cert_len = srv_conf->tls_cert_len; + while (len > 0) { + tls.tls_chunk_len = len; + if (tls.tls_chunk_len > (MAX_IMSG_DATA_SIZE - sizeof(tls))) + tls.tls_chunk_len = MAX_IMSG_DATA_SIZE - sizeof(tls); c = 0; iov[c].iov_base = &tls; iov[c++].iov_len = sizeof(tls); - iov[c].iov_base = srv_conf->tls_cert; - iov[c++].iov_len = srv_conf->tls_cert_len; + iov[c].iov_base = data; + iov[c++].iov_len = tls.tls_chunk_len; if (proc_composev(ps, PROC_SERVER, IMSG_CFG_TLS, iov, c) != 0) { log_warn("%s: failed to compose IMSG_CFG_TLS imsg for " "`%s'", __func__, srv_conf->name); return (-1); } + + tls.tls_chunk_offset += tls.tls_chunk_len; + data += tls.tls_chunk_len; + len -= tls.tls_chunk_len; } - if (srv_conf->tls_key_len != 0) { - DPRINTF("%s: sending tls key \"%s[%u]\" to %s fd %d", __func__, - srv_conf->name, srv_conf->id, ps->ps_title[PROC_SERVER], - srv->srv_s); + return (0); +} - memset(&tls, 0, sizeof(tls)); - tls.id = srv_conf->id; - tls.tls_key_len = srv_conf->tls_key_len; +int +config_setserver_tls(struct httpd *env, struct server *srv) +{ + struct server_config *srv_conf = &srv->srv_conf; - c = 0; - iov[c].iov_base = &tls; - iov[c++].iov_len = sizeof(tls); - iov[c].iov_base = srv_conf->tls_key; - iov[c++].iov_len = srv_conf->tls_key_len; + if ((srv_conf->flags & SRVFLAG_TLS) == 0) + return (0); - if (proc_composev(ps, PROC_SERVER, IMSG_CFG_TLS, iov, c) != 0) { - log_warn("%s: failed to compose IMSG_CFG_TLS imsg for " - "`%s'", __func__, srv_conf->name); - return (-1); - } - } + log_debug("%s: configuring tls for %s", __func__, srv_conf->name); + + if (config_settls(env, srv, TLS_CFG_CERT, "cert", srv_conf->tls_cert, + srv_conf->tls_cert_len) != 0) + return (-1); + + if (config_settls(env, srv, TLS_CFG_KEY, "key", srv_conf->tls_key, + srv_conf->tls_key_len) != 0) + return (-1); + + if (config_settls(env, srv, TLS_CFG_OCSP_STAPLE, "ocsp staple", + srv_conf->tls_ocsp_staple, srv_conf->tls_ocsp_staple_len) != 0) + return (-1); return (0); } @@ -554,49 +583,101 @@ config_getserver(struct httpd *env, struct imsg *imsg) return (-1); } -int -config_gettls(struct httpd *env, struct imsg *imsg) +static int +config_gettls(struct httpd *env, struct server_config *srv_conf, + struct tls_config *tls_conf, const char *label, uint8_t *data, size_t len, + uint8_t **outdata, size_t *outlen) { #ifdef DEBUG struct privsep *ps = env->sc_ps; #endif + + DPRINTF("%s: %s %d getting tls %s (%zu:%zu@%zu) for \"%s[%u]\"", + __func__, ps->ps_title[privsep_process], ps->ps_instance, label, + tls_conf->tls_len, len, tls_conf->tls_chunk_offset, srv_conf->name, + srv_conf->id); + + if (tls_conf->tls_chunk_offset == 0) { + free(*outdata); + *outlen = 0; + if ((*outdata = calloc(1, tls_conf->tls_len)) == NULL) + goto fail; + *outlen = tls_conf->tls_len; + } + + if (*outdata == NULL) { + log_debug("%s: tls config invalid chunk sequence", __func__); + goto fail; + } + + if (*outlen != tls_conf->tls_len) { + log_debug("%s: tls config length mismatch (%zu != %zu)", + __func__, *outlen, tls_conf->tls_len); + goto fail; + } + + if (len > (tls_conf->tls_len - tls_conf->tls_chunk_offset)) { + log_debug("%s: tls config invalid chunk length", __func__); + goto fail; + } + + memcpy(*outdata + tls_conf->tls_chunk_offset, data, len); + + return (0); + + fail: + return (-1); +} + +int +config_getserver_tls(struct httpd *env, struct imsg *imsg) +{ struct server_config *srv_conf = NULL; struct tls_config tls_conf; uint8_t *p = imsg->data; - size_t s; + size_t len; IMSG_SIZE_CHECK(imsg, &tls_conf); memcpy(&tls_conf, p, sizeof(tls_conf)); - s = sizeof(tls_conf); - if ((IMSG_DATA_SIZE(imsg) - s) < - (tls_conf.tls_cert_len + tls_conf.tls_key_len)) { + len = tls_conf.tls_chunk_len; + + if ((IMSG_DATA_SIZE(imsg) - sizeof(tls_conf)) < len) { log_debug("%s: invalid message length", __func__); goto fail; } + p += sizeof(tls_conf); + if ((srv_conf = serverconfig_byid(tls_conf.id)) == NULL) { log_debug("%s: server not found", __func__); goto fail; } - DPRINTF("%s: %s %d tls configuration \"%s[%u]\"", __func__, - ps->ps_title[privsep_process], ps->ps_instance, - srv_conf->name, srv_conf->id); + switch (tls_conf.tls_type) { + case TLS_CFG_CERT: + if (config_gettls(env, srv_conf, &tls_conf, "cert", p, len, + &srv_conf->tls_cert, &srv_conf->tls_cert_len) != 0) + goto fail; + break; - if (tls_conf.tls_cert_len != 0) { - srv_conf->tls_cert_len = tls_conf.tls_cert_len; - if ((srv_conf->tls_cert = get_data(p + s, - tls_conf.tls_cert_len)) == NULL) + case TLS_CFG_KEY: + if (config_gettls(env, srv_conf, &tls_conf, "key", p, len, + &srv_conf->tls_key, &srv_conf->tls_key_len) != 0) goto fail; - s += tls_conf.tls_cert_len; - } - if (tls_conf.tls_key_len != 0) { - srv_conf->tls_key_len = tls_conf.tls_key_len; - if ((srv_conf->tls_key = get_data(p + s, - tls_conf.tls_key_len)) == NULL) + break; + + case TLS_CFG_OCSP_STAPLE: + if (config_gettls(env, srv_conf, &tls_conf, "ocsp staple", + p, len, &srv_conf->tls_ocsp_staple, + &srv_conf->tls_ocsp_staple_len) != 0) goto fail; - s += tls_conf.tls_key_len; + break; + + default: + log_debug("%s: unknown tls config type %i\n", + __func__, tls_conf.tls_type); + goto fail; } return (0); diff --git a/httpd/control.c b/httpd/control.c index c29cd5b..7eea19a 100644 --- a/httpd/control.c +++ b/httpd/control.c @@ -1,4 +1,4 @@ -/* $OpenBSD: control.c,v 1.9 2015/12/05 13:15:27 claudio Exp $ */ +/* $OpenBSD: control.c,v 1.13 2017/01/09 14:49:22 reyk Exp $ */ /* * Copyright (c) 2003, 2004 Henning Brauer @@ -181,9 +181,10 @@ 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 */ + TAILQ_FOREACH(c, &ctl_conns, entry) { + if (c->iev.ibuf.fd == fd) + break; + } return (c); } @@ -273,7 +274,8 @@ control_dispatch_imsg(int fd, short event, void *arg) "client requested notify more than once", __func__); imsg_compose_event(&c->iev, IMSG_CTL_FAIL, - 0, 0, -1, NULL, 0); + 0, env->sc_ps->ps_instance + 1, -1, + NULL, 0); break; } c->flags |= CTL_CONN_NOTIFY; @@ -287,8 +289,8 @@ control_dispatch_imsg(int fd, short event, void *arg) proc_forward_imsg(env->sc_ps, &imsg, PROC_SERVER, -1); memcpy(imsg.data, &verbose, sizeof(verbose)); - control_imsg_forward(&imsg); - log_verbose(verbose); + control_imsg_forward(env->sc_ps, &imsg); + log_setverbose(verbose); break; default: log_debug("%s: error handling imsg %d", @@ -302,13 +304,13 @@ control_dispatch_imsg(int fd, short event, void *arg) } void -control_imsg_forward(struct imsg *imsg) +control_imsg_forward(struct privsep *ps, 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, + 0, ps->ps_instance + 1, -1, imsg->data, imsg->hdr.len - IMSG_HEADER_SIZE); } diff --git a/httpd/httpd.8 b/httpd/httpd.8 index 7084120..f65b5d5 100644 --- a/httpd/httpd.8 +++ b/httpd/httpd.8 @@ -1,4 +1,4 @@ -.\" $OpenBSD: httpd.8,v 1.52 2016/06/10 18:32:40 jmc Exp $ +.\" $OpenBSD: httpd.8,v 1.53 2016/09/15 20:57:07 jmc Exp $ .\" .\" Copyright (c) 2014 Reyk Floeter .\" @@ -14,7 +14,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: June 10 2016 $ +.Dd $Mdocdate: September 15 2016 $ .Dt HTTPD 8 .Os .Sh NAME @@ -85,6 +85,7 @@ Default access log file. Default error log file. .El .Sh SEE ALSO +.Xr acme-client 1 , .Xr httpd.conf 5 , .Xr slowcgi 8 .Sh HISTORY diff --git a/httpd/httpd.c b/httpd/httpd.c index fae7c53..6d1d1ff 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: httpd.c,v 1.56 2016/06/10 12:09:48 florian Exp $ */ +/* $OpenBSD: httpd.c,v 1.67 2017/05/28 10:37:26 benno Exp $ */ /* * Copyright (c) 2014 Reyk Floeter @@ -16,12 +16,10 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include /* nitems */ #include #include #include #include -#include #include #include @@ -59,6 +57,8 @@ int parent_dispatch_server(int, struct privsep_proc *, struct imsg *); int parent_dispatch_logger(int, struct privsep_proc *, struct imsg *); +void parent_tls_ticket_rekey_start(struct server *); +void parent_tls_ticket_rekey(int, short, void *); struct httpd *httpd_env; @@ -71,56 +71,11 @@ 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 { - int len; - - pid = waitpid(WAIT_ANY, &status, WNOHANG); - if (pid <= 0) - continue; - - fail = 0; - if (WIFSIGNALED(status)) { - fail = 1; - len = asprintf(&cause, "terminated; signal %d", - WTERMSIG(status)); - } else if (WIFEXITED(status)) { - if (WEXITSTATUS(status) != 0) { - fail = 1; - len = asprintf(&cause, - "exited abnormally"); - } else - len = asprintf(&cause, "exited okay"); - } else - fatalx("unexpected cause of SIGCHLD"); - - if (len == -1) - fatal("asprintf"); - - 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); + parent_shutdown(ps->ps_env); break; case SIGHUP: log_info("%s: reload requested with SIGHUP", __func__); @@ -164,8 +119,12 @@ main(int argc, char *argv[]) struct httpd *env; struct privsep *ps; const char *conffile = CONF_FILE; + enum privsep_procid proc_id = PROC_PARENT; + int proc_instance = 0; + const char *errp, *title = NULL; + int argc0 = argc; - while ((c = getopt(argc, argv, "dD:nf:v")) != -1) { + while ((c = getopt(argc, argv, "dD:nf:I:P:v")) != -1) { switch (c) { case 'd': debug = 2; @@ -186,6 +145,18 @@ main(int argc, char *argv[]) verbose++; opts |= HTTPD_OPT_VERBOSE; break; + case 'P': + title = optarg; + proc_id = proc_getid(procs, nitems(procs), title); + if (proc_id == PROC_MAX) + fatalx("invalid process name"); + break; + case 'I': + proc_instance = strtonum(optarg, 0, + PROC_MAX_INSTANCES, &errp); + if (errp) + fatalx("invalid process instance"); + break; default: usage(); } @@ -222,18 +193,15 @@ main(int argc, char *argv[]) ps->ps_csock.cs_name = NULL; log_init(debug, LOG_DAEMON); - log_verbose(verbose); - - if (!debug && daemon(1, 0) == -1) - err(1, "failed to daemonize"); + log_setverbose(verbose); 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; + ps->ps_instance = proc_instance; + if (title != NULL) + ps->ps_title[proc_id] = title; if (env->sc_chroot == NULL) env->sc_chroot = ps->ps_pw->pw_dir; @@ -246,30 +214,34 @@ main(int argc, char *argv[]) errx(1, "malloc failed"); } - proc_init(ps, procs, nitems(procs)); + /* only the parent returns */ + proc_init(ps, procs, nitems(procs), argc0, argv, proc_id); + log_procinit("parent"); + if (!debug && daemon(1, 0) == -1) + err(1, "failed to daemonize"); - if (pledge("stdio rpath wpath cpath inet dns proc ioctl sendfd", - NULL) == -1) + if (ps->ps_noaction == 0) + log_info("startup"); + + if (pledge("stdio rpath wpath cpath inet dns sendfd", NULL) == -1) fatal("pledge"); 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)); + proc_connect(ps); if (load_config(env->sc_conffile, env) == -1) { proc_kill(env->sc_ps); @@ -282,6 +254,9 @@ main(int argc, char *argv[]) exit(0); } + /* initialize the TLS session id to a random key for all procs */ + arc4random_buf(env->sc_tls_sid, sizeof(env->sc_tls_sid)); + if (parent_configure(env) == -1) fatalx("configuration failed"); @@ -317,6 +292,10 @@ parent_configure(struct httpd *env) TAILQ_FOREACH(srv, env->sc_servers, srv_entry) { if (srv->srv_conf.flags & SRVFLAG_LOCATION) continue; + /* start the rekey of the tls ticket keys */ + if (srv->srv_conf.flags & SRVFLAG_TLS && + srv->srv_conf.tls_ticket_lifetime) + parent_tls_ticket_rekey_start(srv); if (config_setserver(env, srv) == -1) fatal("send server"); } @@ -336,13 +315,14 @@ parent_configure(struct httpd *env) continue; cf.cf_opts = env->sc_opts; cf.cf_flags = env->sc_flags; + memcpy(cf.cf_tls_sid, env->sc_tls_sid, sizeof(cf.cf_tls_sid)); proc_compose(env->sc_ps, id, IMSG_CFG_DONE, &cf, sizeof(cf)); } ret = 0; - config_purge(env, CONFIG_ALL); + config_purge(env, CONFIG_ALL & ~CONFIG_SERVERS); return (ret); } @@ -427,7 +407,8 @@ parent_shutdown(struct httpd *env) int parent_dispatch_server(int fd, struct privsep_proc *p, struct imsg *imsg) { - struct httpd *env = p->p_env; + struct privsep *ps = p->p_ps; + struct httpd *env = ps->ps_env; switch (imsg->hdr.type) { case IMSG_CFG_DONE: @@ -443,7 +424,8 @@ parent_dispatch_server(int fd, struct privsep_proc *p, struct imsg *imsg) int parent_dispatch_logger(int fd, struct privsep_proc *p, struct imsg *imsg) { - struct httpd *env = p->p_env; + struct privsep *ps = p->p_ps; + struct httpd *env = ps->ps_env; unsigned int v; char *str = NULL; @@ -479,6 +461,38 @@ parent_dispatch_logger(int fd, struct privsep_proc *p, struct imsg *imsg) return (0); } +void +parent_tls_ticket_rekey_start(struct server *srv) +{ + struct timeval tv; + + server_generate_ticket_key(&srv->srv_conf); + + evtimer_set(&srv->srv_evt, parent_tls_ticket_rekey, srv); + timerclear(&tv); + tv.tv_sec = srv->srv_conf.tls_ticket_lifetime / 4; + evtimer_add(&srv->srv_evt, &tv); +} + +void +parent_tls_ticket_rekey(int fd, short events, void *arg) +{ + struct server *srv = arg; + struct timeval tv; + + server_generate_ticket_key(&srv->srv_conf); + proc_compose_imsg(httpd_env->sc_ps, PROC_SERVER, -1, + IMSG_TLSTICKET_REKEY, -1, -1, &srv->srv_conf.tls_ticket_key, + sizeof(srv->srv_conf.tls_ticket_key)); + explicit_bzero(&srv->srv_conf.tls_ticket_key, + sizeof(srv->srv_conf.tls_ticket_key)); + + evtimer_set(&srv->srv_evt, parent_tls_ticket_rekey, srv); + timerclear(&tv); + tv.tv_sec = srv->srv_conf.tls_ticket_lifetime / 4; + evtimer_add(&srv->srv_evt, &tv); +} + /* * Utility functions */ @@ -777,7 +791,7 @@ socket_rlimit(int maxfd) struct rlimit rl; if (getrlimit(RLIMIT_NOFILE, &rl) == -1) - fatal("socket_rlimit: failed to get resource limit"); + fatal("%s: failed to get resource limit", __func__); log_debug("%s: max open files %llu", __func__, rl.rlim_max); /* @@ -789,7 +803,7 @@ socket_rlimit(int maxfd) 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"); + fatal("%s: failed to set resource limit", __func__); } char * diff --git a/httpd/httpd.conf.5 b/httpd/httpd.conf.5 index 2bd3ec7..a3c9762 100644 --- a/httpd/httpd.conf.5 +++ b/httpd/httpd.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: httpd.conf.5,v 1.73 2016/05/09 19:36:54 tj Exp $ +.\" $OpenBSD: httpd.conf.5,v 1.84 2017/08/11 20:30:45 jmc Exp $ .\" .\" Copyright (c) 2014, 2015 Reyk Floeter .\" @@ -14,7 +14,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: May 9 2016 $ +.Dd $Mdocdate: August 11 2017 $ .Dt HTTPD.CONF 5 .Os .Sh NAME @@ -221,6 +221,8 @@ The configured IP address of the server. The configured TCP server port of the server. .It Ic $SERVER_NAME The name of the server. +.It Ic $HTTP_HOST +The host from the HTTP Host header. .It Pf % Ar n The capture index .Ar n @@ -240,8 +242,14 @@ 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 request timeout Ar seconds +Specify the inactivity timeout for HTTP operations between client and server, +for example the maximum time to wait for a request from the client. +The default timeout is 60 seconds (1 minute). +The maximum is 2147483647 seconds (68 years). .It Ic timeout Ar seconds -Specify the inactivity timeout in seconds for accepted sessions. +Specify the inactivity timeout in seconds for accepted sessions, +for example the maximum time to wait for I/O from the FastCGI backend. The default timeout is 600 seconds (10 minutes). The maximum is 2147483647 seconds (68 years). .El @@ -358,6 +366,11 @@ Specify server configuration rules for a specific location. The .Ar path argument will be matched against the request path with shell globbing rules. +In case of multiple location statements in the same context, the +first matching location statement will be put into effect, while all +later ones will be ignored. +Therefore it is advisable to match for more specific paths first +and for generic ones later on. A location section may include most of the server configuration rules except .Ic alias , @@ -519,10 +532,12 @@ 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 ecdhe Ar curves +Specify a comma separated list of elliptic curves to use for ECDHE cipher suites, +in order of preference. +The special value of "default" will use the default curves; see +.Xr tls_config_set_ecdhecurves 3 +for further details. .It Ic key Ar file Specify the private key to use for this server. The @@ -533,6 +548,16 @@ root directory of .Nm httpd . The default is .Pa /etc/ssl/private/server.key . +.It Ic ocsp Ar file +Specify an OCSP response to be stapled during TLS handshakes +with this server. +The +.Ar file +should contain a DER-format OCSP response retrieved from an +OCSP server for the +.Ar certificate +in use. +The default is to not use OCSP stapling. .It Ic protocols Ar string Specify the TLS protocols to enable for this server. If not specified, the value @@ -541,6 +566,13 @@ will be used (secure protocols; TLSv1.2-only). Refer to the .Xr tls_config_parse_protocols 3 function for other valid protocol string values. +.It Ic ticket Ic lifetime Ar seconds +Enable TLS session tickets with a +.Ar seconds +session lifetime. +It is possible to set +.Ar seconds +to default to use the httpd default timeout of 2 hours. .El .El .Sh TYPES @@ -560,6 +592,7 @@ will use built-in media types for .Ar image/gif , .Ar image/png , .Ar image/jpeg , +.Ar image/svg+xml , and .Ar application/javascript . .Pp @@ -659,6 +692,7 @@ server "www.example.com" { .Xr htpasswd 1 , .Xr patterns 7 , .Xr httpd 8 , +.Xr ocspcheck 8 , .Xr slowcgi 8 .Sh AUTHORS .An -nosplit diff --git a/httpd/httpd.h b/httpd/httpd.h index 595cce9..05cbb8e 100644 --- a/httpd/httpd.h +++ b/httpd/httpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: httpd.h,v 1.106 2016/08/15 16:12:34 jsing Exp $ */ +/* $OpenBSD: httpd.h,v 1.134 2017/08/11 18:48:56 jsing Exp $ */ /* * Copyright (c) 2006 - 2015 Reyk Floeter @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -38,6 +39,10 @@ #include "patterns.h" +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + #define CONF_FILE "/etc/httpd.conf" #define HTTPD_SOCKET "/var/run/httpd.sock" #define HTTPD_USER "www" @@ -54,14 +59,14 @@ #define HTTPD_TLS_KEY "/etc/ssl/private/server.key" #define HTTPD_TLS_CIPHERS "compat" #define HTTPD_TLS_DHE_PARAMS "none" -#define HTTPD_TLS_ECDHE_CURVE "auto" +#define HTTPD_TLS_ECDHE_CURVES "default" #define FD_RESERVE 5 #define SERVER_MAX_CLIENTS 1024 #define SERVER_TIMEOUT 600 +#define SERVER_REQUESTTIMEOUT 60 #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 */ @@ -70,6 +75,10 @@ #define SERVER_MAX_PREFETCH 256 #define SERVER_MIN_PREFETCHED 32 #define SERVER_HSTS_DEFAULT_AGE 31536000 +#define SERVER_MAX_RANGES 4 +#define SERVER_DEF_TLS_LIFETIME (2 * 3600) +#define SERVER_MIN_TLS_LIFETIME (60) +#define SERVER_MAX_TLS_LIFETIME (24 * 3600) #define MEDIATYPE_NAMEMAX 128 /* file name extension */ #define MEDIATYPE_TYPEMAX 64 /* length of type/subtype */ @@ -82,12 +91,16 @@ #define FCGI_CONTENT_SIZE 65535 +#define PROC_PARENT_SOCK_FILENO 3 +#define PROC_MAX_INSTANCES 32 + enum httpchunk { TOREAD_UNLIMITED = -1, TOREAD_HTTP_HEADER = -2, TOREAD_HTTP_CHUNK_LENGTH = -3, TOREAD_HTTP_CHUNK_TRAILER = -4, - TOREAD_HTTP_NONE = -5 + TOREAD_HTTP_NONE = -5, + TOREAD_HTTP_RANGE = TOREAD_HTTP_CHUNK_LENGTH }; #if DEBUG @@ -99,6 +112,7 @@ enum httpchunk { struct ctl_flags { uint8_t cf_opts; uint32_t cf_flags; + uint8_t cf_tls_sid[TLS_MAX_SESSION_ID_LENGTH]; }; enum key_type { @@ -178,6 +192,7 @@ struct imsgev { fatalx("bad length imsg received"); \ } while (0) #define IMSG_DATA_SIZE(imsg) ((imsg)->hdr.len - IMSG_HEADER_SIZE) +#define MAX_IMSG_DATA_SIZE (MAX_IMSGSIZE - IMSG_HEADER_SIZE) struct ctl_conn { TAILQ_ENTRY(ctl_conn) entry; @@ -194,6 +209,7 @@ enum imsg_type { IMSG_CTL_OK, IMSG_CTL_FAIL, IMSG_CTL_VERBOSE, + IMSG_CTL_PROCFD, IMSG_CTL_RESET, IMSG_CTL_SHUTDOWN, IMSG_CTL_RELOAD, @@ -208,7 +224,8 @@ enum imsg_type { IMSG_CFG_DONE, IMSG_LOG_ACCESS, IMSG_LOG_ERROR, - IMSG_LOG_OPEN + IMSG_LOG_OPEN, + IMSG_TLSTICKET_REKEY }; enum privsep_procid { @@ -232,11 +249,9 @@ struct privsep { struct imsgev *ps_ievs[PROC_MAX]; const char *ps_title[PROC_MAX]; - pid_t ps_pid[PROC_MAX]; uint8_t ps_what[PROC_MAX]; unsigned int ps_instances[PROC_MAX]; - unsigned int ps_ninstances; unsigned int ps_instance; struct control_sock ps_csock; @@ -260,13 +275,17 @@ struct privsep_proc { enum privsep_procid p_id; int (*p_cb)(int, struct privsep_proc *, struct imsg *); - pid_t (*p_init)(struct privsep *, + void (*p_init)(struct privsep *, struct privsep_proc *); - void (*p_shutdown)(void); - unsigned int p_instance; const char *p_chroot; struct privsep *p_ps; - struct httpd *p_env; + void (*p_shutdown)(void); + struct passwd *p_pw; +}; + +struct privsep_fd { + enum privsep_procid pf_procid; + unsigned int pf_instance; }; enum fcgistate { @@ -275,6 +294,33 @@ enum fcgistate { FCGI_READ_PADDING }; +struct fcgi_data { + enum fcgistate state; + int toread; + int padding_len; + int type; + int chunked; + int end; + int status; + int headersdone; +}; + +struct range { + off_t start; + off_t end; +}; + +struct range_data { + struct range range[SERVER_MAX_RANGES]; + int range_count; + int range_index; + off_t range_toread; + + /* For the Content headers in each part */ + struct media_type *range_media; + size_t range_total; +}; + struct client { uint32_t clt_id; pid_t clt_pid; @@ -293,6 +339,7 @@ struct client { void *clt_descreq; void *clt_descresp; int clt_sndbufsiz; + uint64_t clt_boundary; int clt_fd; struct tls *clt_tls_ctx; @@ -301,17 +348,15 @@ struct client { off_t clt_toread; size_t clt_headerlen; + int clt_headersdone; unsigned int clt_persist; + unsigned int clt_pipelining; 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; + struct range_data clt_ranges; + struct fcgi_data clt_fcgi; char *clt_remote_user; struct evbuffer *clt_srvevb; @@ -405,6 +450,12 @@ struct auth { }; TAILQ_HEAD(serverauth, auth); +struct server_tls_ticket { + uint32_t tt_id; + uint32_t tt_keyrev; + unsigned char tt_key[TLS_TICKET_KEY_SIZE]; +}; + struct server_config { uint32_t id; uint32_t parent_id; @@ -421,6 +472,7 @@ struct server_config { struct sockaddr_storage ss; int prefixlen; struct timeval timeout; + struct timeval requesttimeout; uint32_t maxrequests; size_t maxrequestbody; @@ -429,11 +481,16 @@ struct server_config { char *tls_cert_file; char tls_ciphers[NAME_MAX]; char tls_dhe_params[NAME_MAX]; - char tls_ecdhe_curve[NAME_MAX]; + char tls_ecdhe_curves[NAME_MAX]; uint8_t *tls_key; size_t tls_key_len; char *tls_key_file; uint32_t tls_protocols; + uint8_t *tls_ocsp_staple; + size_t tls_ocsp_staple_len; + char *tls_ocsp_staple_file; + struct server_tls_ticket tls_ticket_key; + int tls_ticket_lifetime; uint32_t flags; int strip; @@ -462,11 +519,19 @@ struct server_config { }; TAILQ_HEAD(serverhosts, server_config); +enum tls_config_type { + TLS_CFG_CERT, + TLS_CFG_KEY, + TLS_CFG_OCSP_STAPLE, +}; + struct tls_config { uint32_t id; - size_t tls_cert_len; - size_t tls_key_len; + enum tls_config_type tls_type; + size_t tls_len; + size_t tls_chunk_len; + size_t tls_chunk_offset; }; struct server { @@ -496,6 +561,8 @@ struct httpd { char *sc_chroot; char *sc_logdir; + uint8_t sc_tls_sid[TLS_MAX_SESSION_ID_LENGTH]; + struct serverlist *sc_servers; struct mediatypes *sc_mediatypes; struct media_type sc_default_type; @@ -513,7 +580,7 @@ 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 *); +void control_imsg_forward(struct privsep *, struct imsg *); struct ctl_conn * control_connbyfd(int); @@ -525,9 +592,11 @@ int load_config(const char *, struct httpd *); int cmdline_symset(char *); /* server.c */ -pid_t server(struct privsep *, struct privsep_proc *); -int server_tls_cmp(struct server *, struct server *); +void server(struct privsep *, struct privsep_proc *); +int server_tls_cmp(struct server *, struct server *, int); int server_tls_load_keypair(struct server *); +int server_tls_load_ocsp(struct server *); +void server_generate_ticket_key(struct server_config *); int server_privinit(struct server *); void server_purge(struct server *); void serverconfig_free(struct server_config *); @@ -568,7 +637,7 @@ SPLAY_PROTOTYPE(client_tree, client, clt_nodes, server_client_cmp); /* server_http.c */ void server_http_init(struct server *); -void server_http(struct httpd *); +void server_http(void); int server_httpdesc_init(struct client *); void server_read_http(struct bufferevent *, void *); void server_abort_http(struct client *, unsigned int, const char *); @@ -580,12 +649,13 @@ const char *server_httperror_byid(unsigned int); void server_read_httpcontent(struct bufferevent *, void *); void server_read_httpchunks(struct bufferevent *, void *); +void server_read_httprange(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 *, unsigned int, struct media_type *, - off_t, time_t); +int server_response_http(struct client *, unsigned int, + struct media_type *, off_t, time_t); void server_reset_http(struct client *); void server_close_http(struct client *); int server_response(struct httpd *, struct client *); @@ -618,9 +688,6 @@ 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 *, uint16_t, uint32_t, - pid_t, int, void *, uint16_t); void socket_rlimit(int); char *evbuffer_getline(struct evbuffer *); char *get_string(uint8_t *, size_t); @@ -663,10 +730,13 @@ const char *print_time(struct timeval *, struct timeval *, char *, size_t); const char *printb_flags(const uint32_t, const char *); void getmonotime(struct timeval *); +extern struct httpd *httpd_env; + /* log.c */ void log_init(int, int); void log_procinit(const char *); -void log_verbose(int); +void log_setverbose(int); +int log_getverbose(void); void log_warn(const char *, ...) __attribute__((__format__ (printf, 1, 2))); void log_warnx(const char *, ...) @@ -685,11 +755,14 @@ __dead void fatalx(const char *, ...) __attribute__((__format__ (printf, 1, 2))); /* proc.c */ -void proc_init(struct privsep *, struct privsep_proc *, unsigned int); +enum privsep_procid + proc_getid(struct privsep_proc *, unsigned int, const char *); +void proc_init(struct privsep *, struct privsep_proc *, unsigned int, + int, char **, enum privsep_procid); void proc_kill(struct privsep *); -void proc_listen(struct privsep *, struct privsep_proc *, size_t); +void proc_connect(struct privsep *); void proc_dispatch(int, short event, void *); -pid_t proc_run(struct privsep *, struct privsep_proc *, +void proc_run(struct privsep *, struct privsep_proc *, struct privsep_proc *, unsigned int, void (*)(struct privsep *, struct privsep_proc *, void *), void *); void proc_range(struct privsep *, enum privsep_procid, int *, int *); @@ -707,6 +780,7 @@ struct imsgbuf * proc_ibuf(struct privsep *, enum privsep_procid, int); struct imsgev * proc_iev(struct privsep *, enum privsep_procid, int); +int proc_flush_imsg(struct privsep *, enum privsep_procid, int); void imsg_event_add(struct imsgev *); int imsg_compose_event(struct imsgev *, uint16_t, uint32_t, pid_t, int, void *, uint16_t); @@ -720,16 +794,16 @@ int config_setreset(struct httpd *, unsigned int); int config_getreset(struct httpd *, struct imsg *); int config_getcfg(struct httpd *, struct imsg *); int config_setserver(struct httpd *, struct server *); -int config_settls(struct httpd *, struct server *); +int config_setserver_tls(struct httpd *, struct server *); int config_getserver(struct httpd *, struct imsg *); -int config_gettls(struct httpd *, struct imsg *); +int config_getserver_tls(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 *); +void 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 index 1f6ff49..b7e25a6 100644 --- a/httpd/log.c +++ b/httpd/log.c @@ -1,4 +1,4 @@ -/* $OpenBSD: log.c,v 1.10 2015/12/07 12:13:51 reyk Exp $ */ +/* $OpenBSD: log.c,v 1.14 2017/03/21 12:06:55 bluhm Exp $ */ /* * Copyright (c) 2003, 2004 Henning Brauer @@ -24,13 +24,14 @@ #include #include -int debug; -int verbose; +static int debug; +static int verbose; const char *log_procname; void log_init(int, int); void log_procinit(const char *); -void log_verbose(int); +void log_setverbose(int); +int log_getverbose(void); void log_warn(const char *, ...) __attribute__((__format__ (printf, 1, 2))); void log_warnx(const char *, ...) @@ -71,11 +72,17 @@ log_procinit(const char *procname) } void -log_verbose(int v) +log_setverbose(int v) { verbose = v; } +int +log_getverbose(void) +{ + return (verbose); +} + void logit(int pri, const char *fmt, ...) { @@ -90,6 +97,7 @@ void vlog(int pri, const char *fmt, va_list ap) { char *nfmt; + int saved_errno = errno; if (debug) { /* best effort in out of mem situations */ @@ -103,31 +111,36 @@ vlog(int pri, const char *fmt, va_list ap) fflush(stderr); } else vsyslog(pri, fmt, ap); -} + errno = saved_errno; +} void log_warn(const char *emsg, ...) { - char *nfmt; - va_list ap; + char *nfmt; + va_list ap; + int saved_errno = errno; /* best effort to even work in out of memory situations */ if (emsg == NULL) - logit(LOG_CRIT, "%s", strerror(errno)); + logit(LOG_ERR, "%s", strerror(saved_errno)); else { va_start(ap, emsg); - if (asprintf(&nfmt, "%s: %s", emsg, strerror(errno)) == -1) { + if (asprintf(&nfmt, "%s: %s", emsg, + strerror(saved_errno)) == -1) { /* we tried it... */ - vlog(LOG_CRIT, emsg, ap); - logit(LOG_CRIT, "%s", strerror(errno)); + vlog(LOG_ERR, emsg, ap); + logit(LOG_ERR, "%s", strerror(saved_errno)); } else { - vlog(LOG_CRIT, nfmt, ap); + vlog(LOG_ERR, nfmt, ap); free(nfmt); } va_end(ap); } + + errno = saved_errno; } void @@ -136,7 +149,7 @@ log_warnx(const char *emsg, ...) va_list ap; va_start(ap, emsg); - vlog(LOG_CRIT, emsg, ap); + vlog(LOG_ERR, emsg, ap); va_end(ap); } @@ -163,7 +176,7 @@ log_debug(const char *emsg, ...) } static void -vfatal(const char *emsg, va_list ap) +vfatalc(int code, const char *emsg, va_list ap) { static char s[BUFSIZ]; const char *sep; @@ -175,9 +188,9 @@ vfatal(const char *emsg, va_list ap) s[0] = '\0'; sep = ""; } - if (errno) + if (code) logit(LOG_CRIT, "%s: %s%s%s", - log_procname, s, sep, strerror(errno)); + log_procname, s, sep, strerror(code)); else logit(LOG_CRIT, "%s%s%s", log_procname, sep, s); } @@ -188,7 +201,7 @@ fatal(const char *emsg, ...) va_list ap; va_start(ap, emsg); - vfatal(emsg, ap); + vfatalc(errno, emsg, ap); va_end(ap); exit(1); } @@ -198,9 +211,8 @@ fatalx(const char *emsg, ...) { va_list ap; - errno = 0; va_start(ap, emsg); - vfatal(emsg, ap); + vfatalc(0, emsg, ap); va_end(ap); exit(1); } diff --git a/httpd/logger.c b/httpd/logger.c index a212fb1..6d469b2 100644 --- a/httpd/logger.c +++ b/httpd/logger.c @@ -1,4 +1,4 @@ -/* $OpenBSD: logger.c,v 1.15 2015/12/02 15:13:00 reyk Exp $ */ +/* $OpenBSD: logger.c,v 1.20 2016/09/01 10:59:38 reyk Exp $ */ /* * Copyright (c) 2014 Reyk Floeter @@ -16,7 +16,6 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include /* nitems */ #include #include #include @@ -44,8 +43,6 @@ 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 uint32_t last_log_id = 0; static struct privsep_proc procs[] = { @@ -53,18 +50,17 @@ static struct privsep_proc procs[] = { { "server", PROC_SERVER, logger_dispatch_server } }; -pid_t +void logger(struct privsep *ps, struct privsep_proc *p) { - env = ps->ps_env; - return (proc_run(ps, p, procs, nitems(procs), logger_init, NULL)); + proc_run(ps, p, procs, nitems(procs), logger_init, NULL); } void logger_shutdown(void) { logger_close(); - config_purge(env, CONFIG_ALL); + config_purge(httpd_env, CONFIG_ALL); } void @@ -76,9 +72,6 @@ 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; @@ -121,7 +114,7 @@ logger_open_file(const char *name) iov[1].iov_base = log->log_name; iov[1].iov_len = strlen(log->log_name) + 1; - if (proc_composev(env->sc_ps, PROC_PARENT, IMSG_LOG_OPEN, + if (proc_composev(httpd_env->sc_ps, PROC_PARENT, IMSG_LOG_OPEN, iov, 2) != 0) { log_warn("%s: failed to compose IMSG_LOG_OPEN imsg", __func__); goto err; @@ -174,7 +167,7 @@ logger_open_priv(struct imsg *imsg) if ((size_t)snprintf(name, sizeof(name), "/%s", p) >= sizeof(name)) return (-1); - if ((len = strlcpy(path, env->sc_logdir, sizeof(path))) + if ((len = strlcpy(path, httpd_env->sc_logdir, sizeof(path))) >= sizeof(path)) return (-1); @@ -191,8 +184,8 @@ logger_open_priv(struct imsg *imsg) return (-1); } - proc_compose_imsg(env->sc_ps, PROC_LOGGER, -1, IMSG_LOG_OPEN, -1, fd, - &id, sizeof(id)); + proc_compose_imsg(httpd_env->sc_ps, PROC_LOGGER, -1, + IMSG_LOG_OPEN, -1, fd, &id, sizeof(id)); DPRINTF("%s: opened log file %s, fd %d", __func__, path, fd); @@ -286,17 +279,17 @@ logger_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg) { switch (imsg->hdr.type) { case IMSG_CFG_SERVER: - config_getserver(env, imsg); + config_getserver(httpd_env, imsg); break; case IMSG_CFG_DONE: - config_getcfg(env, imsg); + config_getcfg(httpd_env, imsg); break; case IMSG_CTL_START: case IMSG_CTL_REOPEN: logger_start(); break; case IMSG_CTL_RESET: - config_getreset(env, imsg); + config_getreset(httpd_env, imsg); break; case IMSG_LOG_OPEN: return (logger_open_fd(imsg)); diff --git a/httpd/parse.y b/httpd/parse.y index 6900bc6..203ddd1 100644 --- a/httpd/parse.y +++ b/httpd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.80 2016/08/15 16:12:34 jsing Exp $ */ +/* $OpenBSD: parse.y,v 1.91 2017/08/11 18:48:56 jsing Exp $ */ /* * Copyright (c) 2007 - 2015 Reyk Floeter @@ -130,10 +130,10 @@ typedef struct { %} %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 MATCH MAXIMUM NO NODELAY ON PORT PREFORK PROTOCOLS -%token REQUEST REQUESTS ROOT SACK SERVER SOCKET STRIP STYLE SYSLOG TCP TIMEOUT -%token TLS TYPE TYPES HSTS MAXAGE SUBDOMAINS DEFAULT PRELOAD +%token COMBINED CONNECTION DHE DIRECTORY ECDHE ERR FCGI INDEX IP KEY LIFETIME +%token LISTEN LOCATION LOG LOGDIR MATCH MAXIMUM NO NODELAY OCSP ON PORT PREFORK +%token PROTOCOLS REQUESTS ROOT SACK SERVER SOCKET STRIP STYLE SYSLOG TCP TICKET +%token TIMEOUT TLS TYPE TYPES HSTS MAXAGE SUBDOMAINS DEFAULT PRELOAD REQUEST %token ERROR INCLUDE AUTHENTICATE WITH BLOCK DROP RETURN PASS %token STRING %token NUMBER @@ -193,7 +193,7 @@ opttls : /*empty*/ { $$ = 0; } main : PREFORK NUMBER { if (loadcfg) break; - if ($2 <= 0 || $2 > SERVER_MAXPROC) { + if ($2 <= 0 || $2 > PROC_MAX_INSTANCES) { yyerror("invalid number of preforked " "servers: %lld", $2); YYERROR; @@ -245,6 +245,8 @@ server : SERVER optmatch STRING { s->srv_conf.parent_id = s->srv_conf.id; s->srv_s = -1; s->srv_conf.timeout.tv_sec = SERVER_TIMEOUT; + s->srv_conf.requesttimeout.tv_sec = + SERVER_REQUESTTIMEOUT; s->srv_conf.maxrequests = SERVER_MAXREQUESTS; s->srv_conf.maxrequestbody = SERVER_MAXREQUESTBODY; s->srv_conf.flags = SRVFLAG_LOG; @@ -264,9 +266,9 @@ server : SERVER optmatch STRING { 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)); + strlcpy(s->srv_conf.tls_ecdhe_curves, + HTTPD_TLS_ECDHE_CURVES, + sizeof(s->srv_conf.tls_ecdhe_curves)); s->srv_conf.hsts_max_age = SERVER_HSTS_DEFAULT_AGE; @@ -314,7 +316,7 @@ server : SERVER optmatch STRING { free(srv); YYERROR; } - if (server_tls_cmp(s, srv) != 0) { + if (server_tls_cmp(s, srv, 0) != 0) { yyerror("server \"%s\": tls " "configuration mismatch on same " "address/port", @@ -342,6 +344,14 @@ server : SERVER optmatch STRING { YYERROR; } + if (server_tls_load_ocsp(srv) == -1) { + yyerror("server \"%s\": failed to load " + "ocsp staple", srv->srv_conf.name); + serverconfig_free(srv_conf); + free(srv); + YYERROR; + } + DPRINTF("adding server \"%s[%u]\"", srv->srv_conf.name, srv->srv_conf.id); @@ -678,6 +688,10 @@ conflags : TIMEOUT timeout { memcpy(&srv_conf->timeout, &$2, sizeof(struct timeval)); } + | REQUEST TIMEOUT timeout { + memcpy(&srv_conf->requesttimeout, &$3, + sizeof(struct timeval)); + } | MAXIMUM REQUESTS NUMBER { srv_conf->maxrequests = $3; } @@ -706,6 +720,13 @@ tlsopts : CERTIFICATE STRING { fatal("out of memory"); free($2); } + | OCSP STRING { + free(srv_conf->tls_ocsp_staple_file); + if ((srv_conf->tls_ocsp_staple_file = strdup($2)) + == NULL) + fatal("out of memory"); + free($2); + } | CIPHERS STRING { if (strlcpy(srv_conf->tls_ciphers, $2, sizeof(srv_conf->tls_ciphers)) >= @@ -727,9 +748,9 @@ tlsopts : CERTIFICATE STRING { free($2); } | ECDHE STRING { - if (strlcpy(srv_conf->tls_ecdhe_curve, $2, - sizeof(srv_conf->tls_ecdhe_curve)) >= - sizeof(srv_conf->tls_ecdhe_curve)) { + if (strlcpy(srv_conf->tls_ecdhe_curves, $2, + sizeof(srv_conf->tls_ecdhe_curves)) >= + sizeof(srv_conf->tls_ecdhe_curves)) { yyerror("ecdhe too long"); free($2); YYERROR; @@ -745,6 +766,23 @@ tlsopts : CERTIFICATE STRING { } free($2); } + | TICKET LIFETIME DEFAULT { + srv_conf->tls_ticket_lifetime = SERVER_DEF_TLS_LIFETIME; + } + | TICKET LIFETIME NUMBER { + if ($3 != 0 && $3 < SERVER_MIN_TLS_LIFETIME) { + yyerror("ticket lifetime too small"); + YYERROR; + } + if ($3 > SERVER_MAX_TLS_LIFETIME) { + yyerror("ticket lifetime too large"); + YYERROR; + } + srv_conf->tls_ticket_lifetime = $3; + } + | NO TICKET { + srv_conf->tls_ticket_lifetime = 0; + } ; root : ROOT rootflags @@ -1197,6 +1235,7 @@ lookup(char *s) { "index", INDEX }, { "ip", IP }, { "key", KEY }, + { "lifetime", LIFETIME }, { "listen", LISTEN }, { "location", LOCATION }, { "log", LOG }, @@ -1206,6 +1245,7 @@ lookup(char *s) { "max-age", MAXAGE }, { "no", NO }, { "nodelay", NODELAY }, + { "ocsp", OCSP }, { "on", ON }, { "pass", PASS }, { "port", PORT }, @@ -1224,6 +1264,7 @@ lookup(char *s) { "subdomains", SUBDOMAINS }, { "syslog", SYSLOG }, { "tcp", TCP }, + { "ticket", TICKET }, { "timeout", TIMEOUT }, { "tls", TLS }, { "type", TYPE }, @@ -1582,8 +1623,7 @@ parse_config(const char *filename, struct httpd *x_conf) endprotoent(); /* Free macros */ - for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) { - next = TAILQ_NEXT(sym, entry); + TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) { if (!sym->persist) { free(sym->nam); free(sym->val); @@ -1673,9 +1713,10 @@ 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 */ + TAILQ_FOREACH(sym, &symhead, entry) { + if (strcmp(nam, sym->nam) == 0) + break; + } if (sym != NULL) { if (sym->persist == 1) @@ -1734,11 +1775,12 @@ symget(const char *nam) { struct sym *sym; - TAILQ_FOREACH(sym, &symhead, entry) + TAILQ_FOREACH(sym, &symhead, entry) { if (strcmp(nam, sym->nam) == 0) { sym->used = 1; return (sym->val); } + } return (NULL); } @@ -2007,10 +2049,11 @@ server_inherit(struct server *src, struct server_config *alias, 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; - dst->srv_conf.tls_cert_len = 0; - dst->srv_conf.tls_key_len = 0; + if (src->srv_conf.tls_ocsp_staple_file != NULL) { + if ((dst->srv_conf.tls_ocsp_staple_file = + strdup(src->srv_conf.tls_ocsp_staple_file)) == NULL) + fatal("out of memory"); + } if (src->srv_conf.return_uri != NULL && (dst->srv_conf.return_uri = @@ -2050,6 +2093,14 @@ server_inherit(struct server *src, struct server_config *alias, return (NULL); } + if (server_tls_load_ocsp(dst) == -1) { + yyerror("failed to load ocsp staple " + "for server %s", dst->srv_conf.name); + serverconfig_free(&dst->srv_conf); + free(dst); + return (NULL); + } + /* Check if the new server already exists */ if (server_match(dst, 1) != NULL) { yyerror("server \"%s\" defined twice", diff --git a/httpd/patterns.7 b/httpd/patterns.7 index a01ede5..1ea3e41 100644 --- a/httpd/patterns.7 +++ b/httpd/patterns.7 @@ -1,4 +1,4 @@ -.\" $OpenBSD: patterns.7,v 1.5 2015/06/30 19:01:05 jmc Exp $ +.\" $OpenBSD: patterns.7,v 1.6 2017/06/10 13:31:45 schwarze Exp $ .\" .\" Copyright (c) 2015 Reyk Floeter .\" Copyright (C) 1994-2015 Lua.org, PUC-Rio. @@ -23,9 +23,9 @@ .\" SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. .\" .\" Derived from section 6.4.1 in manual.html of Lua 5.3.1: -.\" $Id: patterns.7,v 1.5 2015/06/30 19:01:05 jmc Exp $ +.\" $Id: patterns.7,v 1.6 2017/06/10 13:31:45 schwarze Exp $ .\" -.Dd $Mdocdate: June 30 2015 $ +.Dd $Mdocdate: June 10 2017 $ .Dt PATTERNS 7 .Os .Sh NAME @@ -255,7 +255,7 @@ the part of the string matching .Qq a*(.)%w(%s*) is stored as the first capture (and therefore has number 1); the character matching -.So \. Sc +.Qq \&. is captured with number 2, and the part matching .Qq %s* diff --git a/httpd/proc.c b/httpd/proc.c index 7a6124a..cd387a1 100644 --- a/httpd/proc.c +++ b/httpd/proc.c @@ -1,7 +1,7 @@ -/* $OpenBSD: proc.c,v 1.15 2015/12/07 16:05:56 reyk Exp $ */ +/* $OpenBSD: proc.c,v 1.37 2017/05/28 10:37:26 benno Exp $ */ /* - * Copyright (c) 2010 - 2014 Reyk Floeter + * Copyright (c) 2010 - 2016 Reyk Floeter * Copyright (c) 2008 Pierre-Yves Ritschard * * Permission to use, copy, modify, and distribute this software for any @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -34,8 +35,12 @@ #include "httpd.h" -void proc_open(struct privsep *, struct privsep_proc *, - struct privsep_proc *, size_t); +void proc_exec(struct privsep *, struct privsep_proc *, unsigned int, + int, char **); +void proc_setup(struct privsep *, struct privsep_proc *, unsigned int); +void proc_open(struct privsep *, int, int); +void proc_accept(struct privsep *, int, enum privsep_procid, + unsigned int); void proc_close(struct privsep *); int proc_ispeer(struct privsep_proc *, unsigned int, enum privsep_procid); void proc_shutdown(struct privsep_proc *); @@ -55,204 +60,383 @@ proc_ispeer(struct privsep_proc *procs, unsigned int nproc, return (0); } -void -proc_init(struct privsep *ps, struct privsep_proc *procs, unsigned int nproc) +enum privsep_procid +proc_getid(struct privsep_proc *procs, unsigned int nproc, + const char *proc_name) { - unsigned int i, j, src, dst; - struct privsep_pipes *pp; + struct privsep_proc *p; + unsigned int proc; - /* - * 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 (proc = 0; proc < nproc; proc++) { + p = &procs[proc]; + if (strcmp(p->p_title, proc_name)) + continue; - for (i = 0; i < ps->ps_ninstances; i++) { - pp = &ps->ps_pipes[src][i]; + return (p->p_id); + } - 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"); + return (PROC_MAX); +} - /* Mark fd as unused */ - for (j = 0; j < ps->ps_ninstances; j++) - pp->pp_pipes[dst][j] = -1; +void +proc_exec(struct privsep *ps, struct privsep_proc *procs, unsigned int nproc, + int argc, char **argv) +{ + unsigned int proc, nargc, i, proc_i; + char **nargv; + struct privsep_proc *p; + char num[32]; + int fd; + + /* Prepare the new process argv. */ + nargv = calloc(argc + 5, sizeof(char *)); + if (nargv == NULL) + fatal("%s: calloc", __func__); + + /* Copy call argument first. */ + nargc = 0; + nargv[nargc++] = argv[0]; + + /* Set process name argument and save the position. */ + nargv[nargc++] = "-P"; + proc_i = nargc; + nargc++; + + /* Point process instance arg to stack and copy the original args. */ + nargv[nargc++] = "-I"; + nargv[nargc++] = num; + for (i = 1; i < (unsigned int) argc; i++) + nargv[nargc++] = argv[i]; + + nargv[nargc] = NULL; + + for (proc = 0; proc < nproc; proc++) { + p = &procs[proc]; + + /* Update args with process title. */ + nargv[proc_i] = (char *)(uintptr_t)p->p_title; + + /* Fire children processes. */ + for (i = 0; i < ps->ps_instances[p->p_id]; i++) { + /* Update the process instance number. */ + snprintf(num, sizeof(num), "%u", i); + + fd = ps->ps_pipes[p->p_id][i].pp_pipes[PROC_PARENT][0]; + ps->ps_pipes[p->p_id][i].pp_pipes[PROC_PARENT][0] = -1; + + switch (fork()) { + case -1: + fatal("%s: fork", __func__); + break; + case 0: + /* First create a new session */ + if (setsid() == -1) + fatal("setsid"); + + /* Prepare parent socket. */ + if (fd != PROC_PARENT_SOCK_FILENO) { + if (dup2(fd, PROC_PARENT_SOCK_FILENO) + == -1) + fatal("dup2"); + } else if (fcntl(fd, F_SETFD, 0) == -1) + fatal("fcntl"); + + execvp(argv[0], nargv); + fatal("%s: execvp", __func__); + break; + default: + /* Close child end. */ + close(fd); + break; } } } - - /* - * 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]); + free(nargv); } void -proc_kill(struct privsep *ps) +proc_connect(struct privsep *ps) { - pid_t pid; - unsigned int i; + struct imsgev *iev; + unsigned int src, dst, inst; - if (privsep_process != PROC_PARENT) + /* Don't distribute any sockets if we are not really going to run. */ + if (ps->ps_noaction) return; - for (i = 0; i < PROC_MAX; i++) { - if (ps->ps_pid[i] == 0) + for (dst = 0; dst < PROC_MAX; dst++) { + /* We don't communicate with ourselves. */ + if (dst == PROC_PARENT) continue; - killpg(ps->ps_pid[i], SIGTERM); + + for (inst = 0; inst < ps->ps_instances[dst]; inst++) { + iev = &ps->ps_ievs[dst][inst]; + imsg_init(&iev->ibuf, ps->ps_pp->pp_pipes[dst][inst]); + event_set(&iev->ev, iev->ibuf.fd, iev->events, + iev->handler, iev->data); + event_add(&iev->ev, NULL); + } } - do { - pid = waitpid(WAIT_ANY, NULL, 0); - } while (pid != -1 || (pid == -1 && errno == EINTR)); + /* Distribute the socketpair()s for everyone. */ + for (src = 0; src < PROC_MAX; src++) + for (dst = src; dst < PROC_MAX; dst++) { + /* Parent already distributed its fds. */ + if (src == PROC_PARENT || dst == PROC_PARENT) + continue; - proc_close(ps); + proc_open(ps, src, dst); + } } void -proc_open(struct privsep *ps, struct privsep_proc *p, - struct privsep_proc *procs, size_t nproc) +proc_init(struct privsep *ps, struct privsep_proc *procs, unsigned int nproc, + int argc, char **argv, enum privsep_procid proc_id) { + struct privsep_proc *p = NULL; struct privsep_pipes *pa, *pb; + unsigned int proc; + unsigned int dst; int fds[2]; - unsigned 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; - if (procs[proc].p_cb == NULL) - procs[proc].p_cb = proc_dispatch_null; + /* Don't initiate anything if we are not really going to run. */ + if (ps->ps_noaction) + return; - 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]; + if (proc_id == PROC_PARENT) { + privsep_process = PROC_PARENT; + proc_setup(ps, procs, nproc); - /* Check if fds are already set by peer */ - if (pa->pp_pipes[procs[proc].p_id][j] != -1) - continue; + /* + * Create the children sockets so we can use them + * to distribute the rest of the socketpair()s using + * proc_connect() later. + */ + for (dst = 0; dst < PROC_MAX; dst++) { + /* Don't create socket for ourselves. */ + if (dst == PROC_PARENT) + continue; + for (proc = 0; proc < ps->ps_instances[dst]; proc++) { + pa = &ps->ps_pipes[PROC_PARENT][0]; + pb = &ps->ps_pipes[dst][proc]; if (socketpair(AF_UNIX, - SOCK_STREAM | SOCK_NONBLOCK, + SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, PF_UNSPEC, fds) == -1) - fatal("socketpair"); + fatal("%s: socketpair", __func__); - pa->pp_pipes[procs[proc].p_id][j] = fds[0]; - pb->pp_pipes[src][i] = fds[1]; + pa->pp_pipes[dst][proc] = fds[0]; + pb->pp_pipes[PROC_PARENT][0] = fds[1]; } } + + /* Engage! */ + proc_exec(ps, procs, nproc, argc, argv); + return; + } + + /* Initialize a child */ + for (proc = 0; proc < nproc; proc++) { + if (procs[proc].p_id != proc_id) + continue; + p = &procs[proc]; + break; } + if (p == NULL || p->p_init == NULL) + fatalx("%s: process %d missing process initialization", + __func__, proc_id); + + p->p_init(ps, p); + + fatalx("failed to initiate child process"); } void -proc_listen(struct privsep *ps, struct privsep_proc *procs, size_t nproc) +proc_accept(struct privsep *ps, int fd, enum privsep_procid dst, + unsigned int n) { - unsigned int i, dst, src, n, m; + struct privsep_pipes *pp = ps->ps_pp; + struct imsgev *iev; + + if (ps->ps_ievs[dst] == NULL) { +#if DEBUG > 1 + log_debug("%s: %s src %d %d to dst %d %d not connected", + __func__, ps->ps_title[privsep_process], + privsep_process, ps->ps_instance + 1, + dst, n + 1); +#endif + close(fd); + return; + } + + if (pp->pp_pipes[dst][n] != -1) { + log_warnx("%s: duplicated descriptor", __func__); + close(fd); + return; + } else + pp->pp_pipes[dst][n] = fd; + + iev = &ps->ps_ievs[dst][n]; + imsg_init(&iev->ibuf, fd); + event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev->data); + event_add(&iev->ev, NULL); +} + +void +proc_setup(struct privsep *ps, struct privsep_proc *procs, unsigned int nproc) +{ + unsigned int i, j, src, dst, id; struct privsep_pipes *pp; + /* Initialize parent title, ps_instances and procs. */ + ps->ps_title[PROC_PARENT] = "parent"; + + for (src = 0; src < PROC_MAX; src++) + /* Default to 1 process instance */ + if (ps->ps_instances[src] < 1) + ps->ps_instances[src] = 1; + + for (src = 0; src < nproc; src++) { + procs[src].p_ps = ps; + if (procs[src].p_cb == NULL) + procs[src].p_cb = proc_dispatch_null; + + id = procs[src].p_id; + ps->ps_title[id] = procs[src].p_title; + if ((ps->ps_ievs[id] = calloc(ps->ps_instances[id], + sizeof(struct imsgev))) == NULL) + fatal("%s: calloc", __func__); + + /* With this set up, we are ready to call imsg_init(). */ + for (i = 0; i < ps->ps_instances[id]; i++) { + ps->ps_ievs[id][i].handler = proc_dispatch; + ps->ps_ievs[id][i].events = EV_READ; + ps->ps_ievs[id][i].proc = &procs[src]; + ps->ps_ievs[id][i].data = &ps->ps_ievs[id][i]; + } + } + /* - * Close unused pipes + * 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++) { - for (n = 0; n < ps->ps_instances[src]; n++) { - /* Ingore current process */ - if (src == (unsigned int)privsep_process && - n == ps->ps_instance) - continue; + /* Allocate destination array for each process */ + if ((ps->ps_pipes[src] = calloc(ps->ps_instances[src], + sizeof(struct privsep_pipes))) == NULL) + fatal("%s: calloc", __func__); - pp = &ps->ps_pipes[src][n]; + for (i = 0; i < ps->ps_instances[src]; i++) { + pp = &ps->ps_pipes[src][i]; 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; - } + /* Allocate maximum fd integers */ + if ((pp->pp_pipes[dst] = + calloc(ps->ps_instances[dst], + sizeof(int))) == NULL) + fatal("%s: calloc", __func__); + + /* Mark fd as unused */ + for (j = 0; j < ps->ps_instances[dst]; j++) + pp->pp_pipes[dst][j] = -1; } } } - src = privsep_process; - ps->ps_pp = pp = &ps->ps_pipes[src][ps->ps_instance]; + ps->ps_pp = &ps->ps_pipes[privsep_process][ps->ps_instance]; +} - /* - * Listen on appropriate pipes - */ - for (i = 0; i < nproc; i++) { - dst = procs[i].p_id; +void +proc_kill(struct privsep *ps) +{ + char *cause; + pid_t pid; + int len, status; - if (src == dst) - fatal("proc_listen: cannot peer with oneself"); + if (privsep_process != PROC_PARENT) + return; - if ((ps->ps_ievs[dst] = calloc(ps->ps_instances[dst], - sizeof(struct imsgev))) == NULL) - fatal("proc_open"); + proc_close(ps); - for (n = 0; n < ps->ps_instances[dst]; n++) { - if (pp->pp_pipes[dst][n] == -1) + do { + pid = waitpid(WAIT_ANY, &status, 0); + if (pid <= 0) + continue; + + if (WIFSIGNALED(status)) { + len = asprintf(&cause, "terminated; signal %d", + WTERMSIG(status)); + } else if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != 0) + len = asprintf(&cause, "exited abnormally"); + else + len = 0; + } else + len = -1; + + if (len == 0) { + /* child exited OK, don't print a warning message */ + } else if (len != -1) { + log_warnx("lost child: pid %u %s", pid, cause); + free(cause); + } else + log_warnx("lost child: pid %u", pid); + } while (pid != -1 || (pid == -1 && errno == EINTR)); +} + +void +proc_open(struct privsep *ps, int src, int dst) +{ + struct privsep_pipes *pa, *pb; + struct privsep_fd pf; + int fds[2]; + unsigned int i, j; + + /* Exchange pipes between process. */ + for (i = 0; i < ps->ps_instances[src]; i++) { + for (j = 0; j < ps->ps_instances[dst]; j++) { + /* Don't create sockets for ourself. */ + if (src == dst && i == j) 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); + pa = &ps->ps_pipes[src][i]; + pb = &ps->ps_pipes[dst][j]; + if (socketpair(AF_UNIX, + SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, + PF_UNSPEC, fds) == -1) + fatal("%s: socketpair", __func__); + + pa->pp_pipes[dst][j] = fds[0]; + pb->pp_pipes[src][i] = fds[1]; + + pf.pf_procid = src; + pf.pf_instance = i; + if (proc_compose_imsg(ps, dst, j, IMSG_CTL_PROCFD, + -1, pb->pp_pipes[src][i], &pf, sizeof(pf)) == -1) + fatal("%s: proc_compose_imsg", __func__); + + pf.pf_procid = dst; + pf.pf_instance = j; + if (proc_compose_imsg(ps, src, i, IMSG_CTL_PROCFD, + -1, pa->pp_pipes[dst][j], &pf, sizeof(pf)) == -1) + fatal("%s: proc_compose_imsg", __func__); + + /* + * We have to flush to send the descriptors and close + * them to avoid the fd ramp on startup. + */ + if (proc_flush_imsg(ps, src, i) == -1 || + proc_flush_imsg(ps, dst, j) == -1) + fatal("%s: imsg_flush", __func__); } } } @@ -301,7 +485,7 @@ proc_shutdown(struct privsep_proc *p) log_info("%s exiting, pid %d", p->p_title, getpid()); - _exit(0); + exit(0); } void @@ -321,51 +505,39 @@ proc_sig_handler(int sig, short event, void *arg) /* ignore */ break; default: - fatalx("proc_sig_handler: unexpected signal"); + fatalx("%s: unexpected signal", __func__); /* NOTREACHED */ } } -pid_t +void proc_run(struct privsep *ps, struct privsep_proc *p, struct privsep_proc *procs, unsigned int nproc, void (*run)(struct privsep *, struct privsep_proc *, void *), void *arg) { - pid_t pid; struct passwd *pw; const char *root; struct control_sock *rcs; - unsigned 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: - log_procinit(p->p_title); + log_procinit(p->p_title); - /* Set the process group of the current process */ - setpgid(0, 0); - break; - default: - return (pid); - } - - pw = ps->ps_pw; + /* Set the process group of the current process */ + setpgid(0, 0); if (p->p_id == PROC_CONTROL && ps->ps_instance == 0) { if (control_init(ps, &ps->ps_csock) == -1) - fatalx(__func__); + fatalx("%s: control_init", __func__); TAILQ_FOREACH(rcs, &ps->ps_rcsocks, cs_entry) if (control_init(ps, rcs) == -1) - fatalx(__func__); + fatalx("%s: control_init", __func__); } + /* Use non-standard user */ + if (p->p_pw != NULL) + pw = p->p_pw; + else + pw = ps->ps_pw; + /* Change root directory */ if (p->p_chroot != NULL) root = p->p_chroot; @@ -373,9 +545,9 @@ proc_run(struct privsep *ps, struct privsep_proc *p, root = pw->pw_dir; if (chroot(root) == -1) - fatal("proc_run: chroot"); + fatal("%s: chroot", __func__); if (chdir("/") == -1) - fatal("proc_run: chdir(\"/\")"); + fatal("%s: chdir(\"/\")", __func__); privsep_process = p->p_id; @@ -384,20 +556,7 @@ proc_run(struct privsep *ps, struct privsep_proc *p, 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 + fatal("%s: cannot drop privileges", __func__); event_init(); @@ -415,25 +574,26 @@ proc_run(struct privsep *ps, struct privsep_proc *p, signal_add(&ps->ps_evsigpipe, NULL); signal_add(&ps->ps_evsigusr1, NULL); - proc_listen(ps, procs, nproc); - + proc_setup(ps, procs, nproc); + proc_accept(ps, PROC_PARENT_SOCK_FILENO, PROC_PARENT, 0); if (p->p_id == PROC_CONTROL && ps->ps_instance == 0) { TAILQ_INIT(&ctl_conns); if (control_listen(&ps->ps_csock) == -1) - fatalx(__func__); + fatalx("%s: control_listen", __func__); TAILQ_FOREACH(rcs, &ps->ps_rcsocks, cs_entry) if (control_listen(rcs) == -1) - fatalx(__func__); + fatalx("%s: control_listen", __func__); } + DPRINTF("%s: %s %d/%d, pid %d", __func__, p->p_title, + ps->ps_instance + 1, ps->ps_instances[p->p_id], getpid()); + if (run != NULL) run(ps, p, arg); event_dispatch(); proc_shutdown(p); - - return (0); } void @@ -447,13 +607,14 @@ proc_dispatch(int fd, short event, void *arg) ssize_t n; int verbose; const char *title; + struct privsep_fd pf; title = ps->ps_title[privsep_process]; ibuf = &iev->ibuf; if (event & EV_READ) { if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) - fatal(__func__); + fatal("%s: imsg_read", __func__); if (n == 0) { /* this pipe is dead, so remove the event handler */ event_del(&iev->ev); @@ -463,20 +624,26 @@ proc_dispatch(int fd, short event, void *arg) } if (event & EV_WRITE) { - if (msgbuf_write(&ibuf->w) <= 0 && errno != EAGAIN) - fatal(__func__); + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("%s: msgbuf_write", __func__); + if (n == 0) { + /* this pipe is dead, so remove the event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + return; + } } for (;;) { if ((n = imsg_get(ibuf, &imsg)) == -1) - fatal(__func__); + fatal("%s: imsg_get", __func__); if (n == 0) break; #if DEBUG > 1 log_debug("%s: %s %d got imsg %d peerid %d from %s %d", __func__, title, ps->ps_instance + 1, - imsg.hdr.type, imsg.hdr.peerid, p->p_title, p->p_instance); + imsg.hdr.type, imsg.hdr.peerid, p->p_title, imsg.hdr.pid); #endif /* @@ -495,15 +662,20 @@ proc_dispatch(int fd, short event, void *arg) case IMSG_CTL_VERBOSE: IMSG_SIZE_CHECK(&imsg, &verbose); memcpy(&verbose, imsg.data, sizeof(verbose)); - log_verbose(verbose); + log_setverbose(verbose); + break; + case IMSG_CTL_PROCFD: + IMSG_SIZE_CHECK(&imsg, &pf); + memcpy(&pf, imsg.data, sizeof(pf)); + proc_accept(ps, imsg.fd, pf.pf_procid, + pf.pf_instance); break; default: - log_warnx("%s: %s %d got invalid imsg %d peerid %d " + fatalx("%s: %s %d got invalid imsg %d peerid %d " "from %s %d", __func__, title, ps->ps_instance + 1, imsg.hdr.type, imsg.hdr.peerid, - p->p_title, p->p_instance); - fatalx(__func__); + p->p_title, imsg.hdr.pid); } imsg_free(&imsg); } @@ -585,7 +757,7 @@ proc_compose_imsg(struct privsep *ps, enum privsep_procid id, int n, proc_range(ps, id, &n, &m); for (; n < m; n++) { if (imsg_compose_event(&ps->ps_ievs[id][n], - type, peerid, 0, fd, data, datalen) == -1) + type, peerid, ps->ps_instance + 1, fd, data, datalen) == -1) return (-1); } @@ -608,7 +780,7 @@ proc_composev_imsg(struct privsep *ps, enum privsep_procid id, int n, proc_range(ps, id, &n, &m); for (; n < m; n++) if (imsg_composev_event(&ps->ps_ievs[id][n], - type, peerid, 0, fd, iov, iovcnt) == -1) + type, peerid, ps->ps_instance + 1, fd, iov, iovcnt) == -1) return (-1); return (0); @@ -646,3 +818,25 @@ proc_iev(struct privsep *ps, enum privsep_procid id, int n) proc_range(ps, id, &n, &m); return (&ps->ps_ievs[id][n]); } + +/* This function should only be called with care as it breaks async I/O */ +int +proc_flush_imsg(struct privsep *ps, enum privsep_procid id, int n) +{ + struct imsgbuf *ibuf; + int m, ret = 0; + + proc_range(ps, id, &n, &m); + for (; n < m; n++) { + if ((ibuf = proc_ibuf(ps, id, n)) == NULL) + return (-1); + do { + ret = imsg_flush(ibuf); + } while (ret == -1 && errno == EAGAIN); + if (ret == -1) + break; + imsg_event_add(&ps->ps_ievs[id][n]); + } + + return (ret); +} diff --git a/httpd/server.c b/httpd/server.c index 849b92a..1a57d0b 100644 --- a/httpd/server.c +++ b/httpd/server.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server.c,v 1.88 2016/08/15 16:12:34 jsing Exp $ */ +/* $OpenBSD: server.c,v 1.111 2017/08/11 18:48:56 jsing Exp $ */ /* * Copyright (c) 2006 - 2015 Reyk Floeter @@ -16,7 +16,6 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include /* nitems */ #include #include #include @@ -59,13 +58,14 @@ 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 *); +struct server *server_byid(uint32_t); int server_tls_init(struct server *); void server_tls_readcb(int, short, void *); void server_tls_writecb(int, short, void *); +void server_tls_handshake(int, short, void *); void server_accept(int, short, void *); -void server_handshake_tls(int, short, void *); void server_input(struct client *); void server_inflight_dec(struct client *, const char *); @@ -76,28 +76,22 @@ volatile int server_clients; volatile int server_inflight = 0; uint32_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 +void 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); + proc_run(ps, p, procs, nitems(procs), server_init, NULL); + server_http(); } void server_shutdown(void) { - config_purge(env, CONFIG_ALL); + config_purge(httpd_env, CONFIG_ALL); usleep(200); /* XXX server needs to shutdown last */ } @@ -115,7 +109,7 @@ server_privinit(struct server *srv) * 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) { + TAILQ_FOREACH(s, httpd_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, @@ -133,7 +127,7 @@ server_privinit(struct server *srv) } int -server_tls_cmp(struct server *s1, struct server *s2) +server_tls_cmp(struct server *s1, struct server *s2, int match_keypair) { struct server_config *sc1, *sc2; @@ -142,17 +136,22 @@ server_tls_cmp(struct server *s1, struct server *s2) if (sc1->tls_protocols != sc2->tls_protocols) return (-1); - if (strcmp(sc1->tls_cert_file, sc2->tls_cert_file) != 0) - return (-1); - if (strcmp(sc1->tls_key_file, sc2->tls_key_file) != 0) + if (sc1->tls_ticket_lifetime != sc2->tls_ticket_lifetime) return (-1); if (strcmp(sc1->tls_ciphers, sc2->tls_ciphers) != 0) return (-1); if (strcmp(sc1->tls_dhe_params, sc2->tls_dhe_params) != 0) return (-1); - if (strcmp(sc1->tls_ecdhe_curve, sc2->tls_ecdhe_curve) != 0) + if (strcmp(sc1->tls_ecdhe_curves, sc2->tls_ecdhe_curves) != 0) return (-1); + if (match_keypair) { + if (strcmp(sc1->tls_cert_file, sc2->tls_cert_file) != 0) + return (-1); + if (strcmp(sc1->tls_key_file, sc2->tls_key_file) != 0) + return (-1); + } + return (0); } @@ -162,17 +161,15 @@ 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) + 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) + 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); @@ -180,9 +177,30 @@ server_tls_load_keypair(struct server *srv) return (0); } +int +server_tls_load_ocsp(struct server *srv) +{ + if ((srv->srv_conf.flags & SRVFLAG_TLS) == 0) + return (0); + + if (srv->srv_conf.tls_ocsp_staple_file == NULL) + return (0); + + if ((srv->srv_conf.tls_ocsp_staple = tls_load_file( + srv->srv_conf.tls_ocsp_staple_file, + &srv->srv_conf.tls_ocsp_staple_len, NULL)) == NULL) + return (-1); + log_debug("%s: using ocsp staple from %s", __func__, + srv->srv_conf.tls_ocsp_staple_file); + + return (0); +} + int server_tls_init(struct server *srv) { + struct server_config *srv_conf; + if ((srv->srv_conf.flags & SRVFLAG_TLS) == 0) return (0); @@ -201,9 +219,12 @@ server_tls_init(struct server *srv) return (-1); } - tls_config_set_protocols(srv->srv_tls_config, - srv->srv_conf.tls_protocols); - + if (tls_config_set_protocols(srv->srv_tls_config, + srv->srv_conf.tls_protocols) != 0) { + log_warnx("%s: failed to set tls protocols: %s", + __func__, tls_config_error(srv->srv_tls_config)); + return (-1); + } if (tls_config_set_ciphers(srv->srv_tls_config, srv->srv_conf.tls_ciphers) != 0) { log_warnx("%s: failed to set tls ciphers: %s", @@ -216,21 +237,63 @@ server_tls_init(struct server *srv) __func__, tls_config_error(srv->srv_tls_config)); return (-1); } - if (tls_config_set_ecdhecurve(srv->srv_tls_config, - srv->srv_conf.tls_ecdhe_curve) != 0) { - log_warnx("%s: failed to set tls ecdhe curve: %s", + if (tls_config_set_ecdhecurves(srv->srv_tls_config, + srv->srv_conf.tls_ecdhe_curves) != 0) { + log_warnx("%s: failed to set tls ecdhe curves: %s", __func__, tls_config_error(srv->srv_tls_config)); return (-1); } - if (tls_config_set_keypair_mem(srv->srv_tls_config, + if (tls_config_set_keypair_ocsp_mem(srv->srv_tls_config, srv->srv_conf.tls_cert, srv->srv_conf.tls_cert_len, - srv->srv_conf.tls_key, srv->srv_conf.tls_key_len) != 0) { + srv->srv_conf.tls_key, srv->srv_conf.tls_key_len, + srv->srv_conf.tls_ocsp_staple, + srv->srv_conf.tls_ocsp_staple_len) != 0) { log_warnx("%s: failed to set tls certificate/key: %s", __func__, tls_config_error(srv->srv_tls_config)); return (-1); } + TAILQ_FOREACH(srv_conf, &srv->srv_hosts, entry) { + if (srv_conf->tls_cert == NULL || srv_conf->tls_key == NULL) + continue; + log_debug("%s: adding keypair for server %s", __func__, + srv->srv_conf.name); + if (tls_config_add_keypair_ocsp_mem(srv->srv_tls_config, + srv_conf->tls_cert, srv_conf->tls_cert_len, + srv_conf->tls_key, srv_conf->tls_key_len, + srv_conf->tls_ocsp_staple, + srv_conf->tls_ocsp_staple_len) != 0) { + log_warnx("%s: failed to add tls keypair", __func__); + return (-1); + } + } + + /* set common session ID among all processes */ + if (tls_config_set_session_id(srv->srv_tls_config, + httpd_env->sc_tls_sid, sizeof(httpd_env->sc_tls_sid)) == -1) { + log_warnx("%s: could not set the TLS session ID: %s", + __func__, tls_config_error(srv->srv_tls_config)); + return (-1); + } + + /* ticket support */ + if (srv->srv_conf.tls_ticket_lifetime) { + if (tls_config_set_session_lifetime(srv->srv_tls_config, + srv->srv_conf.tls_ticket_lifetime) == -1) { + log_warnx("%s: could not set the TLS session lifetime: " + "%s", __func__, + tls_config_error(srv->srv_tls_config)); + return (-1); + } + tls_config_add_ticket_key(srv->srv_tls_config, + srv->srv_conf.tls_ticket_key.tt_keyrev, + srv->srv_conf.tls_ticket_key.tt_key, + sizeof(srv->srv_conf.tls_ticket_key.tt_key)); + explicit_bzero(&srv->srv_conf.tls_ticket_key, + sizeof(srv->srv_conf.tls_ticket_key)); + } + if (tls_configure(srv->srv_tls_ctx, srv->srv_tls_config) != 0) { log_warnx("%s: failed to configure tls - %s", __func__, tls_error(srv->srv_tls_ctx)); @@ -239,10 +302,8 @@ server_tls_init(struct server *srv) /* 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); + freezero(srv->srv_conf.tls_cert, srv->srv_conf.tls_cert_len); + freezero(srv->srv_conf.tls_key, srv->srv_conf.tls_key_len); srv->srv_conf.tls_cert = NULL; srv->srv_conf.tls_key = NULL; srv->srv_conf.tls_cert_len = 0; @@ -251,17 +312,24 @@ server_tls_init(struct server *srv) return (0); } +void +server_generate_ticket_key(struct server_config *srv_conf) +{ + struct server_tls_ticket *key = &srv_conf->tls_ticket_key; + + key->tt_id = srv_conf->id; + key->tt_keyrev = arc4random(); + arc4random_buf(key->tt_key, sizeof(key->tt_key)); +} + void server_init(struct privsep *ps, struct privsep_proc *p, void *arg) { - server_http(ps->ps_env); + server_http(); 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; @@ -273,9 +341,9 @@ server_init(struct privsep *ps, struct privsep_proc *p, void *arg) #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); + evtimer_set(&ps->ps_env->sc_statev, server_statistics, NULL); + memcpy(&tv, &ps->ps_env->sc_statinterval, sizeof(tv)); + evtimer_add(&ps->ps_env->sc_statev, &tv); #endif } @@ -284,7 +352,10 @@ server_launch(void) { struct server *srv; - TAILQ_FOREACH(srv, env->sc_servers, srv_entry) { + TAILQ_FOREACH(srv, httpd_env->sc_servers, srv_entry) { + log_debug("%s: configuring server %s", __func__, + srv->srv_conf.name); + server_tls_init(srv); server_http_init(srv); @@ -312,7 +383,7 @@ server_purge(struct server *srv) if (srv->srv_s != -1) close(srv->srv_s); - TAILQ_REMOVE(env->sc_servers, srv, srv_entry); + TAILQ_REMOVE(httpd_env->sc_servers, srv, srv_entry); /* cleanup sessions */ while ((clt = @@ -343,16 +414,10 @@ serverconfig_free(struct server_config *srv_conf) free(srv_conf->return_uri); free(srv_conf->tls_cert_file); free(srv_conf->tls_key_file); - - if (srv_conf->tls_cert != NULL) { - explicit_bzero(srv_conf->tls_cert, srv_conf->tls_cert_len); - free(srv_conf->tls_cert); - } - - if (srv_conf->tls_key != NULL) { - explicit_bzero(srv_conf->tls_key, srv_conf->tls_key_len); - free(srv_conf->tls_key); - } + free(srv_conf->tls_ocsp_staple_file); + free(srv_conf->tls_ocsp_staple); + freezero(srv_conf->tls_cert, srv_conf->tls_cert_len); + freezero(srv_conf->tls_key, srv_conf->tls_key_len); } void @@ -364,6 +429,8 @@ serverconfig_reset(struct server_config *srv_conf) srv_conf->tls_cert_file = NULL; srv_conf->tls_key = NULL; srv_conf->tls_key_file = NULL; + srv_conf->tls_ocsp_staple = NULL; + srv_conf->tls_ocsp_staple_file = NULL; } struct server * @@ -371,7 +438,7 @@ server_byaddr(struct sockaddr *addr, in_port_t port) { struct server *srv; - TAILQ_FOREACH(srv, env->sc_servers, srv_entry) { + TAILQ_FOREACH(srv, httpd_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) @@ -387,7 +454,7 @@ serverconfig_byid(uint32_t id) struct server *srv; struct server_config *srv_conf; - TAILQ_FOREACH(srv, env->sc_servers, srv_entry) { + TAILQ_FOREACH(srv, httpd_env->sc_servers, srv_entry) { if (srv->srv_conf.id == id) return (&srv->srv_conf); TAILQ_FOREACH(srv_conf, &srv->srv_hosts, entry) { @@ -399,6 +466,18 @@ serverconfig_byid(uint32_t id) return (NULL); } +struct server * +server_byid(uint32_t id) +{ + struct server *srv; + + TAILQ_FOREACH(srv, httpd_env->sc_servers, srv_entry) { + if (srv->srv_conf.id == id) + return (srv); + } + return (NULL); +} + int server_foreach(int (*srv_cb)(struct server *, struct server_config *, void *), void *arg) @@ -406,7 +485,7 @@ server_foreach(int (*srv_cb)(struct server *, struct server *srv; struct server_config *srv_conf; - TAILQ_FOREACH(srv, env->sc_servers, srv_entry) { + TAILQ_FOREACH(srv, httpd_env->sc_servers, srv_entry) { if ((srv_cb)(srv, &srv->srv_conf, arg) == -1) return (-1); TAILQ_FOREACH(srv_conf, &srv->srv_hosts, entry) { @@ -424,7 +503,7 @@ server_match(struct server *s2, int match_name) struct server *s1; /* Attempt to find matching server. */ - TAILQ_FOREACH(s1, env->sc_servers, srv_entry) { + TAILQ_FOREACH(s1, httpd_env->sc_servers, srv_entry) { if ((s1->srv_conf.flags & SRVFLAG_LOCATION) != 0) continue; if (match_name) { @@ -525,15 +604,33 @@ server_socket(struct sockaddr_storage *ss, in_port_t port, */ if (srv_conf->tcpflags & TCPFLAG_IPTTL) { val = (int)srv_conf->tcpipttl; - if (setsockopt(s, IPPROTO_IP, IP_TTL, - &val, sizeof(val)) == -1) - goto bad; + switch (ss->ss_family) { + case AF_INET: + if (setsockopt(s, IPPROTO_IP, IP_TTL, + &val, sizeof(val)) == -1) + goto bad; + break; + case AF_INET6: + if (setsockopt(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS, + &val, sizeof(val)) == -1) + goto bad; + break; + } } if (srv_conf->tcpflags & TCPFLAG_IPMINTTL) { val = (int)srv_conf->tcpipminttl; - if (setsockopt(s, IPPROTO_IP, IP_MINTTL, - &val, sizeof(val)) == -1) - goto bad; + switch (ss->ss_family) { + case AF_INET: + if (setsockopt(s, IPPROTO_IP, IP_MINTTL, + &val, sizeof(val)) == -1) + goto bad; + break; + case AF_INET6: + if (setsockopt(s, IPPROTO_IPV6, IPV6_MINHOPCOUNT, + &val, sizeof(val)) == -1) + goto bad; + break; + } } /* @@ -761,7 +858,7 @@ server_input(struct client *clt) 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); + srv_conf->requesttimeout.tv_sec, srv_conf->requesttimeout.tv_sec); bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); } @@ -780,8 +877,6 @@ server_write(struct bufferevent *bev, void *arg) if (clt->clt_done) goto done; - bufferevent_enable(bev, EV_READ); - if (clt->clt_srvbev && clt->clt_srvbev_throttled) { bufferevent_enable(clt->clt_srvbev, EV_READ); clt->clt_srvbev_throttled = 0; @@ -789,7 +884,7 @@ server_write(struct bufferevent *bev, void *arg) return; done: - (*bev->errorcb)(bev, EVBUFFER_WRITE|EVBUFFER_EOF, bev->cbarg); + (*bev->errorcb)(bev, EVBUFFER_WRITE, bev->cbarg); return; } @@ -834,7 +929,7 @@ server_read(struct bufferevent *bev, void *arg) return; done: - (*bev->errorcb)(bev, EVBUFFER_READ|EVBUFFER_EOF, bev->cbarg); + (*bev->errorcb)(bev, EVBUFFER_READ, bev->cbarg); return; fail: server_close(clt, strerror(errno)); @@ -847,7 +942,7 @@ server_error(struct bufferevent *bev, short error, void *arg) struct evbuffer *dst; if (error & EVBUFFER_TIMEOUT) { - server_close(clt, "buffer event timeout"); + server_abort_http(clt, 408, "timeout"); return; } if (error & EVBUFFER_ERROR) { @@ -858,7 +953,11 @@ server_error(struct bufferevent *bev, short error, void *arg) server_close(clt, "buffer event error"); return; } - if (error & (EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF)) { + if (error & EVBUFFER_EOF) { + server_close(clt, "closed"); + return; + } + if (error & (EVBUFFER_READ|EVBUFFER_WRITE)) { bufferevent_disable(bev, EV_READ|EV_WRITE); clt->clt_done = 1; @@ -954,9 +1053,6 @@ server_accept(int fd, short event, void *arg) 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) { @@ -971,7 +1067,7 @@ server_accept(int fd, short event, void *arg) return; } event_again(&clt->clt_ev, clt->clt_s, EV_TIMEOUT|EV_READ, - server_handshake_tls, &clt->clt_tv_start, + server_tls_handshake, &clt->clt_tv_start, &srv->srv_conf.timeout, clt); return; } @@ -992,7 +1088,7 @@ server_accept(int fd, short event, void *arg) } void -server_handshake_tls(int fd, short event, void *arg) +server_tls_handshake(int fd, short event, void *arg) { struct client *clt = (struct client *)arg; struct server *srv = (struct server *)clt->clt_srv; @@ -1011,14 +1107,14 @@ server_handshake_tls(int fd, short event, void *arg) server_input(clt); } else if (ret == TLS_WANT_POLLIN) { event_again(&clt->clt_ev, clt->clt_s, EV_TIMEOUT|EV_READ, - server_handshake_tls, &clt->clt_tv_start, + server_tls_handshake, &clt->clt_tv_start, &srv->srv_conf.timeout, clt); } else if (ret == TLS_WANT_POLLOUT) { event_again(&clt->clt_ev, clt->clt_s, EV_TIMEOUT|EV_WRITE, - server_handshake_tls, &clt->clt_tv_start, + server_tls_handshake, &clt->clt_tv_start, &srv->srv_conf.timeout, clt); } else { - log_warnx("%s: tls handshake failed - %s", __func__, + log_debug("%s: tls handshake failed - %s", __func__, tls_error(clt->clt_tls_ctx)); server_close(clt, "tls handshake failed"); } @@ -1071,7 +1167,7 @@ server_sendlog(struct server_config *srv_conf, int cmd, const char *emsg, ...) iov[1].iov_base = msg; iov[1].iov_len = strlen(msg) + 1; - if (proc_composev(env->sc_ps, PROC_LOGGER, cmd, iov, 2) != 0) { + if (proc_composev(httpd_env->sc_ps, PROC_LOGGER, cmd, iov, 2) != 0) { log_warn("%s: failed to compose imsg", __func__); return; } @@ -1084,14 +1180,13 @@ server_log(struct client *clt, const char *msg) struct server_config *srv_conf = clt->clt_srv_conf; char *ptr = NULL, *vmsg = 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) + if (log_getverbose() > 1) debug_cmd = IMSG_LOG_ERROR; if (EVBUFFER_LENGTH(clt->clt_log)) { while ((ptr = @@ -1171,27 +1266,40 @@ server_close(struct client *clt, const char *msg) int server_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg) { + struct server *srv; + struct server_tls_ticket key; + switch (imsg->hdr.type) { case IMSG_CFG_MEDIA: - config_getmedia(env, imsg); + config_getmedia(httpd_env, imsg); break; case IMSG_CFG_AUTH: - config_getauth(env, imsg); + config_getauth(httpd_env, imsg); break; case IMSG_CFG_SERVER: - config_getserver(env, imsg); + config_getserver(httpd_env, imsg); break; case IMSG_CFG_TLS: - config_gettls(env, imsg); + config_getserver_tls(httpd_env, imsg); break; case IMSG_CFG_DONE: - config_getcfg(env, imsg); + config_getcfg(httpd_env, imsg); break; case IMSG_CTL_START: server_launch(); break; case IMSG_CTL_RESET: - config_getreset(env, imsg); + config_getreset(httpd_env, imsg); + break; + case IMSG_TLSTICKET_REKEY: + IMSG_SIZE_CHECK(imsg, (&key)); + memcpy(&key, imsg->data, sizeof(key)); + /* apply to the right server */ + srv = server_byid(key.tt_id); + if (srv) { + tls_config_add_ticket_key(srv->srv_tls_config, + key.tt_keyrev, key.tt_key, sizeof(key.tt_key)); + } break; default: return (-1); diff --git a/httpd/server_fcgi.c b/httpd/server_fcgi.c index 21ebeed..920b2cf 100644 --- a/httpd/server_fcgi.c +++ b/httpd/server_fcgi.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server_fcgi.c,v 1.68 2016/04/24 20:09:45 chrisz Exp $ */ +/* $OpenBSD: server_fcgi.c,v 1.75 2017/07/31 08:02:49 ians Exp $ */ /* * Copyright (c) 2014 Florian Obser @@ -120,7 +120,6 @@ server_fcgi(struct httpd *env, struct client *clt) goto fail; } else { struct sockaddr_un sun; - size_t len; if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0)) == -1) @@ -128,21 +127,21 @@ server_fcgi(struct httpd *env, struct client *clt) 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 too long"; + if (strlcpy(sun.sun_path, srv_conf->socket, + sizeof(sun.sun_path)) >= 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; } memset(hbuf, 0, sizeof(hbuf)); - clt->clt_fcgi_state = FCGI_READ_HEADER; - clt->clt_fcgi_toread = sizeof(struct fcgi_record_header); + clt->clt_fcgi.state = FCGI_READ_HEADER; + clt->clt_fcgi.toread = sizeof(struct fcgi_record_header); + clt->clt_fcgi.status = 200; + clt->clt_fcgi.headersdone = 0; if (clt->clt_srvevb != NULL) evbuffer_free(clt->clt_srvevb); @@ -387,13 +386,13 @@ server_fcgi(struct httpd *env, struct client *clt) } if (strcmp(desc->http_version, "HTTP/1.1") == 0) { - clt->clt_fcgi_chunked = 1; + clt->clt_fcgi.chunked = 1; } else { /* HTTP/1.0 does not support chunked encoding */ - clt->clt_fcgi_chunked = 0; + clt->clt_fcgi.chunked = 0; clt->clt_persist = 0; } - clt->clt_fcgi_end = 0; + clt->clt_fcgi.end = 0; clt->clt_done = 0; free(script); @@ -499,36 +498,36 @@ server_fcgi_read(struct bufferevent *bev, void *arg) char *ptr; do { - len = bufferevent_read(bev, buf, clt->clt_fcgi_toread); + len = bufferevent_read(bev, buf, clt->clt_fcgi.toread); if (evbuffer_add(clt->clt_srvevb, buf, len) == -1) { server_abort_http(clt, 500, "short write"); return; } - clt->clt_fcgi_toread -= 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); + __func__, len, clt->clt_fcgi.toread, + clt->clt_fcgi.state, clt->clt_fcgi.type); - if (clt->clt_fcgi_toread != 0) + if (clt->clt_fcgi.toread != 0) return; - switch (clt->clt_fcgi_state) { + switch (clt->clt_fcgi.state) { case FCGI_READ_HEADER: - clt->clt_fcgi_state = FCGI_READ_CONTENT; + 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; + 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) + if (clt->clt_fcgi.toread != 0) break; - else if (clt->clt_fcgi_type == FCGI_STDOUT && + else if (clt->clt_fcgi.type == FCGI_STDOUT && !clt->clt_chunk) { server_abort_http(clt, 500, "empty stdout"); return; @@ -536,7 +535,7 @@ server_fcgi_read(struct bufferevent *bev, void *arg) /* fallthrough if content_len == 0 */ case FCGI_READ_CONTENT: - switch (clt->clt_fcgi_type) { + switch (clt->clt_fcgi.type) { case FCGI_STDERR: if (EVBUFFER_LENGTH(clt->clt_srvevb) > 0 && (ptr = get_string( @@ -549,13 +548,20 @@ server_fcgi_read(struct bufferevent *bev, void *arg) } 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; + ++clt->clt_chunk; + if (!clt->clt_fcgi.headersdone) { + clt->clt_fcgi.headersdone = + server_fcgi_getheaders(clt); + if (clt->clt_fcgi.headersdone) { + if (server_fcgi_header(clt, + clt->clt_fcgi.status) + == -1) { + server_abort_http(clt, + 500, + "malformed fcgi " + "headers"); + return; + } } if (!EVBUFFER_LENGTH(clt->clt_srvevb)) break; @@ -571,21 +577,21 @@ server_fcgi_read(struct bufferevent *bev, void *arg) } 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 = + 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; + 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 = + clt->clt_fcgi.state = FCGI_READ_HEADER; + clt->clt_fcgi.toread = sizeof(struct fcgi_record_header); break; } @@ -618,7 +624,7 @@ server_fcgi_header(struct client *clt, unsigned int code) return (-1); /* Set chunked encoding */ - if (clt->clt_fcgi_chunked) { + 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) @@ -655,8 +661,10 @@ server_fcgi_header(struct client *clt, unsigned int code) } /* 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) + key.kv_key = "Date"; + if ((kv = kv_find(&resp->http_headers, &key)) == NULL && + (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) */ @@ -727,14 +735,14 @@ server_fcgi_writechunk(struct client *clt) struct evbuffer *evb = clt->clt_srvevb; size_t len; - if (clt->clt_fcgi_type == FCGI_END_REQUEST) { + if (clt->clt_fcgi.type == FCGI_END_REQUEST) { len = 0; } else len = EVBUFFER_LENGTH(evb); - if (clt->clt_fcgi_chunked) { + 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++) + 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 || @@ -751,7 +759,7 @@ server_fcgi_getheaders(struct client *clt) { struct http_descriptor *resp = clt->clt_descresp; struct evbuffer *evb = clt->clt_srvevb; - int code = 200; + int code, ret; char *line, *key, *value; const char *errstr; @@ -760,12 +768,9 @@ server_fcgi_getheaders(struct client *clt) if ((value = strchr(key, ':')) == NULL) break; - if (*value == ':') { - *value++ = '\0'; - value += strspn(value, " \t"); - } else { - *value++ = '\0'; - } + + *value++ = '\0'; + value += strspn(value, " \t"); DPRINTF("%s: %s: %s", __func__, key, value); @@ -775,11 +780,15 @@ server_fcgi_getheaders(struct client *clt) if (errstr != NULL || server_httperror_byid( code) == NULL) code = 200; + clt->clt_fcgi.status = code; } else { (void)kv_add(&resp->http_headers, key, value); } free(line); } - return (code); + ret = (line != NULL && *line == '\0'); + + free(line); + return ret; } diff --git a/httpd/server_file.c b/httpd/server_file.c index 48ecbb5..4255119 100644 --- a/httpd/server_file.c +++ b/httpd/server_file.c @@ -1,7 +1,7 @@ -/* $OpenBSD: server_file.c,v 1.62 2016/05/17 03:12:39 deraadt Exp $ */ +/* $OpenBSD: server_file.c,v 1.65 2017/02/02 22:19:59 reyk Exp $ */ /* - * Copyright (c) 2006 - 2015 Reyk Floeter + * Copyright (c) 2006 - 2017 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 @@ -36,12 +36,6 @@ #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) #define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) -#define MAX_RANGES 4 - -struct range { - off_t start; - off_t end; -}; int server_file_access(struct httpd *, struct client *, char *, size_t); @@ -55,8 +49,7 @@ int server_file_modified_since(struct http_descriptor *, struct stat *); int server_file_method(struct client *); int parse_range_spec(char *, size_t, struct range *); -struct range *parse_range(char *, size_t, int *); -int buffer_add_range(int, struct evbuffer *, struct range *); +int parse_ranges(struct client *, char *, size_t); int server_file_access(struct httpd *env, struct client *clt, @@ -303,11 +296,10 @@ server_partial_file_request(struct httpd *env, struct client *clt, char *path, struct http_descriptor *resp = clt->clt_descresp; struct http_descriptor *desc = clt->clt_descreq; struct media_type *media, multipart_media; + struct range_data *r = &clt->clt_ranges; struct range *range; - struct evbuffer *evb = NULL; - size_t content_length; + size_t content_length = 0; int code = 500, fd = -1, i, nranges, ret; - uint32_t boundary; char content_range[64]; const char *errstr = NULL; @@ -315,7 +307,7 @@ server_partial_file_request(struct httpd *env, struct client *clt, char *path, if (desc->http_method != HTTP_METHOD_GET) return server_file_request(env, clt, path, st); - if ((range = parse_range(range_str, st->st_size, &nranges)) == NULL) { + if ((nranges = parse_ranges(clt, range_str, st->st_size)) < 1) { code = 416; (void)snprintf(content_range, sizeof(content_range), "bytes */%lld", st->st_size); @@ -328,12 +320,10 @@ server_partial_file_request(struct httpd *env, struct client *clt, char *path, goto abort; media = media_find_config(env, srv_conf, path); - if ((evb = evbuffer_new()) == NULL) { - errstr = "failed to allocate file buffer"; - goto abort; - } + r->range_media = media; if (nranges == 1) { + range = &r->range[0]; (void)snprintf(content_range, sizeof(content_range), "bytes %lld-%lld/%lld", range->start, range->end, st->st_size); @@ -341,56 +331,46 @@ server_partial_file_request(struct httpd *env, struct client *clt, char *path, content_range) == NULL) goto abort; - content_length = range->end - range->start + 1; - if (buffer_add_range(fd, evb, range) == 0) - goto abort; - + range = &r->range[0]; + content_length += range->end - range->start + 1; } else { - content_length = 0; - boundary = arc4random(); - /* Generate a multipart payload of byteranges */ - while (nranges--) { - if ((i = evbuffer_add_printf(evb, "\r\n--%ud\r\n", - boundary)) == -1) - goto abort; + /* Add boundary, all parts will be handled by the callback */ + arc4random_buf(&clt->clt_boundary, sizeof(clt->clt_boundary)); - content_length += i; - if ((i = evbuffer_add_printf(evb, - "Content-Type: %s/%s\r\n", - media->media_type, media->media_subtype)) == -1) - goto abort; + /* Calculate Content-Length of the complete multipart body */ + for (i = 0; i < nranges; i++) { + range = &r->range[i]; - content_length += i; - if ((i = evbuffer_add_printf(evb, + /* calculate Content-Length of the complete body */ + if ((ret = snprintf(NULL, 0, + "\r\n--%llu\r\n" + "Content-Type: %s/%s\r\n" "Content-Range: bytes %lld-%lld/%lld\r\n\r\n", - range->start, range->end, st->st_size)) == -1) + clt->clt_boundary, + media->media_type, media->media_subtype, + range->start, range->end, st->st_size)) < 0) goto abort; - content_length += i; - if (buffer_add_range(fd, evb, range) == 0) - goto abort; + /* Add data length */ + content_length += ret + range->end - range->start + 1; - content_length += range->end - range->start + 1; - range++; } - - if ((i = evbuffer_add_printf(evb, "\r\n--%ud--\r\n", - boundary)) == -1) + if ((ret = snprintf(NULL, 0, "\r\n--%llu--\r\n", + clt->clt_boundary)) < 0) goto abort; - - content_length += i; + content_length += ret; /* prepare multipart/byteranges media type */ (void)strlcpy(multipart_media.media_type, "multipart", sizeof(multipart_media.media_type)); (void)snprintf(multipart_media.media_subtype, sizeof(multipart_media.media_subtype), - "byteranges; boundary=%ud", boundary); + "byteranges; boundary=%llu", clt->clt_boundary); media = &multipart_media; } - close(fd); - fd = -1; + /* Start with first range */ + r->range_toread = TOREAD_HTTP_RANGE; ret = server_response_http(clt, 206, media, content_length, MINIMUM(time(NULL), st->st_mtim.tv_sec)); @@ -399,25 +379,32 @@ server_partial_file_request(struct httpd *env, struct client *clt, char *path, goto fail; case 0: /* Connection is already finished */ - evbuffer_free(evb); - evb = NULL; + close(fd); goto done; default: break; } - if (server_bufferevent_write_buffer(clt, evb) == -1) + clt->clt_fd = fd; + if (clt->clt_srvbev != NULL) + bufferevent_free(clt->clt_srvbev); + + clt->clt_srvbev_throttled = 0; + clt->clt_srvbev = bufferevent_new(clt->clt_fd, server_read_httprange, + server_write, server_file_error, clt); + if (clt->clt_srvbev == NULL) { + errstr = "failed to allocate file buffer event"; goto fail; + } - evbuffer_free(evb); - evb = NULL; + /* Adjust read watermark to the socket output buffer size */ + bufferevent_setwatermark(clt->clt_srvbev, EV_READ, 0, + clt->clt_sndbufsiz); - 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; + 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); @@ -602,7 +589,7 @@ void server_file_error(struct bufferevent *bev, short error, void *arg) { struct client *clt = arg; - struct evbuffer *dst; + struct evbuffer *src, *dst; if (error & EVBUFFER_TIMEOUT) { server_close(clt, "buffer event timeout"); @@ -621,6 +608,12 @@ server_file_error(struct bufferevent *bev, short error, void *arg) clt->clt_done = 1; + src = EVBUFFER_INPUT(clt->clt_bev); + + /* Close the connection if a previous pipeline is empty */ + if (clt->clt_pipelining && EVBUFFER_LENGTH(src) == 0) + clt->clt_persist = 0; + if (clt->clt_persist) { /* Close input file and wait for next HTTP request */ if (clt->clt_fd != -1) @@ -629,6 +622,12 @@ server_file_error(struct bufferevent *bev, short error, void *arg) clt->clt_toread = TOREAD_HTTP_HEADER; server_reset_http(clt); bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); + + /* Start pipelining if the buffer is not empty */ + if (EVBUFFER_LENGTH(src)) { + clt->clt_pipelining++; + server_read_http(clt->clt_bev, arg); + } return; } @@ -670,41 +669,44 @@ server_file_modified_since(struct http_descriptor *desc, struct stat *st) return (-1); } -struct range * -parse_range(char *str, size_t file_sz, int *nranges) +int +parse_ranges(struct client *clt, char *str, size_t file_sz) { - static struct range ranges[MAX_RANGES]; int i = 0; char *p, *q; + struct range_data *r = &clt->clt_ranges; + + memset(r, 0, sizeof(*r)); /* Extract range unit */ if ((p = strchr(str, '=')) == NULL) - return (NULL); + return (-1); *p++ = '\0'; /* Check if it's a bytes range spec */ if (strcmp(str, "bytes") != 0) - return (NULL); + return (-1); while ((q = strchr(p, ',')) != NULL) { *q++ = '\0'; /* Extract start and end positions */ - if (parse_range_spec(p, file_sz, &ranges[i]) == 0) + if (parse_range_spec(p, file_sz, &r->range[i]) == 0) continue; i++; - if (i == MAX_RANGES) - return (NULL); + if (i == SERVER_MAX_RANGES) + return (-1); p = q; } - if (parse_range_spec(p, file_sz, &ranges[i]) != 0) + if (parse_range_spec(p, file_sz, &r->range[i]) != 0) i++; - *nranges = i; - return (i ? ranges : NULL); + r->range_total = file_sz; + r->range_count = i; + return (i); } int @@ -754,26 +756,3 @@ parse_range_spec(char *str, size_t size, struct range *r) return (1); } - -int -buffer_add_range(int fd, struct evbuffer *evb, struct range *range) -{ - char buf[BUFSIZ]; - size_t n, range_sz; - ssize_t nread; - - if (lseek(fd, range->start, SEEK_SET) == -1) - return (0); - - range_sz = range->end - range->start + 1; - while (range_sz) { - n = MINIMUM(range_sz, sizeof(buf)); - if ((nread = read(fd, buf, n)) == -1) - return (0); - - evbuffer_add(evb, buf, nread); - range_sz -= nread; - } - - return (1); -} diff --git a/httpd/server_http.c b/httpd/server_http.c index b69805a..e64de0d 100644 --- a/httpd/server_http.c +++ b/httpd/server_http.c @@ -1,7 +1,7 @@ -/* $OpenBSD: server_http.c,v 1.109 2016/07/27 11:02:41 reyk Exp $ */ +/* $OpenBSD: server_http.c,v 1.117 2017/05/15 10:40:47 jsg Exp $ */ /* - * Copyright (c) 2006 - 2015 Reyk Floeter + * Copyright (c) 2006 - 2017 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 @@ -49,17 +49,12 @@ int server_http_authenticate(struct server_config *, 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) +server_http(void) { - if (x_env != NULL) - env = x_env; - DPRINTF("%s: sorting lookup tables, pid %d", __func__, getpid()); /* Sort the HTTP lookup arrays */ @@ -221,16 +216,34 @@ server_read_http(struct bufferevent *bev, void *arg) goto done; } - while (!clt->clt_done && (line = - evbuffer_readln(src, NULL, EVBUFFER_EOL_CRLF_STRICT)) != NULL) { - linelen = strlen(line); + while (!clt->clt_headersdone) { + if (!clt->clt_line) { + /* Peek into the buffer to see if it looks like HTTP */ + key = EVBUFFER_DATA(src); + if (!isalpha(*key)) { + server_abort_http(clt, 400, + "invalid request line"); + goto abort; + } + } + + if ((line = evbuffer_readln(src, + &linelen, EVBUFFER_EOL_CRLF_STRICT)) == NULL) { + /* No newline found after too many bytes */ + if (size > SERVER_MAXHEADERLENGTH) { + server_abort_http(clt, 413, + "request line too long"); + goto abort; + } + break; + } /* * An empty line indicates the end of the request. * libevent already stripped the \r\n for us. */ if (!linelen) { - clt->clt_done = 1; + clt->clt_headersdone = 1; free(line); break; } @@ -365,7 +378,7 @@ server_read_http(struct bufferevent *bev, void *arg) free(line); } - if (clt->clt_done) { + if (clt->clt_headersdone) { if (desc->http_method == HTTP_METHOD_NONE) { server_abort_http(clt, 406, "no method"); return; @@ -377,7 +390,6 @@ server_read_http(struct bufferevent *bev, void *arg) clt->clt_toread = TOREAD_UNLIMITED; bev->readcb = server_read; break; - case HTTP_METHOD_DELETE: case HTTP_METHOD_GET: case HTTP_METHOD_HEAD: /* WebDAV methods */ @@ -385,6 +397,7 @@ server_read_http(struct bufferevent *bev, void *arg) case HTTP_METHOD_MOVE: clt->clt_toread = 0; break; + case HTTP_METHOD_DELETE: case HTTP_METHOD_OPTIONS: case HTTP_METHOD_POST: case HTTP_METHOD_PUT: @@ -435,7 +448,7 @@ server_read_http(struct bufferevent *bev, void *arg) done: if (clt->clt_toread != 0) bufferevent_disable(bev, EV_READ); - server_response(env, clt); + server_response(httpd_env, clt); return; } if (clt->clt_done) { @@ -614,6 +627,101 @@ server_read_httpchunks(struct bufferevent *bev, void *arg) server_close(clt, strerror(errno)); } +void +server_read_httprange(struct bufferevent *bev, void *arg) +{ + struct client *clt = arg; + struct evbuffer *src = EVBUFFER_INPUT(bev); + size_t size; + struct media_type *media; + struct range_data *r = &clt->clt_ranges; + struct range *range; + + getmonotime(&clt->clt_tv_last); + + if (r->range_toread > 0) { + size = EVBUFFER_LENGTH(src); + if (!size) + return; + + /* Read chunk data */ + if ((off_t)size > r->range_toread) { + size = r->range_toread; + if (server_bufferevent_write_chunk(clt, src, size) + == -1) + goto fail; + r->range_toread = 0; + } else { + if (server_bufferevent_write_buffer(clt, src) == -1) + goto fail; + r->range_toread -= size; + } + if (r->range_toread < 1) + r->range_toread = TOREAD_HTTP_RANGE; + DPRINTF("%s: done, size %lu, to read %lld", __func__, + size, r->range_toread); + } + + switch (r->range_toread) { + case TOREAD_HTTP_RANGE: + if (r->range_index >= r->range_count) { + if (r->range_count > 1) { + /* Add end marker */ + if (server_bufferevent_printf(clt, + "\r\n--%llu--\r\n", + clt->clt_boundary) == -1) + goto fail; + } + r->range_toread = TOREAD_HTTP_NONE; + break; + } + + range = &r->range[r->range_index]; + + if (r->range_count > 1) { + media = r->range_media; + if (server_bufferevent_printf(clt, + "\r\n--%llu\r\n" + "Content-Type: %s/%s\r\n" + "Content-Range: bytes %lld-%lld/%zu\r\n\r\n", + clt->clt_boundary, + media->media_type, media->media_subtype, + range->start, range->end, r->range_total) == -1) + goto fail; + } + r->range_toread = range->end - range->start + 1; + + if (lseek(clt->clt_fd, range->start, SEEK_SET) == -1) + goto fail; + + /* Throw away bytes that are already in the input buffer */ + evbuffer_drain(src, EVBUFFER_LENGTH(src)); + + /* Increment for the next part */ + r->range_index++; + break; + case TOREAD_HTTP_NONE: + case 0: + break; + } + + if (clt->clt_done) + goto done; + + if (EVBUFFER_LENGTH(EVBUFFER_OUTPUT(clt->clt_bev)) > (size_t) + SERVER_MAX_PREFETCH * clt->clt_sndbufsiz) { + bufferevent_disable(clt->clt_srvbev, EV_READ); + clt->clt_srvbev_throttled = 1; + } + + return; + done: + (*bev->errorcb)(bev, EVBUFFER_READ, bev->cbarg); + return; + fail: + server_close(clt, strerror(errno)); +} + void server_reset_http(struct client *clt) { @@ -624,8 +732,9 @@ server_reset_http(struct client *clt) server_httpdesc_free(clt->clt_descreq); server_httpdesc_free(clt->clt_descresp); clt->clt_headerlen = 0; - clt->clt_line = 0; + clt->clt_headersdone = 0; clt->clt_done = 0; + clt->clt_line = 0; clt->clt_chunk = 0; free(clt->clt_remote_user); clt->clt_remote_user = NULL; @@ -778,6 +887,8 @@ server_abort_http(struct client *clt, unsigned int code, const char *msg) msg = buf; break; case 401: + if (msg == NULL) + break; if (stravis(&escapedmsg, msg, VIS_DQ) == -1) { code = 500; extraheader = NULL; @@ -789,6 +900,8 @@ server_abort_http(struct client *clt, unsigned int code, const char *msg) } break; case 416: + if (msg == NULL) + break; if (asprintf(&extraheader, "Content-Range: %s\r\n", msg) == -1) { code = 500; @@ -959,6 +1072,14 @@ server_expand_http(struct client *clt, const char *val, char *buf, if (ret != 0) return (NULL); } + if (strstr(val, "$HTTP_HOST") != NULL) { + if (desc->http_host == NULL) + return (NULL); + if ((str = url_encode(desc->http_host)) == NULL) + return (NULL); + expand_string(buf, len, "$HTTP_HOST", str); + free(str); + } if (strstr(val, "$REMOTE_") != NULL) { if (strstr(val, "$REMOTE_ADDR") != NULL) { if (print_host(&clt->clt_ss, @@ -1095,6 +1216,10 @@ server_response(struct httpd *httpd, struct client *clt) if (clt->clt_persist >= srv_conf->maxrequests) clt->clt_persist = 0; + /* pipelining should end after the first "idempotent" method */ + if (clt->clt_pipelining && clt->clt_toread > 0) + clt->clt_persist = 0; + /* * Do we have a Host header and matching configuration? * XXX the Host can also appear in the URL path. diff --git a/regress/tests/Httpd.pm b/regress/tests/Httpd.pm index d5b9df0..485ff68 100644 --- a/regress/tests/Httpd.pm +++ b/regress/tests/Httpd.pm @@ -1,4 +1,4 @@ -# $OpenBSD: Httpd.pm,v 1.1 2015/07/16 16:35:57 reyk Exp $ +# $OpenBSD: Httpd.pm,v 1.2 2017/01/30 21:18:24 reyk Exp $ # Copyright (c) 2010-2015 Alexander Bluhm # Copyright (c) 2015 Reyk Floeter @@ -27,6 +27,7 @@ sub new { my $class = shift; my %args = @_; $args{chroot} ||= "."; + $args{docroot} ||= "htdocs"; $args{logfile} ||= $args{chroot}."/httpd.log"; $args{up} ||= $args{dryrun} || "server_launch: "; $args{down} ||= $args{dryrun} ? "httpd.conf:" : "parent terminating"; @@ -54,7 +55,7 @@ sub new { my $listenport = $self->{listenport}; print $fh "prefork 1\n"; # only crashes of first child are observed - print $fh "chroot \"".$args{chroot}."\"\n"; + print $fh "chroot \"".$args{docroot}."\"\n"; print $fh "logdir \"".$args{chroot}."\"\n"; my @http = @{$self->{http}}; diff --git a/regress/tests/LICENSE b/regress/tests/LICENSE index 8f60827..a5303a4 100644 --- a/regress/tests/LICENSE +++ b/regress/tests/LICENSE @@ -1,4 +1,4 @@ -# Copyright (c) 2010-2015 Alexander Bluhm +# Copyright (c) 2010-2017 Alexander Bluhm # Copyright (c) 2014,2015 Reyk Floeter # # Permission to use, copy, modify, and distribute this software for any diff --git a/regress/tests/Makefile b/regress/tests/Makefile index 5298c7c..1c5c1ec 100644 --- a/regress/tests/Makefile +++ b/regress/tests/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.2 2015/07/16 17:00:41 reyk Exp $ +# $OpenBSD: Makefile,v 1.8 2017/07/14 13:31:44 bluhm Exp $ # The following ports must be installed for the regression tests: # p5-IO-Socket-INET6 object interface for AF_INET and AF_INET6 domain sockets @@ -17,18 +17,9 @@ PERL_REQUIRE != perl -Mstrict -Mwarnings -e ' \ regress: @echo "${PERL_REQUIRE}" @echo install these perl packages for additional tests + @echo SKIPPED .endif -# Fill out these variables if you want to test httpd with -# the httpd process running on a remote machine. You have to specify -# a local and remote ip address for the tcp connections. To control -# the remote machine you need a hostname for ssh to log in. All the -# test files must be in the same directory local and remote. - -LOCAL_ADDR ?= -REMOTE_ADDR ?= -REMOTE_SSH ?= - # Automatically generate regress targets from test cases in directory. ARGS != cd ${.CURDIR} && ls args-*.pl @@ -37,10 +28,10 @@ REGRESS_TARGETS = ${TARGETS:S/^/run-regress-/} CLEANFILES += *.log httpd.conf ktrace.out stamp-* CLEANFILES += *.pem *.req *.crt *.key *.srl md5-* -HTDOCS = 512 1048576 1073741824 -HTDOCS_MD5 = ${HTDOCS:S/^/${.OBJDIR}\/md5-/} +HTDOCS_FILES = 512 1048576 1073741824 +HTDOCS_MD5 = ${HTDOCS_FILES:S,^,md5-,} HTDOCS_SPARSE = yes -CLEANFILES += ${HTDOCS} +CLEANFILES += htdocs/* # Set variables so that make runs with and without obj directory. # Only do that if necessary to keep visible output short. @@ -62,19 +53,20 @@ run-regress-$a: $a ${HTDOCS_MD5} time SUDO=${SUDO} KTRACE=${KTRACE} HTTPD=${HTTPD} perl ${PERLINC} ${PERLPATH}httpd.pl ${.OBJDIR} ${PERLPATH}$a .endfor -# htdocs +# populate htdocs -.for d in ${HTDOCS} -${.OBJDIR}/$d: +.for d in ${HTDOCS_FILES} +htdocs/$d: @echo '\n======== file: $d ========' + mkdir -m 0755 -p ${@:H} .if (${HTDOCS_SPARSE} != "yes") - @dd if=/dev/arandom of=$@ count=$$(($d / 512)) bs=512 + dd if=/dev/arandom of=$@ count=$$(($d / 512)) bs=512 .else - @dd of=$@ seek=$$(($d / 512)) bs=512 count=0 status=none + dd of=$@ seek=$$(($d / 512)) bs=512 count=0 status=none .endif -${.OBJDIR}/md5-$d: ${.OBJDIR}/$d - @md5 -q ${.OBJDIR}/$d > $@ +md5-$d: htdocs/$d + md5 -q htdocs/$d >$@ .endfor # create certificates for TLS diff --git a/regress/tests/README b/regress/tests/README index c9c7836..0d2c8c0 100644 --- a/regress/tests/README +++ b/regress/tests/README @@ -1,16 +1,16 @@ -Run httpd regressions tests. The framework runs a client and a httpd. +Run httpd regression tests. The framework runs a client and an httpd. Each test creates a special httpd.conf and starts those two processes. All processes write log files that are checked for certain messages. The test arguments are kept in the args-*.pl files. -SUDO=sudo -As httpd needs root privileges either run the tests as root or set +SUDO=doas +As httpd needs root privileges, either run the tests as root or set this variable and run make as a regular user. Only the code that -requires it, is run as root. +requires it is run as root. KTRACE=ktrace Set this variable if you want a ktrace output from httpd. Note that -ktrace is invoked after sudo as sudo would disable it. +ktrace is invoked after SUDO as SUDO would disable it. HTTPD=/usr/src/usr.sbin/httpd/obj/httpd Start an alternative httpd program that is not in the path. diff --git a/regress/tests/args-get-1048576.pl b/regress/tests/args-get-1048576.pl index 9253aec..fa06bba 100644 --- a/regress/tests/args-get-1048576.pl +++ b/regress/tests/args-get-1048576.pl @@ -6,7 +6,7 @@ our %args = ( client => { path => "$len", len => $len, - http_vers => [ "1.0" ], + http_vers => [ "1.0" ], }, len => 1048576, md5 => path_md5("$len") diff --git a/regress/tests/args-get-1073741824.pl b/regress/tests/args-get-1073741824.pl index 2b4c5f4..c27e00d 100644 --- a/regress/tests/args-get-1073741824.pl +++ b/regress/tests/args-get-1073741824.pl @@ -6,7 +6,7 @@ my @lengths = ($len, $len); our %args = ( client => { path => "$len", - http_vers => [ "1.0" ], + http_vers => [ "1.0" ], lengths => \@lengths, }, md5 => path_md5("$len"), diff --git a/regress/tests/args-get-512.pl b/regress/tests/args-get-512.pl index 20e92c4..6a0b79a 100644 --- a/regress/tests/args-get-512.pl +++ b/regress/tests/args-get-512.pl @@ -6,7 +6,7 @@ my @lengths = ($len, $len, $len); our %args = ( client => { path => "$len", - http_vers => [ "1.0" ], + http_vers => [ "1.0" ], lengths => \@lengths, }, md5 => path_md5("$len"), diff --git a/regress/tests/args-get-slash.pl b/regress/tests/args-get-slash.pl index 9406f4b..e3e7a3b 100644 --- a/regress/tests/args-get-slash.pl +++ b/regress/tests/args-get-slash.pl @@ -7,11 +7,11 @@ our %args = ( my $self = shift; print "GET /\r\n\r\n"; }, - nocheck => 1 + nocheck => 1 }, httpd => { loggrep => { - qr/"GET \/" 500 0/ => 1, + qr/"GET \/" 400 0/ => 1, }, }, ); diff --git a/regress/tests/funcs.pl b/regress/tests/funcs.pl index beda09a..fde3807 100644 --- a/regress/tests/funcs.pl +++ b/regress/tests/funcs.pl @@ -1,6 +1,6 @@ -# $OpenBSD: funcs.pl,v 1.6 2016/05/03 19:13:04 bluhm Exp $ +# $OpenBSD: funcs.pl,v 1.8 2017/07/14 13:31:44 bluhm Exp $ -# Copyright (c) 2010-2015 Alexander Bluhm +# Copyright (c) 2010-2017 Alexander Bluhm # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above @@ -188,9 +188,12 @@ sub http_request { sub http_response { my ($self, $len) = @_; my $method = $self->{method} || "GET"; + my $code = $self->{code} || "200 OK"; my $vers; my $chunked = 0; + my $multipart = 0; + my $boundary; { local $/ = "\r\n"; local $_ = ; @@ -198,8 +201,8 @@ sub http_response { or die ref($self), " missing http $len response"; chomp; print STDERR "<<< $_\n"; - m{^HTTP/(\d\.\d) 200 OK$} - or die ref($self), " http response not ok" + m{^HTTP/(\d\.\d) $code$} + or die ref($self), " http response not $code" unless $self->{httpnok}; $vers = $1; while () { @@ -207,7 +210,7 @@ sub http_response { print STDERR "<<< $_\n"; last if /^$/; if (/^Content-Length: (.*)/) { - if ($self->{httpnok}) { + if ($self->{httpnok} or $self->{multipart}) { $len = $1; } else { $1 == $len or die ref($self), @@ -217,12 +220,20 @@ sub http_response { if (/^Transfer-Encoding: chunked$/) { $chunked = 1; } + if (/^Content-Type: multipart\/byteranges; boundary=(.*)$/) { + $multipart = 1; + $boundary = $1; + } } } - if ($chunked) { + die ref($self), " no multipart response" + if ($self->{multipart} && $multipart == 0); + + if ($multipart) { + read_multipart($self, $boundary); + } elsif ($chunked) { read_chunked($self); } else { - #$len = $vers eq "1.1" ? $len : undef; read_char($self, $len) if $method eq "GET"; } @@ -265,6 +276,47 @@ sub read_chunked { } } +sub read_multipart { + my $self = shift; + my $boundary = shift; + my $ctx = Digest::MD5->new(); + my $len = 0; + + for (;;) { + my $part = 0; + { + local $/ = "\r\n"; + local $_ = ; + local $_ = ; + defined or die ref($self), " missing boundary"; + chomp; + print STDERR "<<< $_\n"; + /^--$boundary(--)?$/ + or die ref($self), " boundary not found: $_"; + if (not $1) { + while () { + chomp; + if (/^Content-Length: (.*)/) { + $part = $1; + } + if (/^Content-Range: bytes (\d+)-(\d+)\/(\d+)$/) { + $part = $2 - $1 + 1; + } + print STDERR "<<< $_\n"; + last if /^$/; + } + } + } + last unless $part > 0; + + $len += read_part($self, $ctx, $part); + } + + print STDERR "LEN: ", $len, "\n"; + print STDERR "MD5: ", $ctx->hexdigest, "\n"; + +} + sub errignore { $SIG{PIPE} = 'IGNORE'; $SIG{__DIE__} = sub { @@ -277,7 +329,7 @@ sub errignore { } ######################################################################## -# Server funcs +# Common funcs ######################################################################## sub read_char { @@ -285,107 +337,39 @@ sub read_char { my $max = shift // $self->{max}; my $ctx = Digest::MD5->new(); - my $len = 0; - if (defined($max) && $max == 0) { - print STDERR "Max\n"; - } else { - while ((my $r = sysread(STDIN, my $buf, POSIX::BUFSIZ))) { - my $pct; - $_ = $buf; - $len += $r; - $ctx->add($_); - $pct = ($len / $max) * 100.0; - printf(STDERR "%.2f%%\n", $pct); - if (defined($max) && $len >= $max) { - print STDERR "\nMax"; - last; - } - } - print STDERR "\n"; - } + my $len = read_part($self, $ctx, $max); print STDERR "LEN: ", $len, "\n"; print STDERR "MD5: ", $ctx->hexdigest, "\n"; } -sub http_server { +sub read_part { my $self = shift; - my %header = %{$self->{header} || { Server => "Perl/".$^V }}; - my $cookie = $self->{cookie} || ""; + my ($ctx, $max) = @_; - my($method, $url, $vers); - do { - my $len; - { - local $/ = "\r\n"; - local $_ = ; - return unless defined $_; - chomp; - print STDERR "<<< $_\n"; - ($method, $url, $vers) = m{^(\w+) (.*) HTTP/(1\.[01])$} - or die ref($self), " http request not ok"; - $method =~ /^(GET|PUT)$/ - or die ref($self), " unknown method: $method"; - ($len, my @chunks) = $url =~ /(\d+)/g; - $len = [ $len, @chunks ] if @chunks; - while () { - chomp; - print STDERR "<<< $_\n"; - last if /^$/; - if ($method eq "PUT" && - /^Content-Length: (.*)/) { - $1 == $len or die ref($self), - " bad content length $1"; - } - $cookie ||= $1 if /^Cookie: (.*)/; - } - } - if ($method eq "PUT" ) { - if (ref($len) eq 'ARRAY') { - read_chunked($self); - } else { - read_char($self, $len); - } - } - - my @response = ("HTTP/$vers 200 OK"); - $len = defined($len) ? $len : scalar(split /|/,$url); - if ($vers eq "1.1" && $method eq "GET") { - if (ref($len) eq 'ARRAY') { - push @response, "Transfer-Encoding: chunked"; - } else { - push @response, "Content-Length: $len"; - } + my $opct = 0; + my $len = 0; + for (;;) { + if (defined($max) && $len >= $max) { + print STDERR "Max\n"; + last; } - foreach my $key (sort keys %header) { - my $val = $header{$key}; - if (ref($val) eq 'ARRAY') { - push @response, "$key: $_" - foreach @{$val}; - } else { - push @response, "$key: $val"; - } + my $rlen = POSIX::BUFSIZ; + if (defined($max) && $rlen > $max - $len) { + $rlen = $max - $len; } - push @response, "Set-Cookie: $cookie" if $cookie; - push @response, ""; - - print STDERR map { ">>> $_\n" } @response; - print map { "$_\r\n" } @response; - - if ($method eq "GET") { - if (ref($len) eq 'ARRAY') { - if ($vers eq "1.1") { - write_chunked($self, @$len); - } else { - write_char($self, $_) foreach (@$len); - } - } else { - write_char($self, $len); - } + defined(my $n = read(STDIN, my $buf, $rlen)) + or die ref($self), " read failed: $!"; + $n or last; + $len += $n; + $ctx->add($buf); + my $pct = ($len / $max) * 100.0; + if ($pct >= $opct + 1) { + printf(STDERR "%.2f%% $len/$max\n", $pct); + $opct = $pct; } - IO::Handle::flush(\*STDOUT); - } while ($vers eq "1.1"); - $self->{redo}-- if $self->{redo}; + } + return $len; } sub write_chunked { -- cgit v1.2.3-54-g00ecf