From ee8431b22d396d658fd5509b1dfe86745b544974 Mon Sep 17 00:00:00 2001
From: Pierangelo Masarati <ando@openldap.org>
Date: Fri, 24 Aug 2007 02:46:55 +0000
Subject: [PATCH] add memberOf overlay

---
 configure                          |   60 +-
 configure.in                       |   15 +
 doc/man/man5/slapo-memberof.5      |  114 ++
 include/portable.hin               |    3 +
 servers/slapd/bconfig.c            |    1 +
 servers/slapd/overlays/Makefile.in |    4 +
 servers/slapd/overlays/memberof.c  | 1947 ++++++++++++++++++++++++++++
 7 files changed, 2135 insertions(+), 9 deletions(-)
 create mode 100644 doc/man/man5/slapo-memberof.5
 create mode 100644 servers/slapd/overlays/memberof.c

diff --git a/configure b/configure
index c2ef190006..ee0e248313 100755
--- a/configure
+++ b/configure
@@ -1051,6 +1051,7 @@ SLAPD Overlay Options:
     --enable-dds  	  Dynamic Directory Services overlay no|yes|mod [no]
     --enable-dyngroup	  Dynamic Group overlay no|yes|mod [no]
     --enable-dynlist	  Dynamic List overlay no|yes|mod [no]
+    --enable-memberof	  Reverse Group Membership overlay no|yes|mod [no]
     --enable-ppolicy	  Password Policy overlay no|yes|mod [no]
     --enable-proxycache	  Proxy Cache overlay no|yes|mod [no]
     --enable-refint	  Referential Integrity overlay no|yes|mod [no]
@@ -3099,6 +3100,7 @@ Overlays="accesslog \
 	dds \
 	dyngroup \
 	dynlist \
+	memberof \
 	ppolicy \
 	proxycache \
 	refint \
@@ -3281,6 +3283,30 @@ else
 fi;
 # end --enable-dynlist
 
+# OpenLDAP --enable-memberof
+
+	# Check whether --enable-memberof or --disable-memberof was given.
+if test "${enable_memberof+set}" = set; then
+  enableval="$enable_memberof"
+
+	ol_arg=invalid
+	for ol_val in no yes mod ; do
+		if test "$enableval" = "$ol_val" ; then
+			ol_arg="$ol_val"
+		fi
+	done
+	if test "$ol_arg" = "invalid" ; then
+		{ { echo "$as_me:$LINENO: error: bad value $enableval for --enable-memberof" >&5
+echo "$as_me: error: bad value $enableval for --enable-memberof" >&2;}
+   { (exit 1); exit 1; }; }
+	fi
+	ol_enable_memberof="$ol_arg"
+
+else
+  	ol_enable_memberof=${ol_enable_overlays:-no}
+fi;
+# end --enable-memberof
+
 # OpenLDAP --enable-ppolicy
 
 	# Check whether --enable-ppolicy or --disable-ppolicy was given.
@@ -5567,7 +5593,7 @@ ia64-*-hpux*)
   ;;
 *-*-irix6*)
   # Find out which ABI we are using.
