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

%s

\n" + "
%s
\n" + "
%s at %s port %d
\n" + "\n" + "\n", + code, httperr, tmbuf, HTTPD_SERVERNAME, + code, httperr, style, httperr, text, + HTTPD_SERVERNAME, hbuf, ntohs(srv->srv_conf.port)) == -1) + goto done; + + /* Dump the message without checking for success */ + server_dump(clt, httpmsg, strlen(httpmsg)); + free(httpmsg); + + done: + if (asprintf(&httpmsg, "%s (%03d %s)", msg, code, httperr) == -1) + server_close(clt, msg); + else { + server_close(clt, httpmsg); + free(httpmsg); + } +} + +void +server_close_http(struct client *clt) +{ + struct http_descriptor *desc = clt->clt_desc; + + if (desc == NULL) + return; + server_httpdesc_free(desc); + free(desc); +} + +int +server_writeresponse_http(struct client *clt) +{ + struct http_descriptor *desc = (struct http_descriptor *)clt->clt_desc; + + DPRINTF("version: %s rescode: %s resmsg: %s", desc->http_version, + desc->http_rescode, desc->http_resmesg); + + if (server_bufferevent_print(clt, desc->http_version) == -1 || + server_bufferevent_print(clt, " ") == -1 || + server_bufferevent_print(clt, desc->http_rescode) == -1 || + server_bufferevent_print(clt, " ") == -1 || + server_bufferevent_print(clt, desc->http_resmesg) == -1) + return (-1); + + return (0); +} + +int +server_writeheader_kv(struct client *clt, struct kv *hdr) +{ + char *ptr; + const char *key; + + if (hdr->kv_flags & KV_FLAG_INVALID) + return (0); + + /* The key might have been updated in the parent */ + if (hdr->kv_parent != NULL && hdr->kv_parent->kv_key != NULL) + key = hdr->kv_parent->kv_key; + else + key = hdr->kv_key; + + ptr = hdr->kv_value; + DPRINTF("%s: ptr %s", __func__, ptr); + if (server_bufferevent_print(clt, key) == -1 || + (ptr != NULL && + (server_bufferevent_print(clt, ": ") == -1 || + server_bufferevent_print(clt, ptr) == -1 || + server_bufferevent_print(clt, "\r\n") == -1))) + return (-1); + DPRINTF("%s: %s: %s", __func__, key, + hdr->kv_value == NULL ? "" : hdr->kv_value); + + return (0); +} + +int +server_writeheader_http(struct client *clt) +{ + struct kv *hdr, *kv; + struct http_descriptor *desc = (struct http_descriptor *)clt->clt_desc; + + RB_FOREACH(hdr, kvtree, &desc->http_headers) { + if (server_writeheader_kv(clt, hdr) == -1) + return (-1); + TAILQ_FOREACH(kv, &hdr->kv_children, kv_entry) { + if (server_writeheader_kv(clt, kv) == -1) + return (-1); + } + } + + return (0); +} + +enum httpmethod +server_httpmethod_byname(const char *name) +{ + enum httpmethod id = HTTP_METHOD_NONE; + struct http_method method, *res = NULL; + + /* Set up key */ + method.method_name = name; + + /* + * RFC 2616 section 5.1.1 says that the method is case + * sensitive so we don't do a strcasecmp here. + */ + if ((res = bsearch(&method, http_methods, + sizeof(http_methods) / sizeof(http_methods[0]) - 1, + sizeof(http_methods[0]), server_httpmethod_cmp)) != NULL) + id = res->method_id; + + return (id); +} + +const char * +server_httpmethod_byid(u_int id) +{ + const char *name = NULL; + int i; + + for (i = 0; http_methods[i].method_name != NULL; i++) { + if (http_methods[i].method_id == id) { + name = http_methods[i].method_name; + break; + } + } + + return (name); +} + +static int +server_httpmethod_cmp(const void *a, const void *b) +{ + const struct http_method *ma = a; + const struct http_method *mb = b; + return (strcmp(ma->method_name, mb->method_name)); +} + +const char * +server_httperror_byid(u_int id) +{ + struct http_error error, *res = NULL; + + /* Set up key */ + error.error_code = (int)id; + + res = bsearch(&error, http_errors, + sizeof(http_errors) / sizeof(http_errors[0]) - 1, + sizeof(http_errors[0]), server_httperror_cmp); + + return (res->error_name); +} + +static int +server_httperror_cmp(const void *a, const void *b) +{ + const struct http_error *ea = a; + const struct http_error *eb = b; + return (ea->error_code - eb->error_code); +} -- 2.20.1