Edit

IABSD.fr/src/usr.sbin/httpd/httpd.c

Branch :

  • Show log

    Commit

  • Author : kirill
    Date : 2026-05-11 22:33:10
    Hash : febc1864
    Message : usr.sbin/httpd: widen server flags to 64-bit integers OK job@

  • usr.sbin/httpd/httpd.c
  • /*	$OpenBSD: httpd.c,v 1.78 2026/05/11 22:33:10 kirill Exp $	*/
    
    /*
     * 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/stat.h>
    #include <sys/resource.h>
    
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdarg.h>
    #include <string.h>
    #include <signal.h>
    #include <getopt.h>
    #include <netdb.h>
    #include <fnmatch.h>
    #include <err.h>
    #include <errno.h>
    #include <event.h>
    #include <syslog.h>
    #include <unistd.h>
    #include <ctype.h>
    #include <pwd.h>
    
    #include "httpd.h"
    #include "log.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 (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;
    
    	memset(&cf, 0, sizeof(cf));
    
    	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 = 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 uint64_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 & (1ULL << (i - 1))) {
    				if (any) {
    					*p++ = ',';
    					*p++ = ' ';
    				}
    				any = 1;
    				for (; isalnum((unsigned char)*bits) ||
    				    *bits == '_'; bits++) {
    					c = *bits;
    					if (c == '_')
    						*p++ = ' ';
    					else
    						*p++ =
    						    tolower((unsigned char)c);
    				}
    			} else
    				for (; isalnum((unsigned char)*bits) ||
    				    *bits == '_'; 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);
    }