[BACK]Return to relayd.c CVS log [TXT][DIR] Up to [local] / src / usr.sbin / relayd

File: [local] / src / usr.sbin / relayd / relayd.c (download)

Revision 1.191, Sun Jun 25 08:07:38 2023 UTC (11 months, 2 weeks ago) by op
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, HEAD
Changes since 1.190: +1 -4 lines

remove ssl_init()

it's a noop; nowadays both LibreSSL and OpenSSL libcrypto and libssl
initialize themselves automatically before doing anything.

ok tb

/*	$OpenBSD: relayd.c,v 1.191 2023/06/25 08:07:38 op Exp $	*/

/*
 * Copyright (c) 2007 - 2016 Reyk Floeter <reyk@openbsd.org>
 * Copyright (c) 2006 Pierre-Yves Ritschard <pyr@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/wait.h>
#include <sys/resource.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <getopt.h>
#include <fnmatch.h>
#include <syslog.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 <tls.h>

#include "relayd.h"

#define MAXIMUM(a, b)	(((a) > (b)) ? (a) : (b))

__dead void	 usage(void);

int		 parent_configure(struct relayd *);
void		 parent_configure_done(struct relayd *);
void		 parent_reload(struct relayd *, u_int, const char *);
void		 parent_sig_handler(int, short, void *);
void		 parent_shutdown(struct relayd *);
int		 parent_dispatch_pfe(int, struct privsep_proc *, struct imsg *);
int		 parent_dispatch_hce(int, struct privsep_proc *, struct imsg *);
int		 parent_dispatch_relay(int, struct privsep_proc *,
		    struct imsg *);
int		 parent_dispatch_ca(int, struct privsep_proc *,
		    struct imsg *);
int		 bindany(struct ctl_bindany *);
void		 parent_tls_ticket_rekey(int, short, void *);

struct relayd			*relayd_env;

static struct privsep_proc procs[] = {
	{ "pfe",	PROC_PFE, parent_dispatch_pfe, pfe },
	{ "hce",	PROC_HCE, parent_dispatch_hce, hce },
	{ "relay",	PROC_RELAY, parent_dispatch_relay, relay },
	{ "ca",		PROC_CA, parent_dispatch_ca, ca }
};

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:
	case SIGUSR1:
		/* 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 relayd		*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:nI:P:f: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 |= RELAYD_OPT_NOACTION;
			break;
		case 'f':
			conffile = optarg;
			break;
		case 'v':
			verbose++;
			opts |= RELAYD_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);

	relayd_env = env;
	env->sc_ps = ps;
	ps->ps_env = env;
	TAILQ_INIT(&ps->ps_rcsocks);
	env->sc_conffile = conffile;
	env->sc_conf.opts = opts;
	TAILQ_INIT(&env->sc_hosts);
	TAILQ_INIT(&env->sc_sessions);
	env->sc_rtable = getrtable();
	/* initialize the TLS session id to a random key for all relay procs */
	arc4random_buf(env->sc_conf.tls_sid, sizeof(env->sc_conf.tls_sid));

	if (parse_config(env->sc_conffile, env) == -1)
		exit(1);

	if (debug)
		env->sc_conf.opts |= RELAYD_OPT_LOGUPDATE;

	if (geteuid())
		errx(1, "need root privileges");

	if ((ps->ps_pw =  getpwnam(RELAYD_USER)) == NULL)
		errx(1, "unknown user %s", RELAYD_USER);

	log_init(debug, LOG_DAEMON);
	log_setverbose(verbose);

	if (env->sc_conf.opts & RELAYD_OPT_NOACTION)
		ps->ps_noaction = 1;

	ps->ps_instances[PROC_RELAY] = env->sc_conf.prefork_relay;
	ps->ps_instances[PROC_CA] = env->sc_conf.prefork_relay;
	ps->ps_instance = proc_instance;
	if (title != NULL)
		ps->ps_title[proc_id] = title;

	/* 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 (unveil("/", "rx") == -1)
		err(1, "unveil /");
	if (unveil(NULL, NULL) == -1)
		err(1, "unveil");

	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);

	relay_http(NULL);
	if (load_config(env->sc_conffile, env) == -1) {
		proc_kill(env->sc_ps);
		exit(1);
	}

	if (env->sc_conf.opts & RELAYD_OPT_NOACTION) {
		fprintf(stderr, "configuration OK\n");
		proc_kill(env->sc_ps);
		exit(0);
	}

	/* rekey the TLS tickets before pushing the config */
	parent_tls_ticket_rekey(0, 0, env);
	if (parent_configure(env) == -1)
		fatalx("configuration failed");

	init_routes(env);

	event_dispatch();

	parent_shutdown(env);
	/* NOTREACHED */

	return (0);
}

