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

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

Revision 1.45, Wed Jan 17 10:01:24 2024 UTC (4 months, 3 weeks ago) by claudio
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, HEAD
Changes since 1.44: +7 -7 lines

Use imsg_get_fd()

As usual proc_forward_imsg() is never forwarding a file descriptor so
just use -1 there. This should be replaced by imsg_forward().
All other changes are simple conversions.

OK tb@

/*	$OpenBSD: config.c,v 1.45 2024/01/17 10:01:24 claudio Exp $	*/

/*
 * Copyright (c) 2011 - 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/time.h>
#include <sys/uio.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <string.h>
#include <imsg.h>

#include "relayd.h"

int
config_init(struct relayd *env)
{
	struct privsep	*ps = env->sc_ps;
	u_int		 what;

	/* Global configuration */
	if (privsep_process == PROC_PARENT) {
		env->sc_conf.timeout.tv_sec = CHECK_TIMEOUT / 1000;
		env->sc_conf.timeout.tv_usec = (CHECK_TIMEOUT % 1000) * 1000;
		env->sc_conf.interval.tv_sec = CHECK_INTERVAL;
		env->sc_conf.interval.tv_usec = 0;
		env->sc_conf.prefork_relay = RELAY_NUMPROC;
		env->sc_conf.statinterval.tv_sec = RELAY_STATINTERVAL;
		env->sc_ps->ps_csock.cs_name = RELAYD_SOCKET;
	}

	ps->ps_what[PROC_PARENT] = CONFIG_ALL;
	ps->ps_what[PROC_PFE] = CONFIG_ALL & ~(CONFIG_PROTOS|CONFIG_CERTS);
	ps->ps_what[PROC_HCE] = CONFIG_TABLES;
	ps->ps_what[PROC_CA] = CONFIG_RELAYS|CONFIG_CERTS;
	ps->ps_what[PROC_RELAY] = CONFIG_RELAYS|CONFIG_CERTS|
	    CONFIG_TABLES|CONFIG_PROTOS|CONFIG_CA_ENGINE;

	/* Other configuration */
	what = ps->ps_what[privsep_process];
	if (what & CONFIG_TABLES) {
		if ((env->sc_tables =
		    calloc(1, sizeof(*env->sc_tables))) == NULL)
			return (-1);
		TAILQ_INIT(env->sc_tables);

		memset(&env->sc_empty_table, 0, sizeof(env->sc_empty_table));
		env->sc_empty_table.conf.id = EMPTY_TABLE;
		env->sc_empty_table.conf.flags |= F_DISABLE;
		(void)strlcpy(env->sc_empty_table.conf.name, "empty",
		    sizeof(env->sc_empty_table.conf.name));

	}
	if (what & CONFIG_RDRS) {
		if ((env->sc_rdrs =
		    calloc(1, sizeof(*env->sc_rdrs))) == NULL)
			return (-1);
		TAILQ_INIT(env->sc_rdrs);

	}
	if (what & CONFIG_RELAYS) {
		if ((env->sc_relays =
		    calloc(1, sizeof(*env->sc_relays))) == NULL)
			return (-1);
		TAILQ_INIT(env->sc_relays);

		if ((env->sc_certs =
		    calloc(1, sizeof(*env->sc_certs))) == NULL)
			return (-1);
		TAILQ_INIT(env->sc_certs);

		if ((env->sc_pkeys =
		    calloc(1, sizeof(*env->sc_pkeys))) == NULL)
			return (-1);
		TAILQ_INIT(env->sc_pkeys);
	}
	if (what & CONFIG_PROTOS) {
		if ((env->sc_protos =
		    calloc(1, sizeof(*env->sc_protos))) == NULL)
			return (-1);
		TAILQ_INIT(env->sc_protos);

		bzero(&env->sc_proto_default, sizeof(env->sc_proto_default));
		env->sc_proto_default.id = EMPTY_ID;
		env->sc_proto_default.flags = F_USED;
		env->sc_proto_default.tcpflags = TCPFLAG_DEFAULT;
		env->sc_proto_default.tcpbacklog = RELAY_BACKLOG;
		env->sc_proto_default.tlsflags = TLSFLAG_DEFAULT;
		TAILQ_INIT(&env->sc_proto_default.tlscerts);
		(void)strlcpy(env->sc_proto_default.tlsciphers,
		    TLSCIPHERS_DEFAULT,
		    sizeof(env->sc_proto_default.tlsciphers));
		(void)strlcpy(env->sc_proto_default.tlsecdhecurves,
		    TLSECDHECURVES_DEFAULT,
		    sizeof(env->sc_proto_default.tlsecdhecurves));
		(void)strlcpy(env->sc_proto_default.tlsdhparams,
		    TLSDHPARAM_DEFAULT,
		    sizeof(env->sc_proto_default.tlsdhparams));
		env->sc_proto_default.type = RELAY_PROTO_TCP;
		(void)strlcpy(env->sc_proto_default.name, "default",
		    sizeof(env->sc_proto_default.name));
	}
	if (what & CONFIG_RTS) {
		if ((env->sc_rts =
		    calloc(1, sizeof(*env->sc_rts))) == NULL)
			return (-1);
		TAILQ_INIT(env->sc_rts);
	}
	if (what & CONFIG_ROUTES) {
		if ((env->sc_routes =
		    calloc(1, sizeof(*env->sc_routes))) == NULL)
			return (-1);
		TAILQ_INIT(env->sc_routes);
	}

	return (0);
}

