/* * Copyright (C) 2013 Tobias Brunner * HSR Hochschule fuer Technik Rapperswil * * Copyright (C) 2012 Martin Willi * Copyright (C) 2012 revosec AG * * 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 "unity_handler.h" #include #include #include #include typedef struct private_unity_handler_t private_unity_handler_t; /** * Private data of an unity_handler_t object. */ struct private_unity_handler_t { /** * Public unity_handler_t interface. */ unity_handler_t public; /** * List of subnets to include, as entry_t */ linked_list_t *include; /** * Mutex for concurrent access to lists */ mutex_t *mutex; }; /** * Traffic selector entry for networks to include under a given IKE_SA */ typedef struct { /** associated IKE_SA COOKIEs */ ike_sa_id_t *id; /** traffic selector to include/exclude */ traffic_selector_t *ts; } entry_t; /** * Clean up an entry */ static void entry_destroy(entry_t *this) { this->id->destroy(this->id); this->ts->destroy(this->ts); free(this); } /** * Create a traffic selector from a unity subnet definition */ static traffic_selector_t *create_ts(chunk_t subnet) { chunk_t net, mask; int i; net = chunk_create(subnet.ptr, 4); mask = chunk_clonea(chunk_create(subnet.ptr + 4, 4)); for (i = 0; i < net.len; i++) { mask.ptr[i] = (mask.ptr[i] ^ 0xFF) | net.ptr[i]; } return traffic_selector_create_from_bytes(0, TS_IPV4_ADDR_RANGE, net, 0, mask, 65535); } /** * Parse a unity attribute and extract all subnets as traffic selectors */ static linked_list_t *parse_subnets(chunk_t data) { linked_list_t *list = NULL; traffic_selector_t *ts; while (data.len >= 8) { /* the padding is optional */ ts = create_ts(data); if (ts) { if (!list) { list = linked_list_create(); } list->insert_last(list, ts); } /* skip address, mask and 6 bytes of padding */ data = chunk_skip(data, 14); } return list; } /** * Store a list of subnets to include in tunnels under this IKE_SA */ static bool add_include(private_unity_handler_t *this, chunk_t data) { traffic_selector_t *ts; linked_list_t *list; ike_sa_t *ike_sa; entry_t *entry; ike_sa = charon->bus->get_sa(charon->bus); if (!ike_sa) { return FALSE; } list = parse_subnets(data); if (!list) { return FALSE; } while (list->remove_first(list, (void**)&ts) == SUCCESS) { INIT(entry, .id = ike_sa->get_id(ike_sa), .ts = ts, ); entry->id = entry->id->clone(entry->id); this->mutex->lock(this->mutex); this->include->insert_last(this->include, entry); this->mutex->unlock(this->mutex); } list->destroy(list); return TRUE; } /** * Remove a list of subnets from the inclusion list for this IKE_SA */ static bool remove_include(private_unity_handler_t *this, chunk_t data) { enumerator_t *enumerator; traffic_selector_t *ts; linked_list_t *list; ike_sa_t *ike_sa; entry_t *entry; ike_sa = charon->bus->get_sa(charon->bus); if (!ike_sa) { return FALSE; } list = parse_subnets(data); if (!list) { return FALSE; } this->mutex->lock(this->mutex); while (list->remove_first(list, (void**)&ts) == SUCCESS) { enumerator = this->include->create_enumerator(this->include); while (enumerator->enumerate(enumerator, &entry)) { if (entry->id->equals(entry->id, ike_sa->get_id(ike_sa)) && ts->equals(ts, entry->ts)) { this->include->remove_at(this->include, enumerator); entry_destroy(entry); break; } } enumerator->destroy(enumerator); ts->destroy(ts); } this->mutex->unlock(this->mutex); list->destroy(list); return TRUE; } /** * Create a unique shunt name for a bypass policy */ static void create_shunt_name(ike_sa_t *ike_sa, traffic_selector_t *ts, char *buf, size_t len) { snprintf(buf, len, "Unity (%s[%u]: %R)", ike_sa->get_name(ike_sa), ike_sa->get_unique_id(ike_sa), ts); } /** * Install entry as a shunt policy */ static job_requeue_t add_exclude_async(entry_t *entry) { enumerator_t *enumerator; child_cfg_t *child_cfg; child_cfg_create_t child = { .mode = MODE_PASS, }; ike_sa_t *ike_sa; char name[128]; host_t *host; ike_sa = charon->ike_sa_manager->checkout(charon->ike_sa_manager, entry->id); if (ike_sa) { create_shunt_name(ike_sa, entry->ts, name, sizeof(name)); child_cfg = child_cfg_create(name, &child); child_cfg->add_traffic_selector(child_cfg, FALSE, entry->ts->clone(entry->ts)); host = ike_sa->get_my_host(ike_sa); child_cfg->add_traffic_selector(child_cfg, TRUE, traffic_selector_create_from_subnet(host->clone(host), 32, 0, 0, 65535)); enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, TRUE); while (enumerator->enumerate(enumerator, &host)) { child_cfg->add_traffic_selector(child_cfg, TRUE, traffic_selector_create_from_subnet(host->clone(host), 32, 0, 0, 65535)); } enumerator->destroy(enumerator); charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa); charon->shunts->install(charon->shunts, "unity", child_cfg); child_cfg->destroy(child_cfg); DBG1(DBG_IKE, "installed %N bypass policy for %R", configuration_attribute_type_names, UNITY_LOCAL_LAN, entry->ts); } return JOB_REQUEUE_NONE; } /** * Add a bypass policy for a given subnet */ static bool add_exclude(private_unity_handler_t *this, chunk_t data) { traffic_selector_t *ts; linked_list_t *list; ike_sa_t *ike_sa; entry_t *entry; ike_sa = charon->bus->get_sa(charon->bus); if (!ike_sa) { return FALSE; } list = parse_subnets(data); if (!list) { return FALSE; } while (list->remove_first(list, (void**)&ts) == SUCCESS) { INIT(entry, .id = ike_sa->get_id(ike_sa), .ts = ts, ); entry->id = entry->id->clone(entry->id); /* we can't install the shunt policy yet, as we don't know the virtual IP. * Defer installation using an async callback. */ lib->processor->queue_job(lib->processor, (job_t*) callback_job_create((void*)add_exclude_async, entry, (void*)entry_destroy, NULL)); } list->destroy(list); return TRUE; } /** * Remove a bypass policy for a given subnet */ static bool remove_exclude(private_unity_handler_t *this, chunk_t data) { traffic_selector_t *ts; linked_list_t *list; ike_sa_t *ike_sa; char name[128]; bool success = TRUE; ike_sa = charon->bus->get_sa(charon->bus); if (!ike_sa) { return FALSE; } list = parse_subnets(data); if (!list) { return FALSE; } while (list->remove_first(list, (void**)&ts) == SUCCESS) { create_shunt_name(ike_sa, ts, name, sizeof(name)); DBG1(DBG_IKE, "uninstalling %N bypass policy for %R", configuration_attribute_type_names, UNITY_LOCAL_LAN, ts); ts->destroy(ts); success = charon->shunts->uninstall(charon->shunts, "unity", name) && success; } list->destroy(list); return success; } METHOD(attribute_handler_t, handle, bool, private_unity_handler_t *this, ike_sa_t *ike_sa, configuration_attribute_type_t type, chunk_t data) { switch (type) { case UNITY_SPLIT_INCLUDE: return add_include(this, data); case UNITY_LOCAL_LAN: return add_exclude(this, data); default: return FALSE; } } METHOD(attribute_handler_t, release, void, private_unity_handler_t *this, ike_sa_t *ike_sa, configuration_attribute_type_t type, chunk_t data) { switch (type) { case UNITY_SPLIT_INCLUDE: remove_include(this, data); break; case UNITY_LOCAL_LAN: remove_exclude(this, data); break; default: break; } } /** * Configuration attributes to request */ static configuration_attribute_type_t attributes[] = { UNITY_SPLIT_INCLUDE, UNITY_LOCAL_LAN, }; /** * Attribute enumerator implementation */ typedef struct { /** implements enumerator_t */ enumerator_t public; /** position in attributes[] */ int i; } attribute_enumerator_t; METHOD(enumerator_t, enumerate_attributes, bool, attribute_enumerator_t *this, va_list args) { configuration_attribute_type_t *type; chunk_t *data; VA_ARGS_VGET(args, type, data); if (this->i < countof(attributes)) { *type = attributes[this->i++]; *data = chunk_empty; return TRUE; } return FALSE; } METHOD(attribute_handler_t, create_attribute_enumerator, enumerator_t *, unity_handler_t *this, ike_sa_t *ike_sa, linked_list_t *vips) { attribute_enumerator_t *enumerator; ike_sa = charon->bus->get_sa(charon->bus); if (!ike_sa || ike_sa->get_version(ike_sa) != IKEV1 || !ike_sa->supports_extension(ike_sa, EXT_CISCO_UNITY)) { return enumerator_create_empty(); } INIT(enumerator, .public = { .enumerate = enumerator_enumerate_default, .venumerate = _enumerate_attributes, .destroy = (void*)free, }, ); return &enumerator->public; } typedef struct { /** mutex to unlock */ mutex_t *mutex; /** IKE_SA ID to filter for */ ike_sa_id_t *id; } include_filter_t; CALLBACK(include_filter, bool, include_filter_t *data, enumerator_t *orig, va_list args) { entry_t *entry; traffic_selector_t **ts; VA_ARGS_VGET(args, ts); while (orig->enumerate(orig, &entry)) { if (data->id->equals(data->id, entry->id)) { *ts = entry->ts; return TRUE; } } return FALSE; } CALLBACK(destroy_filter, void, include_filter_t *data) { data->mutex->unlock(data->mutex); free(data); } METHOD(unity_handler_t, create_include_enumerator, enumerator_t*, private_unity_handler_t *this, ike_sa_id_t *id) { include_filter_t *data; INIT(data, .mutex = this->mutex, .id = id, ); data->mutex->lock(data->mutex); return enumerator_create_filter( this->include->create_enumerator(this->include), include_filter, data, destroy_filter); } METHOD(unity_handler_t, destroy, void, private_unity_handler_t *this) { this->include->destroy(this->include); this->mutex->destroy(this->mutex); free(this); } /** * See header */ unity_handler_t *unity_handler_create() { private_unity_handler_t *this; INIT(this, .public = { .handler = { .handle = _handle, .release = _release, .create_attribute_enumerator = _create_attribute_enumerator, }, .create_include_enumerator = _create_include_enumerator, .destroy = _destroy, }, .include = linked_list_create(), .mutex = mutex_create(MUTEX_TYPE_DEFAULT), ); return &this->public; }