int
parent_configure(struct relayd *env)
{
	struct table		*tb;
	struct rdr		*rdr;
	struct router		*rt;
	struct protocol		*proto;
	struct relay		*rlay;
	int			 id;
	int			 ret = -1;

	TAILQ_FOREACH(tb, env->sc_tables, entry)
		config_settable(env, tb);
	TAILQ_FOREACH(rdr, env->sc_rdrs, entry)
		config_setrdr(env, rdr);
	TAILQ_FOREACH(rt, env->sc_rts, rt_entry)
		config_setrt(env, rt);
	TAILQ_FOREACH(proto, env->sc_protos, entry)
		config_setproto(env, proto);
	TAILQ_FOREACH(proto, env->sc_protos, entry)
		config_setrule(env, proto);
	TAILQ_FOREACH(rlay, env->sc_relays, rl_entry) {
		/* Check for TLS Inspection */
		if ((rlay->rl_conf.flags & (F_TLS|F_TLSCLIENT)) ==
		    (F_TLS|F_TLSCLIENT) && rlay->rl_tls_cacert_fd != -1)
			rlay->rl_conf.flags |= F_TLSINSPECT;

		config_setrelay(env, rlay);
	}

	/* HCE, PFE, CA and the relays need to reload their config. */
	env->sc_reload = 2 + (2 * env->sc_conf.prefork_relay);

	for (id = 0; id < PROC_MAX; id++) {
		if (id == privsep_process)
			continue;
		proc_compose_imsg(env->sc_ps, id, -1, IMSG_CFG_DONE, -1,
		    -1, &env->sc_conf, sizeof(env->sc_conf));
	}

	ret = 0;

	config_purge(env, CONFIG_ALL & ~CONFIG_RELAYS);
	return (ret);
}

void
parent_reload(struct relayd *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 relayd *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 relayd *env)
{
	config_purge(env, CONFIG_ALL);

	proc_kill(env->sc_ps);
	control_cleanup(&env->sc_ps->ps_csock);
	carp_demote_shutdown();

	free(env->sc_ps);
	free(env);

	log_info("parent terminating, pid %d", getpid());

	exit(0);
}

int
parent_dispatch_pfe(int fd, struct privsep_proc *p, struct imsg *imsg)
{
	struct privsep		*ps = p->p_ps;
	struct relayd		*env = ps->ps_env;
	struct ctl_demote	 demote;
	struct ctl_netroute	 crt;
	u_int			 v;
	char			*str = NULL;

	switch (imsg->hdr.type) {
	case IMSG_DEMOTE:
		IMSG_SIZE_CHECK(imsg, &demote);
		memcpy(&demote, imsg->data, sizeof(demote));
		carp_demote_set(demote.group, demote.level);
		break;
	case IMSG_RTMSG:
		IMSG_SIZE_CHECK(imsg, &crt);
		memcpy(&crt, imsg->data, sizeof(crt));
		pfe_route(env, &crt);
		break;
	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_CFG_DONE:
		parent_configure_done(env);
		break;
	case IMSG_AGENTXSOCK:
		agentx_setsock(env, p->p_id);
		break;
	default:
		return (-1);
	}

	return (0);
}

int
parent_dispatch_hce(int fd, struct privsep_proc *p, struct imsg *imsg)
{
	struct privsep		*ps = p->p_ps;
	struct relayd		*env = ps->ps_env;
	struct ctl_script	 scr;

	switch (imsg->hdr.type) {
	case IMSG_SCRIPT:
		IMSG_SIZE_CHECK(imsg, &scr);
		bcopy(imsg->data, &scr, sizeof(scr));
		scr.retval = script_exec(env, &scr);
		proc_compose(ps, PROC_HCE, IMSG_SCRIPT, &scr, sizeof(scr));
		break;
	case IMSG_CFG_DONE:
		parent_configure_done(env);
		break;
	default:
		return (-1);
	}

	return (0);
}

int
parent_dispatch_relay(int fd, struct privsep_proc *p, struct imsg *imsg)
{
	struct privsep		*ps = p->p_ps;
	struct relayd		*env = ps->ps_env;
	struct ctl_bindany	 bnd;
	int			 s;

	switch (imsg->hdr.type) {
	case IMSG_BINDANY:
		IMSG_SIZE_CHECK(imsg, &bnd);
		bcopy(imsg->data, &bnd, sizeof(bnd));
		if (bnd.bnd_proc > env->sc_conf.prefork_relay)
			fatalx("%s: invalid relay proc", __func__);
		switch (bnd.bnd_proto) {
		case IPPROTO_TCP:
		case IPPROTO_UDP:
			break;
		default:
			fatalx("%s: requested socket "
			    "for invalid protocol", __func__);
			/* NOTREACHED */
		}
		s = bindany(&bnd);
		proc_compose_imsg(ps, PROC_RELAY, bnd.bnd_proc,
		    IMSG_BINDANY, -1, s, &bnd.bnd_id, sizeof(bnd.bnd_id));
		break;
	case IMSG_CFG_DONE:
		parent_configure_done(env);
		break;
	default:
		return (-1);
	}

	return (0);
}

