and IETF/Installed Packages attributes can be processed incrementally on a
per segment basis.
+- The new ext-auth plugin calls an external script to implement custom IKE_SA
+ authorization logic, courtesy of Vyronas Tsingaras.
+
strongswan-5.2.0
----------------
plugins/eap-tnc.opt \
plugins/eap-ttls.opt \
plugins/error-notify.opt \
+ plugins/ext-auth.opt \
plugins/gcrypt.opt \
plugins/ha.opt \
plugins/imc-attestation.opt \
--- /dev/null
+charon.plugins.ext-auth.script =
+ Shell script to invoke for peer authorization.
+
+ Command to pass to the system shell for peer authorization. Authorization
+ is considered successful if the command executes normally with an exit code
+ of zero. For all other exit codes IKE_SA authorization is rejected.
+
+ The following environment variables get passed to the script:
+ _IKE_UNIQUE_ID_: The IKE_SA numerical unique identifier.
+ _IKE_NAME_: The peer configuration connection name.
+ _IKE_LOCAL_HOST_: Local IKE IP address.
+ _IKE_REMOTE_HOST_: Remote IKE IP address.
+ _IKE_LOCAL_ID_: Local IKE identity.
+ _IKE_REMOTE_ID_: Remote IKE identity.
+ _IKE_REMOTE_EAP_ID_: Remote EAP or XAuth identity, if used.
ARG_ENABL_SET([eap-tnc], [enable EAP TNC trusted network connect module.])
ARG_ENABL_SET([eap-dynamic], [enable dynamic EAP proxy module.])
ARG_ENABL_SET([eap-radius], [enable RADIUS proxy authentication module.])
+ARG_ENABL_SET([ext-auth], [enable plugin calling an external authorization script.])
ARG_ENABL_SET([ipseckey], [enable IPSECKEY authentication plugin.])
ARG_ENABL_SET([keychain], [enables OS X Keychain Services credential set.])
ARG_ENABL_SET([pkcs11], [enables the PKCS11 token support plugin.])
ADD_PLUGIN([android-log], [c charon])
ADD_PLUGIN([ha], [c charon])
ADD_PLUGIN([whitelist], [c charon])
+ADD_PLUGIN([ext-auth], [c charon])
ADD_PLUGIN([lookip], [c charon])
ADD_PLUGIN([error-notify], [c charon])
ADD_PLUGIN([certexpire], [c charon])
AM_CONDITIONAL(USE_KERNEL_WFP, test x$kernel_wfp = xtrue)
AM_CONDITIONAL(USE_KERNEL_IPH, test x$kernel_iph = xtrue)
AM_CONDITIONAL(USE_WHITELIST, test x$whitelist = xtrue)
+AM_CONDITIONAL(USE_EXT_AUTH, test x$ext_auth = xtrue)
AM_CONDITIONAL(USE_LOOKIP, test x$lookip = xtrue)
AM_CONDITIONAL(USE_ERROR_NOTIFY, test x$error_notify = xtrue)
AM_CONDITIONAL(USE_CERTEXPIRE, test x$certexpire = xtrue)
src/libcharon/plugins/kernel_wfp/Makefile
src/libcharon/plugins/kernel_iph/Makefile
src/libcharon/plugins/whitelist/Makefile
+ src/libcharon/plugins/ext_auth/Makefile
src/libcharon/plugins/lookip/Makefile
src/libcharon/plugins/error_notify/Makefile
src/libcharon/plugins/certexpire/Makefile
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
--- /dev/null
+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
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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_ @}*/
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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_ @}*/
#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>
}
/**
- * 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);
{
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);
}
/**
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;
}
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 \
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 += \
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
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 \
--- /dev/null
+/*
+ * 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;
+}
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)
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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_ @}*/