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