int
parent_dispatch_ca(int fd, struct privsep_proc *p, struct imsg *imsg)
{
	struct privsep		*ps = p->p_ps;
	struct relayd		*env = ps->ps_env;

	switch (imsg->hdr.type) {
	case IMSG_CFG_DONE:
		parent_configure_done(env);
		break;
	default:
		return (-1);
	}

	return (0);
}

void
purge_table(struct relayd *env, struct tablelist *head, struct table *table)
{
	struct host		*host;

	while ((host = TAILQ_FIRST(&table->hosts)) != NULL) {
		TAILQ_REMOVE(&table->hosts, host, entry);
		TAILQ_REMOVE(&env->sc_hosts, host, globalentry);
		if (event_initialized(&host->cte.ev)) {
			event_del(&host->cte.ev);
			close(host->cte.s);
		}
		ibuf_free(host->cte.buf);
		tls_free(host->cte.tls);
		free(host);
	}
	free(table->sendbuf);
	ibuf_free(table->sendbinbuf);
	tls_config_free(table->tls_cfg);

	if (head != NULL)
		TAILQ_REMOVE(head, table, entry);
	free(table);
}

void
purge_key(char **key, off_t len)
{
	freezero(*key, len);

	*key = NULL;
}

void
purge_relay(struct relayd *env, struct relay *rlay)
{
	struct rsession		*con;
	struct relay_table	*rlt;
	struct relay_cert	*cert, *tmpcert;

	/* shutdown and remove relay */
	if (event_initialized(&rlay->rl_ev))
		event_del(&rlay->rl_ev);
	close(rlay->rl_s);
	TAILQ_REMOVE(env->sc_relays, rlay, rl_entry);

	/* cleanup sessions */
	while ((con =
	    SPLAY_ROOT(&rlay->rl_sessions)) != NULL)
		relay_close(con, NULL, 0);

	/* cleanup relay */
	if (rlay->rl_bev != NULL)
		bufferevent_free(rlay->rl_bev);
	if (rlay->rl_dstbev != NULL)
		bufferevent_free(rlay->rl_dstbev);

	purge_key(&rlay->rl_tls_cakey, rlay->rl_conf.tls_cakey_len);

	if (rlay->rl_tls_pkey != NULL) {
		EVP_PKEY_free(rlay->rl_tls_pkey);
		rlay->rl_tls_pkey = NULL;
	}
	if (rlay->rl_tls_cacertx509 != NULL) {
		X509_free(rlay->rl_tls_cacertx509);
		rlay->rl_tls_cacertx509 = NULL;
	}
	if (rlay->rl_tls_capkey != NULL) {
		EVP_PKEY_free(rlay->rl_tls_capkey);
		rlay->rl_tls_capkey = NULL;
	}

	tls_free(rlay->rl_tls_ctx);
	tls_config_free(rlay->rl_tls_cfg);
	tls_config_free(rlay->rl_tls_client_cfg);

	while ((rlt = TAILQ_FIRST(&rlay->rl_tables))) {
		TAILQ_REMOVE(&rlay->rl_tables, rlt, rlt_entry);
		free(rlt);
	}

	TAILQ_FOREACH_SAFE(cert, env->sc_certs, cert_entry, tmpcert) {
		if (rlay->rl_conf.id != cert->cert_relayid)
			continue;
		if (cert->cert_fd != -1)
			close(cert->cert_fd);
		if (cert->cert_key_fd != -1)
			close(cert->cert_key_fd);
		if (cert->cert_ocsp_fd != -1)
			close(cert->cert_ocsp_fd);
		if (cert->cert_pkey != NULL)
			EVP_PKEY_free(cert->cert_pkey);
		TAILQ_REMOVE(env->sc_certs, cert, cert_entry);
		free(cert);
	}

	free(rlay);
}

struct kv *
kv_add(struct kvtree *keys, char *key, char *value, int unique)
{
	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)
		goto fail;
	if (value != NULL &&
	    (kv->kv_value = strdup(value)) == NULL)
		goto fail;
	TAILQ_INIT(&kv->kv_children);

	if ((oldkv = RB_INSERT(kvtree, keys, kv)) != NULL) {
		/*
		 * return error if the key should occur only once,
		 * or add it to a list attached to the key's node.
		 */
		if (unique)
			goto fail;
		TAILQ_INSERT_TAIL(&oldkv->kv_children, kv, kv_entry);
		kv->kv_parent = oldkv;
	}

	return (kv);
 fail:
	free(kv->kv_key);
	free(kv->kv_value);
	free(kv);
	return (NULL);
}

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)
{
	/*
	 * This function does not clear memory referenced by
	 * kv_children or stuff on the tailqs. Use kv_delete() instead.
	 */

	free(kv->kv_key);
	free(kv->kv_value);
	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);
		}
	}

	if (src->kv_match != NULL)
		dst->kv_match = src->kv_match;
	if (src->kv_matchtree != NULL)
		dst->kv_matchtree = src->kv_matchtree;

	return (dst);
}

