diff options
author | Martin Willi <martin@revosec.ch> | 2014-10-06 18:31:14 +0200 |
---|---|---|
committer | Martin Willi <martin@revosec.ch> | 2014-10-06 18:31:14 +0200 |
commit | 7d3c58a511ae6825353020571b8cdaa45238fa6c (patch) | |
tree | 1657a6d3b8290c8e058f142655a395ae40ad5beb /src | |
parent | 100c1a4bf1f59414af8a1bfb6acb14b1a98e3349 (diff) | |
parent | 9180c921e85a4a55e0ba487d6a0751b127de3471 (diff) | |
download | strongswan-5.2.1dr1.tar.bz2 strongswan-5.2.1dr1.tar.xz |
Merge branch 'ext-auth'5.2.1dr1
Integrates the ext-auth plugin by Vyronas Tsingaras. The new child process
abstraction simplifies implementation in both the new ext-auth and the existing
updown plugin, and makes them available on the Windows platform.
Diffstat (limited to 'src')
-rw-r--r-- | src/libcharon/Makefile.am | 7 | ||||
-rw-r--r-- | src/libcharon/plugins/ext_auth/Makefile.am | 18 | ||||
-rw-r--r-- | src/libcharon/plugins/ext_auth/ext_auth_listener.c | 203 | ||||
-rw-r--r-- | src/libcharon/plugins/ext_auth/ext_auth_listener.h | 59 | ||||
-rw-r--r-- | src/libcharon/plugins/ext_auth/ext_auth_plugin.c | 156 | ||||
-rw-r--r-- | src/libcharon/plugins/ext_auth/ext_auth_plugin.h | 49 | ||||
-rw-r--r-- | src/libcharon/plugins/updown/updown_listener.c | 461 | ||||
-rw-r--r-- | src/libstrongswan/Android.mk | 2 | ||||
-rw-r--r-- | src/libstrongswan/Makefile.am | 4 | ||||
-rw-r--r-- | src/libstrongswan/tests/Makefile.am | 1 | ||||
-rw-r--r-- | src/libstrongswan/tests/suites/test_process.c | 221 | ||||
-rw-r--r-- | src/libstrongswan/tests/tests.h | 1 | ||||
-rw-r--r-- | src/libstrongswan/utils/process.c | 591 | ||||
-rw-r--r-- | src/libstrongswan/utils/process.h | 97 |
14 files changed, 1621 insertions, 249 deletions
diff --git a/src/libcharon/Makefile.am b/src/libcharon/Makefile.am index e81c42405..0eaabf57f 100644 --- a/src/libcharon/Makefile.am +++ b/src/libcharon/Makefile.am @@ -258,6 +258,13 @@ if MONOLITHIC endif endif +if USE_EXT_AUTH + SUBDIRS += plugins/ext_auth +if MONOLITHIC + libcharon_la_LIBADD += plugins/ext_auth/libstrongswan-ext-auth.la +endif +endif + if USE_EAP_IDENTITY SUBDIRS += plugins/eap_identity if MONOLITHIC diff --git a/src/libcharon/plugins/ext_auth/Makefile.am b/src/libcharon/plugins/ext_auth/Makefile.am new file mode 100644 index 000000000..d51ea8881 --- /dev/null +++ b/src/libcharon/plugins/ext_auth/Makefile.am @@ -0,0 +1,18 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/libstrongswan \ + -I$(top_srcdir)/src/libhydra \ + -I$(top_srcdir)/src/libcharon + +AM_CFLAGS = \ + $(PLUGIN_CFLAGS) + +if MONOLITHIC +noinst_LTLIBRARIES = libstrongswan-ext-auth.la +else +plugin_LTLIBRARIES = libstrongswan-ext-auth.la +endif + +libstrongswan_ext_auth_la_SOURCES = ext_auth_plugin.h ext_auth_plugin.c \ + ext_auth_listener.h ext_auth_listener.c + +libstrongswan_ext_auth_la_LDFLAGS = -module -avoid-version diff --git a/src/libcharon/plugins/ext_auth/ext_auth_listener.c b/src/libcharon/plugins/ext_auth/ext_auth_listener.c new file mode 100644 index 000000000..06cec20d7 --- /dev/null +++ b/src/libcharon/plugins/ext_auth/ext_auth_listener.c @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2014 Vyronas Tsingaras (vtsingaras@it.auth.gr) + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec AG + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* for vasprintf() */ +#define _GNU_SOURCE +#include "ext_auth_listener.h" + +#include <daemon.h> +#include <utils/process.h> + +#include <stdio.h> +#include <unistd.h> + +typedef struct private_ext_auth_listener_t private_ext_auth_listener_t; + +/** + * Private data of an ext_auth_listener_t object. + */ +struct private_ext_auth_listener_t { + + /** + * Public ext_auth_listener_listener_t interface. + */ + ext_auth_listener_t public; + + /** + * Path to authorization program + */ + char *script; +}; + +/** + * Allocate and push a format string to the environment + */ +static bool push_env(char *envp[], u_int count, char *fmt, ...) +{ + int i = 0; + char *str; + va_list args; + + while (envp[i]) + { + if (++i + 1 >= count) + { + return FALSE; + } + } + va_start(args, fmt); + if (vasprintf(&str, fmt, args) >= 0) + { + envp[i] = str; + } + va_end(args); + return envp[i] != NULL; +} + +/** + * Free all allocated environment strings + */ +static void free_env(char *envp[]) +{ + int i; + + for (i = 0; envp[i]; i++) + { + free(envp[i]); + } +} + +METHOD(listener_t, authorize, bool, + private_ext_auth_listener_t *this, ike_sa_t *ike_sa, + bool final, bool *success) +{ + if (final) + { + FILE *shell; + process_t *process; + char *envp[32] = {}; + int out, retval; + + *success = FALSE; + + push_env(envp, countof(envp), "IKE_UNIQUE_ID=%u", + ike_sa->get_unique_id(ike_sa)); + push_env(envp, countof(envp), "IKE_NAME=%s", + ike_sa->get_name(ike_sa)); + + push_env(envp, countof(envp), "IKE_LOCAL_HOST=%H", + ike_sa->get_my_host(ike_sa)); + push_env(envp, countof(envp), "IKE_REMOTE_HOST=%H", + ike_sa->get_other_host(ike_sa)); + + push_env(envp, countof(envp), "IKE_LOCAL_ID=%Y", + ike_sa->get_my_id(ike_sa)); + push_env(envp, countof(envp), "IKE_REMOTE_ID=%Y", + ike_sa->get_other_id(ike_sa)); + + if (ike_sa->has_condition(ike_sa, COND_EAP_AUTHENTICATED) || + ike_sa->has_condition(ike_sa, COND_XAUTH_AUTHENTICATED)) + { + push_env(envp, countof(envp), "IKE_REMOTE_EAP_ID=%Y", + ike_sa->get_other_eap_id(ike_sa)); + } + + process = process_start_shell(envp, NULL, &out, NULL, + "2>&1 %s", this->script); + if (process) + { + shell = fdopen(out, "r"); + if (shell) + { + while (TRUE) + { + char resp[128], *e; + + if (fgets(resp, sizeof(resp), shell) == NULL) + { + if (ferror(shell)) + { + DBG1(DBG_CFG, "error reading from ext-auth script"); + } + break; + } + else + { + e = resp + strlen(resp); + if (e > resp && e[-1] == '\n') + { + e[-1] = '\0'; + } + DBG1(DBG_CHD, "ext-auth: %s", resp); + } + } + fclose(shell); + } + else + { + close(out); + } + if (process->wait(process, &retval)) + { + if (retval == EXIT_SUCCESS) + { + *success = TRUE; + } + else + { + DBG1(DBG_CFG, "rejecting IKE_SA for ext-auth result: %d", + retval); + } + } + } + free_env(envp); + } + return TRUE; +} + +METHOD(ext_auth_listener_t, destroy, void, + private_ext_auth_listener_t *this) +{ + free(this); +} + +/** + * See header + */ +ext_auth_listener_t *ext_auth_listener_create(char *script) +{ + private_ext_auth_listener_t *this; + + INIT(this, + .public = { + .listener = { + .authorize = _authorize, + }, + .destroy = _destroy, + }, + .script = script, + ); + + return &this->public; +} diff --git a/src/libcharon/plugins/ext_auth/ext_auth_listener.h b/src/libcharon/plugins/ext_auth/ext_auth_listener.h new file mode 100644 index 000000000..3fec83066 --- /dev/null +++ b/src/libcharon/plugins/ext_auth/ext_auth_listener.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2014 Vyronas Tsingaras (vtsingaras@it.auth.gr) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * @defgroup ext_auth_listener ext_auth_listener + * @{ @ingroup ext_auth + */ + +#ifndef EXT_AUTH_LISTENER_H_ +#define EXT_AUTH_LISTENER_H_ + +#include <bus/listeners/listener.h> + +typedef struct ext_auth_listener_t ext_auth_listener_t; + +/** + * Listener using an external script to authorize connection + */ +struct ext_auth_listener_t { + + /** + * Implements listener_t interface. + */ + listener_t listener; + + /** + * Destroy the listener. + */ + void (*destroy)(ext_auth_listener_t *this); +}; + +/** + * Create ext_auth_listener instance. + * + * @param script path to authorization script + * @return listener instance + */ +ext_auth_listener_t *ext_auth_listener_create(char *script); + +#endif /** ext_auth_LISTENER_H_ @}*/ diff --git a/src/libcharon/plugins/ext_auth/ext_auth_plugin.c b/src/libcharon/plugins/ext_auth/ext_auth_plugin.c new file mode 100644 index 000000000..b3698c767 --- /dev/null +++ b/src/libcharon/plugins/ext_auth/ext_auth_plugin.c @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2014 Vyronas Tsingaras (vtsingaras@it.auth.gr) + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec AG + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "ext_auth_plugin.h" +#include "ext_auth_listener.h" + +#include <daemon.h> + +typedef struct private_ext_auth_plugin_t private_ext_auth_plugin_t; + +/** + * private data of ext_auth plugin + */ +struct private_ext_auth_plugin_t { + + /** + * implements plugin interface + */ + ext_auth_plugin_t public; + + /** + * Listener verifying peers during authorization + */ + ext_auth_listener_t *listener; +}; + +METHOD(plugin_t, get_name, char*, + private_ext_auth_plugin_t *this) +{ + return "ext-auth"; +} + +/** + * Create a listener instance, NULL on error + */ +static ext_auth_listener_t* create_listener() +{ + char *script; + + script = lib->settings->get_str(lib->settings, + "%s.plugins.ext-auth.script", NULL, lib->ns); + if (!script) + { + DBG1(DBG_CFG, "no script for ext-auth script defined, disabled"); + return NULL; + } + DBG1(DBG_CFG, "using ext-auth script '%s'", script); + return ext_auth_listener_create(script); +} + +/** + * Register listener + */ +static bool plugin_cb(private_ext_auth_plugin_t *this, + plugin_feature_t *feature, bool reg, void *cb_data) +{ + if (reg) + { + this->listener = create_listener(); + if (!this->listener) + { + return FALSE; + } + charon->bus->add_listener(charon->bus, &this->listener->listener); + } + else + { + if (this->listener) + { + charon->bus->remove_listener(charon->bus, &this->listener->listener); + this->listener->destroy(this->listener); + } + } + return TRUE; +} + +METHOD(plugin_t, get_features, int, + private_ext_auth_plugin_t *this, plugin_feature_t *features[]) +{ + static plugin_feature_t f[] = { + PLUGIN_CALLBACK((plugin_feature_callback_t)plugin_cb, NULL), + PLUGIN_PROVIDE(CUSTOM, "ext_auth"), + }; + *features = f; + return countof(f); +} + + +METHOD(plugin_t, reload, bool, + private_ext_auth_plugin_t *this) +{ + ext_auth_listener_t *listener; + + /* reload new listener overlapped */ + listener = create_listener(); + if (listener) + { + charon->bus->add_listener(charon->bus, &listener->listener); + } + if (this->listener) + { + charon->bus->remove_listener(charon->bus, &this->listener->listener); + this->listener->destroy(this->listener); + } + this->listener = listener; + + return TRUE; +} + +METHOD(plugin_t, destroy, void, + private_ext_auth_plugin_t *this) +{ + free(this); +} + +/** + * Plugin constructor + */ +plugin_t *ext_auth_plugin_create() +{ + private_ext_auth_plugin_t *this; + + INIT(this, + .public = { + .plugin = { + .get_name = _get_name, + .get_features = _get_features, + .reload = _reload, + .destroy = _destroy, + }, + }, + ); + + return &this->public.plugin; +} diff --git a/src/libcharon/plugins/ext_auth/ext_auth_plugin.h b/src/libcharon/plugins/ext_auth/ext_auth_plugin.h new file mode 100644 index 000000000..1288e240c --- /dev/null +++ b/src/libcharon/plugins/ext_auth/ext_auth_plugin.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2014 Vyronas Tsingaras (vtsingaras@it.auth.gr) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * @defgroup ext_auth ext_auth + * @ingroup cplugins + * + * @defgroup ext_auth_plugin ext_auth_plugin + * @{ @ingroup ext_auth + */ + +#ifndef EXT_AUTH_PLUGIN_H_ +#define EXT_AUTH_PLUGIN_H_ + +#include <plugins/plugin.h> + +typedef struct ext_auth_plugin_t ext_auth_plugin_t; + +/** + * Plugin using an external script to authorize connections. + */ +struct ext_auth_plugin_t { + + /** + * Implements plugin interface. + */ + plugin_t plugin; +}; + +#endif /** EXT_AUTH_PLUGIN_H_ @}*/ diff --git a/src/libcharon/plugins/updown/updown_listener.c b/src/libcharon/plugins/updown/updown_listener.c index 200f298a1..1d15cc55e 100644 --- a/src/libcharon/plugins/updown/updown_listener.c +++ b/src/libcharon/plugins/updown/updown_listener.c @@ -16,9 +16,11 @@ #define _GNU_SOURCE #include <stdio.h> +#include <unistd.h> #include "updown_listener.h" +#include <utils/process.h> #include <hydra.h> #include <daemon.h> #include <config/child_cfg.h> @@ -97,53 +99,84 @@ static char* uncache_iface(private_updown_listener_t *this, u_int32_t reqid) } /** - * Create variables for handled DNS attributes + * Allocate and push a format string to the environment */ -static char *make_dns_vars(private_updown_listener_t *this, ike_sa_t *ike_sa) +static bool push_env(char *envp[], u_int count, char *fmt, ...) { - enumerator_t *enumerator; - host_t *host; - int v4 = 0, v6 = 0; - char total[512] = "", current[64]; + int i = 0; + char *str; + va_list args; - if (!this->handler) + while (envp[i]) { - return strdup(""); + if (++i + 1 >= count) + { + return FALSE; + } } + va_start(args, fmt); + if (vasprintf(&str, fmt, args) >= 0) + { + envp[i] = str; + } + va_end(args); + return envp[i] != NULL; +} - enumerator = this->handler->create_dns_enumerator(this->handler, - ike_sa->get_unique_id(ike_sa)); - while (enumerator->enumerate(enumerator, &host)) +/** + * Free all allocated environment strings + */ +static void free_env(char *envp[]) +{ + int i; + + for (i = 0; envp[i]; i++) { - switch (host->get_family(host)) + free(envp[i]); + } +} + +/** + * Push variables for handled DNS attributes + */ +static void push_dns_env(private_updown_listener_t *this, ike_sa_t *ike_sa, + char *envp[], u_int count) +{ + enumerator_t *enumerator; + host_t *host; + int v4 = 0, v6 = 0; + + if (this->handler) + { + enumerator = this->handler->create_dns_enumerator(this->handler, + ike_sa->get_unique_id(ike_sa)); + while (enumerator->enumerate(enumerator, &host)) { - case AF_INET: - snprintf(current, sizeof(current), - "PLUTO_DNS4_%d='%H' ", ++v4, host); - break; - case AF_INET6: - snprintf(current, sizeof(current), - "PLUTO_DNS6_%d='%H' ", ++v6, host); - break; - default: - continue; + switch (host->get_family(host)) + { + case AF_INET: + push_env(envp, count, "PLUTO_DNS4_%d=%H", ++v4, host); + break; + case AF_INET6: + push_env(envp, count, "PLUTO_DNS6_%d=%H", ++v6, host); + break; + default: + continue; + } } - strncat(total, current, sizeof(total) - strlen(total) - 1); + enumerator->destroy(enumerator); } - enumerator->destroy(enumerator); - - return strdup(total); } /** - * Create variables for local virtual IPs + * Push variables for local virtual IPs */ -static char *make_vip_vars(private_updown_listener_t *this, ike_sa_t *ike_sa) +static void push_vip_env(private_updown_listener_t *this, ike_sa_t *ike_sa, + char *envp[], u_int count) { enumerator_t *enumerator; host_t *host; int v4 = 0, v6 = 0; - char total[512] = "", current[64]; bool first = TRUE; enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, TRUE); @@ -151,28 +184,22 @@ static char *make_vip_vars(private_updown_listener_t *this, ike_sa_t *ike_sa) { if (first) { /* legacy variable for first VIP */ - snprintf(current, sizeof(current), - "PLUTO_MY_SOURCEIP='%H' ", host); - strncat(total, current, sizeof(total) - strlen(total) - 1); + first = FALSE; + push_env(envp, count, "PLUTO_MY_SOURCEIP=%H", host); } switch (host->get_family(host)) { case AF_INET: - snprintf(current, sizeof(current), - "PLUTO_MY_SOURCEIP4_%d='%H' ", ++v4, host); + push_env(envp, count, "PLUTO_MY_SOURCEIP4_%d=%H", ++v4, host); break; case AF_INET6: - snprintf(current, sizeof(current), - "PLUTO_MY_SOURCEIP6_%d='%H' ", ++v6, host); + push_env(envp, count, "PLUTO_MY_SOURCEIP6_%d=%H", ++v6, host); break; default: continue; } - strncat(total, current, sizeof(total) - strlen(total) - 1); } enumerator->destroy(enumerator); - - return strdup(total); } /** @@ -196,240 +223,182 @@ static u_int16_t get_port(traffic_selector_t *me, return local ? me->get_from_port(me) : other->get_from_port(other); } -METHOD(listener_t, child_updown, bool, - private_updown_listener_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa, - bool up) +/** + * Invoke the updown script once for given traffic selectors + */ +static void invoke_once(private_updown_listener_t *this, ike_sa_t *ike_sa, + child_sa_t *child_sa, child_cfg_t *config, bool up, + traffic_selector_t *my_ts, traffic_selector_t *other_ts) { - traffic_selector_t *my_ts, *other_ts; - enumerator_t *enumerator; - child_cfg_t *config; - host_t *me, *other; - char *script; + host_t *me, *other, *host; + char *iface; + u_int8_t mask; + mark_t mark; + bool is_host, is_ipv6; + int out; + FILE *shell; + process_t *process; + char *envp[128] = {}; - config = child_sa->get_config(child_sa); - script = config->get_updown(config); me = ike_sa->get_my_host(ike_sa); other = ike_sa->get_other_host(ike_sa); - if (script == NULL) + push_env(envp, countof(envp), "PLUTO_VERSION=1.1"); + is_host = my_ts->is_host(my_ts, me); + if (is_host) { - return TRUE; + is_ipv6 = me->get_family(me) == AF_INET6; } - - enumerator = child_sa->create_policy_enumerator(child_sa); - while (enumerator->enumerate(enumerator, &my_ts, &other_ts)) + else { - char command[2048]; - host_t *my_client, *other_client; - u_int8_t my_client_mask, other_client_mask; - char *virtual_ip, *iface, *mark_in, *mark_out, *udp_enc, *dns, *xauth; - mark_t mark; - bool is_host, is_ipv6, use_ipcomp; - FILE *shell; - - my_ts->to_subnet(my_ts, &my_client, &my_client_mask); - other_ts->to_subnet(other_ts, &other_client, &other_client_mask); - - virtual_ip = make_vip_vars(this, ike_sa); - - /* check for the presence of an inbound mark */ - mark = config->get_mark(config, TRUE); - if (mark.value) - { - if (asprintf(&mark_in, "PLUTO_MARK_IN='%u/0x%08x' ", - mark.value, mark.mask ) < 0) - { - mark_in = NULL; - } - } - else - { - if (asprintf(&mark_in, "") < 0) - { - mark_in = NULL; - } - } - - /* check for the presence of an outbound mark */ - mark = config->get_mark(config, FALSE); - if (mark.value) - { - if (asprintf(&mark_out, "PLUTO_MARK_OUT='%u/0x%08x' ", - mark.value, mark.mask ) < 0) - { - mark_out = NULL; - } - } - else - { - if (asprintf(&mark_out, "") < 0) - { - mark_out = NULL; - } - } - - /* check for a NAT condition causing ESP_IN_UDP encapsulation */ - if (ike_sa->has_condition(ike_sa, COND_NAT_ANY)) + is_ipv6 = my_ts->get_type(my_ts) == TS_IPV6_ADDR_RANGE; + } + push_env(envp, countof(envp), "PLUTO_VERB=%s%s%s", + up ? "up" : "down", + is_host ? "-host" : "-client", + is_ipv6 ? "-v6" : ""); + push_env(envp, countof(envp), "PLUTO_CONNECTION=%s", + config->get_name(config)); + if (up) + { + if (hydra->kernel_interface->get_interface(hydra->kernel_interface, + me, &iface)) { - if (asprintf(&udp_enc, "PLUTO_UDP_ENC='%u' ", - other->get_port(other)) < 0) - { - udp_enc = NULL; - } - + cache_iface(this, child_sa->get_reqid(child_sa), iface); } else { - if (asprintf(&udp_enc, "") < 0) - { - udp_enc = NULL; - } - + iface = NULL; } + } + else + { + iface = uncache_iface(this, child_sa->get_reqid(child_sa)); + } + push_env(envp, countof(envp), "PLUTO_INTERFACE=%s", + iface ? iface : "unknown"); + push_env(envp, countof(envp), "PLUTO_REQID=%u", + child_sa->get_reqid(child_sa)); + push_env(envp, countof(envp), "PLUTO_PROTO=%s", + child_sa->get_protocol(child_sa) == PROTO_ESP ? "esp" : "ah"); + push_env(envp, countof(envp), "PLUTO_UNIQUEID=%u", + ike_sa->get_unique_id(ike_sa)); + push_env(envp, countof(envp), "PLUTO_ME=%H", me); + push_env(envp, countof(envp), "PLUTO_MY_ID=%Y", ike_sa->get_my_id(ike_sa)); + if (my_ts->to_subnet(my_ts, &host, &mask)) + { + push_env(envp, countof(envp), "PLUTO_MY_CLIENT=%+H/%u", host, mask); + host->destroy(host); + } + push_env(envp, countof(envp), "PLUTO_MY_PORT=%u", + get_port(my_ts, other_ts, TRUE)); + push_env(envp, countof(envp), "PLUTO_MY_PROTOCOL=%u", + my_ts->get_protocol(my_ts)); + push_env(envp, countof(envp), "PLUTO_PEER=%H", other); + push_env(envp, countof(envp), "PLUTO_PEER_ID=%Y", + ike_sa->get_other_id(ike_sa)); + if (other_ts->to_subnet(other_ts, &host, &mask)) + { + push_env(envp, countof(envp), "PLUTO_PEER_CLIENT=%+H/%u", host, mask); + host->destroy(host); + } + push_env(envp, countof(envp), "PLUTO_PEER_PORT=%u", + get_port(my_ts, other_ts, FALSE)); + push_env(envp, countof(envp), "PLUTO_PEER_PROTOCOL=%u", + other_ts->get_protocol(other_ts)); + if (ike_sa->has_condition(ike_sa, COND_EAP_AUTHENTICATED) || + ike_sa->has_condition(ike_sa, COND_XAUTH_AUTHENTICATED)) + { + push_env(envp, countof(envp), "PLUTO_XAUTH_ID=%Y", + ike_sa->get_other_eap_id(ike_sa)); + } + push_vip_env(this, ike_sa, envp, countof(envp)); + mark = config->get_mark(config, TRUE); + if (mark.value) + { + push_env(envp, countof(envp), "PLUTO_MARK_IN=%u/0x%08x", + mark.value, mark.mask); + } + mark = config->get_mark(config, FALSE); + if (mark.value) + { + push_env(envp, countof(envp), "PLUTO_MARK_OUT=%u/0x%08x", + mark.value, mark.mask); + } + if (ike_sa->has_condition(ike_sa, COND_NAT_ANY)) + { + push_env(envp, countof(envp), "PLUTO_UDP_ENC=%u", + other->get_port(other)); + } + if (child_sa->get_ipcomp(child_sa) != IPCOMP_NONE) + { + push_env(envp, countof(envp), "PLUTO_IPCOMP=1"); + } + push_dns_env(this, ike_sa, envp, countof(envp)); + if (config->get_hostaccess(config)) + { + push_env(envp, countof(envp), "PLUTO_HOST_ACCESS=1"); + } - if (ike_sa->has_condition(ike_sa, COND_EAP_AUTHENTICATED) || - ike_sa->has_condition(ike_sa, COND_XAUTH_AUTHENTICATED)) - { - if (asprintf(&xauth, "PLUTO_XAUTH_ID='%Y' ", - ike_sa->get_other_eap_id(ike_sa)) < 0) - { - xauth = NULL; - } - } - else + process = process_start_shell(envp, NULL, &out, NULL, "2>&1 %s", + config->get_updown(config)); + if (process) + { + shell = fdopen(out, "r"); + if (shell) { - if (asprintf(&xauth, "") < 0) + while (TRUE) { - xauth = NULL; - } - } + char resp[128]; - if (up) - { - if (hydra->kernel_interface->get_interface(hydra->kernel_interface, - me, &iface)) - { - cache_iface(this, child_sa->get_reqid(child_sa), iface); - } - else - { - iface = NULL; + if (fgets(resp, sizeof(resp), shell) == NULL) + { + if (ferror(shell)) + { + DBG1(DBG_CHD, "error reading from updown script"); + } + break; + } + else + { + char *e = resp + strlen(resp); + if (e > resp && e[-1] == '\n') + { + e[-1] = '\0'; + } + DBG1(DBG_CHD, "updown: %s", resp); + } } + fclose(shell); } else { - iface = uncache_iface(this, child_sa->get_reqid(child_sa)); + close(out); } + process->wait(process, NULL); + } + free(iface); + free_env(envp); +} - dns = make_dns_vars(this, ike_sa); - - /* check for IPComp */ - use_ipcomp = child_sa->get_ipcomp(child_sa) != IPCOMP_NONE; - - /* determine IPv4/IPv6 and client/host situation */ - is_host = my_ts->is_host(my_ts, me); - is_ipv6 = is_host ? (me->get_family(me) == AF_INET6) : - (my_ts->get_type(my_ts) == TS_IPV6_ADDR_RANGE); - - /* build the command with all env variables. - */ - snprintf(command, sizeof(command), - "2>&1 " - "PLUTO_VERSION='1.1' " - "PLUTO_VERB='%s%s%s' " - "PLUTO_CONNECTION='%s' " - "PLUTO_INTERFACE='%s' " - "PLUTO_REQID='%u' " - "PLUTO_PROTO='%s' " - "PLUTO_UNIQUEID='%u' " - "PLUTO_ME='%H' " - "PLUTO_MY_ID='%Y' " - "PLUTO_MY_CLIENT='%+H/%u' " - "PLUTO_MY_PORT='%u' " - "PLUTO_MY_PROTOCOL='%u' " - "PLUTO_PEER='%H' " - "PLUTO_PEER_ID='%Y' " - "PLUTO_PEER_CLIENT='%+H/%u' " - "PLUTO_PEER_PORT='%u' " - "PLUTO_PEER_PROTOCOL='%u' " - "%s" - "%s" - "%s" - "%s" - "%s" - "%s" - "%s" - "%s" - "%s", - up ? "up" : "down", - is_host ? "-host" : "-client", - is_ipv6 ? "-v6" : "", - config->get_name(config), - iface ? iface : "unknown", - child_sa->get_reqid(child_sa), - child_sa->get_protocol(child_sa) == PROTO_ESP ? "esp" : "ah", - ike_sa->get_unique_id(ike_sa), - me, ike_sa->get_my_id(ike_sa), - my_client, my_client_mask, - get_port(my_ts, other_ts, TRUE), - my_ts->get_protocol(my_ts), - other, ike_sa->get_other_id(ike_sa), - other_client, other_client_mask, - get_port(my_ts, other_ts, FALSE), - other_ts->get_protocol(other_ts), - xauth, - virtual_ip, - mark_in, - mark_out, - udp_enc, - use_ipcomp ? "PLUTO_IPCOMP='1' " : "", - config->get_hostaccess(config) ? "PLUTO_HOST_ACCESS='1' " : "", - dns, - script); - my_client->destroy(my_client); - other_client->destroy(other_client); - free(virtual_ip); - free(mark_in); - free(mark_out); - free(udp_enc); - free(dns); - free(iface); - free(xauth); - - DBG3(DBG_CHD, "running updown script: %s", command); - shell = popen(command, "r"); - - if (shell == NULL) - { - DBG1(DBG_CHD, "could not execute updown script '%s'", script); - return TRUE; - } +METHOD(listener_t, child_updown, bool, + private_updown_listener_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa, + bool up) +{ + traffic_selector_t *my_ts, *other_ts; + enumerator_t *enumerator; + child_cfg_t *config; - while (TRUE) + config = child_sa->get_config(child_sa); + if (config->get_updown(config)) + { + enumerator = child_sa->create_policy_enumerator(child_sa); + while (enumerator->enumerate(enumerator, &my_ts, &other_ts)) { - char resp[128]; - - if (fgets(resp, sizeof(resp), shell) == NULL) - { - if (ferror(shell)) - { - DBG1(DBG_CHD, "error reading output from updown script"); - } - break; - } - else - { - char *e = resp + strlen(resp); - if (e > resp && e[-1] == '\n') - { /* trim trailing '\n' */ - e[-1] = '\0'; - } - DBG1(DBG_CHD, "updown: %s", resp); - } + invoke_once(this, ike_sa, child_sa, config, up, my_ts, other_ts); } - pclose(shell); + enumerator->destroy(enumerator); } - enumerator->destroy(enumerator); return TRUE; } diff --git a/src/libstrongswan/Android.mk b/src/libstrongswan/Android.mk index 3ddd42f11..9b775f9b3 100644 --- a/src/libstrongswan/Android.mk +++ b/src/libstrongswan/Android.mk @@ -37,7 +37,7 @@ selectors/traffic_selector.c settings/settings.c settings/settings_types.c \ settings/settings_parser.c settings/settings_lexer.c \ utils/utils.c utils/chunk.c utils/debug.c utils/enum.c utils/identification.c \ utils/lexparser.c utils/optionsfrom.c utils/capabilities.c utils/backtrace.c \ -utils/parser_helper.c utils/test.c utils/utils/strerror.c +utils/parser_helper.c utils/test.c utils/process.c utils/utils/strerror.c libstrongswan_la_SOURCES += \ threading/thread.c \ diff --git a/src/libstrongswan/Makefile.am b/src/libstrongswan/Makefile.am index 3fb57de5a..0083ffe6b 100644 --- a/src/libstrongswan/Makefile.am +++ b/src/libstrongswan/Makefile.am @@ -35,7 +35,7 @@ selectors/traffic_selector.c settings/settings.c settings/settings_types.c \ settings/settings_parser.y settings/settings_lexer.l \ utils/utils.c utils/chunk.c utils/debug.c utils/enum.c utils/identification.c \ utils/lexparser.c utils/optionsfrom.c utils/capabilities.c utils/backtrace.c \ -utils/parser_helper.c utils/test.c utils/utils/strerror.c +utils/parser_helper.c utils/test.c utils/process.c utils/utils/strerror.c if !USE_WINDOWS libstrongswan_la_SOURCES += \ @@ -102,7 +102,7 @@ utils/lexparser.h utils/optionsfrom.h utils/capabilities.h utils/backtrace.h \ utils/leak_detective.h utils/printf_hook/printf_hook.h \ utils/printf_hook/printf_hook_vstr.h utils/printf_hook/printf_hook_builtin.h \ utils/parser_helper.h utils/test.h utils/integrity_checker.h utils/windows.h \ -utils/utils/strerror.h +utils/process.h utils/utils/strerror.h endif library.lo : $(top_builddir)/config.status diff --git a/src/libstrongswan/tests/Makefile.am b/src/libstrongswan/tests/Makefile.am index e8e8090f3..7ecba19da 100644 --- a/src/libstrongswan/tests/Makefile.am +++ b/src/libstrongswan/tests/Makefile.am @@ -30,6 +30,7 @@ tests_SOURCES = tests.h tests.c \ suites/test_hashtable.c \ suites/test_identification.c \ suites/test_threading.c \ + suites/test_process.c \ suites/test_watcher.c \ suites/test_stream.c \ suites/test_fetch_http.c \ diff --git a/src/libstrongswan/tests/suites/test_process.c b/src/libstrongswan/tests/suites/test_process.c new file mode 100644 index 000000000..c22c47294 --- /dev/null +++ b/src/libstrongswan/tests/suites/test_process.c @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 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 "test_suite.h" + +#include <unistd.h> + +#include <utils/process.h> + +START_TEST(test_retval_true) +{ + process_t *process; + char *argv[] = { +#ifdef WIN32 + "C:\\Windows\\system32\\cmd.exe", + "/C", + "exit 0", +#else + "/bin/true", +#endif + NULL + }; + int retval; + + process = process_start(argv, NULL, NULL, NULL, NULL, TRUE); + ck_assert(process != NULL); + ck_assert(process->wait(process, &retval)); + ck_assert_int_eq(retval, 0); +} +END_TEST + +START_TEST(test_retval_false) +{ + process_t *process; + char *argv[] = { +#ifdef WIN32 + "C:\\Windows\\system32\\cmd.exe", + "/C", + "exit 1", +#else + "/bin/false", +#endif + NULL + }; + int retval; + + process = process_start(argv, NULL, NULL, NULL, NULL, TRUE); + ck_assert(process != NULL); + ck_assert(process->wait(process, &retval)); + ck_assert(retval != 0); +} +END_TEST + +START_TEST(test_not_found) +{ + process_t *process; + char *argv[] = { + "/bin/does-not-exist", + NULL + }; + + process = process_start(argv, NULL, NULL, NULL, NULL, TRUE); + /* both is acceptable behavior */ + ck_assert(process == NULL || !process->wait(process, NULL)); +} +END_TEST + +START_TEST(test_echo) +{ + process_t *process; + char *argv[] = { +#ifdef WIN32 + "C:\\Windows\\system32\\more.com", +#else + "/bin/cat", +#endif + NULL + }; + int retval, in, out; + char *msg = "test"; + char buf[strlen(msg) + 1]; + + memset(buf, 0, strlen(msg) + 1); + + process = process_start(argv, NULL, &in, &out, NULL, TRUE); + ck_assert(process != NULL); + ck_assert_int_eq(write(in, msg, strlen(msg)), strlen(msg)); + ck_assert(close(in) == 0); + ck_assert_int_eq(read(out, buf, strlen(msg) + 1), strlen(msg)); + ck_assert_str_eq(buf, msg); + ck_assert(close(out) == 0); + ck_assert(process->wait(process, &retval)); + ck_assert_int_eq(retval, 0); +} +END_TEST + +START_TEST(test_echo_err) +{ + process_t *process; + char *argv[] = { +#ifdef WIN32 + "C:\\Windows\\system32\\cmd.exe", + "/C", + "1>&2 C:\\Windows\\system32\\more.com", +#else + "/bin/sh", + "-c", + "1>&2 /bin/cat", +#endif + NULL + }; + int retval, in, err; + char *msg = "a longer test message"; + char buf[strlen(msg) + 1]; + + memset(buf, 0, strlen(msg) + 1); + + process = process_start(argv, NULL, &in, NULL, &err, TRUE); + ck_assert(process != NULL); + ck_assert_int_eq(write(in, msg, strlen(msg)), strlen(msg)); + ck_assert(close(in) == 0); + ck_assert_int_eq(read(err, buf, strlen(msg) + 1), strlen(msg)); + ck_assert_str_eq(buf, msg); + ck_assert(close(err) == 0); + ck_assert(process->wait(process, &retval)); + ck_assert_int_eq(retval, 0); +} +END_TEST + +START_TEST(test_env) +{ + process_t *process; + char *argv[] = { +#ifdef WIN32 + "C:\\Windows\\system32\\cmd.exe", + "/C", + "echo %A% %B%", +#else + "/bin/sh", + "-c", + "echo -n $A $B", +#endif + NULL + }; + char *envp[] = { + "A=atest", + "B=bstring", + NULL + }; + int retval, out; + char buf[64] = {}; + + process = process_start(argv, envp, NULL, &out, NULL, TRUE); + ck_assert(process != NULL); + ck_assert(read(out, buf, sizeof(buf)) > 0); +#ifdef WIN32 + ck_assert_str_eq(buf, "atest bstring\r\n"); +#else + ck_assert_str_eq(buf, "atest bstring"); +#endif + ck_assert(close(out) == 0); + ck_assert(process->wait(process, &retval)); + ck_assert_int_eq(retval, 0); +} +END_TEST + +START_TEST(test_shell) +{ + process_t *process; + int retval; + + process = process_start_shell(NULL, NULL, NULL, NULL, "exit %d", 3); + ck_assert(process != NULL); + ck_assert(process->wait(process, &retval)); + ck_assert_int_eq(retval, 3); +} +END_TEST + +Suite *process_suite_create() +{ + Suite *s; + TCase *tc; + + s = suite_create("process"); + + tc = tcase_create("return values"); + tcase_add_test(tc, test_retval_true); + tcase_add_test(tc, test_retval_false); + suite_add_tcase(s, tc); + + tc = tcase_create("not found"); + tcase_add_test(tc, test_not_found); + suite_add_tcase(s, tc); + + tc = tcase_create("echo"); + tcase_add_test(tc, test_echo); + tcase_add_test(tc, test_echo_err); + suite_add_tcase(s, tc); + + tc = tcase_create("env"); + tcase_add_test(tc, test_env); + suite_add_tcase(s, tc); + + tc = tcase_create("shell"); + tcase_add_test(tc, test_shell); + suite_add_tcase(s, tc); + + return s; +} diff --git a/src/libstrongswan/tests/tests.h b/src/libstrongswan/tests/tests.h index ab0f642e4..586227800 100644 --- a/src/libstrongswan/tests/tests.h +++ b/src/libstrongswan/tests/tests.h @@ -24,6 +24,7 @@ TEST_SUITE(hashtable_suite_create) TEST_SUITE(array_suite_create) TEST_SUITE(identification_suite_create) TEST_SUITE(threading_suite_create) +TEST_SUITE(process_suite_create) TEST_SUITE(watcher_suite_create) TEST_SUITE(stream_suite_create) TEST_SUITE(utils_suite_create) diff --git a/src/libstrongswan/utils/process.c b/src/libstrongswan/utils/process.c new file mode 100644 index 000000000..233429447 --- /dev/null +++ b/src/libstrongswan/utils/process.c @@ -0,0 +1,591 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 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. + */ + +/* vasprintf() */ +#define _GNU_SOURCE +#include "process.h" + +#include <library.h> +#include <utils/debug.h> + +#include <fcntl.h> +#include <stdio.h> +#include <stdarg.h> + +typedef struct private_process_t private_process_t; + +/** + * Ends of a pipe() + */ +enum { + PIPE_READ = 0, + PIPE_WRITE = 1, + PIPE_ENDS, +}; + +#ifndef WIN32 + +#include <unistd.h> +#include <errno.h> +#include <sys/wait.h> + +/** + * Private data of an process_t object. + */ +struct private_process_t { + + /** + * Public process_t interface. + */ + process_t public; + + /** + * child stdin pipe + */ + int in[PIPE_ENDS]; + + /** + * child stdout pipe + */ + int out[PIPE_ENDS]; + + /** + * child stderr pipe + */ + int err[PIPE_ENDS]; + + /** + * child process + */ + int pid; +}; + +/** + * Close a file descriptor if it is not -1 + */ +static void close_if(int *fd) +{ + if (*fd != -1) + { + close(*fd); + *fd = -1; + } +} + +/** + * Destroy a process structure, close all pipes + */ +static void process_destroy(private_process_t *this) +{ + close_if(&this->in[PIPE_READ]); + close_if(&this->in[PIPE_WRITE]); + close_if(&this->out[PIPE_READ]); + close_if(&this->out[PIPE_WRITE]); + close_if(&this->err[PIPE_READ]); + close_if(&this->err[PIPE_WRITE]); + free(this); +} + +METHOD(process_t, wait_, bool, + private_process_t *this, int *code) +{ + int status, ret; + + ret = waitpid(this->pid, &status, 0); + process_destroy(this); + if (ret == -1) + { + return FALSE; + } + if (!WIFEXITED(status)) + { + return FALSE; + } + if (code) + { + *code = WEXITSTATUS(status); + } + return TRUE; +} + +/** + * See header + */ +process_t* process_start(char *const argv[], char *const envp[], + int *in, int *out, int *err, bool close_all) +{ + private_process_t *this; + char *empty[] = { NULL }; + + INIT(this, + .public = { + .wait = _wait_, + }, + .in = { -1, -1 }, + .out = { -1, -1 }, + .err = { -1, -1 }, + ); + + if (in && pipe(this->in) != 0) + { + DBG1(DBG_LIB, "creating stdin pipe failed: %s", strerror(errno)); + process_destroy(this); + return NULL; + } + if (out && pipe(this->out) != 0) + { + DBG1(DBG_LIB, "creating stdout pipe failed: %s", strerror(errno)); + process_destroy(this); + return NULL; + } + if (err && pipe(this->err) != 0) + { + DBG1(DBG_LIB, "creating stderr pipe failed: %s", strerror(errno)); + process_destroy(this); + return NULL; + } + + this->pid = fork(); + switch (this->pid) + { + case -1: + DBG1(DBG_LIB, "forking process failed: %s", strerror(errno)); + process_destroy(this); + return NULL; + case 0: + /* child */ + close_if(&this->in[PIPE_WRITE]); + close_if(&this->out[PIPE_READ]); + close_if(&this->err[PIPE_READ]); + if (this->in[PIPE_READ] != -1) + { + if (dup2(this->in[PIPE_READ], 0) == -1) + { + raise(SIGKILL); + } + } + if (this->out[PIPE_WRITE] != -1) + { + if (dup2(this->out[PIPE_WRITE], 1) == -1) + { + raise(SIGKILL); + } + } + if (this->err[PIPE_WRITE] != -1) + { + if (dup2(this->err[PIPE_WRITE], 2) == -1) + { + raise(SIGKILL); + } + } + if (close_all) + { + closefrom(3); + } + if (execve(argv[0], argv, envp ?: empty) == -1) + { + raise(SIGKILL); + } + /* not reached */ + default: + /* parent */ + close_if(&this->in[PIPE_READ]); + close_if(&this->out[PIPE_WRITE]); + close_if(&this->err[PIPE_WRITE]); + if (in) + { + *in = this->in[PIPE_WRITE]; + this->in[PIPE_WRITE] = -1; + } + if (out) + { + *out = this->out[PIPE_READ]; + this->out[PIPE_READ] = -1; + } + if (err) + { + *err = this->err[PIPE_READ]; + this->err[PIPE_READ] = -1; + } + return &this->public; + } +} + +/** + * See header + */ +process_t* process_start_shell(char *const envp[], int *in, int *out, int *err, + char *fmt, ...) +{ + char *argv[] = { + "/bin/sh", + "-c", + NULL, + NULL + }; + process_t *process; + va_list args; + int len; + + va_start(args, fmt); + len = vasprintf(&argv[2], fmt, args); + va_end(args); + if (len < 0) + { + return NULL; + } + + process = process_start(argv, envp, in, out, err, TRUE); + free(argv[2]); + return process; +} + +#else /* WIN32 */ + +/** + * Private data of an process_t object. + */ +struct private_process_t { + + /** + * Public process_t interface. + */ + process_t public; + + /** + * child stdin pipe + */ + HANDLE in[PIPE_ENDS]; + + /** + * child stdout pipe + */ + HANDLE out[PIPE_ENDS]; + + /** + * child stderr pipe + */ + HANDLE err[PIPE_ENDS]; + + /** + * child process information + */ + PROCESS_INFORMATION pi; +}; + +/** + * Clean up state associated to child process + */ +static void process_destroy(private_process_t *this) +{ + if (this->in[PIPE_READ]) + { + CloseHandle(this->in[PIPE_READ]); + } + if (this->in[PIPE_WRITE]) + { + CloseHandle(this->in[PIPE_WRITE]); + } + if (this->out[PIPE_READ]) + { + CloseHandle(this->out[PIPE_READ]); + } + if (this->out[PIPE_WRITE]) + { + CloseHandle(this->out[PIPE_WRITE]); + } + if (this->err[PIPE_READ]) + { + CloseHandle(this->err[PIPE_READ]); + } + if (this->err[PIPE_WRITE]) + { + CloseHandle(this->err[PIPE_WRITE]); + } + if (this->pi.hProcess) + { + CloseHandle(this->pi.hProcess); + CloseHandle(this->pi.hThread); + } + free(this); +} + +METHOD(process_t, wait_, bool, + private_process_t *this, int *code) +{ + DWORD ec; + + if (WaitForSingleObject(this->pi.hProcess, INFINITE) != WAIT_OBJECT_0) + { + DBG1(DBG_LIB, "waiting for child process failed: 0x%08x", + GetLastError()); + process_destroy(this); + return FALSE; + } + if (code) + { + if (!GetExitCodeProcess(this->pi.hProcess, &ec)) + { + DBG1(DBG_LIB, "getting child process exit code failed: 0x%08x", + GetLastError()); + process_destroy(this); + return FALSE; + } + *code = ec; + } + process_destroy(this); + return TRUE; +} + +/** + * Append a command line argument to buf, optionally quoted + */ +static void append_arg(char *buf, u_int len, char *arg, char *quote) +{ + char *space = ""; + int current; + + current = strlen(buf); + if (current) + { + space = " "; + } + snprintf(buf + current, len - current, "%s%s%s%s", space, quote, arg, quote); +} + +/** + * Append a null-terminate env string to buf + */ +static void append_env(char *buf, u_int len, char *env) +{ + char *pos = buf; + int current; + + while (TRUE) + { + pos += strlen(pos); + if (!pos[1]) + { + if (pos == buf) + { + current = 0; + } + else + { + current = pos - buf + 1; + } + snprintf(buf + current, len - current, "%s", env); + break; + } + pos++; + } +} + +/** + * See header + */ +process_t* process_start(char *const argv[], char *const envp[], + int *in, int *out, int *err, bool close_all) +{ + private_process_t *this; + char arg[32768], env[32768]; + SECURITY_ATTRIBUTES sa = { + .nLength = sizeof(SECURITY_ATTRIBUTES), + .bInheritHandle = TRUE, + }; + STARTUPINFO sui = { + .cb = sizeof(STARTUPINFO), + }; + int i; + + memset(arg, 0, sizeof(arg)); + memset(env, 0, sizeof(env)); + + for (i = 0; argv[i]; i++) + { + if (!strchr(argv[i], ' ')) + { /* no spaces, fine for appending */ + append_arg(arg, sizeof(arg) - 1, argv[i], ""); + } + else if (argv[i][0] == '"' && + argv[i][strlen(argv[i]) - 1] == '"' && + strchr(argv[i] + 1, '"') == argv[i] + strlen(argv[i]) - 1) + { /* already properly quoted */ + append_arg(arg, sizeof(arg) - 1, argv[i], ""); + } + else if (strchr(argv[i], ' ') && !strchr(argv[i], '"')) + { /* spaces, but no quotes; append quoted */ + append_arg(arg, sizeof(arg) - 1, argv[i], "\""); + } + else + { + DBG1(DBG_LIB, "invalid command line argument: %s", argv[i]); + return NULL; + } + } + if (envp) + { + for (i = 0; envp[i]; i++) + { + append_env(env, sizeof(env) - 1, envp[i]); + } + } + + INIT(this, + .public = { + .wait = _wait_, + }, + ); + + if (in) + { + sui.dwFlags = STARTF_USESTDHANDLES; + if (!CreatePipe(&this->in[PIPE_READ], &this->in[PIPE_WRITE], &sa, 0)) + { + process_destroy(this); + return NULL; + } + if (!SetHandleInformation(this->in[PIPE_WRITE], HANDLE_FLAG_INHERIT, 0)) + { + process_destroy(this); + return NULL; + } + sui.hStdInput = this->in[PIPE_READ]; + *in = _open_osfhandle((uintptr_t)this->in[PIPE_WRITE], 0); + if (*in == -1) + { + process_destroy(this); + return NULL; + } + } + if (out) + { + sui.dwFlags = STARTF_USESTDHANDLES; + if (!CreatePipe(&this->out[PIPE_READ], &this->out[PIPE_WRITE], &sa, 0)) + { + process_destroy(this); + return NULL; + } + if (!SetHandleInformation(this->out[PIPE_READ], HANDLE_FLAG_INHERIT, 0)) + { + process_destroy(this); + return NULL; + } + sui.hStdOutput = this->out[PIPE_WRITE]; + *out = _open_osfhandle((uintptr_t)this->out[PIPE_READ], 0); + if (*out == -1) + { + process_destroy(this); + return NULL; + } + } + if (err) + { + sui.dwFlags = STARTF_USESTDHANDLES; + if (!CreatePipe(&this->err[PIPE_READ], &this->err[PIPE_WRITE], &sa, 0)) + { + process_destroy(this); + return NULL; + } + if (!SetHandleInformation(this->err[PIPE_READ], HANDLE_FLAG_INHERIT, 0)) + { + process_destroy(this); + return NULL; + } + sui.hStdError = this->err[PIPE_WRITE]; + *err = _open_osfhandle((uintptr_t)this->err[PIPE_READ], 0); + if (*err == -1) + { + process_destroy(this); + return NULL; + } + } + + if (!CreateProcess(argv[0], arg, NULL, NULL, TRUE, + NORMAL_PRIORITY_CLASS, env, NULL, &sui, &this->pi)) + { + DBG1(DBG_LIB, "creating process '%s' failed: 0x%08x", + argv[0], GetLastError()); + process_destroy(this); + return NULL; + } + + /* close child process end of pipes */ + if (this->in[PIPE_READ]) + { + CloseHandle(this->in[PIPE_READ]); + this->in[PIPE_READ] = NULL; + } + if (this->out[PIPE_WRITE]) + { + CloseHandle(this->out[PIPE_WRITE]); + this->out[PIPE_WRITE] = NULL; + } + if (this->err[PIPE_WRITE]) + { + CloseHandle(this->err[PIPE_WRITE]); + this->err[PIPE_WRITE] = NULL; + } + /* our side gets closed over the osf_handle closed by caller */ + this->in[PIPE_WRITE] = NULL; + this->out[PIPE_READ] = NULL; + this->err[PIPE_READ] = NULL; + return &this->public; +} + +/** + * See header + */ +process_t* process_start_shell(char *const envp[], int *in, int *out, int *err, + char *fmt, ...) +{ + char path[MAX_PATH], *exe = "system32\\cmd.exe"; + char *argv[] = { + path, + "/C", + NULL, + NULL + }; + process_t *process; + va_list args; + int len; + + len = GetSystemWindowsDirectory(path, sizeof(path)); + if (len == 0 || len >= sizeof(path) - strlen(exe)) + { + DBG1(DBG_LIB, "resolving Windows directory failed: 0x%08x", + GetLastError()); + return NULL; + } + if (path[len + 1] != '\\') + { + strncat(path, "\\", sizeof(path) - len++); + } + strncat(path, exe, sizeof(path) - len); + + va_start(args, fmt); + len = vasprintf(&argv[2], fmt, args); + va_end(args); + if (len < 0) + { + return NULL; + } + + process = process_start(argv, envp, in, out, err, TRUE); + free(argv[2]); + return process; +} + +#endif /* WIN32 */ diff --git a/src/libstrongswan/utils/process.h b/src/libstrongswan/utils/process.h new file mode 100644 index 000000000..81719201c --- /dev/null +++ b/src/libstrongswan/utils/process.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 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. + */ + +/** + * @defgroup process process + * @{ @ingroup utils + */ + +#ifndef PROCESS_H_ +#define PROCESS_H_ + +#include <utils/utils.h> + +typedef struct process_t process_t; + +/** + * Child process spawning abstraction + */ +struct process_t { + + /** + * Wait for a started process to terminate. + * + * The process object gets destroyed by this call, regardless of the + * return value. + * + * The returned code is the exit code, not the status returned by waitpid(). + * If the program could not be executed or has terminated abnormally + * (by signals etc.), FALSE is returned. + * + * @param code process exit code, set only if TRUE returned + * @return TRUE if program exited normally through exit() + */ + bool (*wait)(process_t *this, int *code); +}; + +/** + * Spawn a child process with redirected I/O. + * + * Forks the current process, optionally redirects stdin/out/err to the current + * process, and executes the provided program with arguments. + * + * The process to execute is specified as argv[0], followed by the process + * arguments, followed by NULL. envp[] has a NULL terminated list of arguments + * to invoke the process with. + * + * If any of in/out/err is given, stdin/out/err from the child process get + * connected over pipe()s to the caller. If close_all is TRUE, all other + * open file descriptors get closed, regardless of any CLOEXEC setting. + * + * A caller must close all of the returned file descriptors to avoid file + * descriptor leaks. + * + * A non-NULL return value does not guarantee that the process has been + * invoked successfully. + * + * @param argv NULL terminated process arguments, with argv[0] as program + * @param envp NULL terminated list of environment variables + * @param in pipe fd returned for redirecting data to child stdin + * @param out pipe fd returned to redirect child stdout data to + * @param err pipe fd returned to redirect child stderr data to + * @param close_all close all open file descriptors above 2 before execve() + * @return process, NULL on failure + */ +process_t* process_start(char *const argv[], char *const envp[], + int *in, int *out, int *err, bool close_all); + +/** + * Spawn a command in a shell child process. + * + * Same as process_start(), but passes a single command to a shell, such as + * "sh -c". See process_start() for I/O redirection notes. + * + * @param envp NULL terminated list of environment variables + * @param in pipe fd returned for redirecting data to child stdin + * @param out pipe fd returned to redirect child stdout data to + * @param err pipe fd returned to redirect child stderr data to + * @param fmt printf format string for command + * @param ... arguments for fmt + * @return process, NULL on failure + */ +process_t* process_start_shell(char *const envp[], int *in, int *out, int *err, + char *fmt, ...); + +#endif /** PROCESS_H_ @}*/ |