Source: strongswan Version: 5.0.4-3 Severity: wishlist Hello Yves-Alexis and Rene, per private email discussion you asked that I submit feature requests through the BTS.
Please enable the ‘unity’ plugin, including patches from upstream bugs 356¹ and 366². This plugin allows for strongSwan to interop with Cisco equipment, particularly the Split-Include, Split-Exclude, and Split-Local-LAN attributes. This plugin is marked as stable according to the PluginList³ wiki and doesn't require any additional build dependencies. Without the plugin, I can still connect to this particular Cisco gear (that's out of my control), but the traffic selector is 0.0.0.0/0 - much too wide. With the plugin the traffic selectors are narrowed to a list of subnets returned by the Split-Include attribute. However without the patches, the plugin can only interpret a single subnet. Patch in bug 356 causes any failure to parse these attributes to revert from selector 0.0.0.0/0 to the selector specified in rightsubnet. The patch in bug 366 supercedes one of the two patches in bug 356 with label ‘0001-unity-Allow-UNITY_LOCAL_LAN-to-be-longer-than-8-byte.patch’. Attached are my modifications to package version 5.0.4-3 in experimental using quilt. ¹ https://wiki.strongswan.org/issues/356 ² https://wiki.strongswan.org/issues/366 ³ https://wiki.strongswan.org/projects/strongswan/wiki/PluginList -- System Information: Debian Release: 7.1 APT prefers stable APT policy: (500, 'stable') Architecture: amd64 (x86_64) Kernel: Linux 3.9-0.bpo.1-amd64 (SMP w/4 CPU cores) Locale: LANG=en_US.UTF-8, LC_CTYPE=en_US.UTF-8 (charmap=UTF-8) (ignored: LC_ALL set to en_US.UTF-8) Shell: /bin/sh linked to /bin/dash -- Gerald Turner Email: gtur...@unzane.com JID: gtur...@unzane.com GPG: 0xFA8CD6D5 21D9 B2E8 7FE7 F19E 5F7D 4D0C 3FA0 810F FA8C D6D5
diff -urN strongswan-5.0.4.orig/debian/changelog strongswan-5.0.4/debian/changelog --- strongswan-5.0.4.orig/debian/changelog 2013-05-18 00:26:23.000000000 -0700 +++ strongswan-5.0.4/debian/changelog 2013-07-29 09:52:34.000000000 -0700 @@ -1,3 +1,61 @@ +strongswan (5.0.4-3.6) stable; urgency=low + + * Refreshed patch from http://wiki.strongswan.org/issues/356 + * Disabled several configuration options that were enabled in 5.0.4-3.2 + that I am unable to test + + -- Gerald Turner <gtur...@unzane.com> Mon, 29 Jul 2013 09:51:23 -0700 + +strongswan (5.0.4-3.5) stable; urgency=low + + * Apply patch from http://wiki.strongswan.org/issues/356 + + -- Gerald Turner <gtur...@unzane.com> Fri, 26 Jul 2013 09:39:11 -0700 + +strongswan (5.0.4-3.4) stable; urgency=low + + * Apply additional patch from http://wiki.strongswan.org/issues/356 + + -- Gerald Turner <gtur...@unzane.com> Thu, 25 Jul 2013 13:10:57 -0700 + +strongswan (5.0.4-3.3) stable; urgency=low + + * Disable --with-user to fix various permission errors (e.g. resolvconf + doesn't work when run as strongswan user) + * Apply patch from http://wiki.strongswan.org/issues/356 to fix bug with + unity plugin: apparently Cisco can return more than one subnet in a + single attribute + + -- Gerald Turner <gtur...@unzane.com> Thu, 25 Jul 2013 12:38:33 -0700 + +strongswan (5.0.4-3.2) stable; urgency=low + + * Install plugins that were enabled (but not installed) in previous + build + * Enable many more configure flags + + -- Gerald Turner <gtur...@unzane.com> Fri, 28 Jun 2013 13:48:36 -0700 + +strongswan (5.0.4-3.1) stable; urgency=low + + * Backport from experimental + * Fix dh_install --list-missing (See #692606) + * Add ./configure options: + --enable-duplicheck + --enable-error-notify + --enable-ipseckey + --enable-lookip + --enable-sqlite + --enable-systime-fix + --enable-unbound + --enable-unity + --enable-whitelist + --with-user=strongswan + --with-group=nogroup + * Add dependencies on libsqlite3-dev, libunbound-dev, libldns-dev + + -- Gerald Turner <gtur...@unzane.com> Wed, 19 Jun 2013 14:08:10 -0700 + strongswan (5.0.4-3) experimental; urgency=low * debian/rules, debian/libstrongswan.install: diff -urN strongswan-5.0.4.orig/debian/control strongswan-5.0.4/debian/control --- strongswan-5.0.4.orig/debian/control 2013-05-17 23:25:36.000000000 -0700 +++ strongswan-5.0.4/debian/control 2013-07-29 09:53:23.000000000 -0700 @@ -12,7 +12,8 @@ hardening-wrapper, libfcgi-dev, clearsilver-dev, libxml2-dev, libsqlite3-dev, network-manager-dev (>= 0.7) [linux-any], libnm-glib-vpn-dev (>= 0.7) [linux-any], libnm-util-dev (>= 0.7) [linux-any], - gperf, libcap-dev [linux-any] + gperf, libcap-dev [linux-any], libmysqlclient-dev, libunbound-dev, + libldns-dev Homepage: http://www.strongswan.org Package: strongswan diff -urN strongswan-5.0.4.orig/debian/libstrongswan.install strongswan-5.0.4/debian/libstrongswan.install --- strongswan-5.0.4.orig/debian/libstrongswan.install 2013-05-17 23:29:13.000000000 -0700 +++ strongswan-5.0.4/debian/libstrongswan.install 2013-07-29 10:12:25.000000000 -0700 @@ -48,4 +48,16 @@ usr/lib/ipsec/plugins/libstrongswan-xauth-eap.so usr/lib/ipsec/plugins/libstrongswan-xauth-generic.so usr/lib/ipsec/plugins/libstrongswan-xauth-pam.so +usr/lib/ipsec/plugins/libstrongswan-af-alg.so +usr/lib/ipsec/plugins/libstrongswan-certexpire.so +usr/lib/ipsec/plugins/libstrongswan-coupling.so +usr/lib/ipsec/plugins/libstrongswan-duplicheck.so +usr/lib/ipsec/plugins/libstrongswan-error-notify.so +usr/lib/ipsec/plugins/libstrongswan-ipseckey.so +usr/lib/ipsec/plugins/libstrongswan-lookip.so +usr/lib/ipsec/plugins/libstrongswan-mysql.so +usr/lib/ipsec/plugins/libstrongswan-sqlite.so +usr/lib/ipsec/plugins/libstrongswan-unbound.so +usr/lib/ipsec/plugins/libstrongswan-unity.so +usr/lib/ipsec/plugins/libstrongswan-whitelist.so etc/strongswan.conf diff -urN strongswan-5.0.4.orig/debian/patches/series strongswan-5.0.4/debian/patches/series --- strongswan-5.0.4.orig/debian/patches/series 2013-05-17 23:25:36.000000000 -0700 +++ strongswan-5.0.4/debian/patches/series 2013-07-29 09:57:29.000000000 -0700 @@ -1 +1,3 @@ 01_fix-manpages.patch +unity-replicate-default-behavior-if-no-UNITY_SPLIT_INCLUDE.patch +unity-handle-multi-valued-UNITY_SPLIT_INCLUDE.patch diff -urN strongswan-5.0.4.orig/debian/patches/unity-handle-multi-valued-UNITY_SPLIT_INCLUDE.patch strongswan-5.0.4/debian/patches/unity-handle-multi-valued-UNITY_SPLIT_INCLUDE.patch --- strongswan-5.0.4.orig/debian/patches/unity-handle-multi-valued-UNITY_SPLIT_INCLUDE.patch 1969-12-31 16:00:00.000000000 -0800 +++ strongswan-5.0.4/debian/patches/unity-handle-multi-valued-UNITY_SPLIT_INCLUDE.patch 2013-07-29 09:58:03.000000000 -0700 @@ -0,0 +1,265 @@ +Description: Make unity plugin capable of parsing multiple subnets in a single attribute +Origin: http://wiki.strongswan.org/issues/366 +Author: Tobias Brunner <tob...@strongswan.org> +Reviewed-By: Gerald Turner <gtur...@unzane.com> + +--- +The information above should follow the Patch Tagging Guidelines, please +checkout http://dep.debian.net/deps/dep3/ to learn about the format. Here +are templates for supplementary fields that you might want to add: + +Origin: <vendor|upstream|other>, <url of original patch> +Bug: <url in upstream bugtracker> +Bug-Debian: http://bugs.debian.org/<bugnumber> +Bug-Ubuntu: https://launchpad.net/bugs/<bugnumber> +Forwarded: <no|not-needed|url proving that it has been forwarded> +Reviewed-By: <name and email of someone who approved the patch> +Last-Update: <YYYY-MM-DD> + +--- strongswan-5.0.4.orig/src/libcharon/plugins/unity/unity_handler.c ++++ strongswan-5.0.4/src/libcharon/plugins/unity/unity_handler.c +@@ -1,4 +1,7 @@ + /* ++ * Copyright (C) 2013 Tobias Brunner ++ * Hochschule fuer Technik Rapperswil ++ * + * Copyright (C) 2012 Martin Willi + * Copyright (C) 2012 revosec AG + * +@@ -70,12 +73,8 @@ static traffic_selector_t *create_ts(chu + chunk_t net, mask; + int i; + +- if (subnet.len != 8) +- { +- return NULL; +- } + net = chunk_create(subnet.ptr, 4); +- mask = chunk_clonea(chunk_skip(subnet, 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]; +@@ -85,11 +84,37 @@ static traffic_selector_t *create_ts(chu + } + + /** +- * Store a subnet to include in tunnels under this IKE_SA ++ * 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 subnet) ++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; + +@@ -98,29 +123,34 @@ static bool add_include(private_unity_ha + { + return FALSE; + } +- ts = create_ts(subnet); +- if (!ts) ++ list = parse_subnets(data); ++ if (!list) + { + return FALSE; + } +- INIT(entry, +- .sa = ike_sa->get_unique_id(ike_sa), +- .ts = ts, +- ); +- +- this->mutex->lock(this->mutex); +- this->include->insert_last(this->include, entry); +- this->mutex->unlock(this->mutex); ++ while (list->remove_first(list, (void**)&ts) == SUCCESS) ++ { ++ INIT(entry, ++ .sa = ike_sa->get_unique_id(ike_sa), ++ .ts = ts, ++ ); ++ ++ this->mutex->lock(this->mutex); ++ this->include->insert_last(this->include, entry); ++ this->mutex->unlock(this->mutex); ++ } ++ list->destroy(list); + return TRUE; + } + + /** +- * Remove a subnet from the inclusion list for this IKE_SA ++ * Remove a list of subnets from the inclusion list for this IKE_SA + */ +-static bool remove_include(private_unity_handler_t *this, chunk_t subnet) ++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; + +@@ -129,27 +159,31 @@ static bool remove_include(private_unity + { + return FALSE; + } +- ts = create_ts(subnet); +- if (!ts) ++ list = parse_subnets(data); ++ if (!list) + { + return FALSE; + } + + this->mutex->lock(this->mutex); +- enumerator = this->include->create_enumerator(this->include); +- while (enumerator->enumerate(enumerator, &entry)) ++ while (list->remove_first(list, (void**)&ts) == SUCCESS) + { +- if (entry->sa == ike_sa->get_unique_id(ike_sa) && +- ts->equals(ts, entry->ts)) ++ enumerator = this->include->create_enumerator(this->include); ++ while (enumerator->enumerate(enumerator, &entry)) + { +- this->include->remove_at(this->include, enumerator); +- entry_destroy(entry); +- break; ++ if (entry->sa == ike_sa->get_unique_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); + } +- enumerator->destroy(enumerator); + this->mutex->unlock(this->mutex); +- ts->destroy(ts); ++ list->destroy(list); + return TRUE; + } + +@@ -212,9 +246,10 @@ static job_requeue_t add_exclude_async(e + /** + * Add a bypass policy for a given subnet + */ +-static bool add_exclude(private_unity_handler_t *this, chunk_t 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; + +@@ -223,48 +258,60 @@ static bool add_exclude(private_unity_ha + { + return FALSE; + } +- ts = create_ts(subnet); +- if (!ts) ++ list = parse_subnets(data); ++ if (!list) + { + return FALSE; + } +- INIT(entry, +- .sa = ike_sa->get_unique_id(ike_sa), +- .ts = ts, +- ); +- +- /* 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)); ++ ++ while (list->remove_first(list, (void**)&ts) == SUCCESS) ++ { ++ INIT(entry, ++ .sa = ike_sa->get_unique_id(ike_sa), ++ .ts = ts, ++ ); ++ ++ /* 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 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; + } +- ts = create_ts(subnet); +- if (!ts) ++ list = parse_subnets(data); ++ if (!list) + { + return FALSE; + } +- 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); +- return charon->shunts->uninstall(charon->shunts, name); ++ 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, name) && success; ++ } ++ list->destroy(list); ++ return success; + } + + METHOD(attribute_handler_t, handle, bool, diff -urN strongswan-5.0.4.orig/debian/patches/unity-replicate-default-behavior-if-no-UNITY_SPLIT_INCLUDE.patch strongswan-5.0.4/debian/patches/unity-replicate-default-behavior-if-no-UNITY_SPLIT_INCLUDE.patch --- strongswan-5.0.4.orig/debian/patches/unity-replicate-default-behavior-if-no-UNITY_SPLIT_INCLUDE.patch 1969-12-31 16:00:00.000000000 -0800 +++ strongswan-5.0.4/debian/patches/unity-replicate-default-behavior-if-no-UNITY_SPLIT_INCLUDE.patch 2013-07-26 09:37:55.000000000 -0700 @@ -0,0 +1,97 @@ +Description: Fallback on rigthsubnet if parsing UNITY_SPLIT_INCLUDE attributes fail +Origin: http://wiki.strongswan.org/issues/356 +Author: Tobias Brunner <tob...@strongswan.org> +Reviewed-By: Gerald Turner <gtur...@unzane.com> + +--- +The information above should follow the Patch Tagging Guidelines, please +checkout http://dep.debian.net/deps/dep3/ to learn about the format. Here +are templates for supplementary fields that you might want to add: + +Origin: <vendor|upstream|other>, <url of original patch> +Bug: <url in upstream bugtracker> +Bug-Debian: http://bugs.debian.org/<bugnumber> +Bug-Ubuntu: https://launchpad.net/bugs/<bugnumber> +Forwarded: <no|not-needed|url proving that it has been forwarded> +Reviewed-By: <name and email of someone who approved the patch> +Last-Update: <YYYY-MM-DD> + +--- strongswan-5.0.4.orig/src/libcharon/plugins/unity/unity_narrow.c ++++ strongswan-5.0.4/src/libcharon/plugins/unity/unity_narrow.c +@@ -36,13 +36,32 @@ struct private_unity_narrow_t { + }; + + /** ++ * Narrow the given received traffic selector with the child configuration and ++ * put them into the given list of TS ++ */ ++static void narrow_ts(child_cfg_t *cfg, traffic_selector_t *ts, ++ linked_list_t *list) ++{ ++ linked_list_t *received, *selected; ++ ++ received = linked_list_create(); ++ received->insert_last(received, ts); ++ selected = cfg->get_traffic_selectors(cfg, FALSE, received, NULL); ++ while (selected->remove_first(selected, (void**)&ts) == SUCCESS) ++ { ++ list->insert_last(list, ts); ++ } ++ selected->destroy(selected); ++ received->destroy(received); ++} ++ ++/** + * Narrow TS as initiator to Unity Split-Include/Local-LAN + */ + static void narrow_initiator(private_unity_narrow_t *this, ike_sa_t *ike_sa, + child_cfg_t *cfg, linked_list_t *remote) + { + traffic_selector_t *current, *orig = NULL; +- linked_list_t *received, *selected; + enumerator_t *enumerator; + + enumerator = this->handler->create_include_enumerator(this->handler, +@@ -56,16 +75,7 @@ static void narrow_initiator(private_uni + break; + } + } +- /* narrow received Unity TS with the child configuration */ +- received = linked_list_create(); +- received->insert_last(received, current); +- selected = cfg->get_traffic_selectors(cfg, FALSE, received, NULL); +- while (selected->remove_first(selected, (void**)¤t) == SUCCESS) +- { +- remote->insert_last(remote, current); +- } +- selected->destroy(selected); +- received->destroy(received); ++ narrow_ts(cfg, current, remote); + } + enumerator->destroy(enumerator); + if (orig) +@@ -75,6 +85,15 @@ static void narrow_initiator(private_uni + UNITY_SPLIT_INCLUDE, remote); + orig->destroy(orig); + } ++ else ++ { /* since we originally changed the traffic selector to 0.0.0.0/0 local ++ * narrowing is not applied if no Split-Include attrs are received */ ++ if (remote->remove_first(remote, (void**)&orig) == SUCCESS) ++ { ++ narrow_ts(cfg, orig, remote); ++ orig->destroy(orig); ++ } ++ } + } + + /** +@@ -93,6 +112,8 @@ static void narrow_initiator_pre(linked_ + "255.255.255.255", 65535); + if (ts) + { ++ DBG2(DBG_CFG, "changing proposed traffic selectors for other:"); ++ DBG2(DBG_CFG, " %R", ts); + list->insert_last(list, ts); + } + } diff -urN strongswan-5.0.4.orig/debian/rules strongswan-5.0.4/debian/rules --- strongswan-5.0.4.orig/debian/rules 2013-05-18 00:17:10.000000000 -0700 +++ strongswan-5.0.4/debian/rules 2013-07-29 09:52:57.000000000 -0700 @@ -18,6 +18,18 @@ --enable-test-vectors \ --enable-xauth-eap --enable-xauth-pam \ --enable-attr-sql \ + --enable-af-alg \ + --enable-certexpire \ + --enable-coupling \ + --enable-duplicheck \ + --enable-error-notify \ + --enable-ipseckey \ + --enable-lookip \ + --enable-mysql \ + --enable-sqlite \ + --enable-unbound \ + --enable-unity \ + --enable-whitelist \ --disable-blowfish --disable-des # BSD-Young license #--with-user=strongswan --with-group=nogroup # --enable-kernel-pfkey --enable-kernel-klips \ @@ -102,7 +114,7 @@ endif # then install the rest, ignoring the above - dh_install --fail-missing \ + dh_install --list-missing \ -X\.la -X\.a \ -Xmedsrv -Xman3 \ -Xlibstrongswan-kernel \ diff -urN strongswan-5.0.4.orig/.pc/applied-patches strongswan-5.0.4/.pc/applied-patches --- strongswan-5.0.4.orig/.pc/applied-patches 2013-07-29 10:03:30.102395557 -0700 +++ strongswan-5.0.4/.pc/applied-patches 2013-07-29 10:44:06.051232370 -0700 @@ -1 +1,3 @@ 01_fix-manpages.patch +unity-replicate-default-behavior-if-no-UNITY_SPLIT_INCLUDE.patch +unity-handle-multi-valued-UNITY_SPLIT_INCLUDE.patch diff -urN strongswan-5.0.4.orig/.pc/unity-handle-multi-valued-UNITY_SPLIT_INCLUDE.patch/src/libcharon/plugins/unity/unity_handler.c strongswan-5.0.4/.pc/unity-handle-multi-valued-UNITY_SPLIT_INCLUDE.patch/src/libcharon/plugins/unity/unity_handler.c --- strongswan-5.0.4.orig/.pc/unity-handle-multi-valued-UNITY_SPLIT_INCLUDE.patch/src/libcharon/plugins/unity/unity_handler.c 1969-12-31 16:00:00.000000000 -0800 +++ strongswan-5.0.4/.pc/unity-handle-multi-valued-UNITY_SPLIT_INCLUDE.patch/src/libcharon/plugins/unity/unity_handler.c 2013-04-10 09:10:30.000000000 -0700 @@ -0,0 +1,429 @@ +/* + * 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, unique ID */ + u_int32_t sa; + /** traffic selector to include/exclude */ + traffic_selector_t *ts; +} entry_t; + +/** + * Clean up an entry + */ +static void entry_destroy(entry_t *this) +{ + 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; + + if (subnet.len != 8) + { + return NULL; + } + net = chunk_create(subnet.ptr, 4); + mask = chunk_clonea(chunk_skip(subnet, 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); +} + +/** + * Store a subnet to include in tunnels under this IKE_SA + */ +static bool add_include(private_unity_handler_t *this, chunk_t subnet) +{ + traffic_selector_t *ts; + ike_sa_t *ike_sa; + entry_t *entry; + + ike_sa = charon->bus->get_sa(charon->bus); + if (!ike_sa) + { + return FALSE; + } + ts = create_ts(subnet); + if (!ts) + { + return FALSE; + } + INIT(entry, + .sa = ike_sa->get_unique_id(ike_sa), + .ts = ts, + ); + + this->mutex->lock(this->mutex); + this->include->insert_last(this->include, entry); + this->mutex->unlock(this->mutex); + return TRUE; +} + +/** + * Remove a subnet from the inclusion list for this IKE_SA + */ +static bool remove_include(private_unity_handler_t *this, chunk_t subnet) +{ + enumerator_t *enumerator; + traffic_selector_t *ts; + ike_sa_t *ike_sa; + entry_t *entry; + + ike_sa = charon->bus->get_sa(charon->bus); + if (!ike_sa) + { + return FALSE; + } + ts = create_ts(subnet); + if (!ts) + { + return FALSE; + } + + this->mutex->lock(this->mutex); + enumerator = this->include->create_enumerator(this->include); + while (enumerator->enumerate(enumerator, &entry)) + { + if (entry->sa == ike_sa->get_unique_id(ike_sa) && + ts->equals(ts, entry->ts)) + { + this->include->remove_at(this->include, enumerator); + entry_destroy(entry); + break; + } + } + enumerator->destroy(enumerator); + this->mutex->unlock(this->mutex); + ts->destroy(ts); + 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; + lifetime_cfg_t lft = { .time = { .life = 0 } }; + ike_sa_t *ike_sa; + char name[128]; + host_t *host; + + ike_sa = charon->ike_sa_manager->checkout_by_id(charon->ike_sa_manager, + entry->sa, FALSE); + if (ike_sa) + { + create_shunt_name(ike_sa, entry->ts, name, sizeof(name)); + + child_cfg = child_cfg_create(name, &lft, NULL, TRUE, MODE_PASS, + ACTION_NONE, ACTION_NONE, ACTION_NONE, + FALSE, 0, 0, NULL, NULL, FALSE); + 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, 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 subnet) +{ + traffic_selector_t *ts; + ike_sa_t *ike_sa; + entry_t *entry; + + ike_sa = charon->bus->get_sa(charon->bus); + if (!ike_sa) + { + return FALSE; + } + ts = create_ts(subnet); + if (!ts) + { + return FALSE; + } + INIT(entry, + .sa = ike_sa->get_unique_id(ike_sa), + .ts = ts, + ); + + /* 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)); + return TRUE; +} + +/** + * Remove a bypass policy for a given subnet + */ +static bool remove_exclude(private_unity_handler_t *this, chunk_t subnet) +{ + traffic_selector_t *ts; + ike_sa_t *ike_sa; + char name[128]; + + ike_sa = charon->bus->get_sa(charon->bus); + if (!ike_sa) + { + return FALSE; + } + ts = create_ts(subnet); + if (!ts) + { + return FALSE; + } + 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); + return charon->shunts->uninstall(charon->shunts, name); +} + +METHOD(attribute_handler_t, handle, bool, + private_unity_handler_t *this, identification_t *id, + 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, identification_t *server, + 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, configuration_attribute_type_t *type, + chunk_t *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, identification_t *id, linked_list_t *vips) +{ + attribute_enumerator_t *enumerator; + ike_sa_t *ike_sa; + + 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 = (void*)_enumerate_attributes, + .destroy = (void*)free, + }, + ); + return &enumerator->public; +} + +typedef struct { + /** mutex to unlock */ + mutex_t *mutex; + /** IKE_SA ID to filter for */ + u_int32_t id; +} include_filter_t; + +/** + * Include enumerator filter function + */ +static bool include_filter(include_filter_t *data, + entry_t **entry, traffic_selector_t **ts) +{ + if ((*entry)->sa == data->id) + { + *ts = (*entry)->ts; + return TRUE; + } + return FALSE; +} + +/** + * Destroy include filter data, unlock mutex + */ +static void destroy_filter(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, u_int32_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), + (void*)include_filter, data, (void*)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; +} diff -urN strongswan-5.0.4.orig/.pc/unity-replicate-default-behavior-if-no-UNITY_SPLIT_INCLUDE.patch/src/libcharon/plugins/unity/unity_narrow.c strongswan-5.0.4/.pc/unity-replicate-default-behavior-if-no-UNITY_SPLIT_INCLUDE.patch/src/libcharon/plugins/unity/unity_narrow.c --- strongswan-5.0.4.orig/.pc/unity-replicate-default-behavior-if-no-UNITY_SPLIT_INCLUDE.patch/src/libcharon/plugins/unity/unity_narrow.c 1969-12-31 16:00:00.000000000 -0800 +++ strongswan-5.0.4/.pc/unity-replicate-default-behavior-if-no-UNITY_SPLIT_INCLUDE.patch/src/libcharon/plugins/unity/unity_narrow.c 2013-04-10 02:10:56.000000000 -0700 @@ -0,0 +1,171 @@ +/* + * 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_narrow.h" + +#include <daemon.h> + +typedef struct private_unity_narrow_t private_unity_narrow_t; + +/** + * Private data of an unity_narrow_t object. + */ +struct private_unity_narrow_t { + + /** + * Public unity_narrow_t interface. + */ + unity_narrow_t public; + + /** + * Unity attribute handler + */ + unity_handler_t *handler; +}; + +/** + * Narrow TS as initiator to Unity Split-Include/Local-LAN + */ +static void narrow_initiator(private_unity_narrow_t *this, ike_sa_t *ike_sa, + child_cfg_t *cfg, linked_list_t *remote) +{ + traffic_selector_t *current, *orig = NULL; + linked_list_t *received, *selected; + enumerator_t *enumerator; + + enumerator = this->handler->create_include_enumerator(this->handler, + ike_sa->get_unique_id(ike_sa)); + while (enumerator->enumerate(enumerator, ¤t)) + { + if (orig == NULL) + { /* got one, replace original TS */ + if (remote->remove_first(remote, (void**)&orig) != SUCCESS) + { + break; + } + } + /* narrow received Unity TS with the child configuration */ + received = linked_list_create(); + received->insert_last(received, current); + selected = cfg->get_traffic_selectors(cfg, FALSE, received, NULL); + while (selected->remove_first(selected, (void**)¤t) == SUCCESS) + { + remote->insert_last(remote, current); + } + selected->destroy(selected); + received->destroy(received); + } + enumerator->destroy(enumerator); + if (orig) + { + DBG1(DBG_CFG, "narrowed CHILD_SA to %N %#R", + configuration_attribute_type_names, + UNITY_SPLIT_INCLUDE, remote); + orig->destroy(orig); + } +} + +/** + * As initiator, bump up TS to 0.0.0.0/0 for on-the-wire bits + */ +static void narrow_initiator_pre(linked_list_t *list) +{ + traffic_selector_t *ts; + + while (list->remove_first(list, (void**)&ts) == SUCCESS) + { + ts->destroy(ts); + } + ts = traffic_selector_create_from_string(0, TS_IPV4_ADDR_RANGE, + "0.0.0.0", 0, + "255.255.255.255", 65535); + if (ts) + { + list->insert_last(list, ts); + } +} + +/** + * As responder, narrow down TS to configuration for installation + */ +static void narrow_responder_post(child_cfg_t *child_cfg, linked_list_t *local) +{ + traffic_selector_t *ts; + linked_list_t *configured; + + while (local->remove_first(local, (void**)&ts) == SUCCESS) + { + ts->destroy(ts); + } + configured = child_cfg->get_traffic_selectors(child_cfg, TRUE, NULL, NULL); + + while (configured->remove_first(configured, (void**)&ts) == SUCCESS) + { + local->insert_last(local, ts); + } + configured->destroy(configured); +} + +METHOD(listener_t, narrow, bool, + private_unity_narrow_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa, + narrow_hook_t type, linked_list_t *local, linked_list_t *remote) +{ + if (ike_sa->get_version(ike_sa) == IKEV1 && + ike_sa->supports_extension(ike_sa, EXT_CISCO_UNITY)) + { + switch (type) + { + case NARROW_INITIATOR_PRE_AUTH: + narrow_initiator_pre(remote); + break; + case NARROW_INITIATOR_POST_AUTH: + narrow_initiator(this, ike_sa, + child_sa->get_config(child_sa), remote); + break; + case NARROW_RESPONDER_POST: + narrow_responder_post(child_sa->get_config(child_sa), local); + break; + default: + break; + } + } + return TRUE; +} + +METHOD(unity_narrow_t, destroy, void, + private_unity_narrow_t *this) +{ + free(this); +} + +/** + * See header + */ +unity_narrow_t *unity_narrow_create(unity_handler_t *handler) +{ + private_unity_narrow_t *this; + + INIT(this, + .public = { + .listener = { + .narrow = _narrow, + }, + .destroy = _destroy, + }, + .handler = handler, + ); + + return &this->public; +} diff -urN strongswan-5.0.4.orig/src/libcharon/plugins/unity/unity_handler.c strongswan-5.0.4/src/libcharon/plugins/unity/unity_handler.c --- strongswan-5.0.4.orig/src/libcharon/plugins/unity/unity_handler.c 2013-04-10 09:10:30.000000000 -0700 +++ strongswan-5.0.4/src/libcharon/plugins/unity/unity_handler.c 2013-07-29 10:44:06.000000000 -0700 @@ -1,4 +1,7 @@ /* + * Copyright (C) 2013 Tobias Brunner + * Hochschule fuer Technik Rapperswil + * * Copyright (C) 2012 Martin Willi * Copyright (C) 2012 revosec AG * @@ -70,12 +73,8 @@ chunk_t net, mask; int i; - if (subnet.len != 8) - { - return NULL; - } net = chunk_create(subnet.ptr, 4); - mask = chunk_clonea(chunk_skip(subnet, 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]; @@ -85,11 +84,37 @@ } /** - * Store a subnet to include in tunnels under this IKE_SA + * 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 subnet) +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; @@ -98,29 +123,34 @@ { return FALSE; } - ts = create_ts(subnet); - if (!ts) + list = parse_subnets(data); + if (!list) { return FALSE; } - INIT(entry, - .sa = ike_sa->get_unique_id(ike_sa), - .ts = ts, - ); - - this->mutex->lock(this->mutex); - this->include->insert_last(this->include, entry); - this->mutex->unlock(this->mutex); + while (list->remove_first(list, (void**)&ts) == SUCCESS) + { + INIT(entry, + .sa = ike_sa->get_unique_id(ike_sa), + .ts = ts, + ); + + this->mutex->lock(this->mutex); + this->include->insert_last(this->include, entry); + this->mutex->unlock(this->mutex); + } + list->destroy(list); return TRUE; } /** - * Remove a subnet from the inclusion list for this IKE_SA + * Remove a list of subnets from the inclusion list for this IKE_SA */ -static bool remove_include(private_unity_handler_t *this, chunk_t subnet) +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; @@ -129,27 +159,31 @@ { return FALSE; } - ts = create_ts(subnet); - if (!ts) + list = parse_subnets(data); + if (!list) { return FALSE; } this->mutex->lock(this->mutex); - enumerator = this->include->create_enumerator(this->include); - while (enumerator->enumerate(enumerator, &entry)) + while (list->remove_first(list, (void**)&ts) == SUCCESS) { - if (entry->sa == ike_sa->get_unique_id(ike_sa) && - ts->equals(ts, entry->ts)) + enumerator = this->include->create_enumerator(this->include); + while (enumerator->enumerate(enumerator, &entry)) { - this->include->remove_at(this->include, enumerator); - entry_destroy(entry); - break; + if (entry->sa == ike_sa->get_unique_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); } - enumerator->destroy(enumerator); this->mutex->unlock(this->mutex); - ts->destroy(ts); + list->destroy(list); return TRUE; } @@ -212,9 +246,10 @@ /** * Add a bypass policy for a given subnet */ -static bool add_exclude(private_unity_handler_t *this, chunk_t 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; @@ -223,48 +258,60 @@ { return FALSE; } - ts = create_ts(subnet); - if (!ts) + list = parse_subnets(data); + if (!list) { return FALSE; } - INIT(entry, - .sa = ike_sa->get_unique_id(ike_sa), - .ts = ts, - ); - - /* 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)); + + while (list->remove_first(list, (void**)&ts) == SUCCESS) + { + INIT(entry, + .sa = ike_sa->get_unique_id(ike_sa), + .ts = ts, + ); + + /* 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 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; } - ts = create_ts(subnet); - if (!ts) + list = parse_subnets(data); + if (!list) { return FALSE; } - 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); - return charon->shunts->uninstall(charon->shunts, name); + 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, name) && success; + } + list->destroy(list); + return success; } METHOD(attribute_handler_t, handle, bool, diff -urN strongswan-5.0.4.orig/src/libcharon/plugins/unity/unity_narrow.c strongswan-5.0.4/src/libcharon/plugins/unity/unity_narrow.c --- strongswan-5.0.4.orig/src/libcharon/plugins/unity/unity_narrow.c 2013-04-10 02:10:56.000000000 -0700 +++ strongswan-5.0.4/src/libcharon/plugins/unity/unity_narrow.c 2013-07-29 10:44:06.000000000 -0700 @@ -36,13 +36,32 @@ }; /** + * Narrow the given received traffic selector with the child configuration and + * put them into the given list of TS + */ +static void narrow_ts(child_cfg_t *cfg, traffic_selector_t *ts, + linked_list_t *list) +{ + linked_list_t *received, *selected; + + received = linked_list_create(); + received->insert_last(received, ts); + selected = cfg->get_traffic_selectors(cfg, FALSE, received, NULL); + while (selected->remove_first(selected, (void**)&ts) == SUCCESS) + { + list->insert_last(list, ts); + } + selected->destroy(selected); + received->destroy(received); +} + +/** * Narrow TS as initiator to Unity Split-Include/Local-LAN */ static void narrow_initiator(private_unity_narrow_t *this, ike_sa_t *ike_sa, child_cfg_t *cfg, linked_list_t *remote) { traffic_selector_t *current, *orig = NULL; - linked_list_t *received, *selected; enumerator_t *enumerator; enumerator = this->handler->create_include_enumerator(this->handler, @@ -56,16 +75,7 @@ break; } } - /* narrow received Unity TS with the child configuration */ - received = linked_list_create(); - received->insert_last(received, current); - selected = cfg->get_traffic_selectors(cfg, FALSE, received, NULL); - while (selected->remove_first(selected, (void**)¤t) == SUCCESS) - { - remote->insert_last(remote, current); - } - selected->destroy(selected); - received->destroy(received); + narrow_ts(cfg, current, remote); } enumerator->destroy(enumerator); if (orig) @@ -75,6 +85,15 @@ UNITY_SPLIT_INCLUDE, remote); orig->destroy(orig); } + else + { /* since we originally changed the traffic selector to 0.0.0.0/0 local + * narrowing is not applied if no Split-Include attrs are received */ + if (remote->remove_first(remote, (void**)&orig) == SUCCESS) + { + narrow_ts(cfg, orig, remote); + orig->destroy(orig); + } + } } /** @@ -93,6 +112,8 @@ "255.255.255.255", 65535); if (ts) { + DBG2(DBG_CFG, "changing proposed traffic selectors for other:"); + DBG2(DBG_CFG, " %R", ts); list->insert_last(list, ts); } }
pgppXSMvLVuQA.pgp
Description: PGP signature