int
kv_log(struct rsession *con, struct kv *kv, u_int16_t labelid,
    enum direction dir)
{
	char	*msg;

	if (con->se_log == NULL)
		return (0);
	if (asprintf(&msg, " %s%s%s%s%s%s%s",
	    dir == RELAY_DIR_REQUEST ? "[" : "{",
	    labelid == 0 ? "" : label_id2name(labelid),
	    labelid == 0 ? "" : ", ",
	    kv->kv_key == NULL ? "(unknown)" : kv->kv_key,
	    kv->kv_value == NULL ? "" : ": ",
	    kv->kv_value == NULL ? "" : kv->kv_value,
	    dir == RELAY_DIR_REQUEST ? "]" : "}") == -1)
		return (-1);
	if (evbuffer_add(con->se_log, msg, strlen(msg)) == -1) {
		free(msg);
		return (-1);
	}
	free(msg);
	con->se_haslog = 1;
	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);
}

struct kv *
kv_find_value(struct kvtree *keys, char *key, const char *value,
    const char *delim)
{
	struct kv	*match, kv;
	char		*val = NULL, *next, *ptr;
	size_t		 len;

	kv.kv_key = key;
	if ((match = RB_FIND(kvtree, keys, &kv)) == NULL)
		return (NULL);

	if (match->kv_value == NULL)
		return (NULL);

	if (delim == NULL) {
		if (strcasecmp(match->kv_value, value) == 0)
			goto done;
	} else {
		if ((val = strdup(match->kv_value)) == NULL)
			return (NULL);
		for (next = ptr = val; ptr != NULL;
		    ptr = strsep(&next, delim)) {
			/* strip whitespace */
			ptr += strspn(ptr, " \t");
			len = strcspn(ptr, " \t");
			if (strncasecmp(ptr, value, len) == 0)
				goto done;
		}
	}

	/* not matched */
	match = NULL;
 done:
#ifdef DEBUG
	if (match != NULL)
		DPRINTF("%s: matched %s: %s", __func__, key, value);
#endif
	free(val);
	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);

int
rule_add(struct protocol *proto, struct relay_rule *rule, const char *rulefile)
{
	struct relay_rule	*r = NULL;
	struct kv		*kv = NULL;
	FILE			*fp = NULL;
	char			 buf[BUFSIZ];
	int			 ret = -1;
	u_int			 i;

	for (i = 0; i < KEY_TYPE_MAX; i++) {
		kv = &rule->rule_kv[i];
		if (kv->kv_type != i)
			continue;

		switch (kv->kv_option) {
		case KEY_OPTION_LOG:
			/* log action needs a key or a file to be specified */
			if (kv->kv_key == NULL && rulefile == NULL &&
			    (kv->kv_key = strdup("*")) == NULL)
				goto fail;
			break;
		default:
			break;
		}

		switch (kv->kv_type) {
		case KEY_TYPE_QUERY:
		case KEY_TYPE_PATH:
		case KEY_TYPE_URL:
			if (rule->rule_dir != RELAY_DIR_REQUEST)
				goto fail;
			break;
		default:
			break;
		}

		if (kv->kv_value != NULL && strchr(kv->kv_value, '$') != NULL)
			kv->kv_flags |= KV_FLAG_MACRO;
		if (kv->kv_key != NULL && strpbrk(kv->kv_key, "*?[") != NULL)
			kv->kv_flags |= KV_FLAG_GLOBBING;
	}

	if (rulefile == NULL) {
		TAILQ_INSERT_TAIL(&proto->rules, rule, rule_entry);
		return (0);
	}

	if ((fp = fopen(rulefile, "r")) == NULL)
		goto fail;

	while (fgets(buf, sizeof(buf), fp) != NULL) {
		/* strip whitespace and newline characters */
		buf[strcspn(buf, "\r\n\t ")] = '\0';
		if (!strlen(buf) || buf[0] == '#')
			continue;

		if ((r = rule_inherit(rule)) == NULL)
			goto fail;

		for (i = 0; i < KEY_TYPE_MAX; i++) {
			kv = &r->rule_kv[i];
			if (kv->kv_type != i)
				continue;
			free(kv->kv_key);
			if ((kv->kv_key = strdup(buf)) == NULL) {
				rule_free(r);
				free(r);
				goto fail;
			}
		}

		TAILQ_INSERT_TAIL(&proto->rules, r, rule_entry);
	}

	ret = 0;
	rule_free(rule);
	free(rule);

 fail:
	if (fp != NULL)
		fclose(fp);
	return (ret);
}

