From 5eb16802a795fa3c1fcb936f3261776db11a8a3c Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Mon, 18 Aug 2014 09:43:48 +0200 Subject: sync --- Makefile | 12 +- config.c | 142 +++++++++++-- control.c | 7 +- http.h | 61 +++++- httpd.8 | 62 +++++- httpd.c | 107 +++++++++- httpd.conf.5 | 206 +++++++++++++++++-- httpd.h | 181 ++++++++++++++--- log.c | 4 +- logger.c | 310 ++++++++++++++++++++++++++++ parse.y | 478 ++++++++++++++++++++++++++++++++++++++++--- proc.c | 9 +- server.c | 543 ++++++++++++++++++++++++++++++++++++++++++++----- server_fcgi.c | 643 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ server_file.c | 285 +++++++++++++++++--------- server_http.c | 220 ++++++++++++++++---- 16 files changed, 2946 insertions(+), 324 deletions(-) create mode 100644 logger.c create mode 100644 server_fcgi.c diff --git a/Makefile b/Makefile index a6c482f..63d50f4 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,15 @@ -# $OpenBSD: Makefile,v 1.20 2014/07/12 23:34:54 reyk Exp $ +# $OpenBSD: Makefile,v 1.25 2014/08/04 17:38:12 reyk Exp $ PROG= httpd SRCS= parse.y -SRCS+= config.c control.c httpd.c log.c proc.c -SRCS+= server.c server_http.c server_file.c +SRCS+= config.c control.c httpd.c log.c logger.c proc.c +SRCS+= server.c server_http.c server_file.c server_fcgi.c MAN= httpd.8 httpd.conf.5 -LDADD= -levent -lssl -lcrypto -lutil -DPADD= ${LIBEVENT} ${LIBSSL} ${LIBCRYPTO} ${LIBUTIL} +LDADD= -levent -lressl -lssl -lcrypto -lutil +DPADD= ${LIBEVENT} ${LIBRESSL} ${LIBSSL} ${LIBCRYPTO} ${LIBUTIL} #DEBUG= -g -DDEBUG=3 -CFLAGS+= -Wall -I${.CURDIR} -Werror +CFLAGS+= -Wall -I${.CURDIR} CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes CFLAGS+= -Wmissing-declarations CFLAGS+= -Wshadow -Wpointer-arith diff --git a/config.c b/config.c index 762a7a3..7ab53b5 100644 --- a/config.c +++ b/config.c @@ -1,4 +1,4 @@ -/* $OpenBSD: config.c,v 1.5 2014/07/25 23:30:58 reyk Exp $ */ +/* $OpenBSD: config.c,v 1.21 2014/08/06 18:21:14 reyk Exp $ */ /* * Copyright (c) 2011 - 2014 Reyk Floeter @@ -36,14 +36,13 @@ #include #include #include +#include #include #include #include #include #include -#include - #include "httpd.h" int config_getserver_config(struct httpd *, struct server *, @@ -61,6 +60,7 @@ config_init(struct httpd *env) ps->ps_what[PROC_PARENT] = CONFIG_ALL; ps->ps_what[PROC_SERVER] = CONFIG_SERVERS|CONFIG_MEDIA; + ps->ps_what[PROC_LOGGER] = CONFIG_SERVERS; } /* Other configuration */ @@ -184,8 +184,17 @@ config_setserver(struct httpd *env, struct server *srv) c = 0; iov[c].iov_base = &s; iov[c++].iov_len = sizeof(s); + if (srv->srv_conf.ssl_cert_len != 0) { + iov[c].iov_base = srv->srv_conf.ssl_cert; + iov[c++].iov_len = srv->srv_conf.ssl_cert_len; + } + if (srv->srv_conf.ssl_key_len != 0) { + iov[c].iov_base = srv->srv_conf.ssl_key; + iov[c++].iov_len = srv->srv_conf.ssl_key_len; + } - if (id == PROC_SERVER) { + if (id == PROC_SERVER && + (srv->srv_conf.flags & SRVFLAG_LOCATION) == 0) { /* XXX imsg code will close the fd after 1st call */ n = -1; proc_range(ps, id, &n, &m); @@ -216,6 +225,7 @@ config_getserver_config(struct httpd *env, struct server *srv, #endif struct server_config *srv_conf; u_int8_t *p = imsg->data; + u_int f; if ((srv_conf = calloc(1, sizeof(*srv_conf))) == NULL) return (-1); @@ -223,11 +233,85 @@ config_getserver_config(struct httpd *env, struct server *srv, IMSG_SIZE_CHECK(imsg, srv_conf); memcpy(srv_conf, p, sizeof(*srv_conf)); - TAILQ_INSERT_TAIL(&srv->srv_hosts, srv_conf, entry); + if (srv_conf->flags & SRVFLAG_LOCATION) { + /* Inherit configuration from the parent */ + f = SRVFLAG_INDEX|SRVFLAG_NO_INDEX; + if ((srv_conf->flags & f) == 0) { + srv_conf->flags |= srv->srv_conf.flags & f; + (void)strlcpy(srv_conf->index, srv->srv_conf.index, + sizeof(srv_conf->index)); + } + + f = SRVFLAG_AUTO_INDEX|SRVFLAG_NO_AUTO_INDEX; + if ((srv_conf->flags & f) == 0) + srv_conf->flags |= srv->srv_conf.flags & f; + + f = SRVFLAG_SOCKET|SRVFLAG_FCGI; + if ((srv_conf->flags & f) == SRVFLAG_FCGI) { + srv_conf->flags |= f; + (void)strlcpy(srv_conf->socket, HTTPD_FCGI_SOCKET, + sizeof(srv_conf->socket)); + } - DPRINTF("%s: %s %d received configuration \"%s\", parent \"%s\"", - __func__, ps->ps_title[privsep_process], ps->ps_instance, - srv_conf->name, srv->srv_conf.name); + f = SRVFLAG_ROOT; + if ((srv_conf->flags & f) == 0) { + srv_conf->flags |= srv->srv_conf.flags & f; + (void)strlcpy(srv_conf->root, srv->srv_conf.root, + sizeof(srv_conf->root)); + } + + f = SRVFLAG_FCGI|SRVFLAG_NO_FCGI; + if ((srv_conf->flags & f) == 0) + srv_conf->flags |= srv->srv_conf.flags & f; + + f = SRVFLAG_LOG|SRVFLAG_NO_LOG; + if ((srv_conf->flags & f) == 0) { + srv_conf->flags |= srv->srv_conf.flags & f; + srv_conf->logformat = srv->srv_conf.logformat; + } + + f = SRVFLAG_SYSLOG|SRVFLAG_NO_SYSLOG; + if ((srv_conf->flags & f) == 0) + srv_conf->flags |= srv->srv_conf.flags & f; + + f = SRVFLAG_SSL; + srv_conf->flags |= srv->srv_conf.flags & f; + + f = SRVFLAG_ACCESS_LOG; + if ((srv_conf->flags & f) == 0) { + srv_conf->flags |= srv->srv_conf.flags & f; + (void)strlcpy(srv_conf->accesslog, + srv->srv_conf.accesslog, + sizeof(srv_conf->accesslog)); + } + + f = SRVFLAG_ERROR_LOG; + if ((srv_conf->flags & f) == 0) { + srv_conf->flags |= srv->srv_conf.flags & f; + (void)strlcpy(srv_conf->errorlog, + srv->srv_conf.errorlog, + sizeof(srv_conf->errorlog)); + } + + memcpy(&srv_conf->timeout, &srv->srv_conf.timeout, + sizeof(srv_conf->timeout)); + srv_conf->maxrequests = srv->srv_conf.maxrequests; + srv_conf->maxrequestbody = srv->srv_conf.maxrequestbody; + + DPRINTF("%s: %s %d location \"%s\", " + "parent \"%s\", flags: %s", + __func__, ps->ps_title[privsep_process], ps->ps_instance, + srv_conf->location, srv->srv_conf.name, + printb_flags(srv_conf->flags, SRVFLAG_BITS)); + } else { + /* Add a new "virtual" server */ + DPRINTF("%s: %s %d server \"%s\", parent \"%s\", flags: %s", + __func__, ps->ps_title[privsep_process], ps->ps_instance, + srv_conf->name, srv->srv_conf.name, + printb_flags(srv_conf->flags, SRVFLAG_BITS)); + } + + TAILQ_INSERT_TAIL(&srv->srv_hosts, srv_conf, entry); return (0); } @@ -238,7 +322,7 @@ config_getserver(struct httpd *env, struct imsg *imsg) #ifdef DEBUG struct privsep *ps = env->sc_ps; #endif - struct server *srv; + struct server *srv = NULL; struct server_config srv_conf; u_int8_t *p = imsg->data; size_t s; @@ -247,15 +331,24 @@ config_getserver(struct httpd *env, struct imsg *imsg) memcpy(&srv_conf, p, sizeof(srv_conf)); s = sizeof(srv_conf); + if ((u_int)(IMSG_DATA_SIZE(imsg) - s) < + (srv_conf.ssl_cert_len + srv_conf.ssl_key_len)) { + log_debug("%s: invalid message length", __func__); + goto fail; + } + /* Check if server with matching listening socket already exists */ if ((srv = server_byaddr((struct sockaddr *) &srv_conf.ss, srv_conf.port)) != NULL) { /* Add "host" to existing listening server */ - close(imsg->fd); - return (config_getserver_config(env, - srv, imsg)); + if (imsg->fd != -1) + close(imsg->fd); + return (config_getserver_config(env, srv, imsg)); } + if (srv_conf.flags & SRVFLAG_LOCATION) + fatalx("invalid location"); + /* Otherwise create a new server */ if ((srv = calloc(1, sizeof(*srv))) == NULL) { close(imsg->fd); @@ -271,11 +364,32 @@ config_getserver(struct httpd *env, struct imsg *imsg) TAILQ_INSERT_TAIL(&srv->srv_hosts, &srv->srv_conf, entry); TAILQ_INSERT_TAIL(env->sc_servers, srv, srv_entry); - DPRINTF("%s: %s %d received configuration \"%s\"", __func__, + DPRINTF("%s: %s %d configuration \"%s\", flags: %s", __func__, ps->ps_title[privsep_process], ps->ps_instance, - srv->srv_conf.name); + srv->srv_conf.name, + printb_flags(srv->srv_conf.flags, SRVFLAG_BITS)); + + if (srv->srv_conf.ssl_cert_len != 0) { + if ((srv->srv_conf.ssl_cert = get_data(p + s, + srv->srv_conf.ssl_cert_len)) == NULL) + goto fail; + s += srv->srv_conf.ssl_cert_len; + } + if (srv->srv_conf.ssl_key_len != 0) { + if ((srv->srv_conf.ssl_key = get_data(p + s, + srv->srv_conf.ssl_key_len)) == NULL) + goto fail; + s += srv->srv_conf.ssl_key_len; + } return (0); + + fail: + free(srv->srv_conf.ssl_cert); + free(srv->srv_conf.ssl_key); + free(srv); + + return (-1); } int diff --git a/control.c b/control.c index 3a8552a..1988ba7 100644 --- a/control.c +++ b/control.c @@ -1,4 +1,4 @@ -/* $OpenBSD: control.c,v 1.1 2014/07/12 23:34:54 reyk Exp $ */ +/* $OpenBSD: control.c,v 1.4 2014/08/04 15:49:28 reyk Exp $ */ /* * Copyright (c) 2003, 2004 Henning Brauer @@ -16,8 +16,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include #include -#include #include #include #include @@ -32,8 +32,6 @@ #include #include -#include - #include "httpd.h" #define CONTROL_BACKLOG 5 @@ -271,6 +269,7 @@ control_dispatch_imsg(int fd, short event, void *arg) switch (imsg.hdr.type) { case IMSG_CTL_SHUTDOWN: case IMSG_CTL_RELOAD: + case IMSG_CTL_REOPEN: proc_forward_imsg(env->sc_ps, &imsg, PROC_PARENT, -1); break; case IMSG_CTL_NOTIFY: diff --git a/http.h b/http.h index 994e98b..43ac089 100644 --- a/http.h +++ b/http.h @@ -1,4 +1,4 @@ -/* $OpenBSD: http.h,v 1.4 2014/07/25 23:23:39 reyk Exp $ */ +/* $OpenBSD: http.h,v 1.7 2014/08/14 09:12:26 doug Exp $ */ /* * Copyright (c) 2012 - 2014 Reyk Floeter @@ -25,7 +25,7 @@ enum httpmethod { HTTP_METHOD_NONE = 0, - /* HTTP/1.1, RFC 2616 */ + /* HTTP/1.1, RFC 7231 */ HTTP_METHOD_GET, HTTP_METHOD_HEAD, HTTP_METHOD_POST, @@ -79,22 +79,39 @@ struct http_error { int error_code; const char *error_name; }; + +/* + * HTTP status codes based on IANA assignments (2014-06-11 version): + * https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml + * plus legacy (306) and non-standard (420). + */ #define HTTP_ERRORS { \ { 100, "Continue" }, \ { 101, "Switching Protocols" }, \ + { 102, "Processing" }, \ + /* 103-199 unassigned */ \ { 200, "OK" }, \ { 201, "Created" }, \ { 202, "Accepted" }, \ - { 203, "Non-Authorative Information" }, \ + { 203, "Non-Authoritative Information" }, \ { 204, "No Content" }, \ { 205, "Reset Content" }, \ { 206, "Partial Content" }, \ + { 207, "Multi-Status" }, \ + { 208, "Already Reported" }, \ + /* 209-225 unassigned */ \ + { 226, "IM Used" }, \ + /* 227-299 unassigned */ \ { 300, "Multiple Choices" }, \ { 301, "Moved Permanently" }, \ - { 302, "Moved Temporarily" }, \ + { 302, "Found" }, \ { 303, "See Other" }, \ { 304, "Not Modified" }, \ + { 305, "Use Proxy" }, \ + { 306, "Switch Proxy" }, \ { 307, "Temporary Redirect" }, \ + { 308, "Permanent Redirect" }, \ + /* 309-399 unassigned */ \ { 400, "Bad Request" }, \ { 401, "Unauthorized" }, \ { 402, "Payment Required" }, \ @@ -108,17 +125,37 @@ struct http_error { { 410, "Gone" }, \ { 411, "Length Required" }, \ { 412, "Precondition Failed" }, \ - { 413, "Request Entity Too Large" }, \ - { 414, "Request-URL Too Long" }, \ + { 413, "Payload Too Large" }, \ + { 414, "URI Too Long" }, \ { 415, "Unsupported Media Type" }, \ - { 416, "Requested Range Not Satisfiable" }, \ + { 416, "Range Not Satisfiable" }, \ { 417, "Expectation Failed" }, \ + /* 418-421 unassigned */ \ + { 420, "Enhance Your Calm" }, \ + { 422, "Unprocessable Entity" }, \ + { 423, "Locked" }, \ + { 424, "Failed Dependency" }, \ + /* 425 unassigned */ \ + { 426, "Upgrade Required" }, \ + /* 427 unassigned */ \ + { 428, "Precondition Required" }, \ + { 429, "Too Many Requests" }, \ + /* 430 unassigned */ \ + { 431, "Request Header Fields Too Large" }, \ + /* 432-499 unassigned */ \ { 500, "Internal Server Error" }, \ { 501, "Not Implemented" }, \ { 502, "Bad Gateway" }, \ { 503, "Service Unavailable" }, \ { 504, "Gateway Timeout" }, \ { 505, "HTTP Version Not Supported" }, \ + { 506, "Variant Also Negotiates" }, \ + { 507, "Insufficient Storage" }, \ + { 508, "Loop Detected" }, \ + /* 509 unassigned */ \ + { 510, "Not Extended" }, \ + { 511, "Network Authentication Required" }, \ + /* 512-599 unassigned */ \ { 0, NULL } \ } @@ -127,7 +164,10 @@ struct http_mediatype { char *media_type; char *media_subtype; }; -/* Some default media types */ +/* + * Some default media types based on (2014-08-04 version): + * https://www.iana.org/assignments/media-types/media-types.xhtml + */ #define MEDIA_TYPES { \ { "css", "text", "css" }, \ { "html", "text", "html" }, \ @@ -151,11 +191,14 @@ struct http_descriptor { #define query_key http_matchquery.kv_key #define query_val http_matchquery.kv_value - char http_host[MAXHOSTNAMELEN]; + char *http_host; enum httpmethod http_method; int http_chunked; char *http_version; + /* Rewritten path remains NULL if not used */ + char *http_path_alias; + /* A tree of headers and attached lists for repeated headers. */ struct kv *http_lastheader; struct kvtree http_headers; diff --git a/httpd.8 b/httpd.8 index a62378d..ab301e1 100644 --- a/httpd.8 +++ b/httpd.8 @@ -1,4 +1,4 @@ -.\" $OpenBSD: httpd.8,v 1.39 2014/07/22 19:03:21 jmc Exp $ +.\" $OpenBSD: httpd.8,v 1.48 2014/08/09 08:49:48 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: July 22 2014 $ +.Dd $Mdocdate: August 9 2014 $ .Dt HTTPD 8 .Os .Sh NAME @@ -26,20 +26,71 @@ .Op Fl D Ar macro Ns = Ns Ar value .Op Fl f Ar file .Sh DESCRIPTION +The +.Nm +daemon is an HTTP server with FastCGI and SSL support. +.Pp +The FastCGI implementation has optional socket support. .Nm -is a simple HTTP server that serves static files. +can log to +.Xr syslog 3 +or per-server files with several standard formats. +.Pp +.Nm +rereads its configuration file when it receives +.Dv SIGHUP +and reopens log files when it receives +.Dv SIGUSR1 . +.Pp +The options are as follows: +.Bl -tag -width Dssmacro=value +.It Fl D Ar macro Ns = Ns Ar value +Set a +.Ar macro +to a +.Ar value . +Macros can be referenced in the configuration files. +.It Fl d +Debug mode. +Create one server and don't detach or become a daemon. +This allows for easy monitoring of +.Nm . +.It Fl f Ar file +Specifies the configuration file. +The default is +.Pa /etc/httpd.conf . +.It Fl n +Check that the configuration is valid, but don't start any servers. +.It Fl v +Verbose mode. +Multiple +.Fl v +options increases the verbosity. +.El .Sh FILES -.Bl -tag -width "/var/run/httpd.sockXX" -compact +.Bl -tag -width "/etc/ssl/private/server.key" -compact .It /etc/httpd.conf Default configuration file. +.It /etc/ssl/private/server.key +Default SSL/TLS server key. +.It /etc/ssl/server.crt +Default SSL/TLS server certificate. .It /var/run/httpd.sock .Ux Ns -domain socket used for communication with .Nm . +.It /var/www/logs/access.log +Default access log file. +.It /var/www/logs/error.log +Default error log file. .El .Sh SEE ALSO .Xr httpd.conf 5 .Sh HISTORY +The +.Nm +program first appeared in +.Ox 5.6 . .Nm is based on .Xr relayd 8 . @@ -49,6 +100,3 @@ The .Nm program was written by .An Reyk Floeter Aq Mt reyk@openbsd.org . -.Sh CAVEATS -.Nm -is not finished yet. diff --git a/httpd.c b/httpd.c index 520ae5e..4b631ec 100644 --- a/httpd.c +++ b/httpd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: httpd.c,v 1.10 2014/07/26 09:59:14 reyk Exp $ */ +/* $OpenBSD: httpd.c,v 1.19 2014/08/13 16:04:28 reyk Exp $ */ /* * Copyright (c) 2014 Reyk Floeter @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -42,8 +43,6 @@ #include #include -#include - #include "httpd.h" __dead void usage(void); @@ -51,15 +50,19 @@ __dead void usage(void); int parent_configure(struct httpd *); void parent_configure_done(struct httpd *); void parent_reload(struct httpd *, u_int, const char *); +void parent_reopen(struct httpd *); void parent_sig_handler(int, short, void *); void parent_shutdown(struct httpd *); int parent_dispatch_server(int, struct privsep_proc *, struct imsg *); +int parent_dispatch_logger(int, struct privsep_proc *, + struct imsg *); struct httpd *httpd_env; static struct privsep_proc procs[] = { - { "server", PROC_SERVER, parent_dispatch_server, server } + { "server", PROC_SERVER, parent_dispatch_server, server }, + { "logger", PROC_LOGGER, parent_dispatch_logger, logger } }; void @@ -123,6 +126,11 @@ parent_sig_handler(int sig, short event, void *arg) case SIGPIPE: /* ignore */ break; + case SIGUSR1: + log_info("%s: reopen requested with SIGUSR1", __func__); + + parent_reopen(ps->ps_env); + break; default: fatalx("unexpected signal"); } @@ -142,6 +150,7 @@ int main(int argc, char *argv[]) { int c; + unsigned int proc; int debug = 0, verbose = 0; u_int32_t opts = 0; struct httpd *env; @@ -194,9 +203,6 @@ main(int argc, char *argv[]) if (parse_config(env->sc_conffile, env) == -1) exit(1); - if (debug) - env->sc_opts |= HTTPD_OPT_LOGUPDATE; - if (geteuid()) errx(1, "need root privileges"); @@ -220,6 +226,11 @@ main(int argc, char *argv[]) ps->ps_instances[PROC_SERVER] = env->sc_prefork_server; ps->ps_ninstances = env->sc_prefork_server; + if (env->sc_chroot == NULL) + env->sc_chroot = ps->ps_pw->pw_dir; + for (proc = 0; proc < nitems(procs); proc++) + procs[proc].p_chroot = env->sc_chroot; + proc_init(ps, procs, nitems(procs)); setproctitle("parent"); @@ -231,12 +242,14 @@ main(int argc, char *argv[]) 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)); @@ -282,7 +295,7 @@ parent_configure(struct httpd *env) } /* The servers need to reload their config. */ - env->sc_reload = env->sc_prefork_server; + env->sc_reload = env->sc_prefork_server + 1; for (id = 0; id < PROC_MAX; id++) { if (id == privsep_process) @@ -333,6 +346,13 @@ parent_reload(struct httpd *env, u_int reset, const char *filename) config_setreset(env, reset); } +void +parent_reopen(struct httpd *env) +{ + proc_compose_imsg(env->sc_ps, PROC_LOGGER, -1, IMSG_CTL_REOPEN, + -1, NULL, 0); +} + void parent_configure_done(struct httpd *env) { @@ -387,6 +407,46 @@ parent_dispatch_server(int fd, struct privsep_proc *p, struct imsg *imsg) return (0); } +int +parent_dispatch_logger(int fd, struct privsep_proc *p, struct imsg *imsg) +{ + struct httpd *env = p->p_env; + u_int v; + char *str = NULL; + + switch (imsg->hdr.type) { + case IMSG_CTL_RESET: + IMSG_SIZE_CHECK(imsg, &v); + memcpy(&v, imsg->data, sizeof(v)); + parent_reload(env, v, NULL); + break; + case IMSG_CTL_RELOAD: + if (IMSG_DATA_SIZE(imsg) > 0) + str = get_string(imsg->data, IMSG_DATA_SIZE(imsg)); + parent_reload(env, CONFIG_RELOAD, str); + if (str != NULL) + free(str); + break; + case IMSG_CTL_SHUTDOWN: + parent_shutdown(env); + break; + case IMSG_CTL_REOPEN: + parent_reopen(env); + break; + case IMSG_CFG_DONE: + parent_configure_done(env); + break; + case IMSG_LOG_OPEN: + if (logger_open_priv(imsg) == -1) + fatalx("failed to open log file"); + break; + default: + return (-1); + } + + return (0); +} + /* * Utility functions */ @@ -493,7 +553,7 @@ canonicalize_path(const char *input, char *path, size_t len) while (i[1] == '/') i++; continue; - } else if (i[1] == '.' && i[2] == '.' && + } else if (i[1] == '.' && i[2] == '.' && (i[3] == '/' || i[3] == '\0')) { /* b) revert '..' to previous directory */ i += 3; @@ -520,6 +580,35 @@ canonicalize_path(const char *input, char *path, size_t len) return (path); } +size_t +path_info(char *path) +{ + char *p, *start, *end, ch; + struct stat st; + int ret; + + start = path; + end = start + strlen(path); + + for (p = end; p > start; p--) { + /* Scan every path component from the end and at each '/' */ + if (p < end && *p != '/') + continue; + + /* Temporarily cut the path component out */ + ch = *p; + *p = '\0'; + ret = stat(path, &st); + *p = ch; + + /* Break if the initial path component was found */ + if (ret == 0) + break; + } + + return (p - start); +} + void socket_rlimit(int maxfd) { diff --git a/httpd.conf.5 b/httpd.conf.5 index 2cb442f..8a539f2 100644 --- a/httpd.conf.5 +++ b/httpd.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: httpd.conf.5,v 1.7 2014/07/25 17:49:11 reyk Exp $ +.\" $OpenBSD: httpd.conf.5,v 1.32 2014/08/17 18:46:29 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: July 25 2014 $ +.Dd $Mdocdate: August 17 2014 $ .Dt HTTPD.CONF 5 .Os .Sh NAME @@ -80,27 +80,29 @@ Macros can be defined that will later be expanded in context. Macro names must start with a letter, digit, or underscore, and may contain any of those characters. Macro names may not be reserved words (for example, -.Ic table , -.Ic relay , +.Ic directory , +.Ic log , or -.Ic timeout ) . +.Ic root ) . Macros are not expanded inside quotes. .Pp For example: .Bd -literal -offset indent ext_ip="10.0.0.1" -server \*(Ltwww\*(Gt { +server "default" { listen on $ext_ip port 80 } .Ed .Sh GLOBAL CONFIGURATION Here are the settings that can be set globally: .Bl -tag -width Ds -.It Xo -.Ic log -.Pq Ic updates Ns | Ns Ic all -.Xc -Set logging verbosity. +.It Ic chroot Ar directory +Set the +.Xr chroot 2 +directory. +If not specified, it defaults to +.Pa /var/www , +the home directory of the www user. .It Ic prefork Ar number Run the specified number of server processes. This increases the performance and prevents delays when connecting @@ -111,10 +113,122 @@ runs 3 server processes by default. .Sh SERVERS The configured web servers. .Pp -The following general table options are available: +Each +.Ic server +must have a +.Ar name +and include one or more lines of the following syntax: .Bl -tag -width Ds -.It Ic listen on Ar address Ic port Ar number +.It Ic connection Ar option +Set the specified options and limits for HTTP connections. +Valid options are: +.Bl -tag -width Ds +.It Ic max request body Ar number +Set the maximum body size in bytes that the client can send to the server. +The default value is 1048576 bytes (1M). +.It Ic max requests Ar number +Set the maximum number of requests per persistent HTTP connection. +Persistent connections are negotiated using the Keep-Alive header in +HTTP/1.0 and enabled by default in HTTP/1.1. +The default maximum number of requests per connection is 100. +.It Ic timeout Ar seconds +Specify the inactivity timeout in seconds for accepted sessions. +The default timeout is 600 seconds (10 minutes). +The maximum is 2147483647 seconds (68 years). +.El +.It Ic directory Ar option +Set the specified options when serving or accessing directories. +Valid options are: +.Bl -tag -width Ds +.It Oo Ic no Oc Ic auto index +If no index file is found, automatically generate a directory listing. +This is disabled by default. +.It Ic index Ar string +Set the directory index file. +If not specified, it defaults to +.Pa index.html . +.It Ic no index +Disable the directory index. +.Xr httpd 8 +will neither display nor generate a directory index. +.El +.It Oo Ic no Oc Ic fastcgi Op Ic socket Ar socket +Enable FastCGI instead of serving files. +The +.Ar socket +is a local path name within the +.Xr chroot 2 +root directory of +.Xr httpd 8 +and defaults to +.Pa /run/slowcgi.sock . +.It Ic listen on Ar address Oo Ic ssl Oc Ic port Ar number Set the listen address and port. +.It Ic location Ar path Brq ... +Specify server configuration rules for a specific location. +The +.Ar path +argument will be matched against the URL path with shell globbing rules. +A location section may include most of the server configuration rules +except +.Ic connection , +.Ic listen on , +.Ic location +and +.Ic tcp . +.It Oo Ic no Oc Ic log Op Ar option +Set the specified logging options. +Logging is enabled by default using the standard +.Ic access +and +.Ic error +log files, +but can be changed per server or location. +Use the +.Ic no log +directive to disable logging of any requests. +Valid options are: +.Bl -tag -width Ds +.It Ic access Ar name +Set the +.Ar name +of the access log file relative to the log directory. +If not specified, it defaults to +.Pa access.log . +.It Ic error Ar name +Set the +.Ar name +of the error log file relative to the log directory. +If not specified, it defaults to +.Pa error.log . +.It Ic style Ar style +Set the logging style. +The +.Ar style +can be +.Cm common , +.Cm combined +or +.Cm connection . +The styles +.Cm common +and +.Cm combined +write a log entry after each request similar to the standard Apache +and nginx access log formats. +The style +.Cm connection +writes a summarized log entry after each connection, +that can have multiple requests, +similar to the format that is used by +.Xr relayd 8 . +If not specified, the default is +.Cm common . +.It Oo Ic no Oc Ic syslog +Enable or disable logging to +.Xr syslog 3 +instead of the log files. +.El .It Ic root Ar directory Set the document root of the server. The @@ -125,17 +239,77 @@ root directory of .Nm httpd . If not specified, it defaults to .Pa /htdocs . +.It Ic ssl Ar option +Set the SSL configuration for the server. +These options are only used if SSL has been enabled via the listen directive. +Valid options are: +.Bl -tag -width Ds +.It Ic certificate Ar file +Specify the certificate to use for this server. +The +.Ar file +should contain a PEM encoded certificate. +.It Ic ciphers Ar string +Specify the SSL cipher string. +If not specified, the default value +.Qq HIGH:!aNULL +will be used (strong crypto cipher suites without anonymous DH). +See the CIPHERS section of +.Xr openssl 1 +for information about SSL cipher suites and preference lists. +.It Ic key Ar file +Specify the private key to use for this server. +The +.Ar file +should contain a PEM encoded private key and reside outside of the +.Xr chroot 2 +root directory of +.Nm httpd . +.El +.It Ic tcp Ar option +Enable or disable the specified TCP/IP options; see +.Xr tcp 4 +and +.Xr ip 4 +for more information about the options. +Valid options are: +.Bl -tag -width Ds +.It Ic backlog Ar number +Set the maximum length the queue of pending connections may grow to. +The backlog option is 10 by default and is limited by the +.Va kern.somaxconn +.Xr sysctl 8 +variable. +.It Ic ip minttl Ar number +This option for the underlying IP connection may be used to discard packets +with a TTL lower than the specified value. +This can be used to implement the +Generalized TTL Security Mechanism (GTSM) +according to RFC 5082. +.It Ic ip ttl Ar number +Change the default time-to-live value in the IP headers. +.It Oo Ic no Oc Ic nodelay +Enable the TCP NODELAY option for this connection. +This is recommended to avoid delays in the relayed data stream, +e.g. for SSH connections. +.It Oo Ic no Oc Ic sack +Use selective acknowledgements for this connection. +.It Ic socket buffer Ar number +Set the socket-level buffer size for input and output for this +connection. +This will affect the TCP window size. +.El .El .Sh TYPES Configure the supported media types. -.Nm httpd +.Xr httpd 8 will set the .Ar Content-Type of the response header based on the file extension listed in the .Ic types section. If not specified, -.Nm httpd +.Xr httpd 8 will use built-in media types for .Ar text/css , .Ar text/html , @@ -163,7 +337,7 @@ One or more names can be specified per line. The following example will start one server that is pre-forked two times and listening on the primary IP address of the network interface that is a member of the -.Ar egress +.Qq egress group. It additionally defines some media types overriding the defaults. .Bd -literal -offset indent diff --git a/httpd.h b/httpd.h index df34b47..371dd1c 100644 --- a/httpd.h +++ b/httpd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: httpd.h,v 1.15 2014/07/25 23:30:58 reyk Exp $ */ +/* $OpenBSD: httpd.h,v 1.53 2014/08/13 16:04:28 reyk Exp $ */ /* * Copyright (c) 2006 - 2014 Reyk Floeter @@ -26,6 +26,7 @@ #include /* MAXHOSTNAMELEN */ #include #include +#include #define CONF_FILE "/etc/httpd.conf" #define HTTPD_SOCKET "/var/run/httpd.sock" @@ -33,6 +34,13 @@ #define HTTPD_SERVERNAME "OpenBSD httpd" #define HTTPD_DOCROOT "/htdocs" #define HTTPD_INDEX "index.html" +#define HTTPD_FCGI_SOCKET "/run/slowcgi.sock" +#define HTTPD_LOGROOT "/logs" +#define HTTPD_ACCESS_LOG "access.log" +#define HTTPD_ERROR_LOG "error.log" +#define HTTPD_SSL_CERT "/etc/ssl/server.crt" +#define HTTPD_SSL_KEY "/etc/ssl/private/server.key" +#define HTTPD_SSL_CIPHERS "HIGH:!aNULL" #define FD_RESERVE 5 #define SERVER_MAX_CLIENTS 1024 @@ -41,6 +49,8 @@ #define SERVER_NUMPROC 3 #define SERVER_MAXPROC 32 #define SERVER_MAXHEADERLENGTH 8192 +#define SERVER_MAXREQUESTS 100 /* max requests per connection */ +#define SERVER_MAXREQUESTBODY 1048576 /* 1M */ #define SERVER_BACKLOG 10 #define SERVER_OUTOF_FD_RETRIES 5 @@ -52,19 +62,7 @@ #define CONFIG_SERVERS 0x02 #define CONFIG_ALL 0xff -#define TCPFLAG_NODELAY 0x01 -#define TCPFLAG_NNODELAY 0x02 -#define TCPFLAG_SACK 0x04 -#define TCPFLAG_NSACK 0x08 -#define TCPFLAG_BUFSIZ 0x10 -#define TCPFLAG_IPTTL 0x20 -#define TCPFLAG_IPMINTTL 0x40 -#define TCPFLAG_NSPLICE 0x80 -#define TCPFLAG_DEFAULT 0x00 - -#define TCPFLAG_BITS \ - "\10\01NODELAY\02NO_NODELAY\03SACK\04NO_SACK" \ - "\05SOCKET_BUFFER_SIZE\06IP_TTL\07IP_MINTTL\10NO_SPLICE" +#define FCGI_CONTENT_SIZE 65535 enum httpchunk { TOREAD_UNLIMITED = -1, @@ -189,20 +187,25 @@ enum imsg_type { IMSG_CTL_NOTIFY, IMSG_CTL_END, IMSG_CTL_START, + IMSG_CTL_REOPEN, IMSG_CFG_SERVER, IMSG_CFG_MEDIA, - IMSG_CFG_DONE + IMSG_CFG_DONE, + IMSG_LOG_ACCESS, + IMSG_LOG_ERROR, + IMSG_LOG_OPEN }; enum privsep_procid { PROC_ALL = -1, PROC_PARENT = 0, PROC_SERVER, + PROC_LOGGER, PROC_MAX } privsep_process; /* Attach the control socket to the following process */ -#define PROC_CONTROL PROC_PARENT +#define PROC_CONTROL PROC_LOGGER struct privsep_pipes { int *pp_pipes[PROC_MAX]; @@ -230,6 +233,7 @@ struct privsep { struct event ps_evsigchld; struct event ps_evsighup; struct event ps_evsigpipe; + struct event ps_evsigusr1; int ps_noaction; struct passwd *ps_pw; @@ -250,6 +254,12 @@ struct privsep_proc { struct httpd *p_env; }; +enum fcgistate { + FCGI_READ_HEADER, + FCGI_READ_CONTENT, + FCGI_READ_PADDING +}; + struct client { u_int32_t clt_id; pid_t clt_pid; @@ -262,19 +272,29 @@ struct client { in_port_t clt_port; struct sockaddr_storage clt_ss; struct bufferevent *clt_bev; + char *clt_buf; + size_t clt_buflen; struct evbuffer *clt_output; struct event clt_ev; void *clt_desc; + int clt_sndbufsiz; int clt_fd; - struct bufferevent *clt_file; + struct ressl *clt_ressl_ctx; + struct bufferevent *clt_srvbev; off_t clt_toread; size_t clt_headerlen; - int clt_persist; + u_int clt_persist; int clt_line; int clt_done; + int clt_chunk; int clt_inflight; + enum fcgistate clt_fcgi_state; + int clt_fcgi_toread; + int clt_fcgi_padding_len; + int clt_fcgi_type; + struct evbuffer *clt_srvevb; struct evbuffer *clt_log; struct timeval clt_timeout; @@ -286,15 +306,91 @@ struct client { }; SPLAY_HEAD(client_tree, client); +#define SRVFLAG_INDEX 0x0001 +#define SRVFLAG_NO_INDEX 0x0002 +#define SRVFLAG_AUTO_INDEX 0x0004 +#define SRVFLAG_NO_AUTO_INDEX 0x0008 +#define SRVFLAG_ROOT 0x0010 +#define SRVFLAG_LOCATION 0x0020 +#define SRVFLAG_FCGI 0x0040 +#define SRVFLAG_NO_FCGI 0x0080 +#define SRVFLAG_LOG 0x0100 +#define SRVFLAG_NO_LOG 0x0200 +#define SRVFLAG_SOCKET 0x0400 +#define SRVFLAG_SYSLOG 0x0800 +#define SRVFLAG_NO_SYSLOG 0x1000 +#define SRVFLAG_SSL 0x2000 +#define SRVFLAG_ACCESS_LOG 0x4000 +#define SRVFLAG_ERROR_LOG 0x8000 + +#define SRVFLAG_BITS \ + "\10\01INDEX\02NO_INDEX\03AUTO_INDEX\04NO_AUTO_INDEX" \ + "\05ROOT\06LOCATION\07FCGI\10NO_FCGI\11LOG\12NO_LOG\13SOCKET" \ + "\14SYSLOG\15NO_SYSLOG\16SSL\17ACCESS_LOG\20ERROR_LOG" + +#define TCPFLAG_NODELAY 0x01 +#define TCPFLAG_NNODELAY 0x02 +#define TCPFLAG_SACK 0x04 +#define TCPFLAG_NSACK 0x08 +#define TCPFLAG_BUFSIZ 0x10 +#define TCPFLAG_IPTTL 0x20 +#define TCPFLAG_IPMINTTL 0x40 +#define TCPFLAG_NSPLICE 0x80 +#define TCPFLAG_DEFAULT 0x00 + +#define TCPFLAG_BITS \ + "\10\01NODELAY\02NO_NODELAY\03SACK\04NO_SACK" \ + "\05SOCKET_BUFFER_SIZE\06IP_TTL\07IP_MINTTL\10NO_SPLICE" + +enum log_format { + LOG_FORMAT_COMMON, + LOG_FORMAT_COMBINED, + LOG_FORMAT_CONNECTION +}; + +struct log_file { + char log_name[NAME_MAX]; + int log_fd; + u_int32_t log_id; + TAILQ_ENTRY(log_file) log_entry; +}; +TAILQ_HEAD(log_files, log_file) log_files; + struct server_config { u_int32_t id; - u_int32_t flags; char name[MAXHOSTNAMELEN]; - char docroot[MAXPATHLEN]; + char location[NAME_MAX]; + char index[NAME_MAX]; + char root[MAXPATHLEN]; + char socket[MAXPATHLEN]; + char accesslog[NAME_MAX]; + char errorlog[NAME_MAX]; + in_port_t port; struct sockaddr_storage ss; int prefixlen; struct timeval timeout; + u_int32_t maxrequests; + size_t maxrequestbody; + + char *ssl_cert; + off_t ssl_cert_len; + char *ssl_cert_file; + char ssl_ciphers[NAME_MAX]; + char *ssl_key; + off_t ssl_key_len; + char *ssl_key_file; + + u_int16_t flags; + u_int8_t tcpflags; + int tcpbufsiz; + int tcpbacklog; + u_int8_t tcpipttl; + u_int8_t tcpipminttl; + + enum log_format logformat; + struct log_file *logaccess; + struct log_file *logerror; TAILQ_ENTRY(server_config) entry; }; @@ -305,16 +401,13 @@ struct server { struct server_config srv_conf; struct serverhosts srv_hosts; - u_int8_t srv_tcpflags; - int srv_tcpbufsiz; - int srv_tcpbacklog; - u_int8_t srv_tcpipttl; - u_int8_t srv_tcpipminttl; - int srv_s; struct event srv_ev; struct event srv_evt; + struct ressl *srv_ressl_ctx; + struct ressl_config *srv_ressl_config; + struct client_tree srv_clients; }; TAILQ_HEAD(serverlist, server); @@ -335,6 +428,8 @@ struct httpd { struct event sc_ev; u_int16_t sc_prefork_server; u_int16_t sc_id; + int sc_paused; + char *sc_chroot; struct serverlist *sc_servers; struct mediatypes *sc_mediatypes; @@ -345,9 +440,6 @@ struct httpd { #define HTTPD_OPT_VERBOSE 0x01 #define HTTPD_OPT_NOACTION 0x04 -#define HTTPD_OPT_LOGUPDATE 0x08 -#define HTTPD_OPT_LOGNOTIFY 0x10 -#define HTTPD_OPT_LOGALL 0x18 /* control.c */ int control_init(struct privsep *, struct control_sock *); @@ -368,14 +460,20 @@ int cmdline_symset(char *); /* server.c */ pid_t server(struct privsep *, struct privsep_proc *); +int server_ssl_load_keypair(struct server *); int server_privinit(struct server *); void server_purge(struct server *); int server_socket_af(struct sockaddr_storage *, in_port_t); in_port_t server_socket_getport(struct sockaddr_storage *); +int server_socket_connect(struct sockaddr_storage *, in_port_t, + struct server_config *); void server_write(struct bufferevent *, void *); void server_read(struct bufferevent *, void *); void server_error(struct bufferevent *, short, void *); +void server_log(struct client *, const char *); +void server_sendlog(struct server_config *, int, const char *, ...) + __attribute__((__format__ (printf, 3, 4))); void server_close(struct client *, const char *); void server_dump(struct client *, const void *, size_t); int server_client_cmp(struct client *, struct client *); @@ -389,6 +487,10 @@ int server_bufferevent_write(struct client *, void *, size_t); void server_inflight_dec(struct client *, const char *); struct server * server_byaddr(struct sockaddr *, in_port_t); +struct server_config * + serverconfig_byid(u_int32_t); +int server_foreach(int (*)(struct server *, + struct server_config *, void *), void *); SPLAY_PROTOTYPE(client_tree, client, clt_nodes, server_client_cmp); @@ -405,19 +507,29 @@ const char *server_httperror_byid(u_int); void server_read_httpcontent(struct bufferevent *, void *); void server_read_httpchunks(struct bufferevent *, void *); -int server_writeheader_kv(struct client *, struct kv *); -int server_writeheader_http(struct client *); +int server_writeheader_http(struct client *clt, struct kv *, void *); +int server_headers(struct client *, + int (*)(struct client *, struct kv *, void *), void *); int server_writeresponse_http(struct client *); int server_response_http(struct client *, u_int, struct media_type *, size_t); void server_reset_http(struct client *); void server_close_http(struct client *); int server_response(struct httpd *, struct client *); +struct server_config * + server_getlocation(struct client *, const char *); const char * server_http_host(struct sockaddr_storage *, char *, size_t); +void server_http_date(char *, size_t); +int server_log_http(struct client *, u_int, size_t); /* server_file.c */ int server_file(struct httpd *, struct client *); +void server_file_error(struct bufferevent *, short, void *); + +/* server_fcgi.c */ +int server_fcgi(struct httpd *, struct client *); +int fcgi_add_stdin(struct client *, struct evbuffer *); /* httpd.c */ void event_again(struct event *, int, short, @@ -425,6 +537,7 @@ void event_again(struct event *, int, short, struct timeval *, struct timeval *, void *); const char *canonicalize_host(const char *, char *, size_t); const char *canonicalize_path(const char *, char *, size_t); +size_t path_info(char *); void imsg_event_add(struct imsgev *); int imsg_compose_event(struct imsgev *, u_int16_t, u_int32_t, pid_t, int, void *, u_int16_t); @@ -435,7 +548,7 @@ int sockaddr_cmp(struct sockaddr *, struct sockaddr *, int); struct in6_addr *prefixlen2mask6(u_int8_t, u_int32_t *); u_int32_t prefixlen2mask(u_int8_t); int accept_reserve(int, struct sockaddr *, socklen_t *, int, - volatile int *); + volatile int *); struct kv *kv_add(struct kvtree *, char *, char *); int kv_set(struct kv *, char *, ...); int kv_setkey(struct kv *, char *, ...); @@ -508,4 +621,8 @@ int config_getserver(struct httpd *, struct imsg *); int config_setmedia(struct httpd *, struct media_type *); int config_getmedia(struct httpd *, struct imsg *); +/* logger.c */ +pid_t logger(struct privsep *, struct privsep_proc *); +int logger_open_priv(struct imsg *); + #endif /* _HTTPD_H */ diff --git a/log.c b/log.c index 059cac6..c500d2e 100644 --- a/log.c +++ b/log.c @@ -1,4 +1,4 @@ -/* $OpenBSD: log.c,v 1.1 2014/07/12 23:34:54 reyk Exp $ */ +/* $OpenBSD: log.c,v 1.2 2014/08/04 11:09:25 reyk Exp $ */ /* * Copyright (c) 2003, 2004 Henning Brauer @@ -37,8 +37,6 @@ #include #include -#include - #include "httpd.h" int debug; diff --git a/logger.c b/logger.c new file mode 100644 index 0000000..665db75 --- /dev/null +++ b/logger.c @@ -0,0 +1,310 @@ +/* $OpenBSD: logger.c,v 1.5 2014/08/06 12:56:58 reyk Exp $ */ + +/* + * Copyright (c) 2014 Reyk Floeter + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "httpd.h" + +int logger_dispatch_parent(int, struct privsep_proc *, + struct imsg *); +int logger_dispatch_server(int, struct privsep_proc *, + struct imsg *); +void logger_shutdown(void); +void logger_close(void); +struct log_file *logger_open_file(const char *); +int logger_open_fd(struct imsg *); +int logger_open(struct server *, struct server_config *, void *); +void logger_init(struct privsep *, struct privsep_proc *p, void *); +int logger_start(void); +int logger_log(struct imsg *); + +static struct httpd *env = NULL; +int proc_id; +static u_int32_t last_log_id = 0; + +static struct privsep_proc procs[] = { + { "parent", PROC_PARENT, logger_dispatch_parent }, + { "server", PROC_SERVER, logger_dispatch_server } +}; + +pid_t +logger(struct privsep *ps, struct privsep_proc *p) +{ + env = ps->ps_env; + return (proc_run(ps, p, procs, nitems(procs), logger_init, NULL)); +} + +void +logger_shutdown(void) +{ + logger_close(); + config_purge(env, CONFIG_ALL); +} + +void +logger_init(struct privsep *ps, struct privsep_proc *p, void *arg) +{ + if (config_init(ps->ps_env) == -1) + fatal("failed to initialize configuration"); + + /* Set to current prefork id */ + proc_id = p->p_instance; + + /* We use a custom shutdown callback */ + p->p_shutdown = logger_shutdown; + + TAILQ_INIT(&log_files); +} + +void +logger_close(void) +{ + struct log_file *log, *next; + + TAILQ_FOREACH_SAFE(log, &log_files, log_entry, next) { + if (log->log_fd != -1) { + close(log->log_fd); + log->log_fd = -1; + } + TAILQ_REMOVE(&log_files, log, log_entry); + } +} + +struct log_file * +logger_open_file(const char *name) +{ + struct log_file *log; + struct iovec iov[2]; + + if ((log = calloc(1, sizeof(*log))) == NULL) { + log_warn("failed to allocate log %s", name); + return (NULL); + } + + log->log_id = ++last_log_id; + (void)strlcpy(log->log_name, name, sizeof(log->log_name)); + + /* The file will be opened by the parent process */ + log->log_fd = -1; + + iov[0].iov_base = &log->log_id; + iov[0].iov_len = sizeof(log->log_id); + iov[1].iov_base = log->log_name; + iov[1].iov_len = strlen(log->log_name) + 1; + + proc_composev_imsg(env->sc_ps, PROC_PARENT, -1, IMSG_LOG_OPEN, -1, + iov, 2); + + TAILQ_INSERT_TAIL(&log_files, log, log_entry); + + return (log); +} + +int +logger_open_fd(struct imsg *imsg) +{ + struct log_file *log; + u_int32_t id; + + IMSG_SIZE_CHECK(imsg, &id); + memcpy(&id, imsg->data, sizeof(id)); + + TAILQ_FOREACH(log, &log_files, log_entry) { + if (log->log_id == id) { + DPRINTF("%s: received log fd %d, file %s", + __func__, imsg->fd, log->log_name); + log->log_fd = imsg->fd; + return (0); + } + } + + return (-1); +} + +int +logger_open_priv(struct imsg *imsg) +{ + char path[MAXPATHLEN]; + char name[NAME_MAX], *p; + u_int32_t id; + size_t len; + int fd; + + /* called from the priviled process */ + IMSG_SIZE_CHECK(imsg, &id); + memcpy(&id, imsg->data, sizeof(id)); + p = (char *)imsg->data + sizeof(id); + + if ((size_t)snprintf(name, sizeof(name), "/%s", p) >= sizeof(name)) + return (-1); + if ((len = (size_t)snprintf(path, sizeof(path), "%s%s", + env->sc_chroot, HTTPD_LOGROOT)) >= sizeof(path)) + return (-1); + + p = path + len; + len = sizeof(path) - len; + + if (canonicalize_path(name, p, len) == NULL) { + log_warnx("invalid log name"); + return (-1); + } + + if ((fd = open(path, O_WRONLY|O_APPEND|O_CREAT, 0644)) == -1) { + log_warn("failed to open %s", path); + return (-1); + } + + proc_compose_imsg(env->sc_ps, PROC_LOGGER, -1, IMSG_LOG_OPEN, fd, + &id, sizeof(id)); + + DPRINTF("%s: opened log file %s, fd %d", __func__, path, fd); + + return (0); +} + +int +logger_open(struct server *srv, struct server_config *srv_conf, void *arg) +{ + struct log_file *log, *logfile = NULL, *errfile = NULL; + + /* disassociate */ + srv_conf->logaccess = srv_conf->logerror = NULL; + + TAILQ_FOREACH(log, &log_files, log_entry) { + if (strcmp(log->log_name, srv_conf->accesslog) == 0) + logfile = log; + if (strcmp(log->log_name, srv_conf->errorlog) == 0) + errfile = log; + } + + if (logfile == NULL) { + if ((srv_conf->logaccess = + logger_open_file(srv_conf->accesslog)) == NULL) + return (-1); + } else + srv_conf->logaccess = logfile; + + if (errfile == NULL) { + if ((srv_conf->logerror = + logger_open_file(srv_conf->errorlog)) == NULL) + return (-1); + } else + srv_conf->logerror = errfile; + + return (0); +} + +int +logger_start(void) +{ + logger_close(); + if (server_foreach(logger_open, NULL) == -1) + fatalx("failed to open log files"); + return (0); +} + +int +logger_log(struct imsg *imsg) +{ + char *logline; + u_int32_t id; + struct server_config *srv_conf; + struct log_file *log; + + IMSG_SIZE_CHECK(imsg, &id); + memcpy(&id, imsg->data, sizeof(id)); + + if ((srv_conf = serverconfig_byid(id)) == NULL) + fatalx("invalid logging requestr"); + + if (imsg->hdr.type == IMSG_LOG_ACCESS) + log = srv_conf->logaccess; + else + log = srv_conf->logerror; + + if (log == NULL || log->log_fd == -1) { + log_warnx("log file %s not opened", log ? log->log_name : ""); + return (0); + } + + /* XXX get_string() would sanitize the string, but add a malloc */ + logline = (char *)imsg->data + sizeof(id); + + /* For debug output */ + log_debug("%s", logline); + + if (dprintf(log->log_fd, "%s\n", logline) == -1) { + if (logger_start() == -1) + return (-1); + } + + return (0); +} + +int +logger_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg) +{ + switch (imsg->hdr.type) { + case IMSG_CFG_SERVER: + config_getserver(env, imsg); + break; + case IMSG_CFG_DONE: + config_getcfg(env, imsg); + break; + case IMSG_CTL_START: + case IMSG_CTL_REOPEN: + logger_start(); + break; + case IMSG_CTL_RESET: + config_getreset(env, imsg); + break; + case IMSG_LOG_OPEN: + return (logger_open_fd(imsg)); + default: + return (-1); + } + + return (0); +} + +int +logger_dispatch_server(int fd, struct privsep_proc *p, struct imsg *imsg) +{ + switch (imsg->hdr.type) { + case IMSG_LOG_ACCESS: + case IMSG_LOG_ERROR: + logger_log(imsg); + break; + default: + return (-1); + } + + return (0); +} diff --git a/parse.y b/parse.y index 1105e55..d66f207 100644 --- a/parse.y +++ b/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.7 2014/07/25 17:04:47 reyk Exp $ */ +/* $OpenBSD: parse.y,v 1.35 2014/08/09 07:35:45 reyk Exp $ */ /* * Copyright (c) 2007 - 2014 Reyk Floeter @@ -53,8 +53,6 @@ #include #include -#include - #include "httpd.h" #include "http.h" @@ -94,7 +92,8 @@ static int errors = 0; static int loadcfg = 0; uint32_t last_server_id = 0; -static struct server *srv = NULL; +static struct server *srv = NULL, *parentsrv = NULL; +static struct server_config *srv_conf = NULL; struct serverlist servers; struct media_type media; @@ -126,12 +125,16 @@ typedef struct { %} -%token ALL PORT LISTEN PREFORK ROOT SERVER ERROR LOG VERBOSE ON TYPES -%token UPDATES INCLUDE +%token ACCESS AUTO BACKLOG BODY BUFFER CERTIFICATE CHROOT CIPHERS COMMON +%token COMBINED CONNECTION DIRECTORY ERR FCGI INDEX IP KEY LISTEN LOCATION +%token LOG MAXIMUM NO NODELAY ON PORT PREFORK REQUEST REQUESTS ROOT SACK +%token SERVER SOCKET SSL STYLE SYSLOG TCP TIMEOUT TYPES +%token ERROR INCLUDE %token STRING %token NUMBER -%type loglevel %type port +%type optssl +%type timeout %% @@ -168,12 +171,11 @@ varset : STRING '=' STRING { } ; -main : LOG loglevel { - if (loadcfg) - break; - conf->sc_opts |= $2; - } - | PREFORK NUMBER { +optssl : /*empty*/ { $$ = 0; } + | SSL { $$ = 1; } + ; + +main : PREFORK NUMBER { if (loadcfg) break; if ($2 <= 0 || $2 > SERVER_MAXPROC) { @@ -183,6 +185,9 @@ main : LOG loglevel { } conf->sc_prefork_server = $2; } + | CHROOT STRING { + conf->sc_chroot = $2; + } ; server : SERVER STRING { @@ -215,10 +220,28 @@ server : SERVER STRING { } free($2); - strlcpy(s->srv_conf.docroot, HTTPD_DOCROOT, - sizeof(s->srv_conf.docroot)); + strlcpy(s->srv_conf.root, HTTPD_DOCROOT, + sizeof(s->srv_conf.root)); + strlcpy(s->srv_conf.index, HTTPD_INDEX, + sizeof(s->srv_conf.index)); + strlcpy(s->srv_conf.accesslog, HTTPD_ACCESS_LOG, + sizeof(s->srv_conf.accesslog)); + strlcpy(s->srv_conf.errorlog, HTTPD_ERROR_LOG, + sizeof(s->srv_conf.errorlog)); s->srv_conf.id = ++last_server_id; s->srv_conf.timeout.tv_sec = SERVER_TIMEOUT; + s->srv_conf.maxrequests = SERVER_MAXREQUESTS; + s->srv_conf.maxrequestbody = SERVER_MAXREQUESTBODY; + s->srv_conf.flags |= SRVFLAG_LOG; + s->srv_conf.logformat = LOG_FORMAT_COMMON; + if ((s->srv_conf.ssl_cert_file = + strdup(HTTPD_SSL_CERT)) == NULL) + fatal("out of memory"); + if ((s->srv_conf.ssl_key_file = + strdup(HTTPD_SSL_KEY)) == NULL) + fatal("out of memory"); + strlcpy(s->srv_conf.ssl_ciphers, HTTPD_SSL_CIPHERS, + sizeof(s->srv_conf.ssl_ciphers)); if (last_server_id == INT_MAX) { yyerror("too many servers defined"); @@ -226,9 +249,23 @@ server : SERVER STRING { YYERROR; } srv = s; - } '{' optnl serveropts_l '}' { + srv_conf = &srv->srv_conf; + SPLAY_INIT(&srv->srv_clients); TAILQ_INSERT_TAIL(conf->sc_servers, srv, srv_entry); + } '{' optnl serveropts_l '}' { + if (srv->srv_conf.ss.ss_family == AF_UNSPEC) { + yyerror("listen address not specified"); + free($2); + YYERROR; + } + if (server_ssl_load_keypair(srv) == -1) { + yyerror("failed to load public/private keys " + "for server %s", srv->srv_conf.name); + YYERROR; + } + srv = NULL; + srv_conf = NULL; } ; @@ -236,25 +273,31 @@ serveropts_l : serveropts_l serveroptsl nl | serveroptsl optnl ; -serveroptsl : LISTEN ON STRING port { +serveroptsl : LISTEN ON STRING optssl port { struct addresslist al; struct address *h; struct server *s; + if (parentsrv != NULL) { + yyerror("listen %s inside location", $3); + free($3); + YYERROR; + } + if (srv->srv_conf.ss.ss_family != AF_UNSPEC) { yyerror("listen address already specified"); free($3); YYERROR; } else s = srv; - if ($4.op != PF_OP_EQ) { + if ($5.op != PF_OP_EQ) { yyerror("invalid port"); free($3); YYERROR; } TAILQ_INIT(&al); - if (host($3, &al, 1, &$4, NULL, -1) <= 0) { + if (host($3, &al, 1, &$5, NULL, -1) <= 0) { yyerror("invalid listen ip: %s", $3); free($3); YYERROR; @@ -266,16 +309,350 @@ serveroptsl : LISTEN ON STRING port { s->srv_conf.port = h->port.val[0]; s->srv_conf.prefixlen = h->prefixlen; host_free(&al); + + if ($4) { + s->srv_conf.flags |= SRVFLAG_SSL; + } } + | TCP { + if (parentsrv != NULL) { + yyerror("tcp flags inside location"); + YYERROR; + } + } tcpip + | CONNECTION { + if (parentsrv != NULL) { + yyerror("connection options inside location"); + YYERROR; + } + } connection + | SSL { + if (parentsrv != NULL) { + yyerror("ssl configuration inside location"); + YYERROR; + } + } ssl | ROOT STRING { - if (strlcpy(srv->srv_conf.docroot, $2, - sizeof(srv->srv_conf.docroot)) >= - sizeof(srv->srv_conf.docroot)) { + if (strlcpy(srv->srv_conf.root, $2, + sizeof(srv->srv_conf.root)) >= + sizeof(srv->srv_conf.root)) { yyerror("document root too long"); free($2); YYERROR; } free($2); + srv->srv_conf.flags |= SRVFLAG_ROOT; + } + | DIRECTORY dirflags + | DIRECTORY '{' dirflags_l '}' + | logformat + | fastcgi + | LOCATION STRING { + struct server *s; + + if (srv->srv_conf.ss.ss_family == AF_UNSPEC) { + yyerror("listen address not specified"); + free($2); + YYERROR; + } + + if (parentsrv != NULL) { + yyerror("location %s inside location", $2); + free($2); + YYERROR; + } + + if (!loadcfg) { + free($2); + YYACCEPT; + } + + TAILQ_FOREACH(s, conf->sc_servers, srv_entry) + if (strcmp(s->srv_conf.name, + srv->srv_conf.name) == 0 && + strcmp(s->srv_conf.location, $2) == 0) + break; + if (s != NULL) { + yyerror("location %s defined twice", $2); + free($2); + YYERROR; + } + + if ((s = calloc(1, sizeof (*s))) == NULL) + fatal("out of memory"); + + if (strlcpy(s->srv_conf.location, $2, + sizeof(s->srv_conf.location)) >= + sizeof(s->srv_conf.location)) { + yyerror("server location truncated"); + free($2); + free(s); + YYERROR; + } + free($2); + + if (strlcpy(s->srv_conf.name, srv->srv_conf.name, + sizeof(s->srv_conf.name)) >= + sizeof(s->srv_conf.name)) { + yyerror("server name truncated"); + free(s); + YYERROR; + } + + /* A location entry uses the parent id */ + s->srv_conf.id = srv->srv_conf.id; + s->srv_conf.flags = SRVFLAG_LOCATION; + memcpy(&s->srv_conf.ss, &srv->srv_conf.ss, + sizeof(s->srv_conf.ss)); + s->srv_conf.port = srv->srv_conf.port; + s->srv_conf.prefixlen = srv->srv_conf.prefixlen; + + if (last_server_id == INT_MAX) { + yyerror("too many servers/locations defined"); + free(s); + YYERROR; + } + parentsrv = srv; + srv = s; + srv_conf = &srv->srv_conf; + SPLAY_INIT(&srv->srv_clients); + TAILQ_INSERT_TAIL(conf->sc_servers, srv, srv_entry); + } '{' optnl serveropts_l '}' { + srv = parentsrv; + srv_conf = &parentsrv->srv_conf; + parentsrv = NULL; + } + ; + +fastcgi : NO FCGI { + srv_conf->flags &= ~SRVFLAG_FCGI; + srv_conf->flags |= SRVFLAG_NO_FCGI; + } + | FCGI { + srv_conf->flags &= ~SRVFLAG_NO_FCGI; + srv_conf->flags |= SRVFLAG_FCGI; + } + | FCGI { + srv_conf->flags &= ~SRVFLAG_NO_FCGI; + srv_conf->flags |= SRVFLAG_FCGI; + } '{' fcgiflags_l '}' + | FCGI { + srv_conf->flags &= ~SRVFLAG_NO_FCGI; + srv_conf->flags |= SRVFLAG_FCGI; + } fcgiflags + ; + +fcgiflags_l : fcgiflags comma fcgiflags_l + | fcgiflags + ; + +fcgiflags : SOCKET STRING { + if (strlcpy(srv_conf->socket, $2, + sizeof(srv_conf->socket)) >= + sizeof(srv_conf->socket)) { + yyerror("fastcgi socket too long"); + free($2); + YYERROR; + } + free($2); + srv_conf->flags |= SRVFLAG_SOCKET; + } + ; + +connection : '{' conflags_l '}' + | conflags + ; + +conflags_l : conflags comma conflags_l + | conflags + ; + +conflags : TIMEOUT timeout { + memcpy(&srv_conf->timeout, &$2, + sizeof(struct timeval)); + } + | MAXIMUM REQUESTS NUMBER { + srv_conf->maxrequests = $3; + } + | MAXIMUM REQUEST BODY NUMBER { + srv_conf->maxrequestbody = $4; + } + ; + +ssl : '{' sslopts_l '}' + | sslopts + ; + +sslopts_l : sslopts comma sslopts_l + | sslopts + ; + +sslopts : CERTIFICATE STRING { + free(srv_conf->ssl_cert_file); + if ((srv_conf->ssl_cert_file = strdup($2)) == NULL) + fatal("out of memory"); + free($2); + } + | KEY STRING { + free(srv_conf->ssl_key_file); + if ((srv_conf->ssl_key_file = strdup($2)) == NULL) + fatal("out of memory"); + free($2); + } + | CIPHERS STRING { + if (strlcpy(srv_conf->ssl_ciphers, $2, + sizeof(srv_conf->ssl_ciphers)) >= + sizeof(srv_conf->ssl_ciphers)) { + yyerror("ciphers too long"); + free($2); + YYERROR; + } + free($2); + } + ; + +dirflags_l : dirflags comma dirflags_l + | dirflags + ; + +dirflags : INDEX STRING { + if (strlcpy(srv_conf->index, $2, + sizeof(srv_conf->index)) >= + sizeof(srv_conf->index)) { + yyerror("index file too long"); + free($2); + YYERROR; + } + srv_conf->flags &= ~SRVFLAG_NO_INDEX; + srv_conf->flags |= SRVFLAG_INDEX; + free($2); + } + | NO INDEX { + srv_conf->flags &= ~SRVFLAG_INDEX; + srv_conf->flags |= SRVFLAG_NO_INDEX; + } + | AUTO INDEX { + srv_conf->flags &= ~SRVFLAG_NO_AUTO_INDEX; + srv_conf->flags |= SRVFLAG_AUTO_INDEX; + } + | NO AUTO INDEX { + srv_conf->flags &= ~SRVFLAG_AUTO_INDEX; + srv_conf->flags |= SRVFLAG_NO_AUTO_INDEX; + } + ; + + +logformat : LOG logflags + | LOG '{' logflags_l '}' + | NO LOG { + srv_conf->flags &= ~SRVFLAG_LOG; + srv_conf->flags |= SRVFLAG_NO_LOG; + } + ; + +logflags_l : logflags comma logflags_l + | logflags + ; + + +logflags : STYLE logstyle + | SYSLOG { + srv_conf->flags &= ~SRVFLAG_NO_SYSLOG; + srv_conf->flags |= SRVFLAG_SYSLOG; + } + | NO SYSLOG { + srv_conf->flags &= ~SRVFLAG_SYSLOG; + srv_conf->flags |= SRVFLAG_NO_SYSLOG; + } + | ACCESS STRING { + if (strlcpy(srv_conf->accesslog, $2, + sizeof(srv_conf->accesslog)) >= + sizeof(srv_conf->accesslog)) { + yyerror("access log name too long"); + free($2); + YYERROR; + } + free($2); + srv_conf->flags |= SRVFLAG_ACCESS_LOG; + } + | ERR STRING { + if (strlcpy(srv_conf->errorlog, $2, + sizeof(srv_conf->errorlog)) >= + sizeof(srv_conf->errorlog)) { + yyerror("error log name too long"); + free($2); + YYERROR; + } + free($2); + srv_conf->flags |= SRVFLAG_ERROR_LOG; + } + ; + +logstyle : COMMON { + srv_conf->flags &= ~SRVFLAG_NO_LOG; + srv_conf->flags |= SRVFLAG_LOG; + srv_conf->logformat = LOG_FORMAT_COMMON; + } + | COMBINED { + srv_conf->flags &= ~SRVFLAG_NO_LOG; + srv_conf->flags |= SRVFLAG_LOG; + srv_conf->logformat = LOG_FORMAT_COMBINED; + } + | CONNECTION { + srv_conf->flags &= ~SRVFLAG_NO_LOG; + srv_conf->flags |= SRVFLAG_LOG; + srv_conf->logformat = LOG_FORMAT_CONNECTION; + } + ; + +tcpip : '{' tcpflags_l '}' + | tcpflags + ; + +tcpflags_l : tcpflags comma tcpflags_l + | tcpflags + ; + +tcpflags : SACK { srv_conf->tcpflags |= TCPFLAG_SACK; } + | NO SACK { srv_conf->tcpflags |= TCPFLAG_NSACK; } + | NODELAY { + srv_conf->tcpflags |= TCPFLAG_NODELAY; + } + | NO NODELAY { + srv_conf->tcpflags |= TCPFLAG_NNODELAY; + } + | BACKLOG NUMBER { + if ($2 < 0 || $2 > SERVER_MAX_CLIENTS) { + yyerror("invalid backlog: %d", $2); + YYERROR; + } + srv_conf->tcpbacklog = $2; + } + | SOCKET BUFFER NUMBER { + srv_conf->tcpflags |= TCPFLAG_BUFSIZ; + if ((srv_conf->tcpbufsiz = $3) < 0) { + yyerror("invalid socket buffer size: %d", $3); + YYERROR; + } + } + | IP STRING NUMBER { + if ($3 < 0) { + yyerror("invalid ttl: %d", $3); + free($2); + YYERROR; + } + if (strcasecmp("ttl", $2) == 0) { + srv_conf->tcpflags |= TCPFLAG_IPTTL; + srv_conf->tcpipttl = $3; + } else if (strcasecmp("minttl", $2) == 0) { + srv_conf->tcpflags |= TCPFLAG_IPMINTTL; + srv_conf->tcpipminttl = $3; + } else { + yyerror("invalid TCP/IP flag: %s", $2); + free($2); + YYERROR; + } + free($2); } ; @@ -286,7 +663,7 @@ mediaopts_l : mediaopts_l mediaoptsl nl | mediaoptsl optnl ; -mediaoptsl : STRING '/' STRING { +mediaoptsl : STRING '/' STRING { if (strlcpy(media.media_type, $1, sizeof(media.media_type)) >= sizeof(media.media_type) || @@ -317,6 +694,9 @@ medianamesl : STRING { } free($1); + if (!loadcfg) + break; + if (media_add(conf->sc_mediatypes, &media) == NULL) { yyerror("failed to add media type"); YYERROR; @@ -360,8 +740,20 @@ port : PORT STRING { } ; -loglevel : UPDATES { $$ = HTTPD_OPT_LOGUPDATE; } - | ALL { $$ = HTTPD_OPT_LOGALL; } +timeout : NUMBER + { + if ($1 < 0) { + yyerror("invalid timeout: %d\n", $1); + YYERROR; + } + $$.tv_sec = $1; + $$.tv_usec = 0; + } + ; + +comma : ',' + | nl + | /* empty */ ; optnl : '\n' optnl @@ -405,17 +797,45 @@ lookup(char *s) { /* this has to be sorted always */ static const struct keywords keywords[] = { - { "all", ALL }, + { "access", ACCESS }, + { "auto", AUTO }, + { "backlog", BACKLOG }, + { "body", BODY }, + { "buffer", BUFFER }, + { "certificate", CERTIFICATE }, + { "chroot", CHROOT }, + { "ciphers", CIPHERS }, + { "combined", COMBINED }, + { "common", COMMON }, + { "connection", CONNECTION }, + { "directory", DIRECTORY }, + { "error", ERR }, + { "fastcgi", FCGI }, { "include", INCLUDE }, + { "index", INDEX }, + { "ip", IP }, + { "key", KEY }, { "listen", LISTEN }, + { "location", LOCATION }, { "log", LOG }, + { "max", MAXIMUM }, + { "no", NO }, + { "nodelay", NODELAY }, { "on", ON }, { "port", PORT }, { "prefork", PREFORK }, + { "request", REQUEST }, + { "requests", REQUESTS }, { "root", ROOT }, + { "sack", SACK }, { "server", SERVER }, - { "types", TYPES }, - { "updates", UPDATES } + { "socket", SOCKET }, + { "ssl", SSL }, + { "style", STYLE }, + { "syslog", SYSLOG }, + { "tcp", TCP }, + { "timeout", TIMEOUT }, + { "types", TYPES } }; const struct keywords *p; @@ -841,7 +1261,7 @@ load_config(const char *filename, struct httpd *x_conf) m.media_name); errors++; } - } + } } return (errors ? -1 : 0); diff --git a/proc.c b/proc.c index 7a6d571..25feff1 100644 --- a/proc.c +++ b/proc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: proc.c,v 1.1 2014/07/12 23:34:54 reyk Exp $ */ +/* $OpenBSD: proc.c,v 1.4 2014/08/04 15:49:28 reyk Exp $ */ /* * Copyright (c) 2010 - 2014 Reyk Floeter @@ -38,8 +38,6 @@ #include #include -#include - #include "httpd.h" void proc_open(struct privsep *, struct privsep_proc *, @@ -163,7 +161,7 @@ proc_open(struct privsep *ps, struct privsep_proc *p, /* * Open socket pairs for our peers - */ + */ for (proc = 0; proc < nproc; proc++) { procs[proc].p_ps = ps; procs[proc].p_env = ps->ps_env; @@ -323,6 +321,7 @@ proc_sig_handler(int sig, short event, void *arg) case SIGCHLD: case SIGHUP: case SIGPIPE: + case SIGUSR1: /* ignore */ break; default: @@ -409,12 +408,14 @@ proc_run(struct privsep *ps, struct privsep_proc *p, signal_set(&ps->ps_evsigchld, SIGCHLD, proc_sig_handler, p); signal_set(&ps->ps_evsighup, SIGHUP, proc_sig_handler, p); signal_set(&ps->ps_evsigpipe, SIGPIPE, proc_sig_handler, p); + signal_set(&ps->ps_evsigusr1, SIGUSR1, proc_sig_handler, p); signal_add(&ps->ps_evsigint, NULL); signal_add(&ps->ps_evsigterm, NULL); signal_add(&ps->ps_evsigchld, NULL); signal_add(&ps->ps_evsighup, NULL); signal_add(&ps->ps_evsigpipe, NULL); + signal_add(&ps->ps_evsigusr1, NULL); proc_listen(ps, procs, nproc); diff --git a/server.c b/server.c index 3ef1401..56f363f 100644 --- a/server.c +++ b/server.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server.c,v 1.13 2014/07/25 23:30:58 reyk Exp $ */ +/* $OpenBSD: server.c,v 1.39 2014/08/06 18:38:11 reyk Exp $ */ /* * Copyright (c) 2006 - 2014 Reyk Floeter @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -36,30 +37,36 @@ #include #include #include +#include #include #include #include #include #include #include - -#include -#include +#include #include "httpd.h" int server_dispatch_parent(int, struct privsep_proc *, struct imsg *); +int server_dispatch_logger(int, struct privsep_proc *, + struct imsg *); void server_shutdown(void); void server_init(struct privsep *, struct privsep_proc *p, void *); void server_launch(void); int server_socket(struct sockaddr_storage *, in_port_t, - struct server *, int, int); + struct server_config *, int, int); int server_socket_listen(struct sockaddr_storage *, in_port_t, - struct server *); + struct server_config *); + +int server_ssl_init(struct server *); +void server_ssl_readcb(int, short, void *); +void server_ssl_writecb(int, short, void *); void server_accept(int, short, void *); +void server_accept_ssl(int, short, void *); void server_input(struct client *); extern void bufferevent_read_pressure_cb(struct evbuffer *, size_t, @@ -73,7 +80,8 @@ static struct httpd *env = NULL; int proc_id; static struct privsep_proc procs[] = { - { "parent", PROC_PARENT, server_dispatch_parent } + { "parent", PROC_PARENT, server_dispatch_parent }, + { "logger", PROC_LOGGER, server_dispatch_logger } }; pid_t @@ -96,11 +104,112 @@ server_shutdown(void) int server_privinit(struct server *srv) { + if (srv->srv_conf.flags & SRVFLAG_LOCATION) + return (0); + log_debug("%s: adding server %s", __func__, srv->srv_conf.name); if ((srv->srv_s = server_socket_listen(&srv->srv_conf.ss, - srv->srv_conf.port, srv)) == -1) + srv->srv_conf.port, &srv->srv_conf)) == -1) + return (-1); + + return (0); +} + +static char * +server_load_file(const char *filename, off_t *len) +{ + struct stat st; + off_t size; + char *buf = NULL; + int fd; + + if ((fd = open(filename, O_RDONLY)) == -1) + return (NULL); + if (fstat(fd, &st) != 0) + goto fail; + size = st.st_size; + if ((buf = calloc(1, size + 1)) == NULL) + goto fail; + if (read(fd, buf, size) != size) + goto fail; + + close(fd); + + *len = size; + return (buf); + + fail: + free(buf); + close(fd); + + return (NULL); +} + +int +server_ssl_load_keypair(struct server *srv) +{ + if ((srv->srv_conf.flags & SRVFLAG_SSL) == 0) + return (0); + + if ((srv->srv_conf.ssl_cert = server_load_file( + srv->srv_conf.ssl_cert_file, &srv->srv_conf.ssl_cert_len)) == NULL) + return (-1); + log_debug("%s: using certificate %s", __func__, + srv->srv_conf.ssl_cert_file); + + if ((srv->srv_conf.ssl_key = server_load_file( + srv->srv_conf.ssl_key_file, &srv->srv_conf.ssl_key_len)) == NULL) return (-1); + log_debug("%s: using private key %s", __func__, + srv->srv_conf.ssl_key_file); + + return (0); +} + +int +server_ssl_init(struct server *srv) +{ + if ((srv->srv_conf.flags & SRVFLAG_SSL) == 0) + return (0); + + log_debug("%s: setting up SSL for %s", __func__, srv->srv_conf.name); + + if (ressl_init() != 0) { + log_warn("%s: failed to initialise ressl", __func__); + return (-1); + } + if ((srv->srv_ressl_config = ressl_config_new()) == NULL) { + log_warn("%s: failed to get ressl config", __func__); + return (-1); + } + if ((srv->srv_ressl_ctx = ressl_server()) == NULL) { + log_warn("%s: failed to get ressl server", __func__); + return (-1); + } + + ressl_config_set_ciphers(srv->srv_ressl_config, + srv->srv_conf.ssl_ciphers); + ressl_config_set_cert_mem(srv->srv_ressl_config, + srv->srv_conf.ssl_cert, srv->srv_conf.ssl_cert_len); + ressl_config_set_key_mem(srv->srv_ressl_config, + srv->srv_conf.ssl_key, srv->srv_conf.ssl_key_len); + + if (ressl_configure(srv->srv_ressl_ctx, srv->srv_ressl_config) != 0) { + log_warn("%s: failed to configure SSL - %s", __func__, + ressl_error(srv->srv_ressl_ctx)); + return (-1); + } + + /* We're now done with the public/private key... */ + explicit_bzero(srv->srv_conf.ssl_cert, srv->srv_conf.ssl_cert_len); + explicit_bzero(srv->srv_conf.ssl_key, srv->srv_conf.ssl_key_len); + free(srv->srv_conf.ssl_cert); + free(srv->srv_conf.ssl_key); + srv->srv_conf.ssl_cert = NULL; + srv->srv_conf.ssl_key = NULL; + srv->srv_conf.ssl_cert_len = 0; + srv->srv_conf.ssl_key_len = 0; return (0); } @@ -136,6 +245,7 @@ server_launch(void) struct server *srv; TAILQ_FOREACH(srv, env->sc_servers, srv_entry) { + server_ssl_init(srv); server_http_init(srv); log_debug("%s: running server %s", __func__, @@ -174,10 +284,16 @@ server_purge(struct server *srv) TAILQ_REMOVE(&srv->srv_hosts, srv_conf, entry); /* It might point to our own "default" entry */ - if (srv_conf != &srv->srv_conf) + if (srv_conf != &srv->srv_conf) { + free(srv_conf->ssl_cert); + free(srv_conf->ssl_key); free(srv_conf); + } } + ressl_config_free(srv->srv_ressl_config); + ressl_free(srv->srv_ressl_ctx); + free(srv); } @@ -196,6 +312,43 @@ server_byaddr(struct sockaddr *addr, in_port_t port) return (NULL); } +struct server_config * +serverconfig_byid(u_int32_t id) +{ + struct server *srv; + struct server_config *srv_conf; + + TAILQ_FOREACH(srv, env->sc_servers, srv_entry) { + if (srv->srv_conf.id == id) + return (&srv->srv_conf); + TAILQ_FOREACH(srv_conf, &srv->srv_hosts, entry) { + if (srv_conf->id == id) + return (srv_conf); + } + } + + return (NULL); +} + +int +server_foreach(int (*srv_cb)(struct server *, + struct server_config *, void *), void *arg) +{ + struct server *srv; + struct server_config *srv_conf; + + TAILQ_FOREACH(srv, env->sc_servers, srv_entry) { + if ((srv_cb)(srv, &srv->srv_conf, arg) == -1) + return (-1); + TAILQ_FOREACH(srv_conf, &srv->srv_hosts, entry) { + if ((srv_cb)(srv, srv_conf, arg) == -1) + return (-1); + } + } + + return (0); +} + int server_socket_af(struct sockaddr_storage *ss, in_port_t port) { @@ -235,7 +388,7 @@ server_socket_getport(struct sockaddr_storage *ss) int server_socket(struct sockaddr_storage *ss, in_port_t port, - struct server *srv, int fd, int reuseport) + struct server_config *srv_conf, int fd, int reuseport) { struct linger lng; int s = -1, val; @@ -256,17 +409,17 @@ server_socket(struct sockaddr_storage *ss, in_port_t port, if (reuseport) { val = 1; if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &val, - sizeof(int)) == -1) + sizeof(int)) == -1) goto bad; } if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) goto bad; - if (srv->srv_tcpflags & TCPFLAG_BUFSIZ) { - val = srv->srv_tcpbufsiz; + if (srv_conf->tcpflags & TCPFLAG_BUFSIZ) { + val = srv_conf->tcpbufsiz; if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val)) == -1) goto bad; - val = srv->srv_tcpbufsiz; + val = srv_conf->tcpbufsiz; if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &val, sizeof(val)) == -1) goto bad; @@ -275,14 +428,14 @@ server_socket(struct sockaddr_storage *ss, in_port_t port, /* * IP options */ - if (srv->srv_tcpflags & TCPFLAG_IPTTL) { - val = (int)srv->srv_tcpipttl; + if (srv_conf->tcpflags & TCPFLAG_IPTTL) { + val = (int)srv_conf->tcpipttl; if (setsockopt(s, IPPROTO_IP, IP_TTL, &val, sizeof(val)) == -1) goto bad; } - if (srv->srv_tcpflags & TCPFLAG_IPMINTTL) { - val = (int)srv->srv_tcpipminttl; + if (srv_conf->tcpflags & TCPFLAG_IPMINTTL) { + val = (int)srv_conf->tcpipminttl; if (setsockopt(s, IPPROTO_IP, IP_MINTTL, &val, sizeof(val)) == -1) goto bad; @@ -291,8 +444,8 @@ server_socket(struct sockaddr_storage *ss, in_port_t port, /* * TCP options */ - if (srv->srv_tcpflags & (TCPFLAG_NODELAY|TCPFLAG_NNODELAY)) { - if (srv->srv_tcpflags & TCPFLAG_NNODELAY) + if (srv_conf->tcpflags & (TCPFLAG_NODELAY|TCPFLAG_NNODELAY)) { + if (srv_conf->tcpflags & TCPFLAG_NNODELAY) val = 0; else val = 1; @@ -300,8 +453,8 @@ server_socket(struct sockaddr_storage *ss, in_port_t port, &val, sizeof(val)) == -1) goto bad; } - if (srv->srv_tcpflags & (TCPFLAG_SACK|TCPFLAG_NSACK)) { - if (srv->srv_tcpflags & TCPFLAG_NSACK) + if (srv_conf->tcpflags & (TCPFLAG_SACK|TCPFLAG_NSACK)) { + if (srv_conf->tcpflags & TCPFLAG_NSACK) val = 0; else val = 1; @@ -320,16 +473,16 @@ server_socket(struct sockaddr_storage *ss, in_port_t port, int server_socket_listen(struct sockaddr_storage *ss, in_port_t port, - struct server *srv) + struct server_config *srv_conf) { int s; - if ((s = server_socket(ss, port, srv, -1, 1)) == -1) + if ((s = server_socket(ss, port, srv_conf, -1, 1)) == -1) return (-1); if (bind(s, (struct sockaddr *)ss, ss->ss_len) == -1) goto bad; - if (listen(s, srv->srv_tcpbacklog) == -1) + if (listen(s, srv_conf->tcpbacklog) == -1) goto bad; return (s); @@ -339,22 +492,168 @@ server_socket_listen(struct sockaddr_storage *ss, in_port_t port, return (-1); } +int +server_socket_connect(struct sockaddr_storage *ss, in_port_t port, + struct server_config *srv_conf) +{ + int s; + + if ((s = server_socket(ss, port, srv_conf, -1, 0)) == -1) + return (-1); + + if (connect(s, (struct sockaddr *)ss, ss->ss_len) == -1) { + if (errno != EINPROGRESS) + goto bad; + } + + return (s); + + bad: + close(s); + return (-1); +} + +void +server_ssl_readcb(int fd, short event, void *arg) +{ + struct bufferevent *bufev = arg; + struct client *clt = bufev->cbarg; + char rbuf[IBUF_READ_SIZE]; + int what = EVBUFFER_READ; + int howmuch = IBUF_READ_SIZE; + int ret; + size_t len; + + if (event == EV_TIMEOUT) { + what |= EVBUFFER_TIMEOUT; + goto err; + } + + if (bufev->wm_read.high != 0) + howmuch = MIN(sizeof(rbuf), bufev->wm_read.high); + + ret = ressl_read(clt->clt_ressl_ctx, rbuf, howmuch, &len); + if (ret == RESSL_READ_AGAIN || ret == RESSL_WRITE_AGAIN) { + goto retry; + } else if (ret != 0) { + what |= EVBUFFER_ERROR; + goto err; + } + + if (evbuffer_add(bufev->input, rbuf, len) == -1) { + what |= EVBUFFER_ERROR; + goto err; + } + + server_bufferevent_add(&bufev->ev_read, bufev->timeout_read); + + len = EVBUFFER_LENGTH(bufev->input); + if (bufev->wm_read.low != 0 && len < bufev->wm_read.low) + return; + if (bufev->wm_read.high != 0 && len > bufev->wm_read.high) { + struct evbuffer *buf = bufev->input; + event_del(&bufev->ev_read); + evbuffer_setcb(buf, bufferevent_read_pressure_cb, bufev); + return; + } + + if (bufev->readcb != NULL) + (*bufev->readcb)(bufev, bufev->cbarg); + return; + + retry: + server_bufferevent_add(&bufev->ev_read, bufev->timeout_read); + return; + + err: + (*bufev->errorcb)(bufev, what, bufev->cbarg); +} + +void +server_ssl_writecb(int fd, short event, void *arg) +{ + struct bufferevent *bufev = arg; + struct client *clt = bufev->cbarg; + int ret; + short what = EVBUFFER_WRITE; + size_t len; + + if (event == EV_TIMEOUT) { + what |= EVBUFFER_TIMEOUT; + goto err; + } + + if (EVBUFFER_LENGTH(bufev->output)) { + if (clt->clt_buf == NULL) { + clt->clt_buflen = EVBUFFER_LENGTH(bufev->output); + if ((clt->clt_buf = malloc(clt->clt_buflen)) == NULL) { + what |= EVBUFFER_ERROR; + goto err; + } + bcopy(EVBUFFER_DATA(bufev->output), + clt->clt_buf, clt->clt_buflen); + } + ret = ressl_write(clt->clt_ressl_ctx, clt->clt_buf, + clt->clt_buflen, &len); + if (ret == RESSL_READ_AGAIN || ret == RESSL_WRITE_AGAIN) { + goto retry; + } else if (ret != 0) { + what |= EVBUFFER_ERROR; + goto err; + } + evbuffer_drain(bufev->output, len); + } + if (clt->clt_buf != NULL) { + free(clt->clt_buf); + clt->clt_buf = NULL; + clt->clt_buflen = 0; + } + + if (EVBUFFER_LENGTH(bufev->output) != 0) + server_bufferevent_add(&bufev->ev_write, bufev->timeout_write); + + if (bufev->writecb != NULL && + EVBUFFER_LENGTH(bufev->output) <= bufev->wm_write.low) + (*bufev->writecb)(bufev, bufev->cbarg); + return; + + retry: + if (clt->clt_buflen != 0) + server_bufferevent_add(&bufev->ev_write, bufev->timeout_write); + return; + + err: + if (clt->clt_buf != NULL) { + free(clt->clt_buf); + clt->clt_buf = NULL; + clt->clt_buflen = 0; + } + (*bufev->errorcb)(bufev, what, bufev->cbarg); +} + void server_input(struct client *clt) { struct server_config *srv_conf = clt->clt_srv_conf; evbuffercb inrd = server_read; evbuffercb inwr = server_write; + socklen_t slen; if (server_httpdesc_init(clt) == -1) { - server_close(clt, - "failed to allocate http descriptor"); + server_close(clt, "failed to allocate http descriptor"); return; } clt->clt_toread = TOREAD_HTTP_HEADER; inrd = server_read_http; + slen = sizeof(clt->clt_sndbufsiz); + if (getsockopt(clt->clt_s, SOL_SOCKET, SO_SNDBUF, + &clt->clt_sndbufsiz, &slen) == -1) { + server_close(clt, "failed to get send buffer size"); + return; + } + /* * Client <-> Server */ @@ -365,6 +664,19 @@ server_input(struct client *clt) return; } + if (srv_conf->flags & SRVFLAG_SSL) { + event_set(&clt->clt_bev->ev_read, clt->clt_s, EV_READ, + server_ssl_readcb, clt->clt_bev); + event_set(&clt->clt_bev->ev_write, clt->clt_s, EV_WRITE, + server_ssl_writecb, clt->clt_bev); + } + + /* Adjust write watermark to the socket buffer output size */ + bufferevent_setwatermark(clt->clt_bev, EV_WRITE, + clt->clt_sndbufsiz, 0); + /* Read at most amount of data that fits in one fcgi record. */ + bufferevent_setwatermark(clt->clt_bev, EV_READ, 0, FCGI_CONTENT_SIZE); + bufferevent_settimeout(clt->clt_bev, srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec); bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); @@ -384,15 +696,19 @@ server_write(struct bufferevent *bev, void *arg) if (clt->clt_done) goto done; + + bufferevent_enable(bev, EV_READ); return; done: - server_close(clt, "done"); + (*bev->errorcb)(bev, EVBUFFER_WRITE|EVBUFFER_EOF, bev->cbarg); return; } void server_dump(struct client *clt, const void *buf, size_t len) { + size_t outlen; + if (!len) return; @@ -402,11 +718,9 @@ server_dump(struct client *clt, const void *buf, size_t len) * of non-blocking events etc. This is useful to print an * error message before gracefully closing the client. */ -#if 0 - if (cre->ssl != NULL) - (void)SSL_write(cre->ssl, buf, len); + if (clt->clt_ressl_ctx != NULL) + (void)ressl_write(clt->clt_ressl_ctx, buf, len, &outlen); else -#endif (void)write(clt->clt_s, buf, len); } @@ -424,10 +738,9 @@ server_read(struct bufferevent *bev, void *arg) goto fail; if (clt->clt_done) goto done; - bufferevent_enable(bev, EV_READ); return; done: - server_close(clt, "done"); + (*bev->errorcb)(bev, EVBUFFER_READ|EVBUFFER_EOF, bev->cbarg); return; fail: server_close(clt, strerror(errno)); @@ -549,6 +862,13 @@ server_accept(int fd, short event, void *arg) return; } + if (srv->srv_conf.flags & SRVFLAG_SSL) { + event_again(&clt->clt_ev, clt->clt_s, EV_TIMEOUT|EV_READ, + server_accept_ssl, &clt->clt_tv_start, + &srv->srv_conf.timeout, clt); + return; + } + server_input(clt); return; @@ -565,6 +885,41 @@ server_accept(int fd, short event, void *arg) } } +void +server_accept_ssl(int fd, short event, void *arg) +{ + struct client *clt = (struct client *)arg; + struct server *srv = (struct server *)clt->clt_srv; + int ret; + + if (event == EV_TIMEOUT) { + server_close(clt, "SSL accept timeout"); + return; + } + + if (srv->srv_ressl_ctx == NULL) + fatalx("NULL ressl context"); + + ret = ressl_accept_socket(srv->srv_ressl_ctx, &clt->clt_ressl_ctx, + clt->clt_s); + if (ret == RESSL_READ_AGAIN) { + event_again(&clt->clt_ev, clt->clt_s, EV_TIMEOUT|EV_READ, + server_accept_ssl, &clt->clt_tv_start, + &srv->srv_conf.timeout, clt); + } else if (ret == RESSL_WRITE_AGAIN) { + event_again(&clt->clt_ev, clt->clt_s, EV_TIMEOUT|EV_WRITE, + server_accept_ssl, &clt->clt_tv_start, + &srv->srv_conf.timeout, clt); + } else if (ret != 0) { + log_warnx("%s: SSL accept failed - %s", __func__, + ressl_error(srv->srv_ressl_ctx)); + return; + } + + server_input(clt); + return; +} + void server_inflight_dec(struct client *clt, const char *why) { @@ -577,38 +932,80 @@ server_inflight_dec(struct client *clt, const char *why) /* the file was never opened, thus this was an inflight client. */ server_inflight--; - log_debug("%s: inflight decremented, now %d, %s", + DPRINTF("%s: inflight decremented, now %d, %s", __func__, server_inflight, why); } void -server_close(struct client *clt, const char *msg) +server_sendlog(struct server_config *srv_conf, int cmd, const char *emsg, ...) { - char ibuf[MAXHOSTNAMELEN], obuf[MAXHOSTNAMELEN]; - char *ptr = NULL; - struct server *srv = clt->clt_srv; - struct server_config *srv_conf = clt->clt_srv_conf; + va_list ap; + char *msg; + int ret; + struct iovec iov[2]; + + if (srv_conf->flags & SRVFLAG_SYSLOG) { + va_start(ap, emsg); + if (cmd == IMSG_LOG_ACCESS) + vlog(LOG_INFO, emsg, ap); + else + vlog(LOG_DEBUG, emsg, ap); + va_end(ap); + return; + } - SPLAY_REMOVE(client_tree, &srv->srv_clients, clt); + va_start(ap, emsg); + ret = vasprintf(&msg, emsg, ap); + va_end(ap); + if (ret == -1) { + log_warn("%s: vasprintf", __func__); + return; + } - /* free the HTTP descriptors incl. headers */ - server_close_http(clt); + iov[0].iov_base = &srv_conf->id; + iov[0].iov_len = sizeof(srv_conf->id); + iov[1].iov_base = msg; + iov[1].iov_len = strlen(msg) + 1; - event_del(&clt->clt_ev); - if (clt->clt_bev != NULL) - bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); - if (clt->clt_file != NULL) - bufferevent_disable(clt->clt_file, EV_READ|EV_WRITE); + proc_composev_imsg(env->sc_ps, PROC_LOGGER, -1, cmd, -1, iov, 2); +} + +void +server_log(struct client *clt, const char *msg) +{ + char ibuf[MAXHOSTNAMELEN], obuf[MAXHOSTNAMELEN]; + struct server_config *srv_conf = clt->clt_srv_conf; + char *ptr = NULL; + int debug_cmd = -1; + extern int verbose; + + switch (srv_conf->logformat) { + case LOG_FORMAT_CONNECTION: + debug_cmd = IMSG_LOG_ACCESS; + break; + default: + if (verbose > 1) + debug_cmd = IMSG_LOG_ERROR; + if (EVBUFFER_LENGTH(clt->clt_log)) { + while ((ptr = + evbuffer_readline(clt->clt_log)) != NULL) { + server_sendlog(srv_conf, + IMSG_LOG_ACCESS, "%s", ptr); + free(ptr); + } + } + break; + } - if ((env->sc_opts & HTTPD_OPT_LOGUPDATE) && msg != NULL) { - memset(&ibuf, 0, sizeof(ibuf)); - memset(&obuf, 0, sizeof(obuf)); + if (debug_cmd != -1 && msg != NULL) { + memset(ibuf, 0, sizeof(ibuf)); + memset(obuf, 0, sizeof(obuf)); (void)print_host(&clt->clt_ss, ibuf, sizeof(ibuf)); (void)server_http_host(&clt->clt_srv_ss, obuf, sizeof(obuf)); if (EVBUFFER_LENGTH(clt->clt_log) && - evbuffer_add_printf(clt->clt_log, "\r\n") != -1) + evbuffer_add_printf(clt->clt_log, "\n") != -1) ptr = evbuffer_readline(clt->clt_log); - log_info("server %s, " + server_sendlog(srv_conf, debug_cmd, "server %s, " "client %d (%d active), %s:%u -> %s, " "%s%s%s", srv_conf->name, clt->clt_id, server_clients, ibuf, ntohs(clt->clt_port), obuf, msg, @@ -616,19 +1013,44 @@ server_close(struct client *clt, const char *msg) if (ptr != NULL) free(ptr); } +} + +void +server_close(struct client *clt, const char *msg) +{ + struct server *srv = clt->clt_srv; + + SPLAY_REMOVE(client_tree, &srv->srv_clients, clt); + + /* free the HTTP descriptors incl. headers */ + server_close_http(clt); + + event_del(&clt->clt_ev); + if (clt->clt_bev != NULL) + bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); + if (clt->clt_srvbev != NULL) + bufferevent_disable(clt->clt_srvbev, EV_READ|EV_WRITE); + + server_log(clt, msg); if (clt->clt_bev != NULL) bufferevent_free(clt->clt_bev); if (clt->clt_output != NULL) evbuffer_free(clt->clt_output); + if (clt->clt_srvevb != NULL) + evbuffer_free(clt->clt_srvevb); - if (clt->clt_file != NULL) - bufferevent_free(clt->clt_file); + if (clt->clt_srvbev != NULL) + bufferevent_free(clt->clt_srvbev); if (clt->clt_fd != -1) close(clt->clt_fd); if (clt->clt_s != -1) close(clt->clt_s); + if (clt->clt_ressl_ctx != NULL) + ressl_close(clt->clt_ressl_ctx); + ressl_free(clt->clt_ressl_ctx); + server_inflight_dec(clt, __func__); if (clt->clt_log != NULL) @@ -664,6 +1086,17 @@ server_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg) return (0); } +int +server_dispatch_logger(int fd, struct privsep_proc *p, struct imsg *imsg) +{ + switch (imsg->hdr.type) { + default: + return (-1); + } + + return (0); +} + int server_bufferevent_add(struct event *ev, int timeout) { diff --git a/server_fcgi.c b/server_fcgi.c new file mode 100644 index 0000000..848bae4 --- /dev/null +++ b/server_fcgi.c @@ -0,0 +1,643 @@ +/* $OpenBSD: server_fcgi.c,v 1.33 2014/08/13 18:00:54 chrisz Exp $ */ + +/* + * Copyright (c) 2014 Florian Obser + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "httpd.h" +#include "http.h" + +#define FCGI_PADDING_SIZE 255 +#define FCGI_RECORD_SIZE \ + (sizeof(struct fcgi_record_header) + FCGI_CONTENT_SIZE + FCGI_PADDING_SIZE) + +#define FCGI_BEGIN_REQUEST 1 +#define FCGI_ABORT_REQUEST 2 +#define FCGI_END_REQUEST 3 +#define FCGI_PARAMS 4 +#define FCGI_STDIN 5 +#define FCGI_STDOUT 6 +#define FCGI_STDERR 7 +#define FCGI_DATA 8 +#define FCGI_GET_VALUES 9 +#define FCGI_GET_VALUES_RESULT 10 +#define FCGI_UNKNOWN_TYPE 11 +#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE) + +#define FCGI_RESPONDER 1 + +struct fcgi_record_header { + uint8_t version; + uint8_t type; + uint16_t id; + uint16_t content_len; + uint8_t padding_len; + uint8_t reserved; +} __packed; + +struct fcgi_begin_request_body { + uint16_t role; + uint8_t flags; + uint8_t reserved[5]; +} __packed; + +struct server_fcgi_param { + int total_len; + uint8_t buf[FCGI_RECORD_SIZE]; +}; + +int server_fcgi_header(struct client *, u_int); +void server_fcgi_read(struct bufferevent *, void *); +int server_fcgi_writeheader(struct client *, struct kv *, void *); +int fcgi_add_param(struct server_fcgi_param *, const char *, const char *, + struct client *); +int get_status(struct evbuffer *); + +int +server_fcgi(struct httpd *env, struct client *clt) +{ + struct server_fcgi_param param; + struct server_config *srv_conf = clt->clt_srv_conf; + struct http_descriptor *desc = clt->clt_desc; + struct fcgi_record_header *h; + struct fcgi_begin_request_body *begin; + char hbuf[MAXHOSTNAMELEN]; + size_t scriptlen; + int pathlen; + int fd = -1, ret; + const char *errstr = NULL; + char *str, *p, *script = NULL; + + if (srv_conf->socket[0] == ':') { + struct sockaddr_storage ss; + in_port_t port; + + p = srv_conf->socket + 1; + + port = strtonum(p, 0, 0xffff, &errstr); + if (errstr != NULL) { + log_warn("%s: strtonum %s, %s", __func__, p, errstr); + goto fail; + } + memset(&ss, 0, sizeof(ss)); + ss.ss_family = AF_INET; + ((struct sockaddr_in *) + &ss)->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + port = htons(port); + + if ((fd = server_socket_connect(&ss, port, srv_conf)) == -1) + goto fail; + } else { + struct sockaddr_un sun; + size_t len; + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + goto fail; + + memset(&sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; + len = strlcpy(sun.sun_path, + srv_conf->socket, sizeof(sun.sun_path)); + if (len >= sizeof(sun.sun_path)) { + errstr = "socket path to long"; + goto fail; + } + sun.sun_len = len; + + if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) + goto fail; + } + + socket_set_blockmode(fd, BM_NONBLOCK); + + memset(hbuf, 0, sizeof(hbuf)); + clt->clt_fcgi_state = FCGI_READ_HEADER; + clt->clt_fcgi_toread = sizeof(struct fcgi_record_header); + + if (clt->clt_srvevb != NULL) + evbuffer_free(clt->clt_srvevb); + + clt->clt_srvevb = evbuffer_new(); + if (clt->clt_srvevb == NULL) { + errstr = "failed to allocate evbuffer"; + goto fail; + } + + clt->clt_fd = fd; + if (clt->clt_srvbev != NULL) + bufferevent_free(clt->clt_srvbev); + + clt->clt_srvbev = bufferevent_new(fd, server_fcgi_read, + NULL, server_file_error, clt); + if (clt->clt_srvbev == NULL) { + errstr = "failed to allocate fcgi buffer event"; + goto fail; + } + + memset(¶m, 0, sizeof(param)); + + h = (struct fcgi_record_header *)¶m.buf; + h->version = 1; + h->type = FCGI_BEGIN_REQUEST; + h->id = htons(1); + h->content_len = htons(sizeof(struct fcgi_begin_request_body)); + h->padding_len = 0; + + begin = (struct fcgi_begin_request_body *)¶m.buf[sizeof(struct + fcgi_record_header)]; + begin->role = htons(FCGI_RESPONDER); + + bufferevent_write(clt->clt_srvbev, ¶m.buf, + sizeof(struct fcgi_record_header) + + sizeof(struct fcgi_begin_request_body)); + + h->type = FCGI_PARAMS; + h->content_len = param.total_len = 0; + + if ((pathlen = asprintf(&script, "%s%s", srv_conf->root, + desc->http_path_alias != NULL ? + desc->http_path_alias : desc->http_path)) == -1) { + errstr = "failed to get script name"; + goto fail; + } + + scriptlen = path_info(script); + /* + * no part of root should show up in PATH_INFO. + * therefore scriptlen should be >= strlen(root) + */ + if (scriptlen < strlen(srv_conf->root)) + scriptlen = strlen(srv_conf->root); + if ((int)scriptlen < pathlen) { + if (fcgi_add_param(¶m, "PATH_INFO", + script + scriptlen, clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + script[scriptlen] = '\0'; + } + + if (fcgi_add_param(¶m, "SCRIPT_NAME", + script + strlen(srv_conf->root), clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + if (fcgi_add_param(¶m, "SCRIPT_FILENAME", script, clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + if (desc->http_query) + if (fcgi_add_param(¶m, "QUERY_STRING", desc->http_query, + clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + if (fcgi_add_param(¶m, "DOCUMENT_ROOT", srv_conf->root, + clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + if (fcgi_add_param(¶m, "DOCUMENT_URI", desc->http_path, + clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + if (fcgi_add_param(¶m, "GATEWAY_INTERFACE", "CGI/1.1", + clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + /* Add HTTP_* headers */ + if (server_headers(clt, server_fcgi_writeheader, ¶m) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + if (srv_conf->flags & SRVFLAG_SSL) + if (fcgi_add_param(¶m, "HTTPS", "on", clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + (void)print_host(&clt->clt_ss, hbuf, sizeof(hbuf)); + if (fcgi_add_param(¶m, "REMOTE_ADDR", hbuf, clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + (void)snprintf(hbuf, sizeof(hbuf), "%d", ntohs(clt->clt_port)); + if (fcgi_add_param(¶m, "REMOTE_PORT", hbuf, clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + if (fcgi_add_param(¶m, "REQUEST_METHOD", + server_httpmethod_byid(desc->http_method), clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + if (!desc->http_query) { + if (fcgi_add_param(¶m, "REQUEST_URI", desc->http_path, + clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + } else if (asprintf(&str, "%s?%s", desc->http_path, + desc->http_query) != -1) { + ret = fcgi_add_param(¶m, "REQUEST_URI", str, clt); + free(str); + if (ret == -1) { + errstr = "failed to encode param"; + goto fail; + } + } + + (void)print_host(&clt->clt_srv_ss, hbuf, sizeof(hbuf)); + if (fcgi_add_param(¶m, "SERVER_ADDR", hbuf, clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + (void)snprintf(hbuf, sizeof(hbuf), "%d", + ntohs(server_socket_getport(&clt->clt_srv_ss))); + if (fcgi_add_param(¶m, "SERVER_PORT", hbuf, clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + if (fcgi_add_param(¶m, "SERVER_NAME", srv_conf->name, + clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + if (fcgi_add_param(¶m, "SERVER_PROTOCOL", desc->http_version, + clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + if (fcgi_add_param(¶m, "SERVER_SOFTWARE", HTTPD_SERVERNAME, + clt) == -1) { + errstr = "failed to encode param"; + goto fail; + } + + if (param.total_len != 0) { /* send last params record */ + bufferevent_write(clt->clt_srvbev, ¶m.buf, + sizeof(struct fcgi_record_header) + + ntohs(h->content_len)); + } + + /* send "no more params" message */ + h->content_len = 0; + bufferevent_write(clt->clt_srvbev, ¶m.buf, + sizeof(struct fcgi_record_header)); + + bufferevent_settimeout(clt->clt_srvbev, + srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec); + bufferevent_enable(clt->clt_srvbev, EV_READ|EV_WRITE); + if (clt->clt_toread != 0) { + server_read_httpcontent(clt->clt_bev, clt); + bufferevent_enable(clt->clt_bev, EV_READ); + } else { + bufferevent_disable(clt->clt_bev, EV_READ); + fcgi_add_stdin(clt, NULL); + } + + /* + * persist is not supported yet because we don't get the + * Content-Length from slowcgi and don't support chunked encoding. + */ + clt->clt_persist = 0; + clt->clt_done = 0; + + free(script); + return (0); + fail: + free(script); + if (errstr == NULL) + errstr = strerror(errno); + server_abort_http(clt, 500, errstr); + return (-1); +} + +int +fcgi_add_stdin(struct client *clt, struct evbuffer *evbuf) +{ + struct fcgi_record_header h; + + memset(&h, 0, sizeof(h)); + h.version = 1; + h.type = FCGI_STDIN; + h.id = htons(1); + h.padding_len = 0; + + if (evbuf == NULL) { + h.content_len = 0; + return bufferevent_write(clt->clt_srvbev, &h, + sizeof(struct fcgi_record_header)); + } else { + h.content_len = htons(EVBUFFER_LENGTH(evbuf)); + if (bufferevent_write(clt->clt_srvbev, &h, + sizeof(struct fcgi_record_header)) == -1) + return -1; + return bufferevent_write_buffer(clt->clt_srvbev, evbuf); + } + return (0); +} + +int +fcgi_add_param(struct server_fcgi_param *p, const char *key, + const char *val, struct client *clt) +{ + struct fcgi_record_header *h; + int len = 0; + int key_len = strlen(key); + int val_len = strlen(val); + uint8_t *param; + + len += key_len + val_len; + len += key_len > 127 ? 4 : 1; + len += val_len > 127 ? 4 : 1; + + DPRINTF("%s: %s[%d] => %s[%d], total_len: %d", __func__, key, key_len, + val, val_len, p->total_len); + + if (len > FCGI_CONTENT_SIZE) + return (-1); + + if (p->total_len + len > FCGI_CONTENT_SIZE) { + bufferevent_write(clt->clt_srvbev, p->buf, + sizeof(struct fcgi_record_header) + p->total_len); + p->total_len = 0; + } + + h = (struct fcgi_record_header *)p->buf; + param = p->buf + sizeof(*h) + p->total_len; + + if (key_len > 127) { + *param++ = ((key_len >> 24) & 0xff) | 0x80; + *param++ = ((key_len >> 16) & 0xff); + *param++ = ((key_len >> 8) & 0xff); + *param++ = (key_len & 0xff); + } else + *param++ = key_len; + + if (val_len > 127) { + *param++ = ((val_len >> 24) & 0xff) | 0x80; + *param++ = ((val_len >> 16) & 0xff); + *param++ = ((val_len >> 8) & 0xff); + *param++ = (val_len & 0xff); + } else + *param++ = val_len; + + memcpy(param, key, key_len); + param += key_len; + memcpy(param, val, val_len); + + p->total_len += len; + + h->content_len = htons(p->total_len); + return (0); +} + +void +server_fcgi_read(struct bufferevent *bev, void *arg) +{ + uint8_t buf[FCGI_RECORD_SIZE]; + struct client *clt = (struct client *) arg; + struct fcgi_record_header *h; + size_t len; + char *ptr; + + do { + len = bufferevent_read(bev, &buf, clt->clt_fcgi_toread); + /* XXX error handling */ + evbuffer_add(clt->clt_srvevb, &buf, len); + clt->clt_fcgi_toread -= len; + DPRINTF("%s: len: %lu toread: %d state: %d", __func__, len, + clt->clt_fcgi_toread, clt->clt_fcgi_state); + + if (clt->clt_fcgi_toread != 0) + return; + + switch (clt->clt_fcgi_state) { + case FCGI_READ_HEADER: + clt->clt_fcgi_state = FCGI_READ_CONTENT; + h = (struct fcgi_record_header *) + EVBUFFER_DATA(clt->clt_srvevb); + DPRINTF("%s: record header: version %d type %d id %d " + "content len %d padding %d", __func__, + h->version, h->type, ntohs(h->id), + ntohs(h->content_len), h->padding_len); + clt->clt_fcgi_type = h->type; + clt->clt_fcgi_toread = ntohs(h->content_len); + clt->clt_fcgi_padding_len = h->padding_len; + evbuffer_drain(clt->clt_srvevb, + EVBUFFER_LENGTH(clt->clt_srvevb)); + if (clt->clt_fcgi_toread != 0) + break; + else if (clt->clt_fcgi_type == FCGI_STDOUT && + !clt->clt_chunk) { + server_abort_http(clt, 500, "empty stdout"); + return; + } + + /* fallthrough if content_len == 0 */ + case FCGI_READ_CONTENT: + if (clt->clt_fcgi_type == FCGI_STDERR && + EVBUFFER_LENGTH(clt->clt_srvevb) > 0) { + if ((ptr = get_string( + EVBUFFER_DATA(clt->clt_srvevb), + EVBUFFER_LENGTH(clt->clt_srvevb))) + != NULL) { + server_sendlog(clt->clt_srv_conf, + IMSG_LOG_ERROR, "%s", ptr); + free(ptr); + } + } + if (clt->clt_fcgi_type == FCGI_STDOUT && + EVBUFFER_LENGTH(clt->clt_srvevb) > 0) { + if (++clt->clt_chunk == 1) + server_fcgi_header(clt, + get_status(clt->clt_srvevb)); + server_bufferevent_write_buffer(clt, + clt->clt_srvevb); + } + evbuffer_drain(clt->clt_srvevb, + EVBUFFER_LENGTH(clt->clt_srvevb)); + if (!clt->clt_fcgi_padding_len) { + clt->clt_fcgi_state = FCGI_READ_HEADER; + clt->clt_fcgi_toread = + sizeof(struct fcgi_record_header); + } else { + clt->clt_fcgi_state = FCGI_READ_PADDING; + clt->clt_fcgi_toread = + clt->clt_fcgi_padding_len; + } + break; + case FCGI_READ_PADDING: + evbuffer_drain(clt->clt_srvevb, + EVBUFFER_LENGTH(clt->clt_srvevb)); + clt->clt_fcgi_state = FCGI_READ_HEADER; + clt->clt_fcgi_toread = + sizeof(struct fcgi_record_header); + break; + } + } while (len > 0); +} + +int +server_fcgi_header(struct client *clt, u_int code) +{ + struct http_descriptor *desc = clt->clt_desc; + const char *error; + char tmbuf[32]; + + if (desc == NULL || (error = server_httperror_byid(code)) == NULL) + return (-1); + + if (server_log_http(clt, code, 0) == -1) + return (-1); + + kv_purge(&desc->http_headers); + + /* Add error codes */ + if (kv_setkey(&desc->http_pathquery, "%lu", code) == -1 || + kv_set(&desc->http_pathquery, "%s", error) == -1) + return (-1); + + /* Add headers */ + if (kv_add(&desc->http_headers, "Server", HTTPD_SERVERNAME) == NULL) + return (-1); + + /* Is it a persistent connection? */ + if (clt->clt_persist) { + if (kv_add(&desc->http_headers, + "Connection", "keep-alive") == NULL) + return (-1); + } else if (kv_add(&desc->http_headers, "Connection", "close") == NULL) + return (-1); + + /* Date header is mandatory and should be added last */ + server_http_date(tmbuf, sizeof(tmbuf)); + if (kv_add(&desc->http_headers, "Date", tmbuf) == NULL) + return (-1); + + /* Write initial header (fcgi might append more) */ + if (server_writeresponse_http(clt) == -1 || + server_bufferevent_print(clt, "\r\n") == -1 || + server_headers(clt, server_writeheader_http, NULL) == -1) + return (-1); + + return (0); +} + +int +server_fcgi_writeheader(struct client *clt, struct kv *hdr, void *arg) +{ + struct server_fcgi_param *param = arg; + char *val, *name, *p; + const char *key; + int ret; + + if (hdr->kv_flags & KV_FLAG_INVALID) + return (0); + + /* The key might have been updated in the parent */ + if (hdr->kv_parent != NULL && hdr->kv_parent->kv_key != NULL) + key = hdr->kv_parent->kv_key; + else + key = hdr->kv_key; + + val = hdr->kv_value; + + if (strcasecmp(key, "Content-Length") == 0 || + strcasecmp(key, "Content-Type") == 0) { + if ((name = strdup(key)) == NULL) + return (-1); + } else { + if (asprintf(&name, "HTTP_%s", key) == -1) + return (-1); + } + + for (p = name; *p != '\0'; p++) { + if (isalpha((unsigned char)*p)) + *p = toupper((unsigned char)*p); + else + *p = '_'; + } + + ret = fcgi_add_param(param, name, val, clt); + free(name); + + return (ret); +} + +int +get_status(struct evbuffer *bev) +{ + int code; + char *statusline, *tok; + const char *errstr; + + /* XXX This is a hack. We need to parse the response header. */ + code = 200; + if (strncmp(EVBUFFER_DATA(bev), "Status: ", strlen("Status: ")) == 0) { + statusline = get_string(EVBUFFER_DATA(bev), + EVBUFFER_LENGTH(bev)); + if (strtok(statusline, " ") != NULL) { + if ((tok = strtok(NULL, " ")) != NULL) { + code = (int) strtonum(tok, 100, 600, &errstr); + if (errstr != NULL || server_httperror_byid( + code) == NULL) + code = 200; + } + } + free(statusline); + } + return code; +} diff --git a/server_file.c b/server_file.c index 0fc3d69..e87bcbd 100644 --- a/server_file.c +++ b/server_file.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server_file.c,v 1.17 2014/07/26 22:38:38 reyk Exp $ */ +/* $OpenBSD: server_file.c,v 1.33 2014/08/14 07:50:35 chrisz Exp $ */ /* * Copyright (c) 2006 - 2014 Reyk Floeter @@ -39,34 +39,43 @@ #include #include #include +#include #include #include -#include - #include "httpd.h" #include "http.h" -int server_file_access(struct http_descriptor *, char *, size_t, +int server_file_access(struct httpd *, struct client *, char *, size_t); +int server_file_request(struct httpd *, struct client *, char *, struct stat *); int server_file_index(struct httpd *, struct client *); -void server_file_error(struct bufferevent *, short, void *); +int server_file_method(struct client *); int -server_file_access(struct http_descriptor *desc, char *path, size_t len, - struct stat *st) +server_file_access(struct httpd *env, struct client *clt, + char *path, size_t len) { - struct stat stb; - char *newpath; + struct http_descriptor *desc = clt->clt_desc; + struct server_config *srv_conf = clt->clt_srv_conf; + struct stat st; + char *newpath; + int ret; errno = 0; if (access(path, R_OK) == -1) { goto fail; - } else if (stat(path, st) == -1) { + } else if (stat(path, &st) == -1) { goto fail; - } else if (S_ISDIR(st->st_mode)) { - if (!len) { + } else if (S_ISDIR(st.st_mode)) { + /* Deny access if directory indexing is disabled */ + if (srv_conf->flags & SRVFLAG_NO_INDEX) { + errno = EACCES; + goto fail; + } + + if (desc->http_path_alias != NULL) { /* Recursion - the index "file" is a directory? */ errno = EINVAL; goto fail; @@ -74,39 +83,56 @@ server_file_access(struct http_descriptor *desc, char *path, size_t len, /* Redirect to path with trailing "/" */ if (path[strlen(path) - 1] != '/') { - if (asprintf(&newpath, "http://%s%s/", + if (asprintf(&newpath, "http%s://%s%s/", + srv_conf->flags & SRVFLAG_SSL ? "s" : "", desc->http_host, desc->http_path) == -1) return (500); - free(desc->http_path); - desc->http_path = newpath; + /* Path alias will be used for the redirection */ + desc->http_path_alias = newpath; /* Indicate that the file has been moved */ return (301); } - /* Otherwise append the default index file */ - if (strlcat(path, HTTPD_INDEX, len) >= len) { + /* Append the default index file to the location */ + if (asprintf(&newpath, "%s%s", desc->http_path, + srv_conf->index) == -1) + return (500); + desc->http_path_alias = newpath; + if (server_getlocation(clt, newpath) != srv_conf) { + /* The location has changed */ + return (server_file(env, clt)); + } + + /* Otherwise append the default index file to the path */ + if (strlcat(path, srv_conf->index, len) >= len) { errno = EACCES; goto fail; } - /* Check again but set len to 0 to avoid recursion */ - if (server_file_access(desc, path, 0, &stb) == 404) { + ret = server_file_access(env, clt, path, len); + if (ret == 404) { /* - * Index file not found, return success but - * indicate directory with S_ISDIR. + * Index file not found; fail if auto-indexing is + * not enabled, otherwise return success but + * indicate directory with S_ISDIR of the previous + * stat. */ - } else { - /* return updated stat from index file */ - memcpy(st, &stb, sizeof(*st)); + if ((srv_conf->flags & SRVFLAG_AUTO_INDEX) == 0) { + errno = EACCES; + goto fail; + } + + return (server_file_index(env, clt)); } - } else if (!S_ISREG(st->st_mode)) { + return (ret); + } else if (!S_ISREG(st.st_mode)) { /* Don't follow symlinks and ignore special files */ errno = EACCES; goto fail; } - return (0); + return (server_file_request(env, clt, path, &st)); fail: switch (errno) { @@ -126,72 +152,117 @@ server_file(struct httpd *env, struct client *clt) { struct http_descriptor *desc = clt->clt_desc; struct server_config *srv_conf = clt->clt_srv_conf; - struct media_type *media; - const char *errstr = NULL; - int fd = -1, ret; char path[MAXPATHLEN]; - struct stat st; + const char *errstr = NULL; + int ret = 500; + + if (srv_conf->flags & SRVFLAG_FCGI) + return (server_fcgi(env, clt)); /* Request path is already canonicalized */ if ((size_t)snprintf(path, sizeof(path), "%s%s", - srv_conf->docroot, desc->http_path) >= sizeof(path)) { - /* Do not echo the uncanonicalized path */ - server_abort_http(clt, 500, desc->http_path); - return (-1); + srv_conf->root, + desc->http_path_alias != NULL ? + desc->http_path_alias : desc->http_path) >= sizeof(path)) { + errstr = desc->http_path; + goto abort; } /* Returns HTTP status code on error */ - if ((ret = server_file_access(desc, path, sizeof(path), &st)) != 0) { - server_abort_http(clt, ret, desc->http_path); - return (-1); + if ((ret = server_file_access(env, clt, path, sizeof(path))) > 0) { + errstr = desc->http_path_alias != NULL ? + desc->http_path_alias : desc->http_path; + goto abort; } - if (S_ISDIR(st.st_mode)) { - /* list directory index */ - return (server_file_index(env, clt)); + return (ret); + + abort: + if (errstr == NULL) + errstr = strerror(errno); + server_abort_http(clt, ret, errstr); + return (-1); +} + +int +server_file_method(struct client *clt) +{ + struct http_descriptor *desc = clt->clt_desc; + + switch (desc->http_method) { + case HTTP_METHOD_GET: + case HTTP_METHOD_HEAD: + return (0); + default: + /* Other methods are not allowed */ + errno = EACCES; + return (405); + } + /* NOTREACHED */ +} + +int +server_file_request(struct httpd *env, struct client *clt, char *path, + struct stat *st) +{ + struct server_config *srv_conf = clt->clt_srv_conf; + struct media_type *media; + const char *errstr = NULL; + int fd = -1, ret, code = 500; + + if ((ret = server_file_method(clt)) != 0) { + code = ret; + goto abort; } /* Now open the file, should be readable or we have another problem */ if ((fd = open(path, O_RDONLY)) == -1) - goto fail; - - /* File descriptor is opened, decrement inflight counter */ - server_inflight_dec(clt, __func__); + goto abort; media = media_find(env->sc_mediatypes, path); - ret = server_response_http(clt, 200, media, st.st_size); + ret = server_response_http(clt, 200, media, st->st_size); switch (ret) { case -1: goto fail; case 0: /* Connection is already finished */ close(fd); - return (0); + goto done; default: break; } clt->clt_fd = fd; - if (clt->clt_file != NULL) - bufferevent_free(clt->clt_file); + if (clt->clt_srvbev != NULL) + bufferevent_free(clt->clt_srvbev); - clt->clt_file = bufferevent_new(clt->clt_fd, server_read, + clt->clt_srvbev = bufferevent_new(clt->clt_fd, server_read, server_write, server_file_error, clt); - if (clt->clt_file == NULL) { + if (clt->clt_srvbev == NULL) { errstr = "failed to allocate file buffer event"; goto fail; } - bufferevent_settimeout(clt->clt_file, + /* Adjust read watermark to the socket output buffer size */ + bufferevent_setwatermark(clt->clt_srvbev, EV_READ, 0, + clt->clt_sndbufsiz); + + bufferevent_settimeout(clt->clt_srvbev, srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec); - bufferevent_enable(clt->clt_file, EV_READ); + bufferevent_enable(clt->clt_srvbev, EV_READ); bufferevent_disable(clt->clt_bev, EV_READ); + done: + server_reset_http(clt); return (0); fail: + bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); + bufferevent_free(clt->clt_bev); + clt->clt_bev = NULL; + abort: if (errstr == NULL) errstr = strerror(errno); - server_abort_http(clt, 500, errstr); + server_abort_http(clt, code, errstr); return (-1); } @@ -199,38 +270,47 @@ int server_file_index(struct httpd *env, struct client *clt) { char path[MAXPATHLEN]; + char tmstr[21]; struct http_descriptor *desc = clt->clt_desc; struct server_config *srv_conf = clt->clt_srv_conf; struct dirent **namelist, *dp; - int namesize, i, ret, fd = -1, width; + int namesize, i, ret, fd = -1, namewidth, skip; + int code = 500; struct evbuffer *evb = NULL; struct media_type *media; const char *style; struct stat st; + struct tm tm; + time_t t; + + if ((ret = server_file_method(clt)) != 0) { + code = ret; + goto abort; + } /* Request path is already canonicalized */ if ((size_t)snprintf(path, sizeof(path), "%s%s", - srv_conf->docroot, desc->http_path) >= sizeof(path)) - goto fail; + srv_conf->root, desc->http_path) >= sizeof(path)) + goto abort; /* Now open the file, should be readable or we have another problem */ if ((fd = open(path, O_RDONLY)) == -1) - goto fail; - - /* File descriptor is opened, decrement inflight counter */ - server_inflight_dec(clt, __func__); + goto abort; if ((evb = evbuffer_new()) == NULL) - goto fail; + goto abort; if ((namesize = scandir(path, &namelist, NULL, alphasort)) == -1) - goto fail; + goto abort; + + /* Indicate failure but continue going through the list */ + skip = 0; /* A CSS stylesheet allows minimal customization by the user */ style = "body { background-color: white; color: black; font-family: " "sans-serif; }"; /* Generate simple HTML index document */ - evbuffer_add_printf(evb, + if (evbuffer_add_printf(evb, "\n" "\n" @@ -241,35 +321,51 @@ server_file_index(struct httpd *env, struct client *clt) "\n" "

