On Sat, 2014-09-20 at 17:33 -0400, Simo Sorce wrote:
> On Sat, 20 Sep 2014 15:39:48 -0400
> Nathaniel McCallum <npmccal...@redhat.com> wrote:
> 
> > On Sat, 2014-09-20 at 00:25 +0200, thierry bordaz wrote:
> > > Hello Nathaniel,
> > > 
> > >         sanitize_input translates MOD/REPLACE into MOD/DEL+MOD/ADD.
> > > It looks good but difficult to think to all possible cases.
> > >         I think to the following corner case:
> > >         The initial entry has ipatokenHOTPcounter=5
> > >         ldapmodify..
> > >         changetype: modify
> > >         add: ipatokenHOTPcounter
> > >         ipatokenHOTPcounter: 6
> > >         -
> > >         replace: ipatokenHOTPcounter
> > >         ipatokenHOTPcounter: 7
> > >         
> > >         It translates
> > >         add: 6
> > >         del: 5
> > >         add: 7
> > >         
> > >         This operation will fail because ipatokenHOTPcounter is
> > >         single-valued although IMHO it should succeed.
> > 
> > No. It should fail. There can only ever be one ipatokenHOTPcounter.
> > 
> > >         This is a so special operation that is may not really be a
> > >         concern.
> > 
> > +1
> > 
> > >         It is important that attribute are single valued. The
> > >         replication changelog will replicated MOD/DEL + MOD/ADD for
> > > a MOD/REPL.
> > >         That means that if the attributes are updated on several
> > >         masters, the number of values can likely increase. Where for
> > >         single value it should only keep the most recent value.
> > 
> > That is a concern, at least for now. Eventually we won't use
> > replication for this at all. But for now, we will.
> > 
> > Here is the problem I foresee. You have two servers: A and B.
> > 
> > The user authenticates on A. This triggers a MOD/DEL(0)+MOD/ADD(1).
> > Replication is sent to server B.
> > 
> > Before the replication is performed on server B, the user
> > authenticates with the next token. This triggers a
> > MOD/DEL(0)+MOD/ADD(2). Replication is sent to server A. This
> > replication will fail because A has a value of 1, not a value of 0.
> > 
> > The end result is that there will be different values for
> > ipatokenHOTPcounter on the two different servers. A will have 1 and B
> > will have 2. Once this happens, the replications can never reconcile
> > (this is a big problem). I see two options here, both theoretical.
> > 
> > Option #1 is to hook 389 in a different place: before the mods are
> > performed by after the replication changelog is generated.
> > Alternatively, we could insert a hook after the replication changelog
> > is generated, but before it is sent. We could consolidate the MOD/DEL
> > +MOD/ADD here into a single MOD/REPLACE operation.
> > 
> > Option #2 is to have some way to translate the MOD/REPLACE(X) into a
> > MOD/DEL(<=X)+MOD/ADD(X).
> > 
> > Are either of these possible? Is there another option?
> 
> I think the only option would be to intercept modifications on
> replicas, check that the mod is due to a replication event and
> discard/change the delete and change the add into a replace. This means
> replay attacks will still be possible, but there is no way around that
> until we handle the replication/quorum thing ourselves.

Attached is a new version. This version replays the counter value
through the mod steps. This allows us to discard delete values when
doing replication and fill them in with the current counter value. It
also allows us to play all the steps and check our parameters on the
final state. I've tested with some fairly complex ldifs and I haven't
seen any bugs. I think this should fix all the outstanding problems.

Nathaniel


From 1f3b1454d5fa665029f898c3b5eeead22a40eb23 Mon Sep 17 00:00:00 2001
From: Nathaniel McCallum <npmccal...@redhat.com>
Date: Wed, 10 Sep 2014 17:31:37 -0400
Subject: [PATCH] Create ipa-otp-decrement 389DS plugin

This plugin ensures that all counter/watermark operations are atomic
and never decrement. Also, deletion is not permitted.

Because this plugin also ensures internal operations behave properly,
this also gives ipa-pwd-extop the appropriate behavior for OTP
authentication.