struct relay_rule *
rule_inherit(struct relay_rule *rule)
{
	struct relay_rule	*r;
	u_int			 i;
	struct kv		*kv;

	if ((r = calloc(1, sizeof(*r))) == NULL)
		return (NULL);
	memcpy(r, rule, sizeof(*r));

	for (i = 0; i < KEY_TYPE_MAX; i++) {
		kv = &rule->rule_kv[i];
		if (kv->kv_type != i)
			continue;
		if (kv_inherit(&r->rule_kv[i], kv) == NULL) {
			free(r);
			return (NULL);
		}
	}

	if (r->rule_label > 0)
		label_ref(r->rule_label);
	if (r->rule_tag > 0)
		tag_ref(r->rule_tag);
	if (r->rule_tagged > 0)
		tag_ref(r->rule_tagged);

	return (r);
}

void
rule_free(struct relay_rule *rule)
{
	u_int	i;

	for (i = 0; i < KEY_TYPE_MAX; i++)
		kv_free(&rule->rule_kv[i]);
	if (rule->rule_label > 0)
		label_unref(rule->rule_label);
	if (rule->rule_tag > 0)
		tag_unref(rule->rule_tag);
	if (rule->rule_tagged > 0)
		tag_unref(rule->rule_tagged);
}

void
rule_delete(struct relay_rules *rules, struct relay_rule *rule)
{
	TAILQ_REMOVE(rules, rule, rule_entry);
	rule_free(rule);
	free(rule);
}

void
rule_settable(struct relay_rules *rules, struct relay_table *rlt)
{
	struct relay_rule	*r;
	char			 pname[TABLE_NAME_SIZE];

	if (rlt->rlt_table == NULL || strlcpy(pname, rlt->rlt_table->conf.name,
	    sizeof(pname)) >= sizeof(pname))
		return;

	pname[strcspn(pname, ":")] = '\0';

	TAILQ_FOREACH(r, rules, rule_entry) {
		if (r->rule_tablename[0] &&
		    strcmp(pname, r->rule_tablename) == 0)
			r->rule_table = rlt;
	}
}

/*
 * Utility functions
 */

struct host *
host_find(struct relayd *env, objid_t id)
{
	struct table	*table;
	struct host	*host;

	TAILQ_FOREACH(table, env->sc_tables, entry)
		TAILQ_FOREACH(host, &table->hosts, entry)
			if (host->conf.id == id)
				return (host);
	return (NULL);
}

struct table *
table_find(struct relayd *env, objid_t id)
{
	struct table	*table;

	TAILQ_FOREACH(table, env->sc_tables, entry)
		if (table->conf.id == id)
			return (table);
	return (NULL);
}

struct rdr *
rdr_find(struct relayd *env, objid_t id)
{
	struct rdr	*rdr;

	TAILQ_FOREACH(rdr, env->sc_rdrs, entry)
		if (rdr->conf.id == id)
			return (rdr);
	return (NULL);
}

struct relay *
relay_find(struct relayd *env, objid_t id)
{
	struct relay	*rlay;

	TAILQ_FOREACH(rlay, env->sc_relays, rl_entry)
		if (rlay->rl_conf.id == id)
			return (rlay);
	return (NULL);
}

struct protocol *
proto_find(struct relayd *env, objid_t id)
{
	struct protocol	*p;

	TAILQ_FOREACH(p, env->sc_protos, entry)
		if (p->id == id)
			return (p);
	return (NULL);
}

struct rsession *
session_find(struct relayd *env, objid_t id)
{
	struct relay		*rlay;
	struct rsession		*con;

	TAILQ_FOREACH(rlay, env->sc_relays, rl_entry)
		SPLAY_FOREACH(con, session_tree, &rlay->rl_sessions)
			if (con->se_id == id)
				return (con);
	return (NULL);
}

struct netroute *
route_find(struct relayd *env, objid_t id)
{
	struct netroute	*nr;

	TAILQ_FOREACH(nr, env->sc_routes, nr_route)
		if (nr->nr_conf.id == id)
			return (nr);
	return (NULL);
}

struct router *
router_find(struct relayd *env, objid_t id)
{
	struct router	*rt;

	TAILQ_FOREACH(rt, env->sc_rts, rt_entry)
		if (rt->rt_conf.id == id)
			return (rt);
	return (NULL);
}

struct host *
host_findbyname(struct relayd *env, const char *name)
{
	struct table	*table;
	struct host	*host;

	TAILQ_FOREACH(table, env->sc_tables, entry)
		TAILQ_FOREACH(host, &table->hosts, entry)
			if (strcmp(host->conf.name, name) == 0)
				return (host);
	return (NULL);
}

struct table *
table_findbyname(struct relayd *env, const char *name)
{
	struct table	*table;

	TAILQ_FOREACH(table, env->sc_tables, entry)
		if (strcmp(table->conf.name, name) == 0)
			return (table);
	return (NULL);
}