void
config_purge(struct relayd *env, u_int reset)
{
	struct privsep		*ps = env->sc_ps;
	struct table		*table;
	struct rdr		*rdr;
	struct address		*virt;
	struct protocol		*proto;
	struct relay_rule	*rule;
	struct relay		*rlay;
	struct netroute		*nr;
	struct router		*rt;
	struct ca_pkey		*pkey;
	struct keyname		*keyname;
	u_int			 what;

	what = ps->ps_what[privsep_process] & reset;

	if (what & CONFIG_TABLES && env->sc_tables != NULL) {
		while ((table = TAILQ_FIRST(env->sc_tables)) != NULL)
			purge_table(env, env->sc_tables, table);
		env->sc_tablecount = 0;
	}
	if (what & CONFIG_RDRS && env->sc_rdrs != NULL) {
		while ((rdr = TAILQ_FIRST(env->sc_rdrs)) != NULL) {
			TAILQ_REMOVE(env->sc_rdrs, rdr, entry);
			while ((virt = TAILQ_FIRST(&rdr->virts)) != NULL) {
				TAILQ_REMOVE(&rdr->virts, virt, entry);
				free(virt);
			}
			free(rdr);
		}
		env->sc_rdrcount = 0;
	}
	if (what & CONFIG_RELAYS && env->sc_pkeys != NULL) {
		while ((pkey = TAILQ_FIRST(env->sc_pkeys)) != NULL) {
			TAILQ_REMOVE(env->sc_pkeys, pkey, pkey_entry);
			free(pkey);
		}
	}
	if (what & CONFIG_RELAYS && env->sc_relays != NULL) {
		while ((rlay = TAILQ_FIRST(env->sc_relays)) != NULL)
			purge_relay(env, rlay);
		env->sc_relaycount = 0;
	}
	if (what & CONFIG_PROTOS && env->sc_protos != NULL) {
		while ((proto = TAILQ_FIRST(env->sc_protos)) != NULL) {
			TAILQ_REMOVE(env->sc_protos, proto, entry);
			while ((rule = TAILQ_FIRST(&proto->rules)) != NULL)
				rule_delete(&proto->rules, rule);
			proto->rulecount = 0;
		}
	}
	if (what & CONFIG_PROTOS && env->sc_protos != NULL) {
		while ((proto = TAILQ_FIRST(env->sc_protos)) != NULL) {
			TAILQ_REMOVE(env->sc_protos, proto, entry);
			free(proto->style);
			free(proto->tlscapass);
			while ((keyname =
			    TAILQ_FIRST(&proto->tlscerts)) != NULL) {
				TAILQ_REMOVE(&proto->tlscerts, keyname, entry);
				free(keyname->name);
				free(keyname);
			}
			free(proto);
		}
		env->sc_protocount = 0;
	}
	if (what & CONFIG_RTS && env->sc_rts != NULL) {
		while ((rt = TAILQ_FIRST(env->sc_rts)) != NULL) {
			TAILQ_REMOVE(env->sc_rts, rt, rt_entry);
			while ((nr = TAILQ_FIRST(&rt->rt_netroutes)) != NULL) {
				TAILQ_REMOVE(&rt->rt_netroutes, nr, nr_entry);
				TAILQ_REMOVE(env->sc_routes, nr, nr_route);
				free(nr);
				env->sc_routecount--;
			}
			free(rt);
		}
		env->sc_routercount = 0;
	}
	if (what & CONFIG_ROUTES && env->sc_routes != NULL) {
		while ((nr = TAILQ_FIRST(env->sc_routes)) != NULL) {
			if ((rt = nr->nr_router) != NULL)
				TAILQ_REMOVE(&rt->rt_netroutes, nr, nr_entry);
			TAILQ_REMOVE(env->sc_routes, nr, nr_route);
			free(nr);
		}
		env->sc_routecount = 0;
	}
}

int
config_setreset(struct relayd *env, u_int reset)
{
	struct privsep	*ps = env->sc_ps;
	int		 id;

	for (id = 0; id < PROC_MAX; id++) {
		if ((reset & ps->ps_what[id]) == 0 ||
		    id == privsep_process)
			continue;
		proc_compose(ps, id, IMSG_CTL_RESET, &reset, sizeof(reset));

		/*
		 * XXX Make sure that the reset message is sent
		 * immediately by flushing the imsg output buffer, before
		 * sending any other imsg that potentially include an fd.
		 * This should better be fixed in the imsg API itself.
		 */
		proc_flush_imsg(ps, id, -1);
	}

	return (0);
}

