On Mon, 17 Feb 2014, Nathaniel McCallum wrote:
From 357cc6a40c58f3f88f8e86c5224f2c042ab974d8 Mon Sep 17 00:00:00 2001
From: Nathaniel McCallum <[email protected]>
Date: Mon, 16 Dec 2013 16:19:08 -0500
Subject: [PATCH 2/4] 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 
e5bf7f552c0d85acc7ae14e3da05ab8c948daa93..b4507a6d972f854331925e72869898576bdfd76f
 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 
40725d2259d09010d2f82381543fc77d84435040..06e6ee8b86f138cce05f2184ac98c39ffaf9757f
 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 
0000000000000000000000000000000000000000..1e3869bfda9f8fd14cd4d93d0d466780932ac40f
--- /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 
0000000000000000000000000000000000000000..e32dc32f5693547bf604480490f42511368fdb81
--- /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 
0000000000000000000000000000000000000000..4abeb671e29b40cdf9b005ff5bc6b12c6d91bb30
--- /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 <[email protected]>
+ *
+ * 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");
I think the order is wrong here and it might be the cause for those
messages I've been getting in the dirsrv error logs.

You need to fetch plugin identity, then set version and description,
set callbacks, and only then register the plugin. Finally, preop_init() will
be called and it should set the callbacks only.

--
/ Alexander Bokovoy

_______________________________________________
Freeipa-devel mailing list
[email protected]
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to