/* $OpenBSD: httpd.c,v 1.73 2022/09/02 07:38:14 benno 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 "httpd.h" #define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) __dead void usage(void); int parent_configure(struct httpd *); void parent_configure_done(struct httpd *); void parent_reload(struct httpd *, unsigned int, const char *); void parent_reopen(struct httpd *); void parent_sig_handler(int, short, void *); void parent_shutdown(struct httpd *); int parent_dispatch_server(int, struct privsep_proc *, struct imsg *); int parent_dispatch_logger(int, struct privsep_proc *, struct imsg *); void parent_tls_ticket_rekey_start(struct server *); void parent_tls_ticket_rekey(int, short, void *); struct httpd *httpd_env; static struct privsep_proc procs[] = { { "server", PROC_SERVER, parent_dispatch_server, server }, { "logger", PROC_LOGGER, parent_dispatch_logger, logger } }; enum privsep_procid privsep_process; void parent_sig_handler(int sig, short event, void *arg) { struct privsep *ps = arg; switch (sig) { case SIGTERM: case SIGINT: parent_shutdown(ps->ps_env); break; case SIGHUP: log_info("%s: reload requested with SIGHUP", __func__); /* * This is safe because libevent uses async signal handlers * that run in the event loop and not in signal context. */ parent_reload(ps->ps_env, CONFIG_RELOAD, NULL); break; case SIGPIPE: /* ignore */ break; case SIGUSR1: log_info("%s: reopen requested with SIGUSR1", __func__); parent_reopen(ps->ps_env); break; default: fatalx("unexpected signal"); } } __dead void usage(void) { extern char *__progname; fprintf(stderr, "usage: %s [-dnv] [-D macro=value] [-f file]\n", __progname); exit(1); } int main(int argc, char *argv[]) { int c; unsigned int proc; int debug = 0, verbose = 0; uint32_t opts = 0; struct httpd *env; struct privsep *ps; const char *conffile = CONF_FILE; enum privsep_procid proc_id = PROC_PARENT; int proc_instance = 0; const char *errp, *title = NULL; int argc0 = argc; while ((c = getopt(argc, argv, "dD:nf:I:P: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; case 'P': title = optarg; proc_id = proc_getid(procs, nitems(procs), title); if (proc_id == PROC_MAX) fatalx("invalid process name"); break; case 'I': proc_instance = strtonum(optarg, 0, PROC_MAX_INSTANCES, &errp); if (errp) fatalx("invalid process instance"); break; default: usage(); } } /* log to stderr until daemonized */ log_init(debug ? debug : 1, LOG_DAEMON); argc -= optind; if (argc > 0) usage(); if ((env = calloc(1, sizeof(*env))) == NULL || (ps = calloc(1, sizeof(*ps))) == NULL) exit(1); httpd_env = env; env->sc_ps = ps; ps->ps_env = env; TAILQ_INIT(&ps->ps_rcsocks); env->sc_conffile = conffile; env->sc_opts = opts; if (parse_config(env->sc_conffile, env) == -1) exit(1); if (geteuid()) errx(1, "need root privileges"); if ((ps->ps_pw = getpwnam(HTTPD_USER)) == NULL) errx(1, "unknown user %s", HTTPD_USER); /* Configure the control socket */ ps->ps_csock.cs_name = NULL; log_init(debug, LOG_DAEMON); log_setverbose(verbose); if (env->sc_opts & HTTPD_OPT_NOACTION) ps->ps_noaction = 1; ps->ps_instances[PROC_SERVER] = env->sc_prefork_server; ps->ps_instance = proc_instance; if (title != NULL) ps->ps_title[proc_id] = title; if (env->sc_chroot == NULL) env->sc_chroot = ps->ps_pw->pw_dir; for (proc = 0; proc < nitems(procs); proc++) procs[proc].p_chroot = env->sc_chroot; if (env->sc_logdir == NULL) { if (asprintf(&env->sc_logdir, "%s%s", env->sc_chroot, HTTPD_LOGROOT) == -1) errx(1, "malloc failed"); } /* only the parent returns */ proc_init(ps, procs, nitems(procs), debug, argc0, argv, proc_id); log_procinit("parent"); if (!debug && daemon(1, 0) == -1) err(1, "failed to daemonize"); if (ps->ps_noaction == 0) log_info("startup"); if (pledge("stdio rpath wpath cpath inet dns sendfd", NULL) == -1) fatal("pledge"); event_init(); signal_set(&ps->ps_evsigint, SIGINT, parent_sig_handler, ps); signal_set(&ps->ps_evsigterm, SIGTERM, parent_sig_handler, ps); signal_set(&ps->ps_evsighup, SIGHUP, parent_sig_handler, ps); signal_set(&ps->ps_evsigpipe, SIGPIPE, parent_sig_handler, ps); signal_set(&ps->ps_evsigusr1, SIGUSR1, parent_sig_handler, ps); signal_add(&ps->ps_evsigint, NULL); signal_add(&ps->ps_evsigterm, NULL); signal_add(&ps->ps_evsighup, NULL); signal_add(&ps->ps_evsigpipe, NULL); signal_add(&ps->ps_evsigusr1, NULL); proc_connect(ps); 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); } /* initialize the TLS session id to a random key for all procs */ arc4random_buf(env->sc_tls_sid, sizeof(env->sc_tls_sid)); if (parent_configure(env) == -1) fatalx("configuration failed"); event_dispatch(); parent_shutdown(env); /* NOTREACHED */ return (0); } int parent_configure(struct httpd *env) { int id; struct ctl_flags cf; int ret = -1; struct server *srv; struct media_type *media; struct auth *auth; RB_FOREACH(media, mediatypes, env->sc_mediatypes) { if (config_setmedia(env, media) == -1) fatal("send media"); } TAILQ_FOREACH(auth, env->sc_auth, auth_entry) { if (config_setauth(env, auth) == -1) fatal("send auth"); } /* First send the servers... */ TAILQ_FOREACH(srv, env->sc_servers, srv_entry) { if (srv->srv_conf.flags & SRVFLAG_LOCATION) continue; /* start the rekey of the tls ticket keys */ if (srv->srv_conf.flags & SRVFLAG_TLS && srv->srv_conf.tls_ticket_lifetime) parent_tls_ticket_rekey_start(srv); if (config_setserver(env, srv) == -1) fatal("send server"); } /* ...and now send the locations */ TAILQ_FOREACH(srv, env->sc_servers, srv_entry) { if ((srv->srv_conf.flags & SRVFLAG_LOCATION) == 0) continue; if (config_setserver(env, srv) == -1) fatal("send location"); } /* The servers need to reload their config. */ env->sc_reload = env->sc_prefork_server + 1; for (id = 0; id < PROC_MAX; id++) { if (id == privsep_process) continue; cf.cf_opts = env->sc_opts; cf.cf_flags = env->sc_flags; memcpy(cf.cf_tls_sid, env->sc_tls_sid, sizeof(cf.cf_tls_sid)); proc_compose(env->sc_ps, id, IMSG_CFG_DONE, &cf, sizeof(cf)); } ret = 0; config_purge(env, CONFIG_ALL & ~CONFIG_SERVERS); return (ret); } void parent_reload(struct httpd *env, unsigned int reset, const char *filename) { if (env->sc_reload) { log_debug("%s: already in progress: %d pending", __func__, env->sc_reload); return; } /* Switch back to the default config file */ if (filename == NULL || *filename == '\0') filename = env->sc_conffile; log_debug("%s: level %d config file %s", __func__, reset, filename); config_purge(env, CONFIG_ALL); if (reset == CONFIG_RELOAD) { if (load_config(filename, env) == -1) { log_debug("%s: failed to load config file %s", __func__, filename); } config_setreset(env, CONFIG_ALL); if (parent_configure(env) == -1) { log_debug("%s: failed to commit config from %s", __func__, filename); } } else config_setreset(env, reset); } void parent_reopen(struct httpd *env) { proc_compose(env->sc_ps, PROC_LOGGER, IMSG_CTL_REOPEN, NULL, 0); } void parent_configure_done(struct httpd *env) { int id; if (env->sc_reload == 0) { log_warnx("%s: configuration already finished", __func__); return; } env->sc_reload--; if (env->sc_reload == 0) { for (id = 0; id < PROC_MAX; id++) { if (id == privsep_process) continue; proc_compose(env->sc_ps, id, IMSG_CTL_START, 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); if (env->sc_ps->ps_csock.cs_name != NULL) (void)unlink(env->sc_ps->ps_csock.cs_name); 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 privsep *ps = p->p_ps; struct httpd *env = ps->ps_env; switch (imsg->hdr.type) { case IMSG_CFG_DONE: parent_configure_done(env); break; default: return (-1); } return (0); } int parent_dispatch_logger(int fd, struct privsep_proc *p, struct imsg *imsg) { struct privsep *ps = p->p_ps; struct httpd *env = ps->ps_env; unsigned int v; char *str = NULL; switch (imsg->hdr.type) { case IMSG_CTL_RESET: IMSG_SIZE_CHECK(imsg, &v); memcpy(&v, imsg->data, sizeof(v)); parent_reload(env, v, NULL); break; case IMSG_CTL_RELOAD: if (IMSG_DATA_SIZE(imsg) > 0) str = get_string(imsg->data, IMSG_DATA_SIZE(imsg)); parent_reload(env, CONFIG_RELOAD, str); free(str); break; case IMSG_CTL_SHUTDOWN: parent_shutdown(env); break; case IMSG_CTL_REOPEN: parent_reopen(env); break; case IMSG_CFG_DONE: parent_configure_done(env); break; case IMSG_LOG_OPEN: if (logger_open_priv(imsg) == -1) fatalx("failed to open log file"); break; default: return (-1); } return (0); } void parent_tls_ticket_rekey_start(struct server *srv) { struct timeval tv; server_generate_ticket_key(&srv->srv_conf); evtimer_set(&srv->srv_evt, parent_tls_ticket_rekey, srv); timerclear(&tv); tv.tv_sec = srv->srv_conf.tls_ticket_lifetime / 4; evtimer_add(&srv->srv_evt, &tv); } void parent_tls_ticket_rekey(int fd, short events, void *arg) { struct server *srv = arg; struct timeval tv; server_generate_ticket_key(&srv->srv_conf); proc_compose_imsg(httpd_env->sc_ps, PROC_SERVER, -1, IMSG_TLSTICKET_REKEY, -1, -1, &srv->srv_conf.tls_ticket_key, sizeof(srv->srv_conf.tls_ticket_key)); explicit_bzero(&srv->srv_conf.tls_ticket_key, sizeof(srv->srv_conf.tls_ticket_key)); evtimer_set(&srv->srv_evt, parent_tls_ticket_rekey, srv); timerclear(&tv); tv.tv_sec = srv->srv_conf.tls_ticket_lifetime / 4; evtimer_add(&srv->srv_evt, &tv); } /* * Utility functions */ void event_again(struct event *ev, int fd, short event, void (*fn)(int, short, void *), struct timeval *start, struct timeval *end, void *arg) { struct timeval tv_next, tv_now, tv; getmonotime(&tv_now); memcpy(&tv_next, end, sizeof(tv_next)); timersub(&tv_now, start, &tv_now); timersub(&tv_next, &tv_now, &tv_next); memset(&tv, 0, sizeof(tv)); if (timercmp(&tv_next, &tv, >)) memcpy(&tv, &tv_next, sizeof(tv)); event_del(ev); event_set(ev, fd, event, fn, arg); event_add(ev, &tv); } int expand_string(char *label, size_t len, const char *srch, const char *repl) { char *tmp; char *p, *q; if ((tmp = calloc(1, len)) == NULL) { log_debug("%s: calloc", __func__); return (-1); } p = q = label; while ((q = strstr(p, srch)) != NULL) { *q = '\0'; if ((strlcat(tmp, p, len) >= len) || (strlcat(tmp, repl, len) >= len)) { log_debug("%s: string too long", __func__); free(tmp); return (-1); } q += strlen(srch); p = q; } if (strlcat(tmp, p, len) >= len) { log_debug("%s: string too long", __func__); free(tmp); return (-1); } (void)strlcpy(label, tmp, len); /* always fits */ free(tmp); return (0); } const char * url_decode(char *url) { char *p, *q; char hex[3]; unsigned long x; hex[2] = '\0'; p = q = url; while (*p != '\0') { switch (*p) { case '%': /* Encoding character is followed by two hex chars */ if (!(isxdigit((unsigned char)p[1]) && isxdigit((unsigned char)p[2]))) return (NULL); hex[0] = p[1]; hex[1] = p[2]; /* * We don't have to validate "hex" because it is * guaranteed to include two hex chars followed by nul. */ x = strtoul(hex, NULL, 16); *q = (char)x; p += 2; break; default: *q = *p; break; } p++; q++; } *q = '\0'; return (url); } const char * canonicalize_path(const char *input, char *path, size_t len) { const char *i; char *p, *start, *end; /* assuming input starts with '/' and is nul-terminated */ i = input; p = path; if (*input != '/' || len < 3) return (NULL); start = p; end = p + (len - 1); while (*i != '\0') { /* Detect truncation */ if (p >= end) return (NULL); /* 1. check for special path elements */ if (i[0] == '/') { if (i[1] == '/') { /* a) skip repeating '//' slashes */ while (i[1] == '/') i++; continue; } else if (i[1] == '.' && i[2] == '.' && (i[3] == '/' || i[3] == '\0')) { /* b) revert '..' to previous directory */ i += 3; while (p > start && *p != '/') p--; *p = '\0'; continue; } else if (i[1] == '.' && (i[2] == '/' || i[2] == '\0')) { /* c) skip unnecessary '.' current dir */ i += 2; continue; } } /* 2. copy any other characters */ *p++ = *i; i++; } if (p == start) *p++ = '/'; *p++ = '\0'; return (path); } size_t path_info(char *path) { char *p, *start, *end, ch; struct stat st; int ret; start = path; end = start + strlen(path); for (p = end; p > start; p--) { /* Scan every path component from the end and at each '/' */ if (p < end && *p != '/') continue; /* Temporarily cut the path component out */ ch = *p; *p = '\0'; ret = stat(path, &st); *p = ch; /* Break if the initial path component was found */ if (ret == 0) break; } return (p - start); } char * url_encode(const char *src) { static char hex[] = "0123456789ABCDEF"; char *dp, *dst; unsigned char c; /* We need 3 times the memory if every letter is encoded. */ if ((dst = calloc(3, strlen(src) + 1)) == NULL) return (NULL); for (dp = dst; *src != 0; src++) { c = (unsigned char) *src; if (c == ' ' || c == '#' || c == '%' || c == '?' || c == '"' || c == '&' || c == '<' || c <= 0x1f || c >= 0x7f) { *dp++ = '%'; *dp++ = hex[c >> 4]; *dp++ = hex[c & 0x0f]; } else *dp++ = *src; } return (dst); } char* escape_html(const char* src) { char *dp, *dst; /* We need 5 times the memory if every letter is "&" */ if ((dst = calloc(5, strlen(src) + 1)) == NULL) return NULL; for (dp = dst; *src != 0; src++) { if (*src == '<') { *dp++ = '&'; *dp++ = 'l'; *dp++ = 't'; *dp++ = ';'; } else if (*src == '>') { *dp++ = '&'; *dp++ = 'g'; *dp++ = 't'; *dp++ = ';'; } else if (*src == '&') { *dp++ = '&'; *dp++ = 'a'; *dp++ = 'm'; *dp++ = 'p'; *dp++ = ';'; } else *dp++ = *src; } return (dst); } void socket_rlimit(int maxfd) { struct rlimit rl; if (getrlimit(RLIMIT_NOFILE, &rl) == -1) fatal("%s: failed to get resource limit", __func__); log_debug("%s: max open files %llu", __func__, rl.rlim_max); /* * Allow the maximum number of open file descriptors for this * login class (which should be the class "daemon" by default). */ if (maxfd == -1) rl.rlim_cur = rl.rlim_max; else rl.rlim_cur = MAXIMUM(rl.rlim_max, (rlim_t)maxfd); if (setrlimit(RLIMIT_NOFILE, &rl) == -1) fatal("%s: failed to set resource limit", __func__); } char * evbuffer_getline(struct evbuffer *evb) { uint8_t *ptr = EVBUFFER_DATA(evb); size_t len = EVBUFFER_LENGTH(evb); char *str; size_t i; /* Safe version of evbuffer_readline() */ if ((str = get_string(ptr, len)) == NULL) return (NULL); for (i = 0; str[i] != '\0'; i++) { if (str[i] == '\r' || str[i] == '\n') break; } if (i == len) { free(str); return (NULL); } str[i] = '\0'; if ((i + 1) < len) { if (ptr[i] == '\r' && ptr[i + 1] == '\n') i++; } evbuffer_drain(evb, ++i); return (str); } char * get_string(uint8_t *ptr, size_t len) { size_t i; for (i = 0; i < len; i++) if (!(isprint((unsigned char)ptr[i]) || isspace((unsigned char)ptr[i]))) break; return strndup(ptr, i); } void * get_data(uint8_t *ptr, size_t len) { uint8_t *data; if ((data = malloc(len)) == NULL) return (NULL); memcpy(data, ptr, len); return (data); } int sockaddr_cmp(struct sockaddr *a, struct sockaddr *b, int prefixlen) { struct sockaddr_in *a4, *b4; struct sockaddr_in6 *a6, *b6; uint32_t av[4], bv[4], mv[4]; if (a->sa_family == AF_UNSPEC || b->sa_family == AF_UNSPEC) return (0); else if (a->sa_family > b->sa_family) return (1); else if (a->sa_family < b->sa_family) return (-1); if (prefixlen == -1) memset(&mv, 0xff, sizeof(mv)); switch (a->sa_family) { case AF_INET: a4 = (struct sockaddr_in *)a; b4 = (struct sockaddr_in *)b; av[0] = a4->sin_addr.s_addr; bv[0] = b4->sin_addr.s_addr; if (prefixlen != -1) mv[0] = prefixlen2mask(prefixlen); if ((av[0] & mv[0]) > (bv[0] & mv[0])) return (1); if ((av[0] & mv[0]) < (bv[0] & mv[0])) return (-1); break; case AF_INET6: a6 = (struct sockaddr_in6 *)a; b6 = (struct sockaddr_in6 *)b; memcpy(&av, &a6->sin6_addr.s6_addr, 16); memcpy(&bv, &b6->sin6_addr.s6_addr, 16); if (prefixlen != -1) prefixlen2mask6(prefixlen, mv); if ((av[3] & mv[3]) > (bv[3] & mv[3])) return (1); if ((av[3] & mv[3]) < (bv[3] & mv[3])) return (-1); if ((av[2] & mv[2]) > (bv[2] & mv[2])) return (1); if ((av[2] & mv[2]) < (bv[2] & mv[2])) return (-1); if ((av[1] & mv[1]) > (bv[1] & mv[1])) return (1); if ((av[1] & mv[1]) < (bv[1] & mv[1])) return (-1); if ((av[0] & mv[0]) > (bv[0] & mv[0])) return (1); if ((av[0] & mv[0]) < (bv[0] & mv[0])) return (-1); break; } return (0); } uint32_t prefixlen2mask(uint8_t prefixlen) { if (prefixlen == 0) return (0); if (prefixlen > 32) prefixlen = 32; return (htonl(0xffffffff << (32 - prefixlen))); } struct in6_addr * prefixlen2mask6(uint8_t prefixlen, uint32_t *mask) { static struct in6_addr s6; int i; if (prefixlen > 128) prefixlen = 128; memset(&s6, 0, sizeof(s6)); for (i = 0; i < prefixlen / 8; i++) s6.s6_addr[i] = 0xff; i = prefixlen % 8; if (i) s6.s6_addr[prefixlen / 8] = 0xff00 >> i; memcpy(mask, &s6, sizeof(s6)); return (&s6); } int accept_reserve(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int reserve, volatile int *counter) { int ret; if (getdtablecount() + reserve + *counter >= getdtablesize()) { errno = EMFILE; return (-1); } if ((ret = accept4(sockfd, addr, addrlen, SOCK_NONBLOCK)) > -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; int ret; va_start(ap, fmt); ret = vasprintf(&value, fmt, ap); va_end(ap); if (ret == -1) return (-1); /* 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 */ free(kv->kv_value); kv->kv_value = value; return (0); } int kv_setkey(struct kv *kv, char *fmt, ...) { va_list ap; char *key = NULL; int ret; va_start(ap, fmt); ret = vasprintf(&key, fmt, ap); va_end(ap); if (ret == -1) return (-1); 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) { free(kv->kv_key); kv->kv_key = NULL; free(kv->kv_value); kv->kv_value = NULL; memset(kv, 0, sizeof(*kv)); } struct kv * kv_find(struct kvtree *keys, struct kv *kv) { return (RB_FIND(kvtree, keys, kv)); } int kv_cmp(struct kv *a, struct kv *b) { return (strcasecmp(a->kv_key, b->kv_key)); } RB_GENERATE(kvtree, kv, kv_node, kv_cmp); struct media_type * media_add(struct mediatypes *types, struct media_type *media) { struct media_type *entry; if ((entry = RB_FIND(mediatypes, types, media)) != NULL) { log_debug("%s: entry overwritten for \"%s\"", __func__, media->media_name); media_delete(types, entry); } if ((entry = malloc(sizeof(*media))) == NULL) return (NULL); memcpy(entry, media, sizeof(*entry)); if (media->media_encoding != NULL && (entry->media_encoding = strdup(media->media_encoding)) == NULL) { free(entry); return (NULL); } RB_INSERT(mediatypes, types, entry); return (entry); } void media_delete(struct mediatypes *types, struct media_type *media) { RB_REMOVE(mediatypes, types, media); free(media->media_encoding); free(media); } void media_purge(struct mediatypes *types) { struct media_type *media; while ((media = RB_MIN(mediatypes, types)) != NULL) media_delete(types, media); } struct media_type * media_find(struct mediatypes *types, const char *file) { struct media_type *match, media; char *p; /* Last component of the file name */ p = strchr(file, '\0'); while (p > file && p[-1] != '.' && p[-1] != '/') p--; if (*p == '\0') return (NULL); if (strlcpy(media.media_name, p, sizeof(media.media_name)) >= sizeof(media.media_name)) { return (NULL); } /* Find media type by extension name */ match = RB_FIND(mediatypes, types, &media); return (match); } struct media_type * media_find_config(struct httpd *env, struct server_config *srv_conf, const char *file) { struct media_type *match; if ((match = media_find(env->sc_mediatypes, file)) != NULL) return (match); else if (srv_conf->flags & SRVFLAG_DEFAULT_TYPE) return (&srv_conf->default_type); /* fallback to the global default type */ return (&env->sc_default_type); } int media_cmp(struct media_type *a, struct media_type *b) { return (strcasecmp(a->media_name, b->media_name)); } RB_GENERATE(mediatypes, media_type, media_entry, media_cmp); struct auth * auth_add(struct serverauth *serverauth, struct auth *auth) { struct auth *entry; TAILQ_FOREACH(entry, serverauth, auth_entry) { if (strcmp(entry->auth_htpasswd, auth->auth_htpasswd) == 0) return (entry); } if ((entry = calloc(1, sizeof(*entry))) == NULL) return (NULL); memcpy(entry, auth, sizeof(*entry)); TAILQ_INSERT_TAIL(serverauth, entry, auth_entry); return (entry); } struct auth * auth_byid(struct serverauth *serverauth, uint32_t id) { struct auth *auth; TAILQ_FOREACH(auth, serverauth, auth_entry) { if (auth->auth_id == id) return (auth); } return (NULL); } void auth_free(struct serverauth *serverauth, struct auth *auth) { TAILQ_REMOVE(serverauth, auth, auth_entry); } 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 * printb_flags(const uint32_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((unsigned 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); }