/* * Copyright (C) 2018 Tobias Brunner * Copyright (C) 2007-2009 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 "backend_manager.h" #include #include #include #include typedef struct private_backend_manager_t private_backend_manager_t; /** * Private data of an backend_manager_t object. */ struct private_backend_manager_t { /** * Public part of backend_manager_t object. */ backend_manager_t public; /** * list of registered backends */ linked_list_t *backends; /** * rwlock for backends */ rwlock_t *lock; }; /** * match of an ike_cfg */ typedef enum ike_cfg_match_t { /* doesn't match at all */ MATCH_NONE = 0x00, /* match for a %any host. For both hosts, hence skip 0x02 */ MATCH_ANY = 0x01, /* IKE version matches exactly (config is not for any version) */ MATCH_VERSION = 0x04, /* local identity matches */ MATCH_ME = 0x08, /* remote identity matches */ MATCH_OTHER = 0x10, } ike_cfg_match_t; /** * data to pass nested IKE enumerator */ typedef struct { private_backend_manager_t *this; host_t *me; host_t *other; } ike_data_t; /** * inner enumerator constructor for IKE cfgs */ static enumerator_t *ike_enum_create(backend_t *backend, ike_data_t *data) { return backend->create_ike_cfg_enumerator(backend, data->me, data->other); } /** * get a match of a candidate ike_cfg for two hosts */ static ike_cfg_match_t get_ike_match(ike_cfg_t *cand, host_t *me, host_t *other, ike_version_t version) { ike_cfg_match_t match = MATCH_NONE; int quality; if (cand->get_version(cand) != IKE_ANY && version != cand->get_version(cand)) { return MATCH_NONE; } if (me) { quality = cand->match_me(cand, me); if (!quality) { return MATCH_NONE; } match += quality * MATCH_ME; } else { match += MATCH_ANY; } if (other) { quality = cand->match_other(cand, other); if (!quality) { return MATCH_NONE; } match += quality * MATCH_OTHER; } else { match += MATCH_ANY; } if (match != MATCH_NONE && cand->get_version(cand) != IKE_ANY) { /* if we have a match, improve it if candidate version specified */ match += MATCH_VERSION; } return match; } /** * list element to help sorting */ typedef struct { ike_cfg_match_t match; ike_cfg_t *cfg; } ike_match_entry_t; CALLBACK(ike_enum_filter, bool, linked_list_t *configs, enumerator_t *orig, va_list args) { ike_match_entry_t *entry; ike_cfg_t **out; VA_ARGS_VGET(args, out); if (orig->enumerate(orig, &entry)) { *out = entry->cfg; return TRUE; } return FALSE; } CALLBACK(ike_match_entry_list_destroy, void, linked_list_t *configs) { ike_match_entry_t *entry; while (configs->remove_last(configs, (void**)&entry) == SUCCESS) { entry->cfg->destroy(entry->cfg); free(entry); } configs->destroy(configs); } /** * Insert entry into match-sorted list */ static void insert_sorted_ike(ike_match_entry_t *entry, linked_list_t *list) { enumerator_t *enumerator; ike_match_entry_t *current; enumerator = list->create_enumerator(list); while (enumerator->enumerate(enumerator, ¤t)) { if (entry->match > current->match) { break; } } list->insert_before(list, enumerator, entry); enumerator->destroy(enumerator); } /** * Create a sorted list of all matching IKE configs */ static linked_list_t *get_matching_ike_cfgs(private_backend_manager_t *this, host_t *me, host_t *other, ike_version_t version) { ike_cfg_t *current; char *my_addr, *other_addr; enumerator_t *enumerator; ike_data_t *data; linked_list_t *configs; ike_cfg_match_t match; ike_match_entry_t *entry; INIT(data, .this = this, .me = me, .other = other, ); configs = linked_list_create(); this->lock->read_lock(this->lock); enumerator = enumerator_create_nested( this->backends->create_enumerator(this->backends), (void*)ike_enum_create, data, (void*)free); while (enumerator->enumerate(enumerator, ¤t)) { my_addr = current->get_my_addr(current); other_addr = current->get_other_addr(current); match = get_ike_match(current, me, other, version); DBG3(DBG_CFG, "ike config match: %d (%s...%s %N)", match, my_addr, other_addr, ike_version_names, current->get_version(current)); if (match) { DBG2(DBG_CFG, " candidate: %s...%s, prio %d", my_addr, other_addr, match); INIT(entry, .match = match, .cfg = current->get_ref(current), ); insert_sorted_ike(entry, configs); } } enumerator->destroy(enumerator); this->lock->unlock(this->lock); return configs; } METHOD(backend_manager_t, get_ike_cfg, ike_cfg_t*, private_backend_manager_t *this, host_t *me, host_t *other, ike_version_t version) { linked_list_t *configs; ike_match_entry_t *entry; ike_cfg_t *found = NULL; char *my_addr, *other_addr; DBG2(DBG_CFG, "looking for an %N config for %H...%H", ike_version_names, version, me, other); configs = get_matching_ike_cfgs(this, me, other, version); if (configs->get_first(configs, (void**)&entry) == SUCCESS) { found = entry->cfg->get_ref(entry->cfg); my_addr = found->get_my_addr(found); other_addr = found->get_other_addr(found); DBG2(DBG_CFG, "found matching ike config: %s...%s with prio %d", my_addr, other_addr, entry->match); } ike_match_entry_list_destroy(configs); return found; } METHOD(backend_manager_t, create_ike_cfg_enumerator, enumerator_t*, private_backend_manager_t *this, host_t *me, host_t *other, ike_version_t version) { linked_list_t *configs; DBG2(DBG_CFG, "looking for %N configs for %H...%H", ike_version_names, version, me, other); configs = get_matching_ike_cfgs(this, me, other, version); return enumerator_create_filter(configs->create_enumerator(configs), ike_enum_filter, configs, ike_match_entry_list_destroy); } /** * Get the best ID match in one of the configs auth_cfg */ static id_match_t get_peer_match(identification_t *id, peer_cfg_t *cfg, bool local) { enumerator_t *enumerator; auth_cfg_t *auth; identification_t *candidate; id_match_t match = ID_MATCH_NONE; char *where = local ? "local" : "remote"; chunk_t data; if (!id) { DBG3(DBG_CFG, " %s id match: %d (%N)", where, ID_MATCH_ANY, id_type_names, ID_ANY); return ID_MATCH_ANY; } /* compare first auth config only */ enumerator = cfg->create_auth_cfg_enumerator(cfg, local); if (enumerator->enumerate(enumerator, &auth)) { candidate = auth->get(auth, AUTH_RULE_IDENTITY); if (candidate) { match = id->matches(id, candidate); /* match vice-versa, as the proposed IDr might be ANY */ if (!match) { match = candidate->matches(candidate, id); } } else { match = ID_MATCH_ANY; } } enumerator->destroy(enumerator); data = id->get_encoding(id); DBG3(DBG_CFG, " %s id match: %d (%N: %#B)", where, match, id_type_names, id->get_type(id), &data); return match; } /** * data to pass nested peer enumerator */ typedef struct { rwlock_t *lock; identification_t *me; identification_t *other; } peer_data_t; /** * list element to help sorting */ typedef struct { id_match_t match_peer; ike_cfg_match_t match_ike; peer_cfg_t *cfg; } match_entry_t; /** * inner enumerator constructor for peer cfgs */ static enumerator_t *peer_enum_create(backend_t *backend, peer_data_t *data) { return backend->create_peer_cfg_enumerator(backend, data->me, data->other); } /** * unlock/cleanup peer enumerator */ static void peer_enum_destroy(peer_data_t *data) { data->lock->unlock(data->lock); free(data); } CALLBACK(peer_enum_filter, bool, linked_list_t *configs, enumerator_t *orig, va_list args) { match_entry_t *entry; peer_cfg_t **out; VA_ARGS_VGET(args, out); if (orig->enumerate(orig, &entry)) { *out = entry->cfg; return TRUE; } return FALSE; } CALLBACK(peer_enum_filter_destroy, void, linked_list_t *configs) { match_entry_t *entry; while (configs->remove_last(configs, (void**)&entry) == SUCCESS) { entry->cfg->destroy(entry->cfg); free(entry); } configs->destroy(configs); } /** * Insert entry into match-sorted list */ static void insert_sorted(match_entry_t *entry, linked_list_t *list) { enumerator_t *enumerator; match_entry_t *current; enumerator = list->create_enumerator(list); while (enumerator->enumerate(enumerator, ¤t)) { if ((entry->match_ike > current->match_ike && entry->match_peer >= current->match_peer) || (entry->match_ike >= current->match_ike && entry->match_peer > current->match_peer)) { break; } } list->insert_before(list, enumerator, entry); enumerator->destroy(enumerator); } METHOD(backend_manager_t, create_peer_cfg_enumerator, enumerator_t*, private_backend_manager_t *this, host_t *me, host_t *other, identification_t *my_id, identification_t *other_id, ike_version_t version) { enumerator_t *enumerator; peer_data_t *data; peer_cfg_t *cfg; linked_list_t *configs; INIT(data, .lock = this->lock, .me = my_id, .other = other_id, ); /* create a sorted list with all matches */ this->lock->read_lock(this->lock); enumerator = enumerator_create_nested( this->backends->create_enumerator(this->backends), (void*)peer_enum_create, data, (void*)peer_enum_destroy); if (!me && !other && !my_id && !other_id) { /* shortcut if we are doing a "listall" */ return enumerator; } configs = linked_list_create(); while (enumerator->enumerate(enumerator, &cfg)) { ike_cfg_t *ike_cfg = cfg->get_ike_cfg(cfg); ike_cfg_match_t match_ike; id_match_t match_peer_me, match_peer_other; match_entry_t *entry; char *my_addr, *other_addr; match_ike = get_ike_match(ike_cfg, me, other, version); my_addr = ike_cfg->get_my_addr(ike_cfg); other_addr = ike_cfg->get_other_addr(ike_cfg); DBG3(DBG_CFG, "peer config \"%s\", ike match: %d (%s...%s %N)", cfg->get_name(cfg), match_ike, my_addr, other_addr, ike_version_names, ike_cfg->get_version(ike_cfg)); if (!match_ike) { continue; } match_peer_me = get_peer_match(my_id, cfg, TRUE); if (!match_peer_me) { continue; } match_peer_other = get_peer_match(other_id, cfg, FALSE); if (match_peer_other) { DBG2(DBG_CFG, " candidate \"%s\", match: %d/%d/%d (me/other/ike)", cfg->get_name(cfg), match_peer_me, match_peer_other, match_ike); INIT(entry, .match_peer = match_peer_me + match_peer_other, .match_ike = match_ike, .cfg = cfg->get_ref(cfg), ); insert_sorted(entry, configs); } } enumerator->destroy(enumerator); return enumerator_create_filter(configs->create_enumerator(configs), peer_enum_filter, configs, peer_enum_filter_destroy); } METHOD(backend_manager_t, get_peer_cfg_by_name, peer_cfg_t*, private_backend_manager_t *this, char *name) { backend_t *backend; peer_cfg_t *config = NULL; enumerator_t *enumerator; this->lock->read_lock(this->lock); enumerator = this->backends->create_enumerator(this->backends); while (config == NULL && enumerator->enumerate(enumerator, (void**)&backend)) { config = backend->get_peer_cfg_by_name(backend, name); } enumerator->destroy(enumerator); this->lock->unlock(this->lock); return config; } METHOD(backend_manager_t, remove_backend, void, private_backend_manager_t *this, backend_t *backend) { this->lock->write_lock(this->lock); this->backends->remove(this->backends, backend, NULL); this->lock->unlock(this->lock); } METHOD(backend_manager_t, add_backend, void, private_backend_manager_t *this, backend_t *backend) { this->lock->write_lock(this->lock); this->backends->insert_last(this->backends, backend); this->lock->unlock(this->lock); } METHOD(backend_manager_t, destroy, void, private_backend_manager_t *this) { this->backends->destroy(this->backends); this->lock->destroy(this->lock); free(this); } /* * Described in header */ backend_manager_t *backend_manager_create() { private_backend_manager_t *this; INIT(this, .public = { .get_ike_cfg = _get_ike_cfg, .create_ike_cfg_enumerator = _create_ike_cfg_enumerator, .get_peer_cfg_by_name = _get_peer_cfg_by_name, .create_peer_cfg_enumerator = _create_peer_cfg_enumerator, .add_backend = _add_backend, .remove_backend = _remove_backend, .destroy = _destroy, }, .backends = linked_list_create(), .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), ); return &this->public; }