https://fedorahosted.org/freeipa/ticket/4493
https://fedorahosted.org/freeipa/ticket/4494
---
 daemons/configure.ac                               |   1 +
 daemons/ipa-slapi-plugins/Makefile.am              |   1 +
 .../ipa-otp-decrement/Makefile.am                  |  25 ++
 .../ipa-otp-decrement/ipa-otp-decrement.sym        |   1 +
 .../ipa-otp-decrement/ipa_otp_decrement.c          | 375 +++++++++++++++++++++
 .../ipa-otp-decrement/otp-decrement-conf.ldif      |  15 +
 freeipa.spec.in                                    |   2 +
 ipaserver/install/dsinstance.py                    |   4 +
 8 files changed, 424 insertions(+)
 create mode 100644 daemons/ipa-slapi-plugins/ipa-otp-decrement/Makefile.am
 create mode 100644 daemons/ipa-slapi-plugins/ipa-otp-decrement/ipa-otp-decrement.sym
 create mode 100644 daemons/ipa-slapi-plugins/ipa-otp-decrement/ipa_otp_decrement.c
 create mode 100644 daemons/ipa-slapi-plugins/ipa-otp-decrement/otp-decrement-conf.ldif

diff --git a/daemons/configure.ac b/daemons/configure.ac
index b4507a6d972f854331925e72869898576bdfd76f..936cba576818e9e6d2c5399bbfe85de7a27141ab 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-decrement/Makefile
     ipa-slapi-plugins/ipa-otp-lasttoken/Makefile
     ipa-slapi-plugins/ipa-pwd-extop/Makefile
     ipa-slapi-plugins/ipa-extdom-extop/Makefile
diff --git a/daemons/ipa-slapi-plugins/Makefile.am b/daemons/ipa-slapi-plugins/Makefile.am
index 06e6ee8b86f138cce05f2184ac98c39ffaf9757f..9c0f048c390dac81f58cd0270a002614693a62d8 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-decrement	\
 	ipa-otp-lasttoken	\
 	ipa-pwd-extop		\
 	ipa-extdom-extop	\
