From ab00dce3f50042062f7171c6a6ab5ea8f494790f Mon Sep 17 00:00:00 2001 From: Nathaniel McCallum Date: Mon, 16 Dec 2013 16:19:08 -0500 Subject: Add OTP last token plugin This plugin prevents the deletion or deactivation of the last valid token for a user. This prevents the user from migrating back to single factor authentication once OTP has been enabled. Thanks to Mark Reynolds for helping me with this patch. --- daemons/configure.ac | 1 + daemons/ipa-slapi-plugins/Makefile.am | 1 + .../ipa-otp-lasttoken/Makefile.am | 28 ++++ .../ipa-otp-lasttoken/ipa-otp-lasttoken.sym | 1 + .../ipa-otp-lasttoken/ipa_otp_lasttoken.c | 183 +++++++++++++++++++++ .../ipa-otp-lasttoken/otp-lasttoken-conf.ldif | 15 ++ freeipa.spec.in | 2 + ipaserver/install/dsinstance.py | 4 + 8 files changed, 235 insertions(+) create mode 100644 daemons/ipa-slapi-plugins/ipa-otp-lasttoken/Makefile.am create mode 100644 daemons/ipa-slapi-plugins/ipa-otp-lasttoken/ipa-otp-lasttoken.sym create mode 100644 daemons/ipa-slapi-plugins/ipa-otp-lasttoken/ipa_otp_lasttoken.c create mode 100644 daemons/ipa-slapi-plugins/ipa-otp-lasttoken/otp-lasttoken-conf.ldif diff --git a/daemons/configure.ac b/daemons/configure.ac index b0bbe96a6..3cdb9384c 100644 --- a/daemons/configure.ac +++ b/daemons/configure.ac @@ -314,6 +314,7 @@ AC_CONFIG_FILES([ ipa-slapi-plugins/ipa-dns/Makefile ipa-slapi-plugins/ipa-enrollment/Makefile ipa-slapi-plugins/ipa-lockout/Makefile + ipa-slapi-plugins/ipa-otp-lasttoken/Makefile ipa-slapi-plugins/ipa-pwd-extop/Makefile ipa-slapi-plugins/ipa-extdom-extop/Makefile ipa-slapi-plugins/ipa-winsync/Makefile diff --git a/daemons/ipa-slapi-plugins/Makefile.am b/daemons/ipa-slapi-plugins/Makefile.am index 40725d225..06e6ee8b8 100644 --- a/daemons/ipa-slapi-plugins/Makefile.am +++ b/daemons/ipa-slapi-plugins/Makefile.am @@ -7,6 +7,7 @@ SUBDIRS = \ ipa-enrollment \ ipa-lockout \ ipa-modrdn \ + ipa-otp-lasttoken \ ipa-pwd-extop \ ipa-extdom-extop \ ipa-uuid \ diff --git a/daemons/ipa-slapi-plugins/ipa-otp-lasttoken/Makefile.am b/daemons/ipa-slapi-plugins/ipa-otp-lasttoken/Makefile.am new file mode 100644 index 000000000..1e3869bfd --- /dev/null +++ b/daemons/ipa-slapi-plugins/ipa-otp-lasttoken/Makefile.am @@ -0,0 +1,28 @@ +MAINTAINERCLEANFILES = *~ Makefile.in +PLUGIN_COMMON_DIR = ../common +AM_CPPFLAGS = \ + -I. \ + -I$(srcdir) \ + -I$(srcdir)/../libotp \ + -I$(PLUGIN_COMMON_DIR) \ + -I/usr/include/dirsrv \ + -DPREFIX=\""$(prefix)"\" \ + -DBINDIR=\""$(bindir)"\" \ + -DLIBDIR=\""$(libdir)"\" \ + -DLIBEXECDIR=\""$(libexecdir)"\" \ + -DDATADIR=\""$(datadir)"\" \ + $(AM_CFLAGS) \ + $(LDAP_CFLAGS) \ + $(WARN_CFLAGS) + +plugindir = $(libdir)/dirsrv/plugins +plugin_LTLIBRARIES = libipa_otp_lasttoken.la +libipa_otp_lasttoken_la_SOURCES = ipa_otp_lasttoken.c +libipa_otp_lasttoken_la_LDFLAGS = -avoid-version -export-symbols ipa-otp-lasttoken.sym +libipa_otp_lasttoken_la_LIBADD = \ + $(LDAP_LIBS) \ + $(builddir)/../libotp/libotp.la + +appdir = $(IPA_DATA_DIR) +app_DATA = otp-lasttoken-conf.ldif +EXTRA_DIST = $(app_DATA) diff --git a/daemons/ipa-slapi-plugins/ipa-otp-lasttoken/ipa-otp-lasttoken.sym b/daemons/ipa-slapi-plugins/ipa-otp-lasttoken/ipa-otp-lasttoken.sym new file mode 100644 index 000000000..e32dc32f5 --- /dev/null +++ b/daemons/ipa-slapi-plugins/ipa-otp-lasttoken/ipa-otp-lasttoken.sym @@ -0,0 +1 @@ +ipa_otp_lasttoken_init diff --git a/daemons/ipa-slapi-plugins/ipa-otp-lasttoken/ipa_otp_lasttoken.c b/daemons/ipa-slapi-plugins/ipa-otp-lasttoken/ipa_otp_lasttoken.c new file mode 100644 index 000000000..4abeb671e --- /dev/null +++ b/daemons/ipa-slapi-plugins/ipa-otp-lasttoken/ipa_otp_lasttoken.c @@ -0,0 +1,183 @@ +/** BEGIN COPYRIGHT BLOCK + * 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 3 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Additional permission under GPLv3 section 7: + * + * In the following paragraph, "GPL" means the GNU General Public + * License, version 3 or any later version, and "Non-GPL Code" means + * code that is governed neither by the GPL nor a license + * compatible with the GPL. + * + * You may link the code of this Program with Non-GPL Code and convey + * linked combinations including the two, provided that such Non-GPL + * Code only links to the code of this Program through those well + * defined interfaces identified in the file named EXCEPTION found in + * the source code files (the "Approved Interfaces"). The files of + * Non-GPL Code may instantiate templates or use macros or inline + * functions from the Approved Interfaces without causing the resulting + * work to be covered by the GPL. Only the copyright holders of this + * Program may make changes or additions to the list of Approved + * Interfaces. + * + * Authors: + * Nathaniel McCallum + * + * Copyright (C) 2013 Red Hat, Inc. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include + +#define PLUGIN_NAME "ipa-otp-lasttoken" +#define LOG(sev, ...) \ + slapi_log_error(SLAPI_LOG_ ## sev, PLUGIN_NAME, \ + "%s: %s\n", __func__, __VA_ARGS__), -1 + +static void *plugin_id; +static const Slapi_PluginDesc preop_desc = { + PLUGIN_NAME, + "FreeIPA", + "FreeIPA/1.0", + "Protect the user's last active token" +}; + +static bool +target_is_only_enabled_token(Slapi_PBlock *pb) +{ + Slapi_DN *target_sdn = NULL; + Slapi_DN *token_sdn = NULL; + struct otptoken **tokens; + char *user_dn = NULL; + bool match; + + /* Ignore internal operations. */ + if (slapi_op_internal(pb)) + return false; + + /* Get the current user's SDN. */ + slapi_pblock_get(pb, SLAPI_CONN_DN, &user_dn); + if (user_dn == NULL) + return false; + + /* Get the SDN of the only enabled token. */ + tokens = otptoken_find(plugin_id, user_dn, NULL, true, NULL); + if (tokens != NULL && tokens[0] != NULL && tokens[1] == NULL) + token_sdn = slapi_sdn_dup(otptoken_get_sdn(tokens[0])); + otptoken_free_array(tokens); + if (token_sdn == NULL) + return false; + + /* Get the target SDN. */ + slapi_pblock_get(pb, SLAPI_TARGET_SDN, &target_sdn); + if (target_sdn == NULL) { + slapi_sdn_free(&token_sdn); + return false; + } + + /* Does the target SDN match the only enabled token SDN? */ + match = slapi_sdn_compare(token_sdn, target_sdn) == 0; + slapi_sdn_free(&token_sdn); + return match; +} + +static inline int +send_error(Slapi_PBlock *pb, int rc, char *errstr) +{ + slapi_send_ldap_result(pb, rc, NULL, errstr, 0, NULL); + slapi_pblock_set(pb, SLAPI_RESULT_CODE, &rc); + return rc; +} + +static int +preop_del(Slapi_PBlock *pb) +{ + if (!target_is_only_enabled_token(pb)) + return 0; + + return send_error(pb, LDAP_UNWILLING_TO_PERFORM, + "Can't delete last active token"); +} + +static int +preop_mod(Slapi_PBlock *pb) +{ + LDAPMod **mods = NULL; + + if (!target_is_only_enabled_token(pb)) + return 0; + + /* Do not permit deactivation of the last active token. */ + slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods); + for (int i = 0; mods != NULL && mods[i] != NULL; i++) { + if (strcasecmp(mods[i]->mod_type, "ipatokenDisabled") == 0) { + return send_error(pb, LDAP_UNWILLING_TO_PERFORM, + "Can't disable last active token"); + } + + if (strcasecmp(mods[i]->mod_type, "ipatokenOwner") == 0) { + return send_error(pb, LDAP_UNWILLING_TO_PERFORM, + "Can't change last active token's owner"); + } + + if (strcasecmp(mods[i]->mod_type, "ipatokenNotBefore") == 0) { + return send_error(pb, LDAP_UNWILLING_TO_PERFORM, + "Can't change last active token's start time"); + } + + if (strcasecmp(mods[i]->mod_type, "ipatokenNotAfter") == 0) { + return send_error(pb, LDAP_UNWILLING_TO_PERFORM, + "Can't change last active token's end time"); + } + } + + return 0; +} + +static int +preop_init(Slapi_PBlock *pb) +{ + if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01)) + goto error; + + if (slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *) &preop_desc)) + goto error; + + if (slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_PRE_DELETE_FN, preop_del)) + goto error; + + if (slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_PRE_MODIFY_FN, preop_mod)) + goto error; + + return 0; + +error: + return LOG(FATAL, "failed to register be_txn_pre_op plugin"); +} + +int +ipa_otp_lasttoken_init(Slapi_PBlock *pb) +{ + slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &plugin_id); + + if (slapi_register_plugin("betxnpreoperation", 1, __func__, preop_init, + PLUGIN_NAME, NULL, plugin_id)) + return LOG(FATAL, "failed to register plugin"); + + return 0; +} diff --git a/daemons/ipa-slapi-plugins/ipa-otp-lasttoken/otp-lasttoken-conf.ldif b/daemons/ipa-slapi-plugins/ipa-otp-lasttoken/otp-lasttoken-conf.ldif new file mode 100644 index 000000000..767883848 --- /dev/null +++ b/daemons/ipa-slapi-plugins/ipa-otp-lasttoken/otp-lasttoken-conf.ldif @@ -0,0 +1,15 @@ +dn: cn=IPA OTP Last Token,cn=plugins,cn=config +changetype: add +objectclass: top +objectclass: nsSlapdPlugin +objectclass: extensibleObject +cn: IPA OTP Last Token +nsslapd-pluginpath: libipa_otp_lasttoken +nsslapd-plugininitfunc: ipa_otp_lasttoken_init +nsslapd-plugintype: preoperation +nsslapd-pluginenabled: on +nsslapd-pluginid: ipa-otp-lasttoken +nsslapd-pluginversion: 1.0 +nsslapd-pluginvendor: Red Hat, Inc. +nsslapd-plugindescription: IPA OTP Last Token plugin +nsslapd-plugin-depends-on-type: database diff --git a/freeipa.spec.in b/freeipa.spec.in index 486cd6156..16378e131 100644 --- a/freeipa.spec.in +++ b/freeipa.spec.in @@ -396,6 +396,7 @@ rm %{buildroot}/%{plugin_dir}/libipa_sidgen.la rm %{buildroot}/%{plugin_dir}/libipa_sidgen_task.la rm %{buildroot}/%{plugin_dir}/libipa_extdom_extop.la rm %{buildroot}/%{plugin_dir}/libipa_range_check.la +rm %{buildroot}/%{plugin_dir}/libipa_otp_lasttoken.la rm %{buildroot}/%{_libdir}/krb5/plugins/kdb/ipadb.la rm %{buildroot}/%{_libdir}/samba/pdb/ipasam.la @@ -742,6 +743,7 @@ fi %attr(755,root,root) %{plugin_dir}/libipa_cldap.so %attr(755,root,root) %{plugin_dir}/libipa_dns.so %attr(755,root,root) %{plugin_dir}/libipa_range_check.so +%attr(755,root,root) %{plugin_dir}/libipa_otp_lasttoken.so %dir %{_localstatedir}/lib/ipa %attr(700,root,root) %dir %{_localstatedir}/lib/ipa/backup %attr(700,root,root) %dir %{_localstatedir}/lib/ipa/sysrestore diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py index de804059c..8fa900f8d 100644 --- a/ipaserver/install/dsinstance.py +++ b/ipaserver/install/dsinstance.py @@ -270,6 +270,7 @@ class DsInstance(service.Service): self.step("configuring DNS plugin", self.__config_dns_module) self.step("enabling entryUSN plugin", self.__enable_entryusn) self.step("configuring lockout plugin", self.__config_lockout_module) + self.step("configuring OTP last token plugin", self.__config_otp_lasttoken_module) self.step("creating indices", self.__create_indices) self.step("enabling referential integrity plugin", self.__add_referint_module) if enable_ssl: @@ -571,6 +572,9 @@ class DsInstance(service.Service): def __config_lockout_module(self): self._ldap_mod("lockout-conf.ldif") + def __config_otp_lasttoken_module(self): + self._ldap_mod("otp-lasttoken-conf.ldif") + def __repoint_managed_entries(self): self._ldap_mod("repoint-managed-entries.ldif", self.sub_dict) -- cgit