Index of %s

\n" "
\n
\n",
-	    desc->http_path, style, desc->http_path);
+	    desc->http_path, style, desc->http_path) == -1)
+		skip = 1;
 
 	for (i = 0; i < namesize; i++) {
 		dp = namelist[i];
-		if (dp->d_name[0] == '.' && dp->d_name[1] == '\0') {
-			/* ignore */
-		} else if (dp->d_type == DT_DIR) {
-			evbuffer_add_printf(evb,
-			    "%s/\n",
-			    dp->d_name, dp->d_name);
-		} else if (dp->d_type == DT_REG) {
-			if (fstatat(fd, dp->d_name, &st, 0) == -1) {
-				free(dp);
-				continue;
-			}
-			width = 50 - strlen(dp->d_name);
-			evbuffer_add_printf(evb,
-			    "%s%*s%llu bytes\n",
+
+		if (skip ||
+		    fstatat(fd, dp->d_name, &st, 0) == -1) {
+			free(dp);
+			continue;
+		}
+
+		t = st.st_mtime;
+		localtime_r(&t, &tm);
+		strftime(tmstr, sizeof(tmstr), "%d-%h-%Y %R", &tm);
+		namewidth = 51 - strlen(dp->d_name);
+
+		if (dp->d_name[0] == '.' &&
+		    !(dp->d_name[1] == '.' && dp->d_name[2] == '\0')) {
+			/* ignore hidden files starting with a dot */
+		} else if (S_ISDIR(st.st_mode)) {
+			namewidth -= 1; /* trailing slash */
+			if (evbuffer_add_printf(evb,
+			    "%s/%*s%s%20s\n",
+			    dp->d_name, dp->d_name,
+			    MAX(namewidth, 0), " ", tmstr, "-") == -1)
+				skip = 1;
+		} else if (S_ISREG(st.st_mode)) {
+			if (evbuffer_add_printf(evb,
+			    "%s%*s%s%20llu\n",
 			    dp->d_name, dp->d_name,
-			    width < 0 ? 50 : width, "", st.st_size);
+			    MAX(namewidth, 0), " ", tmstr, st.st_size) == -1)
+				skip = 1;
 		}
 		free(dp);
 	}
 	free(namelist);
 
-	evbuffer_add_printf(evb,
-	    "
\n
\n\n\n"); + if (skip || + evbuffer_add_printf(evb, + "\n
\n\n\n") == -1) + goto abort; close(fd); + fd = -1; media = media_find(env->sc_mediatypes, "index.html"); ret = server_response_http(clt, 200, media, EVBUFFER_LENGTH(evb)); @@ -279,7 +375,7 @@ server_file_index(struct httpd *env, struct client *clt) case 0: /* Connection is already finished */ evbuffer_free(evb); - return (0); + goto done; default: break; } @@ -287,6 +383,7 @@ server_file_index(struct httpd *env, struct client *clt) if (server_bufferevent_write_buffer(clt, evb) == -1) goto fail; evbuffer_free(evb); + evb = NULL; bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); if (clt->clt_persist) @@ -295,14 +392,19 @@ server_file_index(struct httpd *env, struct client *clt) clt->clt_toread = TOREAD_HTTP_NONE; clt->clt_done = 0; + done: + server_reset_http(clt); return (0); - fail: + bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); + bufferevent_free(clt->clt_bev); + clt->clt_bev = NULL; + abort: if (fd != -1) close(fd); if (evb != NULL) evbuffer_free(evb); - server_abort_http(clt, 500, desc->http_path); + server_abort_http(clt, code, desc->http_path); return (-1); } @@ -321,13 +423,6 @@ server_file_error(struct bufferevent *bev, short error, void *arg) clt->clt_done = 1; - dst = EVBUFFER_OUTPUT(clt->clt_bev); - if (EVBUFFER_LENGTH(dst)) { - /* Finish writing all data first */ - bufferevent_enable(clt->clt_bev, EV_WRITE); - return; - } - if (clt->clt_persist) { /* Close input file and wait for next HTTP request */ if (clt->clt_fd != -1) @@ -338,6 +433,14 @@ server_file_error(struct bufferevent *bev, short error, void *arg) bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); return; } + + dst = EVBUFFER_OUTPUT(clt->clt_bev); + if (EVBUFFER_LENGTH(dst)) { + /* Finish writing all data first */ + bufferevent_enable(clt->clt_bev, EV_WRITE); + return; + } + server_close(clt, "done"); return; } diff --git a/server_http.c b/server_http.c index bfc5272..9e09428 100644 --- a/server_http.c +++ b/server_http.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server_http.c,v 1.19 2014/07/25 23:25:38 reyk Exp $ */ +/* $OpenBSD: server_http.c,v 1.44 2014/08/08 18:29:42 reyk Exp $ */ /* * Copyright (c) 2006 - 2014 Reyk Floeter @@ -40,15 +40,13 @@ #include #include #include +#include #include #include -#include - #include "httpd.h" #include "http.h" -static void server_http_date(char *, size_t); static int server_httpmethod_cmp(const void *, const void *); static int server_httperror_cmp(const void *, const void *); void server_httpdesc_free(struct http_descriptor *); @@ -78,7 +76,7 @@ server_http(struct httpd *x_env) void server_http_init(struct server *srv) { - /* nothing */ + /* nothing */ } int @@ -102,6 +100,10 @@ server_httpdesc_free(struct http_descriptor *desc) free(desc->http_path); desc->http_path = NULL; } + if (desc->http_path_alias != NULL) { + free(desc->http_path_alias); + desc->http_path_alias = NULL; + } if (desc->http_query != NULL) { free(desc->http_query); desc->http_query = NULL; @@ -110,6 +112,10 @@ server_httpdesc_free(struct http_descriptor *desc) free(desc->http_version); desc->http_version = NULL; } + if (desc->http_host != NULL) { + free(desc->http_host); + desc->http_host = NULL; + } kv_purge(&desc->http_headers); desc->http_lastheader = NULL; } @@ -118,6 +124,7 @@ void server_read_http(struct bufferevent *bev, void *arg) { struct client *clt = arg; + struct server_config *srv_conf = clt->clt_srv_conf; struct http_descriptor *desc = clt->clt_desc; struct evbuffer *src = EVBUFFER_INPUT(bev); char *line = NULL, *key, *value; @@ -259,6 +266,11 @@ server_read_http(struct bufferevent *bev, void *arg) server_abort_http(clt, 500, errstr); goto abort; } + if ((size_t)clt->clt_toread > + srv_conf->maxrequestbody) { + server_abort_http(clt, 413, NULL); + goto abort; + } } if (strcasecmp("Transfer-Encoding", key) == 0 && @@ -320,10 +332,10 @@ server_read_http(struct bufferevent *bev, void *arg) } done: - if (clt->clt_toread <= 0) { - server_response(env, clt); - return; - } + if (clt->clt_toread != 0) + bufferevent_disable(bev, EV_READ); + server_response(env, clt); + return; } if (clt->clt_done) { server_close(clt, "done"); @@ -359,12 +371,11 @@ server_read_httpcontent(struct bufferevent *bev, void *arg) /* Read content data */ if ((off_t)size > clt->clt_toread) { size = clt->clt_toread; - if (server_bufferevent_write_chunk(clt, - src, size) == -1) + if (fcgi_add_stdin(clt, src) == -1) goto fail; clt->clt_toread = 0; } else { - if (server_bufferevent_write_buffer(clt, src) == -1) + if (fcgi_add_stdin(clt, src) == -1) goto fail; clt->clt_toread -= size; } @@ -372,17 +383,19 @@ server_read_httpcontent(struct bufferevent *bev, void *arg) size, clt->clt_toread); } if (clt->clt_toread == 0) { + fcgi_add_stdin(clt, NULL); clt->clt_toread = TOREAD_HTTP_HEADER; + bufferevent_disable(bev, EV_READ); bev->readcb = server_read_http; + return; } if (clt->clt_done) goto done; if (bev->readcb != server_read_httpcontent) bev->readcb(bev, arg); - bufferevent_enable(bev, EV_READ); + return; done: - server_close(clt, "last http content read"); return; fail: server_close(clt, strerror(errno)); @@ -514,11 +527,14 @@ server_reset_http(struct client *clt) clt->clt_headerlen = 0; clt->clt_line = 0; clt->clt_done = 0; + clt->clt_chunk = 0; clt->clt_bev->readcb = server_read_http; clt->clt_srv_conf = &srv->srv_conf; + + server_log(clt, NULL); } -static void +void server_http_date(char *tmbuf, size_t len) { time_t t; @@ -565,7 +581,8 @@ server_http_host(struct sockaddr_storage *ss, char *buf, size_t len) void server_abort_http(struct client *clt, u_int code, const char *msg) { - struct server_config *srv_conf = clt->clt_srv_conf; + struct server *srv = clt->clt_srv; + struct server_config *srv_conf = &srv->srv_conf; struct bufferevent *bev = clt->clt_bev; const char *httperr = NULL, *text = ""; char *httpmsg, *extraheader = NULL; @@ -578,6 +595,9 @@ server_abort_http(struct client *clt, u_int code, const char *msg) if (bev == NULL) goto done; + if (server_log_http(clt, code, 0) == -1) + goto done; + /* Some system information */ if (print_host(&srv_conf->ss, hbuf, sizeof(hbuf)) == NULL) goto done; @@ -603,14 +623,7 @@ server_abort_http(struct client *clt, u_int code, const char *msg) /* A CSS stylesheet allows minimal customization by the user */ style = "body { background-color: white; color: black; font-family: " - "'Comic Sans MS', 'Chalkboard SE', 'Comic Neue', sans-serif; }" - "blink { animation:blink 1s; animation-iteration-count: infinite;" - "-webkit-animation:blink 1s;" - "-webkit-animation-iteration-count: infinite;}" - "@keyframes blink { 0%{opacity:0.0;} 50%{opacity:0.0;}" - "50.01%{opacity:1.0;} 100%{opacity:1.0;} }" - "@-webkit-keyframes blink { 0%{opacity:0.0;} 50%{opacity:0.0;}" - "50.01%{opacity:1.0;} 100%{opacity:1.0;} }"; + "'Comic Sans MS', 'Chalkboard SE', 'Comic Neue', sans-serif; }"; /* Generate simple HTTP+HTML error document */ if (asprintf(&httpmsg, "HTTP/1.0 %03d %s\r\n" @@ -628,7 +641,7 @@ server_abort_http(struct client *clt, u_int code, const char *msg) "\n" "\n" "\n" - "

%s

\n" + "

%s

\n" "
%s
\n" "
%s at %s port %d
\n" "\n" @@ -645,9 +658,9 @@ server_abort_http(struct client *clt, u_int code, const char *msg) done: free(extraheader); - if (asprintf(&httpmsg, "%s (%03d %s)", msg, code, httperr) == -1) + if (asprintf(&httpmsg, "%s (%03d %s)", msg, code, httperr) == -1) { server_close(clt, msg); - else { + } else { server_close(clt, httpmsg); free(httpmsg); } @@ -668,11 +681,11 @@ int server_response(struct httpd *httpd, struct client *clt) { char path[MAXPATHLEN]; + char hostname[MAXHOSTNAMELEN]; struct http_descriptor *desc = clt->clt_desc; struct server *srv = clt->clt_srv; struct server_config *srv_conf = &srv->srv_conf; struct kv *kv, key, *host; - int ret; /* Canonicalize the request path */ if (desc->http_path == NULL || @@ -709,6 +722,9 @@ server_response(struct httpd *httpd, struct client *clt) clt->clt_persist = 0; } + if (clt->clt_persist >= srv_conf->maxrequests) + clt->clt_persist = 0; + /* * Do we have a Host header and matching configuration? * XXX the Host can also appear in the URL path. @@ -716,7 +732,8 @@ server_response(struct httpd *httpd, struct client *clt) if (host != NULL) { /* XXX maybe better to turn srv_hosts into a tree */ TAILQ_FOREACH(srv_conf, &srv->srv_hosts, entry) { - if (fnmatch(srv_conf->name, host->kv_value, + if ((srv_conf->flags & SRVFLAG_LOCATION) == 0 && + fnmatch(srv_conf->name, host->kv_value, FNM_CASEFOLD) == 0) { /* Replace host configuration */ clt->clt_srv_conf = srv_conf; @@ -728,27 +745,49 @@ server_response(struct httpd *httpd, struct client *clt) if (srv_conf != NULL) { /* Use the actual server IP address */ - if (server_http_host(&clt->clt_srv_ss, desc->http_host, - sizeof(desc->http_host)) == NULL) + if (server_http_host(&clt->clt_srv_ss, hostname, + sizeof(hostname)) == NULL) goto fail; } else { /* Host header was valid and found */ - if (strlcpy(desc->http_host, host->kv_value, - sizeof(desc->http_host)) >= sizeof(desc->http_host)) + if (strlcpy(hostname, host->kv_value, sizeof(hostname)) >= + sizeof(hostname)) goto fail; + srv_conf = clt->clt_srv_conf; } - if ((ret = server_file(httpd, clt)) == -1) - return (-1); + if ((desc->http_host = strdup(hostname)) == NULL) + goto fail; - server_reset_http(clt); + /* Now search for the location */ + srv_conf = server_getlocation(clt, desc->http_path); - return (0); + return (server_file(httpd, clt)); fail: server_abort_http(clt, 400, "bad request"); return (-1); } +struct server_config * +server_getlocation(struct client *clt, const char *path) +{ + struct server *srv = clt->clt_srv; + struct server_config *srv_conf = clt->clt_srv_conf, *location; + + /* Now search for the location */ + TAILQ_FOREACH(location, &srv->srv_hosts, entry) { + if ((location->flags & SRVFLAG_LOCATION) && + location->id == srv_conf->id && + fnmatch(location->location, path, FNM_CASEFOLD) == 0) { + /* Replace host configuration */ + clt->clt_srv_conf = srv_conf = location; + break; + } + } + + return (srv_conf); +} + int server_response_http(struct client *clt, u_int code, struct media_type *media, size_t size) @@ -761,6 +800,9 @@ server_response_http(struct client *clt, u_int code, if (desc == NULL || (error = server_httperror_byid(code)) == NULL) return (-1); + if (server_log_http(clt, code, size) == -1) + return (-1); + kv_purge(&desc->http_headers); /* Add error codes */ @@ -788,9 +830,9 @@ server_response_http(struct client *clt, u_int code, return (-1); /* Set content length, if specified */ - if (size && ((cl = + if ((cl = kv_add(&desc->http_headers, "Content-Length", NULL)) == NULL || - kv_set(cl, "%ld", size) == -1)) + kv_set(cl, "%ld", size) == -1) return (-1); /* Date header is mandatory and should be added last */ @@ -801,11 +843,11 @@ server_response_http(struct client *clt, u_int code, /* Write completed header */ if (server_writeresponse_http(clt) == -1 || server_bufferevent_print(clt, "\r\n") == -1 || - server_writeheader_http(clt) == -1 || + server_headers(clt, server_writeheader_http, NULL) == -1 || server_bufferevent_print(clt, "\r\n") == -1) return (-1); - if (desc->http_method == HTTP_METHOD_HEAD) { + if (size == 0 || desc->http_method == HTTP_METHOD_HEAD) { bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); if (clt->clt_persist) clt->clt_toread = TOREAD_HTTP_HEADER; @@ -837,7 +879,7 @@ server_writeresponse_http(struct client *clt) } int -server_writeheader_kv(struct client *clt, struct kv *hdr) +server_writeheader_http(struct client *clt, struct kv *hdr, void *arg) { char *ptr; const char *key; @@ -865,16 +907,17 @@ server_writeheader_kv(struct client *clt, struct kv *hdr) } int -server_writeheader_http(struct client *clt) +server_headers(struct client *clt, + int (*hdr_cb)(struct client *, struct kv *, void *), void *arg) { struct kv *hdr, *kv; struct http_descriptor *desc = (struct http_descriptor *)clt->clt_desc; RB_FOREACH(hdr, kvtree, &desc->http_headers) { - if (server_writeheader_kv(clt, hdr) == -1) + if ((hdr_cb)(clt, hdr, arg) == -1) return (-1); TAILQ_FOREACH(kv, &hdr->kv_children, kv_entry) { - if (server_writeheader_kv(clt, kv) == -1) + if ((hdr_cb)(clt, kv, arg) == -1) return (-1); } } @@ -950,3 +993,90 @@ server_httperror_cmp(const void *a, const void *b) const struct http_error *eb = b; return (ea->error_code - eb->error_code); } + +int +server_log_http(struct client *clt, u_int code, size_t len) +{ + static char tstamp[64]; + static char ip[INET6_ADDRSTRLEN]; + time_t t; + struct kv key, *agent, *referrer; + struct tm *tm; + struct server_config *srv_conf; + struct http_descriptor *desc; + + if ((srv_conf = clt->clt_srv_conf) == NULL) + return (-1); + if ((srv_conf->flags & SRVFLAG_LOG) == 0) + return (0); + if ((desc = clt->clt_desc) == NULL) + return (-1); + + if ((t = time(NULL)) == -1) + return (-1); + if ((tm = localtime(&t)) == NULL) + return (-1); + if (strftime(tstamp, sizeof(tstamp), "%d/%b/%Y:%H:%M:%S %z", tm) == 0) + return (-1); + + if (print_host(&clt->clt_ss, ip, sizeof(ip)) == NULL) + return (-1); + + /* + * For details on common log format, see: + * https://httpd.apache.org/docs/current/mod/mod_log_config.html + * + * httpd's format is similar to these Apache LogFormats: + * "%v %h %l %u %t \"%r\" %>s %B" + * "%v %h %l %u %t \"%r\" %>s %B \"%{Referer}i\" \"%{User-agent}i\"" + */ + switch (srv_conf->logformat) { + case LOG_FORMAT_COMMON: + if (evbuffer_add_printf(clt->clt_log, + "%s %s - - [%s] \"%s %s%s%s%s%s\" %03d %zu\n", + srv_conf->name, ip, tstamp, + server_httpmethod_byid(desc->http_method), + desc->http_path == NULL ? "" : desc->http_path, + desc->http_query == NULL ? "" : "?", + desc->http_query == NULL ? "" : desc->http_query, + desc->http_version == NULL ? "" : " ", + desc->http_version == NULL ? "" : desc->http_version, + code, len) == -1) + return (-1); + break; + + case LOG_FORMAT_COMBINED: + key.kv_key = "Referer"; /* sic */ + if ((referrer = kv_find(&desc->http_headers, &key)) != NULL && + referrer->kv_value == NULL) + referrer = NULL; + + key.kv_key = "User-Agent"; + if ((agent = kv_find(&desc->http_headers, &key)) != NULL && + agent->kv_value == NULL) + agent = NULL; + + if (evbuffer_add_printf(clt->clt_log, + "%s %s - - [%s] \"%s %s%s%s%s%s\" %03d %zu \"%s\" \"%s\"\n", + srv_conf->name, ip, tstamp, + server_httpmethod_byid(desc->http_method), + desc->http_path == NULL ? "" : desc->http_path, + desc->http_query == NULL ? "" : "?", + desc->http_query == NULL ? "" : desc->http_query, + desc->http_version == NULL ? "" : " ", + desc->http_version == NULL ? "" : desc->http_version, + code, len, + referrer == NULL ? "" : referrer->kv_value, + agent == NULL ? "" : agent->kv_value) == -1) + return (-1); + break; + + case LOG_FORMAT_CONNECTION: + if (evbuffer_add_printf(clt->clt_log, " [%s]", + desc->http_path == NULL ? "" : desc->http_path) == -1) + return (-1); + break; + } + + return (0); +} -- cgit v1.2.3-54-g00ecf