| /* |
| * 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 <http://www.fsf.org/copyleft/gpl.txt>. |
| * |
| * 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 <daemon.h> |
| #include <threading/mutex.h> |
| #include <collections/linked_list.h> |
| #include <processing/jobs/callback_job.h> |
| |
| 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; |
| } |