-  echo '#line 5570 "configure"' > conftest.$ac_ext
+  echo '#line 5596 "configure"' > conftest.$ac_ext
   if { (eval echo "$as_me:$LINENO: \"$ac_compile\"") >&5
   (eval $ac_compile) 2>&5
   ac_status=$?
@@ -7547,11 +7573,11 @@ else
    -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
    -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
    -e 's:$: $lt_compiler_flag:'`
-   (eval echo "\"\$as_me:7550: $lt_compile\"" >&5)
+   (eval echo "\"\$as_me:7576: $lt_compile\"" >&5)
    (eval "$lt_compile" 2>conftest.err)
    ac_status=$?
    cat conftest.err >&5
-   echo "$as_me:7554: \$? = $ac_status" >&5
+   echo "$as_me:7580: \$? = $ac_status" >&5
    if (exit $ac_status) && test -s "$ac_outfile"; then
      # The compiler can only warn and ignore the option if not recognized
      # So say no if there are warnings other than the usual output.
@@ -7809,11 +7835,11 @@ else
    -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
    -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
    -e 's:$: $lt_compiler_flag:'`
-   (eval echo "\"\$as_me:7812: $lt_compile\"" >&5)
+   (eval echo "\"\$as_me:7838: $lt_compile\"" >&5)
    (eval "$lt_compile" 2>conftest.err)
    ac_status=$?
    cat conftest.err >&5
-   echo "$as_me:7816: \$? = $ac_status" >&5
+   echo "$as_me:7842: \$? = $ac_status" >&5
    if (exit $ac_status) && test -s "$ac_outfile"; then
      # The compiler can only warn and ignore the option if not recognized
      # So say no if there are warnings other than the usual output.
@@ -7871,11 +7897,11 @@ else
    -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \
    -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \
    -e 's:$: $lt_compiler_flag:'`
-   (eval echo "\"\$as_me:7874: $lt_compile\"" >&5)
+   (eval echo "\"\$as_me:7900: $lt_compile\"" >&5)
    (eval "$lt_compile" 2>out/conftest.err)
    ac_status=$?
    cat out/conftest.err >&5
-   echo "$as_me:7878: \$? = $ac_status" >&5
+   echo "$as_me:7904: \$? = $ac_status" >&5
    if (exit $ac_status) && test -s out/conftest2.$ac_objext
    then
      # The compiler can only warn and ignore the option if not recognized
@@ -10119,7 +10145,7 @@ else
   lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
   lt_status=$lt_dlunknown
   cat > conftest.$ac_ext <<EOF
-#line 10122 "configure"
+#line 10148 "configure"
 #include "confdefs.h"
 
 #if HAVE_DLFCN_H
@@ -10217,7 +10243,7 @@ else
   lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
   lt_status=$lt_dlunknown
   cat > conftest.$ac_ext <<EOF
-#line 10220 "configure"
+#line 10246 "configure"
 #include "confdefs.h"
 
 #if HAVE_DLFCN_H
@@ -40560,6 +40586,22 @@ _ACEOF
 
 fi
 
+if test "$ol_enable_memberof" != no ; then
+	BUILD_MEMBEROF=$ol_enable_memberof
+	if test "$ol_enable_memberof" = mod ; then
+		MFLAG=SLAPD_MOD_DYNAMIC
+		SLAPD_DYNAMIC_OVERLAYS="$SLAPD_DYNAMIC_OVERLAYS memberof.la"
+	else
+		MFLAG=SLAPD_MOD_STATIC
+		SLAPD_STATIC_OVERLAYS="$SLAPD_STATIC_OVERLAYS memberof.o"
+	fi
+
+cat >>confdefs.h <<_ACEOF
+#define SLAPD_OVER_MEMBEROF $MFLAG
+_ACEOF
+
+fi
+
 if test "$ol_enable_ppolicy" != no ; then
 	BUILD_PPOLICY=$ol_enable_ppolicy
 	if test "$ol_enable_ppolicy" = mod ; then
diff --git a/configure.in b/configure.in
index 0edfa1ab8f..cad9a70bbb 100644
--- a/configure.in
+++ b/configure.in
@@ -331,6 +331,7 @@ Overlays="accesslog \
 	dds \
 	dyngroup \
 	dynlist \
+	memberof \
 	ppolicy \
 	proxycache \
 	refint \
@@ -359,6 +360,8 @@ OL_ARG_ENABLE(dyngroup,[    --enable-dyngroup	  Dynamic Group overlay],
 	no, [no yes mod], ol_enable_overlays)
 OL_ARG_ENABLE(dynlist,[    --enable-dynlist	  Dynamic List overlay],
 	no, [no yes mod], ol_enable_overlays)
+OL_ARG_ENABLE(memberof,[    --enable-memberof	  Reverse Group Membership overlay],
+	no, [no yes mod], ol_enable_overlays)
 OL_ARG_ENABLE(ppolicy,[    --enable-ppolicy	  Password Policy overlay],
 	no, [no yes mod], ol_enable_overlays)
 OL_ARG_ENABLE(proxycache,[    --enable-proxycache	  Proxy Cache overlay],
@@ -2716,6 +2719,18 @@ if test "$ol_enable_dynlist" != no ; then
 	AC_DEFINE_UNQUOTED(SLAPD_OVER_DYNLIST,$MFLAG,[define for Dynamic List overlay])
 fi
 
+if test "$ol_enable_memberof" != no ; then
+	BUILD_MEMBEROF=$ol_enable_memberof
+	if test "$ol_enable_memberof" = mod ; then
+		MFLAG=SLAPD_MOD_DYNAMIC
+		SLAPD_DYNAMIC_OVERLAYS="$SLAPD_DYNAMIC_OVERLAYS memberof.la"
+	else
+		MFLAG=SLAPD_MOD_STATIC
+		SLAPD_STATIC_OVERLAYS="$SLAPD_STATIC_OVERLAYS memberof.o"
+	fi
+	AC_DEFINE_UNQUOTED(SLAPD_OVER_MEMBEROF,$MFLAG,[define for Reverse Group Membership overlay])
+fi
+
 if test "$ol_enable_ppolicy" != no ; then
 	BUILD_PPOLICY=$ol_enable_ppolicy
 	if test "$ol_enable_ppolicy" = mod ; then
diff --git a/doc/man/man5/slapo-memberof.5 b/doc/man/man5/slapo-memberof.5
new file mode 100644
index 0000000000..b9f6e06761
--- /dev/null
+++ b/doc/man/man5/slapo-memberof.5
@@ -0,0 +1,114 @@
+.TH SLAPO-MEMBEROF 5 "RELEASEDATE" "OpenLDAP LDVERSION"
+.\" Copyright 1998-2007 The OpenLDAP Foundation, All Rights Reserved.
+.\" Copying restrictions apply.  See the COPYRIGHT file.
+.\" $OpenLDAP$
+.SH NAME
+slapo-memberof \- Reverse Group Membership overlay to slapd
+.SH SYNOPSIS
+ETCDIR/slapd.conf
+.SH DESCRIPTION
+The
+.B memberof
+overlay to
+.BR slapd (8)
+allows automatic reverse group membership maintenance.
+Any time a group entry is modified, its members are modified as appropriate
+in order to keep a DN-valued "is member of" attribute updated with the DN
+of the group.
+
+.SH CONFIGURATION
+The config directives that are specific to the
+.B memberof
+overlay must be prefixed by
+.BR memberof\- ,
+to avoid potential conflicts with directives specific to the underlying 
+database or to other stacked overlays.
+
+.TP
+.B overlay memberof
+This directive adds the memberof overlay to the current database; see
+.BR slapd.conf (5)
+for details.
+
+.LP
+The following
+.B slapd.conf
+configuration options are defined for the memberofoverlay.
+
+.TP
+.B memberof-group-oc <group-oc>
+The value 
+.B <group-oc> 
+is the name of the objectClass that triggers the reverse group membership
+update.
+It defaults to \fIgroupOfNames\fP.
+
+.TP
+.B memberof-member-ad <member-ad>
+The value 
+.B <member-ad> 
+is the name of the attribute that contains the names of the members
+in the group objects; it must be DN-valued.
+It defaults to \fImember\fP.
+
+.TP
+.B memberof-memberof-ad <memberof-ad>
+The value 
+.B <memberof-ad> 
+is the name of the attribute that contains the names of the groups
+an entry is member of; it must be DN-valued.  Its contents are 
+automatically updated by the overlay.
+It defaults to \fImemberOf\fP.
+
+.TP
+.B memberof-dn <dn>
+The value 
+.B <dn> 
+contains the DN that is used as \fImodifiersName\fP for internal 
+modifications performed to update the reverse group membership.
+It defaults to the \fIrootdn\fP of the underlying database.
+
+.TP
+.B memberof-dangling {ignore, drop, error}
+This option determines the behavior of the overlay when, during 
+a modification, it encounters dangling references.
+The default is
+.BR ignore ,
+which may leave dangling references.
+Other options are
+.BR drop ,
+which discards those modifications that would result in dangling
+references, and
+.BR error ,
+which causes modifications that would result in dangling references
+to fail.
+
+.TP
+.B memberof-refint {true|FALSE}
+This option determines whether the overlay will try to preserve
+referential integrity or not.
+If set to
+.BR TRUE ,
+when an entry containing values of the "is member of" attribute is modified,
+the corresponding groups are modified as well.
+
+.LP
+The memberof overlay may be used with any backend that provides full 
+read-write functionality, but it is mainly intended for use 
+with local storage backends.
+
+.SH FILES
+.TP
+ETCDIR/slapd.conf
+default slapd configuration file
+.SH SEE ALSO
+.BR slapd.conf (5),
+.BR slapd (8).
+The
+.BR slapo-memberof (5)
+overlay supports dynamic configuration via
+.BR back-config .
+.SH ACKNOWLEDGEMENTS
+.P
+This module was written in 2005 by Pierangelo Masarati for SysNet s.n.c.
+
diff --git a/include/portable.hin b/include/portable.hin
index 3a2f0df7fb..b4ef095a9f 100644
--- a/include/portable.hin
+++ b/include/portable.hin
@@ -966,6 +966,9 @@
 /* define for Dynamic List overlay */
 #undef SLAPD_OVER_DYNLIST
 
+/* define for Reverse Group Membership overlay */
+#undef SLAPD_OVER_MEMBEROF
+
 /* define for Password Policy overlay */
 #undef SLAPD_OVER_PPOLICY
 
diff --git a/servers/slapd/bconfig.c b/servers/slapd/bconfig.c
index b20d3e4db3..9416a8cf29 100644
--- a/servers/slapd/bconfig.c
+++ b/servers/slapd/bconfig.c
@@ -246,6 +246,7 @@ static OidRec OidMacros[] = {
  * OLcfgOv{Oc|At}:15			-> auditlog
  * OLcfgOv{Oc|At}:16			-> rwm
  * OLcfgOv{Oc|At}:17			-> dyngroup
+ * OLcfgOv{Oc|At}:18			-> memberof
  */
 
 /* alphabetical ordering */
diff --git a/servers/slapd/overlays/Makefile.in b/servers/slapd/overlays/Makefile.in
index e50b44d6f8..3321f968a4 100644
--- a/servers/slapd/overlays/Makefile.in
+++ b/servers/slapd/overlays/Makefile.in
@@ -20,6 +20,7 @@ SRCS = overlays.c \
 	dds.c \
 	dyngroup.c \
 	dynlist.c \
+	memberof.c \
 	pcache.c \
 	ppolicy.c \
 	refint.c \
@@ -76,6 +77,9 @@ dyngroup.la : dyngroup.lo
 dynlist.la : dynlist.lo
 	$(LTLINK_MOD) -module -o $@ dynlist.lo version.lo $(LINK_LIBS)
 
+memberof.la : memberof.lo
+	$(LTLINK_MOD) -module -o $@ memberof.lo version.lo $(LINK_LIBS)
+
 pcache.la : pcache.lo
 	$(LTLINK_MOD) -module -o $@ pcache.lo version.lo $(LINK_LIBS)
 
diff --git a/servers/slapd/overlays/memberof.c b/servers/slapd/overlays/memberof.c
new file mode 100644
index 0000000000..eaff0edcf1
--- /dev/null
+++ b/servers/slapd/overlays/memberof.c
@@ -0,0 +1,1947 @@
+/* memberof.c - back-reference for group membership */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2007 Pierangelo Masarati <ando@sys-net.it>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software, sponsored by SysNet s.r.l.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_MEMBEROF
+
+#include <stdio.h>
+
+#include "ac/string.h"
+#include "ac/socket.h"
+
+#include "slap.h"
+#include "config.h"
+#include "lutil.h"
+
+/*
+ *	Glossary:
+ *
+ *		GROUP		a group object (an entry with GROUP_OC
+ *				objectClass)
+ *		MEMBER		a member object (an entry whose DN is
+ *				listed as MEMBER_AT value of a GROUP)
+ *		GROUP_OC	the objectClass of the group object
+ *				(default: groupOfNames)
+ *		MEMBER_AT	the membership attribute, DN-valued;
+ *				note: nameAndOptionalUID is tolerated
+ *				as soon as the optionalUID is absent
+ *				(default: member)
+ *		MEMBER_OF	reverse membership attribute
+ *				(default: memberOf)
+ *
+ * 	- add:
+ *		- if the entry that is being added is a GROUP,
+ *		  the MEMBER_AT defined as values of the add operation
+ *		  get the MEMBER_OF value directly from the request.
+ *
+ *		  if configured to do so, the MEMBER objects do not exist,
+ *		  and no relax control is issued, either:
+ *			- fail
+ *			- drop non-existing members
+ *		  (by default: don't muck with values)
+ *
+ *		- if (configured to do so,) the referenced GROUP exists,
+ *		  the relax control is set and the user has
+ *		  "manage" privileges, allow to add MEMBER_OF values to
+ *		  generic entries.
+ *
+ *	- modify:
+ *		- if the entry being modified is a GROUP_OC and the 
+ *		  MEMBER_AT attribute is modified, the MEMBER_OF value
+ *		  of the (existing) MEMBER_AT entries that are affected
+ *		  is modified according to the request:
+ *			- if a MEMBER is removed from the group,
+ *			  delete the corresponding MEMBER_OF
+ *			- if a MEMBER is added to a group,
+ *			  add the corresponding MEMBER_OF
+ *
+ *		  We need to determine, from the database, if it is
+ *		  a GROUP_OC, and we need to check, from the
+ *		  modification list, if the MEMBER_AT attribute is being
+ *		  affected, and what MEMBER_AT values are affected.
+ *
+ *		  if configured to do so, the entries corresponding to
+ *		  the MEMBER_AT values do not exist, and no relax control
+ *		  is issued, either:
+ *			- fail
+ *			- drop non-existing members
+ *		  (by default: don't muck with values)
+ *
+ *		- if configured to do so, the referenced GROUP exists,
+ *		  (the relax control is set) and the user has
+ *		  "manage" privileges, allow to add MEMBER_OF values to
+ *		  generic entries; the change is NOT automatically reflected
+ *		  in the MEMBER attribute of the GROUP referenced
+ *		  by the value of MEMBER_OF; a separate modification,
+ *		  with or without relax control, needs to be performed.
+ *
+ *	- modrdn:
+ *		- if the entry being renamed is a GROUP, the MEMBER_OF
+ *		  value of the (existing) MEMBER objects is modified
+ *		  accordingly based on the newDN of the GROUP.
+ *
+ *		  We need to determine, from the database, if it is
+ *		  a GROUP; the list of MEMBER objects is obtained from
+ *		  the database.
+ *
+ *		  Non-existing MEMBER objects are ignored, since the
+ *		  MEMBER_AT is not being addressed by the operation.
+ *
+ *		- if the entry being renamed has the MEMBER_OF attribute,
+ *		  the corresponding MEMBER value must be modified in the
+ *		  respective group entries.
+ *		
+ *
+ *	- delete:
+ *		- if the entry being deleted is a GROUP, the (existing)
+ *		  MEMBER objects are modified accordingly; a copy of the 
+ *		  values of the MEMBER_AT is saved and, if the delete 
+ *		  succeeds, the MEMBER_OF value of the (existing) MEMBER
+ *		  objects is deleted.
+ *
+ *		  We need to determine, from the database, if it is
+ *		  a GROUP.
+ *
+ *		  Non-existing MEMBER objects are ignored, since the entry
+ *		  is being deleted.
+ *
+ *		- if the entry being deleted has the MEMBER_OF attribute,
+ *		  the corresponding value of the MEMBER_AT must be deleted
+ *		  from the respective GROUP entries.
+ */
+
+#define	SLAPD_MEMBEROF_ATTR	"memberOf"
+
+static slap_overinst		memberof;
+
+typedef struct memberof_t {
+	struct berval		mo_dn;
+	struct berval		mo_ndn;
+
+	ObjectClass		*mo_oc_group;
+	AttributeDescription	*mo_ad_member;
+	AttributeDescription	*mo_ad_memberof;
+	
+	struct berval		mo_groupFilterstr;
+	AttributeAssertion	mo_groupAVA;
+	Filter			mo_groupFilter;
+
+	struct berval		mo_memberFilterstr;
+	Filter			mo_memberFilter;
+
+	unsigned		mo_flags;
+#define	MEMBEROF_NONE		0x00U
+#define	MEMBEROF_FDANGLING_DROP	0x01U
+#define	MEMBEROF_FDANGLING_ERROR	0x02U
+#define	MEMBEROF_FDANGLING_MASK	(MEMBEROF_FDANGLING_DROP|MEMBEROF_FDANGLING_ERROR)
+#define	MEMBEROF_FREFINT	0x04U
+#define	MEMBEROF_FREVERSE	0x08U
+
+#define MEMBEROF_CHK(mo,f) \
+	(((mo)->mo_flags & (f)) == (f))
+#define MEMBEROF_DANGLING_CHECK(mo) \
+	((mo)->mo_flags & MEMBEROF_FDANGLING_MASK)
+#define MEMBEROF_DANGLING_DROP(mo) \
+	MEMBEROF_CHK((mo),MEMBEROF_FDANGLING_DROP)
+#define MEMBEROF_DANGLING_ERROR(mo) \
+	MEMBEROF_CHK((mo),MEMBEROF_FDANGLING_ERROR)
+#define MEMBEROF_REFINT(mo) \
+	MEMBEROF_CHK((mo),MEMBEROF_FREFINT)
+#define MEMBEROF_REVERSE(mo) \
+	MEMBEROF_CHK((mo),MEMBEROF_FREVERSE)
+} memberof_t;
+
+typedef enum memberof_is_t {
+	MEMBEROF_IS_NONE = 0x00,
+	MEMBEROF_IS_GROUP = 0x01,
+	MEMBEROF_IS_MEMBER = 0x02,
+	MEMBEROF_IS_BOTH = (MEMBEROF_IS_GROUP|MEMBEROF_IS_MEMBER)
+} memberof_is_t;
+
+/*
+ * failover storage for member attribute values of groups being deleted
+ * handles [no]thread cases.
+ */
+static BerVarray	saved_member_vals;
+static BerVarray	saved_memberof_vals;
+
+static void
+memberof_saved_member_free( void *key, void *data )
+{
+	ber_bvarray_free( (BerVarray)data );
+}
+
+static BerVarray
+memberof_saved_member_get( Operation *op, void *keyp )
+{
+	BerVarray	vals;
+	BerVarray	*key = (BerVarray *)keyp;
+
+	assert( op );
+
+	if ( op->o_threadctx == NULL ) {
+		vals = *key;
+		*key = NULL;
+
+	} else {
+		ldap_pvt_thread_pool_getkey( op->o_threadctx,
+				key, (void **)&vals, NULL );
+		ldap_pvt_thread_pool_setkey( op->o_threadctx,
+				key, NULL, NULL );
+	}
+
+	return vals;
+}
+
+static void
+memberof_saved_member_set( Operation *op, void *keyp, BerVarray vals )
+{
+	BerVarray	saved_vals = NULL;
+	BerVarray	*key = (BerVarray*)keyp;
+
+	assert( op );
+
+	if ( vals ) {
+		ber_bvarray_dup_x( &saved_vals, vals, NULL );
+	}
+
+	if ( op->o_threadctx == NULL ) {
+		if ( *key ) {
+			ber_bvarray_free( *key );
+		}
+		*key = saved_vals;
+
+	} else {
+		ldap_pvt_thread_pool_setkey( op->o_threadctx, key,
+				saved_vals, memberof_saved_member_free );
+	}
+}
+
+typedef struct memberof_cookie_t {
+	AttributeDescription	*ad;
+	void			*key;
+	int			foundit;
+} memberof_cookie_t;
+
+static int
+memberof_isGroupOrMember_cb( Operation *op, SlapReply *rs )
+{
+	if ( rs->sr_type == REP_SEARCH ) {
+		memberof_cookie_t	*mc;
+
+		mc = (memberof_cookie_t *)op->o_callback->sc_private;
+		mc->foundit = 1;
+	}
+
+	return 0;
+}
+
+/*
+ * callback for internal search that saves the member attribute values
+ * of groups being deleted.
+ */
+static int
+memberof_saveMember_cb( Operation *op, SlapReply *rs )
+{
+	if ( rs->sr_type == REP_SEARCH ) {
+		memberof_cookie_t	*mc;
+		Attribute		*a;
+
+		mc = (memberof_cookie_t *)op->o_callback->sc_private;
+		mc->foundit = 1;
+
+		assert( rs->sr_entry );
+		assert( rs->sr_entry->e_attrs );
+
+		a = attr_find( rs->sr_entry->e_attrs, mc->ad );
+
+		assert( a != NULL );
+
+		memberof_saved_member_set( op, mc->key, a->a_nvals );
+
+		if ( attr_find( a->a_next, mc->ad ) != NULL ) {
+			Debug( LDAP_DEBUG_ANY,
+				"%s: memberof_saveMember_cb(\"%s\"): "
+				"more than one occurrence of \"%s\" "
+				"attribute.\n",
+				op->o_log_prefix,
+				rs->sr_entry->e_name.bv_val,
+				mc->ad->ad_cname.bv_val );
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * the delete hook performs an internal search that saves the member
+ * attribute values of groups being deleted.
+ */
+static int
+memberof_isGroupOrMember( Operation *op, memberof_is_t *iswhatp )
+{
+	slap_overinst		*on = (slap_overinst *)op->o_bd->bd_info;
+	memberof_t		*mo = (memberof_t *)on->on_bi.bi_private;
+
+	Operation		op2 = *op;
+	SlapReply		rs2 = { REP_RESULT };
+	slap_callback		cb = { 0 };
+	memberof_cookie_t	mc;
+	AttributeName		an[ 2 ];
+
+	memberof_is_t		iswhat = MEMBEROF_IS_NONE;
+
+	assert( iswhatp != NULL );
+	assert( *iswhatp != MEMBEROF_IS_NONE );
+
+	cb.sc_private = &mc;
+	if ( op->o_tag == LDAP_REQ_DELETE ) {
+		cb.sc_response = memberof_saveMember_cb;
+
+	} else {
+		cb.sc_response = memberof_isGroupOrMember_cb;
+	}
+
+	op2.o_tag = LDAP_REQ_SEARCH;
+	op2.o_callback = &cb;
+	op2.o_dn = op->o_bd->be_rootdn;
+	op2.o_ndn = op->o_bd->be_rootndn;
+
+	op2.ors_scope = LDAP_SCOPE_BASE;
+	op2.ors_deref = LDAP_DEREF_NEVER;
+	BER_BVZERO( &an[ 1 ].an_name );
+	op2.ors_attrs = an;
+	op2.ors_attrsonly = 0;
+	op2.ors_limit = NULL;
+	op2.ors_slimit = 1;
+	op2.ors_tlimit = SLAP_NO_LIMIT;
+
+	if ( *iswhatp & MEMBEROF_IS_GROUP ) {
+		mc.ad = mo->mo_ad_member;
+		mc.key = &saved_member_vals;
+		mc.foundit = 0;
+		an[ 0 ].an_desc = mo->mo_ad_member;
+		an[ 0 ].an_name = an[ 0 ].an_desc->ad_cname;
+		op2.ors_filterstr = mo->mo_groupFilterstr;
+		op2.ors_filter = &mo->mo_groupFilter;
+
+		op2.o_bd->bd_info = (BackendInfo *)on->on_info;
+		(void)op->o_bd->be_search( &op2, &rs2 );
+		op2.o_bd->bd_info = (BackendInfo *)on;
+
+		if ( mc.foundit ) {
+			iswhat |= MEMBEROF_IS_GROUP;
+
+		} else {
+			memberof_saved_member_set( op, mc.key, NULL );
+		}
+	}
+
+	if ( *iswhatp & MEMBEROF_IS_MEMBER ) {
+		mc.ad = mo->mo_ad_memberof;
+		mc.key = &saved_memberof_vals;
+		mc.foundit = 0;
+		an[ 0 ].an_desc = mo->mo_ad_memberof;
+		an[ 0 ].an_name = an[ 0 ].an_desc->ad_cname;
+		op2.ors_filterstr = mo->mo_memberFilterstr;
+		op2.ors_filter = &mo->mo_memberFilter;
+
+		op2.o_bd->bd_info = (BackendInfo *)on->on_info;
+		(void)op->o_bd->be_search( &op2, &rs2 );
+		op2.o_bd->bd_info = (BackendInfo *)on;
+
+		if ( mc.foundit ) {
+			iswhat |= MEMBEROF_IS_MEMBER;
+
+		} else {
+			memberof_saved_member_set( op, mc.key, NULL );
+		}
+	}
+
+	*iswhatp = iswhat;
+
+	return LDAP_SUCCESS;
+}
+
+/*
+ * response callback that adds memberof values when a group is modified.
+ */
+static int
+memberof_value_modify(
+	Operation		*op,
+	SlapReply		*rs,
+	struct berval		*ndn,
+	AttributeDescription	*ad,
+	struct berval		*old_dn,
+	struct berval		*old_ndn,
+	struct berval		*new_dn,
+	struct berval		*new_ndn )
+{
+	slap_overinst	*on = (slap_overinst *)op->o_bd->bd_info;
+	memberof_t	*mo = (memberof_t *)on->on_bi.bi_private;
+
+	Operation	op2 = *op;
+	SlapReply	rs2 = { REP_RESULT };
+	slap_callback	cb = { NULL, slap_null_cb, NULL, NULL };
+	Modifications	mod[ 2 ] = { { { 0 } } }, *ml;
+	struct berval	values[ 4 ], nvalues[ 4 ];
+
+	op2.o_tag = LDAP_REQ_MODIFY;
+
+	op2.o_req_dn = *ndn;
+	op2.o_req_ndn = *ndn;
+
+	op2.o_bd->bd_info = (BackendInfo *)on->on_info;
+
+	op2.o_callback = &cb;
+	op2.o_dn = op->o_bd->be_rootdn;
+	op2.o_ndn = op->o_bd->be_rootndn;
+
+	ml = &mod[ 0 ];
+	ml->sml_values = &values[ 0 ];
+	ml->sml_values[ 0 ] = mo->mo_dn;
+	BER_BVZERO( &ml->sml_values[ 1 ] );
+	ml->sml_nvalues = &nvalues[ 0 ];
+	ml->sml_nvalues[ 0 ] = mo->mo_ndn;
+	BER_BVZERO( &ml->sml_nvalues[ 1 ] );
+	ml->sml_desc = slap_schema.si_ad_modifiersName;
+	ml->sml_type = ml->sml_desc->ad_cname;
+	ml->sml_op = LDAP_MOD_REPLACE;
+	ml->sml_flags = SLAP_MOD_INTERNAL;
+	ml->sml_next = NULL;
+	op2.orm_modlist = ml;
+
+	ml = &mod[ 1 ];
+	ml->sml_values = &values[ 2 ];
+	BER_BVZERO( &ml->sml_values[ 1 ] );
+	ml->sml_nvalues = &nvalues[ 2 ];
+	BER_BVZERO( &ml->sml_nvalues[ 1 ] );
+	ml->sml_desc = ad;
+	ml->sml_type = ml->sml_desc->ad_cname;
+	ml->sml_flags = SLAP_MOD_INTERNAL;
+	ml->sml_next = NULL;
+	op2.orm_modlist->sml_next = ml;
+
+	if ( new_ndn != NULL ) {
+		assert( !BER_BVISNULL( new_dn ) );
+		assert( !BER_BVISNULL( new_ndn ) );
+
+		ml->sml_op = LDAP_MOD_ADD;
+
+		ml->sml_values[ 0 ] = *new_dn;
+		ml->sml_nvalues[ 0 ] = *new_ndn;
+
+		(void)op->o_bd->be_modify( &op2, &rs2 );
+
+		assert( op2.orm_modlist == &mod[ 0 ] );
+		assert( op2.orm_modlist->sml_next == &mod[ 1 ] );
+		ml = op2.orm_modlist->sml_next->sml_next;
+		if ( ml != NULL ) {
+			slap_mods_free( ml, 1 );
+		}
+	}
+
+	if ( old_ndn != NULL ) {
+		assert( !BER_BVISNULL( old_dn ) );
+		assert( !BER_BVISNULL( old_ndn ) );
+
+		ml->sml_op = LDAP_MOD_DELETE;
+
+		ml->sml_values[ 0 ] = *old_dn;
+		ml->sml_nvalues[ 0 ] = *old_ndn;
+
+		(void)op->o_bd->be_modify( &op2, &rs2 );
+
+		assert( op2.orm_modlist == &mod[ 0 ] );
+		assert( op2.orm_modlist->sml_next == &mod[ 1 ] );
+		ml = op2.orm_modlist->sml_next->sml_next;
+		if ( ml != NULL ) {
+			slap_mods_free( ml, 1 );
+		}
+	}
+
+	/* FIXME: if old_group_ndn doesn't exist, both delete __and__
+	 * add will fail; better split in two operations, although
+	 * not optimal in terms of performance.  At least it would
+	 * move towards self-repairing capabilities. */
+
+	op2.o_bd->bd_info = (BackendInfo *)on;
+
+	return rs2.sr_err;
+}
+
+static int
+memberof_op_add( Operation *op, SlapReply *rs )
+{
+	slap_overinst	*on = (slap_overinst *)op->o_bd->bd_info;
+	memberof_t	*mo = (memberof_t *)on->on_bi.bi_private;
+
+	Attribute	**ap, **map = NULL;
+	int		rc = SLAP_CB_CONTINUE;
+	int		i;
+	struct berval	save_dn, save_ndn;
+
+	if ( op->ora_e->e_attrs == NULL ) {
+		/* FIXME: global overlay; need to deal with */
+		Debug( LDAP_DEBUG_ANY, "%s: memberof_op_add(\"%s\"): "
+			"consistency checks not implemented when overlay "
+			"is instantiated as global.\n",
+			op->o_log_prefix, op->o_req_dn.bv_val, 0 );
+		return SLAP_CB_CONTINUE;
+	}
+
+	if ( MEMBEROF_REVERSE( mo ) ) {
+		for ( ap = &op->ora_e->e_attrs; *ap; ap = &(*ap)->a_next ) {
+			Attribute	*a = *ap;
+
+			if ( a->a_desc == mo->mo_ad_memberof ) {
+				map = ap;
+				break;
+			}
+		}
+	}
+
+	save_dn = op->o_dn;
+	save_ndn = op->o_ndn;
+
+	if ( MEMBEROF_DANGLING_CHECK( mo )
+			&& !get_relax( op )
+			&& is_entry_objectclass( op->ora_e, mo->mo_oc_group, 0 ) )
+	{
+		op->o_dn = op->o_bd->be_rootdn;
+		op->o_dn = op->o_bd->be_rootndn;
+		op->o_bd->bd_info = (BackendInfo *)on->on_info;
+
+		for ( ap = &op->ora_e->e_attrs; *ap; ) {
+			Attribute	*a = *ap;
+
+			if ( !is_ad_subtype( a->a_desc, mo->mo_ad_member ) ) {
+				ap = &a->a_next;
+				continue;
+			}
+
+			assert( a->a_nvals != NULL );
+
+			for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) {
+				Entry		*e;
+
+				/* FIXME: entry_get_rw does not pass
+				 * thru overlays yet; when it does, we
+				 * might need to make a copy of the DN */
+
+				rc = be_entry_get_rw( op, &a->a_nvals[ i ],
+						NULL, NULL, 0, &e );
+				if ( rc == LDAP_SUCCESS ) {
+					be_entry_release_r( op, e );
+					continue;
+				}
+
+				if ( MEMBEROF_DANGLING_ERROR( mo ) ) {
+					rc = rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+					rs->sr_text = "adding non-existing object "
+						"as group member";
+					send_ldap_result( op, rs );
+					goto done;
+				}
+
+				if ( MEMBEROF_DANGLING_DROP( mo ) ) {
+					int	j;
+	
+					Debug( LDAP_DEBUG_ANY, "%s: memberof_op_add(\"%s\"): "
+						"member=\"%s\" does not exist (stripping...)\n",
+						op->o_log_prefix, op->ora_e->e_name.bv_val,
+						a->a_vals[ i ].bv_val );
+	
+					for ( j = i + 1; !BER_BVISNULL( &a->a_nvals[ j ] ); j++ );
+					ber_memfree( a->a_vals[ i ].bv_val );
+					BER_BVZERO( &a->a_vals[ i ] );
+					if ( a->a_nvals != a->a_vals ) {
+						ber_memfree( a->a_nvals[ i ].bv_val );
+						BER_BVZERO( &a->a_nvals[ i ] );
+					}
+					if ( j - i == 1 ) {
+						break;
+					}
+		
+					AC_MEMCPY( &a->a_vals[ i ], &a->a_vals[ i + 1 ],
+						sizeof( struct berval ) * ( j - i ) );
+					if ( a->a_nvals != a->a_vals ) {
+						AC_MEMCPY( &a->a_nvals[ i ], &a->a_nvals[ i + 1 ],
+							sizeof( struct berval ) * ( j - i ) );
+					}
+					i--;
+				}
+			}
+
+			/* If all values have been removed,
+			 * remove the attribute itself. */
+			if ( BER_BVISNULL( &a->a_nvals[ 0 ] ) ) {
+				*ap = a->a_next;
+				attr_free( a );
+	
+			} else {
+				ap = &a->a_next;
+			}
+		}
+		op->o_dn = save_dn;
+		op->o_ndn = save_ndn;
+		op->o_bd->bd_info = (BackendInfo *)on;
+	}
+
+	if ( map != NULL ) {
+		Attribute		*a = *map;
+		AccessControlState	acl_state = ACL_STATE_INIT;
+
+		for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) {
+			Entry		*e;
+
+			op->o_bd->bd_info = (BackendInfo *)on->on_info;
+			/* access is checked with the original identity */
+			rc = access_allowed( op, op->ora_e, mo->mo_ad_memberof,
+					&a->a_nvals[ i ], ACL_WADD,
+					&acl_state );
+			if ( rc == 0 ) {
+				rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+				rs->sr_text = NULL;
+				send_ldap_result( op, rs );
+				goto done;
+			}
+			rc = be_entry_get_rw( op, &a->a_nvals[ i ],
+					NULL, NULL, 0, &e );
+			op->o_bd->bd_info = (BackendInfo *)on;
+			if ( rc != LDAP_SUCCESS ) {
+				if ( get_relax( op ) ) {
+					continue;
+				}
+
+				if ( MEMBEROF_DANGLING_ERROR( mo ) ) {
+					rc = rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+					rs->sr_text = "adding non-existing object "
+						"as memberof";
+					send_ldap_result( op, rs );
+					goto done;
+				}
+
+				if ( MEMBEROF_DANGLING_DROP( mo ) ) {
+					int	j;
+	
+					Debug( LDAP_DEBUG_ANY, "%s: memberof_op_add(\"%s\"): "
+						"memberof=\"%s\" does not exist (stripping...)\n",
+						op->o_log_prefix, op->ora_e->e_name.bv_val,
+						a->a_nvals[ i ].bv_val );
+	
+					for ( j = i + 1; !BER_BVISNULL( &a->a_nvals[ j ] ); j++ );
+					ber_memfree( a->a_vals[ i ].bv_val );
+					BER_BVZERO( &a->a_vals[ i ] );
+					if ( a->a_nvals != a->a_vals ) {
+						ber_memfree( a->a_nvals[ i ].bv_val );
+						BER_BVZERO( &a->a_nvals[ i ] );
+					}
+					if ( j - i == 1 ) {
+						break;
+					}
+		
+					AC_MEMCPY( &a->a_vals[ i ], &a->a_vals[ i + 1 ],
+						sizeof( struct berval ) * ( j - i ) );
+					if ( a->a_nvals != a->a_vals ) {
+						AC_MEMCPY( &a->a_nvals[ i ], &a->a_nvals[ i + 1 ],
+							sizeof( struct berval ) * ( j - i ) );
+					}
+					i--;
+				}
+				
+				continue;
+			}
+
+			/* access is checked with the original identity */
+			op->o_bd->bd_info = (BackendInfo *)on->on_info;
+			rc = access_allowed( op, e, mo->mo_ad_member,
+					&op->o_req_ndn, ACL_WADD, NULL );
+			be_entry_release_r( op, e );
+			op->o_bd->bd_info = (BackendInfo *)on;
+
+			if ( !rc ) {
+				rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+				rs->sr_text = "insufficient access to object referenced by memberof";
+				send_ldap_result( op, rs );
+				goto done;
+			}
+		}
+
+		if ( BER_BVISNULL( &a->a_nvals[ 0 ] ) ) {
+			*map = a->a_next;
+			attr_free( a );
+		}
+	}
+
+	rc = SLAP_CB_CONTINUE;
+	
+done:;
+	op->o_dn = save_dn;
+	op->o_ndn = save_ndn;
+	op->o_bd->bd_info = (BackendInfo *)on;
+
+	return rc;
+}
+
+static int
+memberof_op_delete( Operation *op, SlapReply *rs )
+{
+	slap_overinst	*on = (slap_overinst *)op->o_bd->bd_info;
+	memberof_t	*mo = (memberof_t *)on->on_bi.bi_private;
+
+	memberof_is_t	iswhat = MEMBEROF_IS_GROUP;
+
+	if ( MEMBEROF_REFINT( mo ) ) {
+		iswhat = MEMBEROF_IS_BOTH;
+	}
+
+	memberof_isGroupOrMember( op, &iswhat );
+
+	return SLAP_CB_CONTINUE;
+}
+
+static int
+memberof_op_modify( Operation *op, SlapReply *rs )
+{
+	slap_overinst	*on = (slap_overinst *)op->o_bd->bd_info;
+	memberof_t	*mo = (memberof_t *)on->on_bi.bi_private;
+
+	Modifications	**mlp, **mmlp = NULL;
+	int		rc = SLAP_CB_CONTINUE;
+	struct berval	save_dn, save_ndn;
+	memberof_is_t	iswhat = MEMBEROF_IS_GROUP;
+
+	if ( MEMBEROF_REVERSE( mo ) ) {
+		for ( mlp = &op->orm_modlist; *mlp; mlp = &(*mlp)->sml_next ) {
+			Modifications	*ml = *mlp;
+
+			if ( ml->sml_desc == mo->mo_ad_memberof ) {
+				mmlp = mlp;
+				break;
+			}
+		}
+	}
+
+	save_dn = op->o_dn;
+	save_ndn = op->o_ndn;
+
+	if ( MEMBEROF_DANGLING_CHECK( mo )
+			&& !get_relax( op )
+			&& memberof_isGroupOrMember( op, &iswhat ) == LDAP_SUCCESS
+			&& ( iswhat & MEMBEROF_IS_GROUP ) )
+	{
+		op->o_dn = op->o_bd->be_rootdn;
+		op->o_dn = op->o_bd->be_rootndn;
+		op->o_bd->bd_info = (BackendInfo *)on->on_info;
+	
+		assert( op->orm_modlist != NULL );
+	
+		for ( mlp = &op->orm_modlist; *mlp; ) {
+			Modifications	*ml = *mlp;
+			int		i;
+	
+			if ( !is_ad_subtype( ml->sml_desc, mo->mo_ad_member ) ) {
+				mlp = &ml->sml_next;
+				continue;
+			}
+	
+			switch ( ml->sml_op ) {
+			case LDAP_MOD_DELETE:
+				/* we don't care about cancellations: if the value
+				 * exists, fine; if it doesn't, we let the underlying
+				 * database fail as appropriate; */
+				mlp = &ml->sml_next;
+				break;
+	
+			case LDAP_MOD_REPLACE:
+			case LDAP_MOD_ADD:
+				/* NOTE: right now, the attributeType we use
+				 * for member must have a normalized value */
+				assert( ml->sml_nvalues );
+	
+				for ( i = 0; !BER_BVISNULL( &ml->sml_nvalues[ i ] ); i++ ) {
+					int		rc;
+					Entry		*e;
+	
+					if ( be_entry_get_rw( op, &ml->sml_nvalues[ i ],
+							NULL, NULL, 0, &e ) == LDAP_SUCCESS )
+					{
+						be_entry_release_r( op, e );
+						continue;
+					}
+	
+					if ( MEMBEROF_DANGLING_ERROR( mo ) ) {
+						rc = rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+						rs->sr_text = "adding non-existing object "
+							"as group member";
+						send_ldap_result( op, rs );
+						goto done;
+					}
+	
+					if ( MEMBEROF_DANGLING_DROP( mo ) ) {
+						int	j;
+	
+						Debug( LDAP_DEBUG_ANY, "%s: memberof_op_modify(\"%s\"): "
+							"member=\"%s\" does not exist (stripping...)\n",
+							op->o_log_prefix, op->o_req_dn.bv_val,
+							ml->sml_nvalues[ i ].bv_val );
+	
+						for ( j = i + 1; !BER_BVISNULL( &ml->sml_nvalues[ j ] ); j++ );
+						ber_memfree( ml->sml_values[ i ].bv_val );
+						BER_BVZERO( &ml->sml_values[ i ] );
+						ber_memfree( ml->sml_nvalues[ i ].bv_val );
+						BER_BVZERO( &ml->sml_nvalues[ i ] );
+						if ( j - i == 1 ) {
+							break;
+						}
+	
+						AC_MEMCPY( &ml->sml_values[ i ], &ml->sml_values[ i + 1 ],
+							sizeof( struct berval ) * ( j - i ) );
+						AC_MEMCPY( &ml->sml_nvalues[ i ], &ml->sml_nvalues[ i + 1 ],
+							sizeof( struct berval ) * ( j - i ) );
+						i--;
+					}
+				}
+	
+				if ( BER_BVISNULL( &ml->sml_nvalues[ 0 ] ) ) {
+					*mlp = ml->sml_next;
+					slap_mod_free( &ml->sml_mod, 0 );
+					free( ml );
+	
+				} else {
+					mlp = &ml->sml_next;
+				}
+	
+				break;
+	
+			default:
+				assert( 0 );
+			}
+		}
+	}
+
+	if ( mmlp != NULL ) {
+		Modifications	*ml = *mmlp;
+		int		i;
+		Entry		*target;
+
+		op->o_bd->bd_info = (BackendInfo *)on->on_info;
+		rc = be_entry_get_rw( op, &op->o_req_ndn,
+				NULL, NULL, 0, &target );
+		op->o_bd->bd_info = (BackendInfo *)on;
+		if ( rc != LDAP_SUCCESS ) {
+			rc = rs->sr_err = LDAP_NO_SUCH_OBJECT;
+			send_ldap_result( op, rs );
+			goto done;
+		}
+
+		switch ( ml->sml_op ) {
+		case LDAP_MOD_DELETE:
+			if ( ml->sml_nvalues != NULL ) {
+				AccessControlState	acl_state = ACL_STATE_INIT;
+
+				for ( i = 0; !BER_BVISNULL( &ml->sml_nvalues[ i ] ); i++ ) {
+					Entry		*e;
+
+					op->o_bd->bd_info = (BackendInfo *)on->on_info;
+					/* access is checked with the original identity */
+					rc = access_allowed( op, target,
+							mo->mo_ad_memberof,
+							&ml->sml_nvalues[ i ],
+							ACL_WDEL,
+							&acl_state );
+					if ( rc == 0 ) {
+						rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+						rs->sr_text = NULL;
+						send_ldap_result( op, rs );
+						goto done2;
+					}
+
+					rc = be_entry_get_rw( op, &ml->sml_nvalues[ i ],
+							NULL, NULL, 0, &e );
+					op->o_bd->bd_info = (BackendInfo *)on;
+					if ( rc != LDAP_SUCCESS ) {
+						if ( get_relax( op ) ) {
+							continue;
+						}
+
+						if ( MEMBEROF_DANGLING_ERROR( mo ) ) {
+							rc = rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+							rs->sr_text = "deleting non-existing object "
+								"as memberof";
+							send_ldap_result( op, rs );
+							goto done2;
+						}
+
+						if ( MEMBEROF_DANGLING_DROP( mo ) ) {
+							int	j;
+	
+							Debug( LDAP_DEBUG_ANY, "%s: memberof_op_modify(\"%s\"): "
+								"memberof=\"%s\" does not exist (stripping...)\n",
+								op->o_log_prefix, op->o_req_ndn.bv_val,
+								ml->sml_nvalues[ i ].bv_val );
+	
+							for ( j = i + 1; !BER_BVISNULL( &ml->sml_nvalues[ j ] ); j++ );
+							ber_memfree( ml->sml_values[ i ].bv_val );
+							BER_BVZERO( &ml->sml_values[ i ] );
+							if ( ml->sml_nvalues != ml->sml_values ) {
+								ber_memfree( ml->sml_nvalues[ i ].bv_val );
+								BER_BVZERO( &ml->sml_nvalues[ i ] );
+							}
+							if ( j - i == 1 ) {
+								break;
+							}
+		
+							AC_MEMCPY( &ml->sml_values[ i ], &ml->sml_values[ i + 1 ],
+								sizeof( struct berval ) * ( j - i ) );
+							if ( ml->sml_nvalues != ml->sml_values ) {
+								AC_MEMCPY( &ml->sml_nvalues[ i ], &ml->sml_nvalues[ i + 1 ],
+									sizeof( struct berval ) * ( j - i ) );
+							}
+							i--;
+						}
+
+						continue;
+					}
+
+					/* access is checked with the original identity */
+					op->o_bd->bd_info = (BackendInfo *)on->on_info;
+					rc = access_allowed( op, e, mo->mo_ad_member,
+							&op->o_req_ndn,
+							ACL_WDEL, NULL );
+					be_entry_release_r( op, e );
+					op->o_bd->bd_info = (BackendInfo *)on;
+
+					if ( !rc ) {
+						rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+						rs->sr_text = "insufficient access to object referenced by memberof";
+						send_ldap_result( op, rs );
+						goto done;
+					}
+				}
+
+				if ( BER_BVISNULL( &ml->sml_nvalues[ 0 ] ) ) {
+					*mmlp = ml->sml_next;
+					slap_mod_free( &ml->sml_mod, 0 );
+					free( ml );
+				}
+
+				break;
+			}
+			/* fall thru */
+
+		case LDAP_MOD_REPLACE:
+
+			op->o_bd->bd_info = (BackendInfo *)on->on_info;
+			/* access is checked with the original identity */
+			rc = access_allowed( op, target,
+					mo->mo_ad_memberof,
+					NULL,
+					ACL_WDEL, NULL );
+			op->o_bd->bd_info = (BackendInfo *)on;
+			if ( rc == 0 ) {
+				rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+				rs->sr_text = NULL;
+				send_ldap_result( op, rs );
+				goto done2;
+			}
+
+			if ( ml->sml_op == LDAP_MOD_DELETE ) {
+				break;
+			}
+			/* fall thru */
+
+		case LDAP_MOD_ADD: {
+			AccessControlState	acl_state = ACL_STATE_INIT;
+
+			for ( i = 0; !BER_BVISNULL( &ml->sml_nvalues[ i ] ); i++ ) {
+				Entry		*e;
+
+				op->o_bd->bd_info = (BackendInfo *)on->on_info;
+				/* access is checked with the original identity */
+				rc = access_allowed( op, target,
+						mo->mo_ad_memberof,
+						&ml->sml_nvalues[ i ],
+						ACL_WADD,
+						&acl_state );
+				if ( rc == 0 ) {
+					rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+					rs->sr_text = NULL;
+					send_ldap_result( op, rs );
+					goto done2;
+				}
+
+				rc = be_entry_get_rw( op, &ml->sml_nvalues[ i ],
+						NULL, NULL, 0, &e );
+				op->o_bd->bd_info = (BackendInfo *)on;
+				if ( rc != LDAP_SUCCESS ) {
+					if ( MEMBEROF_DANGLING_ERROR( mo ) ) {
+						rc = rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+						rs->sr_text = "adding non-existing object "
+							"as memberof";
+						send_ldap_result( op, rs );
+						goto done2;
+					}
+
+					if ( MEMBEROF_DANGLING_DROP( mo ) ) {
+						int	j;
+
+						Debug( LDAP_DEBUG_ANY, "%s: memberof_op_modify(\"%s\"): "
+							"memberof=\"%s\" does not exist (stripping...)\n",
+							op->o_log_prefix, op->o_req_ndn.bv_val,
+							ml->sml_nvalues[ i ].bv_val );
+
+						for ( j = i + 1; !BER_BVISNULL( &ml->sml_nvalues[ j ] ); j++ );
+						ber_memfree( ml->sml_values[ i ].bv_val );
+						BER_BVZERO( &ml->sml_values[ i ] );
+						if ( ml->sml_nvalues != ml->sml_values ) {
+							ber_memfree( ml->sml_nvalues[ i ].bv_val );
+							BER_BVZERO( &ml->sml_nvalues[ i ] );
+						}
+						if ( j - i == 1 ) {
+							break;
+						}
+	
+						AC_MEMCPY( &ml->sml_values[ i ], &ml->sml_values[ i + 1 ],
+							sizeof( struct berval ) * ( j - i ) );
+						if ( ml->sml_nvalues != ml->sml_values ) {
+							AC_MEMCPY( &ml->sml_nvalues[ i ], &ml->sml_nvalues[ i + 1 ],
+								sizeof( struct berval ) * ( j - i ) );
+						}
+						i--;
+					}
+
+					continue;
+				}
+
+				/* access is checked with the original identity */
+				op->o_bd->bd_info = (BackendInfo *)on->on_info;
+				rc = access_allowed( op, e, mo->mo_ad_member,
+						&op->o_req_ndn,
+						ACL_WDEL, NULL );
+				be_entry_release_r( op, e );
+				op->o_bd->bd_info = (BackendInfo *)on;
+
+				if ( !rc ) {
+					rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+					rs->sr_text = "insufficient access to object referenced by memberof";
+					send_ldap_result( op, rs );
+					goto done;
+				}
+			}
+
+			if ( BER_BVISNULL( &ml->sml_nvalues[ 0 ] ) ) {
+				*mmlp = ml->sml_next;
+				slap_mod_free( &ml->sml_mod, 0 );
+				free( ml );
+			}
+
+			} break;
+
+		default:
+			assert( 0 );
+		}
+
+done2:;
+		op->o_bd->bd_info = (BackendInfo *)on->on_info;
+		be_entry_release_r( op, target );
+		op->o_bd->bd_info = (BackendInfo *)on;
+	}
+
+	rc = SLAP_CB_CONTINUE;
+
+done:;
+	op->o_dn = save_dn;
+	op->o_ndn = save_ndn;
+	op->o_bd->bd_info = (BackendInfo *)on;
+
+	return rc;
+}
+
+/*
+ * response callback that adds memberof values when a group is added.
+ */
+static int
+memberof_res_add( Operation *op, SlapReply *rs )
+{
+	slap_overinst	*on = (slap_overinst *)op->o_bd->bd_info;
+	memberof_t	*mo = (memberof_t *)on->on_bi.bi_private;
+
+	int		i;
+
+	if ( MEMBEROF_REVERSE( mo ) ) {
+		Attribute	*ma;
+
+		ma = attr_find( op->ora_e->e_attrs, mo->mo_ad_memberof );
+		if ( ma != NULL ) {
+			Operation	op2 = *op;
+			SlapReply	rs2 = { 0 };
+
+			/* relax is required to allow to add
+			 * a non-existing member */
+			op2.o_relax = SLAP_CONTROL_CRITICAL;
+
+			for ( i = 0; !BER_BVISNULL( &ma->a_nvals[ i ] ); i++ ) {
+		
+				/* the modification is attempted
+				 * with the original identity */
+				(void)memberof_value_modify( &op2, &rs2,
+					&ma->a_nvals[ i ], mo->mo_ad_member,
+					NULL, NULL, &op->o_req_dn, &op->o_req_ndn );
+			}
+		}
+	}
+
+	if ( is_entry_objectclass( op->ora_e, mo->mo_oc_group, 0 ) ) {
+		Attribute	*a;
+
+		for ( a = attrs_find( op->ora_e->e_attrs, mo->mo_ad_member );
+				a != NULL;
+				a = attrs_find( a->a_next, mo->mo_ad_member ) )
+		{
+			for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) {
+				(void)memberof_value_modify( op, rs,
+						&a->a_nvals[ i ],
+						mo->mo_ad_memberof,
+						NULL, NULL,
+						&op->o_req_dn,
+						&op->o_req_ndn );
+			}
+		}
+	}
+
+	return SLAP_CB_CONTINUE;
+}
+
+/*
+ * response callback that deletes memberof values when a group is deleted.
+ */
+static int
+memberof_res_delete( Operation *op, SlapReply *rs )
+{
+	slap_overinst	*on = (slap_overinst *)op->o_bd->bd_info;
+	memberof_t	*mo = (memberof_t *)on->on_bi.bi_private;
+
+ 	BerVarray	vals;
+	int		i;
+
+	vals = memberof_saved_member_get( op, &saved_member_vals );
+	if ( vals != NULL ) {
+		for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+			(void)memberof_value_modify( op, rs,
+					&vals[ i ], mo->mo_ad_memberof,
+					&op->o_req_dn, &op->o_req_ndn,
+					NULL, NULL );
+		}
+
+ 		ber_bvarray_free( vals );
+	}
+
+	if ( MEMBEROF_REFINT( mo ) ) {
+		vals = memberof_saved_member_get( op, &saved_memberof_vals );
+		if ( vals != NULL ) {
+			for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+				(void)memberof_value_modify( op, rs,
+						&vals[ i ], mo->mo_ad_member,
+						&op->o_req_dn, &op->o_req_ndn,
+						NULL, NULL );
+			}
+
+	 		ber_bvarray_free( vals );
+		}
+	}
+
+	return SLAP_CB_CONTINUE;
+}
+
+/*
+ * response callback that adds/deletes memberof values when a group
+ * is modified.
+ */
+static int
+memberof_res_modify( Operation *op, SlapReply *rs )
+{
+	slap_overinst	*on = (slap_overinst *)op->o_bd->bd_info;
+	memberof_t	*mo = (memberof_t *)on->on_bi.bi_private;
+
+	int		i, rc;
+	Modifications	*ml, *mml = NULL;
+	BerVarray	vals;
+	memberof_is_t	iswhat = MEMBEROF_IS_GROUP;
+
+	if ( MEMBEROF_REVERSE( mo ) ) {
+		for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) {
+			if ( ml->sml_desc == mo->mo_ad_memberof ) {
+				mml = ml;
+				break;
+			}
+		}
+	}
+
+	if ( mml != NULL ) {
+		BerVarray	vals = mml->sml_nvalues;
+
+		switch ( mml->sml_op ) {
+		case LDAP_MOD_DELETE:
+			if ( vals != NULL ) {
+				for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+					memberof_value_modify( op, rs,
+							&vals[ i ], mo->mo_ad_member,
+							&op->o_req_dn, &op->o_req_ndn,
+							NULL, NULL );
+				}
+				break;
+			}
+			/* fall thru */
+
+		case LDAP_MOD_REPLACE:
+			/* delete all ... */
+			op->o_bd->bd_info = (BackendInfo *)on->on_info;
+			rc = backend_attribute( op, NULL, &op->o_req_ndn,
+					mo->mo_ad_memberof, &vals, ACL_READ );
+			op->o_bd->bd_info = (BackendInfo *)on;
+			if ( rc == LDAP_SUCCESS ) {
+				for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+					(void)memberof_value_modify( op, rs,
+							&vals[ i ], mo->mo_ad_member,
+							&op->o_req_dn, &op->o_req_ndn,
+							NULL, NULL );
+				}
+				ber_bvarray_free_x( vals, op->o_tmpmemctx );
+			}
+
+			if ( ml->sml_op == LDAP_MOD_DELETE ) {
+				break;
+			}
+			/* fall thru */
+
+		case LDAP_MOD_ADD:
+			assert( vals != NULL );
+
+			for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+				memberof_value_modify( op, rs,
+						&vals[ i ], mo->mo_ad_member,
+						NULL, NULL,
+						&op->o_req_dn, &op->o_req_ndn );
+			}
+			break;
+
+		default:
+			assert( 0 );
+		}
+	}
+
+	if ( memberof_isGroupOrMember( op, &iswhat ) == LDAP_SUCCESS
+			&& ( iswhat & MEMBEROF_IS_GROUP ) )
+	{
+		for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) {
+			if ( ml->sml_desc != mo->mo_ad_member ) {
+				continue;
+			}
+
+			switch ( ml->sml_op ) {
+			case LDAP_MOD_DELETE:
+				vals = ml->sml_nvalues;
+				if ( vals != NULL ) {
+					for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+						memberof_value_modify( op, rs,
+								&vals[ i ], mo->mo_ad_memberof,
+								&op->o_req_dn, &op->o_req_ndn,
+								NULL, NULL );
+					}
+					break;
+				}
+				/* fall thru */
+	
+			case LDAP_MOD_REPLACE:
+				/* delete all ... */
+				op->o_bd->bd_info = (BackendInfo *)on->on_info;
+				rc = backend_attribute( op, NULL, &op->o_req_ndn,
+						mo->mo_ad_member, &vals, ACL_READ );
+				op->o_bd->bd_info = (BackendInfo *)on;
+				if ( rc == LDAP_SUCCESS ) {
+					for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+						(void)memberof_value_modify( op, rs,
+								&vals[ i ], mo->mo_ad_memberof,
+								&op->o_req_dn, &op->o_req_ndn,
+								NULL, NULL );
+					}
+					ber_bvarray_free_x( vals, op->o_tmpmemctx );
+				}
+	
+				if ( ml->sml_op == LDAP_MOD_DELETE ) {
+					break;
+				}
+				/* fall thru */
+	
+			case LDAP_MOD_ADD:
+				assert( ml->sml_nvalues != NULL );
+				vals = ml->sml_nvalues;
+				for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+					memberof_value_modify( op, rs,
+							&vals[ i ], mo->mo_ad_memberof,
+							NULL, NULL,
+							&op->o_req_dn, &op->o_req_ndn );
+				}
+				break;
+	
+			default:
+				assert( 0 );
+			}
+		}
+	}
+
+	return SLAP_CB_CONTINUE;
+}
+
+/*
+ * response callback that adds/deletes member values when a group member
+ * is modified.
+ */
+static int
+memberof_res_rename( Operation *op, SlapReply *rs )
+{
+	slap_overinst	*on = (slap_overinst *)op->o_bd->bd_info;
+	memberof_t	*mo = (memberof_t *)on->on_bi.bi_private;
+
+	struct berval	newPDN, newDN = BER_BVNULL, newPNDN, newNDN;
+	int		i, rc;
+	BerVarray	vals;
+
+	struct berval	save_dn, save_ndn;
+	memberof_is_t	iswhat = MEMBEROF_IS_GROUP;
+
+	if ( MEMBEROF_REFINT( mo ) ) {
+		iswhat |= MEMBEROF_IS_MEMBER;
+	}
+
+	if ( op->orr_nnewSup ) {
+		newPNDN = *op->orr_nnewSup;
+
+	} else {
+		dnParent( &op->o_req_ndn, &newPNDN );
+	}
+
+	build_new_dn( &newNDN, &newPNDN, &op->orr_nnewrdn, op->o_tmpmemctx ); 
+
+	save_dn = op->o_req_dn;
+	save_ndn = op->o_req_ndn;
+
+	op->o_req_dn = newNDN;
+	op->o_req_ndn = newNDN;
+	rc = memberof_isGroupOrMember( op, &iswhat );
+	op->o_req_dn = save_dn;
+	op->o_req_ndn = save_ndn;
+
+	if ( rc != LDAP_SUCCESS || iswhat == MEMBEROF_IS_NONE ) {
+		goto done;
+	}
+
+	if ( op->orr_newSup ) {
+		newPDN = *op->orr_newSup;
+
+	} else {
+		dnParent( &op->o_req_dn, &newPDN );
+	}
+
+	build_new_dn( &newDN, &newPDN, &op->orr_newrdn, op->o_tmpmemctx ); 
+
+	if ( iswhat & MEMBEROF_IS_GROUP ) {
+		op->o_bd->bd_info = (BackendInfo *)on->on_info;
+		rc = backend_attribute( op, NULL, &newNDN,
+				mo->mo_ad_member, &vals, ACL_READ );
+		op->o_bd->bd_info = (BackendInfo *)on;
+
+		if ( rc == LDAP_SUCCESS ) {
+			for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+				(void)memberof_value_modify( op, rs,
+						&vals[ i ], mo->mo_ad_memberof,
+						&op->o_req_dn, &op->o_req_ndn,
+						&newDN, &newNDN );
+			}
+			ber_bvarray_free_x( vals, op->o_tmpmemctx );
+		}
+	}
+
+	if ( MEMBEROF_REFINT( mo ) && ( iswhat & MEMBEROF_IS_MEMBER ) ) {
+		op->o_bd->bd_info = (BackendInfo *)on->on_info;
+		rc = backend_attribute( op, NULL, &newNDN,
+				mo->mo_ad_memberof, &vals, ACL_READ );
+		op->o_bd->bd_info = (BackendInfo *)on;
+
+		if ( rc == LDAP_SUCCESS ) {
+			for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+				(void)memberof_value_modify( op, rs,
+						&vals[ i ], mo->mo_ad_member,
+						&op->o_req_dn, &op->o_req_ndn,
+						&newDN, &newNDN );
+			}
+			ber_bvarray_free_x( vals, op->o_tmpmemctx );
+		}
+	}
+
+done:;
+	if ( !BER_BVISNULL( &newDN ) ) {
+		op->o_tmpfree( newDN.bv_val, op->o_tmpmemctx );
+	}
+	op->o_tmpfree( newNDN.bv_val, op->o_tmpmemctx );
+
+	return SLAP_CB_CONTINUE;
+}
+
+static int
+memberof_response( Operation *op, SlapReply *rs )
+{
+	if ( rs->sr_err != LDAP_SUCCESS ) {
+		return SLAP_CB_CONTINUE;
+	}
+
+	switch ( op->o_tag ) {
+	case LDAP_REQ_ADD:
+		return memberof_res_add( op, rs );
+
+	case LDAP_REQ_DELETE:
+		return memberof_res_delete( op, rs );
+
+	case LDAP_REQ_MODIFY:
+		return memberof_res_modify( op, rs );
+
+	case LDAP_REQ_MODDN:
+		return memberof_res_rename( op, rs );
+
+	default:
+		return SLAP_CB_CONTINUE;
+	}
+}
+
+static int
+memberof_db_init(
+	BackendDB	*be,
+	ConfigReply	*cr )
+{
+	slap_overinst	*on = (slap_overinst *)be->bd_info;
+	memberof_t	*mo;
+
+	int		rc;
+	const char	*text = NULL;
+
+	mo = (memberof_t *)ch_calloc( 1, sizeof( memberof_t ) );
+
+	rc = slap_str2ad( SLAPD_MEMBEROF_ATTR, &mo->mo_ad_memberof, &text );
+	if ( rc != LDAP_SUCCESS ) {
+		Debug( LDAP_DEBUG_ANY,
+			"memberof_db_init: "
+			"unable to find attribute=\"%s\": %s (%d)\n",
+			SLAPD_MEMBEROF_ATTR, text, rc );
+		return rc;
+	}
+
+	rc = slap_str2ad( SLAPD_GROUP_ATTR, &mo->mo_ad_member, &text );
+	if ( rc != LDAP_SUCCESS ) {
+		Debug( LDAP_DEBUG_ANY,
+			"memberof_db_init: "
+			"unable to find attribute=\"%s\": %s (%d)\n",
+			SLAPD_GROUP_ATTR, text, rc );
+		return rc;
+	}
+
+	mo->mo_oc_group = oc_find( SLAPD_GROUP_CLASS );
+	if ( mo->mo_oc_group == NULL ) {
+		Debug( LDAP_DEBUG_ANY,
+			"memberof_db_init: "
+			"unable to find objectClass=\"%s\"\n",
+			SLAPD_GROUP_CLASS, 0, 0 );
+		return 1;
+	}
+
+	on->on_bi.bi_private = (void *)mo;
+
+	return 0;
+}
+
+enum {
+	MO_DN = 1,
+	MO_DANGLING,
+	MO_REFINT,
+#if 0
+	MO_REVERSE,
+#endif
+	MO_GROUP_OC,
+	MO_MEMBER_AD,
+	MO_MEMBER_OF_AD
+};
+
+static ConfigDriver mo_cf_gen;
+
+#define OID		"1.3.6.1.4.1.7136.2.666.4"
+#define	OIDAT		OID ".1.1"
+#define	OIDCFGAT	OID ".1.2"
+#define	OIDOC		OID ".2.1"
+#define	OIDCFGOC	OID ".2.2"
+
+
+static ConfigTable mo_cfg[] = {
+	{ "memberof-dn", "modifiersName",
+		2, 2, 0, ARG_MAGIC|ARG_DN|MO_DN, mo_cf_gen,
+		"( OLcfgOvAt:18.0 NAME 'olcMemberOfDN' "
+			"DESC 'DN to be used as modifiersName' "
+			"SYNTAX OMsDN SINGLE-VALUE )",
+		NULL, NULL },
+
+	{ "memberof-dangling", "ignore|drop|error",
+		2, 2, 0, ARG_MAGIC|MO_DANGLING, mo_cf_gen,
+		"( OLcfgOvAt:18.1 NAME 'olcMemberOfDangling' "
+			"DESC 'Behavior with respect to dangling members, "
+				"constrained to ignore, drop, error' "
+			"SYNTAX OMsDirectoryString SINGLE-VALUE )",
+		NULL, NULL },
+
+	{ "memberof-refint", "true|FALSE",
+		2, 2, 0, ARG_MAGIC|ARG_ON_OFF|MO_REFINT, mo_cf_gen,
+		"( OLcfgOvAt:18.2 NAME 'olcMemberOfRefInt' "
+			"DESC 'Take care of referential integrity' "
+			"SYNTAX OMsBoolean SINGLE-VALUE )",
+		NULL, NULL },
+
+	{ "memberof-group-oc", "objectClass",
+		2, 2, 0, ARG_MAGIC|MO_GROUP_OC, mo_cf_gen,
+		"( OLcfgOvAt:18.3 NAME 'olcMemberOfGroupOC' "
+			"DESC 'Group objectClass' "
+			"SYNTAX OMsDirectoryString SINGLE-VALUE )",
+		NULL, NULL },
+
+	{ "memberof-member-ad", "member attribute",
+		2, 2, 0, ARG_MAGIC|MO_MEMBER_AD, mo_cf_gen,
+		"( OLcfgOvAt:18.4 NAME 'olcMemberOfMemberAD' "
+			"DESC 'member attribute' "
+			"SYNTAX OMsDirectoryString SINGLE-VALUE )",
+		NULL, NULL },
+
+	{ "memberof-memberof-ad", "memberOf attribute",
+		2, 2, 0, ARG_MAGIC|MO_MEMBER_OF_AD, mo_cf_gen,
+		"( OLcfgOvAt:18.5 NAME 'olcMemberOfMemberOfAD' "
+			"DESC 'memberOf attribute' "
+			"SYNTAX OMsDirectoryString SINGLE-VALUE )",
+		NULL, NULL },
+
+#if 0
+	{ "memberof-reverse", "true|FALSE",
+		2, 2, 0, ARG_MAGIC|ARG_ON_OFF|MO_REVERSE, mo_cf_gen,
+		"( OLcfgOvAt:18.6 NAME 'olcMemberOfReverse' "
+			"DESC 'Take care of referential integrity "
+				"also when directly modifying memberOf' "
+			"SYNTAX OMsBoolean SINGLE-VALUE )",
+		NULL, NULL },
+#endif
+
+	{ NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs mo_ocs[] = {
+	{ "( OLcfgOvOc:18.1 "
+		"NAME 'olcMemberOf' "
+		"DESC 'Member-of configuration' "
+		"SUP olcOverlayConfig "
+		"MAY ( "
+			"olcMemberOfDN "
+			"$ olcMemberOfDangling "
+			"$ olcMemberOfRefInt "
+			"$ olcMemberOfGroupOC "
+			"$ olcMemberOfMemberAD "
+			"$ olcMemberOfMemberOfAD "
+#if 0
+			"$ olcMemberOfReverse "
+#endif
+			") "
+		")",
+		Cft_Overlay, mo_cfg, NULL, NULL },
+	{ NULL, 0, NULL }
+};
+
+static slap_verbmasks dangling_mode[] = {
+	{ BER_BVC( "ignore" ),		MEMBEROF_NONE },
+	{ BER_BVC( "drop" ),		MEMBEROF_FDANGLING_DROP },
+	{ BER_BVC( "error" ),		MEMBEROF_FDANGLING_ERROR },
+	{ BER_BVNULL,			0 }
+};
+
+static int
+memberof_make_group_filter( memberof_t *mo )
+{
+	char		*ptr;
+
+	if ( !BER_BVISNULL( &mo->mo_groupFilterstr ) ) {
+		ch_free( mo->mo_groupFilterstr.bv_val );
+	}
+
+	mo->mo_groupFilter.f_choice = LDAP_FILTER_EQUALITY;
+	mo->mo_groupFilter.f_ava = &mo->mo_groupAVA;
+	
+	mo->mo_groupFilter.f_av_desc = slap_schema.si_ad_objectClass;
+	mo->mo_groupFilter.f_av_value = mo->mo_oc_group->soc_cname;
+
+	mo->mo_groupFilterstr.bv_len = STRLENOF( "(=)" )
+		+ slap_schema.si_ad_objectClass->ad_cname.bv_len
+		+ mo->mo_oc_group->soc_cname.bv_len;
+	ptr = mo->mo_groupFilterstr.bv_val = ch_malloc( mo->mo_groupFilterstr.bv_len + 1 );
+	*ptr++ = '(';
+	ptr = lutil_strcopy( ptr, slap_schema.si_ad_objectClass->ad_cname.bv_val );
+	*ptr++ = '=';
+	ptr = lutil_strcopy( ptr, mo->mo_oc_group->soc_cname.bv_val );
+	*ptr++ = ')';
+	*ptr = '\0';
+
+	return 0;
+}
+
+static int
+memberof_make_member_filter( memberof_t *mo )
+{
+	char		*ptr;
+
+	if ( !BER_BVISNULL( &mo->mo_memberFilterstr ) ) {
+		ch_free( mo->mo_memberFilterstr.bv_val );
+	}
+
+	mo->mo_memberFilter.f_choice = LDAP_FILTER_PRESENT;
+	mo->mo_memberFilter.f_desc = mo->mo_ad_memberof;
+
+	mo->mo_memberFilterstr.bv_len = STRLENOF( "(=*)" )
+		+ mo->mo_ad_memberof->ad_cname.bv_len;
+	ptr = mo->mo_memberFilterstr.bv_val = ch_malloc( mo->mo_memberFilterstr.bv_len + 1 );
+	*ptr++ = '(';
+	ptr = lutil_strcopy( ptr, mo->mo_ad_memberof->ad_cname.bv_val );
+	ptr = lutil_strcopy( ptr, "=*)" );
+
+	return 0;
+}
+
+static int
+mo_cf_gen( ConfigArgs *c )
+{
+	slap_overinst	*on = (slap_overinst *)c->bi;
+	memberof_t	*mo = (memberof_t *)on->on_bi.bi_private;
+
+	int		i, rc = 0;
+
+	if ( c->op == SLAP_CONFIG_EMIT ) {
+		struct berval bv = BER_BVNULL;
+
+		switch( c->type ) {
+		case MO_DN:
+			value_add_one( &c->rvalue_vals, &mo->mo_dn );
+			value_add_one( &c->rvalue_nvals, &mo->mo_ndn );
+			break;
+
+		case MO_DANGLING:
+			enum_to_verb( dangling_mode, (mo->mo_flags & MEMBEROF_FDANGLING_MASK), &bv );
+			if ( BER_BVISNULL( &bv ) ) {
+				/* there's something wrong... */
+				assert( 0 );
+				rc = 1;
+
+			} else {
+				value_add_one( &c->rvalue_vals, &bv );
+			}
+			break;
+
+		case MO_REFINT:
+			c->value_int = MEMBEROF_REFINT( mo );
+			break;
+
+#if 0
+		case MO_REVERSE:
+			c->value_int = MEMBEROF_REVERSE( mo );
+			break;
+#endif
+
+		case MO_GROUP_OC:
+			assert( mo->mo_oc_group != NULL );
+			value_add_one( &c->rvalue_vals, &mo->mo_oc_group->soc_cname );
+			break;
+
+		case MO_MEMBER_AD:
+			assert( mo->mo_ad_member != NULL );
+			value_add_one( &c->rvalue_vals, &mo->mo_ad_member->ad_cname );
+			break;
+
+		case MO_MEMBER_OF_AD:
+			assert( mo->mo_ad_memberof != NULL );
+			value_add_one( &c->rvalue_vals, &mo->mo_ad_memberof->ad_cname );
+			break;
+
+		default:
+			assert( 0 );
+			return 1;
+		}
+
+		return rc;
+
+	} else if ( c->op == LDAP_MOD_DELETE ) {
+		return 1;	/* FIXME */
+
+	} else {
+		switch( c->type ) {
+		case MO_DN:
+			if ( !BER_BVISNULL( &mo->mo_dn ) ) {
+				ber_memfree( mo->mo_dn.bv_val );
+				ber_memfree( mo->mo_ndn.bv_val );
+			}
+			mo->mo_dn = c->value_dn;
+			mo->mo_ndn = c->value_ndn;
+			break;
+
+		case MO_DANGLING:
+			i = verb_to_mask( c->argv[ 1 ], dangling_mode );
+			if ( BER_BVISNULL( &dangling_mode[ i ].word ) ) {
+				return 1;
+			}
+
+			mo->mo_flags &= ~MEMBEROF_FDANGLING_MASK;
+			mo->mo_flags |= dangling_mode[ i ].mask;
+			break;
+
+		case MO_REFINT:
+			if ( c->value_int ) {
+				mo->mo_flags |= MEMBEROF_FREFINT;
+
+			} else {
+				mo->mo_flags &= ~MEMBEROF_FREFINT;
+			}
+			break;
+
+#if 0
+		case MO_REVERSE:
+			if ( c->value_int ) {
+				mo->mo_flags |= MEMBEROF_FREVERSE;
+
+			} else {
+				mo->mo_flags &= ~MEMBEROF_FREVERSE;
+			}
+			break;
+#endif
+
+		case MO_GROUP_OC: {
+			ObjectClass	*oc = NULL;
+
+			oc = oc_find( c->argv[ 1 ] );
+			if ( oc == NULL ) {
+				snprintf( c->cr_msg, sizeof( c->cr_msg ),
+					"unable to find group objectClass=\"%s\"",
+					c->argv[ 1 ] );
+				Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n",
+					c->log, c->cr_msg, 0 );
+				return 1;
+			}
+
+			mo->mo_oc_group = oc;
+			memberof_make_group_filter( mo );
+			} break;
+
+		case MO_MEMBER_AD: {
+			AttributeDescription	*ad = NULL;
+			const char		*text = NULL;
+
+
+			rc = slap_str2ad( c->argv[ 1 ], &ad, &text );
+			if ( rc != LDAP_SUCCESS ) {
+				snprintf( c->cr_msg, sizeof( c->cr_msg ),
+					"unable to find member attribute=\"%s\": %s (%d)",
+					c->argv[ 1 ], text, rc );
+				Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n",
+					c->log, c->cr_msg, 0 );
+				return 1;
+			}
+
+			if ( !is_at_syntax( ad->ad_type, SLAPD_DN_SYNTAX )		/* e.g. "member" */
+				&& !is_at_syntax( ad->ad_type, SLAPD_NAMEUID_SYNTAX ) )	/* e.g. "uniqueMember" */
+			{
+				snprintf( c->cr_msg, sizeof( c->cr_msg ),
+					"member attribute=\"%s\" must either "
+					"have DN (%s) or nameUID (%s) syntax",
+					c->argv[ 1 ], SLAPD_DN_SYNTAX, SLAPD_NAMEUID_SYNTAX );
+				Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n",
+					c->log, c->cr_msg, 0 );
+				return 1;
+			}
+
+			mo->mo_ad_member = ad;
+			} break;
+
+		case MO_MEMBER_OF_AD: {
+			AttributeDescription	*ad = NULL;
+			const char		*text = NULL;
+
+
+			rc = slap_str2ad( c->argv[ 1 ], &ad, &text );
+			if ( rc != LDAP_SUCCESS ) {
+				snprintf( c->cr_msg, sizeof( c->cr_msg ),
+					"unable to find memberof attribute=\"%s\": %s (%d)",
+					c->argv[ 1 ], text, rc );
+				Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n",
+					c->log, c->cr_msg, 0 );
+				return 1;
+			}
+
+			if ( !is_at_syntax( ad->ad_type, SLAPD_DN_SYNTAX )		/* e.g. "member" */
+				&& !is_at_syntax( ad->ad_type, SLAPD_NAMEUID_SYNTAX ) )	/* e.g. "uniqueMember" */
+			{
+				snprintf( c->cr_msg, sizeof( c->cr_msg ),
+					"memberof attribute=\"%s\" must either "
+					"have DN (%s) or nameUID (%s) syntax",
+					c->argv[ 1 ], SLAPD_DN_SYNTAX, SLAPD_NAMEUID_SYNTAX );
+				Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n",
+					c->log, c->cr_msg, 0 );
+				return 1;
+			}
+
+			mo->mo_ad_memberof = ad;
+			memberof_make_member_filter( mo );
+			} break;
+
+		default:
+			assert( 0 );
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+static int
+memberof_db_open(
+	BackendDB	*be,
+	ConfigReply	*cr )
+{
+	slap_overinst	*on = (slap_overinst *)be->bd_info;
+	memberof_t	*mo = (memberof_t *)on->on_bi.bi_private;
+
+	if ( BER_BVISNULL( &mo->mo_dn ) ) {
+		ber_dupbv( &mo->mo_dn, &be->be_rootdn );
+		ber_dupbv( &mo->mo_ndn, &be->be_rootndn );
+	}
+
+	if ( BER_BVISNULL( &mo->mo_groupFilterstr ) ) {
+		memberof_make_group_filter( mo );
+	}
+
+	if ( BER_BVISNULL( &mo->mo_memberFilterstr ) ) {
+		memberof_make_member_filter( mo );
+	}
+
+	return 0;
+}
+
+static int
+memberof_db_destroy(
+	BackendDB	*be,
+	ConfigReply	*cr )
+{
+	slap_overinst	*on = (slap_overinst *)be->bd_info;
+	memberof_t	*mo = (memberof_t *)on->on_bi.bi_private;
+
+	if ( mo ) {
+		if ( !BER_BVISNULL( &mo->mo_dn ) ) {
+			ber_memfree( mo->mo_dn.bv_val );
+			ber_memfree( mo->mo_ndn.bv_val );
+		}
+
+		if ( !BER_BVISNULL( &mo->mo_groupFilterstr ) ) {
+			ber_memfree( mo->mo_groupFilterstr.bv_val );
+		}
+
+		if ( !BER_BVISNULL( &mo->mo_memberFilterstr ) ) {
+			ber_memfree( mo->mo_memberFilterstr.bv_val );
+		}
+
+		ber_memfree( mo );
+	}
+
+	return 0;
+}
+
+/* unused */
+static AttributeDescription	*ad_memberOf;
+
+static struct {
+	char	*desc;
+	AttributeDescription **adp;
+} as[] = {
+	{ "( 1.2.840.113556.1.2.102 "
+		"NAME 'memberOf' "
+		"DESC 'Group that the entry belongs to' "
+		"SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' "
+		"EQUALITY distinguishedNameMatch "	/* added */
+		"USAGE directoryOperation "		/* questioned */
+		/* "NO-USER-MODIFICATION " */
+		"X-ORIGIN 'iPlanet Delegated Administrator' )",
+		&ad_memberOf },
+	{ NULL }
+};
+
+#if SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC
+static
+#endif /* SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC */
+int
+memberof_initialize( void )
+{
+	int			code, i;
+
+	for ( i = 0; as[ i ].desc != NULL; i++ ) {
+		code = register_at( as[ i ].desc, as[ i ].adp, 0 );
+		if ( code ) {
+			Debug( LDAP_DEBUG_ANY,
+				"memberof_initialize: register_at #%d failed\n",
+				i, 0, 0 );
+			return code;
+		}
+	}
+
+	memberof.on_bi.bi_type = "memberof";
+
+	memberof.on_bi.bi_db_init = memberof_db_init;
+	memberof.on_bi.bi_db_open = memberof_db_open;
+	memberof.on_bi.bi_db_destroy = memberof_db_destroy;
+
+	memberof.on_bi.bi_op_add = memberof_op_add;
+	memberof.on_bi.bi_op_delete = memberof_op_delete;
+	memberof.on_bi.bi_op_modify = memberof_op_modify;
+
+	memberof.on_response = memberof_response;
+
+	memberof.on_bi.bi_cf_ocs = mo_ocs;
+
+	code = config_register_schema( mo_cfg, mo_ocs );
+	if ( code ) return code;
+
+	return overlay_register( &memberof );
+}
+
+#if SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+	return memberof_initialize();
+}
+#endif /* SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC */
+
+#endif /* SLAPD_OVER_MEMBEROF */
-- 
GitLab