summaryrefslogtreecommitdiff
path: root/httpd.c
diff options
context:
space:
mode:
authorReyk Floeter <reyk@esdenera.com>2014-07-12 19:00:52 +0200
committerReyk Floeter <reyk@esdenera.com>2014-07-12 19:00:52 +0200
commit3d88bcbb432cda12794bc4e0aac180e5489901ff (patch)
treea98a20f0806c3e5476bc64798d503f70442c4a97 /httpd.c
downloadhttpd-3d88bcbb432cda12794bc4e0aac180e5489901ff.tar.gz
httpd-3d88bcbb432cda12794bc4e0aac180e5489901ff.zip
Import httpd experiment based on relayd.
Diffstat (limited to 'httpd.c')
-rw-r--r--httpd.c527
1 files changed, 527 insertions, 0 deletions
diff --git a/httpd.c b/httpd.c
new file mode 100644
index 0000000..73ab565
--- /dev/null
+++ b/httpd.c
@@ -0,0 +1,527 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2014 Reyk Floeter <reyk@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/resource.h>
+#include <sys/hash.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <fnmatch.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <sha1.h>
+#include <md5.h>
+
+#include <openssl/ssl.h>
+
+#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;
+
+#if 0
+ TAILQ_FOREACH(srv, env->sc_server, entry)
+ config_setserver(env, srv);
+#endif
+
+ /* 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);
+ bcopy(end, &tv_next, sizeof(tv_next));
+ timersub(&tv_now, start, &tv_now);
+ timersub(&tv_next, &tv_now, &tv_next);
+
+ bzero(&tv, sizeof(tv));
+ if (timercmp(&tv_next, &tv, >))
+ bcopy(&tv_next, &tv, 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);
+ bzero(name, 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);
+}