]> git.ipfire.org Git - people/ms/strongswan.git/commitdiff
Merge branch 'ext-auth'
authorMartin Willi <martin@revosec.ch>
Mon, 6 Oct 2014 16:31:14 +0000 (18:31 +0200)
committerMartin Willi <martin@revosec.ch>
Mon, 6 Oct 2014 16:31:14 +0000 (18:31 +0200)
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.

18 files changed:
NEWS
conf/Makefile.am
conf/plugins/ext-auth.opt [new file with mode: 0644]
configure.ac
src/libcharon/Makefile.am
src/libcharon/plugins/ext_auth/Makefile.am [new file with mode: 0644]
src/libcharon/plugins/ext_auth/ext_auth_listener.c [new file with mode: 0644]
src/libcharon/plugins/ext_auth/ext_auth_listener.h [new file with mode: 0644]
src/libcharon/plugins/ext_auth/ext_auth_plugin.c [new file with mode: 0644]
src/libcharon/plugins/ext_auth/ext_auth_plugin.h [new file with mode: 0644]
src/libcharon/plugins/updown/updown_listener.c
src/libstrongswan/Android.mk
src/libstrongswan/Makefile.am
src/libstrongswan/tests/Makefile.am
src/libstrongswan/tests/suites/test_process.c [new file with mode: 0644]
src/libstrongswan/tests/tests.h
src/libstrongswan/utils/process.c [new file with mode: 0644]
src/libstrongswan/utils/process.h [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index 4720448e6d41b7f24df9311fd2c5c3102a724a2f..395486dcf0c4bf28af0efe9a3b182aa65eb10d51 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -11,6 +11,9 @@ strongswan-5.2.1
   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
 ----------------
index ee9ce72eaf018fe61c1ebb8f42233a6e1d40cfb3..e5077391aaa803dd1332ddfc0c94ce3391df8fe4 100644 (file)
@@ -45,6 +45,7 @@ plugins = \
        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 \
diff --git a/conf/plugins/ext-auth.opt b/conf/plugins/ext-auth.opt
new file mode 100644 (file)
index 0000000..bf127b9
--- /dev/null
@@ -0,0 +1,15 @@
+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.
index 85f7cb35c9770842ff70ee4d46aaa1e6055a817c..100e29afcd4800173b000ed55085808e14350cab 100644 (file)
@@ -189,6 +189,7 @@ ARG_ENABL_SET([eap-peap],       [enable EAP PEAP authentication module.])
 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.])
@@ -1285,6 +1286,7 @@ ADD_PLUGIN([android-dns],          [c charon])
 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])
@@ -1396,6 +1398,7 @@ AM_CONDITIONAL(USE_KERNEL_LIBIPSEC, test x$kernel_libipsec = xtrue)
 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)
@@ -1695,6 +1698,7 @@ AC_CONFIG_FILES([
        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
index e81c424050744454752fb0b2cd23dafe0bc27429..0eaabf57f32c6ad729058e12ee9dffa094fef513 100644 (file)
@@ -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 (file)
index 0000000..d51ea88
--- /dev/null
@@ -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 (file)
index 0000000..06cec20
--- /dev/null
@@ -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 (file)
index 0000000..3fec830
--- /dev/null
@@ -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 (file)
index 0000000..b3698c7
--- /dev/null
@@ -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 (file)
index 0000000..1288e24
--- /dev/null
@@ -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_ @}*/
index 200f298a133773b24baa1510102c5e0e7fae57f3..1d15cc55ef2d9d19c96da4445742af26001d60cb 100644 (file)
 
 #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;
 }
 
index 3ddd42f116c846493b7edf019500e4a932fbdf60..9b775f9b3a1b9e4adb331da87594cf557b7b5baa 100644 (file)
@@ -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 \
index 3fb57de5a0067b406de9f55e9930da2479c7c2ff..0083ffe6b0f010fd5bedd4f80945e871c5c12c3b 100644 (file)
@@ -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
index e8e8090f3a10174101df20578403bf88daf62185..7ecba19dacbfaa1873fb1c16c79e0e828249773f 100644 (file)
@@ -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 (file)
index 0000000..c22c472
--- /dev/null
@@ -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;
+}
index ab0f642e40ae8edad590dea30ba4dea3f2ba1376..586227800785dc4ba6600dc684fb36309b766ec3 100644 (file)
@@ -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 (file)
index 0000000..2334294
--- /dev/null
@@ -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 (file)
index 0000000..8171920
--- /dev/null
@@ -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_ @}*/