int
config_getreset(struct relayd *env, struct imsg *imsg)
{
	u_int		 mode;

	IMSG_SIZE_CHECK(imsg, &mode);
	memcpy(&mode, imsg->data, sizeof(mode));

	config_purge(env, mode);

	return (0);
}

int
config_getcfg(struct relayd *env, struct imsg *imsg)
{
	struct privsep		*ps = env->sc_ps;
	struct table		*tb;
	struct host		*h, *ph;
	u_int			 what;

	if (IMSG_DATA_SIZE(imsg) != sizeof(struct relayd_config))
		return (0); /* ignore */

	/* Update runtime flags */
	memcpy(&env->sc_conf, imsg->data, sizeof(env->sc_conf));

	what = ps->ps_what[privsep_process];

	if (what & CONFIG_TABLES) {
		/* Update the tables */
		TAILQ_FOREACH(tb, env->sc_tables, entry) {
			TAILQ_FOREACH(h, &tb->hosts, entry) {
				if (h->conf.parentid && (ph = host_find(env,
				    h->conf.parentid)) != NULL) {
					SLIST_INSERT_HEAD(&ph->children,
					    h, child);
				}
			}
		}
	}

	if (env->sc_conf.flags & (F_TLS|F_TLSCLIENT)) {
		if (what & CONFIG_CA_ENGINE)
			ca_engine_init(env);
	}

	if (privsep_process != PROC_PARENT)
		proc_compose(env->sc_ps, PROC_PARENT, IMSG_CFG_DONE, NULL, 0);

	return (0);
}

int
config_settable(struct relayd *env, struct table *tb)
{
	struct privsep	*ps = env->sc_ps;
	struct host	*host;
	int		 id, c;
	struct iovec	 iov[2];

	for (id = 0; id < PROC_MAX; id++) {
		if ((ps->ps_what[id] & CONFIG_TABLES) == 0 ||
		    id == privsep_process)
			continue;

		/* XXX need to send table to pfe for control socket */
		if (id == PROC_HCE && tb->conf.check == CHECK_NOCHECK)
			continue;

		DPRINTF("%s: sending table %s %d to %s", __func__,
		    tb->conf.name, tb->conf.id, env->sc_ps->ps_title[id]);

		c = 0;
		iov[c].iov_base = &tb->conf;
		iov[c++].iov_len = sizeof(tb->conf);
		if (tb->sendbuf != NULL) {
			iov[c].iov_base = tb->sendbuf;
			iov[c++].iov_len = strlen(tb->sendbuf);
		}

		proc_composev(ps, id, IMSG_CFG_TABLE, iov, c);

		TAILQ_FOREACH(host, &tb->hosts, entry) {
			proc_compose(ps, id, IMSG_CFG_HOST,
			    &host->conf, sizeof(host->conf));
		}
	}

	return (0);
}

int
config_gettable(struct relayd *env, struct imsg *imsg)
{
	struct table		*tb;
	size_t			 sb;
	u_int8_t		*p = imsg->data;
	size_t			 s;

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

	IMSG_SIZE_CHECK(imsg, &tb->conf);
	memcpy(&tb->conf, p, sizeof(tb->conf));
	s = sizeof(tb->conf);

	sb = IMSG_DATA_SIZE(imsg) - s;
	if (sb > 0) {
		if ((tb->sendbuf = get_string(p + s, sb)) == NULL) {
			free(tb);
			return (-1);
		}
	}
	if (tb->conf.check == CHECK_BINSEND_EXPECT) {
		tb->sendbinbuf = string2binary(tb->sendbuf);
		if (tb->sendbinbuf == NULL) {
			free(tb);
			return (-1);
		}
	}

	TAILQ_INIT(&tb->hosts);
	TAILQ_INSERT_TAIL(env->sc_tables, tb, entry);

	env->sc_tablecount++;

	DPRINTF("%s: %s %d received table %d (%s)", __func__,
	    env->sc_ps->ps_title[privsep_process], env->sc_ps->ps_instance,
	    tb->conf.id, tb->conf.name);

	return (0);
}

int
config_gethost(struct relayd *env, struct imsg *imsg)
{
	struct table		*tb;
	struct host		*host;

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

	IMSG_SIZE_CHECK(imsg, &host->conf);
	memcpy(&host->conf, imsg->data, sizeof(host->conf));

	if (host_find(env, host->conf.id) != NULL) {
		log_debug("%s: host %d already exists",
		    __func__, host->conf.id);
		free(host);
		return (-1);
	}

	if ((tb = table_find(env, host->conf.tableid)) == NULL) {
		log_debug("%s: "
		    "received host for unknown table %d", __func__,
		    host->conf.tableid);
		free(host);
		return (-1);
	}

	host->tablename = tb->conf.name;
	host->cte.s = -1;

	SLIST_INIT(&host->children);
	TAILQ_INSERT_TAIL(&tb->hosts, host, entry);
	TAILQ_INSERT_TAIL(&env->sc_hosts, host, globalentry);

	DPRINTF("%s: %s %d received host %s for table %s", __func__,
	    env->sc_ps->ps_title[privsep_process], env->sc_ps->ps_instance,
	    host->conf.name, tb->conf.name);

	return (0);
}

