Merge branch 'ext-auth'

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.
This commit is contained in:
Martin Willi 2014-10-06 18:31:14 +02:00
commit 7d3c58a511
18 changed files with 1663 additions and 268 deletions

3
NEWS
View File

@ -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
----------------

View 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 \

15
conf/plugins/ext-auth.opt Normal file
View File

@ -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.

View 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

View 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

View File

@ -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

View File

@ -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;
}

View File

@ -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_ @}*/

View File

@ -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;
}

View File

@ -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_ @}*/

View File

@ -16,9 +16,11 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include "updown_listener.h"
#include <utils/process.h>
#include <hydra.h>
#include <daemon.h>
#include <config/child_cfg.h>
@ -97,53 +99,84 @@ static char* uncache_iface(private_updown_listener_t *this, u_int32_t reqid)
}
/**
* Create variables for handled DNS attributes
* Allocate and push a format string to the environment
*/
static char *make_dns_vars(private_updown_listener_t *this, ike_sa_t *ike_sa)
static bool push_env(char *envp[], u_int count, char *fmt, ...)
{
enumerator_t *enumerator;
host_t *host;
int v4 = 0, v6 = 0;
char total[512] = "", current[64];
int i = 0;
char *str;
va_list args;
if (!this->handler)
while (envp[i])
{
return strdup("");
}
enumerator = this->handler->create_dns_enumerator(this->handler,
ike_sa->get_unique_id(ike_sa));
while (enumerator->enumerate(enumerator, &host))
{
switch (host->get_family(host))
if (++i + 1 >= count)
{
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;
return FALSE;
}
strncat(total, current, sizeof(total) - strlen(total) - 1);
}
enumerator->destroy(enumerator);
return strdup(total);
va_start(args, fmt);
if (vasprintf(&str, fmt, args) >= 0)
{
envp[i] = str;
}
va_end(args);
return envp[i] != NULL;
}
/**
* Create variables for local virtual IPs
* Free all allocated environment strings
*/
static char *make_vip_vars(private_updown_listener_t *this, ike_sa_t *ike_sa)
static void free_env(char *envp[])
{
int i;
for (i = 0; envp[i]; i++)
{
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))
{
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;
}
}
enumerator->destroy(enumerator);
}
}
/**
* Push variables for local virtual IPs
*/
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,6 +223,164 @@ static u_int16_t get_port(traffic_selector_t *me,
return local ? me->get_from_port(me) : other->get_from_port(other);
}
/**
* 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)
{
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] = {};
me = ike_sa->get_my_host(ike_sa);
other = ike_sa->get_other_host(ike_sa);
push_env(envp, countof(envp), "PLUTO_VERSION=1.1");
is_host = my_ts->is_host(my_ts, me);
if (is_host)
{
is_ipv6 = me->get_family(me) == AF_INET6;
}
else
{
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))
{
cache_iface(this, child_sa->get_reqid(child_sa), iface);
}
else
{
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");
}
process = process_start_shell(envp, NULL, &out, NULL, "2>&1 %s",
config->get_updown(config));
if (process)
{
shell = fdopen(out, "r");
if (shell)
{
while (TRUE)
{
char resp[128];
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
{
close(out);
}
process->wait(process, NULL);
}
free(iface);
free_env(envp);
}
METHOD(listener_t, child_updown, bool,
private_updown_listener_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa,
bool up)
@ -203,233 +388,17 @@ METHOD(listener_t, child_updown, bool,
traffic_selector_t *my_ts, *other_ts;
enumerator_t *enumerator;
child_cfg_t *config;
host_t *me, *other;
char *script;
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)
if (config->get_updown(config))
{
return TRUE;
enumerator = child_sa->create_policy_enumerator(child_sa);
while (enumerator->enumerate(enumerator, &my_ts, &other_ts))
{
invoke_once(this, ike_sa, child_sa, config, up, my_ts, other_ts);
}
enumerator->destroy(enumerator);
}
enumerator = child_sa->create_policy_enumerator(child_sa);
while (enumerator->enumerate(enumerator, &my_ts, &other_ts))
{
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))
{
if (asprintf(&udp_enc, "PLUTO_UDP_ENC='%u' ",
other->get_port(other)) < 0)
{
udp_enc = NULL;
}
}
else
{
if (asprintf(&udp_enc, "") < 0)
{
udp_enc = NULL;
}
}
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
{
if (asprintf(&xauth, "") < 0)
{
xauth = NULL;
}
}
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;
}
}
else
{
iface = uncache_iface(this, child_sa->get_reqid(child_sa));
}
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;
}
while (TRUE)
{
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);
}
}
pclose(shell);
}
enumerator->destroy(enumerator);
return TRUE;
}

View 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 \

View 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

View 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 \

View File

@ -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;
}

View 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)

View File

@ -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 */

View File

@ -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_ @}*/