/* $OpenBSD: config.c,v 1.43 2021/12/05 13:48:14 jsg Exp $ */ /* * Copyright (c) 2011 - 2014 Reyk Floeter * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include "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)) { ssl_init(env); 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->fd; 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->fd; break; case RELAY_FD_KEY: cert->cert_key_fd = imsg->fd; break; case RELAY_FD_OCSP: cert->cert_ocsp_fd = imsg->fd; break; case RELAY_FD_CACERT: rlay->rl_tls_ca_fd = imsg->fd; break; case RELAY_FD_CAFILE: rlay->rl_tls_cacert_fd = imsg->fd; 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); }