diff --git a/daemons/ipa-slapi-plugins/ipa-otp-decrement/Makefile.am b/daemons/ipa-slapi-plugins/ipa-otp-decrement/Makefile.am
new file mode 100644
index 0000000000000000000000000000000000000000..24aea58d22de57fcf88cbbe27f88c162ad47077c
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-otp-decrement/Makefile.am
@@ -0,0 +1,25 @@
+MAINTAINERCLEANFILES = *~ Makefile.in
+PLUGIN_COMMON_DIR = ../common
+AM_CPPFLAGS =							\
+	-I.							\
+	-I$(srcdir)						\
+	-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_decrement.la
+libipa_otp_decrement_la_SOURCES = ipa_otp_decrement.c
+libipa_otp_decrement_la_LDFLAGS = -avoid-version -export-symbols ipa-otp-decrement.sym
+libipa_otp_decrement_la_LIBADD = $(LDAP_LIBS)
+
+appdir = $(IPA_DATA_DIR)
+app_DATA = otp-decrement-conf.ldif
+EXTRA_DIST = $(app_DATA)
diff --git a/daemons/ipa-slapi-plugins/ipa-otp-decrement/ipa-otp-decrement.sym b/daemons/ipa-slapi-plugins/ipa-otp-decrement/ipa-otp-decrement.sym
new file mode 100644
index 0000000000000000000000000000000000000000..27829ead6cc5fcb13b0fbff0b7ac3d638cbf4e2f
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-otp-decrement/ipa-otp-decrement.sym
@@ -0,0 +1 @@
+ipa_otp_decrement_init
diff --git a/daemons/ipa-slapi-plugins/ipa-otp-decrement/ipa_otp_decrement.c b/daemons/ipa-slapi-plugins/ipa-otp-decrement/ipa_otp_decrement.c
new file mode 100644
index 0000000000000000000000000000000000000000..d17bad0cdfafda1be09e226569cc3cf7b58567d3
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-otp-decrement/ipa_otp_decrement.c
@@ -0,0 +1,375 @@
+/** 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 <npmccal...@redhat.com>
+ *
+ * Copyright (C) 2014 Red Hat, Inc.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+#include <slapi-plugin.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <errno.h>
+
+#include <plstr.h>
+
+#define ch_malloc(type) \
+    (type*) slapi_ch_malloc(sizeof(type))
+#define ch_calloc(count, type) \
+    (type*) slapi_ch_calloc(count, sizeof(type))
+#define ch_free(p) \
+    slapi_ch_free((void**) &(p))
+
+typedef struct {
+    bool exists;
+    long long value;
+} counter;
+
+static int
+send_error(Slapi_PBlock *pb, int rc, char *template, ...)
+{
+    va_list ap;
+    int res;
+
+    va_start(ap, template);
+    res = vsnprintf(NULL, 0, template, ap);
+    va_end(ap);
+
+    if (res > 0) {
+        char str[res + 1];
+
+        va_start(ap, template);
+        res = vsnprintf(str, sizeof(str), template, ap);
+        va_end(ap);
+
+        if (res > 0)
+            slapi_send_ldap_result(pb, rc, NULL, str, 0, NULL);
+    }
+
+    if (res <= 0)
+        slapi_send_ldap_result(pb, rc, NULL, NULL, 0, NULL);
+
+    slapi_pblock_set(pb, SLAPI_RESULT_CODE, &rc);
+    return rc;
+}
+
+static long long
+get_value(const LDAPMod *mod, long long def)
+{
+    const struct berval *bv;
+    long long val;
+
+    if (mod == NULL)
+        return def;
+
+    if (mod->mod_vals.modv_bvals == NULL)
+        return def;
+
+    bv = mod->mod_vals.modv_bvals[0];
+    if (bv == NULL)
+        return def;
+
+    char buf[bv->bv_len + 1];
+    memcpy(buf, bv->bv_val, bv->bv_len);
+    buf[sizeof(buf)-1] = '\0';
+
+    val = strtoll(buf, NULL, 10);
+    if (val == LLONG_MIN || val == LLONG_MAX)
+        return def;
+
+    return val;
+}
+
+static struct berval **
+make_berval_array(long long value)
+{
+    struct berval **bvs;
+
+    bvs = ch_calloc(2, struct berval*);
+    bvs[0] = ch_malloc(struct berval);
+    bvs[0]->bv_val = slapi_ch_smprintf("%lld", value);
+    bvs[0]->bv_len = strlen(bvs[0]->bv_val);
+
+    return bvs;
+}
+
+static LDAPMod *
+make_ldapmod(int op, const char *attr, long long value)
+{
+    LDAPMod *mod;
+
+    mod = (LDAPMod*) slapi_ch_calloc(1, sizeof(LDAPMod));
+    mod->mod_op = op | LDAP_MOD_BVALUES;
+    mod->mod_type = slapi_ch_strdup(attr);
+    mod->mod_bvalues = make_berval_array(value);
+
+    return mod;
+}
+
+static void
+convert_ldapmod_to_bval(LDAPMod *mod)
+{
+    if (mod == NULL || (mod->mod_op & LDAP_MOD_BVALUES))
+        return;
+
+    mod->mod_op |= LDAP_MOD_BVALUES;
+
+    if (mod->mod_values == NULL) {
+        mod->mod_bvalues = NULL;
+        return;
+    }
+
+    for (size_t i = 0; mod->mod_values[i] != NULL; i++) {
+        struct berval *bv = ch_malloc(struct berval);
+        bv->bv_val = mod->mod_values[i];
+        bv->bv_len = strlen(bv->bv_val);
+        mod->mod_bvalues[i] = bv;
+    }
+}
+
+static counter
+get_counter(Slapi_Entry *entry, const char *attr)
+{
+    Slapi_Attr *sattr = NULL;
+
+    return (counter) {
+        slapi_entry_attr_find(entry, attr, &sattr) == 0,
+        slapi_entry_attr_get_longlong(entry, attr)
+    };
+}
+
+static void
+berval_free_array(struct berval    ***bvals)
+{
+    for (size_t i = 0; (*bvals)[i] != NULL; i++) {
+        ch_free((*bvals)[i]->bv_val);
+        ch_free((*bvals)[i]);
+    }
+
+    slapi_ch_free((void**) bvals);
+}
+
+static bool
+is_replication(Slapi_PBlock *pb)
+{
+    int repl = 0;
+    slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &repl);
+    return repl != 0;
+}
+
+static const char *
+get_attribute(Slapi_Entry *entry)
+{
+    static struct {
+        const char *clss;
+        const char *attr;
+    } table[] = {
+        { "ipatokenHOTP", "ipatokenHOTPcounter" },
+        { "ipatokenTOTP", "ipatokenTOTPwatermark" },
+        { NULL, NULL }
+    };
+
+    const char *attr = NULL;
+    char **clsses = NULL;
+
+    clsses = slapi_entry_attr_get_charray(entry, "objectClass");
+    if (clsses == NULL)
+        return NULL;
+
+    for (size_t i = 0; attr == NULL && clsses[i] != NULL; i++) {
+        for (size_t j = 0; attr == NULL && table[j].clss != NULL; j++) {
+            if (PL_strcasecmp(table[j].clss, clsses[i]) == 0)
+                attr = table[j].attr;
+        }
+    }
+
+    slapi_ch_array_free(clsses);
+    return attr;
+}
+
+static int
+preop_mod(Slapi_PBlock *pb)
+{
+    size_t count = 0, attrs = 0, extras = 0;
+    Slapi_Entry *entry = NULL;
+    const char *attr = NULL;
+    LDAPMod **inmods = NULL;
+    LDAPMod **mods = NULL;
+    counter ctr, orig;
+
+    /* Get the input values. */
+    slapi_pblock_get(pb, SLAPI_ENTRY_PRE_OP, &entry);
+    slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &inmods);
+    if (entry == NULL || inmods == NULL)
+        return 0;
+
+    /* Get the attribute name. */
+    attr = get_attribute(entry);
+    if (attr == NULL)
+        return 0; /* Not a token. */
+
+    /* Count items. */
+    while (inmods != NULL && inmods[count] != NULL) {
+        LDAPMod *mod = inmods[count++];
+
+        if (PL_strcasecmp(mod->mod_type, attr) != 0)
+            continue;
+
+        attrs++;
+        switch (mod->mod_op & LDAP_MOD_OP) {
+        case LDAP_MOD_REPLACE:
+        case LDAP_MOD_INCREMENT:
+            extras++;
+            break;
+        }
+    }
+
+    /* Not operating on the counter/watermark. */
+    if (attrs == 0)
+        return 0;
+
+    /* Get the counter. */
+    orig = ctr = get_counter(entry, attr);
+
+    /* Filter the modify operations. */
+    mods = ch_calloc(count + extras + 1, LDAPMod*);
+    for (size_t i = 0, j = 0; inmods != NULL && inmods[i] != NULL; mods[j++] = inmods[i++]) {
+        LDAPMod *mod = inmods[i];
+
+        if (PL_strcasecmp(mod->mod_type, attr) != 0)
+            continue;
+
+        convert_ldapmod_to_bval(mod);
+
+        switch (mod->mod_op & LDAP_MOD_OP) {
+        case LDAP_MOD_DELETE:
+            ctr.exists = false;
+            if (mod->mod_bvalues != NULL && mod->mod_bvalues[0] == NULL)
+                berval_free_array(&mod->mod_bvalues);
+
+            if (is_replication(pb))
+                berval_free_array(&mod->mod_bvalues);
+
+            if (mod->mod_bvalues == NULL)
+                mod->mod_bvalues = make_berval_array(ctr.value);
+            break;
+
+        case LDAP_MOD_INCREMENT:
+            mods[j++] = make_ldapmod(LDAP_MOD_DELETE, attr, ctr.value);
+
+            ctr.value += get_value(mod, 1);
+
+            berval_free_array(&mod->mod_bvalues);
+            mod->mod_op &= ~LDAP_MOD_OP;
+            mod->mod_op |= LDAP_MOD_ADD;
+            mod->mod_bvalues = make_berval_array(ctr.value);
+            break;
+
+        case LDAP_MOD_REPLACE:
+            if (ctr.exists)
+                mods[j++] = make_ldapmod(LDAP_MOD_DELETE, attr, ctr.value);
+
+            mod->mod_op &= ~LDAP_MOD_OP;
+            mod->mod_op |= LDAP_MOD_ADD;
+
+        case LDAP_MOD_ADD:
+            ctr.value = get_value(mod, 0);
+            ctr.exists = true;
+            break;
+        }
+    }
+
+    /* Set the modified operations. */
+    slapi_pblock_set(pb, SLAPI_MODIFY_MODS, mods);
+    ch_free(inmods);
+
+    /* Ensure we aren't deleting the counter. */
+    if (orig.exists && !ctr.exists)
+        return send_error(pb, LDAP_UNWILLING_TO_PERFORM,
+                          "Will not delete %s", attr);
+
+    /* Ensure we aren't rolling back the counter. */
+    if (orig.value > ctr.value)
+        return send_error(pb, LDAP_UNWILLING_TO_PERFORM,
+                          "Will not decrement %s", attr);
+
+    return 0;
+}
+
+static int
+preop_init(Slapi_PBlock *pb)
+{
+    return slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_PRE_MODIFY_FN, preop_mod);
+}
+
+static int
+start_fn(Slapi_PBlock *pb)
+{
+    return 0;
+}
+
+static int
+close_fn(Slapi_PBlock *pb)
+{
+    return 0;
+}
+
+int
+ipa_otp_decrement_init(Slapi_PBlock *pb)
+{
+    static const Slapi_PluginDesc preop_desc = {
+        "ipa-otp-decrement",
+        "FreeIPA",
+        "FreeIPA/1.0",
+        "Protect token counters from decrement"
+    };
+
+    void *plugin_id = NULL;
+    int ret = 0;
+
+    ret |= slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &plugin_id);
+    ret |= slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01);
+    ret |= slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *) &preop_desc);
+    ret |= slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, start_fn);
+    ret |= slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, close_fn);
+    ret |= slapi_register_plugin("betxnpreoperation", 1, __func__, preop_init,
+                                 preop_desc.spd_id, NULL, plugin_id);
+
+    return ret;
+}
diff --git a/daemons/ipa-slapi-plugins/ipa-otp-decrement/otp-decrement-conf.ldif b/daemons/ipa-slapi-plugins/ipa-otp-decrement/otp-decrement-conf.ldif
new file mode 100644
index 0000000000000000000000000000000000000000..4064770f9191fffe1a51167b82d2e09c16ea86b6
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-otp-decrement/otp-decrement-conf.ldif
@@ -0,0 +1,15 @@
+dn: cn=IPA OTP Decrement Prevention,cn=plugins,cn=config
+changetype: add
+objectclass: top
+objectclass: nsSlapdPlugin
+objectclass: extensibleObject
+cn: IPA OTP Decrement Prevention
+nsslapd-pluginpath: libipa_otp_decrement
+nsslapd-plugininitfunc: ipa_otp_decrement_init
+nsslapd-plugintype: preoperation
+nsslapd-pluginenabled: on
+nsslapd-pluginid: ipa-otp-decrement
+nsslapd-pluginversion: 1.0
+nsslapd-pluginvendor: Red Hat, Inc.
+nsslapd-plugindescription: IPA OTP Decrement Prevention plugin
+nsslapd-plugin-depends-on-type: database
diff --git a/freeipa.spec.in b/freeipa.spec.in
index 685b345fedb9d157c8deedc66f8712da32c5963b..2b4f42aa028d1112f306a52dfb78da4e1dadce9e 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -348,6 +348,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_decrement.la
 rm %{buildroot}/%{plugin_dir}/libipa_otp_lasttoken.la
 rm %{buildroot}/%{_libdir}/krb5/plugins/kdb/ipadb.la
 rm %{buildroot}/%{_libdir}/samba/pdb/ipasam.la
@@ -697,6 +698,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_decrement.so
 %attr(755,root,root) %{plugin_dir}/libipa_otp_lasttoken.so
 %dir %{_localstatedir}/lib/ipa
 %attr(700,root,root) %dir %{_localstatedir}/lib/ipa/backup
diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
index 0518dd0e0f20255f4e42911af6f1f95fc25f554e..247132f7b76b66368d31d32bfe26763d27637b76 100644
--- a/ipaserver/install/dsinstance.py
+++ b/ipaserver/install/dsinstance.py
@@ -276,6 +276,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 decrement prevention plugin", self.__config_otp_decrement_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)
@@ -588,6 +589,9 @@ class DsInstance(service.Service):
     def __config_lockout_module(self):
         self._ldap_mod("lockout-conf.ldif")
 
+    def __config_otp_decrement_module(self):
+        self._ldap_mod("otp-decrement-conf.ldif")
+
     def __config_otp_lasttoken_module(self):
         self._ldap_mod("otp-lasttoken-conf.ldif")
 
-- 
2.1.0

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to