root/usr.sbin/relayd/config.c
/*      $OpenBSD: config.c,v 1.50 2026/04/03 13:21:00 op 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"
#include "log.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;
                        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);
                                }
                                if (rlay->rl_tls_client_ca_fd != -1 &&
                                    config_setrelayfd(ps, id, n, 0,
                                    rlay->rl_conf.id, RELAY_FD_CLIENTCACERT,
                                    rlay->rl_tls_client_ca_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_client_ca_fd != -1) {
                close(rlay->rl_tls_client_ca_fd);
                rlay->rl_tls_client_ca_fd = -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;
        rlay->rl_tls_client_ca_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;
        case RELAY_FD_CLIENTCACERT:
                rlay->rl_tls_client_ca_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);
}