On Mon, 2014-09-22 at 09:32 -0400, Simo Sorce wrote:
> On Sun, 21 Sep 2014 22:33:47 -0400
> Nathaniel McCallum <npmccal...@redhat.com> wrote:
> 
> Comments inline.
> 
> > +
> > +#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))
> 
> please do not redefine slapi functions, it just makes it harder to know
> what you used.
> 
> 
> > +typedef struct {
> > +    bool exists;
> > +    long long value;
> > +} counter;
> 
> 
> please no typedefs of structures, use "struct counter { ... };" and
> reference it as "struct counter" in the code.
> 
> Btw, FWIW you could use a value of -1 to indicate non-existence of the
> counter value, given counters can only be positive, this would avoid
> the need for a struct.
> 
> > +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;
> > +}
> 
> This function seem not really useful, you use send_error() only at the
> end of one single function where you could have the classic scheme of
> using a done: label and just use directly slapi_ch_smprintf. This would
> remove the need to use vsnprintf and all the va_ machinery which is
> more than 50% of this function.
> 
> 
> > +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;
> 
> In general (here and elsewhere) I prefer to always use {} in if clauses.
> I have been bitten enough time by people adding an instruction that
> should be in the braces but forgot to add braces (defensive programming
> style). But up to you.
> 
> > +    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;
> > +}
> 
> Can you put a comment here that explains what you are going to do in
> each cases in plain English ? This will help people in future figuring
> out if/how to modify the code or why something happens a specific way.
> It will also help the reviewer follow what is going on.
> 
> 
> > +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) {
> 
> ^^ this one would read much better as:
>     for (count = 0; inmods[count] != NULL; count++) {
> 
> You do not need to check for insmods != NULL as you already check for
> it and return 0 a few lines above.
> 
> > +        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++]) {
> 
> Please remove check for insmods != NULL, it is useless, and removing it
> will bring back the line under 80columns
> 
> > +        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;
> 
> I am not sure I understand this segment, why are you touching the
> delete operation outside of a replication event ?
> Doesn't this defeat and admin tool trying to correctly delete/add to
> avoid races ?
> 
> > +        case LDAP_MOD_INCREMENT:
> > +            mods[j++] = make_ldapmod(LDAP_MOD_DELETE, attr,
> > ctr.value); +
> > +            ctr.value += get_value(mod, 1);
> 
> uhmmm if you had an ADD followed by INCREMENT operation, would this
> cause the value to become value*2+1 instead of just value+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;
> 
> uhmm why are you converting mod_increment in all cases ? (including
> replication)
> 
> > +        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;
> 
> same question here, why are you converting a replace coming from
> replication into a delete/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;
> > +}
> 
> 
> see above about send_error().
> 
> I think what is needed most is the comment that explains the process
> at the to of the main function.
> 
> Simo.

All of the above are fixed.

Also, I have addressed Thierry's concern about putting the plugin in
betxnpreoperation by splitting the plugin into two phases: modification
and validation. Now all modifications occur in bepreoperation and all
validation occurs in betxnpreoperation.

Additionally, I have new code to trigger a new replication in the case
where a validation error occurs and we are in a replication transaction.
Thus, when A attempts to push an old counter value to B, B will now
replicate the latest value back to A.

Nathaniel

