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:
commit
7d3c58a511
3
NEWS
3
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
|
||||
----------------
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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.
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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_ @}*/
|
|
@ -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;
|
||||
}
|
|
@ -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_ @}*/
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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 */
|
|
@ -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_ @}*/
|
Loading…
Reference in New Issue