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

https://fedorahosted.org/freeipa/ticket/4494
From 9256d83755bb9a19eae98a248eb0a33a4fecd089 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.

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          | 385 +++++++++++++++++++++
 .../ipa-otp-decrement/otp-decrement-conf.ldif      |  15 +
 freeipa.spec.in                                    |   2 +
 ipaserver/install/dsinstance.py                    |   4 +
 8 files changed, 434 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..86e851bd991318cfe88fdbebbe1725e6b22d6b06
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/ipa-otp-decrement/ipa_otp_decrement.c
@@ -0,0 +1,385 @@
+/** 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>
+
+#define PLUGIN_NAME "ipa-otp-decrement"
+
+#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))
+
+static void *plugin_id;
+static const Slapi_PluginDesc preop_desc = {
+    PLUGIN_NAME,
+    "FreeIPA",
+    "FreeIPA/1.0",
+    "Protect token counters from decrement"
+};
+
+static const char *ATTRS[] = {
+    "ipatokenHOTPcounter",
+    "ipatokenTOTPwatermark",
+    NULL
+};
+
+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
+load_counter(Slapi_PBlock *pb, long long *ctr)
+{
+    char *filt = "(|(objectClass=ipatokenHOTP)(objectClass=ipatokenTOTP))";
+    Slapi_Entry **entry = NULL;
+    Slapi_PBlock *spb = NULL;
+    int res = LDAP_SUCCESS;
+    char *dn = NULL;
+
+    slapi_pblock_get(pb, SLAPI_MODIFY_TARGET, &dn);
+
+    spb = slapi_pblock_new();
+    slapi_search_internal_set_pb(spb, dn, LDAP_SCOPE_BASE, filt,
+                                 (char **) ATTRS, 1, NULL, NULL,
+                                 plugin_id, 0);
+    slapi_search_internal_pb(spb);
+
+    slapi_pblock_get(spb, SLAPI_PLUGIN_INTOP_RESULT, &res);
+    if (res != LDAP_SUCCESS)
+        goto egress;
+
+    slapi_pblock_get(spb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entry);
+    if (entry == NULL || entry[0] == NULL || entry[1] != NULL) {
+        res = LDAP_FILTER_ERROR; /* No entry; which error code? */
+        goto egress;
+    }
+
+    *ctr = 0; /* Default value. */
+    for (size_t i = 0; ATTRS[i] != NULL; i++) {
+        Slapi_Attr *attr = NULL;
+
+        if (slapi_entry_attr_find(entry[0], ATTRS[i], &attr) == 0) {
+            *ctr = slapi_entry_attr_get_longlong(entry[0], ATTRS[i]);
+            break;
+        }
+    }
+
+egress:
+    slapi_free_search_results_internal(spb);
+    slapi_pblock_destroy(spb);
+    return res;
+}
+
+/*
+ * Get's the specified value from the mod.
+ *
+ * Returns ENOENT if there is no item.
+ * Returns ERANGE if the value doesn't fit.
+ */
+static int
+ldapmod_get_bvalue(const LDAPMod *mod, size_t indx, long long *val)
+{
+    if (mod == NULL)
+        return ENOENT;
+
+    if (mod->mod_vals.modv_bvals == NULL)
+        return ENOENT;
+
+    for (size_t i = 0; i <= indx; i++) {
+        if (mod->mod_vals.modv_bvals[i] == NULL)
+            return ENOENT;
+    }
+
+    const struct berval *bv = mod->mod_vals.modv_bvals[indx];
+    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 ERANGE;
+
+    return 0;
+}
+
+static bool
+ldapmod_is_attrs(const LDAPMod *mod, const char **attrs)
+{
+    if (mod == NULL || attrs == NULL)
+        return false;
+
+    for (size_t i = 0; attrs[i] != NULL; i++) {
+        if (strcasecmp(mod->mod_type, attrs[i]) == 0)
+            return true;
+    }
+
+    return false;
+}
+
+static bool
+ldapmod_is_op_attrs(const LDAPMod *mod, int op, const char **attrs)
+{
+    if (mod == NULL)
+        return false;
+
+    if (op != (mod->mod_op & LDAP_MOD_OP))
+        return false;
+
+    return ldapmod_is_attrs(mod, attrs);
+}
+
+static bool
+ldapmod_is_op_attr(const LDAPMod *mod, int op, const char *attr)
+{
+    return ldapmod_is_op_attrs(mod, op, (const char*[]) { attr, NULL });
+}
+
+static struct berval *
+berval_make(long long value)
+{
+    struct berval *bv;
+
+    bv = (struct berval*) slapi_ch_calloc(1, sizeof(struct berval));
+    bv->bv_val = slapi_ch_smprintf("%lld", value);
+    bv->bv_len = strlen(bv->bv_val);
+
+    return bv;
+}
+
+static struct berval **
+berval_make_array(long long value)
+{
+    struct berval **bvs;
+
+    bvs = ch_calloc(2, struct berval*);
+    bvs[0] = berval_make(value);
+
+    return bvs;
+}
+
+static LDAPMod *
+ldapmod_make(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 = berval_make_array(value);
+
+    return mod;
+}
+
+static void
+ldapmod_convert_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;
+    }
+}
+
+/* Returns false if target entry is not a token. */
+static bool
+sanitize_input(Slapi_PBlock *pb, LDAPMod ***outmods)
+{
+    size_t count = 0, replaces = 0;
+    LDAPMod **inmods = NULL;
+    long long counter = 0;
+    bool needctr = false;
+
+    /* Get input. */
+    slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &inmods);
+
+    /* Count items. Ensure berval. Load counter if needed. */
+    while (inmods != NULL && inmods[count] != NULL) {
+        LDAPMod *mod = inmods[count++];
+
+        if (!ldapmod_is_attrs(mod, ATTRS))
+            continue;
+
+        ldapmod_convert_to_bval(mod);
+
+        switch (mod->mod_op & LDAP_MOD_OP) {
+        case LDAP_MOD_REPLACE:
+            replaces++;
+            needctr = true;
+            break;
+
+        case LDAP_MOD_DELETE:
+            needctr |= mod->mod_bvalues == NULL || mod->mod_bvalues[0] == NULL;
+            break;
+        }
+    }
+
+    if (needctr && load_counter(pb, &counter) != LDAP_SUCCESS)
+        return false;
+
+    /* Create output array. */
+    *outmods = ch_calloc(count + replaces + 1, LDAPMod*);
+    for (size_t i = 0, j = 0; inmods != NULL && inmods[i] != NULL; i++, j++) {
+        LDAPMod *mod = inmods[i];
+
+        if (ldapmod_is_attrs(mod, ATTRS)) {
+            switch (mod->mod_op & LDAP_MOD_OP) {
+            /* Ensure that a DELETE operation specifies a counter value. */
+            case LDAP_MOD_DELETE:
+                if (mod->mod_bvalues != NULL && mod->mod_bvalues[0] == NULL)
+                    ch_free(mod->mod_bvalues);
+
+                if (mod->mod_bvalues == NULL)
+                    mod->mod_bvalues = berval_make_array(counter);
+                break;
+
+            /* Convert a REPLACE operation to DEL/ADD operations. */
+            case LDAP_MOD_REPLACE:
+                mod->mod_op &= ~LDAP_MOD_OP;
+                mod->mod_op |= LDAP_MOD_ADD;
+                (*outmods)[j++] = ldapmod_make(LDAP_MOD_DELETE,
+                                               mod->mod_type,
+                                               counter);
+                break;
+            }
+        }
+
+        (*outmods)[j] = mod;
+    }
+
+    slapi_pblock_set(pb, SLAPI_MODIFY_MODS, *outmods);
+    ch_free(inmods);
+    return true;
+}
+
+static int
+preop_mod(Slapi_PBlock *pb)
+{
+    LDAPMod **mods = NULL;
+
+    if (!sanitize_input(pb, &mods))
+        return send_error(pb, LDAP_UNWILLING_TO_PERFORM, "Invalid target");
+
+    /* Validate the input. */
+    for (size_t i = 0 ; mods[i] != NULL; i++) {
+        bool haveadd = false;
+
+        if (!ldapmod_is_op_attrs(mods[i], LDAP_MOD_DELETE, ATTRS))
+            continue;
+
+        /* If a delete op is specified, ensure a following add op.
+         * We don't permit plain deletion of counters/watermarks. */
+        for (size_t j = i; mods[j] != NULL; j++) {
+            long long add, del = 0;
+
+            if (!ldapmod_is_op_attr(mods[j], LDAP_MOD_ADD, mods[i]->mod_type))
+                continue;
+
+            if (ldapmod_get_bvalue(mods[j], 0, &add) != 0)
+                return send_error(pb, LDAP_UNWILLING_TO_PERFORM,
+                                  "Invalid counter/watermark add value");
+
+            for (size_t k = 0;
+                    ldapmod_get_bvalue(mods[i], k, &del) != ENOENT;
+                    k++) {
+                if (del >= add)
+                    return send_error(pb, LDAP_UNWILLING_TO_PERFORM,
+                                      "Must increment counter/watermark");
+            }
+
+            haveadd = true;
+            break;
+        }
+
+        if (!haveadd)
+            return send_error(pb, LDAP_UNWILLING_TO_PERFORM,
+                              "Will not delete counter/watermark");
+    }
+
+    return 0;
+}
+
+static int
+preop_init(Slapi_PBlock *pb)
+{
+    int ret = 0;
+
+    ret |= slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_PRE_MODIFY_FN, preop_mod);
+
+    return ret;
+}
+
+int
+ipa_otp_decrement_init(Slapi_PBlock *pb)
+{
+    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_register_plugin("betxnpreoperation", 1, __func__, preop_init,
+                                 PLUGIN_NAME, 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