int
config_setrdr(struct relayd *env, struct rdr *rdr)
{
	struct privsep	*ps = env->sc_ps;
	struct address	*virt;
	int		 id;

	for (id = 0; id < PROC_MAX; id++) {
		if ((ps->ps_what[id] & CONFIG_RDRS) == 0 ||
		    id == privsep_process)
			continue;

		DPRINTF("%s: sending rdr %s to %s", __func__,
		    rdr->conf.name, ps->ps_title[id]);

		proc_compose(ps, id, IMSG_CFG_RDR,
		    &rdr->conf, sizeof(rdr->conf));

		TAILQ_FOREACH(virt, &rdr->virts, entry) {
			virt->rdrid = rdr->conf.id;
			proc_compose(ps, id, IMSG_CFG_VIRT,
			    virt, sizeof(*virt));
		}
	}

	return (0);
}

int
config_getrdr(struct relayd *env, struct imsg *imsg)
{
	struct rdr		*rdr;

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

	IMSG_SIZE_CHECK(imsg, &rdr->conf);
	memcpy(&rdr->conf, imsg->data, sizeof(rdr->conf));

	if ((rdr->table = table_find(env, rdr->conf.table_id)) == NULL) {
		log_debug("%s: table not found", __func__);
		free(rdr);
		return (-1);
	}
	if ((rdr->backup = table_find(env, rdr->conf.backup_id)) == NULL) {
		rdr->conf.backup_id = EMPTY_TABLE;
		rdr->backup = &env->sc_empty_table;
	}

	TAILQ_INIT(&rdr->virts);
	TAILQ_INSERT_TAIL(env->sc_rdrs, rdr, entry);

	env->sc_rdrcount++;

	DPRINTF("%s: %s %d received rdr %s", __func__,
	    env->sc_ps->ps_title[privsep_process], env->sc_ps->ps_instance,
	    rdr->conf.name);

	return (0);
}

int
config_getvirt(struct relayd *env, struct imsg *imsg)
{
	struct rdr	*rdr;
	struct address	*virt;

	IMSG_SIZE_CHECK(imsg, virt);

	if ((virt = calloc(1, sizeof(*virt))) == NULL)
		return (-1);
	memcpy(virt, imsg->data, sizeof(*virt));

	if ((rdr = rdr_find(env, virt->rdrid)) == NULL) {
		log_debug("%s: rdr not found", __func__);
		free(virt);
		return (-1);
	}

	TAILQ_INSERT_TAIL(&rdr->virts, virt, entry);

	DPRINTF("%s: %s %d received address for rdr %s", __func__,
	    env->sc_ps->ps_title[privsep_process], env->sc_ps->ps_instance,
	    rdr->conf.name);

	return (0);
}

int
config_setrt(struct relayd *env, struct router *rt)
{
	struct privsep	*ps = env->sc_ps;
	struct netroute	*nr;
	int		 id;

	for (id = 0; id < PROC_MAX; id++) {
		if ((ps->ps_what[id] & CONFIG_RTS) == 0 ||
		    id == privsep_process)
			continue;

		DPRINTF("%s: sending router %s to %s tbl %d", __func__,
		    rt->rt_conf.name, ps->ps_title[id], rt->rt_conf.gwtable);

		proc_compose(ps, id, IMSG_CFG_ROUTER,
		    &rt->rt_conf, sizeof(rt->rt_conf));

		TAILQ_FOREACH(nr, &rt->rt_netroutes, nr_entry) {
			proc_compose(ps, id, IMSG_CFG_ROUTE,
			    &nr->nr_conf, sizeof(nr->nr_conf));
		}
	}

	return (0);
}

int
config_getrt(struct relayd *env, struct imsg *imsg)
{
	struct router		*rt;

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

	IMSG_SIZE_CHECK(imsg, &rt->rt_conf);
	memcpy(&rt->rt_conf, imsg->data, sizeof(rt->rt_conf));

	if ((rt->rt_gwtable = table_find(env, rt->rt_conf.gwtable)) == NULL) {
		log_debug("%s: table not found", __func__);
		free(rt);
		return (-1);
	}

	TAILQ_INIT(&rt->rt_netroutes);
	TAILQ_INSERT_TAIL(env->sc_rts, rt, rt_entry);

	env->sc_routercount++;

	DPRINTF("%s: %s %d received router %s", __func__,
	    env->sc_ps->ps_title[privsep_process], env->sc_ps->ps_instance,
	    rt->rt_conf.name);

	return (0);
}