From 1d1f3d285bfd4b0d97ec27c5b2ef4bd792e3525a 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-slapi-plugins/ipa-otp-decrement/berval.c   | 111 +++++
 .../ipa-slapi-plugins/ipa-otp-decrement/berval.h   |  70 +++
 .../ipa-otp-decrement/ipa-otp-decrement.sym        |   1 +
 .../ipa-otp-decrement/ipa_otp_decrement.c          | 471 +++++++++++++++++++++
 .../ipa-slapi-plugins/ipa-otp-decrement/ldapmod.c  |  99 +++++
 .../ipa-slapi-plugins/ipa-otp-decrement/ldapmod.h  |  51 +++
 .../ipa-otp-decrement/otp-decrement-conf.ldif      |  15 +
 freeipa.spec.in                                    |   2 +
 ipaserver/install/dsinstance.py                    |   4 +
 12 files changed, 851 insertions(+)
 create mode 100644 daemons/ipa-slapi-plugins/ipa-otp-decrement/Makefile.am
 create mode 100644 daemons/ipa-slapi-plugins/ipa-otp-decrement/berval.c
 create mode 100644 daemons/ipa-slapi-plugins/ipa-otp-decrement/berval.h
 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/ldapmod.c
 create mode 100644 daemons/ipa-slapi-plugins/ipa-otp-decrement/ldapmod.h
 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..6ff0e24ddfcb8153b0c13f8e88458e1a613d42cb
