]> git.ipfire.org Git - thirdparty/openldap.git/commitdiff
ITS#10161 Add nestgroup overlay
authorHoward Chu <hyc@openldap.org>
Fri, 26 Jan 2024 17:22:16 +0000 (17:22 +0000)
committerQuanah Gibson-Mount <quanah@openldap.org>
Tue, 16 Apr 2024 16:08:15 +0000 (16:08 +0000)
configure.ac
doc/man/man5/slapo-nestgroup.5 [new file with mode: 0644]
servers/slapd/bconfig.c
servers/slapd/overlays/Makefile.in
servers/slapd/overlays/nestgroup.c [new file with mode: 0644]
tests/data/nestgroup.out.1 [new file with mode: 0644]
tests/data/nestgroup.out.2 [new file with mode: 0644]
tests/scripts/conf.sh
tests/scripts/defines.sh
tests/scripts/test089-nestgroup [new file with mode: 0755]

index 5fe4e0c41c634aa4ef123c18f1407dbcc708f5c0..20de381444a398cd36df98bf3a9e5f3ab79ce24f 100644 (file)
@@ -349,6 +349,7 @@ Overlays="accesslog \
        dynlist \
        homedir \
        memberof \
+       nestgroup \
        otp \
        ppolicy \
        proxycache \
@@ -392,6 +393,8 @@ OL_ARG_ENABLE(homedir, [AS_HELP_STRING([--enable-homedir], [Home Directory Manag
        no, [no yes mod], ol_enable_overlays)
 OL_ARG_ENABLE(memberof, [AS_HELP_STRING([--enable-memberof], [Reverse Group Membership overlay])],
        no, [no yes mod], ol_enable_overlays)