int
config_getroute(struct relayd *env, struct imsg *imsg)
{
	struct router		*rt;
	struct netroute		*nr;

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

	IMSG_SIZE_CHECK(imsg, &nr->nr_conf);
	memcpy(&nr->nr_conf, imsg->data, sizeof(nr->nr_conf));

	if (route_find(env, nr->nr_conf.id) != NULL) {
		log_debug("%s: route %d already exists",
		    __func__, nr->nr_conf.id);
		free(nr);
		return (-1);
	}

	if ((rt = router_find(env, nr->nr_conf.routerid)) == NULL) {
		log_debug("%s: received route for unknown router", __func__);
		free(nr);
		return (-1);
	}

	nr->nr_router = rt;

	TAILQ_INSERT_TAIL(env->sc_routes, nr, nr_route);
	TAILQ_INSERT_TAIL(&rt->rt_netroutes, nr, nr_entry);

	env->sc_routecount++;

	DPRINTF("%s: %s %d received route %d for router %s", __func__,
	    env->sc_ps->ps_title[privsep_process], env->sc_ps->ps_instance,
	    nr->nr_conf.id, rt->rt_conf.name);

	return (0);
}

int
config_setproto(struct relayd *env, struct protocol *proto)
{
	struct privsep		*ps = env->sc_ps;
	int			 id;
	struct iovec		 iov[IOV_MAX];
	size_t			 c;

	for (id = 0; id < PROC_MAX; id++) {
		if ((ps->ps_what[id] & CONFIG_PROTOS) == 0 ||
		    id == privsep_process)
			continue;

		DPRINTF("%s: sending protocol %s to %s", __func__,
		    proto->name, ps->ps_title[id]);

		c = 0;
		iov[c].iov_base = proto;
		iov[c++].iov_len = sizeof(*proto);

		if (proto->style != NULL) {
			iov[c].iov_base = proto->style;
			iov[c++].iov_len = strlen(proto->style) + 1;
		}

		proc_composev(ps, id, IMSG_CFG_PROTO, iov, c);
	}

	return (0);
}

int
config_setrule(struct relayd *env, struct protocol *proto)
{
	struct privsep		*ps = env->sc_ps;
	struct relay_rule	*rule;
	struct iovec		 iov[IOV_MAX];
	int			 id;
	size_t			 c, i;

	for (id = 0; id < PROC_MAX; id++) {
		if ((ps->ps_what[id] & CONFIG_PROTOS) == 0 ||
		    id == privsep_process)
			continue;

		DPRINTF("%s: sending rules %s to %s", __func__,
		    proto->name, ps->ps_title[id]);

		/* Now send all the rules */
		TAILQ_FOREACH(rule, &proto->rules, rule_entry) {
			rule->rule_protoid = proto->id;
			bzero(&rule->rule_ctl, sizeof(rule->rule_ctl));
			c = 0;
			iov[c].iov_base = rule;
			iov[c++].iov_len = sizeof(*rule);
			for (i = 1; i < KEY_TYPE_MAX; i++) {
				if (rule->rule_kv[i].kv_key != NULL) {
					rule->rule_ctl.kvlen[i].key =
					    strlen(rule->rule_kv[i].kv_key);
					iov[c].iov_base =
					    rule->rule_kv[i].kv_key;
					iov[c++].iov_len =
					    rule->rule_ctl.kvlen[i].key;
				} else
					rule->rule_ctl.kvlen[i].key = -1;
				if (rule->rule_kv[i].kv_value != NULL) {
					rule->rule_ctl.kvlen[i].value =
					    strlen(rule->rule_kv[i].kv_value);
					iov[c].iov_base =
					    rule->rule_kv[i].kv_value;
					iov[c++].iov_len =
					    rule->rule_ctl.kvlen[i].value;
				} else
					rule->rule_ctl.kvlen[i].value = -1;
			}

			proc_composev(ps, id, IMSG_CFG_RULE, iov, c);
		}
	}

	return (0);
}

int
config_getproto(struct relayd *env, struct imsg *imsg)
{
	struct protocol		*proto;
	size_t			 styl;
	size_t			 s;
	u_int8_t		*p = imsg->data;

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

	IMSG_SIZE_CHECK(imsg, proto);
	memcpy(proto, p, sizeof(*proto));
	s = sizeof(*proto);

	styl = IMSG_DATA_SIZE(imsg) - s;
	proto->style = NULL;
	if (styl > 0) {
		if ((proto->style = get_string(p + s, styl - 1)) == NULL) {
			free(proto);
			return (-1);
		}
	}

	TAILQ_INIT(&proto->rules);
	TAILQ_INIT(&proto->tlscerts);
	proto->tlscapass = NULL;

	TAILQ_INSERT_TAIL(env->sc_protos, proto, entry);

	env->sc_protocount++;

	DPRINTF("%s: %s %d received protocol %s", __func__,
	    env->sc_ps->ps_title[privsep_process], env->sc_ps->ps_instance,
	    proto->name);

	return (0);
}

