diff --git a/conf/Makefile.am b/conf/Makefile.am index ee9ce72ea..e5077391a 100644 --- a/conf/Makefile.am +++ b/conf/Makefile.am @@ -45,6 +45,7 @@ plugins = \ plugins/eap-tnc.opt \ plugins/eap-ttls.opt \ plugins/error-notify.opt \ + plugins/ext-auth.opt \ plugins/gcrypt.opt \ plugins/ha.opt \ plugins/imc-attestation.opt \ diff --git a/conf/plugins/ext-auth.opt b/conf/plugins/ext-auth.opt new file mode 100644 index 000000000..bf127b9d7 --- /dev/null +++ b/conf/plugins/ext-auth.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. diff --git a/configure.ac b/configure.ac index 85f7cb35c..100e29afc 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/src/libcharon/Makefile.am b/src/libcharon/Makefile.am index e81c42405..0eaabf57f 100644 --- a/src/libcharon/Makefile.am +++ b/src/libcharon/Makefile.am @@ -258,6 +258,13 @@ if MONOLITHIC endif endif +if USE_EXT_AUTH + SUBDIRS += plugins/ext_auth +if MONOLITHIC + libcharon_la_LIBADD += plugins/ext_auth/libstrongswan-ext-auth.la +endif +endif + if USE_EAP_IDENTITY SUBDIRS += plugins/eap_identity if MONOLITHIC diff --git a/src/libcharon/plugins/ext_auth/Makefile.am b/src/libcharon/plugins/ext_auth/Makefile.am new file mode 100644 index 000000000..d51ea8881 --- /dev/null +++ b/src/libcharon/plugins/ext_auth/Makefile.am @@ -0,0 +1,18 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/libstrongswan \ + -I$(top_srcdir)/src/libhydra \ + -I$(top_srcdir)/src/libcharon + +AM_CFLAGS = \ + $(PLUGIN_CFLAGS) + +if MONOLITHIC +noinst_LTLIBRARIES = libstrongswan-ext-auth.la +else +plugin_LTLIBRARIES = libstrongswan-ext-auth.la +endif + +libstrongswan_ext_auth_la_SOURCES = ext_auth_plugin.h ext_auth_plugin.c \ + ext_auth_listener.h ext_auth_listener.c + +libstrongswan_ext_auth_la_LDFLAGS = -module -avoid-version diff --git a/src/libcharon/plugins/ext_auth/ext_auth_listener.c b/src/libcharon/plugins/ext_auth/ext_auth_listener.c new file mode 100644 index 000000000..06cec20d7 --- /dev/null +++ b/src/libcharon/plugins/ext_auth/ext_auth_listener.c @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2014 Vyronas Tsingaras (vtsingaras@it.auth.gr) + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec AG + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* for vasprintf() */ +#define _GNU_SOURCE +#include "ext_auth_listener.h" + +#include +#include + +#include +#include + +typedef struct private_ext_auth_listener_t private_ext_auth_listener_t; + +/** + * Private data of an ext_auth_listener_t object. + */ +struct private_ext_auth_listener_t { + + /** + * Public ext_auth_listener_listener_t interface. + */ + ext_auth_listener_t public; + + /** + * Path to authorization program + */ + char *script; +}; + +/** + * Allocate and push a format string to the environment + */ +static bool push_env(char *envp[], u_int count, char *fmt, ...) +{ + int i = 0; + char *str; + va_list args; + + while (envp[i]) + { + if (++i + 1 >= count) + { + return FALSE; + } + } + va_start(args, fmt); + if (vasprintf(&str, fmt, args) >= 0) + { + envp[i] = str; + } + va_end(args); + return envp[i] != NULL; +} + +/** + * Free all allocated environment strings + */ +static void free_env(char *envp[]) +{ + int i; + + for (i = 0; envp[i]; i++) + { + free(envp[i]); + } +} + +METHOD(listener_t, authorize, bool, + private_ext_auth_listener_t *this, ike_sa_t *ike_sa, + bool final, bool *success) +{ + if (final) + { + FILE *shell; + process_t *process; + char *envp[32] = {}; + int out, retval; + + *success = FALSE; + + push_env(envp, countof(envp), "IKE_UNIQUE_ID=%u", + ike_sa->get_unique_id(ike_sa)); + push_env(envp, countof(envp), "IKE_NAME=%s", + ike_sa->get_name(ike_sa)); + + push_env(envp, countof(envp), "IKE_LOCAL_HOST=%H", + ike_sa->get_my_host(ike_sa)); + push_env(envp, countof(envp), "IKE_REMOTE_HOST=%H", + ike_sa->get_other_host(ike_sa)); + + push_env(envp, countof(envp), "IKE_LOCAL_ID=%Y", + ike_sa->get_my_id(ike_sa)); + push_env(envp, countof(envp), "IKE_REMOTE_ID=%Y", + ike_sa->get_other_id(ike_sa)); + + if (ike_sa->has_condition(ike_sa, COND_EAP_AUTHENTICATED) || + ike_sa->has_condition(ike_sa, COND_XAUTH_AUTHENTICATED)) + { + push_env(envp, countof(envp), "IKE_REMOTE_EAP_ID=%Y", + ike_sa->get_other_eap_id(ike_sa)); + } + + process = process_start_shell(envp, NULL, &out, NULL, + "2>&1 %s", this->script); + if (process) + { + shell = fdopen(out, "r"); + if (shell) + { + while (TRUE) + { + char resp[128], *e; + + if (fgets(resp, sizeof(resp), shell) == NULL) + { + if (ferror(shell)) + { + DBG1(DBG_CFG, "error reading from ext-auth script"); + } + break; + } + else + { + e = resp + strlen(resp); + if (e > resp && e[-1] == '\n') + { + e[-1] = '\0'; + } + DBG1(DBG_CHD, "ext-auth: %s", resp); + } + } + fclose(shell); + } + else + { + close(out); + } + if (process->wait(process, &retval)) + { + if (retval == EXIT_SUCCESS) + { + *success = TRUE; + } + else + { + DBG1(DBG_CFG, "rejecting IKE_SA for ext-auth result: %d", + retval); + } + } + } + free_env(envp); + } + return TRUE; +} + +METHOD(ext_auth_listener_t, destroy, void, + private_ext_auth_listener_t *this) +{ + free(this); +} + +/** + * See header + */ +ext_auth_listener_t *ext_auth_listener_create(char *script) +{ + private_ext_auth_listener_t *this; + + INIT(this, + .public = { + .listener = { + .authorize = _authorize, + }, + .destroy = _destroy, + }, + .script = script, + ); + + return &this->public; +} diff --git a/src/libcharon/plugins/ext_auth/ext_auth_listener.h b/src/libcharon/plugins/ext_auth/ext_auth_listener.h new file mode 100644 index 000000000..3fec83066 --- /dev/null +++ b/src/libcharon/plugins/ext_auth/ext_auth_listener.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2014 Vyronas Tsingaras (vtsingaras@it.auth.gr) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * @defgroup ext_auth_listener ext_auth_listener + * @{ @ingroup ext_auth + */ + +#ifndef EXT_AUTH_LISTENER_H_ +#define EXT_AUTH_LISTENER_H_ + +#include + +typedef struct ext_auth_listener_t ext_auth_listener_t; + +/** + * Listener using an external script to authorize connection + */ +struct ext_auth_listener_t { + + /** + * Implements listener_t interface. + */ + listener_t listener; + + /** + * Destroy the listener. + */ + void (*destroy)(ext_auth_listener_t *this); +}; + +/** + * Create ext_auth_listener instance. + * + * @param script path to authorization script + * @return listener instance + */ +ext_auth_listener_t *ext_auth_listener_create(char *script); + +#endif /** ext_auth_LISTENER_H_ @}*/ diff --git a/src/libcharon/plugins/ext_auth/ext_auth_plugin.c b/src/libcharon/plugins/ext_auth/ext_auth_plugin.c new file mode 100644 index 000000000..b3698c767 --- /dev/null +++ b/src/libcharon/plugins/ext_auth/ext_auth_plugin.c @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2014 Vyronas Tsingaras (vtsingaras@it.auth.gr) + * Copyright (C) 2014 Martin Willi + * Copyright (C) 2014 revosec AG + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "ext_auth_plugin.h" +#include "ext_auth_listener.h" + +#include + +typedef struct private_ext_auth_plugin_t private_ext_auth_plugin_t; + +/** + * private data of ext_auth plugin + */ +struct private_ext_auth_plugin_t { + + /** + * implements plugin interface + */ + ext_auth_plugin_t public; + + /** + * Listener verifying peers during authorization + */ + ext_auth_listener_t *listener; +}; + +METHOD(plugin_t, get_name, char*, + private_ext_auth_plugin_t *this) +{ + return "ext-auth"; +} + +/** + * Create a listener instance, NULL on error + */ +static ext_auth_listener_t* create_listener() +{ + char *script; + + script = lib->settings->get_str(lib->settings, + "%s.plugins.ext-auth.script", NULL, lib->ns); + if (!script) + { + DBG1(DBG_CFG, "no script for ext-auth script defined, disabled"); + return NULL; + } + DBG1(DBG_CFG, "using ext-auth script '%s'", script); + return ext_auth_listener_create(script); +} + +/** + * Register listener + */ +static bool plugin_cb(private_ext_auth_plugin_t *this, + plugin_feature_t *feature, bool reg, void *cb_data) +{ + if (reg) + { + this->listener = create_listener(); + if (!this->listener) + { + return FALSE; + } + charon->bus->add_listener(charon->bus, &this->listener->listener); + } + else + { + if (this->listener) + { + charon->bus->remove_listener(charon->bus, &this->listener->listener); + this->listener->destroy(this->listener); + } + } + return TRUE; +} + +METHOD(plugin_t, get_features, int, + private_ext_auth_plugin_t *this, plugin_feature_t *features[]) +{ + static plugin_feature_t f[] = { + PLUGIN_CALLBACK((plugin_feature_callback_t)plugin_cb, NULL), + PLUGIN_PROVIDE(CUSTOM, "ext_auth"), + }; + *features = f; + return countof(f); +} + + +METHOD(plugin_t, reload, bool, + private_ext_auth_plugin_t *this) +{ + ext_auth_listener_t *listener; + + /* reload new listener overlapped */ + listener = create_listener(); + if (listener) + { + charon->bus->add_listener(charon->bus, &listener->listener); + } + if (this->listener) + { + charon->bus->remove_listener(charon->bus, &this->listener->listener); + this->listener->destroy(this->listener); + } + this->listener = listener; + + return TRUE; +} + +METHOD(plugin_t, destroy, void, + private_ext_auth_plugin_t *this) +{ + free(this); +} + +/** + * Plugin constructor + */ +plugin_t *ext_auth_plugin_create() +{ + private_ext_auth_plugin_t *this; + + INIT(this, + .public = { + .plugin = { + .get_name = _get_name, + .get_features = _get_features, + .reload = _reload, + .destroy = _destroy, + }, + }, + ); + + return &this->public.plugin; +} diff --git a/src/libcharon/plugins/ext_auth/ext_auth_plugin.h b/src/libcharon/plugins/ext_auth/ext_auth_plugin.h new file mode 100644 index 000000000..1288e240c --- /dev/null +++ b/src/libcharon/plugins/ext_auth/ext_auth_plugin.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2014 Vyronas Tsingaras (vtsingaras@it.auth.gr) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * @defgroup ext_auth ext_auth + * @ingroup cplugins + * + * @defgroup ext_auth_plugin ext_auth_plugin + * @{ @ingroup ext_auth + */ + +#ifndef EXT_AUTH_PLUGIN_H_ +#define EXT_AUTH_PLUGIN_H_ + +#include + +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_ @}*/