/* * Copyright (C) 2012-2014 Tobias Brunner * Copyright (C) 2008 Martin Willi * 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. */ #include "stroke_config.h" #include #include #include #include typedef struct private_stroke_config_t private_stroke_config_t; /** * private data of stroke_config */ struct private_stroke_config_t { /** * public functions */ stroke_config_t public; /** * list of peer_cfg_t */ linked_list_t *list; /** * mutex to lock config list */ mutex_t *mutex; /** * ca sections */ stroke_ca_t *ca; /** * credentials */ stroke_cred_t *cred; /** * Virtual IP pool / DNS backend */ stroke_attribute_t *attributes; }; METHOD(backend_t, create_peer_cfg_enumerator, enumerator_t*, private_stroke_config_t *this, identification_t *me, identification_t *other) { this->mutex->lock(this->mutex); return enumerator_create_cleaner(this->list->create_enumerator(this->list), (void*)this->mutex->unlock, this->mutex); } 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, &cfg)) { *out = cfg->get_ike_cfg(cfg); return TRUE; } return FALSE; } METHOD(backend_t, create_ike_cfg_enumerator, enumerator_t*, private_stroke_config_t *this, host_t *me, host_t *other) { this->mutex->lock(this->mutex); return enumerator_create_filter(this->list->create_enumerator(this->list), ike_filter, this->mutex, (void*)this->mutex->unlock); } METHOD(backend_t, get_peer_cfg_by_name, peer_cfg_t*, private_stroke_config_t *this, char *name) { enumerator_t *e1, *e2; peer_cfg_t *current, *found = NULL; child_cfg_t *child; this->mutex->lock(this->mutex); e1 = this->list->create_enumerator(this->list); while (e1->enumerate(e1, ¤t)) { /* compare peer_cfgs name first */ if (streq(current->get_name(current), name)) { found = current; found->get_ref(found); break; } /* compare all child_cfg names otherwise */ e2 = current->create_child_cfg_enumerator(current); while (e2->enumerate(e2, &child)) { if (streq(child->get_name(child), name)) { found = current; found->get_ref(found); break; } } e2->destroy(e2); if (found) { break; } } e1->destroy(e1); this->mutex->unlock(this->mutex); return found; } /** * parse a proposal string, either into ike_cfg or child_cfg */ static bool add_proposals(private_stroke_config_t *this, char *string, ike_cfg_t *ike_cfg, child_cfg_t *child_cfg, protocol_id_t proto) { if (string) { char *single; char *strict; proposal_t *proposal; strict = string + strlen(string) - 1; if (*strict == '!') { *strict = '\0'; } else { strict = NULL; } while ((single = strsep(&string, ","))) { proposal = proposal_create_from_string(proto, single); if (proposal) { if (ike_cfg) { ike_cfg->add_proposal(ike_cfg, proposal); } else { child_cfg->add_proposal(child_cfg, proposal); } continue; } DBG1(DBG_CFG, "skipped invalid proposal string: %s", single); return FALSE; } if (strict) { return TRUE; } /* add default porposal to the end if not strict */ } if (ike_cfg) { ike_cfg->add_proposal(ike_cfg, proposal_create_default(proto)); ike_cfg->add_proposal(ike_cfg, proposal_create_default_aead(proto)); } else { child_cfg->add_proposal(child_cfg, proposal_create_default(proto)); child_cfg->add_proposal(child_cfg, proposal_create_default_aead(proto)); } return TRUE; } /** * Check if any addresses in the given string are local */ static bool is_local(char *address, bool any_allowed) { enumerator_t *enumerator; host_t *host; char *token; bool found = FALSE; enumerator = enumerator_create_token(address, ",", " "); while (enumerator->enumerate(enumerator, &token)) { if (!strchr(token, '/')) { host = host_create_from_dns(token, 0, 0); if (host) { if (charon->kernel->get_interface(charon->kernel, host, NULL)) { found = TRUE; } else if (any_allowed && host->is_anyaddr(host)) { found = TRUE; } host->destroy(host); if (found) { break; } } } } enumerator->destroy(enumerator); return found; } /** * Swap ends if indicated by left|right */ static void swap_ends(stroke_msg_t *msg) { if (!lib->settings->get_bool(lib->settings, "%s.plugins.stroke.allow_swap", TRUE, lib->ns)) { return; } if (is_local(msg->add_conn.other.address, FALSE)) { stroke_end_t tmp_end; DBG2(DBG_CFG, "left is other host, swapping ends"); tmp_end = msg->add_conn.me; msg->add_conn.me = msg->add_conn.other; msg->add_conn.other = tmp_end; } else if (!is_local(msg->add_conn.me.address, TRUE)) { DBG1(DBG_CFG, "left nor right host is our side, assuming left=local"); } } /** * Build an IKE config from a stroke message */ static ike_cfg_t *build_ike_cfg(private_stroke_config_t *this, stroke_msg_t *msg) { ike_cfg_create_t ike; ike_cfg_t *ike_cfg; char me[256], other[256]; swap_ends(msg); ike = (ike_cfg_create_t){ .version = msg->add_conn.version, .local = msg->add_conn.me.address, .local_port = msg->add_conn.me.ikeport, .remote = msg->add_conn.other.address, .remote_port = msg->add_conn.other.ikeport, .no_certreq = msg->add_conn.other.sendcert == CERT_NEVER_SEND, .force_encap = msg->add_conn.force_encap, .fragmentation = msg->add_conn.fragmentation, .dscp = msg->add_conn.ikedscp, }; if (msg->add_conn.me.allow_any) { snprintf(me, sizeof(me), "%s,0.0.0.0/0,::/0", msg->add_conn.me.address); ike.local = me; } if (msg->add_conn.other.allow_any) { snprintf(other, sizeof(other), "%s,0.0.0.0/0,::/0", msg->add_conn.other.address); ike.remote = other; } if (ike.local_port == IKEV2_UDP_PORT) { ike.local_port = charon->socket->get_port(charon->socket, FALSE); } ike_cfg = ike_cfg_create(&ike); if (!add_proposals(this, msg->add_conn.algorithms.ike, ike_cfg, NULL, PROTO_IKE)) { ike_cfg->destroy(ike_cfg); return NULL; } return ike_cfg; } /** * Add CRL constraint to config */ static void build_crl_policy(auth_cfg_t *cfg, bool local, int policy) { /* CRL/OCSP policy, for remote config only */ if (!local) { switch (policy) { case CRL_STRICT_YES: /* if yes, we require a GOOD validation */ cfg->add(cfg, AUTH_RULE_CRL_VALIDATION, VALIDATION_GOOD); break; case CRL_STRICT_IFURI: /* for ifuri, a SKIPPED validation is sufficient */ cfg->add(cfg, AUTH_RULE_CRL_VALIDATION, VALIDATION_SKIPPED); break; default: break; } } } /** * build authentication config */ static auth_cfg_t *build_auth_cfg(private_stroke_config_t *this, stroke_msg_t *msg, bool local, bool primary) { identification_t *identity; certificate_t *certificate; char *auth, *id, *pubkey, *cert, *ca, *groups; stroke_end_t *end, *other_end; auth_cfg_t *cfg; bool loose = FALSE; /* select strings */ if (local) { end = &msg->add_conn.me; other_end = &msg->add_conn.other; } else { end = &msg->add_conn.other; other_end = &msg->add_conn.me; } if (primary) { auth = end->auth; id = end->id; if (!id) { /* leftid/rightid fallback to address */ id = end->address; } cert = end->cert; ca = end->ca; if (ca && streq(ca, "%same")) { ca = other_end->ca; } } else { auth = end->auth2; id = end->id2; if (local && !id) { /* leftid2 falls back to leftid */ id = end->id; } cert = end->cert2; ca = end->ca2; if (ca && streq(ca, "%same")) { ca = other_end->ca2; } } if (id && *id == '%' && !streq(id, "%any") && !streq(id, "%any6")) { /* has only an effect on rightid/2 */ loose = !local; id++; } if (!auth) { if (primary) { auth = "pubkey"; } else { /* no second authentication round, fine. But load certificates * for other purposes (EAP-TLS) */ if (cert) { certificate = this->cred->load_peer(this->cred, cert); if (certificate) { certificate->destroy(certificate); } } return NULL; } } cfg = auth_cfg_create(); /* add identity and peer certificate */ identity = identification_create_from_string(id); if (cert) { enumerator_t *enumerator; bool has_subject = FALSE; certificate_t *first = NULL; enumerator = enumerator_create_token(cert, ",", " "); while (enumerator->enumerate(enumerator, &cert)) { certificate = this->cred->load_peer(this->cred, cert); if (certificate) { cfg->add(cfg, AUTH_RULE_SUBJECT_CERT, certificate); if (!first) { first = certificate; } if (identity->get_type(identity) != ID_ANY && certificate->has_subject(certificate, identity)) { has_subject = TRUE; } } } enumerator->destroy(enumerator); if (first && !has_subject) { DBG1(DBG_CFG, " id '%Y' not confirmed by certificate, " "defaulting to '%Y'", identity, first->get_subject(first)); identity->destroy(identity); identity = first->get_subject(first); identity = identity->clone(identity); } } /* add raw RSA public key */ pubkey = end->rsakey; if (pubkey && !streq(pubkey, "") && !streq(pubkey, "%cert")) { certificate = this->cred->load_pubkey(this->cred, pubkey, identity); if (certificate) { cfg->add(cfg, AUTH_RULE_SUBJECT_CERT, certificate); } } if (identity->get_type(identity) != ID_ANY) { cfg->add(cfg, AUTH_RULE_IDENTITY, identity); if (loose) { cfg->add(cfg, AUTH_RULE_IDENTITY_LOOSE, TRUE); } } else { identity->destroy(identity); } /* CA constraint */ if (ca) { identity = identification_create_from_string(ca); certificate = lib->credmgr->get_cert(lib->credmgr, CERT_X509, KEY_ANY, identity, TRUE); identity->destroy(identity); if (certificate) { cfg->add(cfg, AUTH_RULE_CA_CERT, certificate); } else { DBG1(DBG_CFG, "CA certificate \"%s\" not found, discarding CA " "constraint", ca); } } /* groups */ groups = primary ? end->groups : end->groups2; if (groups) { enumerator_t *enumerator; char *group; enumerator = enumerator_create_token(groups, ",", " "); while (enumerator->enumerate(enumerator, &group)) { cfg->add(cfg, AUTH_RULE_GROUP, identification_create_from_string(group)); } enumerator->destroy(enumerator); } /* certificatePolicies */ if (end->cert_policy) { enumerator_t *enumerator; char *policy; enumerator = enumerator_create_token(end->cert_policy, ",", " "); while (enumerator->enumerate(enumerator, &policy)) { cfg->add(cfg, AUTH_RULE_CERT_POLICY, strdup(policy)); } enumerator->destroy(enumerator); } /* authentication method (class, actually) */ if (strpfx(auth, "ike:") || strpfx(auth, "pubkey") || strpfx(auth, "rsa") || strpfx(auth, "ecdsa") || strpfx(auth, "bliss")) { cfg->add(cfg, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY); build_crl_policy(cfg, local, msg->add_conn.crl_policy); cfg->add_pubkey_constraints(cfg, auth, TRUE); } else if (streq(auth, "psk") || streq(auth, "secret")) { cfg->add(cfg, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PSK); } else if (strpfx(auth, "xauth")) { char *pos; pos = strchr(auth, '-'); if (pos) { cfg->add(cfg, AUTH_RULE_XAUTH_BACKEND, strdup(++pos)); } cfg->add(cfg, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_XAUTH); if (msg->add_conn.xauth_identity) { cfg->add(cfg, AUTH_RULE_XAUTH_IDENTITY, identification_create_from_string(msg->add_conn.xauth_identity)); } } else if (strpfx(auth, "eap")) { eap_vendor_type_t *type; char *pos; cfg->add(cfg, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP); /* check for public key constraints for EAP-TLS etc. */ pos = strchr(auth, ':'); if (pos) { *pos = 0; cfg->add_pubkey_constraints(cfg, pos + 1, FALSE); } type = eap_vendor_type_from_string(auth); 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); } if (msg->add_conn.eap_identity) { if (streq(msg->add_conn.eap_identity, "%identity")) { identity = identification_create_from_encoding(ID_ANY, chunk_empty); } else { identity = identification_create_from_string( msg->add_conn.eap_identity); } cfg->add(cfg, AUTH_RULE_EAP_IDENTITY, identity); } if (msg->add_conn.aaa_identity) { cfg->add(cfg, AUTH_RULE_AAA_IDENTITY, identification_create_from_string(msg->add_conn.aaa_identity)); } } else { if (!streq(auth, "any")) { DBG1(DBG_CFG, "authentication method %s unknown, fallback to any", auth); } build_crl_policy(cfg, local, msg->add_conn.crl_policy); } return cfg; } /** * build a mem_pool_t from an address range */ static mem_pool_t *create_pool_range(char *str) { mem_pool_t *pool; host_t *from, *to; if (!host_create_from_range(str, &from, &to)) { return NULL; } pool = mem_pool_create_range(str, from, to); from->destroy(from); to->destroy(to); return pool; } /** * build a peer_cfg from a stroke msg */ static peer_cfg_t *build_peer_cfg(private_stroke_config_t *this, stroke_msg_t *msg, ike_cfg_t *ike_cfg) { peer_cfg_t *peer_cfg; auth_cfg_t *auth_cfg; peer_cfg_create_t peer = { .cert_policy = msg->add_conn.me.sendcert, .keyingtries = msg->add_conn.rekey.tries, .no_mobike = !msg->add_conn.mobike, .aggressive = msg->add_conn.aggressive, .push_mode = msg->add_conn.pushmode, .dpd = msg->add_conn.dpd.delay, .dpd_timeout = msg->add_conn.dpd.timeout, }; #ifdef ME if (msg->add_conn.ikeme.mediation && msg->add_conn.ikeme.mediated_by) { DBG1(DBG_CFG, "a mediation connection cannot be a mediated connection " "at the same time, aborting"); return NULL; } if (msg->add_conn.ikeme.mediation) { peer.mediation = TRUE; /* force unique connections for mediation connections */ msg->add_conn.unique = 1; } else if (msg->add_conn.ikeme.mediated_by) { peer.mediated_by = msg->add_conn.ikeme.mediated_by; if (msg->add_conn.ikeme.peerid) { peer.peer_id = identification_create_from_string( msg->add_conn.ikeme.peerid); } else if (msg->add_conn.other.id) { peer.peer_id = identification_create_from_string( msg->add_conn.other.id); } } #endif /* ME */ peer.jitter_time = msg->add_conn.rekey.margin * msg->add_conn.rekey.fuzz / 100; peer.over_time = msg->add_conn.rekey.margin; if (msg->add_conn.rekey.reauth) { peer.reauth_time = msg->add_conn.rekey.ike_lifetime - peer.over_time; } else { peer.rekey_time = msg->add_conn.rekey.ike_lifetime - peer.over_time; } switch (msg->add_conn.unique) { case 1: /* yes */ case 2: /* replace */ peer.unique = UNIQUE_REPLACE; break; case 3: /* keep */ peer.unique = UNIQUE_KEEP; break; case 4: /* never */ peer.unique = UNIQUE_NEVER; break; default: /* no */ peer.unique = UNIQUE_NO; break; } if (msg->add_conn.dpd.action == 0) { /* dpdaction=none disables DPD */ peer.dpd = 0; } /* other.sourceip is managed in stroke_attributes. If it is set, we define * the pool name as the connection name, which the attribute provider * uses to serve pool addresses. */ peer_cfg = peer_cfg_create(msg->add_conn.name, ike_cfg, &peer); if (msg->add_conn.other.sourceip) { enumerator_t *enumerator; char *token; enumerator = enumerator_create_token(msg->add_conn.other.sourceip, ",", " "); while (enumerator->enumerate(enumerator, &token)) { if (streq(token, "%modeconfig") || streq(token, "%modecfg") || streq(token, "%config") || streq(token, "%cfg") || streq(token, "%config4") || streq(token, "%config6")) { /* empty pool, uses connection name */ this->attributes->add_pool(this->attributes, mem_pool_create(msg->add_conn.name, NULL, 0)); peer_cfg->add_pool(peer_cfg, msg->add_conn.name); } else if (*token == '%') { /* external named pool */ peer_cfg->add_pool(peer_cfg, token + 1); } else { /* in-memory pool, using range or CIDR notation */ mem_pool_t *pool; host_t *base; int bits; pool = create_pool_range(token); if (!pool) { base = host_create_from_subnet(token, &bits); if (base) { pool = mem_pool_create(token, base, bits); base->destroy(base); } } if (pool) { this->attributes->add_pool(this->attributes, pool); peer_cfg->add_pool(peer_cfg, token); } else { DBG1(DBG_CFG, "IP pool %s invalid, ignored", token); } } } enumerator->destroy(enumerator); } if (msg->add_conn.me.sourceip && msg->add_conn.other.sourceip) { DBG1(DBG_CFG, "'%s' has both left- and rightsourceip, but IKE can " "negotiate one virtual IP only, ignoring local virtual IP", msg->add_conn.name); } else if (msg->add_conn.me.sourceip) { enumerator_t *enumerator; char *token; enumerator = enumerator_create_token(msg->add_conn.me.sourceip, ",", " "); while (enumerator->enumerate(enumerator, &token)) { host_t *vip = NULL; if (streq(token, "%modeconfig") || streq(token, "%modecfg") || streq(token, "%config") || streq(token, "%cfg")) { /* try to deduce an address family */ if (msg->add_conn.me.subnets) { /* use the same family as in local subnet, if any */ if (strchr(msg->add_conn.me.subnets, '.')) { vip = host_create_any(AF_INET); } else { vip = host_create_any(AF_INET6); } } else if (msg->add_conn.other.subnets) { /* use the same family as in remote subnet, if any */ if (strchr(msg->add_conn.other.subnets, '.')) { vip = host_create_any(AF_INET); } else { vip = host_create_any(AF_INET6); } } else { char *addr, *next, *hit; /* guess virtual IP family based on local address. If * multiple addresses are specified, we look at the first * only, as with leftallowany a ::/0 is always appended. */ addr = ike_cfg->get_my_addr(ike_cfg); next = strchr(addr, ','); hit = strchr(addr, ':'); if (hit && (!next || hit < next)) { vip = host_create_any(AF_INET6); } else { vip = host_create_any(AF_INET); } } } else if (streq(token, "%config4")) { vip = host_create_any(AF_INET); } else if (streq(token, "%config6")) { vip = host_create_any(AF_INET6); } else { vip = host_create_from_string(token, 0); if (!vip) { DBG1(DBG_CFG, "ignored invalid subnet token: %s", token); } } if (vip) { peer_cfg->add_virtual_ip(peer_cfg, vip); } } enumerator->destroy(enumerator); } /* build leftauth= */ auth_cfg = build_auth_cfg(this, msg, TRUE, TRUE); if (auth_cfg) { peer_cfg->add_auth_cfg(peer_cfg, auth_cfg, TRUE); } else { /* we require at least one config on our side */ peer_cfg->destroy(peer_cfg); return NULL; } /* build leftauth2= */ auth_cfg = build_auth_cfg(this, msg, TRUE, FALSE); if (auth_cfg) { peer_cfg->add_auth_cfg(peer_cfg, auth_cfg, TRUE); } /* build rightauth= */ auth_cfg = build_auth_cfg(this, msg, FALSE, TRUE); if (auth_cfg) { peer_cfg->add_auth_cfg(peer_cfg, auth_cfg, FALSE); } /* build rightauth2= */ auth_cfg = build_auth_cfg(this, msg, FALSE, FALSE); if (auth_cfg) { peer_cfg->add_auth_cfg(peer_cfg, auth_cfg, FALSE); } return peer_cfg; } /** * Parse a protoport specifier */ static bool parse_protoport(char *token, uint16_t *from_port, uint16_t *to_port, uint8_t *protocol) { char *sep, *port = "", *endptr; struct protoent *proto; struct servent *svc; long int p; sep = strrchr(token, ']'); if (!sep) { return FALSE; } *sep = '\0'; sep = strchr(token, '/'); if (sep) { /* protocol/port */ *sep = '\0'; port = sep + 1; } if (streq(token, "%any")) { *protocol = 0; } else { proto = getprotobyname(token); if (proto) { *protocol = proto->p_proto; } else { p = strtol(token, &endptr, 0); if ((*token && *endptr) || p < 0 || p > 0xff) { return FALSE; } *protocol = (uint8_t)p; } } if (streq(port, "%any")) { *from_port = 0; *to_port = 0xffff; } else if (streq(port, "%opaque")) { *from_port = 0xffff; *to_port = 0; } else if (*port) { svc = getservbyname(port, NULL); if (svc) { *from_port = *to_port = ntohs(svc->s_port); } else { p = strtol(port, &endptr, 0); if (p < 0 || p > 0xffff) { return FALSE; } *from_port = p; if (*endptr == '-') { port = endptr + 1; p = strtol(port, &endptr, 0); if (p < 0 || p > 0xffff) { return FALSE; } } *to_port = p; if (*endptr) { return FALSE; } } } return TRUE; } /** * build a traffic selector from a stroke_end */ static void add_ts(private_stroke_config_t *this, stroke_end_t *end, child_cfg_t *child_cfg, bool local) { traffic_selector_t *ts; bool ts_added = FALSE; if (end->subnets) { enumerator_t *enumerator; char *subnet, *pos; uint16_t from_port, to_port; uint8_t proto; enumerator = enumerator_create_token(end->subnets, ",", " "); while (enumerator->enumerate(enumerator, &subnet)) { from_port = end->from_port; to_port = end->to_port; proto = end->protocol; pos = strchr(subnet, '['); if (pos) { *(pos++) = '\0'; if (!parse_protoport(pos, &from_port, &to_port, &proto)) { DBG1(DBG_CFG, "invalid proto/port: %s, skipped subnet", pos); continue; } } if (streq(subnet, "%dynamic")) { ts = traffic_selector_create_dynamic(proto, from_port, to_port); } else { ts = traffic_selector_create_from_cidr(subnet, proto, from_port, to_port); } if (ts) { child_cfg->add_traffic_selector(child_cfg, local, ts); ts_added = TRUE; } else { DBG1(DBG_CFG, "invalid subnet: %s, skipped", subnet); } } enumerator->destroy(enumerator); } if (!ts_added) { ts = traffic_selector_create_dynamic(end->protocol, end->from_port, end->to_port); child_cfg->add_traffic_selector(child_cfg, local, ts); } } /** * map starter magic values to our action type */ static action_t map_action(int starter_action) { switch (starter_action) { case 2: /* =hold */ return ACTION_ROUTE; case 3: /* =restart */ return ACTION_RESTART; default: return ACTION_NONE; } } /** * build a child config from the stroke message */ static child_cfg_t *build_child_cfg(private_stroke_config_t *this, stroke_msg_t *msg) { child_cfg_t *child_cfg; bool success; child_cfg_create_t child = { .lifetime = { .time = { .life = msg->add_conn.rekey.ipsec_lifetime, .rekey = msg->add_conn.rekey.ipsec_lifetime - msg->add_conn.rekey.margin, .jitter = msg->add_conn.rekey.margin * msg->add_conn.rekey.fuzz / 100 }, .bytes = { .life = msg->add_conn.rekey.life_bytes, .rekey = msg->add_conn.rekey.life_bytes - msg->add_conn.rekey.margin_bytes, .jitter = msg->add_conn.rekey.margin_bytes * msg->add_conn.rekey.fuzz / 100 }, .packets = { .life = msg->add_conn.rekey.life_packets, .rekey = msg->add_conn.rekey.life_packets - msg->add_conn.rekey.margin_packets, .jitter = msg->add_conn.rekey.margin_packets * msg->add_conn.rekey.fuzz / 100 }, }, .mark_in = { .value = msg->add_conn.mark_in.value, .mask = msg->add_conn.mark_in.mask }, .mark_out = { .value = msg->add_conn.mark_out.value, .mask = msg->add_conn.mark_out.mask }, .reqid = msg->add_conn.reqid, .mode = msg->add_conn.mode, .options = (msg->add_conn.proxy_mode ? OPT_PROXY_MODE : 0) | (msg->add_conn.ipcomp ? OPT_IPCOMP : 0) | (msg->add_conn.me.hostaccess ? OPT_HOSTACCESS : 0) | (msg->add_conn.install_policy ? 0 : OPT_NO_POLICIES) | (msg->add_conn.sha256_96 ? OPT_SHA256_96 : 0), .tfc = msg->add_conn.tfc, .inactivity = msg->add_conn.inactivity, .dpd_action = map_action(msg->add_conn.dpd.action), .close_action = map_action(msg->add_conn.close_action), .updown = msg->add_conn.me.updown, }; child_cfg = child_cfg_create(msg->add_conn.name, &child); if (msg->add_conn.replay_window != -1) { child_cfg->set_replay_window(child_cfg, msg->add_conn.replay_window); } add_ts(this, &msg->add_conn.me, child_cfg, TRUE); add_ts(this, &msg->add_conn.other, child_cfg, FALSE); if (msg->add_conn.algorithms.ah) { success = add_proposals(this, msg->add_conn.algorithms.ah, NULL, child_cfg, PROTO_AH); } else { success = add_proposals(this, msg->add_conn.algorithms.esp, NULL, child_cfg, PROTO_ESP); } if (!success) { child_cfg->destroy(child_cfg); return NULL; } return child_cfg; } METHOD(stroke_config_t, add, void, private_stroke_config_t *this, stroke_msg_t *msg) { ike_cfg_t *ike_cfg, *existing_ike; peer_cfg_t *peer_cfg, *existing; child_cfg_t *child_cfg; enumerator_t *enumerator; bool use_existing = FALSE; ike_cfg = build_ike_cfg(this, msg); if (!ike_cfg) { return; } peer_cfg = build_peer_cfg(this, msg, ike_cfg); if (!peer_cfg) { ike_cfg->destroy(ike_cfg); return; } enumerator = create_peer_cfg_enumerator(this, NULL, NULL); while (enumerator->enumerate(enumerator, &existing)) { existing_ike = existing->get_ike_cfg(existing); if (existing->equals(existing, peer_cfg) && existing_ike->equals(existing_ike, peer_cfg->get_ike_cfg(peer_cfg))) { use_existing = TRUE; peer_cfg->destroy(peer_cfg); peer_cfg = existing; peer_cfg->get_ref(peer_cfg); DBG1(DBG_CFG, "added child to existing configuration '%s'", peer_cfg->get_name(peer_cfg)); break; } } enumerator->destroy(enumerator); child_cfg = build_child_cfg(this, msg); if (!child_cfg) { peer_cfg->destroy(peer_cfg); return; } peer_cfg->add_child_cfg(peer_cfg, child_cfg); if (use_existing) { peer_cfg->destroy(peer_cfg); } else { /* add config to backend */ DBG1(DBG_CFG, "added configuration '%s'", msg->add_conn.name); this->mutex->lock(this->mutex); this->list->insert_last(this->list, peer_cfg); this->mutex->unlock(this->mutex); } } METHOD(stroke_config_t, del, void, private_stroke_config_t *this, stroke_msg_t *msg) { enumerator_t *enumerator, *children; peer_cfg_t *peer; child_cfg_t *child; bool deleted = FALSE; this->mutex->lock(this->mutex); enumerator = this->list->create_enumerator(this->list); while (enumerator->enumerate(enumerator, &peer)) { bool keep = FALSE; /* remove any child with such a name */ children = peer->create_child_cfg_enumerator(peer); while (children->enumerate(children, &child)) { if (streq(child->get_name(child), msg->del_conn.name)) { peer->remove_child_cfg(peer, children); child->destroy(child); deleted = TRUE; } else { keep = TRUE; } } children->destroy(children); /* if peer config has no children anymore, remove it */ if (!keep) { this->list->remove_at(this->list, enumerator); peer->destroy(peer); } } enumerator->destroy(enumerator); this->mutex->unlock(this->mutex); if (deleted) { DBG1(DBG_CFG, "deleted connection '%s'", msg->del_conn.name); } else { DBG1(DBG_CFG, "connection '%s' not found", msg->del_conn.name); } } METHOD(stroke_config_t, set_user_credentials, void, private_stroke_config_t *this, stroke_msg_t *msg, FILE *prompt) { enumerator_t *enumerator, *children, *remote_auth; peer_cfg_t *peer, *found = NULL; auth_cfg_t *auth_cfg, *remote_cfg; auth_class_t auth_class; child_cfg_t *child; identification_t *id, *identity, *gw = NULL; shared_key_type_t type = SHARED_ANY; chunk_t password = chunk_empty; this->mutex->lock(this->mutex); enumerator = this->list->create_enumerator(this->list); while (enumerator->enumerate(enumerator, (void**)&peer)) { /* find the peer (or child) config with the given name */ if (streq(peer->get_name(peer), msg->user_creds.name)) { found = peer; } else { children = peer->create_child_cfg_enumerator(peer); while (children->enumerate(children, &child)) { if (streq(child->get_name(child), msg->user_creds.name)) { found = peer; break; } } children->destroy(children); } if (found) { break; } } enumerator->destroy(enumerator); if (!found) { DBG1(DBG_CFG, " no config named '%s'", msg->user_creds.name); fprintf(prompt, "no config named '%s'\n", msg->user_creds.name); this->mutex->unlock(this->mutex); return; } id = identification_create_from_string(msg->user_creds.username); if (strlen(msg->user_creds.username) == 0 || !id || id->get_type(id) == ID_ANY) { DBG1(DBG_CFG, " invalid username '%s'", msg->user_creds.username); fprintf(prompt, "invalid username '%s'\n", msg->user_creds.username); this->mutex->unlock(this->mutex); DESTROY_IF(id); return; } /* replace/set the username in the first EAP/XAuth auth_cfg, also look for * a suitable remote ID. * note that adding the identity here is not fully thread-safe as the * peer_cfg and in turn the auth_cfg could be in use. for the default use * case (setting user credentials before upping the connection) this will * not be a problem, though. */ enumerator = found->create_auth_cfg_enumerator(found, TRUE); remote_auth = found->create_auth_cfg_enumerator(found, FALSE); while (enumerator->enumerate(enumerator, (void**)&auth_cfg)) { if (remote_auth->enumerate(remote_auth, (void**)&remote_cfg)) { /* fall back on rightid, in case aaa_identity is not specified */ identity = remote_cfg->get(remote_cfg, AUTH_RULE_IDENTITY); if (identity && identity->get_type(identity) != ID_ANY) { gw = identity; } } auth_class = (uintptr_t)auth_cfg->get(auth_cfg, AUTH_RULE_AUTH_CLASS); if (auth_class == AUTH_CLASS_EAP || auth_class == AUTH_CLASS_XAUTH) { if (auth_class == AUTH_CLASS_EAP) { auth_cfg->add(auth_cfg, AUTH_RULE_EAP_IDENTITY, id->clone(id)); /* if aaa_identity is specified use that as remote ID */ identity = auth_cfg->get(auth_cfg, AUTH_RULE_AAA_IDENTITY); if (identity && identity->get_type(identity) != ID_ANY) { gw = identity; } DBG1(DBG_CFG, " configured EAP-Identity %Y", id); } else { auth_cfg->add(auth_cfg, AUTH_RULE_XAUTH_IDENTITY, id->clone(id)); DBG1(DBG_CFG, " configured XAuth username %Y", id); } type = SHARED_EAP; break; } } enumerator->destroy(enumerator); remote_auth->destroy(remote_auth); /* clone the gw ID before unlocking the mutex */ if (gw) { gw = gw->clone(gw); } this->mutex->unlock(this->mutex); if (type == SHARED_ANY) { DBG1(DBG_CFG, " config '%s' unsuitable for user credentials", msg->user_creds.name); fprintf(prompt, "config '%s' unsuitable for user credentials\n", msg->user_creds.name); id->destroy(id); DESTROY_IF(gw); return; } if (msg->user_creds.password) { char *pass; pass = msg->user_creds.password; password = chunk_clone(chunk_create(pass, strlen(pass))); memwipe(pass, strlen(pass)); } else { /* prompt the user for the password */ char buf[256]; fprintf(prompt, "Password:\n"); if (fgets(buf, sizeof(buf), prompt)) { password = chunk_clone(chunk_create(buf, strlen(buf))); if (password.len > 0) { /* trim trailing \n */ password.len--; } memwipe(buf, sizeof(buf)); } } if (password.len) { shared_key_t *shared; linked_list_t *owners; shared = shared_key_create(type, password); owners = linked_list_create(); owners->insert_last(owners, id->clone(id)); if (gw && gw->get_type(gw) != ID_ANY) { owners->insert_last(owners, gw->clone(gw)); DBG1(DBG_CFG, " added %N secret for %Y %Y", shared_key_type_names, type, id, gw); } else { DBG1(DBG_CFG, " added %N secret for %Y", shared_key_type_names, type, id); } this->cred->add_shared(this->cred, shared, owners); DBG4(DBG_CFG, " secret: %#B", &password); } else { /* in case a user answers the password prompt by just pressing enter */ chunk_clear(&password); } id->destroy(id); DESTROY_IF(gw); } METHOD(stroke_config_t, destroy, void, private_stroke_config_t *this) { this->list->destroy_offset(this->list, offsetof(peer_cfg_t, destroy)); this->mutex->destroy(this->mutex); free(this); } /* * see header file */ stroke_config_t *stroke_config_create(stroke_ca_t *ca, stroke_cred_t *cred, stroke_attribute_t *attributes) { private_stroke_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, }, .add = _add, .del = _del, .set_user_credentials = _set_user_credentials, .destroy = _destroy, }, .list = linked_list_create(), .mutex = mutex_create(MUTEX_TYPE_RECURSIVE), .ca = ca, .cred = cred, .attributes = attributes, ); return &this->public; }