int
config_getrule(struct relayd *env, struct imsg *imsg)
{
	struct protocol		*proto;
	struct relay_rule	*rule;
	size_t			 s, i;
	u_int8_t		*p = imsg->data;
	ssize_t			 len;

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

	IMSG_SIZE_CHECK(imsg, rule);
	memcpy(rule, p, sizeof(*rule));
	s = sizeof(*rule);
	len = IMSG_DATA_SIZE(imsg) - s;

	if ((proto = proto_find(env, rule->rule_protoid)) == NULL) {
		free(rule);
		return (-1);
	}

#define GETKV(_n, _f)	{						\
	if (rule->rule_ctl.kvlen[_n]._f >= 0) {				\
		/* Also accept "empty" 0-length strings */		\
		if ((len < rule->rule_ctl.kvlen[_n]._f) ||		\
		    (rule->rule_kv[_n].kv_##_f =			\
		    get_string(p + s,					\
		    rule->rule_ctl.kvlen[_n]._f)) == NULL) {		\
			free(rule);					\
			return (-1);					\
		}							\
		s += rule->rule_ctl.kvlen[_n]._f;			\
		len -= rule->rule_ctl.kvlen[_n]._f;			\
									\
		DPRINTF("%s: %s %s (len %ld, option %d): %s", __func__,	\
		    #_n, #_f, rule->rule_ctl.kvlen[_n]._f,		\
		    rule->rule_kv[_n].kv_option,			\
		    rule->rule_kv[_n].kv_##_f);				\
	}								\
}

	memset(&rule->rule_kv[0], 0, sizeof(struct kv));
	for (i = 1; i < KEY_TYPE_MAX; i++) {
		TAILQ_INIT(&rule->rule_kv[i].kv_children);
		GETKV(i, key);
		GETKV(i, value);
	}

	if (rule->rule_labelname[0])
		rule->rule_label = label_name2id(rule->rule_labelname);

	if (rule->rule_tagname[0])
		rule->rule_tag = tag_name2id(rule->rule_tagname);

	if (rule->rule_taggedname[0])
		rule->rule_tagged = tag_name2id(rule->rule_taggedname);

	rule->rule_id = proto->rulecount++;

	TAILQ_INSERT_TAIL(&proto->rules, rule, rule_entry);

	DPRINTF("%s: %s %d received rule %u for protocol %s", __func__,
	    env->sc_ps->ps_title[privsep_process], env->sc_ps->ps_instance,
	    rule->rule_id, proto->name);

	return (0);
}

static int
config_setrelayfd(struct privsep *ps, int id, int n,
    objid_t obj_id, objid_t rlay_id, enum fd_type type, int ofd)
{
	struct ctl_relayfd	rfd;
	int			fd;

	rfd.id = obj_id;
	rfd.relayid = rlay_id;
	rfd.type = type;

	if ((fd = dup(ofd)) == -1)
		return (-1);
	if (proc_compose_imsg(ps, id, n, IMSG_CFG_RELAY_FD, -1, fd,
	    &rfd, sizeof(rfd)) != 0)
		return (-1);

	return (0);
}

