summaryrefslogtreecommitdiffstats
path: root/ipa-server/ipa-slapi-plugins
diff options
context:
space:
mode:
authorPete Rowley <prowley@redhat.com>2007-08-10 16:06:23 -0700
committerPete Rowley <prowley@redhat.com>2007-08-10 16:06:23 -0700
commitd3415f85b00ee993679afad6f6a2f64c881ed0ec (patch)
tree2273d8677d53f9f9bc650f799ab6502c0dd8e609 /ipa-server/ipa-slapi-plugins
parent9ad12ae2eb4ea75c586f0d061f89cddf40750c28 (diff)
downloadfreeipa-d3415f85b00ee993679afad6f6a2f64c881ed0ec.tar.gz
freeipa-d3415f85b00ee993679afad6f6a2f64c881ed0ec.tar.xz
freeipa-d3415f85b00ee993679afad6f6a2f64c881ed0ec.zip
initial commit of memberof DS plugin
Diffstat (limited to 'ipa-server/ipa-slapi-plugins')
-rw-r--r--ipa-server/ipa-slapi-plugins/ipa-memberof/Makefile5
-rw-r--r--ipa-server/ipa-slapi-plugins/ipa-memberof/ipa-memberof.c1731
-rw-r--r--ipa-server/ipa-slapi-plugins/ipa-memberof/memberof-conf.ldif13
3 files changed, 1749 insertions, 0 deletions
diff --git a/ipa-server/ipa-slapi-plugins/ipa-memberof/Makefile b/ipa-server/ipa-slapi-plugins/ipa-memberof/Makefile
new file mode 100644
index 000000000..013d6273a
--- /dev/null
+++ b/ipa-server/ipa-slapi-plugins/ipa-memberof/Makefile
@@ -0,0 +1,5 @@
+all:
+ gcc ipa-memberof.c -I/usr/include/nss3 -I/usr/include/mozldap -I/usr/include/nspr4 -I/usr/include/fedora-ds -I/usr/include -llber -shared -fPIC -DPIC -g -Wl,-soname -Wl,libipa-memberof-plugin.so -o libipa-memberof-plugin.so
+
+install:
+ cp -f libipa-memberof-plugin.so /usr/lib/fedora-ds/plugins/
diff --git a/ipa-server/ipa-slapi-plugins/ipa-memberof/ipa-memberof.c b/ipa-server/ipa-slapi-plugins/ipa-memberof/ipa-memberof.c
new file mode 100644
index 000000000..f528a0608
--- /dev/null
+++ b/ipa-server/ipa-slapi-plugins/ipa-memberof/ipa-memberof.c
@@ -0,0 +1,1731 @@
+/** 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; version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * In addition, as a special exception, Red Hat, Inc. gives You the additional
+ * right to link the code of this Program with code not covered under the GNU
+ * General Public License ("Non-GPL Code") and to distribute linked combinations
+ * including the two, subject to the limitations in this paragraph. Non-GPL Code
+ * permitted under this exception must only link 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 GNU General Public License. Only Red Hat, Inc. may make changes or
+ * additions to the list of Approved Interfaces. You must obey the GNU General
+ * Public License in all respects for all of the Program code and other code used
+ * in conjunction with the Program except the Non-GPL Code covered by this
+ * exception. If you modify this file, you may extend this exception to your
+ * version of the file, but you are not obligated to do so. If you do not wish to
+ * provide this exception without modification, you must delete this exception
+ * statement from your version and license this file solely under the GPL without
+ * exception.
+ *
+ * Authors:
+ * Pete Rowley <prowley@redhat.com>
+ *
+ * Copyright (C) 2007 Red Hat, Inc.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK
+ **/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "slapi-plugin.h"
+#include "string.h"
+#include "nspr.h"
+
+#define IPA_GROUP_ATTR "uniquemember"
+#define IPA_MEMBEROF_ATTR "memberof"
+#define IPA_GROUP_ATTR_IS_DN 1
+#define IPA_GROUP_ATTR_TYPE "uid"
+#define IPA_GROUP_FILTER "(" IPA_GROUP_ATTR "=*)"
+
+#define IPAMO_PLUGIN_SUBSYSTEM "ipa-memberof-plugin" /* used for logging */
+static Slapi_PluginDesc pdesc = { "ipamo", "FreeIPA project", "FreeIPA/1.0",
+ "IPA memberof plugin" };
+
+static void* _PluginID = NULL;
+static Slapi_Filter *ipa_group_filter = NULL;
+static Slapi_Mutex *ipamo_operation_lock = 0;
+
+typedef struct _ipamostringll
+{
+ char *dn;
+ void *next;
+} ipamostringll;
+
+
+
+/*** function prototypes ***/
+
+/* exported functions */
+int ipamo_postop_init(Slapi_PBlock *pb );
+
+/* plugin callbacks */
+static int ipamo_postop_del(Slapi_PBlock *pb );
+static int ipamo_postop_modrdn(Slapi_PBlock *pb );
+static int ipamo_postop_modify(Slapi_PBlock *pb );
+static int ipamo_postop_add(Slapi_PBlock *pb );
+static int ipamo_postop_start(Slapi_PBlock *pb);
+static int ipamo_postop_close(Slapi_PBlock *pb);
+
+/* supporting cast */
+static int ipamo_oktodo(Slapi_PBlock *pb);
+static char *ipamo_getdn(Slapi_PBlock *pb);
+static int ipamo_modop_one(Slapi_PBlock *pb, int mod_op, char *op_this, char *op_to);
+static int ipamo_modop_one_r(Slapi_PBlock *pb, int mod_op, char *group_dn,
+ char *op_this, char *op_to, ipamostringll *stack);
+static int ipamo_add_one(Slapi_PBlock *pb, char *addthis, char *addto);
+static int ipamo_del_one(Slapi_PBlock *pb, char *delthis, char *delfrom);
+static int ipamo_mod_smod_list(Slapi_PBlock *pb, int mod, char *groupdn,
+ Slapi_Mod *smod);
+static int ipamo_add_smod_list(Slapi_PBlock *pb, char *groupdn, Slapi_Mod *smod);
+static int ipamo_del_smod_list(Slapi_PBlock *pb, char *groupdn, Slapi_Mod *smod);
+static int ipamo_mod_attr_list(Slapi_PBlock *pb, int mod, char *groupdn,
+ Slapi_Attr *attr);
+static int ipamo_mod_attr_list_r(Slapi_PBlock *pb, int mod, char *group_dn,
+ char *op_this, Slapi_Attr *attr, ipamostringll *stack);
+static int ipamo_add_attr_list(Slapi_PBlock *pb, char *groupdn, Slapi_Attr *attr);
+static int ipamo_del_attr_list(Slapi_PBlock *pb, char *groupdn, Slapi_Attr *attr);
+static int ipamo_moddn_attr_list(Slapi_PBlock *pb, char *pre_dn, char *post_dn,
+ Slapi_Attr *attr);
+static int ipamod_replace_list(Slapi_PBlock *pb, char *group_dn);
+static void ipamo_set_plugin_id(void * plugin_id);
+static void *ipamo_get_plugin_id();
+static int ipamo_compare(const void *a, const void *b);
+static void ipamo_load_array(Slapi_Value **array, Slapi_Attr *attr);
+static Slapi_Filter *ipamo_string2filter(char *strfilter);
+static int ipamo_is_legit_member(Slapi_PBlock *pb, char *group_dn,
+ char *op_this, char *op_to, ipamostringll *stack);
+static int ipamo_memberof_search_callback(Slapi_Entry *e, void *callback_data);
+static int ipamo_del_dn_from_groups(Slapi_PBlock *pb, char *dn);
+static int ipamo_call_foreach_dn(Slapi_PBlock *pb, char *dn,
+ char *type, plugin_search_entry_callback callback, void *callback_data);
+static int ipamo_is_group_member(Slapi_Value *groupdn, Slapi_Value *memberdn);
+static int ipamo_test_membership(Slapi_PBlock *pb, char *dn);
+static int ipamo_test_membership_callback(Slapi_Entry *e, void *callback_data);
+static int ipamo_del_dn_type_callback(Slapi_Entry *e, void *callback_data);
+static int ipamo_replace_dn_type_callback(Slapi_Entry *e, void *callback_data);
+static int ipamo_replace_dn_from_groups(Slapi_PBlock *pb, char *pre_dn, char *post_dn);
+static int ipamo_modop_one_replace_r(Slapi_PBlock *pb, int mod_op, char *group_dn,
+ char *op_this, char *replace_with, char *op_to, ipamostringll *stack);
+static void ipamo_lock();
+static void ipamo_unlock();
+static int ipamo_add_groups_search_callback(Slapi_Entry *e, void *callback_data);
+static int ipamo_add_membership(Slapi_PBlock *pb, char *op_this, char *op_to);
+
+
+/*** implementation ***/
+
+
+/*** exported functions ***/
+
+/*
+ * ipamo_postop_init()
+ *
+ * Register plugin call backs
+ *
+ */
+int
+ipamo_postop_init(Slapi_PBlock *pb)
+{
+ int ret = 0;
+ char *ipamo_plugin_identity = 0;
+
+ slapi_log_error( SLAPI_LOG_TRACE, IPAMO_PLUGIN_SUBSYSTEM,
+ "--> ipamo_postop_init\n" );
+ /*
+ * Get plugin identity and stored it for later use
+ * Used for internal operations
+ */
+
+ slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &ipamo_plugin_identity);
+ PR_ASSERT (ipamo_plugin_identity);
+ ipamo_set_plugin_id(ipamo_plugin_identity);
+
+ if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
+ SLAPI_PLUGIN_VERSION_01 ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
+ (void *)&pdesc ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_POST_DELETE_FN,
+ (void *) ipamo_postop_del ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_POST_MODRDN_FN,
+ (void *) ipamo_postop_modrdn ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_POST_MODIFY_FN,
+ (void *) ipamo_postop_modify ) != 0 ||
+ slapi_pblock_set( pb, SLAPI_PLUGIN_POST_ADD_FN,
+ (void *) ipamo_postop_add ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN,
+ (void *) ipamo_postop_start ) != 0 ||
+ slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN,
+ (void *) ipamo_postop_close ) != 0)
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, IPAMO_PLUGIN_SUBSYSTEM,
+ "ipamo_postop_init failed\n" );
+ ret = -1;
+ }
+
+ slapi_log_error( SLAPI_LOG_TRACE, IPAMO_PLUGIN_SUBSYSTEM,
+ "<-- ipamo_postop_init\n" );
+ return ret;
+}
+
+/*
+ * ipamo_postop_start()
+ *
+ * Do plugin start up stuff
+ *
+ */
+int ipamo_postop_start(Slapi_PBlock *pb)
+{
+ int rc = 0;
+
+ slapi_log_error( SLAPI_LOG_TRACE, IPAMO_PLUGIN_SUBSYSTEM,
+ "--> ipamo_postop_start\n" );
+
+ ipa_group_filter = ipamo_string2filter(IPA_GROUP_FILTER);
+
+ ipamo_operation_lock = slapi_new_mutex();
+
+ if(0 == ipa_group_filter || 0 == ipamo_operation_lock)
+ {
+ rc = -1;
+ goto bail;
+ }
+
+ /*
+ * TODO: start up operation actor thread
+ * need to get to a point where server failure
+ * or shutdown doesn't hose our operations
+ * so we should create a task entry that contains
+ * all required information to complete the operation
+ * then the tasks can be restarted safely if
+ * interrupted
+ */
+
+bail:
+ slapi_log_error( SLAPI_LOG_TRACE, IPAMO_PLUGIN_SUBSYSTEM,
+ "<-- ipamo_postop_start\n" );
+
+ return rc;
+}
+
+/*
+ * ipamo_postop_close()
+ *
+ * Do plugin shut down stuff
+ *
+ */
+int ipamo_postop_close(Slapi_PBlock *pb)
+{
+ slapi_log_error( SLAPI_LOG_TRACE, IPAMO_PLUGIN_SUBSYSTEM,
+ "--> ipamo_postop_close\n" );
+
+
+
+ slapi_log_error( SLAPI_LOG_TRACE, IPAMO_PLUGIN_SUBSYSTEM,
+ "<-- ipamo_postop_close\n" );
+ return 0;
+}
+
+/*
+ * ipamo_postop_del()
+ *
+ * All entries with a memberOf attribute that contains the group DN get retrieved
+ * and have the their memberOf attribute regenerated (it is far too complex and
+ * error prone to attempt to change only those dn values involved in this case -
+ * mainly because the deleted group may itself be a member of other groups which
+ * may be members of other groups etc. in a big recursive mess involving dependency
+ * chains that must be created and traversed in order to decide if an entry should
+ * really have those groups removed too)
+ */
+int ipamo_postop_del(Slapi_PBlock *pb)
+{
+ int ret = 0;
+ char *dn;
+
+ slapi_log_error( SLAPI_LOG_TRACE, IPAMO_PLUGIN_SUBSYSTEM,
+ "--> ipamo_postop_del\n" );
+
+ if(ipamo_oktodo(pb) && (dn = ipamo_getdn(pb)))
+ {
+ struct slapi_entry *e = NULL;
+
+ slapi_pblock_get( pb, SLAPI_ENTRY_PRE_OP, &e );
+
+ ipamo_lock();
+
+ /* remove this group DN from the
+ * membership lists of groups
+ */
+ ipamo_del_dn_from_groups(pb, dn);
+
+ /* is the entry of interest as a group? */
+ if(e && !slapi_filter_test_simple(e, ipa_group_filter))
+ {
+ Slapi_Attr *attr = 0;
+
+ if(0 == slapi_entry_attr_find(e, IPA_GROUP_ATTR, &attr))
+ {
+ ipamo_del_attr_list(pb, dn, attr);
+ }
+ }
+
+ ipamo_unlock();
+ }
+
+ slapi_log_error( SLAPI_LOG_TRACE, IPAMO_PLUGIN_SUBSYSTEM,
+ "<-- ipamo_postop_del\n" );
+ return ret;
+}
+
+typedef struct _del_dn_data
+{
+ char *dn;
+ char *type;
+} del_dn_data;
+
+int ipamo_del_dn_from_groups(Slapi_PBlock *pb, char *dn)
+{
+ del_dn_data data = {dn, IPA_GROUP_ATTR};
+
+ ipamo_call_foreach_dn(pb, dn, IPA_GROUP_ATTR, ipamo_del_dn_type_callback, &data);
+}
+
+int ipamo_del_dn_type_callback(Slapi_Entry *e, void *callback_data)
+{
+ int rc = 0;
+ LDAPMod mod;
+ LDAPMod *mods[2];
+ char *val[2];
+ Slapi_PBlock *mod_pb = 0;
+
+ mod_pb = slapi_pblock_new();
+
+ mods[0] = &mod;
+ mods[1] = 0;
+
+ val[0] = ((del_dn_data *)callback_data)->dn;
+ val[1] = 0;
+
+ mod.mod_op = LDAP_MOD_DELETE;
+ mod.mod_type = ((del_dn_data *)callback_data)->type;
+ mod.mod_values = val;
+
+ slapi_modify_internal_set_pb(
+ mod_pb, slapi_entry_get_dn(e),
+ mods, 0, 0,
+ ipamo_get_plugin_id(), 0);
+
+ slapi_modify_internal_pb(mod_pb);
+
+ slapi_pblock_get(mod_pb,
+ SLAPI_PLUGIN_INTOP_RESULT,
+ &rc);
+
+ slapi_pblock_destroy(mod_pb);
+
+ return rc;
+}
+
+int ipamo_call_foreach_dn(Slapi_PBlock *pb, char *dn,
+ char *type, plugin_search_entry_callback callback, void *callback_data)
+{
+ int rc = 0;
+ Slapi_PBlock *search_pb = slapi_pblock_new();
+ Slapi_Backend *be = 0;
+ Slapi_DN *sdn = 0;
+ Slapi_DN *base_sdn = 0;
+ char *attrlist[2] = {"1.1",0};
+ char *filter_str = 0;
+
+ /* get the base dn for the backend we are in
+ (we don't support having members and groups in
+ different backends - issues with offline / read only backends)
+ */
+ sdn = slapi_sdn_new_dn_byref(dn);
+ be = slapi_be_select(sdn);
+ if(be)
+ {
+ base_sdn = (Slapi_DN*)slapi_be_getsuffix(be,0);
+ }
+
+
+ if(base_sdn)
+ {
+ int filter_size =
+ (strlen(type) +
+ strlen(dn) + 4); /* 4 for (=) + null */
+ filter_str = (char*)slapi_ch_malloc(filter_size);
+
+ sprintf(filter_str, "(%s=%s)", type, dn);
+ }
+
+ if(filter_str)
+ {
+ slapi_search_internal_set_pb(search_pb, slapi_sdn_get_dn(base_sdn),
+ LDAP_SCOPE_SUBTREE, filter_str, 0, 0,
+ 0, 0,
+ ipamo_get_plugin_id(),
+ 0);
+
+ slapi_search_internal_callback_pb(search_pb,
+ callback_data,
+ 0, callback,
+ 0);
+ }
+
+ slapi_sdn_free(&sdn);
+ slapi_pblock_destroy(search_pb);
+ slapi_ch_free_string(&filter_str);
+ return rc;
+}
+
+/*
+ * ipamo_postop_modrdn()
+ *
+ * All entries with a memberOf attribute that contains the old group DN get retrieved
+ * and have the old group DN deleted and the new group DN added to their memberOf attribute
+ */
+int ipamo_postop_modrdn(Slapi_PBlock *pb)
+{
+ int ret = 0;
+ char *dn = 0;
+
+ slapi_log_error( SLAPI_LOG_TRACE, IPAMO_PLUGIN_SUBSYSTEM,
+ "--> ipamo_postop_modrdn\n" );
+
+ if(ipamo_oktodo(pb))
+ {
+ struct slapi_entry *pre_e = NULL;
+ struct slapi_entry *post_e = NULL;
+ char *pre_dn = 0;
+ char *post_dn = 0;
+
+ slapi_pblock_get( pb, SLAPI_ENTRY_PRE_OP, &pre_e );
+ slapi_pblock_get( pb, SLAPI_ENTRY_POST_OP, &post_e );
+
+ if(pre_e && post_e)
+ {
+ pre_dn = slapi_entry_get_ndn(pre_e);
+ post_dn = slapi_entry_get_ndn(post_e);
+ }
+
+ /* is the entry of interest? */
+ if(pre_dn && post_dn &&
+ !slapi_filter_test_simple(post_e, ipa_group_filter))
+ {
+ Slapi_Attr *attr = 0;
+
+ ipamo_lock();
+
+ if(0 == slapi_entry_attr_find(post_e, IPA_GROUP_ATTR, &attr))
+ {
+ ipamo_moddn_attr_list(pb, pre_dn, post_dn, attr);
+ }
+
+ /* modrdn must change the dns in groups that have
+ * this group as a member.
+ */
+ ipamo_replace_dn_from_groups(pb, pre_dn, post_dn);
+
+ ipamo_unlock();
+ }
+ }
+
+
+ slapi_log_error( SLAPI_LOG_TRACE, IPAMO_PLUGIN_SUBSYSTEM,
+ "<-- ipamo_postop_modrdn\n" );
+ return ret;slapi_log_error( SLAPI_LOG_TRACE, IPAMO_PLUGIN_SUBSYSTEM,
+ "--> ipamo_postop_modify\n" );
+}
+
+typedef struct _replace_dn_data
+{
+ char *pre_dn;
+ char *post_dn;
+ char *type;
+} replace_dn_data;
+
+int ipamo_replace_dn_from_groups(Slapi_PBlock *pb, char *pre_dn, char *post_dn)
+{
+ replace_dn_data data = {pre_dn, post_dn, IPA_GROUP_ATTR};
+
+ return ipamo_call_foreach_dn(pb, pre_dn, IPA_GROUP_ATTR,
+ ipamo_replace_dn_type_callback, &data);
+}
+
+
+int ipamo_replace_dn_type_callback(Slapi_Entry *e, void *callback_data)
+{
+ int rc = 0;
+ LDAPMod delmod;
+ LDAPMod addmod;
+ LDAPMod *mods[3];
+ char *delval[2];
+ char *addval[2];
+ Slapi_PBlock *mod_pb = 0;
+
+ mod_pb = slapi_pblock_new();
+
+ mods[0] = &delmod;
+ mods[1] = &addmod;
+ mods[2] = 0;
+
+ delval[0] = ((replace_dn_data *)callback_data)->pre_dn;
+ delval[1] = 0;
+
+ delmod.mod_op = LDAP_MOD_DELETE;
+ delmod.mod_type = ((replace_dn_data *)callback_data)->type;
+ delmod.mod_values = delval;
+
+ addval[0] = ((replace_dn_data *)callback_data)->post_dn;
+ addval[1] = 0;
+
+ addmod.mod_op = LDAP_MOD_ADD;
+ addmod.mod_type = ((replace_dn_data *)callback_data)->type;
+ addmod.mod_values = addval;
+
+ slapi_modify_internal_set_pb(
+ mod_pb, slapi_entry_get_dn(e),
+ mods, 0, 0,
+ ipamo_get_plugin_id(), 0);
+
+ slapi_modify_internal_pb(mod_pb);
+
+ slapi_pblock_get(mod_pb,
+ SLAPI_PLUGIN_INTOP_RESULT,
+ &rc);
+
+ slapi_pblock_destroy(mod_pb);
+
+ return rc;
+}
+
+/*
+ * ipamo_postop_modify()
+ *
+ * Added members are retrieved and have the group DN added to their memberOf attribute
+ * Deleted members are retrieved and have the group DN deleted from their memberOf attribute
+ * On replace of the membership attribute values:
+ * 1. Sort old and new values
+ * 2. Iterate through both lists at same time
+ * 3. Any value not in old list but in new list - add group DN to memberOf attribute
+ * 4. Any value in old list but not in new list - remove group DN from memberOf attribute
+ *
+ * Note: this will suck for large groups but nonetheless is optimal (it's linear) given
+ * current restrictions i.e. originally adding members in sorted order would allow
+ * us to sort one list only (the new one) but that is under server control, not this plugin
+ */
+int ipamo_postop_modify(Slapi_PBlock *pb)
+{
+ int ret = 0;
+ char *dn = 0;
+ Slapi_Mods *smods = 0;
+ Slapi_Mod *smod = 0;
+ LDAPMod **mods;
+ Slapi_Mod *next_mod = 0;
+
+ slapi_log_error( SLAPI_LOG_TRACE, IPAMO_PLUGIN_SUBSYSTEM,
+ "--> ipamo_postop_modify\n" );
+
+ if(ipamo_oktodo(pb) &&
+ (dn = ipamo_getdn(pb)))
+ {
+ /* get the mod set */
+ slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods);
+ smods = slapi_mods_new();
+ slapi_mods_init_passin(smods, mods);
+
+ next_mod = slapi_mod_new();
+ smod = slapi_mods_get_first_smod(smods, next_mod);
+ while(smod)
+ {
+ char *type = (char *)slapi_mod_get_type(smod);
+
+ /* we only care about the group attribute */
+ if(slapi_attr_types_equivalent(type,IPA_GROUP_ATTR))
+ {
+ int op = slapi_mod_get_operation(smod);
+
+ ipamo_lock();
+
+ /* the modify op decides the function */
+ switch(op & ~LDAP_MOD_BVALUES)
+ {
+ case LDAP_MOD_ADD:
+ {
+ /* add group DN to targets */
+ ipamo_add_smod_list(pb, dn, smod);
+ break;
+ }
+
+ case LDAP_MOD_DELETE:
+ {
+ /* remove group DN from targets */
+ ipamo_del_smod_list(pb, dn, smod);
+ break;
+ }
+
+ case LDAP_MOD_REPLACE:
+ {
+ /* replace current values */
+ ipamod_replace_list(pb, dn);
+ break;
+ }
+
+ default:
+ {
+ slapi_log_error(
+ SLAPI_LOG_PLUGIN,
+ IPAMO_PLUGIN_SUBSYSTEM,
+ "ipamo_postop_modify: unknown mod type\n" );
+ break;
+ }
+ }
+
+ ipamo_unlock();
+ }
+
+ slapi_mod_done(next_mod);
+ smod = slapi_mods_get_next_smod(smods, next_mod);
+ }
+
+ slapi_mod_free(&next_mod);
+ }
+
+ slapi_log_error( SLAPI_LOG_TRACE, IPAMO_PLUGIN_SUBSYSTEM,
+ "<-- ipamo_postop_modify\n" );
+ return ret;
+}
+
+
+/*
+ * ipamo_postop_add()
+ *
+ * All members in the membership attribute of the new entry get retrieved
+ * and have the group DN added to their memberOf attribute
+ */
+int ipamo_postop_add(Slapi_PBlock *pb)
+{
+ int ret = 0;
+ char *dn = 0;
+
+ slapi_log_error( SLAPI_LOG_TRACE, IPAMO_PLUGIN_SUBSYSTEM,
+ "--> ipamo_postop_add\n" );
+
+ if(ipamo_oktodo(pb) && (dn = ipamo_getdn(pb)))
+ {
+ struct slapi_entry *e = NULL;
+
+ slapi_pblock_get( pb, SLAPI_ENTRY_POST_OP, &e );
+
+ /* is the entry of interest? */
+ if(e && !slapi_filter_test_simple(e, ipa_group_filter))
+ {
+ Slapi_Attr *attr = 0;
+
+ ipamo_lock();
+
+ if(0 == slapi_entry_attr_find(e, IPA_GROUP_ATTR, &attr))
+ {
+ ipamo_add_attr_list(pb, dn, attr);
+ }
+
+ ipamo_unlock();
+ }
+ }
+
+ slapi_log_error( SLAPI_LOG_TRACE, IPAMO_PLUGIN_SUBSYSTEM,
+ "<-- ipamo_postop_add\n" );
+ return ret;
+}
+
+/*** Support functions ***/
+
+/*
+ * ipamo_oktodo()
+ *
+ * Check that the op succeeded
+ * Note: we also respond to replicated ops so we don't test for that
+ * this does require that the memberOf attribute not be replicated
+ * and this means that memberof is consistent with local state
+ * not the network system state
+ *
+ */
+int ipamo_oktodo(Slapi_PBlock *pb)
+{
+ int ret = 1;
+ int oprc = 0;
+
+ slapi_log_error( SLAPI_LOG_TRACE, IPAMO_PLUGIN_SUBSYSTEM,
+ "--> ipamo_postop_oktodo\n" );
+
+ if(slapi_pblock_get(pb, SLAPI_PLUGIN_OPRETURN, &oprc) != 0)
+ {
+ slapi_log_error( SLAPI_LOG_FATAL, IPAMO_PLUGIN_SUBSYSTEM,
+ "ipamo_postop_oktodo: could not get parameters\n" );
+ ret = -1;
+ }
+
+ /* this plugin should only execute if the operation succeeded
+ */
+ if(oprc != 0)
+ {
+ ret = 0;
+ }
+
+ slapi_log_error( SLAPI_LOG_TRACE, IPAMO_PLUGIN_SUBSYSTEM,
+ "<-- ipamo_postop_oktodo\n" );
+
+ return ret;
+}
+
+/*
+ * ipamo_getdn()
+ *
+ * Get dn of target entry
+ *
+ */
+char *ipamo_getdn(Slapi_PBlock *pb)
+{
+ char *dn = 0;
+
+ slapi_pblock_get(pb, SLAPI_TARGET_DN, &dn);
+
+ return dn;
+}
+
+/*
+ * ipamo_modop_one()
+ *
+ * Perform op on memberof attribute of op_to using op_this as the value
+ * However, if op_to happens to be a group, we must arrange for the group
+ * members to have the mod performed on them instead, and we must take
+ * care to not recurse when we have visted a group before
+ *
+ * Also, we must not delete entries that are a member of the group
+ */
+int ipamo_modop_one(Slapi_PBlock *pb, int mod_op, char *op_this, char *op_to)
+{
+ ipamo_modop_one_r(pb, mod_op, op_this, op_this, op_to, 0);
+}
+
+/* ipamo_modop_one_r()
+ *
+ * recursive function to perform above (most things don't need the replace arg)
+ */
+
+int ipamo_modop_one_r(Slapi_PBlock *pb, int mod_op, char *group_dn,
+ char *op_this, char *op_to, ipamostringll *stack)
+{
+ return ipamo_modop_one_replace_r(
+ pb, mod_op, group_dn, op_this, 0, op_to, stack);
+}
+
+/* ipamo_modop_one_replace_r()
+ *
+ * recursive function to perform above (with added replace arg)
+ */
+int ipamo_modop_one_replace_r(Slapi_PBlock *pb, int mod_op, char *group_dn,
+ char *op_this, char *replace_with, char *op_to, ipamostringll *stack)
+{
+ int rc = 0;
+ LDAPMod mod;
+ LDAPMod replace_mod;
+ LDAPMod *mods[3];
+ char *val[2];
+ char *replace_val[2];
+ Slapi_PBlock *mod_pb = 0;
+ char *attrlist[2] = {IPA_GROUP_ATTR,0};
+ Slapi_DN *op_to_sdn = 0;
+ Slapi_Entry *e = 0;
+ ipamostringll *ll = 0;
+ char *op_str = 0;
+
+ /* determine if this is a group op or single entry */
+ op_to_sdn = slapi_sdn_new_dn_byref(op_to);
+ slapi_search_internal_get_entry( op_to_sdn, attrlist,
+ &e, ipamo_get_plugin_id());
+ slapi_sdn_free(&op_to_sdn);
+ if(!e)
+ {
+ if(LDAP_MOD_DELETE == mod_op)
+ {
+ /* in the case of delete we must guard against
+ * having groups in a nested chain having been
+ * deleted during the window of opportunity
+ * and we must fall back to testing all members
+ * of the (potentially deleted group) for valid
+ * membership given the delete operation that
+ * triggered this operation
+ */
+ ipamo_test_membership(pb, group_dn);
+ }
+
+ goto bail;
+ }
+
+ if(LDAP_MOD_DELETE == mod_op)
+ {
+ op_str = "DELETE";
+ }
+ else if(LDAP_MOD_ADD == mod_op)
+ {
+ op_str = "ADD";
+ }
+
+ slapi_log_error( SLAPI_LOG_PLUGIN, IPAMO_PLUGIN_SUBSYSTEM,
+ "ipamo_modop_one_r: %s %s in %s\n"
+ ,op_str, op_this, op_to);
+
+ if(!slapi_filter_test_simple(e, ipa_group_filter))
+ {
+ /* group */
+ Slapi_Value *ll_dn_val = 0;
+ Slapi_Value *to_dn_val = slapi_value_new_string(op_to);
+ Slapi_Attr *members = 0;
+
+ ll = stack;
+
+ /* have we been here before? */
+ while(ll)
+ {
+ ll_dn_val = slapi_value_new_string(ll->dn);
+
+ if(0 == ipamo_compare(&ll_dn_val, &to_dn_val))
+ {
+ slapi_value_free(&to_dn_val);
+ slapi_value_free(&ll_dn_val);
+
+ /* someone set up infinitely
+ recursive groups - crash here please */
+ slapi_log_error( SLAPI_LOG_PLUGIN,
+ IPAMO_PLUGIN_SUBSYSTEM,
+ "ipamo_modop_one_r: group recursion"
+ " detected in %s\n"
+ ,op_to);
+ goto bail;
+ }
+
+ slapi_value_free(&ll_dn_val);
+ ll = ll->next;
+ }
+
+ slapi_value_free(&to_dn_val);
+
+ /* do op on group */
+ slapi_log_error( SLAPI_LOG_PLUGIN,
+ IPAMO_PLUGIN_SUBSYSTEM,
+ "ipamo_modop_one_r: descending into group %s\n",
+ op_to);
+ ll = (ipamostringll*)slapi_ch_malloc(sizeof(ipamostringll));
+ ll->dn = group_dn;
+ ll->next = stack;
+
+ slapi_entry_attr_find( e, IPA_GROUP_ATTR, &members );
+ if(members)
+ {
+ ipamo_mod_attr_list_r(pb, mod_op, group_dn, op_this, members, ll);
+ }
+
+ slapi_ch_free((void**)&ll);
+ }
+ /* continue with operation */
+ {
+ if(stack && LDAP_MOD_DELETE == mod_op)
+ {
+ if(ipamo_is_legit_member(pb, group_dn,
+ op_this, op_to, stack))
+ {
+ /* entry is member some other way too */
+ slapi_log_error( SLAPI_LOG_PLUGIN,
+ IPAMO_PLUGIN_SUBSYSTEM,
+ "ipamo_modop_one_r: not deleting %s\n"
+ ,op_to);
+ goto bail;
+ }
+ }
+
+ /* single entry - do mod */
+ mod_pb = slapi_pblock_new();
+
+ mods[0] = &mod;
+ if(LDAP_MOD_REPLACE == mod_op)
+ {
+ mods[1] = &replace_mod;
+ mods[2] = 0;
+ }
+ else
+ {
+ mods[1] = 0;
+ }
+
+ val[0] = op_this;
+ val[1] = 0;
+
+ mod.mod_op = LDAP_MOD_REPLACE == mod_op?LDAP_MOD_DELETE:mod_op;
+ mod.mod_type = IPA_MEMBEROF_ATTR;
+ mod.mod_values = val;
+
+ if(LDAP_MOD_REPLACE == mod_op)
+ {
+ replace_val[0] = replace_with;
+ replace_val[1] = 0;
+
+ replace_mod.mod_op = LDAP_MOD_ADD;
+ replace_mod.mod_type = IPA_MEMBEROF_ATTR;
+ replace_mod.mod_values = replace_val;
+ }
+
+ slapi_modify_internal_set_pb(
+ mod_pb, op_to,
+ mods, 0, 0,
+ ipamo_get_plugin_id(), 0);
+
+ slapi_modify_internal_pb(mod_pb);
+
+ slapi_pblock_get(mod_pb,
+ SLAPI_PLUGIN_INTOP_RESULT,
+ &rc);
+
+ slapi_pblock_destroy(mod_pb);
+
+ if(LDAP_MOD_DELETE == mod_op)
+ {
+ /* fix up membership for groups that have been orphaned */
+ ipamo_test_membership_callback(e, 0);
+ }
+
+ if(LDAP_MOD_ADD == mod_op)
+ {
+ /* fix up membership for groups that are now in scope */
+ ipamo_add_membership(pb, op_this, op_to);
+ }
+ }
+
+bail:
+ return rc;
+}
+
+
+/*
+ * ipamo_add_one()
+ *
+ * Add addthis DN to the memberof attribute of addto
+ *
+ */
+int ipamo_add_one(Slapi_PBlock *pb, char *addthis, char *addto)
+{
+ return ipamo_modop_one(pb, LDAP_MOD_ADD, addthis, addto);
+}
+
+/*
+ * ipamo_del_one()
+ *
+ * Delete delthis DN from the memberof attribute of delfrom
+ *
+ */
+int ipamo_del_one(Slapi_PBlock *pb, char *delthis, char *delfrom)
+{
+ return ipamo_modop_one(pb, LDAP_MOD_DELETE, delthis, delfrom);
+}
+
+/*
+ * ipamo_mod_smod_list()
+ *
+ * Perform mod for group DN to the memberof attribute of the list of targets
+ *
+ */
+int ipamo_mod_smod_list(Slapi_PBlock *pb, int mod, char *group_dn, Slapi_Mod *smod)
+{
+ int rc = 0;
+ struct berval *bv = slapi_mod_get_first_value(smod);
+ int last_size = 0;
+ char *last_str = 0;
+
+ while(bv)
+ {
+ char *dn_str = 0;
+
+ if(last_size > bv->bv_len)
+ {
+ dn_str = last_str;
+ }
+ else
+ {
+ int the_size = (bv->bv_len * 2) + 1;
+
+ if(last_str)
+ slapi_ch_free_string(&last_str);
+
+ dn_str = (char*)slapi_ch_malloc(the_size);
+
+ last_str = dn_str;
+ last_size = the_size;
+ }
+
+ memset(dn_str, 0, last_size);
+
+ strncpy(dn_str, bv->bv_val, (size_t)bv->bv_len);
+
+ ipamo_modop_one(pb, mod, group_dn, dn_str);
+
+ bv = slapi_mod_get_next_value(smod);
+ }
+
+ if(last_str)
+ slapi_ch_free_string(&last_str);
+
+ return rc;
+}
+
+/*
+ * ipamo_add_smod_list()
+ *
+ * Add group DN to the memberof attribute of the list of targets
+ *
+ */
+int ipamo_add_smod_list(Slapi_PBlock *pb, char *groupdn, Slapi_Mod *smod)
+{
+ return ipamo_mod_smod_list(pb, LDAP_MOD_ADD, groupdn, smod);
+}
+
+
+/*
+ * ipamo_del_smod_list()
+ *
+ * Remove group DN from the memberof attribute of the list of targets
+ *
+ */
+int ipamo_del_smod_list(Slapi_PBlock *pb, char *groupdn, Slapi_Mod *smod)
+{
+ return ipamo_mod_smod_list(pb, LDAP_MOD_DELETE, groupdn, smod);
+}
+
+/**
+ * Plugin identity mgmt
+ */
+void ipamo_set_plugin_id(void * plugin_id)
+{
+ _PluginID=plugin_id;
+}
+
+void * ipamo_get_plugin_id()
+{
+ return _PluginID;
+}
+
+
+/*
+ * ipamo_mod_attr_list()
+ *
+ * Perform mod for group DN to the memberof attribute of the list of targets
+ *
+ */
+int ipamo_mod_attr_list(Slapi_PBlock *pb, int mod, char *group_dn, Slapi_Attr *attr)
+{
+ return ipamo_mod_attr_list_r(pb, mod, group_dn, group_dn, attr, 0);
+}
+
+int ipamo_mod_attr_list_r(Slapi_PBlock *pb, int mod, char *group_dn, char *op_this,
+ Slapi_Attr *attr, ipamostringll *stack)
+{
+ int rc = 0;
+ Slapi_Value *val = 0;
+ int last_size = 0;
+ char *last_str = 0;
+ int hint = slapi_attr_first_value(attr, &val);
+
+ while(val)
+ {
+ char *dn_str = 0;
+ struct berval *bv = (struct berval *)slapi_value_get_berval(val);
+
+ if(last_size > bv->bv_len)
+ {
+ dn_str = last_str;
+ }
+ else
+ {
+ int the_size = (bv->bv_len * 2) + 1;
+
+ if(last_str)
+ slapi_ch_free_string(&last_str);
+
+ dn_str = (char*)slapi_ch_malloc(the_size);
+
+ last_str = dn_str;
+ last_size = the_size;
+ }
+
+ memset(dn_str, 0, last_size);
+
+ strncpy(dn_str, bv->bv_val, (size_t)bv->bv_len);
+
+ ipamo_modop_one_r(pb, mod, group_dn, op_this, dn_str, stack);
+
+ hint = slapi_attr_next_value(attr, hint, &val);
+ }
+
+ if(last_str)
+ slapi_ch_free_string(&last_str);
+
+ return rc;
+}
+
+/*
+ * ipamo_add_attr_list()
+ *
+ * Add group DN to the memberof attribute of the list of targets
+ *
+ */
+int ipamo_add_attr_list(Slapi_PBlock *pb, char *groupdn, Slapi_Attr *attr)
+{
+ return ipamo_mod_attr_list(pb, LDAP_MOD_ADD, groupdn, attr);
+}
+
+/*
+ * ipamo_del_attr_list()
+ *
+ * Remove group DN from the memberof attribute of the list of targets
+ *
+ */
+int ipamo_del_attr_list(Slapi_PBlock *pb, char *groupdn, Slapi_Attr *attr)
+{
+ return ipamo_mod_attr_list(pb, LDAP_MOD_DELETE, groupdn, attr);
+}
+
+/*
+ * ipamo_moddn_attr_list()
+ *
+ * Perform mod for group DN to the memberof attribute of the list of targets
+ *
+ */
+int ipamo_moddn_attr_list(Slapi_PBlock *pb, char *pre_dn, char *post_dn, Slapi_Attr *attr)
+{
+ int rc = 0;
+ Slapi_Value *val = 0;
+ int last_size = 0;
+ char *last_str = 0;
+ int hint = slapi_attr_first_value(attr, &val);
+
+ while(val)
+ {
+ char *dn_str = 0;
+ struct berval *bv = (struct berval *)slapi_value_get_berval(val);
+
+ if(last_size > bv->bv_len)
+ {
+ dn_str = last_str;
+ }
+ else
+ {
+ int the_size = (bv->bv_len * 2) + 1;
+
+ if(last_str)
+ slapi_ch_free_string(&last_str);
+
+ dn_str = (char*)slapi_ch_malloc(the_size);
+
+ last_str = dn_str;
+ last_size = the_size;
+ }
+
+ memset(dn_str, 0, last_size);
+
+ strncpy(dn_str, bv->bv_val, (size_t)bv->bv_len);
+
+ ipamo_modop_one_replace_r(pb, LDAP_MOD_REPLACE,
+ post_dn, pre_dn, post_dn, dn_str, 0);
+
+ hint = slapi_attr_next_value(attr, hint, &val);
+ }
+
+ if(last_str)
+ slapi_ch_free_string(&last_str);
+
+ return rc;
+}
+
+typedef struct _ipamo_add_groups
+{
+ char *target_dn;
+ char *group_dn;
+} ipamo_add_groups;
+
+int ipamo_add_membership(Slapi_PBlock *pb, char *op_this, char *op_to)
+{
+ ipamo_add_groups data = {op_to, op_this};
+
+ return ipamo_call_foreach_dn(pb, op_this, IPA_GROUP_ATTR,
+ ipamo_add_groups_search_callback, &data);
+}
+
+int ipamo_add_groups_search_callback(Slapi_Entry *e, void *callback_data)
+{
+ return ipamo_add_one(0, slapi_entry_get_dn(e),
+ ((ipamo_add_groups*)callback_data)->target_dn);
+}
+
+/* ipamo_is_group_member()
+ * tests membership of memberdn in group groupdn
+ * returns non-zero when true, zero otherwise
+ */
+int ipamo_is_group_member(Slapi_Value *groupdn, Slapi_Value *memberdn)
+{
+ int rc = 0;
+ Slapi_DN *sdn = 0;
+ char *attrlist[2] = {IPA_GROUP_ATTR,0};
+ Slapi_Entry *group_e = 0;
+ Slapi_Attr *attr = 0;
+
+ sdn = slapi_sdn_new_dn_byref(slapi_value_get_string(groupdn));
+
+ slapi_search_internal_get_entry(sdn, attrlist,
+ &group_e, ipamo_get_plugin_id());
+
+ if(group_e)
+ {
+ slapi_entry_attr_find(group_e, IPA_GROUP_ATTR, &attr );
+ if(attr)
+ {
+ rc = 0 == slapi_attr_value_find(
+ attr, slapi_value_get_berval(memberdn));
+ }
+ }
+
+ slapi_sdn_free(&sdn);
+ return rc;
+}
+
+/* ipamo_memberof_search_callback()
+ * for each attribute in the member of attribute
+ * determine if the entry is still a member
+ *
+ * test each for direct membership
+ * move groups entry is member of to member group
+ * test remaining groups for membership in member groups
+ * iterate until a pass fails to move a group over to member groups
+ * remaining groups should be deleted
+ */
+int ipamo_test_membership(Slapi_PBlock *pb, char *dn)
+{
+ return ipamo_call_foreach_dn(pb, dn, IPA_MEMBEROF_ATTR,
+ ipamo_test_membership_callback ,0);
+}
+
+int ipamo_test_membership_callback(Slapi_Entry *e, void *callback_data)
+{
+ int rc = 0;
+ Slapi_Attr *attr = 0;
+ int total = 0;
+ Slapi_Value **member_array = 0;
+ Slapi_Value **candidate_array = 0;
+ Slapi_Value *entry_dn = 0;
+
+ entry_dn = slapi_value_new_string(slapi_entry_get_dn(e));
+
+ if(0 == entry_dn)
+ {
+ goto bail;
+ }
+
+ /* divide groups into member and non-member lists */
+ slapi_entry_attr_find(e, IPA_MEMBEROF_ATTR, &attr );
+ if(attr)
+ {
+ slapi_attr_get_numvalues( attr, &total);
+ if(total)
+ {
+ Slapi_Value *val = 0;
+ int hint = 0;
+ int c_index = 0;
+ int m_index = 0;
+ int member_found = 1;
+ int outer_index = 0;
+
+ candidate_array =
+ (Slapi_Value**)
+ slapi_ch_malloc(sizeof(Slapi_Value*)*total);
+ memset(candidate_array, 0, sizeof(Slapi_Value*)*total);
+ member_array =
+ (Slapi_Value**)
+ slapi_ch_malloc(sizeof(Slapi_Value*)*total);
+ memset(member_array, 0, sizeof(Slapi_Value*)*total);
+
+ hint = slapi_attr_first_value(attr, &val);
+
+ while(val)
+ {
+ /* test for membership */
+ if(ipamo_is_group_member(val, entry_dn))
+ {
+ /* it is a member */
+ member_array[m_index] = val;
+ m_index++;
+ }
+ else
+ {
+ /* not a member, still a candidate */
+ candidate_array[c_index] = val;
+ c_index++;
+ }
+
+ hint = slapi_attr_next_value(attr, hint, &val);
+ }
+
+ /* now iterate over members testing for membership
+ in candidate groups and moving candidates to members
+ when successful, quit when a full iteration adds no
+ new members
+ */
+ while(member_found)
+ {
+ member_found = 0;
+
+ while(outer_index < m_index)
+ {
+ int inner_index = 0;
+
+ while(inner_index < c_index)
+ {
+ if((void*)1 ==
+ candidate_array[inner_index])
+ {
+ /* was moved, skip */
+ inner_index++;
+ continue;
+ }
+
+ if(ipamo_is_group_member(
+ candidate_array[inner_index],
+ member_array[outer_index]))
+ {
+ member_array[m_index] =
+ candidate_array
+ [inner_index];
+ m_index++;
+
+ candidate_array[inner_index] =
+ (void*)1;
+
+ member_found = 1;
+ }
+
+ inner_index++;
+ }
+
+ outer_index++;
+ }
+ }
+
+ /* here we are left only with values to delete
+ from the memberof attribute in the candidate list
+ */
+ outer_index = 0;
+ while(outer_index < c_index)
+ {
+ if((void*)1 == candidate_array[outer_index])
+ {
+ /* item moved, skip */
+ outer_index++;
+ continue;
+ }
+
+ ipamo_del_one(
+ 0,
+ (char*)slapi_value_get_string(
+ candidate_array[outer_index]),
+ (char*)slapi_value_get_string(entry_dn));
+
+ outer_index++;
+ }
+
+ slapi_ch_free((void**)&candidate_array);
+ slapi_ch_free((void**)&member_array);
+ }
+ }
+
+bail:
+ slapi_value_free(&entry_dn);
+
+ return rc;
+}
+
+/*
+ * ipamo_replace_list()
+ *
+ * Perform replace the group DN list in the memberof attribute of the list of targets
+ *
+ */
+int ipamod_replace_list(Slapi_PBlock *pb, char *group_dn)
+{
+ struct slapi_entry *pre_e = NULL;
+ struct slapi_entry *post_e = NULL;
+ Slapi_Attr *pre_attr = 0;
+ Slapi_Attr *post_attr = 0;
+
+ slapi_pblock_get( pb, SLAPI_ENTRY_PRE_OP, &pre_e );
+ slapi_pblock_get( pb, SLAPI_ENTRY_POST_OP, &post_e );
+
+ if(pre_e && post_e)
+ {
+ slapi_entry_attr_find( pre_e, IPA_GROUP_ATTR, &pre_attr );
+ slapi_entry_attr_find( post_e, IPA_GROUP_ATTR, &post_attr );
+ }
+
+ if(pre_attr || post_attr)
+ {
+ int pre_total = 0;
+ int post_total = 0;
+ Slapi_Value **pre_array = 0;
+ Slapi_Value **post_array = 0;
+ int pre_index = 0;
+ int post_index = 0;
+
+ /* create arrays of values */
+ if(pre_attr)
+ {
+ slapi_attr_get_numvalues( pre_attr, &pre_total);
+ }
+
+ if(post_attr)
+ {
+ slapi_attr_get_numvalues( post_attr, &post_total);
+ }
+
+ if(pre_total)
+ {
+ pre_array =
+ (Slapi_Value**)
+ slapi_ch_malloc(sizeof(Slapi_Value*)*pre_total);
+ ipamo_load_array(pre_array, pre_attr);
+ qsort(
+ pre_array,
+ pre_total,
+ sizeof(Slapi_Value*),
+ ipamo_compare);
+ }
+
+ if(post_total)
+ {
+ post_array =
+ (Slapi_Value**)
+ slapi_ch_malloc(sizeof(Slapi_Value*)*post_total);
+ ipamo_load_array(post_array, post_attr);
+ qsort(
+ post_array,
+ post_total,
+ sizeof(Slapi_Value*),
+ ipamo_compare);
+ }
+
+
+ /* work through arrays, following these rules:
+ in pre, in post, do nothing
+ in pre, not in post, delete from entry
+ not in pre, in post, add to entry
+ */
+ while(pre_index < pre_total || post_index < post_total)
+ {
+ if(pre_index == pre_total)
+ {
+ /* add the rest of post */
+ ipamo_add_one(
+ pb,
+ group_dn,
+ (char*)slapi_value_get_string(
+ post_array[post_index]));
+
+ post_index++;
+ }
+ else if(post_index == post_total)
+ {
+ /* delete the rest of pre */
+ ipamo_del_one(
+ pb,
+ group_dn,
+ (char*)slapi_value_get_string(
+ pre_array[pre_index]));
+
+ pre_index++;
+ }
+ else
+ {
+ /* decide what to do */
+ int cmp = ipamo_compare(
+ &(pre_array[pre_index]),
+ &(post_array[post_index]));
+
+ if(cmp < 0)
+ {
+ /* delete pre array */
+ ipamo_del_one(
+ pb,
+ group_dn,
+ (char*)slapi_value_get_string(
+ pre_array[pre_index]));
+
+ pre_index++;
+ }
+ else if(cmp > 0)
+ {
+ /* add post array */
+ ipamo_add_one(
+ pb,
+ group_dn,
+ (char*)slapi_value_get_string(
+ post_array[post_index]));
+
+ post_index++;
+ }
+ else
+ {
+ /* do nothing, advance */
+ pre_index++;
+ post_index++;
+ }
+ }
+ }
+ }
+
+}
+
+/* ipamo_load_array()
+ *
+ * put attribute values in array structure
+ */
+void ipamo_load_array(Slapi_Value **array, Slapi_Attr *attr)
+{
+ Slapi_Value *val = 0;
+ int hint = slapi_attr_first_value(attr, &val);
+
+ while(val)
+ {
+ *array = val;
+ array++;
+ hint = slapi_attr_next_value(attr, hint, &val);
+ }
+}
+
+/* ipamo_compare()
+ *
+ * compare two attr values
+ */
+int ipamo_compare(const void *a, const void *b)
+{
+ static Slapi_Attr *attr = 0;
+ static int first_time = 1;
+ Slapi_Value *val1 = *((Slapi_Value **)a);
+ Slapi_Value *val2 = *((Slapi_Value **)b);
+
+ if(first_time)
+ {
+ first_time = 0;
+ attr = slapi_attr_new();
+ slapi_attr_init(attr, IPA_GROUP_ATTR);
+ }
+
+ return slapi_attr_value_cmp(
+ attr,
+ slapi_value_get_berval(val1),
+ slapi_value_get_berval(val2));
+}
+
+/* ipamo_string2filter()
+ *
+ * For some reason slapi_str2filter writes to its input
+ * which means you cannot pass in a string constant
+ * so this is a fix up function for that
+ */
+Slapi_Filter *ipamo_string2filter(char *strfilter)
+{
+ Slapi_Filter *ret = 0;
+ char *idontbelieveit = slapi_ch_strdup(strfilter);
+
+ ret = slapi_str2filter( idontbelieveit );
+
+ slapi_ch_free_string(&idontbelieveit);
+
+ return ret;
+}
+
+/* ipamo_is_legit_member()
+ *
+ * before we rush to remove this group from the entry
+ * we need to be sure that the entry is not a member
+ * of the group for another legitimate reason i.e.
+ * that it is not itself a direct member of the group,
+ * and that all groups in its memberof attribute except
+ * the second from bottom one of our stack do not appear
+ * in the membership attribute of the group
+*/
+int ipamo_is_legit_member(Slapi_PBlock *pb, char *group_dn,
+ char *op_this, char *op_to, ipamostringll *stack)
+{
+ int rc = 0;
+ Slapi_DN *group_sdn = 0;
+ Slapi_Entry *group_e = 0;
+ Slapi_DN *opto_sdn = 0;
+ Slapi_Entry *opto_e = 0;
+ char *filter_str = 0;
+ Slapi_Filter *filter = 0;
+ int filter_size = 0;
+ ipamostringll *ll = 0;
+ char *attrlist[2] = {IPA_GROUP_ATTR,0};
+ char *optolist[2] = {IPA_MEMBEROF_ATTR,0};
+ Slapi_Attr *memberof = 0;
+ Slapi_Value *memberdn = 0;
+ int hint = 0;
+ char *delete_group_dn = 0;
+
+ slapi_log_error( SLAPI_LOG_TRACE, IPAMO_PLUGIN_SUBSYSTEM,
+ "--> ipamo_is_legit_member\n" );
+
+ /* first test entry */
+ group_sdn = slapi_sdn_new_dn_byref(op_this);
+ slapi_search_internal_get_entry( group_sdn, attrlist,
+ &group_e, ipamo_get_plugin_id());
+ slapi_sdn_free(&group_sdn);
+
+ if(!group_e)
+ {
+ goto bail;
+ }
+
+ filter_size = 2 *
+ (strlen(IPA_GROUP_ATTR) +
+ strlen(op_to) + 4); /* 4 for (=) + null */
+ filter_str = (char*)slapi_ch_malloc(filter_size);
+
+ sprintf(filter_str, "(%s=%s)", IPA_GROUP_ATTR, op_to);
+
+ filter = ipamo_string2filter(filter_str);
+
+ if(!slapi_filter_test_simple(group_e, filter))
+ {
+ /* entry is direct member */
+ slapi_log_error( SLAPI_LOG_PLUGIN, IPAMO_PLUGIN_SUBSYSTEM,
+ "ipamo_is_legit_member: %s direct member of %s\n"
+ ,op_to,op_this);
+ slapi_filter_free(filter,0);
+ rc = 1;
+ goto bail;
+ }
+
+ slapi_filter_free(filter,0);
+
+ /* test all group dns in stack
+ the top dn is the group we remove the entry from
+ second from bottom dn is being removed from the
+ bottom group, we ignore those two
+ */
+ ll = stack;
+
+ /* need to be 2 items left on the stack */
+ while( ll &&
+ ll->next &&
+ ((ipamostringll*)ll->next)->next)
+ {
+ ll = ll->next;
+ }
+
+ if(!ll || !ll->next)
+ {
+ /* tight recursion, bail */
+ goto bail;
+ }
+
+ delete_group_dn = ((ipamostringll*)ll->next)->dn;
+
+ /* get the target entry memberof attribute */
+ opto_sdn = slapi_sdn_new_dn_byref(op_to);
+ slapi_search_internal_get_entry( opto_sdn, optolist,
+ &opto_e, ipamo_get_plugin_id());
+ slapi_sdn_free(&opto_sdn);
+
+ if(opto_e)
+ {
+ slapi_entry_attr_find(opto_e, IPA_MEMBEROF_ATTR, &memberof);
+ }
+
+ if(0 == memberof)
+ {
+ goto bail;
+ }
+
+ /* iterate through memberof values and test against group membership */
+ hint = slapi_attr_first_value(memberof, &memberdn);
+
+ while(memberdn)
+ {
+ char *dn = (char*)slapi_value_get_string(memberdn);
+ int current_size =
+ (strlen(IPA_GROUP_ATTR) +
+ strlen(dn) + 4); /* 4 for (=) + null */
+
+ /* disregard the group being removed */
+ if(0 == strcmp(dn, delete_group_dn))
+ {
+ hint = slapi_attr_next_value(memberof, hint, &memberdn);
+ continue;
+ }
+
+ if(current_size > filter_size)
+ {
+ filter_size = 2 * current_size;
+ filter_str = slapi_ch_realloc(
+ filter_str, filter_size);
+ }
+
+ sprintf(filter_str, "(%s=%s)", IPA_GROUP_ATTR, dn);
+ filter = ipamo_string2filter(filter_str);
+
+ if(!slapi_filter_test_simple(group_e, filter))
+ {
+ /* another group allows entry */
+ slapi_log_error( SLAPI_LOG_PLUGIN, IPAMO_PLUGIN_SUBSYSTEM,
+ "ipamo_is_legit_member: %s is group member of %s\n"
+ ,op_to,dn);
+ slapi_filter_free(filter,0);
+
+ rc = 1;
+ goto bail;
+ }
+
+ slapi_filter_free(filter,0);
+
+ hint = slapi_attr_next_value(memberof, hint, &memberdn);
+ }
+
+bail:
+ slapi_ch_free((void**)&filter_str);
+
+ slapi_log_error( SLAPI_LOG_TRACE, IPAMO_PLUGIN_SUBSYSTEM,
+ "<-- ipamo_is_legit_member\n" );
+ return rc;
+}
+
+void ipamo_lock()
+{
+ slapi_lock_mutex(ipamo_operation_lock);
+}
+
+void ipamo_unlock()
+{
+ slapi_unlock_mutex(ipamo_operation_lock);
+}
+
diff --git a/ipa-server/ipa-slapi-plugins/ipa-memberof/memberof-conf.ldif b/ipa-server/ipa-slapi-plugins/ipa-memberof/memberof-conf.ldif
new file mode 100644
index 000000000..d07796173
--- /dev/null
+++ b/ipa-server/ipa-slapi-plugins/ipa-memberof/memberof-conf.ldif
@@ -0,0 +1,13 @@
+dn: cn=ipa-memberof,cn=plugins,cn=config
+objectclass: top
+objectclass: nsSlapdPlugin
+objectclass: extensibleObject
+cn: ipa-memberof
+nsslapd-pluginpath: /usr/lib/fedora-ds/plugins/libipa-memberof-plugin.so
+nsslapd-plugininitfunc: ipamo_postop_init
+nsslapd-plugintype: postoperation
+nsslapd-pluginenabled: on
+nsslapd-pluginid: memberof
+nsslapd-pluginversion: 1.0
+nsslapd-pluginvendor: Red Hat
+nsslapd-plugindescription: Memberof plugin