/* * Copyright (C) 2014 Martin Willi * Copyright (C) 2014 revosec AG * * Copyright (C) 2015-2019 Tobias Brunner * Copyright (C) 2015-2018 Andreas Steffen * HSR Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ /* * Copyright (C) 2014 Timo Teräs * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #define _GNU_SOURCE #include "vici_config.h" #include "vici_builder.h" #include #include #include #include #include #include #include #include /** * Magic value for an undefined lifetime */ #define LFT_UNDEFINED (~(uint64_t)0) /** * Default IKE rekey time */ #define LFT_DEFAULT_IKE_REKEY_TIME (4 * 60 * 60) /** * Default CHILD rekey time */ #define LFT_DEFAULT_CHILD_REKEY_TIME (1 * 60 * 60) /** * Default CHILD rekey bytes */ #define LFT_DEFAULT_CHILD_REKEY_BYTES 0 /** * Default CHILD rekey packets */ #define LFT_DEFAULT_CHILD_REKEY_PACKETS 0 /** * Undefined replay window */ #define REPLAY_UNDEFINED (~(uint32_t)0) typedef struct private_vici_config_t private_vici_config_t; /** * Private data of an vici_config_t object. */ struct private_vici_config_t { /** * Public vici_config_t interface. */ vici_config_t public; /** * Dispatcher */ vici_dispatcher_t *dispatcher; /** * Hashtable of loaded connections, as peer_cfg_t */ hashtable_t *conns; /** * Lock for conns table */ rwlock_t *lock; /** * Condvar used to sync running actions */ rwlock_condvar_t *condvar; /** * True while we run or undo a start action */ bool handling_actions; /** * Credential backend managed by VICI used for our certificates */ vici_cred_t *cred; /** * Auxiliary certification authority information */ vici_authority_t *authority; }; CALLBACK(peer_filter, bool, void *data, enumerator_t *orig, va_list args) { peer_cfg_t **out; VA_ARGS_VGET(args, out); if (orig->enumerate(orig, NULL, out)) { return TRUE; } return FALSE; } METHOD(backend_t, create_peer_cfg_enumerator, enumerator_t*, private_vici_config_t *this, identification_t *me, identification_t *other) { this->lock->read_lock(this->lock); return enumerator_create_filter(this->conns->create_enumerator(this->conns), peer_filter, this->lock, (void*)this->lock->unlock); } CALLBACK(ike_filter, bool, void *data, enumerator_t *orig, va_list args) { peer_cfg_t *cfg; ike_cfg_t **out; VA_ARGS_VGET(args, out); if (orig->enumerate(orig, NULL, &cfg)) { *out = cfg->get_ike_cfg(cfg); return TRUE; } return FALSE; } METHOD(backend_t, create_ike_cfg_enumerator, enumerator_t*, private_vici_config_t *this, host_t *me, host_t *other) { this->lock->read_lock(this->lock); return enumerator_create_filter(this->conns->create_enumerator(this->conns), ike_filter, this->lock, (void*)this->lock->unlock); } METHOD(backend_t, get_peer_cfg_by_name, peer_cfg_t*, private_vici_config_t *this, char *name) { peer_cfg_t *found; this->lock->read_lock(this->lock); found = this->conns->get(this->conns, name); if (found) { found->get_ref(found); } this->lock->unlock(this->lock); return found; } /** * Create a (error) reply message */ static vici_message_t* create_reply(char *fmt, ...) { vici_builder_t *builder; va_list args; builder = vici_builder_create(); builder->add_kv(builder, "success", fmt ? "no" : "yes"); if (fmt) { va_start(args, fmt); builder->vadd_kv(builder, "errmsg", fmt, args); va_end(args); } return builder->finalize(builder); } /** * A rule to parse a key/value or list item */ typedef struct { /** name of the key/value or list */ char *name; /** function to parse value */ bool (*parse)(void *out, chunk_t value); /** result, passed to parse() */ void *out; } parse_rule_t; /** * Parse key/values using a rule-set */ static bool parse_rules(parse_rule_t *rules, int count, char *name, chunk_t value, vici_message_t **reply) { int i; for (i = 0; i < count; i++) { if (streq(name, rules[i].name)) { if (rules[i].parse(rules[i].out, value)) { return TRUE; } *reply = create_reply("invalid value for: %s, config discarded", name); return FALSE; } } *reply = create_reply("unknown option: %s, config discarded", name); return FALSE; } /** * Parse callback data, passed to each callback */ typedef struct { private_vici_config_t *this; vici_message_t *reply; } request_data_t; /** * Certificate data */ typedef struct { request_data_t *request; char *handle; uint32_t slot; char *module; char *file; } cert_data_t; /** * Clean up certificate data */ static void free_cert_data(cert_data_t *data) { free(data->handle); free(data->module); free(data->file); free(data); } /** * Auth config data */ typedef struct { request_data_t *request; auth_cfg_t *cfg; uint32_t round; } auth_data_t; /** * Clean up auth config data */ static void free_auth_data(auth_data_t *data) { DESTROY_IF(data->cfg); free(data); } /** * Data associated to a peer config */ typedef struct { request_data_t *request; uint32_t version; bool aggressive; bool encap; bool mobike; bool send_certreq; bool pull; identification_t *ppk_id; bool ppk_required; cert_policy_t send_cert; uint64_t dpd_delay; uint64_t dpd_timeout; fragmentation_t fragmentation; childless_t childless; unique_policy_t unique; uint32_t keyingtries; uint32_t local_port; uint32_t remote_port; char *local_addrs; char *remote_addrs; linked_list_t *local; linked_list_t *remote; linked_list_t *proposals; linked_list_t *children; linked_list_t *vips; char *pools; uint64_t reauth_time; uint64_t rekey_time; uint64_t over_time; uint64_t rand_time; uint8_t dscp; uint32_t if_id_in; uint32_t if_id_out; #ifdef ME bool mediation; char *mediated_by; identification_t *peer_id; #endif /* ME */ } peer_data_t; /** * Log relevant auth config data */ static void log_auth(auth_cfg_t *auth) { enumerator_t *enumerator; auth_rule_t rule; union { uintptr_t u; identification_t *id; certificate_t *cert; char *str; } v; enumerator = auth->create_enumerator(auth); while (enumerator->enumerate(enumerator, &rule, &v)) { switch (rule) { case AUTH_RULE_AUTH_CLASS: DBG2(DBG_CFG, " class = %N", auth_class_names, v.u); break; case AUTH_RULE_EAP_TYPE: DBG2(DBG_CFG, " eap-type = %N", eap_type_names, v.u); break; case AUTH_RULE_EAP_VENDOR: DBG2(DBG_CFG, " eap-vendor = %u", v.u); break; case AUTH_RULE_XAUTH_BACKEND: DBG2(DBG_CFG, " xauth = %s", v.str); break; case AUTH_RULE_CRL_VALIDATION: DBG2(DBG_CFG, " revocation = %N", cert_validation_names, v.u); break; case AUTH_RULE_IDENTITY: DBG2(DBG_CFG, " id = %Y", v.id); break; case AUTH_RULE_CA_IDENTITY: DBG2(DBG_CFG, " ca_id = %Y", v.id); break; case AUTH_RULE_AAA_IDENTITY: DBG2(DBG_CFG, " aaa_id = %Y", v.id); break; case AUTH_RULE_EAP_IDENTITY: DBG2(DBG_CFG, " eap_id = %Y", v.id); break; case AUTH_RULE_XAUTH_IDENTITY: DBG2(DBG_CFG, " xauth_id = %Y", v.id); break; case AUTH_RULE_GROUP: DBG2(DBG_CFG, " group = %Y", v.id); break; case AUTH_RULE_SUBJECT_CERT: DBG2(DBG_CFG, " cert = %Y", v.cert->get_subject(v.cert)); break; case AUTH_RULE_CA_CERT: DBG2(DBG_CFG, " cacert = %Y", v.cert->get_subject(v.cert)); break; default: break; } } enumerator->destroy(enumerator); } /** * Log parsed peer data */ static void log_peer_data(peer_data_t *data) { enumerator_t *enumerator; auth_data_t *auth; host_t *host; DBG2(DBG_CFG, " version = %u", data->version); DBG2(DBG_CFG, " local_addrs = %s", data->local_addrs); DBG2(DBG_CFG, " remote_addrs = %s", data->remote_addrs); DBG2(DBG_CFG, " local_port = %u", data->local_port); DBG2(DBG_CFG, " remote_port = %u", data->remote_port); DBG2(DBG_CFG, " send_certreq = %u", data->send_certreq); DBG2(DBG_CFG, " send_cert = %N", cert_policy_names, data->send_cert); DBG2(DBG_CFG, " ppk_id = %Y", data->ppk_id); DBG2(DBG_CFG, " ppk_required = %u", data->ppk_required); DBG2(DBG_CFG, " mobike = %u", data->mobike); DBG2(DBG_CFG, " aggressive = %u", data->aggressive); DBG2(DBG_CFG, " dscp = 0x%.2x", data->dscp); DBG2(DBG_CFG, " encap = %u", data->encap); DBG2(DBG_CFG, " dpd_delay = %llu", data->dpd_delay); DBG2(DBG_CFG, " dpd_timeout = %llu", data->dpd_timeout); DBG2(DBG_CFG, " fragmentation = %u", data->fragmentation); DBG2(DBG_CFG, " childless = %u", data->childless); DBG2(DBG_CFG, " unique = %N", unique_policy_names, data->unique); DBG2(DBG_CFG, " keyingtries = %u", data->keyingtries); DBG2(DBG_CFG, " reauth_time = %llu", data->reauth_time); DBG2(DBG_CFG, " rekey_time = %llu", data->rekey_time); DBG2(DBG_CFG, " over_time = %llu", data->over_time); DBG2(DBG_CFG, " rand_time = %llu", data->rand_time); DBG2(DBG_CFG, " proposals = %#P", data->proposals); DBG2(DBG_CFG, " if_id_in = %u", data->if_id_in); DBG2(DBG_CFG, " if_id_out = %u", data->if_id_out); #ifdef ME DBG2(DBG_CFG, " mediation = %u", data->mediation); if (data->mediated_by) { DBG2(DBG_CFG, " mediated_by = %s", data->mediated_by); DBG2(DBG_CFG, " mediation_peer = %Y", data->peer_id); } #endif /* ME */ if (data->vips->get_count(data->vips)) { DBG2(DBG_CFG, " vips:"); } enumerator = data->vips->create_enumerator(data->vips); while (enumerator->enumerate(enumerator, &host)) { DBG2(DBG_CFG, " %H", host); } enumerator->destroy(enumerator); enumerator = data->local->create_enumerator(data->local); while (enumerator->enumerate(enumerator, &auth)) { DBG2(DBG_CFG, " local:"); log_auth(auth->cfg); } enumerator->destroy(enumerator); enumerator = data->remote->create_enumerator(data->remote); while (enumerator->enumerate(enumerator, &auth)) { DBG2(DBG_CFG, " remote:"); log_auth(auth->cfg); } enumerator->destroy(enumerator); } /** * Clean up peer config data */ static void free_peer_data(peer_data_t *data) { data->local->destroy_function(data->local, (void*)free_auth_data); data->remote->destroy_function(data->remote, (void*)free_auth_data); data->children->destroy_offset(data->children, offsetof(child_cfg_t, destroy)); data->proposals->destroy_offset(data->proposals, offsetof(proposal_t, destroy)); data->vips->destroy_offset(data->vips, offsetof(host_t, destroy)); free(data->pools); free(data->local_addrs); free(data->remote_addrs); DESTROY_IF(data->ppk_id); #ifdef ME free(data->mediated_by); DESTROY_IF(data->peer_id); #endif /* ME */ } /** * CHILD config data */ typedef struct { request_data_t *request; linked_list_t *proposals; linked_list_t *local_ts; linked_list_t *remote_ts; uint32_t replay_window; child_cfg_create_t cfg; } child_data_t; /** * Log parsed CHILD config data */ static void log_child_data(child_data_t *data, char *name) { child_cfg_create_t *cfg = &data->cfg; #define has_opt(opt) ({ (cfg->options & (opt)) == (opt); }) DBG2(DBG_CFG, " child %s:", name); DBG2(DBG_CFG, " rekey_time = %llu", cfg->lifetime.time.rekey); DBG2(DBG_CFG, " life_time = %llu", cfg->lifetime.time.life); DBG2(DBG_CFG, " rand_time = %llu", cfg->lifetime.time.jitter); DBG2(DBG_CFG, " rekey_bytes = %llu", cfg->lifetime.bytes.rekey); DBG2(DBG_CFG, " life_bytes = %llu", cfg->lifetime.bytes.life); DBG2(DBG_CFG, " rand_bytes = %llu", cfg->lifetime.bytes.jitter); DBG2(DBG_CFG, " rekey_packets = %llu", cfg->lifetime.packets.rekey); DBG2(DBG_CFG, " life_packets = %llu", cfg->lifetime.packets.life); DBG2(DBG_CFG, " rand_packets = %llu", cfg->lifetime.packets.jitter); DBG2(DBG_CFG, " updown = %s", cfg->updown); DBG2(DBG_CFG, " hostaccess = %u", has_opt(OPT_HOSTACCESS)); DBG2(DBG_CFG, " ipcomp = %u", has_opt(OPT_IPCOMP)); DBG2(DBG_CFG, " mode = %N%s", ipsec_mode_names, cfg->mode, has_opt(OPT_PROXY_MODE) ? "_PROXY" : ""); DBG2(DBG_CFG, " policies = %u", !has_opt(OPT_NO_POLICIES)); DBG2(DBG_CFG, " policies_fwd_out = %u", has_opt(OPT_FWD_OUT_POLICIES)); if (data->replay_window != REPLAY_UNDEFINED) { DBG2(DBG_CFG, " replay_window = %u", data->replay_window); } DBG2(DBG_CFG, " dpd_action = %N", action_names, cfg->dpd_action); DBG2(DBG_CFG, " start_action = %N", action_names, cfg->start_action); DBG2(DBG_CFG, " close_action = %N", action_names, cfg->close_action); DBG2(DBG_CFG, " reqid = %u", cfg->reqid); DBG2(DBG_CFG, " tfc = %d", cfg->tfc); DBG2(DBG_CFG, " priority = %d", cfg->priority); DBG2(DBG_CFG, " interface = %s", cfg->interface); DBG2(DBG_CFG, " if_id_in = %u", cfg->if_id_in); DBG2(DBG_CFG, " if_id_out = %u", cfg->if_id_out); DBG2(DBG_CFG, " mark_in = %u/%u", cfg->mark_in.value, cfg->mark_in.mask); DBG2(DBG_CFG, " mark_in_sa = %u", has_opt(OPT_MARK_IN_SA)); DBG2(DBG_CFG, " mark_out = %u/%u", cfg->mark_out.value, cfg->mark_out.mask); DBG2(DBG_CFG, " set_mark_in = %u/%u", cfg->set_mark_in.value, cfg->set_mark_in.mask); DBG2(DBG_CFG, " set_mark_out = %u/%u", cfg->set_mark_out.value, cfg->set_mark_out.mask); DBG2(DBG_CFG, " inactivity = %llu", cfg->inactivity); DBG2(DBG_CFG, " proposals = %#P", data->proposals); DBG2(DBG_CFG, " local_ts = %#R", data->local_ts); DBG2(DBG_CFG, " remote_ts = %#R", data->remote_ts); DBG2(DBG_CFG, " hw_offload = %N", hw_offload_names, cfg->hw_offload); DBG2(DBG_CFG, " sha256_96 = %u", has_opt(OPT_SHA256_96)); DBG2(DBG_CFG, " copy_df = %u", !has_opt(OPT_NO_COPY_DF)); DBG2(DBG_CFG, " copy_ecn = %u", !has_opt(OPT_NO_COPY_ECN)); DBG2(DBG_CFG, " copy_dscp = %N", dscp_copy_names, cfg->copy_dscp); } /** * Clean up CHILD config data */ static void free_child_data(child_data_t *data) { data->proposals->destroy_offset(data->proposals, offsetof(proposal_t, destroy)); data->local_ts->destroy_offset(data->local_ts, offsetof(traffic_selector_t, destroy)); data->remote_ts->destroy_offset(data->remote_ts, offsetof(traffic_selector_t, destroy)); free(data->cfg.updown); free(data->cfg.interface); } /** * Common proposal parsing */ static bool parse_proposal(linked_list_t *list, protocol_id_t proto, chunk_t v) { char buf[BUF_LEN]; proposal_t *proposal; if (!vici_stringify(v, buf, sizeof(buf))) { return FALSE; } if (strcaseeq("default", buf)) { proposal = proposal_create_default(proto); if (proposal) { list->insert_last(list, proposal); } proposal = proposal_create_default_aead(proto); if (proposal) { list->insert_last(list, proposal); } return TRUE; } proposal = proposal_create_from_string(proto, buf); if (proposal) { list->insert_last(list, proposal); return TRUE; } return FALSE; } /** * Parse IKE proposal */ CALLBACK(parse_ike_proposal, bool, linked_list_t *out, chunk_t v) { return parse_proposal(out, PROTO_IKE, v); } /** * Parse ESP proposal */ CALLBACK(parse_esp_proposal, bool, linked_list_t *out, chunk_t v) { return parse_proposal(out, PROTO_ESP, v); } /** * Parse AH proposal */ CALLBACK(parse_ah_proposal, bool, linked_list_t *out, chunk_t v) { return parse_proposal(out, PROTO_AH, v); } /** * Parse a traffic selector */ CALLBACK(parse_ts, bool, linked_list_t *out, chunk_t v) { char buf[BUF_LEN], *protoport, *sep, *port = "", *end; traffic_selector_t *ts = NULL; struct protoent *protoent; struct servent *svc; long int p; uint16_t from = 0, to = 0xffff; uint8_t proto = 0; if (!vici_stringify(v, buf, sizeof(buf))) { return FALSE; } protoport = strchr(buf, '['); if (protoport) { *(protoport++) = '\0'; sep = strrchr(protoport, ']'); if (!sep) { return FALSE; } *sep = '\0'; sep = strchr(protoport, '/'); if (sep) { /* protocol/port */ *sep = '\0'; port = sep + 1; } if (streq(protoport, "any")) { proto = 0; } else { protoent = getprotobyname(protoport); if (protoent) { proto = protoent->p_proto; } else { p = strtol(protoport, &end, 0); if ((*protoport && *end) || p < 0 || p > 0xff) { return FALSE; } proto = (uint8_t)p; } } if (streq(port, "opaque")) { from = 0xffff; to = 0; } else if (*port && !streq(port, "any")) { svc = getservbyname(port, NULL); if (svc) { from = to = ntohs(svc->s_port); } else { p = strtol(port, &end, 0); if (p < 0 || p > 0xffff) { return FALSE; } from = p; if (*end == '-') { port = end + 1; p = strtol(port, &end, 0); if (p < 0 || p > 0xffff) { return FALSE; } } to = p; if (*end) { return FALSE; } } } } if (streq(buf, "dynamic")) { ts = traffic_selector_create_dynamic(proto, from, to); } else if (strchr(buf, '-')) { host_t *lower, *upper; ts_type_t type; if (host_create_from_range(buf, &lower, &upper)) { type = (lower->get_family(lower) == AF_INET) ? TS_IPV4_ADDR_RANGE : TS_IPV6_ADDR_RANGE; ts = traffic_selector_create_from_bytes(proto, type, lower->get_address(lower), from, upper->get_address(upper), to); lower->destroy(lower); upper->destroy(upper); } } else { ts = traffic_selector_create_from_cidr(buf, proto, from, to); } if (!ts) { return FALSE; } out->insert_last(out, ts); return TRUE; } /** * Parse a string */ CALLBACK(parse_string, bool, char **out, chunk_t v) { if (!chunk_printable(v, NULL, ' ')) { return FALSE; } free(*out); *out = NULL; if (asprintf(out, "%.*s", (int)v.len, v.ptr) == -1) { return FALSE; } return TRUE; } /** * Map a string to an integer */ typedef struct { char *str; int d; } enum_map_t; /** * Parse a string to an integer mapping */ static bool parse_map(enum_map_t *map, int count, int *out, chunk_t v) { char buf[BUF_LEN]; int i; if (!vici_stringify(v, buf, sizeof(buf))) { return FALSE; } for (i = 0; i < count; i++) { if (strcaseeq(map[i].str, buf)) { *out = map[i].d; return TRUE; } } return FALSE; } /** * Parse a boolean */ CALLBACK(parse_bool, bool, bool *out, chunk_t v) { enum_map_t map[] = { { "yes", TRUE }, { "true", TRUE }, { "enabled", TRUE }, { "1", TRUE }, { "no", FALSE }, { "false", FALSE }, { "disabled", FALSE }, { "0", FALSE }, }; int d; if (parse_map(map, countof(map), &d, v)) { *out = d; return TRUE; } return FALSE; } /** * Parse a ipsec_mode_t */ CALLBACK(parse_mode, bool, child_cfg_create_t *cfg, chunk_t v) { enum_map_t map[] = { { "tunnel", MODE_TUNNEL }, { "transport", MODE_TRANSPORT }, { "transport_proxy", MODE_TRANSPORT }, { "beet", MODE_BEET }, { "drop", MODE_DROP }, { "pass", MODE_PASS }, }; int d; if (parse_map(map, countof(map), &d, v)) { cfg->mode = d; if ((d == MODE_TRANSPORT) && (v.len > 9)) { cfg->options |= OPT_PROXY_MODE; } return TRUE; } return FALSE; } /** * Enable a child_cfg_option_t, the flag controls whether the option is enabled * if the parsed value is TRUE or FALSE. */ static bool parse_option(child_cfg_option_t *out, child_cfg_option_t opt, chunk_t v, bool add_if_true) { bool val; if (parse_bool(&val, v)) { if (val == add_if_true) { *out |= opt; } return TRUE; } return FALSE; } /** * Parse OPT_HOSTACCESS option */ CALLBACK(parse_opt_haccess, bool, child_cfg_option_t *out, chunk_t v) { return parse_option(out, OPT_HOSTACCESS, v, TRUE); } /** * Parse OPT_NO_POLICIES option */ CALLBACK(parse_opt_policies, bool, child_cfg_option_t *out, chunk_t v) { return parse_option(out, OPT_NO_POLICIES, v, FALSE); } /** * Parse OPT_FWD_OUT_POLICIES option */ CALLBACK(parse_opt_fwd_out, bool, child_cfg_option_t *out, chunk_t v) { return parse_option(out, OPT_FWD_OUT_POLICIES, v, TRUE); } /** * Parse OPT_IPCOMP option */ CALLBACK(parse_opt_ipcomp, bool, child_cfg_option_t *out, chunk_t v) { return parse_option(out, OPT_IPCOMP, v, TRUE); } /** * Parse OPT_SHA256_96 option */ CALLBACK(parse_opt_sha256_96, bool, child_cfg_option_t *out, chunk_t v) { return parse_option(out, OPT_SHA256_96, v, TRUE); } /** * Parse OPT_MARK_IN_SA option */ CALLBACK(parse_opt_mark_in, bool, child_cfg_option_t *out, chunk_t v) { return parse_option(out, OPT_MARK_IN_SA, v, TRUE); } /** * Parse OPT_NO_COPY_DF option */ CALLBACK(parse_opt_copy_df, bool, child_cfg_option_t *out, chunk_t v) { return parse_option(out, OPT_NO_COPY_DF, v, FALSE); } /** * Parse OPT_NO_COPY_ECN option */ CALLBACK(parse_opt_copy_ecn, bool, child_cfg_option_t *out, chunk_t v) { return parse_option(out, OPT_NO_COPY_ECN, v, FALSE); } /** * Parse a dscp_copy_t */ CALLBACK(parse_copy_dscp, bool, dscp_copy_t *out, chunk_t v) { enum_map_t map[] = { { "no", DSCP_COPY_NO }, { "in", DSCP_COPY_IN_ONLY }, { "out", DSCP_COPY_OUT_ONLY }, { "yes", DSCP_COPY_YES }, }; int d; if (parse_map(map, countof(map), &d, v)) { *out = d; return TRUE; } return FALSE; } /** * Parse an action_t */ CALLBACK(parse_action, bool, action_t *out, chunk_t v) { enum_map_t map[] = { { "start", ACTION_RESTART }, { "restart", ACTION_RESTART }, { "route", ACTION_ROUTE }, { "trap", ACTION_ROUTE }, { "none", ACTION_NONE }, { "clear", ACTION_NONE }, }; int d; if (parse_map(map, countof(map), &d, v)) { *out = d; return TRUE; } return FALSE; } /** * Parse an hw_offload_t */ CALLBACK(parse_hw_offload, bool, action_t *out, chunk_t v) { enum_map_t map[] = { { "no", HW_OFFLOAD_NO }, { "yes", HW_OFFLOAD_YES }, { "auto", HW_OFFLOAD_AUTO }, }; int d; if (parse_map(map, countof(map), &d, v)) { *out = d; return TRUE; } return FALSE; } /** * Parse a uint32_t with the given base */ static bool parse_uint32_base(uint32_t *out, chunk_t v, int base) { char buf[16], *end; u_long l; if (!vici_stringify(v, buf, sizeof(buf))) { return FALSE; } l = strtoul(buf, &end, base); if (*end == 0) { *out = l; return TRUE; } return FALSE; } /** * Parse a uint32_t */ CALLBACK(parse_uint32, bool, uint32_t *out, chunk_t v) { return parse_uint32_base(out, v, 0); } /** * Parse a uint32_t in binary encoding */ CALLBACK(parse_uint32_bin, bool, uint32_t *out, chunk_t v) { return parse_uint32_base(out, v, 2); } /** * Parse a uint64_t */ CALLBACK(parse_uint64, bool, uint64_t *out, chunk_t v) { char buf[16], *end; unsigned long long l; if (!vici_stringify(v, buf, sizeof(buf))) { return FALSE; } l = strtoull(buf, &end, 0); if (*end == 0) { *out = l; return TRUE; } return FALSE; } /** * Parse a relative time */ CALLBACK(parse_time, bool, uint64_t *out, chunk_t v) { char buf[16], *end; u_long l; if (!vici_stringify(v, buf, sizeof(buf))) { return FALSE; } l = strtoul(buf, &end, 0); while (*end == ' ') { end++; } switch (*end) { case 'd': case 'D': l *= 24; /* fall */ case 'h': case 'H': l *= 60; /* fall */ case 'm': case 'M': l *= 60; /* fall */ case 's': case 'S': end++; break; case '\0': break; default: return FALSE; } if (*end) { return FALSE; } *out = l; return TRUE; } /** * Parse a relative time (32-bit) */ CALLBACK(parse_time32, bool, uint32_t *out, chunk_t v) { uint64_t time; if (parse_time(&time, v)) { *out = time; return TRUE; } return FALSE; } /** * Parse byte volume */ CALLBACK(parse_bytes, bool, uint64_t *out, chunk_t v) { char buf[16], *end; unsigned long long l; if (!vici_stringify(v, buf, sizeof(buf))) { return FALSE; } l = strtoull(buf, &end, 0); while (*end == ' ') { end++; } switch (*end) { case 'g': case 'G': l *= 1024; /* fall */ case 'm': case 'M': l *= 1024; /* fall */ case 'k': case 'K': l *= 1024; end++; break; case '\0': break; default: return FALSE; } if (*end) { return FALSE; } *out = l; return TRUE; } /** * Parse a mark_t */ CALLBACK(parse_mark, bool, mark_t *out, chunk_t v) { char buf[32]; if (!vici_stringify(v, buf, sizeof(buf))) { return FALSE; } return mark_from_string(buf, MARK_OP_UNIQUE, out); } /** * Parse a mark_t when using it as set_mark. */ CALLBACK(parse_set_mark, bool, mark_t *out, chunk_t v) { char buf[32]; if (!vici_stringify(v, buf, sizeof(buf))) { return FALSE; } return mark_from_string(buf, MARK_OP_SAME, out); } /** * Parse interface ID */ CALLBACK(parse_if_id, bool, uint32_t *out, chunk_t v) { char buf[32]; if (!vici_stringify(v, buf, sizeof(buf))) { return FALSE; } return if_id_from_string(buf, out); } /** * Parse TFC padding option */ CALLBACK(parse_tfc, bool, uint32_t *out, chunk_t v) { if (chunk_equals(v, chunk_from_str("mtu"))) { *out = -1; return TRUE; } return parse_uint32(out, v); } /** * Parse 6-bit DSCP value */ CALLBACK(parse_dscp, bool, uint8_t *out, chunk_t v) { if (parse_uint32_bin(out, v)) { *out = *out & 0x3f; return TRUE; } return FALSE; } /** * Parse authentication config */ CALLBACK(parse_auth, bool, auth_cfg_t *cfg, chunk_t v) { char buf[64], *pos; eap_vendor_type_t *type; if (!vici_stringify(v, buf, sizeof(buf))) { return FALSE; } if (strpfx(buf, "ike:") || strpfx(buf, "pubkey") || strpfx(buf, "rsa") || strpfx(buf, "ecdsa") || strpfx(buf, "bliss")) { cfg->add(cfg, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY); cfg->add_pubkey_constraints(cfg, buf, TRUE); return TRUE; } if (strcaseeq(buf, "psk")) { cfg->add(cfg, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PSK); return TRUE; } if (strcasepfx(buf, "xauth")) { pos = strchr(buf, '-'); if (pos) { cfg->add(cfg, AUTH_RULE_XAUTH_BACKEND, strdup(++pos)); } cfg->add(cfg, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_XAUTH); return TRUE; } if (strcasepfx(buf, "eap")) { char *pos; cfg->add(cfg, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP); pos = strchr(buf, ':'); if (pos) { *pos = 0; cfg->add_pubkey_constraints(cfg, pos + 1, FALSE); } type = eap_vendor_type_from_string(buf); if (type) { cfg->add(cfg, AUTH_RULE_EAP_TYPE, type->type); if (type->vendor) { cfg->add(cfg, AUTH_RULE_EAP_VENDOR, type->vendor); } free(type); } return TRUE; } return FALSE; } /** * Parse identity; add as auth rule to config */ static bool parse_id(auth_cfg_t *cfg, auth_rule_t rule, chunk_t v) { char buf[BUF_LEN]; if (!vici_stringify(v, buf, sizeof(buf))) { return FALSE; } cfg->add(cfg, rule, identification_create_from_string(buf)); return TRUE; } /** * Parse IKE identity */ CALLBACK(parse_ike_id, bool, auth_cfg_t *cfg, chunk_t v) { return parse_id(cfg, AUTH_RULE_IDENTITY, v); } /** * Parse CA identity constraint */ CALLBACK(parse_ca_id, bool, auth_cfg_t *cfg, chunk_t v) { return parse_id(cfg, AUTH_RULE_CA_IDENTITY, v); } /** * Parse AAA identity */ CALLBACK(parse_aaa_id, bool, auth_cfg_t *cfg, chunk_t v) { return parse_id(cfg, AUTH_RULE_AAA_IDENTITY, v); } /** * Parse EAP identity */ CALLBACK(parse_eap_id, bool, auth_cfg_t *cfg, chunk_t v) { return parse_id(cfg, AUTH_RULE_EAP_IDENTITY, v); } /** * Parse XAuth identity */ CALLBACK(parse_xauth_id, bool, auth_cfg_t *cfg, chunk_t v) { return parse_id(cfg, AUTH_RULE_XAUTH_IDENTITY, v); } /** * Parse group membership */ CALLBACK(parse_group, bool, auth_cfg_t *cfg, chunk_t v) { return parse_id(cfg, AUTH_RULE_GROUP, v); } /** * Parse certificate policy */ CALLBACK(parse_cert_policy, bool, auth_cfg_t *cfg, chunk_t v) { char buf[BUF_LEN]; if (!vici_stringify(v, buf, sizeof(buf))) { return FALSE; } cfg->add(cfg, AUTH_RULE_CERT_POLICY, strdup(buf)); return TRUE; } /** * Add a certificate as auth rule to config */ static bool add_cert(auth_data_t *auth, auth_rule_t rule, certificate_t *cert) { vici_authority_t *authority; vici_cred_t *cred; if (rule == AUTH_RULE_CA_CERT) { authority = auth->request->this->authority; cert = authority->add_ca_cert(authority, cert); } else { cred = auth->request->this->cred; cert = cred->add_cert(cred, cert); } auth->cfg->add(auth->cfg, rule, cert); return TRUE; } /** * Parse a certificate; add as auth rule to config */ static bool parse_cert(auth_data_t *auth, auth_rule_t rule, chunk_t v) { certificate_t *cert; cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, BUILD_BLOB_PEM, v, BUILD_END); if (cert) { return add_cert(auth, rule, cert); } return FALSE; } /** * Parse subject certificates */ CALLBACK(parse_certs, bool, auth_data_t *auth, chunk_t v) { return parse_cert(auth, AUTH_RULE_SUBJECT_CERT, v); } /** * Parse CA certificates */ CALLBACK(parse_cacerts, bool, auth_data_t *auth, chunk_t v) { return parse_cert(auth, AUTH_RULE_CA_CERT, v); } /** * Parse raw public keys */ CALLBACK(parse_pubkeys, bool, auth_data_t *auth, chunk_t v) { certificate_t *cert; cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_TRUSTED_PUBKEY, BUILD_BLOB_PEM, v, BUILD_END); if (cert) { return add_cert(auth, AUTH_RULE_SUBJECT_CERT, cert); } return FALSE; } /** * Parse revocation status */ CALLBACK(parse_revocation, bool, auth_cfg_t *cfg, chunk_t v) { enum_map_t map[] = { { "strict", VALIDATION_GOOD }, { "ifuri", VALIDATION_SKIPPED }, { "relaxed", VALIDATION_FAILED }, }; int d; if (parse_map(map, countof(map), &d, v)) { if (d != VALIDATION_FAILED) { cfg->add(cfg, AUTH_RULE_CRL_VALIDATION, d); } return TRUE; } return FALSE; } /** * Parse list items to comma separated strings */ CALLBACK(parse_stringlist, bool, char **out, chunk_t v) { char *current; if (!chunk_printable(v, NULL, ' ')) { return FALSE; } current = *out; if (current) { if (asprintf(out, "%s, %.*s", current, (int)v.len, v.ptr) == -1) { return FALSE; } free(current); } else { if (asprintf(out, "%.*s", (int)v.len, v.ptr) == -1) { return FALSE; } } return TRUE; } /** * Parse an fragmentation_t */ CALLBACK(parse_frag, bool, fragmentation_t *out, chunk_t v) { enum_map_t map[] = { { "yes", FRAGMENTATION_YES }, { "accept", FRAGMENTATION_ACCEPT }, { "no", FRAGMENTATION_NO }, { "force", FRAGMENTATION_FORCE }, }; int d; if (parse_map(map, countof(map), &d, v)) { *out = d; return TRUE; } return FALSE; } /** * Parse a childless_t */ CALLBACK(parse_childless, bool, childless_t *out, chunk_t v) { enum_map_t map[] = { { "allow", CHILDLESS_ALLOW }, { "never", CHILDLESS_NEVER }, { "force", CHILDLESS_FORCE }, }; int d; if (parse_map(map, countof(map), &d, v)) { *out = d; return TRUE; } return FALSE; } /** * Parse a cert_policy_t */ CALLBACK(parse_send_cert, bool, cert_policy_t *out, chunk_t v) { enum_map_t map[] = { { "ifasked", CERT_SEND_IF_ASKED }, { "always", CERT_ALWAYS_SEND }, { "never", CERT_NEVER_SEND }, }; int d; if (parse_map(map, countof(map), &d, v)) { *out = d; return TRUE; } return FALSE; } /** * Parse a unique_policy_t */ CALLBACK(parse_unique, bool, unique_policy_t *out, chunk_t v) { enum_map_t map[] = { { "never", UNIQUE_NEVER }, { "no", UNIQUE_NO }, { "replace", UNIQUE_REPLACE }, { "keep", UNIQUE_KEEP }, }; int d; if (parse_map(map, countof(map), &d, v)) { *out = d; return TRUE; } return FALSE; } /** * Parse host_t into a list */ CALLBACK(parse_hosts, bool, linked_list_t *list, chunk_t v) { char buf[64]; host_t *host; if (!vici_stringify(v, buf, sizeof(buf))) { return FALSE; } host = host_create_from_string(buf, 0); if (!host) { return FALSE; } list->insert_last(list, host); return TRUE; } /** * Parse peer/ppk ID */ CALLBACK(parse_peer_id, bool, identification_t **out, chunk_t v) { char buf[BUF_LEN]; if (!vici_stringify(v, buf, sizeof(buf))) { return FALSE; } *out = identification_create_from_string(buf); return TRUE; } CALLBACK(cert_kv, bool, cert_data_t *cert, vici_message_t *message, char *name, chunk_t value) { parse_rule_t rules[] = { { "handle", parse_string, &cert->handle }, { "slot", parse_uint32, &cert->slot }, { "module", parse_string, &cert->module }, { "file", parse_string, &cert->file }, }; return parse_rules(rules, countof(rules), name, value, &cert->request->reply); } CALLBACK(child_li, bool, child_data_t *child, vici_message_t *message, char *name, chunk_t value) { parse_rule_t rules[] = { { "ah_proposals", parse_ah_proposal, child->proposals }, { "esp_proposals", parse_esp_proposal, child->proposals }, { "local_ts", parse_ts, child->local_ts }, { "remote_ts", parse_ts, child->remote_ts }, }; return parse_rules(rules, countof(rules), name, value, &child->request->reply); } CALLBACK(child_kv, bool, child_data_t *child, vici_message_t *message, char *name, chunk_t value) { parse_rule_t rules[] = { { "updown", parse_string, &child->cfg.updown }, { "hostaccess", parse_opt_haccess, &child->cfg.options }, { "mode", parse_mode, &child->cfg }, { "policies", parse_opt_policies, &child->cfg.options }, { "policies_fwd_out", parse_opt_fwd_out, &child->cfg.options }, { "replay_window", parse_uint32, &child->replay_window }, { "rekey_time", parse_time, &child->cfg.lifetime.time.rekey }, { "life_time", parse_time, &child->cfg.lifetime.time.life }, { "rand_time", parse_time, &child->cfg.lifetime.time.jitter }, { "rekey_bytes", parse_bytes, &child->cfg.lifetime.bytes.rekey }, { "life_bytes", parse_bytes, &child->cfg.lifetime.bytes.life }, { "rand_bytes", parse_bytes, &child->cfg.lifetime.bytes.jitter }, { "rekey_packets", parse_uint64, &child->cfg.lifetime.packets.rekey }, { "life_packets", parse_uint64, &child->cfg.lifetime.packets.life }, { "rand_packets", parse_uint64, &child->cfg.lifetime.packets.jitter }, { "dpd_action", parse_action, &child->cfg.dpd_action }, { "start_action", parse_action, &child->cfg.start_action }, { "close_action", parse_action, &child->cfg.close_action }, { "ipcomp", parse_opt_ipcomp, &child->cfg.options }, { "inactivity", parse_time32, &child->cfg.inactivity }, { "reqid", parse_uint32, &child->cfg.reqid }, { "mark_in", parse_mark, &child->cfg.mark_in }, { "mark_in_sa", parse_opt_mark_in, &child->cfg.options }, { "mark_out", parse_mark, &child->cfg.mark_out }, { "set_mark_in", parse_set_mark, &child->cfg.set_mark_in }, { "set_mark_out", parse_set_mark, &child->cfg.set_mark_out }, { "tfc_padding", parse_tfc, &child->cfg.tfc }, { "priority", parse_uint32, &child->cfg.priority }, { "interface", parse_string, &child->cfg.interface }, { "hw_offload", parse_hw_offload, &child->cfg.hw_offload }, { "sha256_96", parse_opt_sha256_96,&child->cfg.options }, { "copy_df", parse_opt_copy_df, &child->cfg.options }, { "copy_ecn", parse_opt_copy_ecn, &child->cfg.options }, { "copy_dscp", parse_copy_dscp, &child->cfg.copy_dscp }, { "if_id_in", parse_if_id, &child->cfg.if_id_in }, { "if_id_out", parse_if_id, &child->cfg.if_id_out }, }; return parse_rules(rules, countof(rules), name, value, &child->request->reply); } CALLBACK(auth_li, bool, auth_data_t *auth, vici_message_t *message, char *name, chunk_t value) { parse_rule_t rules[] = { { "groups", parse_group, auth->cfg }, { "cert_policy", parse_cert_policy, auth->cfg }, { "certs", parse_certs, auth }, { "cacerts", parse_cacerts, auth }, { "pubkeys", parse_pubkeys, auth }, }; return parse_rules(rules, countof(rules), name, value, &auth->request->reply); } CALLBACK(auth_kv, bool, auth_data_t *auth, vici_message_t *message, char *name, chunk_t value) { parse_rule_t rules[] = { { "auth", parse_auth, auth->cfg }, { "id", parse_ike_id, auth->cfg }, { "ca_id", parse_ca_id, auth->cfg }, { "aaa_id", parse_aaa_id, auth->cfg }, { "eap_id", parse_eap_id, auth->cfg }, { "xauth_id", parse_xauth_id, auth->cfg }, { "revocation", parse_revocation, auth->cfg }, { "round", parse_uint32, &auth->round }, }; return parse_rules(rules, countof(rules), name, value, &auth->request->reply); } CALLBACK(peer_li, bool, peer_data_t *peer, vici_message_t *message, char *name, chunk_t value) { parse_rule_t rules[] = { { "local_addrs", parse_stringlist, &peer->local_addrs }, { "remote_addrs", parse_stringlist, &peer->remote_addrs }, { "proposals", parse_ike_proposal, peer->proposals }, { "vips", parse_hosts, peer->vips }, { "pools", parse_stringlist, &peer->pools }, }; return parse_rules(rules, countof(rules), name, value, &peer->request->reply); } CALLBACK(peer_kv, bool, peer_data_t *peer, vici_message_t *message, char *name, chunk_t value) { parse_rule_t rules[] = { { "version", parse_uint32, &peer->version }, { "aggressive", parse_bool, &peer->aggressive }, { "pull", parse_bool, &peer->pull }, { "dscp", parse_dscp, &peer->dscp }, { "encap", parse_bool, &peer->encap }, { "mobike", parse_bool, &peer->mobike }, { "dpd_delay", parse_time, &peer->dpd_delay }, { "dpd_timeout", parse_time, &peer->dpd_timeout }, { "fragmentation", parse_frag, &peer->fragmentation }, { "childless", parse_childless, &peer->childless }, { "send_certreq", parse_bool, &peer->send_certreq }, { "send_cert", parse_send_cert, &peer->send_cert }, { "keyingtries", parse_uint32, &peer->keyingtries }, { "unique", parse_unique, &peer->unique }, { "local_port", parse_uint32, &peer->local_port }, { "remote_port", parse_uint32, &peer->remote_port }, { "reauth_time", parse_time, &peer->reauth_time }, { "rekey_time", parse_time, &peer->rekey_time }, { "over_time", parse_time, &peer->over_time }, { "rand_time", parse_time, &peer->rand_time }, { "ppk_id", parse_peer_id, &peer->ppk_id }, { "ppk_required", parse_bool, &peer->ppk_required }, { "if_id_in", parse_if_id, &peer->if_id_in }, { "if_id_out", parse_if_id, &peer->if_id_out }, #ifdef ME { "mediation", parse_bool, &peer->mediation }, { "mediated_by", parse_string, &peer->mediated_by }, { "mediation_peer", parse_peer_id, &peer->peer_id }, #endif /* ME */ }; return parse_rules(rules, countof(rules), name, value, &peer->request->reply); } CALLBACK(auth_sn, bool, auth_data_t *auth, vici_message_t *message, vici_parse_context_t *ctx, char *name) { if (strcasepfx(name, "cert") || strcasepfx(name, "cacert")) { cert_data_t *data; auth_rule_t rule; certificate_t *cert; chunk_t handle; INIT(data, .request = auth->request, .slot = -1, ); if (!message->parse(message, ctx, NULL, cert_kv, NULL, data)) { free_cert_data(data); return FALSE; } if (!data->handle && !data->file) { auth->request->reply = create_reply("handle or file path missing: " "%s", name); free_cert_data(data); return FALSE; } else if (data->handle && data->file) { auth->request->reply = create_reply("handle and file path given: " "%s", name); free_cert_data(data); return FALSE; } if (data->file) { cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, BUILD_FROM_FILE, data->file, BUILD_END); } else { handle = chunk_from_hex(chunk_from_str(data->handle), NULL); if (data->slot != -1) { cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, BUILD_PKCS11_KEYID, handle, BUILD_PKCS11_SLOT, data->slot, data->module ? BUILD_PKCS11_MODULE : BUILD_END, data->module, BUILD_END); } else { cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509, BUILD_PKCS11_KEYID, handle, data->module ? BUILD_PKCS11_MODULE : BUILD_END, data->module, BUILD_END); } chunk_free(&handle); } free_cert_data(data); if (!cert) { auth->request->reply = create_reply("unable to load certificate: " "%s", name); return FALSE; } rule = strcasepfx(name, "cert") ? AUTH_RULE_SUBJECT_CERT : AUTH_RULE_CA_CERT; return add_cert(auth, rule, cert); } auth->request->reply = create_reply("invalid section: %s", name); return FALSE; } /** * Check and update lifetimes */ static void check_lifetimes(lifetime_cfg_t *lft) { /* if no hard lifetime specified, add one at soft lifetime + 10% */ if (lft->time.life == LFT_UNDEFINED) { lft->time.life = lft->time.rekey * 110 / 100; } if (lft->bytes.life == LFT_UNDEFINED) { lft->bytes.life = lft->bytes.rekey * 110 / 100; } if (lft->packets.life == LFT_UNDEFINED) { lft->packets.life = lft->packets.rekey * 110 / 100; } /* if no rand time defined, use difference of hard and soft */ if (lft->time.jitter == LFT_UNDEFINED) { lft->time.jitter = lft->time.life - min(lft->time.life, lft->time.rekey); } if (lft->bytes.jitter == LFT_UNDEFINED) { lft->bytes.jitter = lft->bytes.life - min(lft->bytes.life, lft->bytes.rekey); } if (lft->packets.jitter == LFT_UNDEFINED) { lft->packets.jitter = lft->packets.life - min(lft->packets.life, lft->packets.rekey); } } CALLBACK(children_sn, bool, peer_data_t *peer, vici_message_t *message, vici_parse_context_t *ctx, char *name) { child_data_t child = { .request = peer->request, .proposals = linked_list_create(), .local_ts = linked_list_create(), .remote_ts = linked_list_create(), .replay_window = REPLAY_UNDEFINED, .cfg = { .mode = MODE_TUNNEL, .lifetime = { .time = { .rekey = LFT_DEFAULT_CHILD_REKEY_TIME, .life = LFT_UNDEFINED, .jitter = LFT_UNDEFINED, }, .bytes = { .rekey = LFT_DEFAULT_CHILD_REKEY_BYTES, .life = LFT_UNDEFINED, .jitter = LFT_UNDEFINED, }, .packets = { .rekey = LFT_DEFAULT_CHILD_REKEY_PACKETS, .life = LFT_UNDEFINED, .jitter = LFT_UNDEFINED, }, }, }, }; child_cfg_t *cfg; proposal_t *proposal; traffic_selector_t *ts; if (!message->parse(message, ctx, NULL, child_kv, child_li, &child)) { free_child_data(&child); return FALSE; } if (child.local_ts->get_count(child.local_ts) == 0) { child.local_ts->insert_last(child.local_ts, traffic_selector_create_dynamic(0, 0, 65535)); } if (child.remote_ts->get_count(child.remote_ts) == 0) { child.remote_ts->insert_last(child.remote_ts, traffic_selector_create_dynamic(0, 0, 65535)); } if (child.proposals->get_count(child.proposals) == 0) { proposal = proposal_create_default_aead(PROTO_ESP); if (proposal) { child.proposals->insert_last(child.proposals, proposal); } proposal = proposal_create_default(PROTO_ESP); if (proposal) { child.proposals->insert_last(child.proposals, proposal); } } check_lifetimes(&child.cfg.lifetime); log_child_data(&child, name); cfg = child_cfg_create(name, &child.cfg); if (child.replay_window != REPLAY_UNDEFINED) { cfg->set_replay_window(cfg, child.replay_window); } while (child.local_ts->remove_first(child.local_ts, (void**)&ts) == SUCCESS) { cfg->add_traffic_selector(cfg, TRUE, ts); } while (child.remote_ts->remove_first(child.remote_ts, (void**)&ts) == SUCCESS) { cfg->add_traffic_selector(cfg, FALSE, ts); } while (child.proposals->remove_first(child.proposals, (void**)&proposal) == SUCCESS) { cfg->add_proposal(cfg, proposal); } peer->children->insert_last(peer->children, cfg); free_child_data(&child); return TRUE; } CALLBACK(peer_sn, bool, peer_data_t *peer, vici_message_t *message, vici_parse_context_t *ctx, char *name) { if (strcaseeq(name, "children")) { return message->parse(message, ctx, children_sn, NULL, NULL, peer); } if (strcasepfx(name, "local") || strcasepfx(name, "remote")) { enumerator_t *enumerator; linked_list_t *auths; auth_data_t *auth, *current; auth_rule_t rule; certificate_t *cert; pubkey_cert_t *pubkey_cert; identification_t *id; bool default_id = FALSE; INIT(auth, .request = peer->request, .cfg = auth_cfg_create(), ); if (!message->parse(message, ctx, auth_sn, auth_kv, auth_li, auth)) { free_auth_data(auth); return FALSE; } id = auth->cfg->get(auth->cfg, AUTH_RULE_IDENTITY); enumerator = auth->cfg->create_enumerator(auth->cfg); while (enumerator->enumerate(enumerator, &rule, &cert)) { if (rule == AUTH_RULE_SUBJECT_CERT && !default_id) { if (id == NULL) { id = cert->get_subject(cert); DBG1(DBG_CFG, " id not specified, defaulting to" " cert subject '%Y'", id); auth->cfg->add(auth->cfg, AUTH_RULE_IDENTITY, id->clone(id)); default_id = TRUE; } else if (cert->get_type(cert) == CERT_TRUSTED_PUBKEY && id->get_type != ID_ANY) { /* set the subject of all raw public keys to the id */ pubkey_cert = (pubkey_cert_t*)cert; pubkey_cert->set_subject(pubkey_cert, id); } } } enumerator->destroy(enumerator); auths = strcasepfx(name, "local") ? peer->local : peer->remote; enumerator = auths->create_enumerator(auths); while (enumerator->enumerate(enumerator, ¤t)) { if (auth->round < current->round) { break; } } auths->insert_before(auths, enumerator, auth); enumerator->destroy(enumerator); return TRUE; } peer->request->reply = create_reply("invalid section: %s", name); return FALSE; } /** * Perform start actions associated with a child config */ static void run_start_action(private_vici_config_t *this, peer_cfg_t *peer_cfg, child_cfg_t *child_cfg) { switch (child_cfg->get_start_action(child_cfg)) { case ACTION_RESTART: DBG1(DBG_CFG, "initiating '%s'", child_cfg->get_name(child_cfg)); charon->controller->initiate(charon->controller, peer_cfg->get_ref(peer_cfg), child_cfg->get_ref(child_cfg), NULL, NULL, 0, FALSE); break; case ACTION_ROUTE: DBG1(DBG_CFG, "installing '%s'", child_cfg->get_name(child_cfg)); switch (child_cfg->get_mode(child_cfg)) { case MODE_PASS: case MODE_DROP: charon->shunts->install(charon->shunts, peer_cfg->get_name(peer_cfg), child_cfg); break; default: charon->traps->install(charon->traps, peer_cfg, child_cfg); break; } break; default: break; } } /** * Undo start actions associated with a child config */ static void clear_start_action(private_vici_config_t *this, char *peer_name, child_cfg_t *child_cfg) { enumerator_t *enumerator, *children; child_sa_t *child_sa; ike_sa_t *ike_sa; uint32_t id = 0, others; array_t *ids = NULL, *ikeids = NULL; char *name; name = child_cfg->get_name(child_cfg); switch (child_cfg->get_start_action(child_cfg)) { case ACTION_RESTART: enumerator = charon->controller->create_ike_sa_enumerator( charon->controller, TRUE); while (enumerator->enumerate(enumerator, &ike_sa)) { if (!streq(ike_sa->get_name(ike_sa), peer_name)) { continue; } others = id = 0; children = ike_sa->create_child_sa_enumerator(ike_sa); while (children->enumerate(children, &child_sa)) { if (child_sa->get_state(child_sa) != CHILD_DELETING && child_sa->get_state(child_sa) != CHILD_DELETED) { if (streq(name, child_sa->get_name(child_sa))) { id = child_sa->get_unique_id(child_sa); } else { others++; } } } children->destroy(children); if (!ike_sa->get_child_count(ike_sa) || (id && !others)) { /* found no children or only matching, delete IKE_SA */ id = ike_sa->get_unique_id(ike_sa); array_insert_create_value(&ikeids, sizeof(id), ARRAY_TAIL, &id); } else { children = ike_sa->create_child_sa_enumerator(ike_sa); while (children->enumerate(children, &child_sa)) { if (streq(name, child_sa->get_name(child_sa))) { id = child_sa->get_unique_id(child_sa); array_insert_create_value(&ids, sizeof(id), ARRAY_TAIL, &id); } } children->destroy(children); } } enumerator->destroy(enumerator); if (array_count(ids)) { while (array_remove(ids, ARRAY_HEAD, &id)) { DBG1(DBG_CFG, "closing '%s' #%u", name, id); charon->controller->terminate_child(charon->controller, id, NULL, NULL, 0); } array_destroy(ids); } if (array_count(ikeids)) { while (array_remove(ikeids, ARRAY_HEAD, &id)) { DBG1(DBG_CFG, "closing IKE_SA #%u", id); charon->controller->terminate_ike(charon->controller, id, FALSE, NULL, NULL, 0); } array_destroy(ikeids); } break; case ACTION_ROUTE: DBG1(DBG_CFG, "uninstalling '%s'", name); switch (child_cfg->get_mode(child_cfg)) { case MODE_PASS: case MODE_DROP: charon->shunts->uninstall(charon->shunts, peer_name, name); break; default: charon->traps->uninstall(charon->traps, peer_name, name); break; } break; default: break; } } /** * Run or undo a start actions associated with a child config */ static void handle_start_action(private_vici_config_t *this, peer_cfg_t *peer_cfg, child_cfg_t *child_cfg, bool undo) { this->handling_actions = TRUE; this->lock->unlock(this->lock); if (undo) { clear_start_action(this, peer_cfg->get_name(peer_cfg), child_cfg); } else { run_start_action(this, peer_cfg, child_cfg); } this->lock->write_lock(this->lock); this->handling_actions = FALSE; } /** * Run or undo start actions associated with all child configs of a peer config */ static void handle_start_actions(private_vici_config_t *this, peer_cfg_t *peer_cfg, bool undo) { enumerator_t *enumerator; child_cfg_t *child_cfg; this->handling_actions = TRUE; this->lock->unlock(this->lock); enumerator = peer_cfg->create_child_cfg_enumerator(peer_cfg); while (enumerator->enumerate(enumerator, &child_cfg)) { if (undo) { clear_start_action(this, peer_cfg->get_name(peer_cfg), child_cfg); } else { run_start_action(this, peer_cfg, child_cfg); } } enumerator->destroy(enumerator); this->lock->write_lock(this->lock); this->handling_actions = FALSE; } /** * Replace children of a peer config by a new config */ static void replace_children(private_vici_config_t *this, peer_cfg_t *from, peer_cfg_t *to) { enumerator_t *enumerator; child_cfg_t *child; bool added; enumerator = to->replace_child_cfgs(to, from); while (enumerator->enumerate(enumerator, &child, &added)) { handle_start_action(this, to, child, !added); } enumerator->destroy(enumerator); } /** * Merge/replace a peer config with existing configs */ static void merge_config(private_vici_config_t *this, peer_cfg_t *peer_cfg) { peer_cfg_t *found; ike_cfg_t *ike_cfg; this->lock->write_lock(this->lock); while (this->handling_actions) { this->condvar->wait(this->condvar, this->lock); } found = this->conns->get(this->conns, peer_cfg->get_name(peer_cfg)); if (found) { ike_cfg = found->get_ike_cfg(found); if (peer_cfg->equals(peer_cfg, found) && ike_cfg->equals(ike_cfg, peer_cfg->get_ike_cfg(peer_cfg))) { DBG1(DBG_CFG, "updated vici connection: %s", peer_cfg->get_name(peer_cfg)); replace_children(this, peer_cfg, found); peer_cfg->destroy(peer_cfg); } else { DBG1(DBG_CFG, "replaced vici connection: %s", peer_cfg->get_name(peer_cfg)); this->conns->put(this->conns, peer_cfg->get_name(peer_cfg), peer_cfg); handle_start_actions(this, found, TRUE); handle_start_actions(this, peer_cfg, FALSE); found->destroy(found); } } else { DBG1(DBG_CFG, "added vici connection: %s", peer_cfg->get_name(peer_cfg)); this->conns->put(this->conns, peer_cfg->get_name(peer_cfg), peer_cfg); handle_start_actions(this, peer_cfg, FALSE); } this->condvar->signal(this->condvar); this->lock->unlock(this->lock); } CALLBACK(config_sn, bool, request_data_t *request, vici_message_t *message, vici_parse_context_t *ctx, char *name) { peer_data_t peer = { .request = request, .local = linked_list_create(), .remote = linked_list_create(), .vips = linked_list_create(), .children = linked_list_create(), .proposals = linked_list_create(), .mobike = TRUE, .send_certreq = TRUE, .pull = TRUE, .send_cert = CERT_SEND_IF_ASKED, .version = IKE_ANY, .remote_port = IKEV2_UDP_PORT, .fragmentation = FRAGMENTATION_YES, .unique = UNIQUE_NO, .keyingtries = 1, .rekey_time = LFT_UNDEFINED, .reauth_time = LFT_UNDEFINED, .over_time = LFT_UNDEFINED, .rand_time = LFT_UNDEFINED, }; enumerator_t *enumerator; peer_cfg_create_t cfg; peer_cfg_t *peer_cfg; ike_cfg_create_t ike; ike_cfg_t *ike_cfg; child_cfg_t *child_cfg; auth_data_t *auth; proposal_t *proposal; host_t *host; char *str; DBG2(DBG_CFG, " conn %s:", name); if (!message->parse(message, ctx, peer_sn, peer_kv, peer_li, &peer)) { free_peer_data(&peer); return FALSE; } if (peer.local->get_count(peer.local) == 0) { INIT(auth, .cfg = auth_cfg_create(), ); peer.local->insert_last(peer.local, auth); } if (peer.remote->get_count(peer.remote) == 0) { INIT(auth, .cfg = auth_cfg_create(), ); peer.remote->insert_last(peer.remote, auth); } if (peer.proposals->get_count(peer.proposals) == 0) { proposal = proposal_create_default(PROTO_IKE); if (proposal) { peer.proposals->insert_last(peer.proposals, proposal); } proposal = proposal_create_default_aead(PROTO_IKE); if (proposal) { peer.proposals->insert_last(peer.proposals, proposal); } } if (!peer.local_addrs) { peer.local_addrs = strdup("%any"); } if (!peer.remote_addrs) { peer.remote_addrs = strdup("%any"); } if (!peer.local_port) { peer.local_port = charon->socket->get_port(charon->socket, FALSE); } if (peer.rekey_time == LFT_UNDEFINED && peer.reauth_time == LFT_UNDEFINED) { /* apply a default rekey time if no rekey/reauth time set */ peer.rekey_time = LFT_DEFAULT_IKE_REKEY_TIME; peer.reauth_time = 0; } if (peer.rekey_time == LFT_UNDEFINED) { peer.rekey_time = 0; } if (peer.reauth_time == LFT_UNDEFINED) { peer.reauth_time = 0; } if (peer.over_time == LFT_UNDEFINED) { /* default over_time to 10% of rekey/reauth time if not given */ peer.over_time = max(peer.rekey_time, peer.reauth_time) / 10; } if (peer.rand_time == LFT_UNDEFINED) { /* default rand_time to over_time if not given, but don't make it * longer than half of rekey/rauth time */ if (peer.rekey_time && peer.reauth_time) { peer.rand_time = min(peer.rekey_time, peer.reauth_time); } else { peer.rand_time = max(peer.rekey_time, peer.reauth_time); } peer.rand_time = min(peer.over_time, peer.rand_time / 2); } #ifdef ME if (peer.mediation && peer.mediated_by) { DBG1(DBG_CFG, "a mediation connection cannot be a mediated connection " "at the same time, config discarded"); free_peer_data(&peer); return FALSE; } if (peer.mediation) { /* force unique connections for mediation connections */ peer.unique = UNIQUE_REPLACE; } else if (peer.mediated_by) { /* fallback to remote identity of first auth round if peer_id is not * given explicitly */ auth_cfg_t *cfg; if (!peer.peer_id && peer.remote->get_first(peer.remote, (void**)&cfg) == SUCCESS) { peer.peer_id = cfg->get(cfg, AUTH_RULE_IDENTITY); if (peer.peer_id) { peer.peer_id = peer.peer_id->clone(peer.peer_id); } else { DBG1(DBG_CFG, "mediation peer missing for mediated connection, " "config discarded"); free_peer_data(&peer); return FALSE; } } } #endif /* ME */ log_peer_data(&peer); ike = (ike_cfg_create_t){ .version = peer.version, .local = peer.local_addrs, .local_port = peer.local_port, .remote = peer.remote_addrs, .remote_port = peer.remote_port, .no_certreq = !peer.send_certreq, .force_encap = peer.encap, .fragmentation = peer.fragmentation, .childless = peer.childless, .dscp = peer.dscp, }; ike_cfg = ike_cfg_create(&ike); cfg = (peer_cfg_create_t){ .cert_policy = peer.send_cert, .unique = peer.unique, .keyingtries = peer.keyingtries, .rekey_time = peer.rekey_time, .reauth_time = peer.reauth_time, .jitter_time = peer.rand_time, .over_time = peer.over_time, .no_mobike = !peer.mobike, .aggressive = peer.aggressive, .push_mode = !peer.pull, .dpd = peer.dpd_delay, .dpd_timeout = peer.dpd_timeout, .ppk_id = peer.ppk_id ? peer.ppk_id->clone(peer.ppk_id) : NULL, .ppk_required = peer.ppk_required, .if_id_in = peer.if_id_in, .if_id_out = peer.if_id_out, }; #ifdef ME cfg.mediation = peer.mediation; if (peer.mediated_by) { cfg.mediated_by = peer.mediated_by; if (peer.peer_id) { cfg.peer_id = peer.peer_id->clone(peer.peer_id); } } #endif /* ME */ peer_cfg = peer_cfg_create(name, ike_cfg, &cfg); while (peer.local->remove_first(peer.local, (void**)&auth) == SUCCESS) { peer_cfg->add_auth_cfg(peer_cfg, auth->cfg, TRUE); auth->cfg = NULL; free_auth_data(auth); } while (peer.remote->remove_first(peer.remote, (void**)&auth) == SUCCESS) { peer_cfg->add_auth_cfg(peer_cfg, auth->cfg, FALSE); auth->cfg = NULL; free_auth_data(auth); } while (peer.children->remove_first(peer.children, (void**)&child_cfg) == SUCCESS) { peer_cfg->add_child_cfg(peer_cfg, child_cfg); } while (peer.proposals->remove_first(peer.proposals, (void**)&proposal) == SUCCESS) { ike_cfg->add_proposal(ike_cfg, proposal); } while (peer.vips->remove_first(peer.vips, (void**)&host) == SUCCESS) { peer_cfg->add_virtual_ip(peer_cfg, host); } if (peer.pools) { enumerator = enumerator_create_token(peer.pools, ",", " "); while (enumerator->enumerate(enumerator, &str)) { peer_cfg->add_pool(peer_cfg, str); } enumerator->destroy(enumerator); } free_peer_data(&peer); merge_config(request->this, peer_cfg); return TRUE; } CALLBACK(load_conn, vici_message_t*, private_vici_config_t *this, char *name, u_int id, vici_message_t *message) { request_data_t request = { .this = this, }; if (!message->parse(message, NULL, config_sn, NULL, NULL, &request)) { if (request.reply) { return request.reply; } return create_reply("parsing request failed"); } return create_reply(NULL); } CALLBACK(unload_conn, vici_message_t*, private_vici_config_t *this, char *name, u_int id, vici_message_t *message) { peer_cfg_t *cfg; char *conn_name; conn_name = message->get_str(message, NULL, "name"); if (!conn_name) { return create_reply("unload: missing connection name"); } this->lock->write_lock(this->lock); while (this->handling_actions) { this->condvar->wait(this->condvar, this->lock); } cfg = this->conns->remove(this->conns, conn_name); if (cfg) { handle_start_actions(this, cfg, TRUE); cfg->destroy(cfg); } this->condvar->signal(this->condvar); this->lock->unlock(this->lock); if (!cfg) { return create_reply("unload: connection '%s' not found", conn_name); } return create_reply(NULL); } CALLBACK(get_conns, vici_message_t*, private_vici_config_t *this, char *name, u_int id, vici_message_t *message) { vici_builder_t *builder; enumerator_t *enumerator; peer_cfg_t *cfg; builder = vici_builder_create(); builder->begin_list(builder, "conns"); this->lock->read_lock(this->lock); enumerator = this->conns->create_enumerator(this->conns); while (enumerator->enumerate(enumerator, NULL, &cfg)) { builder->add_li(builder, "%s", cfg->get_name(cfg)); } enumerator->destroy(enumerator); this->lock->unlock(this->lock); builder->end_list(builder); return builder->finalize(builder); } static void manage_command(private_vici_config_t *this, char *name, vici_command_cb_t cb, bool reg) { this->dispatcher->manage_command(this->dispatcher, name, reg ? cb : NULL, this); } /** * (Un-)register dispatcher functions */ static void manage_commands(private_vici_config_t *this, bool reg) { manage_command(this, "load-conn", load_conn, reg); manage_command(this, "unload-conn", unload_conn, reg); manage_command(this, "get-conns", get_conns, reg); } CALLBACK(destroy_conn, void, peer_cfg_t *cfg, const void *key) { cfg->destroy(cfg); } METHOD(vici_config_t, destroy, void, private_vici_config_t *this) { manage_commands(this, FALSE); this->conns->destroy_function(this->conns, destroy_conn); this->condvar->destroy(this->condvar); this->lock->destroy(this->lock); free(this); } /** * See header */ vici_config_t *vici_config_create(vici_dispatcher_t *dispatcher, vici_authority_t *authority, vici_cred_t *cred) { private_vici_config_t *this; INIT(this, .public = { .backend = { .create_peer_cfg_enumerator = _create_peer_cfg_enumerator, .create_ike_cfg_enumerator = _create_ike_cfg_enumerator, .get_peer_cfg_by_name = _get_peer_cfg_by_name, }, .destroy = _destroy, }, .dispatcher = dispatcher, .conns = hashtable_create(hashtable_hash_str, hashtable_equals_str, 32), .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), .condvar = rwlock_condvar_create(), .authority = authority, .cred = cred, ); manage_commands(this, TRUE); return &this->public; }