int
config_setrelay(struct relayd *env, struct relay *rlay)
{
	struct privsep		*ps = env->sc_ps;
	struct ctl_relaytable	 crt;
	struct relay_table	*rlt;
	struct relay_config	 rl;
	struct relay_cert	*cert;
	int			 id;
	int			 fd, n, m;
	struct iovec		 iov[6];
	size_t			 c;
	u_int			 what;

	/* opens listening sockets etc. */
	if (relay_privinit(rlay) == -1)
		return (-1);

	for (id = 0; id < PROC_MAX; id++) {
		what = ps->ps_what[id];

		if ((what & CONFIG_RELAYS) == 0 || id == privsep_process)
			continue;

		DPRINTF("%s: sending relay %s to %s fd %d", __func__,
		    rlay->rl_conf.name, ps->ps_title[id], rlay->rl_s);

		memcpy(&rl, &rlay->rl_conf, sizeof(rl));

		c = 0;
		iov[c].iov_base = &rl;
		iov[c++].iov_len = sizeof(rl);

		if ((what & CONFIG_CA_ENGINE) == 0 &&
		    rl.tls_cakey_len) {
			iov[c].iov_base = rlay->rl_tls_cakey;
			iov[c++].iov_len = rl.tls_cakey_len;
		} else
			rl.tls_cakey_len = 0;

		if (id == PROC_RELAY) {
			/* XXX imsg code will close the fd after 1st call */
			n = -1;
			proc_range(ps, id, &n, &m);
			for (n = 0; n < m; n++) {
				if ((fd = dup(rlay->rl_s)) == -1)
					return (-1);
				if (proc_composev_imsg(ps, id, n,
				    IMSG_CFG_RELAY, -1, fd, iov, c) != 0) {
					log_warn("%s: failed to compose "
					    "IMSG_CFG_RELAY imsg for `%s'",
					    __func__, rlay->rl_conf.name);
					return (-1);
				}
				/* Prevent fd exhaustion in the parent. */
				if (proc_flush_imsg(ps, id, n) == -1) {
					log_warn("%s: failed to flush "
					    "IMSG_CFG_RELAY imsg for `%s'",
					    __func__, rlay->rl_conf.name);
					return (-1);
				}
			}
		} else {
			if (proc_composev(ps, id,
			    IMSG_CFG_RELAY, iov, c) != 0) {
				log_warn("%s: failed to compose "
				    "IMSG_CFG_RELAY imsg for `%s'",
				    __func__, rlay->rl_conf.name);
				return (-1);
			}
		}

		/* cert keypairs */
		TAILQ_FOREACH(cert, env->sc_certs, cert_entry) {
			if (cert->cert_relayid != rlay->rl_conf.id)
				continue;
			n = -1;
			proc_range(ps, id, &n, &m);
			for (n = 0; (what & CONFIG_CERTS) && n < m; n++) {
				if (cert->cert_fd != -1 &&
				    config_setrelayfd(ps, id, n,
				    cert->cert_id, cert->cert_relayid,
				    RELAY_FD_CERT, cert->cert_fd) == -1) {
					log_warn("%s: fd passing failed for "
					    "`%s'", __func__,
					    rlay->rl_conf.name);
					return (-1);
				}
				if (id == PROC_RELAY &&
				    cert->cert_ocsp_fd != -1 &&
				    config_setrelayfd(ps, id, n,
				    cert->cert_id, cert->cert_relayid,
				    RELAY_FD_OCSP, cert->cert_ocsp_fd) == -1) {
					log_warn("%s: fd passing failed for "
					    "`%s'", __func__,
					    rlay->rl_conf.name);
					return (-1);
				}
				if (id == PROC_CA &&
				    cert->cert_key_fd != -1 &&
				    config_setrelayfd(ps, id, n,
				    cert->cert_id, cert->cert_relayid,
				    RELAY_FD_KEY, cert->cert_key_fd) == -1) {
					log_warn("%s: fd passing failed for "
					    "`%s'", __func__,
					    rlay->rl_conf.name);
					return (-1);
				}
			}
		}

		/* CA certs */
		if (what & CONFIG_CERTS) {
			n = -1;
			proc_range(ps, id, &n, &m);
			for (n = 0; n < m; n++) {
				if (rlay->rl_tls_ca_fd != -1 &&
				    config_setrelayfd(ps, id, n, 0,
				    rlay->rl_conf.id, RELAY_FD_CACERT,
				    rlay->rl_tls_ca_fd) == -1) {
					log_warn("%s: fd passing failed for "
					    "`%s'", __func__,
					    rlay->rl_conf.name);
					return (-1);
				}
				if (rlay->rl_tls_cacert_fd != -1 &&
				    config_setrelayfd(ps, id, n, 0,
				    rlay->rl_conf.id, RELAY_FD_CAFILE,
				    rlay->rl_tls_cacert_fd) == -1) {
					log_warn("%s: fd passing failed for "
					    "`%s'", __func__,
					    rlay->rl_conf.name);
					return (-1);
				}
				/* Prevent fd exhaustion in the parent. */
				if (proc_flush_imsg(ps, id, n) == -1) {
					log_warn("%s: failed to flush "
					    "IMSG_CFG_RELAY imsg for `%s'",
					    __func__, rlay->rl_conf.name);
					return (-1);
				}
			}
		}

		if ((what & CONFIG_TABLES) == 0)
			continue;

		/* Now send the tables associated to this relay */
		TAILQ_FOREACH(rlt, &rlay->rl_tables, rlt_entry) {
			crt.id = rlt->rlt_table->conf.id;
			crt.relayid = rlay->rl_conf.id;
			crt.mode = rlt->rlt_mode;
			crt.flags = rlt->rlt_flags;

			c = 0;
			iov[c].iov_base = &crt;
			iov[c++].iov_len = sizeof(crt);

			proc_composev(ps, id, IMSG_CFG_RELAY_TABLE, iov, c);
		}
	}

	/* Close server socket early to prevent fd exhaustion in the parent. */
	if (rlay->rl_s != -1) {
		close(rlay->rl_s);
		rlay->rl_s = -1;
	}
	if (rlay->rl_tls_cacert_fd != -1) {
		close(rlay->rl_tls_cacert_fd);
		rlay->rl_tls_cacert_fd = -1;
	}
	if (rlay->rl_tls_ca_fd != -1) {
		close(rlay->rl_tls_ca_fd);
		rlay->rl_tls_ca_fd = -1;
	}
	TAILQ_FOREACH(cert, env->sc_certs, cert_entry) {
		if (cert->cert_relayid != rlay->rl_conf.id)
			continue;

		if (cert->cert_fd != -1) {
			close(cert->cert_fd);
			cert->cert_fd = -1;
		}
		if (cert->cert_key_fd != -1) {
			close(cert->cert_key_fd);
			cert->cert_key_fd = -1;
		}
		if (cert->cert_ocsp_fd != -1) {
			close(cert->cert_ocsp_fd);
			cert->cert_ocsp_fd = -1;
		}
	}

	return (0);
}

