summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNathaniel McCallum <npmccallum@redhat.com>2013-12-16 16:19:08 -0500
committerroot <root@ipa-01.t.vda.li>2014-02-10 19:13:21 +0200
commitab00dce3f50042062f7171c6a6ab5ea8f494790f (patch)
tree3907e751fb76376b99566f7fbd28f865513b3e99
parent28630d550ff1f756fadc00a81595cd69c8b11ab6 (diff)
downloadfreeipa-ab00dce3f50042062f7171c6a6ab5ea8f494790f.tar.gz
freeipa-ab00dce3f50042062f7171c6a6ab5ea8f494790f.tar.xz
freeipa-ab00dce3f50042062f7171c6a6ab5ea8f494790f.zip
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.
-rw-r--r--daemons/configure.ac1
-rw-r--r--daemons/ipa-slapi-plugins/Makefile.am1
-rw-r--r--daemons/ipa-slapi-plugins/ipa-otp-lasttoken/Makefile.am28
-rw-r--r--daemons/ipa-slapi-plugins/ipa-otp-lasttoken/ipa-otp-lasttoken.sym1
-rw-r--r--daemons/ipa-slapi-plugins/ipa-otp-lasttoken/ipa_otp_lasttoken.c183
-rw-r--r--daemons/ipa-slapi-plugins/ipa-otp-lasttoken/otp-lasttoken-conf.ldif15
-rw-r--r--freeipa.spec.in2
-rw-r--r--ipaserver/install/dsinstance.py4
8 files changed, 235 insertions, 0 deletions
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 <http://www.gnu.org/licenses/>.
+ *
+ * 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 <npmccallum@redhat.com>
+ *
+ * Copyright (C) 2013 Red Hat, Inc.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <libotp.h>
+#include <time.h>
+
+#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)