--- /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 = berval.c berval.h ldapmod.c ldapmod.h 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/berval.c b/daemons/ipa-slapi-plugins/ipa-otp-decrement/berval.c
new file mode 100644
index 0000000000000000000000000000000000000000..c13a8427a432a49a20d966f5f933dc2a28b04e7e
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-otp-decrement/berval.c
@@ -0,0 +1,111 @@
+/** 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 **/
+
+#include "berval.h"
+
+#include <slapi-plugin.h>
+
+#include <limits.h>
+
+struct berval *
+berval_new_longlong(long long value)
+{
+    struct berval *bv;
+
+    bv = (struct berval*) slapi_ch_malloc(sizeof(struct berval*));
+    bv->bv_val = slapi_ch_smprintf("%lld", value);
+    bv->bv_len = strlen(bv->bv_val);
+
+    return bv;
+}
+
+void
+berval_free(struct berval **bv)
+{
+    if (*bv == NULL)
+        return;
+
+    slapi_ch_free((void **) &(*bv)->bv_val);
+    slapi_ch_free((void **) bv);
+}
+
+long long
+berval_to_longlong(const struct berval *bv)
+{
+    char buf[bv->bv_len + 1];
+    memcpy(buf, bv->bv_val, bv->bv_len);
+    buf[sizeof(buf)-1] = '\0';
+
+    return strtoll(buf, NULL, 10);
+}
+
+bool
+bervals_has_value(struct berval **bvs, long long value)
+{
+    for (size_t k = 0; bvs[k] != NULL; k++) {
+        long long v = berval_to_longlong(bvs[k]);
+        if (v == LLONG_MIN || v == LLONG_MAX)
+            continue;
+
+        if (v == value)
+            return true;
+    }
+
+    return false;
+}
+
+struct berval **
+bervals_new_longlong(long long value)
+{
+    struct berval **bvs;
+
+    bvs = (struct berval**) slapi_ch_calloc(2, sizeof(struct berval*));
+    bvs[0] = berval_new_longlong(value);
+
+    return bvs;
+}
+
+void
+bervals_free(struct berval ***bvals)
+{
+    for (struct berval **itr = *bvals; *itr != NULL; itr++)
+        berval_free(itr);
+
+    slapi_ch_free((void**) bvals);
+}
diff --git a/daemons/ipa-slapi-plugins/ipa-otp-decrement/berval.h b/daemons/ipa-slapi-plugins/ipa-otp-decrement/berval.h
new file mode 100644
index 0000000000000000000000000000000000000000..abd9c0c7880a569f281d2032749a6d214c8744a0
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-otp-decrement/berval.h
@@ -0,0 +1,70 @@
+/** 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 **/
+
+#pragma once
+
+#include <lber.h>
+#include <stdbool.h>
+
+/* Creates a new berval from a long long. */
+struct berval *
+berval_new_longlong(long long value);
+
+/* Frees a berval. */
+void
+berval_free(struct berval **bv);
+
+/* Converts a berval to a long long.
+ * If the value will not fit in a long long,
+ * LLONG_MIN or LLONG_MAX will be returned.
+ */
+long long
+berval_to_longlong(const struct berval *bv);
+
+/* Creates a NULL-terminated array with a single berval. */
+struct berval **
+bervals_new_longlong(long long value);
+
+/* Frees a NULL-terminated array of bervals. */
+void
+bervals_free(struct berval ***bvals);
+
+/* Returns true if the specified value is found in the array. */
+bool
+bervals_has_value(struct berval **bvs, long long value);
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..43e7a7838f636218ece35529ea7543875054848c
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-otp-decrement/ipa_otp_decrement.c
@@ -0,0 +1,471 @@
+/** 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 **/
+
+/**
+ * The purpose of this plugin is to ensure that counter/watermark values:
+ * 1. Have atomic operations.
+ * 2. Never go backwards.
+ * 3. Never get deleted.
+ *
+ * This is accomplished via two distinct passes:
+ * 1. Request modification - preop_mod()
+ * 2. Request validation - txnpreop_mod()
+ *
+ * In the first pass, we convert each INCREMENT or REPLACE operation to a
+ * DELETE/ADD operation pair. This ensures atomicity. We also generally
+ * sanitize input. All modifications are performed in this pass, but no
+ * errors are generated.
+ *
+ * In the second pass, we validate the operations before performing them.
+ * This is where we ensure that the operation will not result in counter
+ * decrement or deletion. We also have various checks to ensure that
+ * atomicity cannot be bypassed.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+#include "berval.h"
+#include "ldapmod.h"
+
+#include <limits.h>
+
+#include <plstr.h>
+
+static void *plugin_id;
+
+struct counter {
+    bool exists;
+    long long value;
+};
+
+static struct counter
+get_counter(Slapi_Entry *entry, const char *attr)
+{
+    Slapi_Attr *sattr = NULL;
+
+    return (struct counter) {
+        slapi_entry_attr_find(entry, attr, &sattr) == 0,
+        slapi_entry_attr_get_longlong(entry, attr)
+    };
+}
+
+static bool
+is_replication(Slapi_PBlock *pb)
+{
+    int repl = 0;
+    slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &repl);
+    return repl != 0;
+}
+
+/* The goal of this function is to determine the name of the counter
+ * or watermark attribute based upon the objectClass of the entry.
+ *
+ * If no match is found, this function returns NULL indicating that
+ * this entry is not a known token type.
+ */
+static const char *
+find_counter_name(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;
+}
+
+/* Counts the number of mods once all expansions are performed.
+ * Returns 0 if there are no operations on the counter.
+ */
+static size_t
+count_expanded(LDAPMod **mods, const char *attr)
+{
+    size_t o; /* Counts the number of operations. */
+    size_t c; /* Counts the number of counter operations. */
+    size_t e; /* Counts the number of expansions. */
+
+    if (mods == NULL)
+        return 0;
+
+    for (o = c = e = 0; mods[o] != NULL; o++) {
+        if (PL_strcasecmp(mods[o]->mod_type, attr) != 0)
+            continue;
+
+        switch (mods[o]->mod_op & LDAP_MOD_OP) {
+        case LDAP_MOD_REPLACE:
+        case LDAP_MOD_INCREMENT:
+            e++;
+        default:
+            c++;
+        }
+    }
+
+    if (c == 0)
+        return 0;
+
+    return o + e;
+}
+
+static int
+preop_mod(Slapi_PBlock *pb)
+{
+    Slapi_Entry *entry = NULL;
+    const char *attr = NULL;
+    LDAPMod **inmods = NULL;
+    LDAPMod **mods = NULL;
+    struct counter ctr;
+    size_t size;
+
+    /* 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 of the counter. */
+    attr = find_counter_name(entry);
+    if (attr == NULL)
+        return 0; /* Not a token. */
+
+    /* Get the counter. */
+    ctr = get_counter(entry, attr);
+
+    /* Get the size of the mods when all expansions are performed. */
+    size = count_expanded(inmods, attr);
+    if (size == 0)
+        return 0; /* Not operating on counter. */
+
+    /* Filter the modify operations. */
+    mods = (LDAPMod **) slapi_ch_calloc(size + 1, sizeof(LDAPMod*));
+    for (size_t i = 0, j = 0; inmods[i] != NULL; mods[j++] = inmods[i++]) {
+        LDAPMod *mod = inmods[i];
+
+        if (PL_strcasecmp(mod->mod_type, attr) != 0)
+            continue;
+
+        ldapmod_convert_bvalues(mod);
+
+        switch (mod->mod_op & LDAP_MOD_OP) {
+        /* Incoming delete operations need to be restricted to the counter
+         * value only. Without this, we cannot guarantee atomicity. */
+        case LDAP_MOD_DELETE:
+            /* If this is a replication operation, we delete the specified
+             * value. This is because in cases where the values have diverged
+             * we don't care that the old value is the same, just the new
+             * value (from the add operation). */
+            if (is_replication(pb))
+                bervals_free(&mod->mod_bvalues);
+
+            if (mod->mod_bvalues != NULL) {
+                /* Normalize input: if an empty array is allocated, free it. */
+                if (mod->mod_bvalues[0] == NULL)
+                    bervals_free(&mod->mod_bvalues);
+
+                /* Otherwise, if the delete operation contains the current
+                 * counter value, free the values array. This will be
+                 * filled in with only the counter value below.
+                 *
+                 * This enables atomicity when multiple delete values are
+                 * specified.
+                 *
+                 * If the current counter value is not in the list of
+                 * delete values, we will catch and reject this in the
+                 * validation pass.
+                 */
+                else if (bervals_has_value(mod->mod_bvalues, ctr.value))
+                    bervals_free(&mod->mod_bvalues);
+            }
+
+            /* A value MUST be specified on delete operations.
+             * Without this, atomicity cannot be guaranteed. */
+            if (mod->mod_bvalues == NULL)
+                mod->mod_bvalues = bervals_new_longlong(ctr.value);
+
+            ctr.exists = false;
+            break;
+
+        /* All increment operations need to be converted to a pair of delete
+         * and add operations in order to guarantee atomicity. */
+        case LDAP_MOD_INCREMENT:
+            /* Create the delete operation. */
+            mods[j++] = ldapmod_new_longlong(LDAP_MOD_DELETE, attr, ctr.value);
+
+            /* Increment the counter. */
+            ctr.value += ldapmod_get_value(mod, 1);
+
+            /* Convert the existing increment operation to an add operation. */
+            bervals_free(&mod->mod_bvalues);
+            mod->mod_op = LDAP_MOD_ADD | LDAP_MOD_BVALUES;
+            mod->mod_bvalues = bervals_new_longlong(ctr.value);
+            break;
+
+        /* All replace operations need to be converted to a pair of delete
+         * and add operations in order to guarantee atomicity. */
+        case LDAP_MOD_REPLACE:
+            /* If there is no current counter, we don't need to delete it. */
+            if (ctr.exists) {
+                mods[j++] = ldapmod_new_longlong(LDAP_MOD_DELETE,
+                                                 attr, ctr.value);
+            }
+
+            /* Convert the existing replace operation to an add operation. */
+            mod->mod_op = LDAP_MOD_ADD | LDAP_MOD_BVALUES;
+
+            /* Fall through. */
+
+        /* No modification of incoming add operations is required. */
+        case LDAP_MOD_ADD:
+            ctr.value = ldapmod_get_value(mod, 0);
+            ctr.exists = true;
+            break;
+        }
+    }
+
+    /* Set the modified operations. */
+    slapi_pblock_set(pb, SLAPI_MODIFY_MODS, mods);
+    slapi_ch_free((void **) &inmods);
+
+    return 0;
+}
+
+static void
+trigger_replication(Slapi_Entry *entry, const char *attr, long long value)
+{
+    Slapi_PBlock *pb = NULL;
+    char buf[32];
+
+    LDAPMod *mods[] = {
+        &(LDAPMod) {
+            LDAP_MOD_DELETE, (char *) attr,
+            .mod_values = (char *[]) { buf, NULL }
+        },
+        &(LDAPMod) {
+            LDAP_MOD_ADD, (char *) attr,
+            .mod_values = (char *[]) { buf, NULL }
+        },
+        NULL
+    };
+
+    snprintf(buf, sizeof(buf), "%lld", value);
+
+    pb = slapi_pblock_new();
+    slapi_modify_internal_set_pb(pb, slapi_entry_get_dn_const(entry),
+                                 mods, NULL, NULL, plugin_id, 0);
+    slapi_pblock_destroy(pb);
+}
+
+static int
+txnpreop_mod(Slapi_PBlock *pb)
+{
+    Slapi_Entry *entry = NULL;
+    struct counter ctr, orig;
+    const char *attr = NULL;
+    const char *err = NULL;
+    LDAPMod **mods = NULL;
+
+    /* Get the input values. */
+    slapi_pblock_get(pb, SLAPI_ENTRY_PRE_OP, &entry);
+    slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods);
+    if (entry == NULL || mods == NULL)
+        return 0;
+
+    /* Get the attribute name of the counter. */
+    attr = find_counter_name(entry);
+    if (attr == NULL)
+        return 0; /* Not a token. */
+
+    /* Get the counter. */
+    orig = ctr = get_counter(entry, attr);
+
+    /* Run through the counter mods for validation. */
+    for (size_t i = 0; mods[i] != NULL; i++) {
+        LDAPMod *mod = mods[i];
+
+        if (PL_strcasecmp(mod->mod_type, attr) != 0)
+            continue;
+
+        switch (mod->mod_op & LDAP_MOD_OP) {
+        case LDAP_MOD_DELETE:
+            /* Delete operations MUST contain only one value. */
+            if (mod->mod_bvalues == NULL
+                || mod->mod_bvalues[0] == NULL
+                || mod->mod_bvalues[1] != NULL) {
+                err = "Invalid delete value count on";
+                goto error;
+            }
+
+            ctr.exists = false;
+            break;
+
+        case LDAP_MOD_REPLACE:
+            err = "Replace is invalid on";
+            goto error;
+
+        case LDAP_MOD_INCREMENT:
+            err = "Increment is invalid on";
+            goto error;
+
+
+        case LDAP_MOD_ADD:
+            /* Delete operations MUST contain only one value. */
+            if (mod->mod_bvalues == NULL
+                || mod->mod_bvalues[0] == NULL
+                || mod->mod_bvalues[1] != NULL) {
+                err = "Invalid add value count on";
+                goto error;
+            }
+
+            /* The value must be sane. */
+            ctr.value = berval_to_longlong(mod->mod_bvalues[0]);
+            if (ctr.value == LLONG_MAX || ctr.value == LLONG_MIN) {
+                err = "Invalid add value on";
+                goto error;
+            }
+
+            ctr.exists = true;
+            break;
+        }
+    }
+
+    /* Ensure a positive counter. */
+    if (ctr.value < 0) {
+        err = "Negative value invalid on";
+        goto error;
+    }
+
+    /* Ensure we aren't deleting the counter. */
+    if (orig.exists && !ctr.exists) {
+        err = "Will not delete";
+        goto error;
+    }
+
+    /* Ensure we aren't rolling back the counter. */
+    if (orig.value > ctr.value) {
+        err = "Will not decrement";
+        goto error;
+    }
+
+    return 0;
+
+error:
+    if (is_replication(pb))
+        trigger_replication(entry, attr, orig.value);
+
+    char msg[strlen(err) + strlen(attr) + 2];
+    int rc = LDAP_UNWILLING_TO_PERFORM;
+    strcpy(msg, err);
+    strcat(msg, " ");
+    strcat(msg, attr);
+
+    slapi_send_ldap_result(pb, rc, NULL, msg, 0, NULL);
+    slapi_pblock_set(pb, SLAPI_RESULT_CODE, &rc);
+    return rc;
+}
+
+static int
+preop_init(Slapi_PBlock *pb)
+{
+    return slapi_pblock_set(pb, SLAPI_PLUGIN_BE_PRE_MODIFY_FN, preop_mod);
+}
+
+static int
+txnpreop_init(Slapi_PBlock *pb)
+{
+    return slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_PRE_MODIFY_FN, txnpreop_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 desc = {
+        "ipa-otp-decrement",
+        "FreeIPA",
+        "FreeIPA/1.0",
+        "Protect token counters from decrement"
+    };
+
+    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 *) &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("bepreoperation", 1, __func__, preop_init,
+                                 "ipa-otp-decrement bepreoperation",
+                                 NULL, plugin_id);
+    ret |= slapi_register_plugin("betxnpreoperation", 1, __func__, txnpreop_init,
+                                 "ipa-otp-decrement betxnpreoperation",
+                                 NULL, plugin_id);
+
+    return ret;
+}
diff --git a/daemons/ipa-slapi-plugins/ipa-otp-decrement/ldapmod.c b/daemons/ipa-slapi-plugins/ipa-otp-decrement/ldapmod.c
new file mode 100644
index 0000000000000000000000000000000000000000..1b22d578f7c7411d6eb7d59b274f5028d112bd6e
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-otp-decrement/ldapmod.c
@@ -0,0 +1,99 @@
+/** 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 **/
+
+#include "ldapmod.h"
+#include "berval.h"
+
+#include <limits.h>
+
+long long
+ldapmod_get_value(const LDAPMod *mod, long long def)
+{
+    long long v;
+
+    if (mod == NULL)
+        return def;
+
+    if (mod->mod_bvalues == NULL)
+        return def;
+
+    if (mod->mod_bvalues[0] == NULL)
+        return def;
+
+    v = berval_to_longlong(mod->mod_bvalues[0]);
+    if (v == LLONG_MIN || v == LLONG_MAX)
+        return def;
+
+    return v;
+}
+
+LDAPMod *
+ldapmod_new_longlong(int op, const char *attr, long long value)
+{
+    LDAPMod *mod;
+
+    mod = (LDAPMod*) slapi_ch_malloc(sizeof(LDAPMod));
+    mod->mod_op = op | LDAP_MOD_BVALUES;
+    mod->mod_type = slapi_ch_strdup(attr);
+    mod->mod_bvalues = bervals_new_longlong(value);
+
+    return mod;
+}
+
+void
+ldapmod_convert_bvalues(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;
+        bv = (struct berval*) slapi_ch_malloc(sizeof(struct berval));
+        bv->bv_val = mod->mod_values[i];
+        bv->bv_len = strlen(bv->bv_val);
+        mod->mod_bvalues[i] = bv;
+    }
+}
diff --git a/daemons/ipa-slapi-plugins/ipa-otp-decrement/ldapmod.h b/daemons/ipa-slapi-plugins/ipa-otp-decrement/ldapmod.h
new file mode 100644
index 0000000000000000000000000000000000000000..e0cb9d4bef7da4ee649aa64d8d04fcdb5005e0ab
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-otp-decrement/ldapmod.h
@@ -0,0 +1,51 @@
+/** 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 **/
+
+#pragma once
+
+#include <slapi-plugin.h>
+
+long long
+ldapmod_get_value(const LDAPMod *mod, long long def);
+
+LDAPMod *
+ldapmod_new_longlong(int op, const char *attr, long long value);
+
+void
+ldapmod_convert_bvalues(LDAPMod *mod);
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