int
config_getrelay(struct relayd *env, struct imsg *imsg)
{
	struct privsep		*ps = env->sc_ps;
	struct relay		*rlay;
	u_int8_t		*p = imsg->data;
	size_t			 s;

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

	IMSG_SIZE_CHECK(imsg, &rlay->rl_conf);
	memcpy(&rlay->rl_conf, p, sizeof(rlay->rl_conf));
	s = sizeof(rlay->rl_conf);

	rlay->rl_s = imsg_get_fd(imsg);
	rlay->rl_tls_ca_fd = -1;
	rlay->rl_tls_cacert_fd = -1;

	if (ps->ps_what[privsep_process] & CONFIG_PROTOS) {
		if (rlay->rl_conf.proto == EMPTY_ID)
			rlay->rl_proto = &env->sc_proto_default;
		else if ((rlay->rl_proto =
		    proto_find(env, rlay->rl_conf.proto)) == NULL) {
			log_debug("%s: unknown protocol", __func__);
			goto fail;
		}
	}

	if ((off_t)(IMSG_DATA_SIZE(imsg) - s) <
	    (rlay->rl_conf.tls_cakey_len)) {
		log_debug("%s: invalid message length", __func__);
		goto fail;
	}

	if (rlay->rl_conf.tls_cakey_len) {
		if ((rlay->rl_tls_cakey = get_data(p + s,
		    rlay->rl_conf.tls_cakey_len)) == NULL)
			goto fail;
		s += rlay->rl_conf.tls_cakey_len;
	}

	TAILQ_INIT(&rlay->rl_tables);
	TAILQ_INSERT_TAIL(env->sc_relays, rlay, rl_entry);

	env->sc_relaycount++;

	DPRINTF("%s: %s %d received relay %s", __func__,
	    ps->ps_title[privsep_process], ps->ps_instance,
	    rlay->rl_conf.name);

	return (0);

 fail:
	free(rlay->rl_tls_cakey);
	close(rlay->rl_s);
	free(rlay);
	return (-1);
}

int
config_getrelaytable(struct relayd *env, struct imsg *imsg)
{
	struct relay_table	*rlt = NULL;
	struct ctl_relaytable	 crt;
	struct relay		*rlay;
	struct table		*table;
	u_int8_t		*p = imsg->data;

	IMSG_SIZE_CHECK(imsg, &crt);
	memcpy(&crt, p, sizeof(crt));

	if ((rlay = relay_find(env, crt.relayid)) == NULL) {
		log_debug("%s: unknown relay", __func__);
		goto fail;
	}

	if ((table = table_find(env, crt.id)) == NULL) {
		log_debug("%s: unknown table", __func__);
		goto fail;
	}

	if ((rlt = calloc(1, sizeof(*rlt))) == NULL)
		goto fail;

	rlt->rlt_table = table;
	rlt->rlt_mode = crt.mode;
	rlt->rlt_flags = crt.flags;

	TAILQ_INSERT_TAIL(&rlay->rl_tables, rlt, rlt_entry);

	DPRINTF("%s: %s %d received relay table %s for relay %s", __func__,
	    env->sc_ps->ps_title[privsep_process], env->sc_ps->ps_instance,
	    table->conf.name, rlay->rl_conf.name);

	return (0);

 fail:
	free(rlt);
	return (-1);
}

int
config_getrelayfd(struct relayd *env, struct imsg *imsg)
{
	struct ctl_relayfd	 crfd;
	struct relay		*rlay = NULL;
	struct relay_cert	*cert;
	u_int8_t		*p = imsg->data;

	IMSG_SIZE_CHECK(imsg, &crfd);
	memcpy(&crfd, p, sizeof(crfd));

	switch (crfd.type) {
	case RELAY_FD_CERT:
	case RELAY_FD_KEY:
	case RELAY_FD_OCSP:
		if ((cert = cert_find(env, crfd.id)) == NULL) {
			if ((cert = cert_add(env, crfd.id)) == NULL)
				return (-1);
			cert->cert_relayid = crfd.relayid;
		}
		/* FALLTHROUGH */
	default:
		if ((rlay = relay_find(env, crfd.relayid)) == NULL) {
			log_debug("%s: unknown relay", __func__);
			return (-1);
		}
		break;
	}

	switch (crfd.type) {
	case RELAY_FD_CERT:
		cert->cert_fd = imsg_get_fd(imsg);
		break;
	case RELAY_FD_KEY:
		cert->cert_key_fd = imsg_get_fd(imsg);
		break;
	case RELAY_FD_OCSP:
		cert->cert_ocsp_fd = imsg_get_fd(imsg);
		break;
	case RELAY_FD_CACERT:
		rlay->rl_tls_ca_fd = imsg_get_fd(imsg);
		break;
	case RELAY_FD_CAFILE:
		rlay->rl_tls_cacert_fd = imsg_get_fd(imsg);
		break;
	}

	DPRINTF("%s: %s %d received relay fd %d type %d for relay %s", __func__,
	    env->sc_ps->ps_title[privsep_process], env->sc_ps->ps_instance,
	    imsg->fd, crfd.type, rlay->rl_conf.name);

	return (0);
}