struct table *
table_findbyconf(struct relayd *env, struct table *tb)
{
	struct table		*table;
	struct table_config	 a, b;

	bcopy(&tb->conf, &a, sizeof(a));
	a.id = a.rdrid = 0;
	a.flags &= ~(F_USED|F_BACKUP);

	TAILQ_FOREACH(table, env->sc_tables, entry) {
		bcopy(&table->conf, &b, sizeof(b));
		b.id = b.rdrid = 0;
		b.flags &= ~(F_USED|F_BACKUP);

		/*
		 * Compare two tables and return the existing table if
		 * the configuration seems to be the same.
		 */
		if (bcmp(&a, &b, sizeof(b)) == 0 &&
		    ((tb->sendbuf == NULL && table->sendbuf == NULL) ||
		    (tb->sendbuf != NULL && table->sendbuf != NULL &&
		    strcmp(tb->sendbuf, table->sendbuf) == 0)))
			return (table);
	}
	return (NULL);
}

struct rdr *
rdr_findbyname(struct relayd *env, const char *name)
{
	struct rdr	*rdr;

	TAILQ_FOREACH(rdr, env->sc_rdrs, entry)
		if (strcmp(rdr->conf.name, name) == 0)
			return (rdr);
	return (NULL);
}

struct relay *
relay_findbyname(struct relayd *env, const char *name)
{
	struct relay	*rlay;

	TAILQ_FOREACH(rlay, env->sc_relays, rl_entry)
		if (strcmp(rlay->rl_conf.name, name) == 0)
			return (rlay);
	return (NULL);
}

struct relay *
relay_findbyaddr(struct relayd *env, struct relay_config *rc)
{
	struct relay	*rlay;

	TAILQ_FOREACH(rlay, env->sc_relays, rl_entry)
		if (bcmp(&rlay->rl_conf.ss, &rc->ss, sizeof(rc->ss)) == 0 &&
		    rlay->rl_conf.port == rc->port)
			return (rlay);
	return (NULL);
}

EVP_PKEY *
pkey_find(struct relayd *env, char * hash)
{
	struct ca_pkey	*pkey;

	TAILQ_FOREACH(pkey, env->sc_pkeys, pkey_entry)
		if (strcmp(hash, pkey->pkey_hash) == 0)
			return (pkey->pkey);
	return (NULL);
}

struct ca_pkey *
pkey_add(struct relayd *env, EVP_PKEY *pkey, char *hash)
{
	struct ca_pkey	*ca_pkey;

	if (env->sc_pkeys == NULL)
		fatalx("pkeys");

	if ((ca_pkey = calloc(1, sizeof(*ca_pkey))) == NULL)
		return (NULL);

	ca_pkey->pkey = pkey;
	if (strlcpy(ca_pkey->pkey_hash, hash, sizeof(ca_pkey->pkey_hash)) >=
	    sizeof(ca_pkey->pkey_hash)) {
		free(ca_pkey);
		return (NULL);
	}

	TAILQ_INSERT_TAIL(env->sc_pkeys, ca_pkey, pkey_entry);

	return (ca_pkey);
}

struct relay_cert *
cert_add(struct relayd *env, objid_t id)
{
	static objid_t		 last_cert_id = 0;
	struct relay_cert	*cert;

	if ((cert = calloc(1, sizeof(*cert))) == NULL)
		return (NULL);

	if (id == 0)
		id = ++last_cert_id;
	if (id == INT_MAX) {
		log_warnx("too many tls keypairs defined");
		free(cert);
		return (NULL);
	}

	cert->cert_id = id;
	cert->cert_fd = -1;
	cert->cert_key_fd = -1;
	cert->cert_ocsp_fd = -1;

	TAILQ_INSERT_TAIL(env->sc_certs, cert, cert_entry);

	return (cert);
}

struct relay_cert *
cert_find(struct relayd *env, objid_t id)
{
	struct relay_cert	*cert;

	TAILQ_FOREACH(cert, env->sc_certs, cert_entry)
		if (cert->cert_id == id)
			return (cert);
	return (NULL);
}

char *
relay_load_fd(int fd, off_t *len)
{
	char		*buf = NULL;
	struct stat	 st;
	off_t		 size;
	ssize_t		 rv;
	int		 err;

	if (fstat(fd, &st) != 0)
		goto fail;
	size = st.st_size;
	if ((buf = calloc(1, size + 1)) == NULL)
		goto fail;
	if ((rv = pread(fd, buf, size, 0)) != size)
		goto fail;

	close(fd);

	*len = size;
	return (buf);

 fail:
	err = errno;
	free(buf);
	close(fd);
	errno = err;
	return (NULL);
}