+OL_ARG_ENABLE(nestgroup, [AS_HELP_STRING([--enable-nestgroup], [Nested Group overlay])],
+       no, [no yes mod], ol_enable_overlays)
 OL_ARG_ENABLE(otp, [AS_HELP_STRING([--enable-otp], [OTP 2-factor authentication overlay])],
        no, [no yes mod], ol_enable_overlays)
 OL_ARG_ENABLE(ppolicy, [AS_HELP_STRING([--enable-ppolicy], [Password Policy overlay])],
@@ -2866,6 +2869,17 @@ if test "$ol_enable_memberof" != no ; then
        AC_DEFINE_UNQUOTED(SLAPD_OVER_MEMBEROF,$MFLAG,[define for Reverse Group Membership overlay])
 fi
 
+if test "$ol_enable_nestgroup" != no ; then
+       if test "$ol_enable_nestgroup" = mod ; then
+               MFLAG=SLAPD_MOD_DYNAMIC
+               SLAPD_DYNAMIC_OVERLAYS="$SLAPD_DYNAMIC_OVERLAYS nestgroup.la"
+       else
+               MFLAG=SLAPD_MOD_STATIC
+               SLAPD_STATIC_OVERLAYS="$SLAPD_STATIC_OVERLAYS nestgroup.o"
+       fi
+       AC_DEFINE_UNQUOTED(SLAPD_OVER_NESTGROUP,$MFLAG,[define for Nested Group overlay])
+fi
+
 if test "$ol_enable_otp" != no ; then
        if test $ol_with_tls = no ; then
                AC_MSG_ERROR([--enable-otp=$ol_enable_otp requires --with-tls])
diff --git a/doc/man/man5/slapo-nestgroup.5 b/doc/man/man5/slapo-nestgroup.5
new file mode 100644 (file)
index 0000000..9f0ddbb
--- /dev/null
@@ -0,0 +1,92 @@
+.TH SLAPO-NESTGROUP 5 "RELEASEDATE" "OpenLDAP LDVERSION"
+.\" Copyright 2024 The OpenLDAP Foundation, All Rights Reserved.
+.\" Copying restrictions apply.  See the COPYRIGHT file.
+.\" $OpenLDAP$
+.SH NAME
+slapo\-nestgroup \- Nested Group overlay to slapd
+.SH SYNOPSIS
+ETCDIR/slapd.conf
+.SH DESCRIPTION
+The
+.B nestgroup
+overlay to
+.BR slapd (8)
+supports evaluation of nested groups in Search operations. Support consists
+of four possible features: inclusion of parent groups when searching with
+(member=) filters, inclusion of child groups when searching with (memberOf=)
+filters, expansion of child groups when returning member attributes, and
+expansion of parent groups when returning memberOf attributes. Each of
+these features may be enabled independently. By default, no features are
+enabled, so this overlay does nothing unless explicitly enabled.
+
+.SH CONFIGURATION
+The config directives that are specific to the
+.B nestgroup
+overlay must be prefixed by
+.BR nestgroup\- ,
+to avoid potential conflicts with directives specific to the underlying
+database or to other stacked overlays.
+
+.TP
+.B overlay nestgroup
+This directive adds the nestgroup overlay to the current database; see
+.BR slapd.conf (5)
+for details.
+
+.LP
+The following
+.B slapd.conf
+configuration options are defined for the nestgroup overlay.
+
+.TP
+.BI nestgroup\-member \ <member-ad>
+The value
+.I <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
+.BI nestgroup\-memberof \ <memberof-ad>
+The value
+.I <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.
+It defaults to \fImemberOf\fP.
+
+.TP
+.BI nestgroup\-base \ <dn>
+The value
+.I <dn>
+specifies a subtree that contains group entries in the DIT. This
+may be specified multiple times for multiple distinct subtrees.
+It has no default and the overlay does no processing unless it is
+explicitly configured.
+
+.TP
+.BI "nestgroup\-flags {" member-filter ", " memberof-filter ", " member-values ", " memberof-values "}"
+This option specifies which features to enable in the overlay.
+By default, nothing is enabled and the overlay is a no-op.
+
+.LP
+The nestgroup overlay may be used with any backend that provides standard
+search functionality.
+
+.SH FILES
+.TP
+ETCDIR/slapd.conf
+default slapd configuration file
+.SH SEE ALSO
+.BR slapo\-dynlist (5),
+.BR slapo\-memberof (5),
+.BR slapd.conf (5),
+.BR slapd\-config (5),
+.BR slapd (8).
+The
+.BR slapo\-nestgroup (5)
+overlay supports dynamic configuration via
+.BR back-config .
+.SH ACKNOWLEDGEMENTS
+.P
+This module was written in 2024 by Howard Chu of Symas Corporation.
+
index 00b0191c3fdf18dad393937ae2ffb6d8cc2721e7..572bcc4b2d369b68284c426d73e44a7b6975e95d 100644 (file)
@@ -285,6 +285,7 @@ static OidRec OidMacros[] = {
  * OLcfgOv{Oc|At}:21                   -> sssvlv
  * OLcfgOv{Oc|At}:22                   -> autoca
  * OLcfgOv{Oc|At}:24                   -> remoteauth
+ * OLcfgOv{Oc|At}:25                   -> nestgroup
  */
 
 /* alphabetical ordering */
index 1fb18b749d813558046d0599b68e9c6e749c074a..6d886f8503bb98e41203c528903858382306bfae 100644 (file)
@@ -24,6 +24,7 @@ SRCS = overlays.c \
        dynlist.c \
        homedir.c \
        memberof.c \
+       nestgroup.c \
        otp.c \
        pcache.c \
        collect.c \
@@ -96,6 +97,9 @@ homedir.la : homedir.lo
 memberof.la : memberof.lo
        $(LTLINK_MOD) -module -o $@ memberof.lo version.lo $(LINK_LIBS)
 
+nestgroup.la : nestgroup.lo
+       $(LTLINK_MOD) -module -o $@ nestgroup.lo version.lo $(LINK_LIBS)
+
 otp.la : otp.lo
        $(LTLINK_MOD) -module -o $@ otp.lo version.lo $(LINK_LIBS)
 
diff --git a/servers/slapd/overlays/nestgroup.c b/servers/slapd/overlays/nestgroup.c
new file mode 100644 (file)
index 0000000..ca5c928
--- /dev/null
@@ -0,0 +1,909 @@
+/* nestgroup.c - nested group overlay */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2024 The OpenLDAP Foundation.
+ * Copyright 2024 by Howard Chu.
+ * 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>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Howard Chu for inclusion in
+ * OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_NESTGROUP
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "lutil.h"
+#include "slap.h"
+#include "slap-config.h"
+
+/* This overlay dynamically constructs member and memberOf attributes
+ * for nested groups.
+ */
+
+#define SLAPD_MEMBEROF_ATTR    "memberOf"
+
+#define NG_MBR_VALUES  0x01
+#define NG_MBR_FILTER  0x02
+#define NG_MOF_VALUES  0x04
+#define NG_MOF_FILTER  0x08
+#define NG_NEGATED             0x10
+
+static AttributeDescription *ad_member;
+static AttributeDescription *ad_memberOf;
+
+static slap_verbmasks nestgroup_flags[] = {
+       { BER_BVC("member-values"),     NG_MBR_VALUES },
+       { BER_BVC("member-filter"),     NG_MBR_FILTER },
+       { BER_BVC("memberof-values"),   NG_MOF_VALUES },
+       { BER_BVC("memberof-filter"),   NG_MOF_FILTER },
+       { BER_BVNULL,   0 }
+};
+
+enum {
+       NG_MEMBER = 1,
+       NG_MEMBEROF,
+       NG_GROUPBASE,
+       NG_FLAGS
+};
+
+typedef struct nestgroup_info_t {
+       AttributeDescription *ngi_member;
+       AttributeDescription *ngi_memberOf;
+       BerVarray ngi_groupBase;
+       BerVarray ngi_ngroupBase;
+       int ngi_flags;
+} nestgroup_info_t;
+
+static int ngroup_cf( ConfigArgs *c )
+{
+       slap_overinst *on = (slap_overinst *)c->bi;
+       nestgroup_info_t *ngi = (nestgroup_info_t *)on->on_bi.bi_private;
+       int rc = 1;
+
+       if ( c->op == SLAP_CONFIG_EMIT ) {
+               switch( c->type ) {
+               case NG_MEMBER:
+                       if ( ngi->ngi_member ) {
+                               value_add_one( &c->rvalue_vals, &ngi->ngi_member->ad_cname );
+                               rc = 0;
+                       }
+                       break;
+               case NG_MEMBEROF:
+                       if ( ngi->ngi_memberOf ) {
+                               value_add_one( &c->rvalue_vals, &ngi->ngi_memberOf->ad_cname );
+                               rc = 0;
+                       }
+                       break;
+               case NG_GROUPBASE:
+                       if ( ngi->ngi_groupBase ) {
+                               value_add( &c->rvalue_vals, ngi->ngi_groupBase );
+                               value_add( &c->rvalue_nvals, ngi->ngi_ngroupBase );
+                               rc = 0;
+                       }
+                       break;
+               case NG_FLAGS:
+                       return mask_to_verbs( nestgroup_flags, ngi->ngi_flags, &c->rvalue_vals );
+               default:
+                       break;
+               }
+               return rc;
+       } else if ( c->op == LDAP_MOD_DELETE ) {
+               switch( c->type ) {
+               case NG_MEMBER:
+                       ngi->ngi_member = ad_member;
+                       rc = 0;
+                       break;
+               case NG_MEMBEROF:
+                       ngi->ngi_memberOf = ad_memberOf;
+                       rc = 0;
+                       break;
+               case NG_GROUPBASE:
+                       if ( c->valx < 0 ) {
+                               ber_bvarray_free( ngi->ngi_groupBase );
+                               ber_bvarray_free( ngi->ngi_ngroupBase );
+                               ngi->ngi_groupBase = NULL;
+                               ngi->ngi_ngroupBase = NULL;
+                       } else {
+                               int i = c->valx;
+                               ch_free( ngi->ngi_groupBase[i].bv_val );
+                               ch_free( ngi->ngi_ngroupBase[i].bv_val );
+                               do {
+                                       ngi->ngi_groupBase[i] = ngi->ngi_groupBase[i+1];
+                                       ngi->ngi_ngroupBase[i] = ngi->ngi_ngroupBase[i+1];
+                                       i++;
+                               } while ( !BER_BVISNULL( &ngi->ngi_groupBase[i] ));
+                       }
+                       rc = 0;
+                       break;
+               case NG_FLAGS:
+                       if ( !c->line ) {
+                               ngi->ngi_flags = 0;
+                       } else {
+                               int i = verb_to_mask( c->line, nestgroup_flags );
+                               ngi->ngi_flags &= ~nestgroup_flags[i].mask;
+                       }
+                       rc = 0;
+                       break;
+               default:
+                       break;
+               }
+               return rc;
+       }
+
+       switch( c->type ) {
+       case NG_MEMBER:
+               if ( !is_at_syntax( c->value_ad->ad_type, SLAPD_DN_SYNTAX ) &&
+                       !is_at_syntax( c->value_ad->ad_type, SLAPD_NAMEUID_SYNTAX )) {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "member attribute=\"%s\" must use DN (%s) or NAMEUID (%s) syntax",
+                               c->argv[1], SLAPD_DN_SYNTAX, SLAPD_NAMEUID_SYNTAX );
+                       Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+                               "%s: %s\n", c->log, c->cr_msg );
+                       return ARG_BAD_CONF;
+               }
+               ngi->ngi_member = c->value_ad;
+               rc = 0;
+               break;
+       case NG_MEMBEROF:
+               if ( !is_at_syntax( c->value_ad->ad_type, SLAPD_DN_SYNTAX ) &&
+                       !is_at_syntax( c->value_ad->ad_type, SLAPD_NAMEUID_SYNTAX )) {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "memberOf attribute=\"%s\" must use DN (%s) or NAMEUID (%s) syntax",
+                               c->argv[1], SLAPD_DN_SYNTAX, SLAPD_NAMEUID_SYNTAX );
+                       Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+                               "%s: %s\n", c->log, c->cr_msg );
+                       return ARG_BAD_CONF;
+               }
+               ngi->ngi_memberOf = c->value_ad;
+               rc = 0;
+               break;
+       case NG_GROUPBASE:
+               ber_bvarray_add( &ngi->ngi_groupBase, &c->value_dn );
+               ber_bvarray_add( &ngi->ngi_ngroupBase, &c->value_ndn );
+               rc = 0;
+               break;
+       case NG_FLAGS: {
+               slap_mask_t flags = 0;
+               int i;
+               if ( c->op != SLAP_CONFIG_ADD && c->argc > 2 ) {
+                       /* We wouldn't know how to delete these values later */
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "Please insert multiple names as separate %s values",
+                               c->argv[0] );
+                       Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+                               "%s: %s\n", c->log, c->cr_msg );
+                       rc = LDAP_INVALID_SYNTAX;
+                       break;
+               }
+               i = verbs_to_mask( c->argc, c->argv, nestgroup_flags, &flags );
+               if ( i ) {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unknown option", c->argv[0] );
+                       Debug(LDAP_DEBUG_ANY, "%s: %s %s\n",
+                               c->log, c->cr_msg, c->argv[i]);
+                       return(1);
+               }
+               ngi->ngi_flags |= flags;
+               rc = 0;
+               break; }
+       default:
+               break;
+       }
+
+       return rc;
+}
+
+static ConfigTable ngroupcfg[] = {
+       { "nestgroup-member", "member-ad", 2, 2, 0,
+         ARG_MAGIC|ARG_ATDESC|NG_MEMBER, ngroup_cf,
+         "( OLcfgOvAt:25.1 NAME 'olcNestGroupMember' "
+         "EQUALITY caseIgnoreMatch "
+         "DESC 'Member attribute' "
+         "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+       { "nestgroup-memberof", "memberOf-ad", 2, 2, 0,
+         ARG_MAGIC|ARG_ATDESC|NG_MEMBEROF, ngroup_cf,
+         "( OLcfgOvAt:25.2 NAME 'olcNestGroupMemberOf' "
+         "EQUALITY caseIgnoreMatch "
+         "DESC 'MemberOf attribute' "
+         "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+       { "nestgroup-base", "dn", 2, 2, 0,
+         ARG_DN|ARG_QUOTE|ARG_MAGIC|NG_GROUPBASE, ngroup_cf,
+         "( OLcfgOvAt:25.3 NAME 'olcNestGroupBase' "
+         "EQUALITY distinguishedNameMatch "
+         "DESC 'Base[s] of group subtree[s]' "
+         "SYNTAX OMsDN )", NULL, NULL },
+       { "nestgroup-flags", "options", 2, 0, 0,
+         ARG_MAGIC|NG_FLAGS, ngroup_cf,
+         "( OLcfgOvAt:25.4 NAME 'olcNestGroupFlags' "
+         "EQUALITY caseIgnoreMatch "
+         "DESC 'Features to use' "
+         "SYNTAX OMsDirectoryString )", NULL, NULL },
+       { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs ngroupocs[] = {
+       { "( OLcfgOvOc:25.1 "
+         "NAME 'olcNestGroupConfig' "
+         "DESC 'Nested Group configuration' "
+         "SUP olcOverlayConfig "
+         "MAY ( olcNestGroupMember $ olcNestGroupMemberOf $ "
+         " olcNestGroupBase $ olcNestGroupFlags ) ) ",
+         Cft_Overlay, ngroupcfg },
+       { NULL, 0, NULL }
+};
+
+typedef struct nestgroup_filterinst_t {
+       Filter *nf_f;
+       Filter *nf_new;
+       Entry *nf_e;
+} nestgroup_filterinst_t;
+
+/* Record occurrences of ad in filter. Ignore in negated filters. */
+static void
+nestgroup_filter_instances( Operation *op, AttributeDescription *ad, Filter *f, int not,
+       int *nfn, nestgroup_filterinst_t **nfp, int *negated )
+{
+       if ( !f )
+               return;
+
+       switch( f->f_choice & SLAPD_FILTER_MASK ) {
+       case LDAP_FILTER_EQUALITY:
+               if ( f->f_av_desc == ad ) {
+                       if ( not ) {
+                               *negated = 1;
+                       } else {
+                               nestgroup_filterinst_t *nf = *nfp;
+                               int n = *nfn;
+                               nf = op->o_tmprealloc( nf, (n + 1) * sizeof(nestgroup_filterinst_t), op->o_tmpmemctx );
+                               nf[n].nf_f = f;
+                               nf[n].nf_new = NULL;
+                               nf[n++].nf_e = NULL;
+                               *nfp = nf;
+                               *nfn = n;
+                       }
+               }
+               break;
+       case SLAPD_FILTER_COMPUTED:
+       case LDAP_FILTER_PRESENT:
+       case LDAP_FILTER_GE:
+       case LDAP_FILTER_LE:
+       case LDAP_FILTER_APPROX:
+       case LDAP_FILTER_SUBSTRINGS:
+       case LDAP_FILTER_EXT:
+               break;
+       case LDAP_FILTER_NOT:   not ^= 1;
+               /* FALLTHRU */
+       case LDAP_FILTER_AND:
+       case LDAP_FILTER_OR:
+               for ( f = f->f_list; f; f = f->f_next )
+                       nestgroup_filter_instances( op, ad, f, not, nfn, nfp, negated );
+       }
+}
+
+static int
+nestgroup_check_needed( Operation *op, int attrflags, AttributeDescription *ad )
+{
+       if ( is_at_operational( ad->ad_type )) {
+               if ( SLAP_OPATTRS( attrflags ))
+                       return 1;
+       } else {
+               if ( SLAP_USERATTRS( attrflags ))
+                       return 1;
+       }
+       return ( ad_inlist( ad, op->ors_attrs ));
+}
+
+typedef struct DNpair {
+       struct berval dp_ndn;
+       struct berval dp_dn;
+       struct DNpair *dp_next;
+       int dp_flag;
+} DNpair;
+
+typedef struct gdn_info {
+       TAvlnode *gi_DNs;
+       DNpair *gi_DNlist;
+       nestgroup_info_t *gi_ngi;
+       int gi_numDNs;
+       int gi_saveDN;
+       Attribute *gi_merge;
+} gdn_info;
+
+static int
+nestgroup_dncmp( const void *v1, const void *v2 )
+{
+       return ber_bvcmp((const struct berval *)v1, (const struct berval *)v2);
+}
+
+static int
+nestgroup_gotDNresp( Operation *op, SlapReply *rs )
+{
+       if ( rs->sr_type == REP_SEARCH ) {
+               gdn_info *gi = (gdn_info *)(op->o_callback+1);
+               DNpair *dp = op->o_tmpalloc( sizeof(DNpair), op->o_tmpmemctx );
+               dp->dp_ndn = rs->sr_entry->e_nname;
+               if ( ldap_tavl_insert( &gi->gi_DNs, dp, nestgroup_dncmp, ldap_avl_dup_error )) {
+                       op->o_tmpfree( dp, op->o_tmpmemctx );
+               } else {
+                       ber_dupbv_x( &dp->dp_ndn, &rs->sr_entry->e_nname, op->o_tmpmemctx );
+                       if ( gi->gi_saveDN )
+                               ber_dupbv_x( &dp->dp_dn, &rs->sr_entry->e_name, op->o_tmpmemctx );
+                       gi->gi_numDNs++;
+                       dp->dp_next = gi->gi_DNlist;
+                       dp->dp_flag = 0;
+                       gi->gi_DNlist = dp;
+               }
+       }
+       return 0;
+}
+
+static void
+nestgroup_get_parentDNs( Operation *op, struct berval *ndn )
+{
+       SlapReply r = { REP_SEARCH };
+       gdn_info *gi = (gdn_info *)(op->o_callback+1);
+       nestgroup_info_t *ngi = gi->gi_ngi;
+       int i;
+
+       op->ors_filter->f_av_value = *ndn;
+       for ( i=0; !BER_BVISEMPTY( &ngi->ngi_ngroupBase[i] ); i++ ) {
+               op->o_req_dn = ngi->ngi_groupBase[i];
+               op->o_req_ndn = ngi->ngi_ngroupBase[i];
+               op->o_bd->be_search( op, &r );
+       }
+       gi->gi_numDNs = 0; /* ignore first count, that's just the original member= result set */
+
+       while ( gi->gi_DNlist ) {
+               int prevnum;
+               DNpair *dp = gi->gi_DNlist;
+               gi->gi_DNlist = NULL;
+               for ( ; dp; dp=dp->dp_next ) {
+                       op->ors_filter->f_av_value = dp->dp_ndn;
+                       prevnum = gi->gi_numDNs;
+                       for ( i=0; !BER_BVISEMPTY( &ngi->ngi_ngroupBase[i] ); i++ ) {
+                               op->o_req_dn = ngi->ngi_groupBase[i];
+                               op->o_req_ndn = ngi->ngi_ngroupBase[i];
+                               op->o_bd->be_search( op, &r );
+                       }
+                       if ( gi->gi_numDNs > prevnum )
+                               dp->dp_flag = 1;        /* this group had a parent */
+               }
+       }
+}
+
+static void
+nestgroup_memberFilter( Operation *op, int mbr_nf, nestgroup_filterinst_t *mbr_f )
+{
+       slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+       nestgroup_info_t *ngi = on->on_bi.bi_private;
+       AttributeDescription *ad = mbr_f[0].nf_f->f_av_desc;
+       slap_callback *sc;
+       gdn_info *gi;
+       Filter mf;
+       AttributeAssertion mava;
+       Operation o = *op;
+       int i;
+
+       o.o_managedsait = SLAP_CONTROL_CRITICAL;
+       sc = op->o_tmpcalloc( 1, sizeof(slap_callback) + sizeof(gdn_info), op->o_tmpmemctx);
+       gi = (gdn_info *)(sc+1);
+       gi->gi_ngi = ngi;
+       o.o_callback = sc;
+       sc->sc_response = nestgroup_gotDNresp;
+       o.ors_attrs = slap_anlist_no_attrs;
+
+       mf.f_choice = LDAP_FILTER_EQUALITY;
+       mf.f_ava = &mava;
+       mf.f_av_desc = ad;
+       mf.f_next = NULL;
+
+       o.ors_scope = LDAP_SCOPE_SUBTREE;
+       o.ors_deref = LDAP_DEREF_NEVER;
+       o.ors_limit = NULL;
+       o.ors_tlimit = SLAP_NO_LIMIT;
+       o.ors_slimit = SLAP_NO_LIMIT;
+       o.ors_filter = &mf;
+       o.o_bd->bd_info = (BackendInfo *)on->on_info;
+
+       for ( i=0; i<mbr_nf; i++ ) {
+               gi->gi_DNs = NULL;
+               gi->gi_numDNs = 0;
+               nestgroup_get_parentDNs( &o, &mbr_f[i].nf_f->f_av_value );
+               if ( gi->gi_numDNs ) {
+                       int j;
+                       Filter *f, *nf;
+                       TAvlnode *t;
+                       DNpair *dp;
+
+                       f = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+                       f->f_next = NULL;
+                       t = ldap_tavl_end( gi->gi_DNs, TAVL_DIR_RIGHT );
+                       do {
+                               dp = t->avl_data;
+                               if ( dp->dp_flag ) {
+                                       nf = f;
+                                       nf->f_ava = op->o_tmpcalloc( 1, sizeof( AttributeAssertion ), op->o_tmpmemctx );
+                                       nf->f_choice = LDAP_FILTER_EQUALITY;
+                                       nf->f_av_desc = ad;
+                                       nf->f_av_value = dp->dp_ndn;
+                                       f = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+                                       f->f_next = nf;
+                               }
+                               t = ldap_tavl_next( t, TAVL_DIR_LEFT );
+                               op->o_tmpfree( dp, op->o_tmpmemctx );
+                       } while ( t );
+                       ldap_tavl_free( gi->gi_DNs, NULL );
+                       f->f_choice = LDAP_FILTER_EQUALITY;
+                       f->f_ava = mbr_f[i].nf_f->f_ava;
+                       mbr_f[i].nf_new = f;
+               }
+       }
+       o.o_bd->bd_info = (BackendInfo *)on->on_info;
+       op->o_tmpfree( sc, op->o_tmpmemctx );
+}
+
+static void
+nestgroup_addUnique( Operation *op, Attribute *old, Attribute *new )
+{
+       /* strip out any duplicates from new before adding */
+       struct berval *bv, *nbv;
+       int i, j, flags;
+
+       bv = op->o_tmpalloc( (new->a_numvals + 1) * 2 * sizeof(struct berval), op->o_tmpmemctx );
+       nbv = bv + new->a_numvals+1;
+
+       flags = SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH|SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH;
+       for (i=0,j=0; i<new->a_numvals; i++) {
+               int rc = attr_valfind( old, flags, &new->a_nvals[i], NULL, NULL );
+               if ( rc ) {
+                       bv[j] = new->a_vals[i];
+                       nbv[j++] = new->a_nvals[i];
+               }
+       }
+       BER_BVZERO( &bv[j] );
+       BER_BVZERO( &nbv[j] );
+       attr_valadd( old, bv, nbv, j );
+       op->o_tmpfree( bv, op->o_tmpmemctx );
+}
+
+static void
+nestgroup_get_childDNs( Operation *op, slap_overinst *on, gdn_info *gi, struct berval *ndn )
+{
+       nestgroup_info_t *ngi = on->on_bi.bi_private;
+       Entry *e;
+       Attribute *a;
+
+       if ( overlay_entry_get_ov( op, ndn, NULL, NULL, 0, &e, on ) != LDAP_SUCCESS || e == NULL )
+               return;
+
+       a = attr_find( e->e_attrs, ngi->ngi_member );
+       if ( a ) {
+               int i, j;
+               for (i = 0; i<a->a_numvals; i++ ) {
+                       /* record all group entries */
+                       for (j = 0; !BER_BVISEMPTY( &ngi->ngi_groupBase[j] ); j++) {
+                               if ( dnIsSuffix( &a->a_nvals[i], &ngi->ngi_ngroupBase[j] )) {
+                                       DNpair *dp = op->o_tmpalloc( sizeof(DNpair), op->o_tmpmemctx );
+                                       dp->dp_ndn = a->a_nvals[i];
+                                       if ( ldap_tavl_insert( &gi->gi_DNs, dp, nestgroup_dncmp, ldap_avl_dup_error )) {
+                                               op->o_tmpfree( dp, op->o_tmpmemctx );
+                                       } else {
+                                               ber_dupbv_x( &dp->dp_ndn, &a->a_nvals[i], op->o_tmpmemctx );
+                                               gi->gi_numDNs++;
+                                               dp->dp_next = gi->gi_DNlist;
+                                               gi->gi_DNlist = dp;
+                                       }
+                                       break;
+                               }
+                       }
+               }
+               if ( gi->gi_merge ) {
+                       nestgroup_addUnique( op, gi->gi_merge, a );
+               }
+       }
+       overlay_entry_release_ov( op, e, 0, on );
+}
+
+static void
+nestgroup_memberOfFilter( Operation *op, int mof_nf, nestgroup_filterinst_t *mof_f )
+{
+       slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+       AttributeDescription *ad = mof_f[0].nf_f->f_av_desc;
+       gdn_info gi = {0};
+       int i;
+
+       for ( i=0; i<mof_nf; i++ ) {
+               gi.gi_DNs = NULL;
+               gi.gi_numDNs = 0;
+               nestgroup_get_childDNs( op, on, &gi, &mof_f[i].nf_f->f_av_value );
+
+               while ( gi.gi_DNlist ) {
+                       DNpair *dp = gi.gi_DNlist;
+                       gi.gi_DNlist = NULL;
+                       for ( ; dp; dp=dp->dp_next ) {
+                               nestgroup_get_childDNs( op, on, &gi, &dp->dp_ndn );
+                       }
+               }
+
+               if ( gi.gi_numDNs ) {
+                       int j;
+                       Filter *f, *nf;
+                       TAvlnode *t;
+                       DNpair *dp;
+
+                       f = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+                       f->f_next = NULL;
+                       t = ldap_tavl_end( gi.gi_DNs, TAVL_DIR_RIGHT );
+                       do {
+                               dp = t->avl_data;
+                               nf = f;
+                               nf->f_ava = op->o_tmpcalloc( 1, sizeof( AttributeAssertion ), op->o_tmpmemctx );
+                               nf->f_choice = LDAP_FILTER_EQUALITY;
+                               nf->f_av_desc = ad;
+                               nf->f_av_value = dp->dp_ndn;
+                               f = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+                               f->f_next = nf;
+                               t = ldap_tavl_next( t, TAVL_DIR_LEFT );
+                               op->o_tmpfree( dp, op->o_tmpmemctx );
+                       } while ( t );
+                       ldap_tavl_free( gi.gi_DNs, NULL );
+                       f->f_choice = LDAP_FILTER_EQUALITY;
+                       f->f_ava = mof_f[i].nf_f->f_ava;
+                       mof_f[i].nf_new = f;
+               }
+       }
+}
+
+static void
+nestgroup_memberOfVals( Operation *op, slap_overinst *on, Attribute *a )
+{
+       nestgroup_info_t *ngi = on->on_bi.bi_private;
+       Operation o = *op;
+       slap_callback *sc;
+       gdn_info *gi;
+       Filter mf;
+       AttributeAssertion mava;
+       int i;
+
+       o.o_managedsait = SLAP_CONTROL_CRITICAL;
+       sc = op->o_tmpcalloc( 1, sizeof(slap_callback) + sizeof(gdn_info), op->o_tmpmemctx);
+       gi = (gdn_info *)(sc+1);
+       gi->gi_ngi = ngi;
+       o.o_callback = sc;
+       sc->sc_response = nestgroup_gotDNresp;
+       o.ors_attrs = slap_anlist_no_attrs;
+
+       mf.f_choice = LDAP_FILTER_EQUALITY;
+       mf.f_ava = &mava;
+       mf.f_av_desc = ngi->ngi_member;
+       mf.f_next = NULL;
+
+       o.ors_filter = &mf;
+       o.ors_scope = LDAP_SCOPE_SUBTREE;
+       o.ors_deref = LDAP_DEREF_NEVER;
+       o.ors_limit = NULL;
+       o.ors_tlimit = SLAP_NO_LIMIT;
+       o.ors_slimit = SLAP_NO_LIMIT;
+       o.o_bd->bd_info = (BackendInfo *)on->on_info;
+       gi->gi_saveDN = 1;
+
+       for ( i=0; i<a->a_numvals; i++ ) {
+               nestgroup_get_parentDNs( &o, &a->a_nvals[i] );
+
+               while ( gi->gi_DNlist ) {
+                       DNpair *dp = gi->gi_DNlist;
+                       gi->gi_DNlist = NULL;
+                       for ( ; dp; dp=dp->dp_next ) {
+                               nestgroup_get_parentDNs( &o, &dp->dp_ndn );
+                       }
+               }
+       }
+       if ( gi->gi_DNs ) {
+               TAvlnode *p = ldap_tavl_end( gi->gi_DNs, TAVL_DIR_LEFT );
+               int flags = SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH|SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH;
+               do {
+                       DNpair *dp = p->avl_data;
+                       int rc = attr_valfind( a, flags, &dp->dp_ndn, NULL, NULL );
+                       if ( rc )
+                               attr_valadd( a, &dp->dp_dn, &dp->dp_ndn, 1 );
+                       op->o_tmpfree( dp->dp_dn.bv_val, op->o_tmpmemctx );
+                       op->o_tmpfree( dp->dp_ndn.bv_val, op->o_tmpmemctx );
+                       op->o_tmpfree( dp, op->o_tmpmemctx );
+                       p = ldap_tavl_next( p, TAVL_DIR_RIGHT );
+               } while ( p );
+               ldap_tavl_free( gi->gi_DNs, NULL );
+       }
+       o.o_bd->bd_info = (BackendInfo *)on->on_info;
+       op->o_tmpfree( sc, op->o_tmpmemctx );
+}
+
+typedef struct nestgroup_cbinfo {
+       slap_overinst *nc_on;
+       int nc_needed;
+} nestgroup_cbinfo;
+
+static int
+nestgroup_searchresp( Operation *op, SlapReply *rs )
+{
+       if (rs->sr_type == REP_SEARCH ) {
+               nestgroup_cbinfo *nc = op->o_callback->sc_private;
+               slap_overinst *on = nc->nc_on;
+               nestgroup_info_t *ngi = on->on_bi.bi_private;
+               Attribute *a;
+
+               if ( nc->nc_needed & NG_MBR_VALUES ) {
+                       a = attr_find( rs->sr_entry->e_attrs, ngi->ngi_member );
+                       if ( a ) {
+                               gdn_info gi = {0};
+                               int i, j;
+                               if ( !( rs->sr_flags & REP_ENTRY_MODIFIABLE )) {
+                                       Entry *e = entry_dup( rs->sr_entry );
+                                       rs_replace_entry( op, rs, on, e );
+                                       rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED;
+                                       a = attr_find( e->e_attrs, ngi->ngi_member );
+                               }
+                               gi.gi_merge = a;
+
+                               for ( i=0; i<a->a_numvals; i++ ) {
+                                       for ( j=0; !BER_BVISEMPTY( &ngi->ngi_ngroupBase[j] ); j++ ) {
+                                               if ( dnIsSuffix( &a->a_nvals[i], &ngi->ngi_ngroupBase[j] )) {
+                                                       nestgroup_get_childDNs( op, on, &gi, &a->a_nvals[i] );
+
+                                                       while ( gi.gi_DNlist ) {
+                                                               DNpair *dp = gi.gi_DNlist;
+                                                               gi.gi_DNlist = NULL;
+                                                               for ( ; dp; dp=dp->dp_next ) {
+                                                                       nestgroup_get_childDNs( op, on, &gi, &dp->dp_ndn );
+                                                               }
+                                                       }
+                                                       break;
+                                               }
+                                       }
+                               }
+                               if ( gi.gi_numDNs ) {
+                                       TAvlnode *p = ldap_tavl_end( gi.gi_DNs, TAVL_DIR_LEFT );
+                                       do {
+                                               DNpair *dp = p->avl_data;
+                                               op->o_tmpfree( dp->dp_ndn.bv_val, op->o_tmpmemctx );
+                                               op->o_tmpfree( dp, op->o_tmpmemctx );
+                                               p = ldap_tavl_next( p, TAVL_DIR_RIGHT );
+                                       } while ( p );
+                                       ldap_tavl_free( gi.gi_DNs, NULL );
+                               }
+                       }
+               }
+
+               if ( nc->nc_needed & NG_MOF_VALUES ) {
+                       a = attr_find( rs->sr_entry->e_attrs, ngi->ngi_memberOf );
+                       if ( a ) {
+                               if ( !( rs->sr_flags & REP_ENTRY_MODIFIABLE )) {
+                                       Entry *e = entry_dup( rs->sr_entry );
+                                       rs_replace_entry( op, rs, on, e );
+                                       rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED;
+                                       a = attr_find( e->e_attrs, ngi->ngi_memberOf );
+                               }
+                               nestgroup_memberOfVals( op, on, a );
+                       }
+               }
+               if (( nc->nc_needed & NG_NEGATED ) &&
+                       test_filter( op, rs->sr_entry, op->ors_filter ) != LDAP_COMPARE_TRUE )
+                       return 0;
+       }
+       return SLAP_CB_CONTINUE;
+}
+
+static int
+nestgroup_op_search( Operation *op, SlapReply *rs )
+{
+       slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+       nestgroup_info_t *ngi = on->on_bi.bi_private;
+       int mbr_nf = 0, mof_nf = 0, negated = 0;
+       nestgroup_filterinst_t *mbr_f = NULL, *mof_f = NULL;
+
+       if ( get_manageDSAit( op ))
+               return SLAP_CB_CONTINUE;
+
+       /* groupBase must be explicitly configured */
+       if ( !ngi->ngi_ngroupBase )
+               return SLAP_CB_CONTINUE;
+
+       /* handle attrs in filter */
+       if ( ngi->ngi_flags & NG_MBR_FILTER ) {
+               nestgroup_filter_instances( op, ngi->ngi_member, op->ors_filter, 0, &mbr_nf, &mbr_f, &negated );
+               if ( mbr_nf ) {
+                       /* find member=(parent groups) */
+                       nestgroup_memberFilter( op, mbr_nf, mbr_f );
+               }
+       }
+       if ( ngi->ngi_flags & NG_MOF_FILTER ) {
+               nestgroup_filter_instances( op, ngi->ngi_memberOf, op->ors_filter, 0, &mof_nf, &mof_f, &negated );
+               if ( mof_nf ) {
+                       /* find memberOf=(child groups) */
+                       nestgroup_memberOfFilter( op, mof_nf, mof_f );
+               }
+       }
+       if ( mbr_nf ) {
+               int i;
+               for ( i=0; i<mbr_nf; i++ ) {
+                       if ( mbr_f[i].nf_new ) {
+                               mbr_f[i].nf_f->f_choice = LDAP_FILTER_OR;
+                               mbr_f[i].nf_f->f_list = mbr_f[i].nf_new;
+                       }
+               }
+               op->o_tmpfree( mbr_f, op->o_tmpmemctx );
+       }
+       if ( mof_nf ) {
+               int i;
+               for ( i=0; i<mof_nf; i++ ) {
+                       if ( mof_f[i].nf_new ) {
+                               mof_f[i].nf_f->f_choice = LDAP_FILTER_OR;
+                               mof_f[i].nf_f->f_list = mof_f[i].nf_new;
+                       }
+               }
+               op->o_tmpfree( mof_f, op->o_tmpmemctx );
+       }
+
+       if ( ngi->ngi_flags & ( NG_MBR_VALUES|NG_MOF_VALUES )) {
+               /* check for attrs in attrlist */
+               int attrflags = slap_attr_flags( op->ors_attrs );
+               int needed = 0;
+               if (( ngi->ngi_flags & NG_MBR_VALUES ) &&
+                       nestgroup_check_needed( op, attrflags, ngi->ngi_member )) {
+                       /* collect all members from child groups */
+                       needed |= NG_MBR_VALUES;
+               }
+               if (( ngi->ngi_flags & NG_MOF_VALUES ) &&
+                       nestgroup_check_needed( op, attrflags, ngi->ngi_memberOf )) {
+                       /* collect DNs of all parent groups */
+                       needed |= NG_MOF_VALUES;
+               }
+               if ( needed ) {
+                       nestgroup_cbinfo *nc;
+                       slap_callback *sc = op->o_tmpcalloc( 1, sizeof(slap_callback)+sizeof(nestgroup_cbinfo), op->o_tmpmemctx );
+                       nc = (nestgroup_cbinfo *)(sc+1);
+                       sc->sc_private = nc;
+                       nc->nc_needed = needed;
+                       nc->nc_on = on;
+                       sc->sc_response = nestgroup_searchresp;
+                       sc->sc_next = op->o_callback;
+                       op->o_callback = sc;
+                       if ( negated ) nc->nc_needed |= NG_NEGATED;
+               }
+       }
+       return SLAP_CB_CONTINUE;
+}
+
+static int
+nestgroup_db_init(
+       BackendDB *be,
+       ConfigReply *cr)
+{
+       slap_overinst *on = (slap_overinst *)be->bd_info;
+       nestgroup_info_t *ngi;
+       int rc;
+       const char *text = NULL;
+
+       ngi = (nestgroup_info_t *)ch_calloc( 1, sizeof( *ngi ));
+       on->on_bi.bi_private = ngi;
+
+       if ( !ad_memberOf ) {
+               rc = slap_str2ad( SLAPD_MEMBEROF_ATTR, &ad_memberOf, &text );
+               if ( rc != LDAP_SUCCESS ) {
+                       Debug( LDAP_DEBUG_ANY, "nestgroup_db_init: "
+                                       "unable to find attribute=\"%s\": %s (%d)\n",
+                                       SLAPD_MEMBEROF_ATTR, text, rc );
+                       return rc;
+               }
+       }
+
+       if ( !ad_member ) {
+               rc = slap_str2ad( SLAPD_GROUP_ATTR, &ad_member, &text );
+               if ( rc != LDAP_SUCCESS ) {
+                       Debug( LDAP_DEBUG_ANY, "nestgroup_db_init: "
+                                       "unable to find attribute=\"%s\": %s (%d)\n",
+                                       SLAPD_GROUP_ATTR, text, rc );
+                       return rc;
+               }
+       }
+
+       return 0;
+}
+
+static int
+nestgroup_db_open(
+       BackendDB *be,
+       ConfigReply *cr)
+{
+       slap_overinst *on = (slap_overinst *)be->bd_info;
+       nestgroup_info_t *ngi = on->on_bi.bi_private;
+
+       if ( !ngi->ngi_member )
+               ngi->ngi_member = ad_member;
+
+       if ( !ngi->ngi_memberOf )
+               ngi->ngi_memberOf = ad_memberOf;
+
+       return 0;
+}
+
+static int
+nestgroup_db_destroy(
+       BackendDB *be,
+       ConfigReply *cr
+)
+{
+       slap_overinst *on = (slap_overinst *) be->bd_info;
+       nestgroup_info_t *ngi = on->on_bi.bi_private;
+
+       ber_bvarray_free( ngi->ngi_groupBase );
+       ber_bvarray_free( ngi->ngi_ngroupBase );
+       ch_free( ngi );
+
+       return 0;
+}
+
+static slap_overinst nestgroup;
+
+/* This overlay is set up for dynamic loading via moduleload. For static
+ * configuration, you'll need to arrange for the slap_overinst to be
+ * initialized and registered by some other function inside slapd.
+ */
+
+int nestgroup_initialize() {
+       int code;
+
+       code = register_at(
+       "( 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 dSAOperation "                   /* added; questioned */
+       "NO-USER-MODIFICATION "                 /* added */
+       "X-ORIGIN 'iPlanet Delegated Administrator' )",
+       &ad_memberOf, 0 );
+       if ( code && code != SLAP_SCHERR_ATTR_DUP ) {
+               Debug( LDAP_DEBUG_ANY,
+                       "nestgroup_initialize: register_at (memberOf) failed\n" );
+               return code;
+       }
+
+       nestgroup.on_bi.bi_type = "nestgroup";
+       nestgroup.on_bi.bi_db_init = nestgroup_db_init;
+       nestgroup.on_bi.bi_db_open = nestgroup_db_open;
+       nestgroup.on_bi.bi_db_destroy = nestgroup_db_destroy;
+
+       nestgroup.on_bi.bi_op_search = nestgroup_op_search;
+/*     nestgroup.on_bi.bi_op_compare = nestgroup_op_compare; */
+
+       nestgroup.on_bi.bi_cf_ocs = ngroupocs;
+       code = config_register_schema( ngroupcfg, ngroupocs );
+       if ( code ) return code;
+
+       return overlay_register( &nestgroup );
+}
+
+#if SLAPD_OVER_NESTGROUP == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+       return nestgroup_initialize();
+}
+#endif
+
+#endif /* defined(SLAPD_OVER_NESTGROUP) */
diff --git a/tests/data/nestgroup.out.1 b/tests/data/nestgroup.out.1
new file mode 100644 (file)
index 0000000..93716ac
--- /dev/null
@@ -0,0 +1,389 @@
+# Search the entire database...
+dn: cn=A-M,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: A-M
+member: cn=Baby Herman,ou=People,dc=example,dc=com
+member: cn=Bugs Bunny,ou=People,dc=example,dc=com
+member: cn=Daffy Duck,ou=People,dc=example,dc=com
+member: cn=Elmer Fudd,ou=People,dc=example,dc=com
+member: cn=Foghorn Leghorn,ou=People,dc=example,dc=com
+member: cn=Jessica Rabbit,ou=People,dc=example,dc=com
+
+dn: cn=Baby Herman,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Baby Herman
+sn: Herman
+
+dn: cn=Bugs Bunny,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Bugs Bunny
+sn: Bunny
+
+dn: cn=Daffy Duck,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Daffy Duck
+sn: Duck
+
+dn: cn=Desert Foes,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Desert Foes
+member: cn=Road Runner,ou=People,dc=example,dc=com
+member: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+
+dn: cn=Elmer Fudd,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Elmer Fudd
+sn: Fudd
+
+dn: cn=Endless Loop,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Endless Loop
+member: cn=Road Runner,ou=People,dc=example,dc=com
+member: cn=Loop\2C Endless,ou=Groups,dc=example,dc=com
+
+dn: dc=example,dc=com
+objectClass: organization
+objectClass: dcObject
+o: Example, Inc.
+dc: example
+
+dn: cn=Foghorn Leghorn,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Foghorn Leghorn
+sn: Leghorn
+
+dn: ou=Groups,dc=example,dc=com
+objectClass: organizationalUnit
+ou: Groups
+
+dn: cn=Humans,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Humans
+member: cn=Elmer Fudd,ou=People,dc=example,dc=com
+member: cn=Yosemite Sam,ou=People,dc=example,dc=com
+
+dn: cn=Jessica Rabbit,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Jessica Rabbit
+sn: Rabbit
+
+dn: cn=Leporidae,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Leporidae
+member: cn=Bugs Bunny,ou=People,dc=example,dc=com
+member: cn=Rabbits,ou=Groups,dc=example,dc=com
+
+dn: cn=Looney Tunes,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Looney Tunes
+member: cn=Porky Pig,ou=People,dc=example,dc=com
+member: cn=Daffy Duck,ou=People,dc=example,dc=com
+member: cn=Elmer Fudd,ou=People,dc=example,dc=com
+member: cn=Bugs Bunny,ou=People,dc=example,dc=com
+member: cn=Tweety Bird,ou=People,dc=example,dc=com
+
+dn: cn=Loop\2C Endless,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Loop, Endless
+member: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+member: cn=Endless Loop,ou=Groups,dc=example,dc=com
+
+dn: cn=Mixer1,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Mixer1
+member: cn=Leporidae,ou=Groups,dc=example,dc=com
+member: cn=Desert Foes,ou=Groups,dc=example,dc=com
+member: cn=Foghorn Leghorn,ou=People,dc=example,dc=com
+
+dn: cn=Mixer2,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Mixer2
+member: cn=Humans,ou=Groups,dc=example,dc=com
+member: cn=Baby Herman,ou=People,dc=example,dc=com
+
+dn: cn=Mixer3,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Mixer3
+member: cn=Desert Foes,ou=Groups,dc=example,dc=com
+member: cn=Porky Pig,ou=People,dc=example,dc=com
+
+dn: cn=Mixer4,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Mixer4
+member: cn=Mixer1,ou=Groups,dc=example,dc=com
+member: cn=Mixer2,ou=Groups,dc=example,dc=com
+member: cn=Foghorn Leghorn,ou=People,dc=example,dc=com
+
+dn: cn=Mixer5,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Mixer5
+member: cn=Mixer2,ou=Groups,dc=example,dc=com
+member: cn=Mixer3,ou=Groups,dc=example,dc=com
+member: cn=A-M,ou=Groups,dc=example,dc=com
+
+dn: cn=N-Z,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: N-Z
+member: cn=Porky Pig,ou=People,dc=example,dc=com
+member: cn=Road Runner,ou=People,dc=example,dc=com
+member: cn=Roger Rabbit,ou=People,dc=example,dc=com
+member: cn=Tweety Bird,ou=People,dc=example,dc=com
+member: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+member: cn=Yosemite Sam,ou=People,dc=example,dc=com
+
+dn: ou=People,dc=example,dc=com
+objectClass: organizationalUnit
+ou: People
+
+dn: cn=Porky Pig,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Porky Pig
+sn: Pig
+
+dn: cn=Rabbits,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Rabbits
+member: cn=Roger Rabbit,ou=People,dc=example,dc=com
+member: cn=Jessica Rabbit,ou=People,dc=example,dc=com
+
+dn: cn=Road Runner,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Road Runner
+sn: Runner
+
+dn: cn=Roger Rabbit,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Roger Rabbit
+sn: Rabbit
+
+dn: cn=Tweety Bird,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Tweety Bird
+sn: Bird
+
+dn: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Wile E. Coyote
+sn: Coyote
+
+dn: cn=Yosemite Sam,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Yosemite Sam
+sn: Sam
+
+# Search for member=cn=Bugs Bunny...
+dn: cn=A-M,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: A-M
+member: cn=Baby Herman,ou=People,dc=example,dc=com
+member: cn=Bugs Bunny,ou=People,dc=example,dc=com
+member: cn=Daffy Duck,ou=People,dc=example,dc=com
+member: cn=Elmer Fudd,ou=People,dc=example,dc=com
+member: cn=Foghorn Leghorn,ou=People,dc=example,dc=com
+member: cn=Jessica Rabbit,ou=People,dc=example,dc=com
+
+dn: cn=Leporidae,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Leporidae
+member: cn=Bugs Bunny,ou=People,dc=example,dc=com
+member: cn=Rabbits,ou=Groups,dc=example,dc=com
+
+dn: cn=Looney Tunes,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Looney Tunes
+member: cn=Porky Pig,ou=People,dc=example,dc=com
+member: cn=Daffy Duck,ou=People,dc=example,dc=com
+member: cn=Elmer Fudd,ou=People,dc=example,dc=com
+member: cn=Bugs Bunny,ou=People,dc=example,dc=com
+member: cn=Tweety Bird,ou=People,dc=example,dc=com
+
+# Re-search for nested member=cn=Bugs Bunny...
+dn: cn=A-M,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: A-M
+member: cn=Baby Herman,ou=People,dc=example,dc=com
+member: cn=Bugs Bunny,ou=People,dc=example,dc=com
+member: cn=Daffy Duck,ou=People,dc=example,dc=com
+member: cn=Elmer Fudd,ou=People,dc=example,dc=com
+member: cn=Foghorn Leghorn,ou=People,dc=example,dc=com
+member: cn=Jessica Rabbit,ou=People,dc=example,dc=com
+
+dn: cn=Leporidae,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Leporidae
+member: cn=Bugs Bunny,ou=People,dc=example,dc=com
+member: cn=Rabbits,ou=Groups,dc=example,dc=com
+
+dn: cn=Looney Tunes,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Looney Tunes
+member: cn=Porky Pig,ou=People,dc=example,dc=com
+member: cn=Daffy Duck,ou=People,dc=example,dc=com
+member: cn=Elmer Fudd,ou=People,dc=example,dc=com
+member: cn=Bugs Bunny,ou=People,dc=example,dc=com
+member: cn=Tweety Bird,ou=People,dc=example,dc=com
+
+dn: cn=Mixer1,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Mixer1
+member: cn=Leporidae,ou=Groups,dc=example,dc=com
+member: cn=Desert Foes,ou=Groups,dc=example,dc=com
+member: cn=Foghorn Leghorn,ou=People,dc=example,dc=com
+
+dn: cn=Mixer4,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Mixer4
+member: cn=Mixer1,ou=Groups,dc=example,dc=com
+member: cn=Mixer2,ou=Groups,dc=example,dc=com
+member: cn=Foghorn Leghorn,ou=People,dc=example,dc=com
+
+dn: cn=Mixer5,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Mixer5
+member: cn=Mixer2,ou=Groups,dc=example,dc=com
+member: cn=Mixer3,ou=Groups,dc=example,dc=com
+member: cn=A-M,ou=Groups,dc=example,dc=com
+
+# Search the expanded groups...
+dn: cn=A-M,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: A-M
+member: cn=Baby Herman,ou=People,dc=example,dc=com
+member: cn=Bugs Bunny,ou=People,dc=example,dc=com
+member: cn=Daffy Duck,ou=People,dc=example,dc=com
+member: cn=Elmer Fudd,ou=People,dc=example,dc=com
+member: cn=Foghorn Leghorn,ou=People,dc=example,dc=com
+member: cn=Jessica Rabbit,ou=People,dc=example,dc=com
+
+dn: cn=Desert Foes,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Desert Foes
+member: cn=Road Runner,ou=People,dc=example,dc=com
+member: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+
+dn: cn=Endless Loop,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Endless Loop
+member: cn=Road Runner,ou=People,dc=example,dc=com
+member: cn=Loop\2C Endless,ou=Groups,dc=example,dc=com
+member: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+member: cn=Endless Loop,ou=Groups,dc=example,dc=com
+
+dn: ou=Groups,dc=example,dc=com
+objectClass: organizationalUnit
+ou: Groups
+
+dn: cn=Humans,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Humans
+member: cn=Elmer Fudd,ou=People,dc=example,dc=com
+member: cn=Yosemite Sam,ou=People,dc=example,dc=com
+
+dn: cn=Leporidae,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Leporidae
+member: cn=Bugs Bunny,ou=People,dc=example,dc=com
+member: cn=Rabbits,ou=Groups,dc=example,dc=com
+member: cn=Roger Rabbit,ou=People,dc=example,dc=com
+member: cn=Jessica Rabbit,ou=People,dc=example,dc=com
+
+dn: cn=Looney Tunes,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Looney Tunes
+member: cn=Porky Pig,ou=People,dc=example,dc=com
+member: cn=Daffy Duck,ou=People,dc=example,dc=com
+member: cn=Elmer Fudd,ou=People,dc=example,dc=com
+member: cn=Bugs Bunny,ou=People,dc=example,dc=com
+member: cn=Tweety Bird,ou=People,dc=example,dc=com
+
+dn: cn=Loop\2C Endless,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Loop, Endless
+member: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+member: cn=Endless Loop,ou=Groups,dc=example,dc=com
+member: cn=Road Runner,ou=People,dc=example,dc=com
+member: cn=Loop\2C Endless,ou=Groups,dc=example,dc=com
+
+dn: cn=Mixer1,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Mixer1
+member: cn=Leporidae,ou=Groups,dc=example,dc=com
+member: cn=Desert Foes,ou=Groups,dc=example,dc=com
+member: cn=Foghorn Leghorn,ou=People,dc=example,dc=com
+member: cn=Bugs Bunny,ou=People,dc=example,dc=com
+member: cn=Rabbits,ou=Groups,dc=example,dc=com
+member: cn=Roger Rabbit,ou=People,dc=example,dc=com
+member: cn=Jessica Rabbit,ou=People,dc=example,dc=com
+member: cn=Road Runner,ou=People,dc=example,dc=com
+member: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+
+dn: cn=Mixer2,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Mixer2
+member: cn=Humans,ou=Groups,dc=example,dc=com
+member: cn=Baby Herman,ou=People,dc=example,dc=com
+member: cn=Elmer Fudd,ou=People,dc=example,dc=com
+member: cn=Yosemite Sam,ou=People,dc=example,dc=com
+
+dn: cn=Mixer3,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Mixer3
+member: cn=Desert Foes,ou=Groups,dc=example,dc=com
+member: cn=Porky Pig,ou=People,dc=example,dc=com
+member: cn=Road Runner,ou=People,dc=example,dc=com
+member: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+
+dn: cn=Mixer4,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Mixer4
+member: cn=Mixer1,ou=Groups,dc=example,dc=com
+member: cn=Mixer2,ou=Groups,dc=example,dc=com
+member: cn=Foghorn Leghorn,ou=People,dc=example,dc=com
+member: cn=Leporidae,ou=Groups,dc=example,dc=com
+member: cn=Desert Foes,ou=Groups,dc=example,dc=com
+member: cn=Road Runner,ou=People,dc=example,dc=com
+member: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+member: cn=Bugs Bunny,ou=People,dc=example,dc=com
+member: cn=Rabbits,ou=Groups,dc=example,dc=com
+member: cn=Roger Rabbit,ou=People,dc=example,dc=com
+member: cn=Jessica Rabbit,ou=People,dc=example,dc=com
+member: cn=Humans,ou=Groups,dc=example,dc=com
+member: cn=Baby Herman,ou=People,dc=example,dc=com
+member: cn=Elmer Fudd,ou=People,dc=example,dc=com
+member: cn=Yosemite Sam,ou=People,dc=example,dc=com
+
+dn: cn=Mixer5,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Mixer5
+member: cn=Mixer2,ou=Groups,dc=example,dc=com
+member: cn=Mixer3,ou=Groups,dc=example,dc=com
+member: cn=A-M,ou=Groups,dc=example,dc=com
+member: cn=Humans,ou=Groups,dc=example,dc=com
+member: cn=Baby Herman,ou=People,dc=example,dc=com
+member: cn=Elmer Fudd,ou=People,dc=example,dc=com
+member: cn=Yosemite Sam,ou=People,dc=example,dc=com
+member: cn=Desert Foes,ou=Groups,dc=example,dc=com
+member: cn=Porky Pig,ou=People,dc=example,dc=com
+member: cn=Road Runner,ou=People,dc=example,dc=com
+member: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+member: cn=Bugs Bunny,ou=People,dc=example,dc=com
+member: cn=Daffy Duck,ou=People,dc=example,dc=com
+member: cn=Foghorn Leghorn,ou=People,dc=example,dc=com
+member: cn=Jessica Rabbit,ou=People,dc=example,dc=com
+
+dn: cn=N-Z,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: N-Z
+member: cn=Porky Pig,ou=People,dc=example,dc=com
+member: cn=Road Runner,ou=People,dc=example,dc=com
+member: cn=Roger Rabbit,ou=People,dc=example,dc=com
+member: cn=Tweety Bird,ou=People,dc=example,dc=com
+member: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+member: cn=Yosemite Sam,ou=People,dc=example,dc=com
+
+dn: cn=Rabbits,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Rabbits
+member: cn=Roger Rabbit,ou=People,dc=example,dc=com
+member: cn=Jessica Rabbit,ou=People,dc=example,dc=com
+
diff --git a/tests/data/nestgroup.out.2 b/tests/data/nestgroup.out.2
new file mode 100644 (file)
index 0000000..3269c81
--- /dev/null
@@ -0,0 +1,606 @@
+# Re-search the entire database after adding memberof configuration...
+dn: cn=A-M,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: A-M
+member: cn=Baby Herman,ou=People,dc=example,dc=com
+member: cn=Bugs Bunny,ou=People,dc=example,dc=com
+member: cn=Daffy Duck,ou=People,dc=example,dc=com
+member: cn=Elmer Fudd,ou=People,dc=example,dc=com
+member: cn=Foghorn Leghorn,ou=People,dc=example,dc=com
+member: cn=Jessica Rabbit,ou=People,dc=example,dc=com
+memberOf: cn=Mixer5,ou=Groups,dc=example,dc=com
+
+dn: cn=Baby Herman,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Baby Herman
+sn: Herman
+memberOf: cn=A-M,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer2,ou=Groups,dc=example,dc=com
+
+dn: cn=Bugs Bunny,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Bugs Bunny
+sn: Bunny
+memberOf: cn=Leporidae,ou=Groups,dc=example,dc=com
+memberOf: cn=A-M,ou=Groups,dc=example,dc=com
+memberOf: cn=Looney Tunes,ou=Groups,dc=example,dc=com
+
+dn: cn=Daffy Duck,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Daffy Duck
+sn: Duck
+memberOf: cn=A-M,ou=Groups,dc=example,dc=com
+memberOf: cn=Looney Tunes,ou=Groups,dc=example,dc=com
+
+dn: cn=Desert Foes,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Desert Foes
+member: cn=Road Runner,ou=People,dc=example,dc=com
+member: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+memberOf: cn=Mixer1,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer3,ou=Groups,dc=example,dc=com
+
+dn: cn=Elmer Fudd,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Elmer Fudd
+sn: Fudd
+memberOf: cn=A-M,ou=Groups,dc=example,dc=com
+memberOf: cn=Humans,ou=Groups,dc=example,dc=com
+memberOf: cn=Looney Tunes,ou=Groups,dc=example,dc=com
+
+dn: cn=Endless Loop,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Endless Loop
+member: cn=Road Runner,ou=People,dc=example,dc=com
+member: cn=Loop\2C Endless,ou=Groups,dc=example,dc=com
+member: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+member: cn=Endless Loop,ou=Groups,dc=example,dc=com
+memberOf: cn=Loop\2C Endless,ou=Groups,dc=example,dc=com
+
+dn: dc=example,dc=com
+objectClass: organization
+objectClass: dcObject
+o: Example, Inc.
+dc: example
+
+dn: cn=Foghorn Leghorn,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Foghorn Leghorn
+sn: Leghorn
+memberOf: cn=A-M,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer1,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer4,ou=Groups,dc=example,dc=com
+
+dn: ou=Groups,dc=example,dc=com
+objectClass: organizationalUnit
+ou: Groups
+
+dn: cn=Humans,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Humans
+member: cn=Elmer Fudd,ou=People,dc=example,dc=com
+member: cn=Yosemite Sam,ou=People,dc=example,dc=com
+memberOf: cn=Mixer2,ou=Groups,dc=example,dc=com
+
+dn: cn=Jessica Rabbit,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Jessica Rabbit
+sn: Rabbit
+memberOf: cn=Rabbits,ou=Groups,dc=example,dc=com
+memberOf: cn=A-M,ou=Groups,dc=example,dc=com
+
+dn: cn=Leporidae,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Leporidae
+member: cn=Bugs Bunny,ou=People,dc=example,dc=com
+member: cn=Rabbits,ou=Groups,dc=example,dc=com
+member: cn=Roger Rabbit,ou=People,dc=example,dc=com
+member: cn=Jessica Rabbit,ou=People,dc=example,dc=com
+memberOf: cn=Mixer1,ou=Groups,dc=example,dc=com
+
+dn: cn=Looney Tunes,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Looney Tunes
+member: cn=Porky Pig,ou=People,dc=example,dc=com
+member: cn=Daffy Duck,ou=People,dc=example,dc=com
+member: cn=Elmer Fudd,ou=People,dc=example,dc=com
+member: cn=Bugs Bunny,ou=People,dc=example,dc=com
+member: cn=Tweety Bird,ou=People,dc=example,dc=com
+
+dn: cn=Loop\2C Endless,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Loop, Endless
+memberOf: cn=Endless Loop,ou=Groups,dc=example,dc=com
+member: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+member: cn=Endless Loop,ou=Groups,dc=example,dc=com
+member: cn=Road Runner,ou=People,dc=example,dc=com
+member: cn=Loop\2C Endless,ou=Groups,dc=example,dc=com
+
+dn: cn=Mixer1,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Mixer1
+member: cn=Leporidae,ou=Groups,dc=example,dc=com
+member: cn=Desert Foes,ou=Groups,dc=example,dc=com
+member: cn=Foghorn Leghorn,ou=People,dc=example,dc=com
+member: cn=Bugs Bunny,ou=People,dc=example,dc=com
+member: cn=Rabbits,ou=Groups,dc=example,dc=com
+member: cn=Roger Rabbit,ou=People,dc=example,dc=com
+member: cn=Jessica Rabbit,ou=People,dc=example,dc=com
+member: cn=Road Runner,ou=People,dc=example,dc=com
+member: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+memberOf: cn=Mixer4,ou=Groups,dc=example,dc=com
+
+dn: cn=Mixer2,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Mixer2
+member: cn=Humans,ou=Groups,dc=example,dc=com
+member: cn=Baby Herman,ou=People,dc=example,dc=com
+member: cn=Elmer Fudd,ou=People,dc=example,dc=com
+member: cn=Yosemite Sam,ou=People,dc=example,dc=com
+memberOf: cn=Mixer4,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer5,ou=Groups,dc=example,dc=com
+
+dn: cn=Mixer3,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Mixer3
+member: cn=Desert Foes,ou=Groups,dc=example,dc=com
+member: cn=Porky Pig,ou=People,dc=example,dc=com
+member: cn=Road Runner,ou=People,dc=example,dc=com
+member: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+memberOf: cn=Mixer5,ou=Groups,dc=example,dc=com
+
+dn: cn=Mixer4,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Mixer4
+member: cn=Mixer1,ou=Groups,dc=example,dc=com
+member: cn=Mixer2,ou=Groups,dc=example,dc=com
+member: cn=Foghorn Leghorn,ou=People,dc=example,dc=com
+member: cn=Leporidae,ou=Groups,dc=example,dc=com
+member: cn=Desert Foes,ou=Groups,dc=example,dc=com
+member: cn=Road Runner,ou=People,dc=example,dc=com
+member: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+member: cn=Bugs Bunny,ou=People,dc=example,dc=com
+member: cn=Rabbits,ou=Groups,dc=example,dc=com
+member: cn=Roger Rabbit,ou=People,dc=example,dc=com
+member: cn=Jessica Rabbit,ou=People,dc=example,dc=com
+member: cn=Humans,ou=Groups,dc=example,dc=com
+member: cn=Baby Herman,ou=People,dc=example,dc=com
+member: cn=Elmer Fudd,ou=People,dc=example,dc=com
+member: cn=Yosemite Sam,ou=People,dc=example,dc=com
+
+dn: cn=Mixer5,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Mixer5
+member: cn=Mixer2,ou=Groups,dc=example,dc=com
+member: cn=Mixer3,ou=Groups,dc=example,dc=com
+member: cn=A-M,ou=Groups,dc=example,dc=com
+member: cn=Humans,ou=Groups,dc=example,dc=com
+member: cn=Baby Herman,ou=People,dc=example,dc=com
+member: cn=Elmer Fudd,ou=People,dc=example,dc=com
+member: cn=Yosemite Sam,ou=People,dc=example,dc=com
+member: cn=Desert Foes,ou=Groups,dc=example,dc=com
+member: cn=Porky Pig,ou=People,dc=example,dc=com
+member: cn=Road Runner,ou=People,dc=example,dc=com
+member: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+member: cn=Bugs Bunny,ou=People,dc=example,dc=com
+member: cn=Daffy Duck,ou=People,dc=example,dc=com
+member: cn=Foghorn Leghorn,ou=People,dc=example,dc=com
+member: cn=Jessica Rabbit,ou=People,dc=example,dc=com
+
+dn: cn=N-Z,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: N-Z
+member: cn=Porky Pig,ou=People,dc=example,dc=com
+member: cn=Road Runner,ou=People,dc=example,dc=com
+member: cn=Roger Rabbit,ou=People,dc=example,dc=com
+member: cn=Tweety Bird,ou=People,dc=example,dc=com
+member: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+member: cn=Yosemite Sam,ou=People,dc=example,dc=com
+
+dn: ou=People,dc=example,dc=com
+objectClass: organizationalUnit
+ou: People
+
+dn: cn=Porky Pig,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Porky Pig
+sn: Pig
+memberOf: cn=N-Z,ou=Groups,dc=example,dc=com
+memberOf: cn=Looney Tunes,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer3,ou=Groups,dc=example,dc=com
+
+dn: cn=Rabbits,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Rabbits
+member: cn=Roger Rabbit,ou=People,dc=example,dc=com
+member: cn=Jessica Rabbit,ou=People,dc=example,dc=com
+memberOf: cn=Leporidae,ou=Groups,dc=example,dc=com
+
+dn: cn=Road Runner,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Road Runner
+sn: Runner
+memberOf: cn=N-Z,ou=Groups,dc=example,dc=com
+memberOf: cn=Desert Foes,ou=Groups,dc=example,dc=com
+memberOf: cn=Endless Loop,ou=Groups,dc=example,dc=com
+
+dn: cn=Roger Rabbit,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Roger Rabbit
+sn: Rabbit
+memberOf: cn=Rabbits,ou=Groups,dc=example,dc=com
+memberOf: cn=N-Z,ou=Groups,dc=example,dc=com
+
+dn: cn=Tweety Bird,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Tweety Bird
+sn: Bird
+memberOf: cn=N-Z,ou=Groups,dc=example,dc=com
+memberOf: cn=Looney Tunes,ou=Groups,dc=example,dc=com
+
+dn: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Wile E. Coyote
+sn: Coyote
+memberOf: cn=N-Z,ou=Groups,dc=example,dc=com
+memberOf: cn=Desert Foes,ou=Groups,dc=example,dc=com
+memberOf: cn=Loop\2C Endless,ou=Groups,dc=example,dc=com
+
+dn: cn=Yosemite Sam,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Yosemite Sam
+sn: Sam
+memberOf: cn=N-Z,ou=Groups,dc=example,dc=com
+memberOf: cn=Humans,ou=Groups,dc=example,dc=com
+
+# Search for memberOf=cn=Mixer3...
+dn: cn=Desert Foes,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Desert Foes
+member: cn=Road Runner,ou=People,dc=example,dc=com
+member: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+memberOf: cn=Mixer1,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer3,ou=Groups,dc=example,dc=com
+
+dn: cn=Porky Pig,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Porky Pig
+sn: Pig
+memberOf: cn=N-Z,ou=Groups,dc=example,dc=com
+memberOf: cn=Looney Tunes,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer3,ou=Groups,dc=example,dc=com
+
+# Re-search for memberOf=cn=Mixer3 with filter nesting...
+dn: cn=Desert Foes,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Desert Foes
+member: cn=Road Runner,ou=People,dc=example,dc=com
+member: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+memberOf: cn=Mixer1,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer3,ou=Groups,dc=example,dc=com
+
+dn: cn=Porky Pig,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Porky Pig
+sn: Pig
+memberOf: cn=N-Z,ou=Groups,dc=example,dc=com
+memberOf: cn=Looney Tunes,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer3,ou=Groups,dc=example,dc=com
+
+dn: cn=Road Runner,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Road Runner
+sn: Runner
+memberOf: cn=N-Z,ou=Groups,dc=example,dc=com
+memberOf: cn=Desert Foes,ou=Groups,dc=example,dc=com
+memberOf: cn=Endless Loop,ou=Groups,dc=example,dc=com
+
+dn: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Wile E. Coyote
+sn: Coyote
+memberOf: cn=N-Z,ou=Groups,dc=example,dc=com
+memberOf: cn=Desert Foes,ou=Groups,dc=example,dc=com
+memberOf: cn=Loop\2C Endless,ou=Groups,dc=example,dc=com
+
+# Re-search for memberOf=cn=Mixer3 with filter and value nesting...
+dn: cn=Desert Foes,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Desert Foes
+member: cn=Road Runner,ou=People,dc=example,dc=com
+member: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+memberOf: cn=Mixer1,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer3,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer4,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer5,ou=Groups,dc=example,dc=com
+
+dn: cn=Porky Pig,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Porky Pig
+sn: Pig
+memberOf: cn=N-Z,ou=Groups,dc=example,dc=com
+memberOf: cn=Looney Tunes,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer3,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer5,ou=Groups,dc=example,dc=com
+
+dn: cn=Road Runner,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Road Runner
+sn: Runner
+memberOf: cn=N-Z,ou=Groups,dc=example,dc=com
+memberOf: cn=Desert Foes,ou=Groups,dc=example,dc=com
+memberOf: cn=Endless Loop,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer1,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer3,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer4,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer5,ou=Groups,dc=example,dc=com
+memberOf: cn=Loop\2C Endless,ou=Groups,dc=example,dc=com
+
+dn: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Wile E. Coyote
+sn: Coyote
+memberOf: cn=N-Z,ou=Groups,dc=example,dc=com
+memberOf: cn=Desert Foes,ou=Groups,dc=example,dc=com
+memberOf: cn=Loop\2C Endless,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer1,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer3,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer4,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer5,ou=Groups,dc=example,dc=com
+memberOf: cn=Endless Loop,ou=Groups,dc=example,dc=com
+
+# Re-search the entire database with memberof value nesting...
+dn: cn=A-M,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: A-M
+member: cn=Baby Herman,ou=People,dc=example,dc=com
+member: cn=Bugs Bunny,ou=People,dc=example,dc=com
+member: cn=Daffy Duck,ou=People,dc=example,dc=com
+member: cn=Elmer Fudd,ou=People,dc=example,dc=com
+member: cn=Foghorn Leghorn,ou=People,dc=example,dc=com
+member: cn=Jessica Rabbit,ou=People,dc=example,dc=com
+memberOf: cn=Mixer5,ou=Groups,dc=example,dc=com
+
+dn: cn=Baby Herman,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Baby Herman
+sn: Herman
+memberOf: cn=A-M,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer2,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer4,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer5,ou=Groups,dc=example,dc=com
+
+dn: cn=Bugs Bunny,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Bugs Bunny
+sn: Bunny
+memberOf: cn=Leporidae,ou=Groups,dc=example,dc=com
+memberOf: cn=A-M,ou=Groups,dc=example,dc=com
+memberOf: cn=Looney Tunes,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer1,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer4,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer5,ou=Groups,dc=example,dc=com
+
+dn: cn=Daffy Duck,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Daffy Duck
+sn: Duck
+memberOf: cn=A-M,ou=Groups,dc=example,dc=com
+memberOf: cn=Looney Tunes,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer5,ou=Groups,dc=example,dc=com
+
+dn: cn=Desert Foes,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Desert Foes
+member: cn=Road Runner,ou=People,dc=example,dc=com
+member: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+memberOf: cn=Mixer1,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer3,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer4,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer5,ou=Groups,dc=example,dc=com
+
+dn: cn=Elmer Fudd,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Elmer Fudd
+sn: Fudd
+memberOf: cn=A-M,ou=Groups,dc=example,dc=com
+memberOf: cn=Humans,ou=Groups,dc=example,dc=com
+memberOf: cn=Looney Tunes,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer2,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer4,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer5,ou=Groups,dc=example,dc=com
+
+dn: cn=Endless Loop,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Endless Loop
+member: cn=Road Runner,ou=People,dc=example,dc=com
+member: cn=Loop\2C Endless,ou=Groups,dc=example,dc=com
+memberOf: cn=Loop\2C Endless,ou=Groups,dc=example,dc=com
+memberOf: cn=Endless Loop,ou=Groups,dc=example,dc=com
+
+dn: dc=example,dc=com
+objectClass: organization
+objectClass: dcObject
+o: Example, Inc.
+dc: example
+
+dn: cn=Foghorn Leghorn,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Foghorn Leghorn
+sn: Leghorn
+memberOf: cn=A-M,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer1,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer4,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer5,ou=Groups,dc=example,dc=com
+
+dn: ou=Groups,dc=example,dc=com
+objectClass: organizationalUnit
+ou: Groups
+
+dn: cn=Humans,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Humans
+member: cn=Elmer Fudd,ou=People,dc=example,dc=com
+member: cn=Yosemite Sam,ou=People,dc=example,dc=com
+memberOf: cn=Mixer2,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer4,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer5,ou=Groups,dc=example,dc=com
+
+dn: cn=Jessica Rabbit,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Jessica Rabbit
+sn: Rabbit
+memberOf: cn=Rabbits,ou=Groups,dc=example,dc=com
+memberOf: cn=A-M,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer1,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer4,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer5,ou=Groups,dc=example,dc=com
+memberOf: cn=Leporidae,ou=Groups,dc=example,dc=com
+
+dn: cn=Leporidae,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Leporidae
+member: cn=Bugs Bunny,ou=People,dc=example,dc=com
+member: cn=Rabbits,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer1,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer4,ou=Groups,dc=example,dc=com
+
+dn: cn=Looney Tunes,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Looney Tunes
+member: cn=Porky Pig,ou=People,dc=example,dc=com
+member: cn=Daffy Duck,ou=People,dc=example,dc=com
+member: cn=Elmer Fudd,ou=People,dc=example,dc=com
+member: cn=Bugs Bunny,ou=People,dc=example,dc=com
+member: cn=Tweety Bird,ou=People,dc=example,dc=com
+
+dn: cn=Loop\2C Endless,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Loop, Endless
+memberOf: cn=Endless Loop,ou=Groups,dc=example,dc=com
+memberOf: cn=Loop\2C Endless,ou=Groups,dc=example,dc=com
+member: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+member: cn=Endless Loop,ou=Groups,dc=example,dc=com
+
+dn: cn=Mixer1,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Mixer1
+member: cn=Leporidae,ou=Groups,dc=example,dc=com
+member: cn=Desert Foes,ou=Groups,dc=example,dc=com
+member: cn=Foghorn Leghorn,ou=People,dc=example,dc=com
+memberOf: cn=Mixer4,ou=Groups,dc=example,dc=com
+
+dn: cn=Mixer2,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Mixer2
+member: cn=Humans,ou=Groups,dc=example,dc=com
+member: cn=Baby Herman,ou=People,dc=example,dc=com
+memberOf: cn=Mixer4,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer5,ou=Groups,dc=example,dc=com
+
+dn: cn=Mixer3,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Mixer3
+member: cn=Desert Foes,ou=Groups,dc=example,dc=com
+member: cn=Porky Pig,ou=People,dc=example,dc=com
+memberOf: cn=Mixer5,ou=Groups,dc=example,dc=com
+
+dn: cn=Mixer4,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Mixer4
+member: cn=Mixer1,ou=Groups,dc=example,dc=com
+member: cn=Mixer2,ou=Groups,dc=example,dc=com
+member: cn=Foghorn Leghorn,ou=People,dc=example,dc=com
+
+dn: cn=Mixer5,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Mixer5
+member: cn=Mixer2,ou=Groups,dc=example,dc=com
+member: cn=Mixer3,ou=Groups,dc=example,dc=com
+member: cn=A-M,ou=Groups,dc=example,dc=com
+
+dn: cn=N-Z,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: N-Z
+member: cn=Porky Pig,ou=People,dc=example,dc=com
+member: cn=Road Runner,ou=People,dc=example,dc=com
+member: cn=Roger Rabbit,ou=People,dc=example,dc=com
+member: cn=Tweety Bird,ou=People,dc=example,dc=com
+member: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+member: cn=Yosemite Sam,ou=People,dc=example,dc=com
+
+dn: ou=People,dc=example,dc=com
+objectClass: organizationalUnit
+ou: People
+
+dn: cn=Porky Pig,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Porky Pig
+sn: Pig
+memberOf: cn=N-Z,ou=Groups,dc=example,dc=com
+memberOf: cn=Looney Tunes,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer3,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer5,ou=Groups,dc=example,dc=com
+
+dn: cn=Rabbits,ou=Groups,dc=example,dc=com
+objectClass: groupOfNames
+cn: Rabbits
+member: cn=Roger Rabbit,ou=People,dc=example,dc=com
+member: cn=Jessica Rabbit,ou=People,dc=example,dc=com
+memberOf: cn=Leporidae,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer1,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer4,ou=Groups,dc=example,dc=com
+
+dn: cn=Road Runner,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Road Runner
+sn: Runner
+memberOf: cn=N-Z,ou=Groups,dc=example,dc=com
+memberOf: cn=Desert Foes,ou=Groups,dc=example,dc=com
+memberOf: cn=Endless Loop,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer1,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer3,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer4,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer5,ou=Groups,dc=example,dc=com
+memberOf: cn=Loop\2C Endless,ou=Groups,dc=example,dc=com
+
+dn: cn=Roger Rabbit,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Roger Rabbit
+sn: Rabbit
+memberOf: cn=Rabbits,ou=Groups,dc=example,dc=com
+memberOf: cn=N-Z,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer1,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer4,ou=Groups,dc=example,dc=com
+memberOf: cn=Leporidae,ou=Groups,dc=example,dc=com
+
+dn: cn=Tweety Bird,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Tweety Bird
+sn: Bird
+memberOf: cn=N-Z,ou=Groups,dc=example,dc=com
+memberOf: cn=Looney Tunes,ou=Groups,dc=example,dc=com
+
+dn: cn=Wile E. Coyote,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Wile E. Coyote
+sn: Coyote
+memberOf: cn=N-Z,ou=Groups,dc=example,dc=com
+memberOf: cn=Desert Foes,ou=Groups,dc=example,dc=com
+memberOf: cn=Loop\2C Endless,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer1,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer3,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer4,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer5,ou=Groups,dc=example,dc=com
+memberOf: cn=Endless Loop,ou=Groups,dc=example,dc=com
+
+dn: cn=Yosemite Sam,ou=People,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Yosemite Sam
+sn: Sam
+memberOf: cn=N-Z,ou=Groups,dc=example,dc=com
+memberOf: cn=Humans,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer2,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer4,ou=Groups,dc=example,dc=com
+memberOf: cn=Mixer5,ou=Groups,dc=example,dc=com
+
index 837abfd082a5abf8bc3b374bdaf395ee955e8383..47420c844076c01530ba81a8289f9de046d9b312 100755 (executable)
@@ -42,6 +42,7 @@ sed -e "s/@BACKEND@/${BACKEND}/"                      \
        -e "s/^#${AC_dynlist}#//"                       \
        -e "s/^#${AC_homedir}#//"                       \
        -e "s/^#${AC_memberof}#//"                      \
+       -e "s/^#${AC_nestgroup}#//"                     \
        -e "s/^#${AC_pcache}#//"                        \
        -e "s/^#${AC_ppolicy}#//"                       \
        -e "s/^#${AC_refint}#//"                        \
index 7caab6d2f50b24d4ade0bcf366c91568aadc62e0..df9e1edb419b12ba952dbba512c864221503a90c 100755 (executable)
@@ -51,6 +51,7 @@ DEREF=${AC_deref-derefno}
 DYNLIST=${AC_dynlist-dynlistno}
 HOMEDIR=${AC_homedir-homedirno}
 MEMBEROF=${AC_memberof-memberofno}
+NESTGROUP=${AC_nestgroup-nestgroupno}
 OTP=${AC_otp-otpno}
 PROXYCACHE=${AC_pcache-pcacheno}
 PPOLICY=${AC_ppolicy-ppolicyno}
@@ -460,6 +461,8 @@ DDSOUT=$DATADIR/dds.out
 DEREFOUT=$DATADIR/deref.out
 MEMBEROFOUT=$DATADIR/memberof.out
 MEMBEROFREFINTOUT=$DATADIR/memberof-refint.out
+NESTGROUPOUT1=$DATADIR/nestgroup.out.1
+NESTGROUPOUT2=$DATADIR/nestgroup.out.2
 SHTOOL="$TOPSRCDIR/build/shtool"
 
 . $ABS_SRCDIR/scripts/functions.sh
diff --git a/tests/scripts/test089-nestgroup b/tests/scripts/test089-nestgroup
new file mode 100755 (executable)
index 0000000..0c8d7dd
--- /dev/null
@@ -0,0 +1,662 @@
+#! /bin/sh
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2024 The OpenLDAP Foundation.
+## 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>.
+
+echo "running defines.sh"
+. $SRCDIR/scripts/defines.sh
+
+if test $NESTGROUP = nestgroupno; then
+       echo "Nestgroup overlay not available, test skipped"
+       exit 0
+fi
+if test $MEMBEROF = memberofno; then
+       echo "Memberof overlay not available, memberof testing disabled"
+fi
+
+mkdir -p $TESTDIR $DBDIR1 $TESTDIR/confdir
+
+$SLAPPASSWD -g -n >$CONFIGPWF
+echo "rootpw `$SLAPPASSWD -T $CONFIGPWF`" >$TESTDIR/configpw.conf
+
+echo "Starting slapd on TCP/IP port $PORT1..."
+. $CONFFILTER $BACKEND < $NAKEDCONF > $CONF1
+$SLAPD -f $CONF1 -F $TESTDIR/confdir -h $URI1 -d $LVL > $LOG1 2>&1 &
+PID=$!
+if test $WAIT != 0 ; then
+    echo PID $PID
+    read foo
+fi
+KILLPIDS="$PID"
+
+sleep 1
+for i in 0 1 2 3 4 5; do
+       $LDAPSEARCH -s base -b "$MONITOR" -H $URI1 \
+               'objectclass=*' > /dev/null 2>&1
+       RC=$?
+       if test $RC = 0 ; then
+               break
+       fi
+       echo "Waiting 5 seconds for slapd to start..."
+       sleep 5
+done
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+cat /dev/null > $TESTOUT
+
+if [ "$NESTGROUP" = nestgroupmod ]; then
+       echo "Inserting nestgroup overlay on provider..."
+       $LDAPADD -D cn=config -H $URI1 -y $CONFIGPWF <<EOF > $TESTOUT 2>&1
+dn: cn=module,cn=config
+objectClass: olcModuleList
+cn: module
+olcModulePath: ../servers/slapd/overlays
+olcModuleLoad: nestgroup.la
+EOF
+       RC=$?
+       if test $RC != 0 ; then
+               echo "ldapadd failed for moduleLoad ($RC)!"
+               test $KILLSERVERS != no && kill -HUP $KILLPIDS
+               exit $RC
+       fi
+fi
+
+indexInclude="" mainInclude="" nullExclude=""
+test $INDEXDB = indexdb        || indexInclude="# "
+test $MAINDB  = maindb || mainInclude="# "
+case $BACKEND in
+null) nullExclude="# " ;;
+esac
+
+echo "Running ldapadd to build slapd config database..."
+$LDAPADD -H $URI1 -D 'cn=config' -w `cat $CONFIGPWF` \
+       >> $TESTOUT 2>&1 <<EOF
+dn: olcDatabase={1}$BACKEND,cn=config
+objectClass: olcDatabaseConfig
+${nullExclude}objectClass: olc${BACKEND}Config
+olcDatabase: {1}$BACKEND
+olcSuffix: $BASEDN
+olcRootDN: cn=Manager,$BASEDN
+olcRootPW:: c2VjcmV0
+olcMonitoring: TRUE
+${nullExclude}olcDbDirectory: $TESTDIR/db.1.a/
+${indexInclude}olcDbIndex: objectClass eq
+${indexInclude}olcDbIndex: cn pres,eq,sub
+${indexInclude}olcDbIndex: uid pres,eq,sub
+${indexInclude}olcDbIndex: sn pres,eq,sub
+${indexInclude}olcDbIndex: member,memberOf eq
+${mainInclude}olcDbMode: 384"
+
+dn: olcOverlay={0}nestgroup,olcDatabase={1}$BACKEND,cn=config
+objectClass: olcOverlayConfig
+objectClass: olcNestGroupConfig
+olcOverlay: {0}nestgroup
+olcNestgroupMember: member
+olcNestgroupMemberOf: memberOf
+olcNestgroupBase: ou=Groups,$BASEDN
+
+EOF
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapadd failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Running ldapadd to build slapd database..."
+$LDAPADD -H $URI1 \
+       -D "cn=Manager,$BASEDN" -w secret \
+       >> $TESTOUT 2>&1 << EOF
+dn: $BASEDN
+objectClass: organization
+objectClass: dcObject
+o: Example, Inc.
+dc: example
+
+dn: ou=People,$BASEDN
+objectClass: organizationalUnit
+ou: People
+
+dn: ou=Groups,$BASEDN
+objectClass: organizationalUnit
+ou: Groups
+
+dn: cn=Roger Rabbit,ou=People,$BASEDN
+objectClass: inetOrgPerson
+cn: Roger Rabbit
+sn: Rabbit
+
+dn: cn=Baby Herman,ou=People,$BASEDN
+objectClass: inetOrgPerson
+cn: Baby Herman
+sn: Herman
+
+dn: cn=Jessica Rabbit,ou=People,$BASEDN
+objectClass: inetOrgPerson
+cn: Jessica Rabbit
+sn: Rabbit
+
+dn: cn=Bugs Bunny,ou=People,$BASEDN
+objectClass: inetOrgPerson
+cn: Bugs Bunny
+sn: Bunny
+
+dn: cn=Daffy Duck,ou=People,$BASEDN
+objectClass: inetOrgPerson
+cn: Daffy Duck
+sn: Duck
+
+dn: cn=Elmer Fudd,ou=People,$BASEDN
+objectClass: inetOrgPerson
+cn: Elmer Fudd
+sn: Fudd
+
+dn: cn=Yosemite Sam,ou=People,$BASEDN
+objectClass: inetOrgPerson
+cn: Yosemite Sam
+sn: Sam
+
+dn: cn=Foghorn Leghorn,ou=People,$BASEDN
+objectClass: inetOrgPerson
+cn: Foghorn Leghorn
+sn: Leghorn
+
+dn: cn=Wile E. Coyote,ou=People,$BASEDN
+objectClass: inetOrgPerson
+cn: Wile E. Coyote
+sn: Coyote
+
+dn: cn=Road Runner,ou=People,$BASEDN
+objectClass: inetOrgPerson
+cn: Road Runner
+sn: Runner
+
+dn: cn=Tweety Bird,ou=People,$BASEDN
+objectClass: inetOrgPerson
+cn: Tweety Bird
+sn: Bird
+
+dn: cn=Porky Pig,ou=People,$BASEDN
+objectClass: inetOrgPerson
+cn: Porky Pig
+sn: Pig
+
+dn: cn=Rabbits,ou=Groups,$BASEDN
+objectClass: groupOfNames
+cn: Rabbits
+member: cn=Roger Rabbit,ou=People,$BASEDN
+member: cn=Jessica Rabbit,ou=People,$BASEDN
+
+dn: cn=Leporidae,ou=Groups,$BASEDN
+objectClass: groupOfNames
+cn: Leporidae
+member: cn=Bugs Bunny,ou=People,$BASEDN
+member: cn=Rabbits,ou=Groups,$BASEDN
+
+dn: cn=A-M,ou=Groups,$BASEDN
+objectClass: groupOfNames
+cn: A-M
+member: cn=Baby Herman,ou=People,$BASEDN
+member: cn=Bugs Bunny,ou=People,$BASEDN
+member: cn=Daffy Duck,ou=People,$BASEDN
+member: cn=Elmer Fudd,ou=People,$BASEDN
+member: cn=Foghorn Leghorn,ou=People,$BASEDN
+member: cn=Jessica Rabbit,ou=People,$BASEDN
+
+dn: cn=N-Z,ou=Groups,$BASEDN
+objectClass: groupOfNames
+cn: N-Z
+member: cn=Porky Pig,ou=People,$BASEDN
+member: cn=Road Runner,ou=People,$BASEDN
+member: cn=Roger Rabbit,ou=People,$BASEDN
+member: cn=Tweety Bird,ou=People,$BASEDN
+member: cn=Wile E. Coyote,ou=People,$BASEDN
+member: cn=Yosemite Sam,ou=People,$BASEDN
+
+dn: cn=Humans,ou=Groups,$BASEDN
+objectClass: groupOfNames
+cn: Humans
+member: cn=Elmer Fudd,ou=People,$BASEDN
+member: cn=Yosemite Sam,ou=People,$BASEDN
+
+dn: cn=Looney Tunes,ou=Groups,$BASEDN
+objectClass: groupOfNames
+cn: Looney Tunes
+member: cn=Porky Pig,ou=People,$BASEDN
+member: cn=Daffy Duck,ou=People,$BASEDN
+member: cn=Elmer Fudd,ou=People,$BASEDN
+member: cn=Bugs Bunny,ou=People,$BASEDN
+member: cn=Tweety Bird,ou=People,$BASEDN
+
+dn: cn=Desert Foes,ou=Groups,$BASEDN
+objectClass: groupOfNames
+cn: Desert Foes
+member: cn=Road Runner,ou=People,$BASEDN
+member: cn=Wile E. Coyote,ou=People,$BASEDN
+
+dn: cn=Mixer1,ou=Groups,$BASEDN
+objectClass: groupOfNames
+cn: Mixer1
+member: cn=Leporidae,ou=Groups,$BASEDN
+member: cn=Desert Foes,ou=Groups,$BASEDN
+member: cn=Foghorn Leghorn,ou=People,$BASEDN
+
+dn: cn=Mixer2,ou=Groups,$BASEDN
+objectClass: groupOfNames
+cn: Mixer2
+member: cn=Humans,ou=Groups,$BASEDN
+member: cn=Baby Herman,ou=People,$BASEDN
+
+dn: cn=Mixer3,ou=Groups,$BASEDN
+objectClass: groupOfNames
+cn: Mixer3
+member: cn=Desert Foes,ou=Groups,$BASEDN
+member: cn=Porky Pig,ou=People,$BASEDN
+
+dn: cn=Mixer4,ou=Groups,$BASEDN
+objectClass: groupOfNames
+cn: Mixer4
+member: cn=Mixer1,ou=Groups,$BASEDN
+member: cn=Mixer2,ou=Groups,$BASEDN
+member: cn=Foghorn Leghorn,ou=People,$BASEDN
+
+dn: cn=Mixer5,ou=Groups,$BASEDN
+objectClass: groupOfNames
+cn: Mixer5
+member: cn=Mixer2,ou=Groups,$BASEDN
+member: cn=Mixer3,ou=Groups,$BASEDN
+member: cn=A-M,ou=Groups,$BASEDN
+
+dn: cn=Endless Loop,ou=Groups,$BASEDN
+objectClass: groupOfNames
+cn: Endless Loop
+member: cn=Road Runner,ou=People,$BASEDN
+member: cn=Loop\, Endless,ou=Groups,$BASEDN
+
+dn: cn=Loop\, Endless,ou=Groups,$BASEDN
+objectClass: groupOfNames
+cn: Loop, Endless
+member: cn=Wile E. Coyote,ou=People,$BASEDN
+member: cn=Endless Loop,ou=Groups,$BASEDN
+
+EOF
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapadd failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Search the entire database..."
+echo "# Search the entire database..." > $SEARCHOUT
+$LDAPSEARCH -S "" -b "$BASEDN" -H $URI1 \
+       '(objectClass=*)' '*' >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Search for member=cn=Bugs Bunny..."
+echo "# Search for member=cn=Bugs Bunny..." >> $SEARCHOUT
+$LDAPSEARCH -S "" -b "$BASEDN" -H $URI1 \
+       "(member=cn=Bugs Bunny,ou=People,$BASEDN)" '*' memberof >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Running ldapmodify to enable nested member filter..."
+$LDAPMODIFY -H $URI1 -D 'cn=config' -w `cat $CONFIGPWF` \
+       >> $TESTOUT 2>&1 <<EOF
+dn: olcOverlay={0}nestgroup,olcDatabase={1}$BACKEND,cn=config
+changetype: modify
+replace: olcNestgroupFlags
+olcNestgroupFlags: member-filter
+
+EOF
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Re-search for nested member=cn=Bugs Bunny..."
+echo "# Re-search for nested member=cn=Bugs Bunny..." >> $SEARCHOUT
+$LDAPSEARCH -S "" -b "$BASEDN" -H $URI1 \
+       "(member=cn=Bugs Bunny,ou=People,$BASEDN)" '*' memberof >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Running ldapmodify to enable nested member values..."
+$LDAPMODIFY -H $URI1 -D 'cn=config' -w `cat $CONFIGPWF` \
+       >> $TESTOUT 2>&1 <<EOF
+dn: olcOverlay={0}nestgroup,olcDatabase={1}$BACKEND,cn=config
+changetype: modify
+replace: olcNestgroupFlags
+olcNestgroupFlags: member-values
+
+EOF
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Search the expanded groups..."
+echo "# Search the expanded groups..." >> $SEARCHOUT
+$LDAPSEARCH -S "" -b "ou=Groups,$BASEDN" -H $URI1 \
+       '(objectClass=*)' '*' memberof >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+LDIF=$NESTGROUPOUT1
+
+echo "Filtering ldapsearch results..."
+$LDIFFILTER < $SEARCHOUT > $SEARCHFLT
+echo "Filtering original ldif used to create database..."
+$LDIFFILTER < $LDIF > $LDIFFLT
+echo "Comparing filter output..."
+$CMP $SEARCHFLT $LDIFFLT > $CMPOUT
+
+if test $? != 0 ; then
+       echo "Comparison failed"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit 1
+fi
+
+echo ">>>>> Test succeeded (first half)"
+
+if [ "$MEMBEROF" = memberofno ]; then
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+
+       test $KILLSERVERS != no && wait
+
+       exit 0
+fi
+
+echo "Adding memberof overlay to database configuration..."
+
+if [ "$MEMBEROF" = memberofmod ]; then
+       echo "Inserting memberof module on provider..."
+       $LDAPADD -D cn=config -H $URI1 -y $CONFIGPWF <<EOF > $TESTOUT 2>&1
+dn: cn=module,cn=config
+objectClass: olcModuleList
+cn: module
+olcModulePath: ../servers/slapd/overlays
+olcModuleLoad: memberof.la
+EOF
+       RC=$?
+       if test $RC != 0 ; then
+               echo "ldapadd failed for moduleLoad ($RC)!"
+               test $KILLSERVERS != no && kill -HUP $KILLPIDS
+               exit $RC
+       fi
+fi
+
+$LDAPADD -H $URI1 -D 'cn=config' -w `cat $CONFIGPWF` \
+       >> $TESTOUT 2>&1 <<EOF
+dn: olcOverlay={1}memberof,olcDatabase={1}$BACKEND,cn=config
+objectClass: olcOverlayConfig
+objectClass: olcMemberofConfig
+olcOverlay: {1}memberof
+olcMemberOfGroupOC: groupOfNames
+olcMemberOfMemberAD: member
+olcMemberOfMemberOfAD: memberOf
+
+EOF
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapadd failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Recreating group entries to set memberof values..."
+$LDAPMODIFY -H $URI1 \
+       -D "cn=Manager,$BASEDN" -w secret \
+       >> $TESTOUT 2>&1 <<EOF
+dn: cn=Rabbits,ou=Groups,$BASEDN
+changetype: modify
+replace: member
+member: cn=Roger Rabbit,ou=People,$BASEDN
+member: cn=Jessica Rabbit,ou=People,$BASEDN
+
+dn: cn=Leporidae,ou=Groups,$BASEDN
+changetype: modify
+replace: member
+member: cn=Bugs Bunny,ou=People,$BASEDN
+member: cn=Rabbits,ou=Groups,$BASEDN
+
+dn: cn=A-M,ou=Groups,$BASEDN
+changetype: modify
+replace: member
+member: cn=Baby Herman,ou=People,$BASEDN
+member: cn=Bugs Bunny,ou=People,$BASEDN
+member: cn=Daffy Duck,ou=People,$BASEDN
+member: cn=Elmer Fudd,ou=People,$BASEDN
+member: cn=Foghorn Leghorn,ou=People,$BASEDN
+member: cn=Jessica Rabbit,ou=People,$BASEDN
+
+dn: cn=N-Z,ou=Groups,$BASEDN
+changetype: modify
+replace: member
+member: cn=Porky Pig,ou=People,$BASEDN
+member: cn=Road Runner,ou=People,$BASEDN
+member: cn=Roger Rabbit,ou=People,$BASEDN
+member: cn=Tweety Bird,ou=People,$BASEDN
+member: cn=Wile E. Coyote,ou=People,$BASEDN
+member: cn=Yosemite Sam,ou=People,$BASEDN
+
+dn: cn=Humans,ou=Groups,$BASEDN
+changetype: modify
+replace: member
+member: cn=Elmer Fudd,ou=People,$BASEDN
+member: cn=Yosemite Sam,ou=People,$BASEDN
+
+dn: cn=Looney Tunes,ou=Groups,$BASEDN
+changetype: modify
+replace: member
+member: cn=Porky Pig,ou=People,$BASEDN
+member: cn=Daffy Duck,ou=People,$BASEDN
+member: cn=Elmer Fudd,ou=People,$BASEDN
+member: cn=Bugs Bunny,ou=People,$BASEDN
+member: cn=Tweety Bird,ou=People,$BASEDN
+
+dn: cn=Desert Foes,ou=Groups,$BASEDN
+changetype: modify
+replace: member
+member: cn=Road Runner,ou=People,$BASEDN
+member: cn=Wile E. Coyote,ou=People,$BASEDN
+
+dn: cn=Mixer1,ou=Groups,$BASEDN
+changetype: modify
+replace: member
+member: cn=Leporidae,ou=Groups,$BASEDN
+member: cn=Desert Foes,ou=Groups,$BASEDN
+member: cn=Foghorn Leghorn,ou=People,$BASEDN
+
+dn: cn=Mixer2,ou=Groups,$BASEDN
+changetype: modify
+replace: member
+member: cn=Humans,ou=Groups,$BASEDN
+member: cn=Baby Herman,ou=People,$BASEDN
+
+dn: cn=Mixer3,ou=Groups,$BASEDN
+changetype: modify
+replace: member
+member: cn=Desert Foes,ou=Groups,$BASEDN
+member: cn=Porky Pig,ou=People,$BASEDN
+
+dn: cn=Mixer4,ou=Groups,$BASEDN
+changetype: modify
+replace: member
+member: cn=Mixer1,ou=Groups,$BASEDN
+member: cn=Mixer2,ou=Groups,$BASEDN
+member: cn=Foghorn Leghorn,ou=People,$BASEDN
+
+dn: cn=Mixer5,ou=Groups,$BASEDN
+changetype: modify
+replace: member
+member: cn=Mixer2,ou=Groups,$BASEDN
+member: cn=Mixer3,ou=Groups,$BASEDN
+member: cn=A-M,ou=Groups,$BASEDN
+
+dn: cn=Endless Loop,ou=Groups,$BASEDN
+changetype: modify
+replace: member
+member: cn=Road Runner,ou=People,$BASEDN
+member: cn=Loop\, Endless,ou=Groups,$BASEDN
+
+dn: cn=Loop\, Endless,ou=Groups,$BASEDN
+changetype: modify
+replace: member
+member: cn=Wile E. Coyote,ou=People,$BASEDN
+member: cn=Endless Loop,ou=Groups,$BASEDN
+
+EOF
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Moving previous results to $SEARCHOUT.0"
+mv $SEARCHOUT $SEARCHOUT.0
+
+echo "Re-search the entire database..."
+echo "# Re-search the entire database after adding memberof configuration..." >> $SEARCHOUT
+$LDAPSEARCH -S "" -b "$BASEDN" -H $URI1 \
+       '(objectClass=*)' '*' memberOf >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Search for memberOf=cn=Mixer3..."
+echo "# Search for memberOf=cn=Mixer3..." >> $SEARCHOUT
+$LDAPSEARCH -S "" -b "$BASEDN" -H $URI1 \
+       "(memberOf=cn=Mixer3,ou=Groups,$BASEDN)" '*' memberof >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Running ldapmodify to enable nested memberOf filter..."
+$LDAPMODIFY -H $URI1 -D 'cn=config' -w `cat $CONFIGPWF` \
+       >> $TESTOUT 2>&1 <<EOF
+dn: olcOverlay={0}nestgroup,olcDatabase={1}$BACKEND,cn=config
+changetype: modify
+replace: olcNestgroupFlags
+olcNestgroupFlags: memberof-filter
+
+EOF
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Re-search for memberOf=cn=Mixer3 with filter nesting..."
+echo "# Re-search for memberOf=cn=Mixer3 with filter nesting..." >> $SEARCHOUT
+$LDAPSEARCH -S "" -b "$BASEDN" -H $URI1 \
+       "(memberOf=cn=Mixer3,ou=Groups,$BASEDN)" '*' memberof >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Running ldapmodify to also enable nested memberOf values..."
+$LDAPMODIFY -H $URI1 -D 'cn=config' -w `cat $CONFIGPWF` \
+       >> $TESTOUT 2>&1 <<EOF
+dn: olcOverlay={0}nestgroup,olcDatabase={1}$BACKEND,cn=config
+changetype: modify
+add: olcNestgroupFlags
+olcNestgroupFlags: memberof-values
+
+EOF
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Re-search for memberOf=cn=Mixer3 with filter and value nesting..."
+echo "# Re-search for memberOf=cn=Mixer3 with filter and value nesting..." >> $SEARCHOUT
+$LDAPSEARCH -S "" -b "$BASEDN" -H $URI1 \
+       "(memberOf=cn=Mixer3,ou=Groups,$BASEDN)" '*' memberof >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Re-search the entire database with memberof value nesting..."
+echo "# Re-search the entire database with memberof value nesting..." >> $SEARCHOUT
+$LDAPSEARCH -S "" -b "$BASEDN" -H $URI1 \
+       '(objectClass=*)' '*' memberOf >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+test $KILLSERVERS != no && kill -HUP $KILLPIDS
+
+LDIF=$NESTGROUPOUT2
+
+echo "Filtering ldapsearch results..."
+$LDIFFILTER < $SEARCHOUT > $SEARCHFLT
+echo "Filtering original ldif used to create database..."
+$LDIFFILTER < $LDIF > $LDIFFLT
+echo "Comparing filter output..."
+$CMP $SEARCHFLT $LDIFFLT > $CMPOUT
+
+if test $? != 0 ; then
+       echo "Comparison failed"
+       exit 1
+fi
+
+echo ">>>>> Test succeeded"
+
+test $KILLSERVERS != no && wait
+
+exit 0