int
relay_load_certfiles(struct relayd *env, struct relay *rlay, const char *name)
{
	char	 certfile[PATH_MAX];
	char	 hbuf[PATH_MAX];
	struct protocol *proto = rlay->rl_proto;
	struct relay_cert *cert;
	int	 useport = htons(rlay->rl_conf.port);
	int	 cert_fd = -1, key_fd = -1, ocsp_fd = -1;

	if (rlay->rl_conf.flags & F_TLSCLIENT) {
		if (strlen(proto->tlsca) && rlay->rl_tls_ca_fd == -1) {
			if ((rlay->rl_tls_ca_fd =
			    open(proto->tlsca, O_RDONLY)) == -1)
				return (-1);
			log_debug("%s: using ca %s", __func__, proto->tlsca);
		}
		if (strlen(proto->tlscacert) && rlay->rl_tls_cacert_fd == -1) {
			if ((rlay->rl_tls_cacert_fd =
			    open(proto->tlscacert, O_RDONLY)) == -1)
				return (-1);
			log_debug("%s: using ca certificate %s", __func__,
			    proto->tlscacert);
		}
		if (strlen(proto->tlscakey) && !rlay->rl_conf.tls_cakey_len &&
		    proto->tlscapass != NULL) {
			if ((rlay->rl_tls_cakey =
			    ssl_load_key(env, proto->tlscakey,
			    &rlay->rl_conf.tls_cakey_len,
			    proto->tlscapass)) == NULL)
				return (-1);
			log_debug("%s: using ca key %s", __func__,
			    proto->tlscakey);
		}
	}

	if ((rlay->rl_conf.flags & F_TLS) == 0)
		return (0);

	if (name == NULL &&
	    print_host(&rlay->rl_conf.ss, hbuf, sizeof(hbuf)) == NULL)
		goto fail;
	else if (name != NULL &&
	    strlcpy(hbuf, name, sizeof(hbuf)) >= sizeof(hbuf))
		goto fail;

	if (snprintf(certfile, sizeof(certfile),
	    "/etc/ssl/%s:%u.crt", hbuf, useport) == -1)
		goto fail;
	if ((cert_fd = open(certfile, O_RDONLY)) == -1) {
		if (snprintf(certfile, sizeof(certfile),
		    "/etc/ssl/%s.crt", hbuf) == -1)
			goto fail;
		if ((cert_fd = open(certfile, O_RDONLY)) == -1)
			goto fail;
		useport = 0;
	}
	log_debug("%s: using certificate %s", __func__, certfile);

	if (useport) {
		if (snprintf(certfile, sizeof(certfile),
		    "/etc/ssl/private/%s:%u.key", hbuf, useport) == -1)
			goto fail;
	} else {
		if (snprintf(certfile, sizeof(certfile),
		    "/etc/ssl/private/%s.key", hbuf) == -1)
			goto fail;
	}
	if ((key_fd = open(certfile, O_RDONLY)) == -1)
		goto fail;
	log_debug("%s: using private key %s", __func__, certfile);

	if (useport) {
		if (snprintf(certfile, sizeof(certfile),
		    "/etc/ssl/%s:%u.ocsp", hbuf, useport) == -1)
			goto fail;
	} else {
		if (snprintf(certfile, sizeof(certfile),
		    "/etc/ssl/%s.ocsp", hbuf) == -1)
			goto fail;
	}
	if ((ocsp_fd = open(certfile, O_RDONLY)) != -1)
		log_debug("%s: using OCSP staple file %s", __func__, certfile);

	if ((cert = cert_add(env, 0)) == NULL)
		goto fail;

	cert->cert_relayid = rlay->rl_conf.id;
	cert->cert_fd = cert_fd;
	cert->cert_key_fd = key_fd;
	cert->cert_ocsp_fd = ocsp_fd;

	return (0);

 fail:
	if (cert_fd != -1)
		close(cert_fd);
	if (key_fd != -1)
		close(key_fd);
	if (ocsp_fd != -1)
		close(ocsp_fd);

	return (-1);
}

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);
}

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);
}

void
translate_string(char *str)
{
	char	*reader;
	char	*writer;

	reader = writer = str;

	while (*reader) {
		if (*reader == '\\') {
			reader++;
			switch (*reader) {
			case 'n':
				*writer++ = '\n';
				break;
			case 'r':
				*writer++ = '\r';
				break;
			default:
				*writer++ = *reader;
			}
		} else
			*writer++ = *reader;
		reader++;
	}
	*writer = '\0';
}

char *
digeststr(enum digest_type type, const u_int8_t *data, size_t len, char *buf)
{
	switch (type) {
	case DIGEST_SHA1:
		return (SHA1Data(data, len, buf));
		break;
	case DIGEST_MD5:
		return (MD5Data(data, len, buf));
		break;
	default:
		break;
	}
	return (NULL);
}

const char *
canonicalize_host(const char *host, char *name, size_t len)
{
	struct sockaddr_in	 sin4;
	struct sockaddr_in6	 sin6;
	size_t			 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((unsigned char)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);
}

int
parse_url(const char *url, char **protoptr, char **hostptr, char **pathptr)
{
	char	*p, *proto = NULL, *host = NULL, *path = NULL;

	/* return error if it is not a URL */
	if ((p = strstr(url, ":/")) == NULL ||
	    (strcspn(url, ":/") != (size_t)(p - url)))
		return (-1);

	/* get protocol */
	if ((proto = strdup(url)) == NULL)
		goto fail;
	p = proto + (p - url);

	/* get host */
	p += strspn(p, ":/");
	if (*p == '\0' || (host = strdup(p)) == NULL)
		goto fail;
	*p = '\0';

	/* find and copy path or default to "/" */
	if ((p = strchr(host, '/')) == NULL)
		p = "/";
	if ((path = strdup(p)) == NULL)
		goto fail;

	/* strip path after host */
	host[strcspn(host, "/")] = '\0';

	DPRINTF("%s: %s proto %s, host %s, path %s", __func__,
	    url, proto, host, path);

	*protoptr = proto;
	*hostptr = host;
	*pathptr = path;

	return (0);

 fail:
	free(proto);
	free(host);
	free(path);
	return (-1);
}

int
bindany(struct ctl_bindany *bnd)
{
	int	s, v;

	s = -1;
	v = 1;

	if (relay_socket_af(&bnd->bnd_ss, bnd->bnd_port) == -1)
		goto fail;
	if ((s = socket(bnd->bnd_ss.ss_family,
	    bnd->bnd_proto == IPPROTO_TCP ? SOCK_STREAM : SOCK_DGRAM,
	    bnd->bnd_proto)) == -1)
		goto fail;
	if (setsockopt(s, SOL_SOCKET, SO_BINDANY,
	    &v, sizeof(v)) == -1)
		goto fail;
	if (bind(s, (struct sockaddr *)&bnd->bnd_ss,
	    bnd->bnd_ss.ss_len) == -1)
		goto fail;

	return (s);

 fail:
	if (s != -1)
		close(s);
	return (-1);
}

int
map6to4(struct sockaddr_storage *in6)
{
	struct sockaddr_storage	 out4;
	struct sockaddr_in	*sin4 = (struct sockaddr_in *)&out4;
	struct sockaddr_in6	*sin6 = (struct sockaddr_in6 *)in6;

	bzero(sin4, sizeof(*sin4));
	sin4->sin_len = sizeof(*sin4);
	sin4->sin_family = AF_INET;
	sin4->sin_port = sin6->sin6_port;

	bcopy(&sin6->sin6_addr.s6_addr[12], &sin4->sin_addr.s_addr,
	    sizeof(sin4->sin_addr));

	if (sin4->sin_addr.s_addr == INADDR_ANY ||
	    sin4->sin_addr.s_addr == INADDR_BROADCAST ||
	    IN_MULTICAST(ntohl(sin4->sin_addr.s_addr)))
		return (-1);

	bcopy(&out4, in6, sizeof(*in6));

	return (0);
}

int
map4to6(struct sockaddr_storage *in4, struct sockaddr_storage *map)
{
	struct sockaddr_storage	 out6;
	struct sockaddr_in	*sin4 = (struct sockaddr_in *)in4;
	struct sockaddr_in6	*sin6 = (struct sockaddr_in6 *)&out6;
	struct sockaddr_in6	*map6 = (struct sockaddr_in6 *)map;

	if (sin4->sin_addr.s_addr == INADDR_ANY ||
	    sin4->sin_addr.s_addr == INADDR_BROADCAST ||
	    IN_MULTICAST(ntohl(sin4->sin_addr.s_addr)))
		return (-1);

	bcopy(map6, sin6, sizeof(*sin6));
	sin6->sin6_len = sizeof(*sin6);
	sin6->sin6_family = AF_INET6;
	sin6->sin6_port = sin4->sin_port;

	bcopy(&sin4->sin_addr.s_addr, &sin6->sin6_addr.s6_addr[12],
	    sizeof(sin4->sin_addr));

	bcopy(&out6, in4, sizeof(*in4));

	return (0);
}

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 *
get_string(u_int8_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(u_int8_t *ptr, size_t len)
{
	u_int8_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;
	u_int32_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);
}

u_int32_t
prefixlen2mask(u_int8_t prefixlen)
{
	if (prefixlen == 0)
		return (0);

	if (prefixlen > 32)
		prefixlen = 32;

	return (htonl(0xffffffff << (32 - prefixlen)));
}

struct in6_addr *
prefixlen2mask6(u_int8_t prefixlen, u_int32_t *mask)
{
	static struct in6_addr  s6;
	int			i;

	if (prefixlen > 128)
		prefixlen = 128;

	bzero(&s6, 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);
}

void
parent_tls_ticket_rekey(int fd, short events, void *arg)
{
	static struct event	 rekeyev;
	struct relayd		*env = arg;
	struct timeval		 tv;
	struct relay_ticket_key	 key;

	log_debug("%s: rekeying tickets", __func__);

	key.tt_keyrev = arc4random();
	arc4random_buf(key.tt_key, sizeof(key.tt_key));

	proc_compose_imsg(env->sc_ps, PROC_RELAY, -1, IMSG_TLSTICKET_REKEY,
	    -1, -1, &key, sizeof(key));

	evtimer_set(&rekeyev, parent_tls_ticket_rekey, env);
	timerclear(&tv);
	tv.tv_sec = TLS_SESSION_LIFETIME / 4;
	evtimer_add(&rekeyev, &tv);
}