[Date Prev][Date Next] [Chronological] [Thread] [Top]

Re: dynamic groups



Howard Chu wrote: 
> I think the amount of comments is too few. It takes more than one read thru the 
> code to understand what an rdynlist_entry_t is used for; this should have been 
> noted in a comment. Likewise for rdynlist_filter_t.

In the attached Technology Preview version no. 2 I have fixed all of the
bugs You pointed out, and a few additional ones. I have also added the
much needed comments.

> > I have tested it, and it works without problems (at least on my setup).
> 
> Try it with a database with about 1 million users, with 900,000 or so members 
> of a dynamic group.

I see the problem, but even static groups with 900,000 member attributes are
slow in updating, so this slowdown is unfortunately unavoidable.

> While it's good that you got this code working, I still believe you've 
> completely missed the point of dynamic groups.

I agree with you that this is not a truly dynamic group. This overlay is more 
of a "automatically updated static group," so maybe the name of this overlay is not
representing what it does exactly. But without modifying much of 
the OpenLDAP codebase I don't see a better way to do it (but maybe I'm wrong, 
so feel free to correct me).
--
MichaÅ SzulczyÅski
    Praktykant
    Altkom Akademia S.A. http://www.altkom.pl
    Warszawa, ul. ChÅodna 51
    
    

SÄd Rejonowy dla m.st. Warszawy w Warszawie, XII WydziaÅ Gospodarczy Krajowego Rejestru SÄdowego,
KRS: 0000120139, NIP 118-00-08-391, KapitaÅ zakÅadowy: 1000 000 PLN.  Adres rejestrowy Firmy - ul. Stawki 2, 00-193 Warszawa.
Niniejsza wiadomoÅÄ zawiera informacje zastrzeÅone i stanowiÄce tajemnicÄ przedsiÄbiorstwa firmy Altkom Akademia S.A.
Ujawnianie tych informacji osobom trzecim lub nieuprawnione wykorzystanie ich do wÅasnych celÃw jest zabronione.
JeÅeli otrzymaliÅcie PaÅstwo niniejszÄ wiadomoÅÄ omyÅkowo, prosimy o niezwÅoczne skontaktowanie siÄ z nadawcÄ oraz usuniÄcie wszelkich kopii niniejszej wiadomoÅci.
This message contains proprietary information and trade secrets of Altkom Akademia S.A. company.
Unauthorized use or disclosure of this information to any third party is prohibited.
If you received this message by mistake, please contact the sender immediately and delete all copies of this message. 
/* rdynlist.c - really dynamic list overlay */
/* $OpenLDAP: $ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
 *
 * Copyright 2007 MichaÅ SzulczyÅski.
 * 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 MichaÅ SzulczyÅski
 * for Altkom Akademia S.A., for inclusion in OpenLDAP Software.
 */

#include "portable.h"

#ifdef SLAPD_OVER_RDYNLIST

#include <stdio.h>

#include <ac/string.h>

#include "slap.h"
#include "config.h"
#include "lutil.h"

/* Filter represents the memberURL of a group. */
typedef struct rdynlist_filter_t {
	struct berval			rdlf_dn;	/* The base DN in memberURL */
	struct berval			rdlf_ndn;
	struct berval			rdlf_filterstr;
	Filter				*rdlf_filter;
	int				rdlf_scope;
	struct rdynlist_filter_t	*rdlf_next;
} rdynlist_filter_t;

/* Description of group attributes. */
typedef struct rdynlist_def_t {
	ObjectClass		*rdld_oc;
	AttributeDescription	*rdld_member_url_ad;
	AttributeDescription	*rdld_member_ad;
	struct rdynlist_def_t	*rdld_next;
} rdynlist_def_t;

/* Represents the group entry. */
typedef struct rdynlist_entry_t {
	BerValue		rdle_dn;
	BerValue		rdle_ndn;
	rdynlist_filter_t	*rdle_filter; /* List of filters made from memberURLs */
	rdynlist_def_t		*rdle_def; /* Attribute definition */
	ldap_pvt_thread_mutex_t rdle_mutex;
	struct rdynlist_entry_t	*rdle_next;
} rdynlist_entry_t;

/* Holds pointers to attribute definitions and groups. */
typedef struct rdynlist_info_t {
	rdynlist_def_t		*rdli_def;	/* Group attributes definitions. */
	rdynlist_entry_t	*rdli_entry;	/* Group entries.  */
	ldap_pvt_thread_mutex_t rdli_mutex;
} rdynlist_info_t;

/* Search callback for adding groups initially. */
typedef struct rdynlist_sc_t {
	rdynlist_info_t		*rdls_info;	/* Group definitions and entries.  */
	rdynlist_def_t		*rdls_def;	/* Attributes definition of the group being added. */
} rdynlist_sc_t;

/* Used for adding members, found when searching, to a group. */
typedef struct rdynlist_ga_t {
	rdynlist_entry_t	*rdlg_group;	/* The group to which the members will be added. */
	Entry			*rdlg_entry;	/* Used in rdynlist_member_search_cb to modify 
						this entry with the search results. */
						
	Modifications		*rdlg_mod;	/* Used in rdynlist_member_search_modify_cb to hold the 
						search results which will be added to the group. */
						
	Modifications		*rdlg_mod_last; /* Used in rdynlist_member_search_modify_cb so we don't 
						have to search for the last mod added. */
} rdynlist_ga_t;


/* 
**  dn, ndn	- the DN of the member to add
**  rdle	- the group to which the member DN will be added
*/
static int
rdynlist_add_member_to_group( Operation *op, BerValue *dn, BerValue *ndn, rdynlist_entry_t *rdle )
{
        slap_overinst	*on = (slap_overinst *)op->o_bd->bd_info;
	Modifications	modlist;
	SlapReply	sreply = {REP_RESULT};
	BerValue	vals[ 2 ], nvals[ 2 ];
	slap_callback	cb = { NULL, slap_null_cb, NULL, NULL };
	Operation	o = *op;
       					
	Debug(LDAP_DEBUG_TRACE, "==> rdynlist_add_member_to_group adding <%s> to <%s>\n",
		dn->bv_val, rdle->rdle_dn.bv_val, 0);

	assert( dn != NULL );
	assert( ndn != NULL );
	
	vals[ 0 ] = *dn;
	BER_BVZERO( &vals[ 1 ] );
	nvals[ 0 ] = *ndn;
	BER_BVZERO( &nvals[ 1 ] );
       					
	modlist.sml_op = LDAP_MOD_ADD;
	modlist.sml_desc = rdle->rdle_def->rdld_member_ad;
	modlist.sml_type = rdle->rdle_def->rdld_member_ad->ad_cname;
	modlist.sml_values = vals;
	modlist.sml_nvalues = nvals;
	modlist.sml_flags = SLAP_MOD_INTERNAL;
	modlist.sml_next = NULL;
					
	o.o_tag = LDAP_REQ_MODIFY;
	o.o_callback = &cb;
	o.orm_modlist = &modlist;
	o.o_req_dn = rdle->rdle_dn;
	o.o_req_ndn = rdle->rdle_ndn;
	o.o_permissive_modify = 1;
	o.o_managedsait = SLAP_CONTROL_CRITICAL;
	o.o_relax = SLAP_CONTROL_CRITICAL;
	
	o.o_bd->bd_info = (BackendInfo *)on->on_info;
	(void)op->o_bd->be_modify( &o, &sreply );
	o.o_bd->bd_info = (BackendInfo *)on;

	return sreply.sr_err;
}

/*
** dn,ndn	- the DN to be deleted
** rdle		- the group from which the DN will be deleted
** If we pass a NULL dn and ndn, all members are deleted from the group. 
*/
static int
rdynlist_delete_member_from_group( Operation *op, BerValue *dn, BerValue *ndn, rdynlist_entry_t *rdle )
{
        slap_overinst	*on = (slap_overinst *)op->o_bd->bd_info;
	Modifications	modlist;
	SlapReply	sreply = {REP_RESULT};
	BerValue	vals[ 2 ], nvals[ 2 ];
	slap_callback	cb = { NULL, slap_null_cb, NULL, NULL };
	Operation	o = *op;

	if ( dn == NULL || ndn == NULL ) {
		Debug(LDAP_DEBUG_TRACE, "==> rdynlist_delete_member_from_group removing all members from <%s>\n",
			rdle->rdle_dn.bv_val, 0 ,0);
		
		modlist.sml_values = NULL;
		modlist.sml_nvalues = NULL;
       	} else {
		Debug(LDAP_DEBUG_TRACE, "==> rdynlist_delete_member_from_group removing <%s> from <%s>\n",
			dn->bv_val, rdle->rdle_dn.bv_val, 0);

		vals[ 0 ] = *dn;
		BER_BVZERO( &vals[ 1 ] );
		nvals[ 0 ] = *ndn;
		BER_BVZERO( &nvals[ 1 ] );
		
		modlist.sml_values = vals;
		modlist.sml_nvalues = nvals;
       	}
       					
       					
	modlist.sml_op = LDAP_MOD_DELETE;
	modlist.sml_desc = rdle->rdle_def->rdld_member_ad;
	modlist.sml_type = rdle->rdle_def->rdld_member_ad->ad_cname;
	modlist.sml_flags = SLAP_MOD_INTERNAL;
	modlist.sml_next = NULL;
					
	o.o_callback = &cb;
	o.o_tag = LDAP_REQ_MODIFY;
	o.orm_modlist = &modlist;
	o.o_req_dn = rdle->rdle_dn;
	o.o_req_ndn = rdle->rdle_ndn;
	o.o_relax = SLAP_CONTROL_CRITICAL;
	o.o_managedsait = SLAP_CONTROL_CRITICAL;
	o.o_permissive_modify = 1;

	o.o_bd->bd_info = (BackendInfo *)on->on_info;
	(void)op->o_bd->be_modify( &o, &sreply );
	o.o_bd->bd_info = (BackendInfo *)on;

	return sreply.sr_err;
}

/* 
** Callback used to add entries to a group, 
** which are going to be written in the database
** (used in bi_op_add)
** The group is passed in rdynlist_ga_t->rdlg_group
*/
static int
rdynlist_member_search_cb( Operation *op, SlapReply *rs )
{
	slap_overinst 		*on = (slap_overinst *)op->o_bd->bd_info;

	assert( op->o_tag == LDAP_REQ_SEARCH );

	if ( rs->sr_type == REP_SEARCH ) {
		rdynlist_ga_t		*rdlg = (rdynlist_ga_t *)op->o_callback->sc_private;
		rdynlist_entry_t	*rdle = rdlg->rdlg_group;
		Modification		mod;
		const char		*text = NULL;
		char			textbuf[1024];
		struct berval		vals[ 2 ], nvals[ 2 ];
		
		Debug(LDAP_DEBUG_TRACE, "==> rdynlist_member_search_cb <%s>\n",
			rs->sr_entry ? rs->sr_entry->e_name.bv_val : "UNKNOWN_DN", 0, 0);

		vals[ 0 ] = rs->sr_entry->e_name;
		BER_BVZERO( &vals[ 1 ] );
		nvals[ 0 ] = rs->sr_entry->e_nname;
		BER_BVZERO( &nvals[ 1 ] );
		
		mod.sm_op = LDAP_MOD_ADD;
		mod.sm_desc = rdle->rdle_def->rdld_member_ad;
		mod.sm_type = rdle->rdle_def->rdld_member_ad->ad_cname;
		mod.sm_values = vals;
		mod.sm_nvalues = nvals;

		modify_add_values( rdlg->rdlg_entry, &mod, /* permissive */ 1, &text, textbuf, sizeof( textbuf ) );
	}

	return 0;
}

/* 
** Callback used to add entries to a group, which is already in the database.
** (used in on_response)
** The group is passed in rdynlist_ga_t->rdlg_group
** NOTE: Very slow.
*/
static int
rdynlist_member_search_modify_cb( Operation *op, SlapReply *rs )
{
	slap_overinst 		*on = (slap_overinst *)op->o_bd->bd_info;

	assert( op->o_tag == LDAP_REQ_SEARCH );

	if ( rs->sr_type == REP_SEARCH ) {
		rdynlist_ga_t		*rdlg = (rdynlist_ga_t *)op->o_callback->sc_private;
		rdynlist_entry_t	*rdle = rdlg->rdlg_group;
		Operation		o = *op;
		Modifications		*modlist;
		SlapReply		sreply = {REP_RESULT};
		const char		*text = NULL;
		char			textbuf[1024];
		struct berval		vals[ 2 ], nvals[ 2 ];
		slap_callback		cb = { NULL, slap_null_cb, NULL, NULL };
		
		Debug(LDAP_DEBUG_TRACE, "==> rdynlist_member_search_modify_cb <%s>\n",
			rs->sr_entry ? rs->sr_entry->e_name.bv_val : "UNKNOWN_DN", 0, 0);

		vals[ 0 ] = rs->sr_entry->e_name;
		BER_BVZERO( &vals[ 1 ] );
		nvals[ 0 ] = rs->sr_entry->e_nname;
		BER_BVZERO( &nvals[ 1 ] );
		
		modlist = (Modifications *)ch_calloc( 1, sizeof( Modifications ) );
		
		modlist->sml_op = LDAP_MOD_ADD;
		modlist->sml_desc = rdle->rdle_def->rdld_member_ad;
		modlist->sml_type = rdle->rdle_def->rdld_member_ad->ad_cname;
		
		ber_bvarray_dup_x( &modlist->sml_values, vals, NULL );
		ber_bvarray_dup_x( &modlist->sml_nvalues, nvals, NULL );
		
		modlist->sml_flags = SLAP_MOD_INTERNAL;
		modlist->sml_next = NULL;
		
		if ( rdlg->rdlg_mod == NULL ) {
			rdlg->rdlg_mod = modlist;
			rdlg->rdlg_mod_last = modlist;
		} else {
			rdlg->rdlg_mod_last->sml_next = modlist;
			rdlg->rdlg_mod_last = modlist;
		}

	}

	return 0;
}


/*
** Adds all entries matching the passed filter to the specified group.
** If modify == 1, then we modify the group's entry in the database using be_modify.
** If modify == 0, then, we must supply a rw entry for the group, 
** 	because we only modify the entry, without calling be_modify.
** e	- the group entry, to which the members will be added
** rdle	- the group
** rdlf	- the filter
*/
static int
rdynlist_add_members_from_filter( Operation *op, Entry *e, rdynlist_entry_t *rdle, rdynlist_filter_t *rdlf, int modify)
{
	slap_overinst 		*on = (slap_overinst *)op->o_bd->bd_info;
	Operation		o = *op;
	SlapReply		rs = { REP_SEARCH };
	slap_callback		cb = { 0 };
	slap_callback		null_cb = { NULL, slap_null_cb, NULL, NULL };
	rdynlist_ga_t		rdlg;
			
	Debug(LDAP_DEBUG_TRACE, "==> rdynlist_add_members_from_filter <%s>\n",
		rdle->rdle_dn.bv_val, 0, 0);

	o.ors_attrsonly = 0;
	o.o_tag = LDAP_REQ_SEARCH;

	o.o_req_dn = rdlf->rdlf_dn;
	o.o_req_ndn = rdlf->rdlf_ndn;

	o.ors_filterstr = rdlf->rdlf_filterstr;
	o.ors_filter = rdlf->rdlf_filter;

	o.ors_scope = rdlf->rdlf_scope;
	o.ors_deref = LDAP_DEREF_NEVER;
	o.ors_limit = NULL;
	o.ors_tlimit = SLAP_NO_LIMIT;
	o.ors_slimit = SLAP_NO_LIMIT;
	o.ors_attrs =  slap_anlist_no_attrs;
		
	rdlg.rdlg_group = rdle;
	rdlg.rdlg_mod = NULL;
	rdlg.rdlg_mod_last = NULL;
	rdlg.rdlg_entry = e;
	cb.sc_private = &rdlg;
	
	if ( modify == 1 ) {
		cb.sc_response = rdynlist_member_search_modify_cb;
	} else {
		cb.sc_response = rdynlist_member_search_cb;
	}
	
	cb.sc_cleanup = NULL;
	cb.sc_next = NULL;
		
	o.o_callback = &cb;

	o.o_bd->bd_info = (BackendInfo *)on->on_info;
	op->o_bd->be_search( &o, &rs );
	o.o_bd->bd_info = (BackendInfo *)on;	

	if ( modify == 1 ) {
		o = *op;
		o.o_callback = &null_cb;
		o.o_tag = LDAP_REQ_MODIFY;
		o.orm_modlist = rdlg.rdlg_mod;
		o.o_req_dn = rdle->rdle_dn;
		o.o_req_ndn = rdle->rdle_ndn;
		o.o_relax = SLAP_CONTROL_CRITICAL;
		o.o_managedsait = SLAP_CONTROL_NONCRITICAL;
		o.o_permissive_modify = 1;

		o.o_bd->bd_info = (BackendInfo *)on->on_info;
		(void)op->o_bd->be_modify( &o, &rs );
		o.o_bd->bd_info = (BackendInfo *)on;	
	
		slap_mods_free(rdlg.rdlg_mod, 1);
	}
	
	return 0;
}

/* 
** Adds a group to the internal list from the passed entry.
** scan specifies whether to add all maching members to the group.
** modify specifies whether to modify the given group entry (when modify == 0),
**	or to modify the group entry in the database (when modify == 1 and e = NULL and ndn != NULL).
** rdli	- pointer to the groups and the attribute definitions
** rdld - the attribute definition of the added group
** e	- the entry representing the group, can be NULL if the ndn is specified, and modify == 1
** ndn	- the DN of the group, can be NULL if we give a non-NULL e
*/
static int
rdynlist_add_group( Operation *op, rdynlist_info_t *rdli, rdynlist_def_t *rdld, Entry *e, BerValue *ndn, int scan, int modify)
{
	rdynlist_entry_t	**rdlep = &rdli->rdli_entry;
	rdynlist_filter_t	*rdlf, *rdlf_prev = NULL;
	slap_overinst 		*on = (slap_overinst *)op->o_bd->bd_info;
	LDAPURLDesc		*lud = NULL;
	Attribute		*a;
	BerValue		*bv, dn;
	int			rc = 0, match = 1, null_entry = 0;
		
	if ( e == NULL ) {
		if ( overlay_entry_get_ov( op, ndn, NULL, NULL, 0, &e, on ) !=
			LDAP_SUCCESS || e == NULL ) {
			Debug( LDAP_DEBUG_TRACE, "rdynlist_add_group: cannot get entry for <%s>\n", ndn->bv_val, 0, 0);
			return 1;
		}
		
		null_entry = 1;
	}
	
	Debug(LDAP_DEBUG_TRACE, "==> rdynlist_add_group <%s>\n",
		e->e_name.bv_val, 0, 0);

	if ( rdli->rdli_entry != NULL ) {
		for ( ; *rdlep ; rdlep = &(*rdlep)->rdle_next ) {
			dnMatch( &match, 0, NULL, NULL, &e->e_nname, &(*rdlep)->rdle_ndn );
			if ( match == 0 ) {
				Debug( LDAP_DEBUG_TRACE, "rdynlist_add_group: group already exists: <%s>\n", e->e_name.bv_val,0,0);
				return 1;
			}
			/* goto last */;
		}
	}
		

	*rdlep = (rdynlist_entry_t *)ch_calloc( 1, sizeof( rdynlist_entry_t ) );
	ldap_pvt_thread_mutex_init( &(*rdlep)->rdle_mutex );
	(*rdlep)->rdle_def = rdld;
	(*rdlep)->rdle_filter = NULL;

	ber_dupbv( &(*rdlep)->rdle_dn, &e->e_name );
	ber_dupbv( &(*rdlep)->rdle_ndn, &e->e_nname );

	a = attrs_find( e->e_attrs, rdld->rdld_member_url_ad );
	
	if ( null_entry == 1 ) {
		a = attrs_dup( a );
		overlay_entry_release_ov( op, e, 0, on );
	}

	if( a == NULL ) {
		Debug( LDAP_DEBUG_TRACE, "rdynlist_add_group: group has no memberURL\n", 0,0,0);
	} else {
		for ( bv = a->a_nvals; !BER_BVISNULL( bv ); bv++ ) {

			rdlf = (rdynlist_filter_t*)ch_calloc( 1, sizeof( rdynlist_filter_t ) );

			if ( ldap_url_parse( bv->bv_val, &lud ) != LDAP_URL_SUCCESS ) {
				Debug( LDAP_DEBUG_TRACE, "rdynlist_add_group: cannot parse url <%s>\n", bv->bv_val,0,0);
				/* FIXME: error? */
				ch_free( rdlf ); 
				continue;
			}

			rdlf->rdlf_scope = lud->lud_scope;

			if ( lud->lud_dn == NULL ) {
				BER_BVSTR( &dn, "" );
			} else {
				ber_str2bv( lud->lud_dn, 0, 0, &dn );
			}

			rc = dnPrettyNormal( NULL, &dn, &rdlf->rdlf_dn, &rdlf->rdlf_ndn, NULL );
			if ( rc != LDAP_SUCCESS ) {
				Debug( LDAP_DEBUG_TRACE, "rdynlist_add_group: cannot normalize DN <%s>\n", dn.bv_val,0,0);
				/* FIXME: error? */
				goto cleanup;
			}
				
			if ( lud->lud_filter != NULL ) {
				ber_str2bv( lud->lud_filter, 0, 1, &rdlf->rdlf_filterstr);
				rdlf->rdlf_filter = str2filter( lud->lud_filter );
			}			

			rdlf->rdlf_next = NULL;
				

			if( (*rdlep)->rdle_filter == NULL ) {
				(*rdlep)->rdle_filter = rdlf;
			}
				
			if( rdlf_prev != NULL ) {
				rdlf_prev->rdlf_next = rdlf;
			}

			rdlf_prev = rdlf;
			
			if ( scan == 1 ){
				rdynlist_add_members_from_filter( op, e, (*rdlep), rdlf, modify );
			}

			Debug( LDAP_DEBUG_TRACE, "rdynlist_add_group: added memberURL DN <%s> with filter <%s>\n", rdlf->rdlf_ndn.bv_val, rdlf->rdlf_filterstr.bv_val, 0);

			ldap_free_urldesc( lud );

			continue;

				
cleanup:;

			ldap_free_urldesc( lud );				
			ch_free( rdlf ); 
		}
	}

	if ( null_entry == 1 ) {
		attrs_free( a );
	}
	return rc;
}

/* 
** Used when opening the database to add all existing 
** groups from the database to our internal list.
*/
static int
rdynlist_group_add_cb( Operation *op, SlapReply *rs )
{
	slap_overinst 		*on = (slap_overinst *)op->o_bd->bd_info;

	assert( op->o_tag == LDAP_REQ_SEARCH );
	

	if ( rs->sr_type == REP_SEARCH ) {
		rdynlist_sc_t		*rdls = (rdynlist_sc_t *)op->o_callback->sc_private;

		Debug(LDAP_DEBUG_TRACE, "==> rdynlist_group_add_cb <%s>\n",
			rs->sr_entry ? rs->sr_entry->e_name.bv_val : "UNKNOWN_DN", 0, 0);

		rdynlist_add_group( op, rdls->rdls_info, rdls->rdls_def, rs->sr_entry, NULL, 0, 0);
	}

	return 0;
}


/*
** When adding a group, we first strip any existing members,
** and add all which match the filters ourselfs.
*/
static int
rdynlist_add_entry( Operation *op, SlapReply *rs)
{
        slap_overinst		*on = (slap_overinst *)op->o_bd->bd_info;
	rdynlist_info_t		*rdli = (rdynlist_info_t *)on->on_bi.bi_private;
	rdynlist_def_t		*rdld = rdli->rdli_def;
	rdynlist_entry_t	*rdle = rdli->rdli_entry;
	rdynlist_filter_t	*rdlf;
	Attribute		*a;
	int			rc = 0;

	Debug( LDAP_DEBUG_TRACE, "==> rdynlist_add_entry <%s>\n", 
		op->ora_e->e_name.bv_val, 0, 0);

	ldap_pvt_thread_mutex_lock( &rdli->rdli_mutex );		
        
	/* Check if it's a group. */
        for ( ; rdld ; rdld = rdld->rdld_next ) {
        	if ( is_entry_objectclass_or_sub( op->ora_e, rdld->rdld_oc ) ) {
			Modification		mod;
			const char		*text = NULL;
			char			textbuf[1024];

			mod.sm_op = LDAP_MOD_DELETE;
			mod.sm_desc = rdld->rdld_member_ad;
			mod.sm_type = rdld->rdld_member_ad->ad_cname;
			mod.sm_values = NULL;
			mod.sm_nvalues = NULL;

			/* We don't want any member attributes added by the user. */
			modify_delete_values( op->ora_e, &mod, /* permissive */ 1, &text, textbuf, sizeof( textbuf ) );

        		rdynlist_add_group( op, rdli, rdld, op->ora_e, NULL, 1 , 0);
			ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );		
			return SLAP_CB_CONTINUE;
		}
	}
	
        for ( ; rdle ; rdle = rdle->rdle_next ) {
		ldap_pvt_thread_mutex_lock( &rdle->rdle_mutex );		
       		
		/* Check if any of the filters are the suffix to the entry DN. 
		   If yes, we can test that filter against the entry. */

	        for ( rdlf = rdle->rdle_filter; rdlf ; rdlf = rdlf->rdlf_next ) {
	       		if ( dnIsSuffix( &op->o_req_ndn, &rdlf->rdlf_ndn ) ) {
       				rc = test_filter( op, op->ora_e, rdlf->rdlf_filter );
       				if ( rc == LDAP_COMPARE_TRUE ) {
					rdynlist_add_member_to_group( op, &op->ora_e->e_name, &op->ora_e->e_nname, rdle );
       					break;
       				}
       			}
       		
		}
		ldap_pvt_thread_mutex_unlock( &rdle->rdle_mutex );		

	}

	ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );		

        return SLAP_CB_CONTINUE;
}

/*
** rdli	- internal group and attribute definitions list
** e	- the group to remove from the internal list
*/
static int
rdynlist_delete_group( rdynlist_info_t *rdli, rdynlist_entry_t *e )
{
	rdynlist_entry_t	*rdle = rdli->rdli_entry,
				*rdle_prev = NULL,
				*rdle_next;
	int			rc = 1;
	
	Debug( LDAP_DEBUG_TRACE, "==> rdynlist_delete_group <%s>\n", 
		rdle->rdle_dn.bv_val, 0, 0);
		
	for ( rdle_next = rdle ; rdle_next ; rdle_prev = rdle, rdle = rdle_next ) {
		rdle_next = rdle->rdle_next;
		
		if ( rdle == e ) {
       			rdynlist_filter_t	*rdlf = rdle->rdle_filter,
       						*rdlf_next;
        			
			if ( rdle_prev != NULL ) {
				rdle_prev->rdle_next = rdle_next;
			} else {
				rdli->rdli_entry = NULL;
			}
        				
			ch_free( rdle->rdle_dn.bv_val );
			ch_free( rdle->rdle_ndn.bv_val );
				
			for( rdlf_next = rdlf ; rdlf_next ; rdlf = rdlf_next ){
				rdlf_next = rdlf->rdlf_next;

				filter_free( rdlf->rdlf_filter );
				ch_free( rdlf->rdlf_filterstr.bv_val );
				ch_free( rdlf->rdlf_dn.bv_val );
				ch_free( rdlf->rdlf_ndn.bv_val );
			}

			ldap_pvt_thread_mutex_unlock( &rdle->rdle_mutex );		
			ldap_pvt_thread_mutex_destroy( &rdle->rdle_mutex );
			ch_free( rdle );
			
			rc = 0;	
			return rc;
			
		}
	}
	
	Debug( LDAP_DEBUG_TRACE, "rdynlist_delete_group: group <%s> not found, should not happen\n", rdle->rdle_dn.bv_val, 0, 0);
	
	return rc;

}

static int
rdynlist_delete_entry( Operation *op, SlapReply *rs)
{
        slap_overinst		*on = (slap_overinst *)op->o_bd->bd_info;
	rdynlist_info_t		*rdli = (rdynlist_info_t *)on->on_bi.bi_private;
	rdynlist_def_t		*rdld = rdli->rdli_def;
	rdynlist_entry_t	*rdle = rdli->rdli_entry,
				*rdle_prev, *rdle_next;
	rdynlist_filter_t	*rdlf;
	Entry			*e;
	int			matched_group = 0, rc = 0;

	Debug( LDAP_DEBUG_TRACE, "==> rdynlist_delete_entry <%s>\n", op->o_req_dn.bv_val, 0, 0);

	ldap_pvt_thread_mutex_lock( &rdli->rdli_mutex );

	if ( overlay_entry_get_ov( op, &op->o_req_ndn, NULL, NULL, 0, &e, on ) !=
		LDAP_SUCCESS || e == NULL ) {
		Debug( LDAP_DEBUG_TRACE, "rdynlist_delete_entry: cannot get entry for <%s>\n", op->o_req_dn.bv_val, 0, 0);
		ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );			
		return SLAP_CB_CONTINUE;
	}

	/* Check if the entry to be deleted is one of our groups. */
	for ( rdle_next = rdle ; rdle_next ; rdle_prev = rdle, rdle = rdle_next ) {
		ldap_pvt_thread_mutex_lock( &rdle->rdle_mutex );
		rdle_next = rdle->rdle_next;
        	
        	if ( is_entry_objectclass_or_sub( e, rdle->rdle_def->rdld_oc ) ) {
       			int match = 1;

			matched_group = 1;
        			
       			dnMatch( &match, 0, NULL, NULL, &e->e_nname, &rdle->rdle_ndn );
        			
       			if ( match == 0 ) {
       				rdynlist_filter_t	*rdlf = rdle->rdle_filter,
       							*rdlf_next;
        			
        			rdynlist_delete_group( rdli, rdle );
        			break;
       			}
       		}
       		
		ldap_pvt_thread_mutex_unlock( &rdle->rdle_mutex );			
       	}

	if ( matched_group == 1 ) {
		overlay_entry_release_ov( op, e, 0, on );
		ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );		
		return SLAP_CB_CONTINUE;
	}

	/* Check if the entry matches any of the groups.
	   If yes, we can delete the entry from that group. */

        for ( rdle = rdli->rdli_entry ; rdle ; rdle = rdle->rdle_next ) {
        
		ldap_pvt_thread_mutex_lock( &rdle->rdle_mutex );		

	        for ( rdlf = rdle->rdle_filter; rdlf ; rdlf = rdlf->rdlf_next ) {
	       		if ( dnIsSuffix( &op->o_req_ndn, &rdlf->rdlf_ndn ) ) {
       				rc = test_filter( op, e, rdlf->rdlf_filter );
       				if ( rc == LDAP_COMPARE_TRUE ) {
					rdynlist_delete_member_from_group( op, &e->e_name, &e->e_nname, rdle );
       					break;
       				}
       			}
       		
		}
		
		ldap_pvt_thread_mutex_unlock( &rdle->rdle_mutex );
		
	}
	
 	overlay_entry_release_ov( op, e, 0, on );
	ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );		

        return SLAP_CB_CONTINUE;
}

static int
rdynlist_response( Operation *op, SlapReply *rs )
{
        slap_overinst		*on = (slap_overinst *)op->o_bd->bd_info;
	rdynlist_info_t		*rdli = (rdynlist_info_t *)on->on_bi.bi_private;
	rdynlist_def_t		*rdld = rdli->rdli_def;
	rdynlist_entry_t	*rdle = rdli->rdli_entry;
	rdynlist_filter_t	*rdlf;
	BerValue		new_dn, new_ndn, pdn;
	Entry			*e, *group;
	Attribute		*a;
	int			is_olddn, is_newdn, dn_equal;

	if ( op->o_tag == LDAP_REQ_MODRDN ) {
		if ( rs->sr_type == REP_RESULT && rs->sr_err == LDAP_SUCCESS && !get_manageDSAit( op )) {

			Debug( LDAP_DEBUG_TRACE, "==> rdynlist_response MODRDN from <%s>\n", op->o_req_dn.bv_val, 0, 0);
			
			ldap_pvt_thread_mutex_lock( &rdli->rdli_mutex );			

			if ( op->oq_modrdn.rs_newSup ) {
				pdn = *op->oq_modrdn.rs_newSup;
			} else {
				dnParent( &op->o_req_dn, &pdn );
			}
			build_new_dn( &new_dn, &pdn, &op->orr_newrdn, op->o_tmpmemctx );
	
			if ( op->oq_modrdn.rs_nnewSup ) {
				pdn = *op->oq_modrdn.rs_nnewSup;
			} else {
				dnParent( &op->o_req_ndn, &pdn );
			}
			build_new_dn( &new_ndn, &pdn, &op->orr_nnewrdn, op->o_tmpmemctx );

			Debug( LDAP_DEBUG_TRACE, "rdynlist_response MODRDN to <%s>\n", new_dn.bv_val, 0, 0);
			
			dnMatch( &dn_equal, 0, NULL, NULL, &op->o_req_ndn, &new_ndn );

			if ( overlay_entry_get_ov( op, &new_ndn, NULL, NULL, 0, &e, on ) !=
				LDAP_SUCCESS || e == NULL ) {
				Debug( LDAP_DEBUG_TRACE, "rdynlist_response MODRDN cannot get entry for <%s>\n", new_dn.bv_val, 0, 0);
				ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );
				return SLAP_CB_CONTINUE;
			}

			a = attrs_find( e->e_attrs, slap_schema.si_ad_objectClass );


			if ( a == NULL ) {
				Debug( LDAP_DEBUG_TRACE, "rdynlist_response MODRDN entry <%s> has no objectClass\n", new_dn.bv_val, 0, 0);
			 	overlay_entry_release_ov( op, e, 0, on );
				ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );		
				return SLAP_CB_CONTINUE;
			}
	
		 	
			/* If a groups DN is modified, just update rdle_dn/ndn of that group with the new DN. */
			for ( ; rdld; rdld = rdld->rdld_next ) {

				if ( value_find_ex( slap_schema.si_ad_objectClass,
						SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
						SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
						a->a_nvals, &rdld->rdld_oc->soc_cname,
						op->o_tmpmemctx ) == 0 )
				{		
					for ( rdle = rdli->rdli_entry ; rdle ; rdle = rdle->rdle_next ) {
						int match = 1;
						
						dnMatch( &match, 0, NULL, NULL, &rdle->rdle_ndn, &op->o_req_ndn );
						if ( match == 0 ) {
							Debug( LDAP_DEBUG_TRACE, "rdynlist_response MODRDN updating group's DN to <%s>\n", new_dn.bv_val, 0, 0);
							ber_dupbv( &rdle->rdle_dn, &new_dn );
							ber_dupbv( &rdle->rdle_ndn, &new_ndn );
					
							op->o_tmpfree( new_dn.bv_val, op->o_tmpmemctx  );
							op->o_tmpfree( new_ndn.bv_val, op->o_tmpmemctx );
						 	overlay_entry_release_ov( op, e, 0, on );
							ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );		
							return SLAP_CB_CONTINUE;
						}
					}

				}
			}

		 	overlay_entry_release_ov( op, e, 0, on );

			/* For each group: 
			   1. check if the orginal entry's DN is in the group.
			   2. chceck if the any of the group filter's base DN is a suffix of the new DN 
			
			   If 1 and 2 are both false, we do nothing.
			   If 1 and 2 is true, we remove the old DN from the group, and add the new DN.
			   If 1 is false, and 2 is true, we check the entry against the group's filters,
				and add it's DN to the group.
			   If 1 is true, and 2 is false, we delete the entry's DN from the group.
			*/
			for ( rdle = rdli->rdli_entry ; rdle ; rdle = rdle->rdle_next ) {
				is_olddn = 0;
				is_newdn = 0;


				ldap_pvt_thread_mutex_lock( &rdle->rdle_mutex );

				if ( overlay_entry_get_ov( op, &rdle->rdle_ndn, NULL, NULL, 0, &group, on ) !=
					LDAP_SUCCESS || group == NULL ) {
					Debug( LDAP_DEBUG_TRACE, "rdynlist_response MODRDN cannot get group entry <%s>\n", rdle->rdle_dn.bv_val, 0, 0);

					op->o_tmpfree( new_dn.bv_val, op->o_tmpmemctx );
					op->o_tmpfree( new_ndn.bv_val, op->o_tmpmemctx );

					ldap_pvt_thread_mutex_unlock( &rdle->rdle_mutex );
					ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );
					return SLAP_CB_CONTINUE;
				}

				a = attrs_find( group->e_attrs, rdle->rdle_def->rdld_member_ad );

				if ( a != NULL ) {
					if ( value_find_ex( rdle->rdle_def->rdld_member_ad,
							SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
							SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
							a->a_nvals, &op->o_req_ndn, op->o_tmpmemctx ) == 0 ) 
					{
						is_olddn = 1;
					}

				}

			 	overlay_entry_release_ov( op, group, 0, on );
		
				for ( rdlf = rdle->rdle_filter ; rdlf ; rdlf = rdlf->rdlf_next ) {
					if ( dnIsSuffix( &new_ndn, &rdlf->rdlf_ndn ) ) {
						is_newdn = 1;
						break;
					}
				}
		
		
				if ( is_olddn == 1 && is_newdn == 0 ) {
					rdynlist_delete_member_from_group( op, &op->o_req_dn, &op->o_req_ndn, rdle );
				} else
				if ( is_olddn == 0 && is_newdn == 1 ) {
					for ( rdlf = rdle->rdle_filter; rdlf; rdlf = rdlf->rdlf_next ) {
						if ( test_filter( op, e, rdlf->rdlf_filter ) == LDAP_COMPARE_TRUE ) {
							rdynlist_add_member_to_group( op, &new_dn, &new_ndn, rdle );
							break;
						}
					}
				} else
				if ( is_olddn == 1 && is_newdn == 1 && dn_equal != 0 ) {
					rdynlist_delete_member_from_group( op, &op->o_req_dn, &op->o_req_ndn, rdle );
					rdynlist_add_member_to_group( op, &new_dn, &new_ndn, rdle );
				}

				ldap_pvt_thread_mutex_unlock( &rdle->rdle_mutex );
			}

			op->o_tmpfree( new_dn.bv_val, op->o_tmpmemctx );
			op->o_tmpfree( new_ndn.bv_val, op->o_tmpmemctx );
 	
			ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );			
		}
	}
	
	if ( op->o_tag == LDAP_REQ_MODIFY ) {
		if ( rs->sr_type == REP_RESULT && rs->sr_err == LDAP_SUCCESS  && !get_manageDSAit( op ) ) {
			Debug( LDAP_DEBUG_TRACE, "==> rdynlist_response MODIFY <%s>\n", op->o_req_dn.bv_val, 0, 0);

			ldap_pvt_thread_mutex_lock( &rdli->rdli_mutex );			

			if ( overlay_entry_get_ov( op, &op->o_req_ndn, NULL, NULL, 0, &e, on ) !=
				LDAP_SUCCESS || e == NULL ) {
				Debug( LDAP_DEBUG_TRACE, "rdynlist_response MODIFY cannot get entry for <%s>\n", op->o_req_dn.bv_val, 0, 0);
				ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );
				return SLAP_CB_CONTINUE;
			}

			a = attrs_find( e->e_attrs, slap_schema.si_ad_objectClass );


			if ( a == NULL ) {
				Debug( LDAP_DEBUG_TRACE, "rdynlist_response MODIFY entry <%s> has no objectClass\n", op->o_req_dn.bv_val, 0, 0);
		 		overlay_entry_release_ov( op, e, 0, on );
				ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );		
				return SLAP_CB_CONTINUE;
			}
	
		 	
			/* If we modify a group's memberURL, we have to delete all of it's members,
			   and add them anew, because we cannot tell from which memberURL a member was added. */
			for ( ; rdld; rdld = rdld->rdld_next ) {

				if ( value_find_ex( slap_schema.si_ad_objectClass,
						SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
						SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
						a->a_nvals, &rdld->rdld_oc->soc_cname,
						op->o_tmpmemctx ) == 0 )
				{
					Modifications	*m;
					int		match = 1;
			
					m = op->orm_modlist;
			
					for ( ; rdle ; rdle = rdle->rdle_next ) {
						ldap_pvt_thread_mutex_lock( &rdle->rdle_mutex );

						dnMatch( &match, 0, NULL, NULL, &op->o_req_ndn, &rdle->rdle_ndn );
				
						if ( match == 0 ) {
							for ( ; m ; m = m->sml_next ) {
								if ( m->sml_desc == rdle->rdle_def->rdld_member_url_ad ) {
									rdynlist_def_t	*group_rdld = rdle->rdle_def;
									Debug( LDAP_DEBUG_TRACE, "rdynlist_response MODIFY changing memberURL for group <%s>\n", 
										op->o_req_dn.bv_val, 0, 0);
									
									overlay_entry_release_ov( op, e, 0, on );
							 		
									rdynlist_delete_member_from_group( op, NULL, NULL, rdle );
									rdynlist_delete_group( rdli, rdle );

									rdynlist_add_group( op, rdli, group_rdld, NULL, &op->o_req_ndn, 1, 1);

									ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );
									return SLAP_CB_CONTINUE;
								}
							}
							
							ldap_pvt_thread_mutex_unlock( &rdle->rdle_mutex );
							break;
						}
						
						ldap_pvt_thread_mutex_unlock( &rdle->rdle_mutex );
					}
	
			 		overlay_entry_release_ov( op, e, 0, on );
					ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );
					return SLAP_CB_CONTINUE;
				}
			}

	 		overlay_entry_release_ov( op, e, 0, on );
	
			/* When modifing any of the attributes of an entry, we must
			   check if the entry is in any of our groups, and if
			   the modified entry maches any of the filters of that group.

			   If the entry exists in a group, but the modified attributes do
			   	not match any of the group's filters, we delete the entry from that group.
			   If the entry doesn't exist in a group, but matches a filter, 
			   	we add it to that group.
			*/
			for ( rdle = rdli->rdli_entry ; rdle ; rdle = rdle->rdle_next ) {
				is_olddn = 0;
				is_newdn = 0;


				ldap_pvt_thread_mutex_lock( &rdle->rdle_mutex );

				if ( overlay_entry_get_ov( op, &rdle->rdle_ndn, NULL, NULL, 0, &group, on ) !=
					LDAP_SUCCESS || group == NULL ) {
					Debug( LDAP_DEBUG_TRACE, "rdynlist_response MODIFY cannot get entry for <%s>\n", 
						rdle->rdle_dn.bv_val, 0, 0);

					ldap_pvt_thread_mutex_unlock( &rdle->rdle_mutex );
					ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );
					return SLAP_CB_CONTINUE;
				}

				a = attrs_find( group->e_attrs, rdle->rdle_def->rdld_member_ad );

				if ( a != NULL ) {
					if ( value_find_ex( rdle->rdle_def->rdld_member_ad,
							SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
							SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
							a->a_nvals, &op->o_req_ndn, op->o_tmpmemctx ) == 0 ) 
					{
						is_olddn = 1;
					}

				}

			 	overlay_entry_release_ov( op, group, 0, on );

				for ( rdlf = rdle->rdle_filter ; rdlf ; rdlf = rdlf->rdlf_next ) {
					if ( dnIsSuffix( &op->o_req_ndn, &rdlf->rdlf_ndn ) ) {
						if ( test_filter( op, e, rdlf->rdlf_filter ) == LDAP_COMPARE_TRUE ) {
							is_newdn = 1;
							break;
						}
					}
				}
		
				if ( is_olddn == 1 && is_newdn == 0 ) {
					rdynlist_delete_member_from_group( op, &op->o_req_dn, &op->o_req_ndn, rdle );
				} else
				if ( is_olddn == 0 && is_newdn == 1 ) {
					rdynlist_add_member_to_group( op, &op->o_req_dn, &op->o_req_ndn, rdle );
				} 

				ldap_pvt_thread_mutex_unlock( &rdle->rdle_mutex );
			}

			ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );
		}
	}
	
	return SLAP_CB_CONTINUE;
}

/*
** When modifing a group, we must deny any modifications to the member attribute,
** because the group would be inconsistent.
*/
static int
rdynlist_modify_entry( Operation *op, SlapReply *rs)
{
        slap_overinst		*on = (slap_overinst *)op->o_bd->bd_info;
	rdynlist_info_t		*rdli = (rdynlist_info_t *)on->on_bi.bi_private;
	rdynlist_def_t		*rdld = rdli->rdli_def;
	rdynlist_entry_t	*rdle = rdli->rdli_entry;
	Entry			*e;
	Attribute		*a;

	if ( get_manageDSAit( op ) ) {
		return SLAP_CB_CONTINUE;
	}

	Debug( LDAP_DEBUG_TRACE, "==> rdynlist_modify_entry <%s>\n", op->o_req_dn.bv_val, 0, 0);
	ldap_pvt_thread_mutex_lock( &rdli->rdli_mutex );			
	
	if ( overlay_entry_get_ov( op, &op->o_req_ndn, NULL, NULL, 0, &e, on ) !=
		LDAP_SUCCESS || e == NULL ) {
		Debug( LDAP_DEBUG_TRACE, "rdynlist_modify_entry cannot get entry for <%s>\n", op->o_req_dn.bv_val, 0, 0);
		ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );
		return SLAP_CB_CONTINUE;
	}

	a = attrs_find( e->e_attrs, slap_schema.si_ad_objectClass );

	if ( a == NULL ) {
		Debug( LDAP_DEBUG_TRACE, "rdynlist_modify_entry entry <%s> has no objectClass\n", op->o_req_dn.bv_val, 0, 0);
		ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );		
		return SLAP_CB_CONTINUE;
	}
	
		 	
	for ( ; rdld; rdld = rdld->rdld_next ) {

		if ( value_find_ex( slap_schema.si_ad_objectClass,
				SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
				SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
				a->a_nvals, &rdld->rdld_oc->soc_cname,
				op->o_tmpmemctx ) == 0 )
		{
			Modifications	*m;
			int		match = 1;
			
			m = op->orm_modlist;
			
			for ( ; rdle ; rdle = rdle->rdle_next ) {
				dnMatch( &match, 0, NULL, NULL, &op->o_req_ndn, &rdle->rdle_ndn );
				
				if ( match == 0 ) {
					for ( ; m ; m = m->sml_next ) {
						if ( m->sml_desc == rdle->rdle_def->rdld_member_ad ) {
						 	overlay_entry_release_ov( op, e, 0, on );
							ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );
							Debug( LDAP_DEBUG_TRACE, "rdynlist_modify_entry attempted to modify group's <%s> member attribute\n", op->o_req_dn.bv_val, 0, 0);
							send_ldap_error(op, rs, LDAP_CONSTRAINT_VIOLATION, "attempt to modify dynamic group member attribute");
							return LDAP_CONSTRAINT_VIOLATION;
						}
					}
					break;
				}
			}
			
		 	overlay_entry_release_ov( op, e, 0, on );
			ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );
			return SLAP_CB_CONTINUE;
		}
	}
	
 	overlay_entry_release_ov( op, e, 0, on );
	ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );			
	return SLAP_CB_CONTINUE;
}

/* 
** Builds a filter for searching for the 
** group entries, according to the objectClass. 
*/
static int
rdynlist_build_def_filter( rdynlist_def_t *rdld, Operation *op )
{
        char    *ptr;

	Debug( LDAP_DEBUG_TRACE, "==> rdynlist_build_def_filter\n", 0, 0, 0);

	op->ors_filterstr.bv_len = STRLENOF( "(=)" ) 
			+ slap_schema.si_ad_objectClass->ad_cname.bv_len
			+ rdld->rdld_oc->soc_cname.bv_len;
        ptr = op->ors_filterstr.bv_val = op->o_tmpalloc( op->ors_filterstr.bv_len + 1, op->o_tmpmemctx );
	*ptr++ = '(';
	ptr = lutil_strcopy( ptr, slap_schema.si_ad_objectClass->ad_cname.bv_val );
	*ptr++ = '=';
	ptr = lutil_strcopy( ptr, rdld->rdld_oc->soc_cname.bv_val );
	*ptr++ = ')';
	*ptr = '\0';

	op->ors_filter = str2filter_x( op, op->ors_filterstr.bv_val );
		
        assert( op->ors_filterstr.bv_len == ptr - op->ors_filterstr.bv_val );

        return 0;
}

enum {
	RDL_ATTRSET = 1,
	RDL_LAST
};

static ConfigDriver	rdl_cfgen;

static ConfigTable rdlcfg[] = {
	{ "rdynlist-attrset", "group-oc> <URL-ad> <member-ad",
		3, 4, 0, ARG_MAGIC|RDL_ATTRSET, rdl_cfgen,
		"( OLcfgOvAt:20.1 NAME 'olcRDLattrSet' "
			"DESC 'Really dynamic list: <group objectClass>, <URL attributeDescription>, <member attributeDescription>' "
			"EQUALITY caseIgnoreMatch "
			"SYNTAX OMsDirectoryString "
			"X-ORDERED 'VALUES' )",
			NULL, NULL },
	{ NULL, NULL, 0, 0, 0, ARG_IGNORED }
};

static ConfigOCs rdlocs[] = {
	{ "( OLcfgOvOc:20.1 "
		"NAME 'olcReallyDynamicList' "
		"DESC 'Really dynamic list configuration' "
		"SUP olcOverlayConfig "
		"MAY olcRDLattrSet )",
		Cft_Overlay, rdlcfg, NULL, NULL },
	{ NULL, 0, NULL }
};


static int
rdl_cfgen( ConfigArgs *c )
{
	slap_overinst   	*on = (slap_overinst *)c->bi;
	rdynlist_info_t		*rdli = (rdynlist_info_t *)on->on_bi.bi_private;
	rdynlist_def_t		*rdld;
	rdynlist_entry_t	*rdle;

	int rc = 0, i;

	Debug( LDAP_DEBUG_TRACE, "==> rdynlist_cfgen\n", 0, 0, 0);

	if( rdli == NULL ) {
		rdli = (rdynlist_info_t*)ch_calloc( 1, sizeof(rdynlist_info_t) );
		ldap_pvt_thread_mutex_init( &rdli->rdli_mutex );
		rdli->rdli_def = NULL;
		rdli->rdli_entry = NULL;
		on->on_bi.bi_private = (void *)rdli;
	}

	rdld = rdli->rdli_def;
	rdle = rdli->rdli_entry;
	

	
	if ( c->op == SLAP_CONFIG_EMIT ) {

		ldap_pvt_thread_mutex_lock( &rdli->rdli_mutex );

		for ( i = 0 ; rdld ; i++, rdld = rdld->rdld_next ) {
			struct berval	bv;
			char		*ptr = c->cr_msg;
			
			assert(rdld->rdld_oc != NULL);
			assert(rdld->rdld_member_url_ad != NULL);
			assert(rdld->rdld_member_ad != NULL);
			
			ptr += snprintf( c->cr_msg, sizeof( c->cr_msg ),
				SLAP_X_ORDERED_FMT "%s %s %s", i,
				rdld->rdld_oc->soc_cname.bv_val,
				rdld->rdld_member_url_ad->ad_cname.bv_val,
				rdld->rdld_member_ad->ad_cname.bv_val );
			
			bv.bv_val = c->cr_msg;
			bv.bv_len = ptr - bv.bv_val;
			value_add_one ( &c->rvalue_vals, &bv );
			
		}
		ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );
		
		return rc;
		
	}else if ( c->op == LDAP_MOD_DELETE ) {
		if ( c->valx < 0) {
			rdynlist_def_t 		*rdld_next;
			rdynlist_entry_t	*rdle_next;
			rdynlist_filter_t	*rdlf = rdle->rdle_filter,
						*rdlf_next;
			
			ldap_pvt_thread_mutex_lock( &rdli->rdli_mutex );

			for ( rdld_next = rdld; rdld_next; rdld = rdld_next ) {
				rdld_next = rdld->rdld_next;
				
				ch_free( rdld );
			}
			
			
			for ( rdle_next = rdle ; rdle_next ; rdle = rdle_next ) {
				rdle_next = rdle->rdle_next;

				ch_free( rdle->rdle_dn.bv_val );
				ch_free( rdle->rdle_ndn.bv_val );
				
				for( rdlf_next = rdlf ; rdlf_next ; rdlf = rdlf_next ){
					rdlf_next = rdlf->rdlf_next;

					filter_free( rdlf->rdlf_filter );
					ch_free( rdlf->rdlf_filterstr.bv_val );
					ch_free( rdlf->rdlf_dn.bv_val );
					ch_free( rdlf->rdlf_ndn.bv_val );
				}

				ldap_pvt_thread_mutex_init( &rdle->rdle_mutex );
				ch_free( rdle );
			}
			
			ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );
			
			ldap_pvt_thread_mutex_destroy( &rdli->rdli_mutex );
			ch_free( rdli );
			on->on_bi.bi_private = NULL;
			
		} else {
			rdynlist_def_t		**rdldp;
			rdynlist_entry_t	*rdle_next, *rdle_prev;
			rdynlist_filter_t	*rdlf,
						*rdlf_next;
			struct berval		*bv;
			
			ldap_pvt_thread_mutex_lock( &rdli->rdli_mutex );

			for ( i = 0, rdldp = &rdli->rdli_def;
				i < c->valx; i++ ) 
			{
				if ( *rdldp == NULL) {
					return 1;
				}
				rdldp = &(*rdldp)->rdld_next;
			}
			
			rdld = *rdldp;
			*rdldp = rdld->rdld_next;
			
			for ( rdle_next = rdle , rdle_prev = NULL ; rdle_next ; rdle_prev = rdle, rdle = rdle_next ) {
				rdle_next = rdle->rdle_next;
				
				if( rdle->rdle_def == rdld ) {
					rdlf = rdle->rdle_filter;

					ch_free( rdle->rdle_dn.bv_val );
					ch_free( rdle->rdle_ndn.bv_val );

					for ( rdlf_next = rdlf; rdlf_next ; rdlf = rdlf_next ) {
						rdlf_next = rdlf->rdlf_next;
						filter_free( rdlf->rdlf_filter );
						ch_free( rdlf->rdlf_filterstr.bv_val );
						ch_free( rdlf->rdlf_dn.bv_val );
						ch_free( rdlf->rdlf_ndn.bv_val );
					}

					ldap_pvt_thread_mutex_destroy( &rdle->rdle_mutex );
					ch_free( rdle );
					
					rdle = rdle_prev;
					
					if( rdle_prev != NULL ) {
						rdle_prev->rdle_next = rdle_next;
					}
				}
			}
			
			ch_free( rdld );
			rdld = rdli->rdli_def;
			ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );

		}
		
		return rc;
	}

	switch(c->type){
	case RDL_ATTRSET: {
		rdynlist_def_t		**rdldp,
					*rdld_next = NULL;
		ObjectClass		*oc = NULL;
		AttributeDescription	*member_url_ad = NULL,
					*member_ad = NULL;
		const char		*text;
	
	
		oc = oc_find( c->argv[ 1 ] );
		if( oc == NULL ){
			snprintf( c->cr_msg, sizeof( c->cr_msg ),
				"\"rdynlist-attrset <oc> <URL-ad> <member-ad>\": "
				"unable to find ObjectClass \"%s\"",
				c->argv[ 1 ] );
			Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
				c->log, c->cr_msg, 0 );
			return 1;
		}
	
	
		rc = slap_str2ad( c->argv[ 2 ], &member_url_ad, &text );
		if( rc != LDAP_SUCCESS ) {
			snprintf( c->cr_msg, sizeof( c->cr_msg ),
				"\"rdynlist-attrset <oc> <URL-ad> <member-ad>\": "
				"unable to find AttributeDescription \"%s\"",
				c->argv[ 2 ] );
			Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
				c->log, c->cr_msg, 0 );		
			return 1;
		}
	
		if( !is_at_subtype( member_url_ad->ad_type, slap_schema.si_ad_labeledURI->ad_type ) ) {
			snprintf( c->cr_msg, sizeof( c->cr_msg ),
				"\"rdynlist-attrset <oc> <URL-ad> <member-ad>\": "
				"AttributeDescription \"%s\" ",
				"must be of a subtype \"labeledURI\"",
				c->argv[ 2 ] );
			Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
				c->log, c->cr_msg, 0 );
			return 1;
		}
	
		rc = slap_str2ad( c->argv[3], &member_ad, &text );
		if( rc != LDAP_SUCCESS ) {
			snprintf( c->cr_msg, sizeof( c->cr_msg ),
				"\"rdynlist-attrset <oc> <URL-ad> <member-ad>\": "
				"unable to find AttributeDescription \"%s\"",
				c->argv[ 3 ] );
			Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
				c->log, c->cr_msg, 0 );
			return 1;
		}

		ldap_pvt_thread_mutex_lock( &rdli->rdli_mutex );

		for ( rdldp = &rdli->rdli_def ; *rdldp ; rdldp = &(*rdldp)->rdld_next ) {
			/* The same URL attribute / member attribute pair
			* cannot be repeated */
			
			if ( (*rdldp)->rdld_member_url_ad == member_url_ad && (*rdldp)->rdld_member_ad == member_ad ) {
				snprintf( c->cr_msg, sizeof( c->cr_msg ),
					"\"rdynlist-attrset <oc> <URL-ad> <member-ad>\": "
					"URL attributeDescription \"%s\" already mapped",
					member_ad->ad_cname.bv_val );
				Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
					c->log, c->cr_msg, 0 );
/*				return 1; //warning*/
			}
		}
	
		if ( c->valx > 0 ) {
			int	i;

			for ( i = 0, rdldp = &rdli->rdli_def ;
				i < c->valx; i++ )
			{
				if ( *rdldp == NULL ) {
					snprintf( c->cr_msg, sizeof( c->cr_msg ),
						"\"rdynlist-attrset <oc> <URL-ad> <member-ad>\": "
						"invalid index {%d}",
						c->valx );
					Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
						c->log, c->cr_msg, 0 );

					ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );		
					return 1;
				}
				rdldp = &(*rdldp)->rdld_next;
			}
			rdld_next = *rdldp;
	
		} else {
			for ( rdldp = &rdli->rdli_def; *rdldp;
				rdldp = &(*rdldp)->rdld_next )
				/* goto last */;
		}
		
		*rdldp = (rdynlist_def_t *)ch_calloc( 1, sizeof(rdynlist_info_t));
		
		(*rdldp)->rdld_oc = oc;
		(*rdldp)->rdld_member_url_ad = member_url_ad;
		(*rdldp)->rdld_member_ad = member_ad;
		(*rdldp)->rdld_next = rdld_next;

		ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );

		} break;
		
	default:
		rc = 1;
		break;
	}
	
	return rc;
}

/* 
** Do a search for all the groups in the
** database, and add them to out internal list.
*/
static int
rdynlist_db_open(
	BackendDB	*be,
	ConfigReply	*cr )
{
	slap_overinst           *on = (slap_overinst *) be->bd_info,
				*on_bd;
	rdynlist_info_t		*rdli = on->on_bi.bi_private;
	rdynlist_def_t		*rdld;
	rdynlist_sc_t		rdls;
	Operation		*op;
	SlapReply		rs = { REP_RESULT };
	slap_callback		cb = { 0 };
	
	void            	*thrctx = ldap_pvt_thread_pool_context();
	Connection      	conn = { 0 };
	OperationBuffer 	opbuf;
	BerValue		bv;
	char			*ptr;
	int			rc = 0;
	
	Debug( LDAP_DEBUG_TRACE, "==> rdynlist_db_open\n", 0, 0, 0);

	connection_fake_init( &conn, &opbuf, thrctx );
	op = &opbuf.ob_op;
	
	op->ors_attrsonly = 0;
	op->o_tag = LDAP_REQ_SEARCH;
	op->o_dn = be->be_rootdn;
	op->o_ndn = be->be_rootndn;

	op->o_req_dn = be->be_suffix[0];
	op->o_req_ndn = be->be_nsuffix[0];

	op->ors_scope = LDAP_SCOPE_SUBTREE;
	op->ors_deref = LDAP_DEREF_NEVER;
	op->ors_limit = NULL;
	op->ors_tlimit = SLAP_NO_LIMIT;
	op->ors_slimit = SLAP_NO_LIMIT;
	op->ors_attrs =  slap_anlist_no_attrs;
	
	op->o_bd = select_backend(&op->o_req_ndn, 0);

	ldap_pvt_thread_mutex_lock( &rdli->rdli_mutex );
	for (rdld = rdli->rdli_def ; rdld ; rdld = rdld->rdld_next) {

		rdynlist_build_def_filter(rdld, op);
		

		rdls.rdls_info = rdli;
		rdls.rdls_def = rdld;
		cb.sc_private = &rdls;
		cb.sc_response = rdynlist_group_add_cb;
		cb.sc_cleanup = NULL;
		cb.sc_next = NULL;
		
		op->o_callback = &cb;

		op->o_bd->bd_info = (BackendInfo *)on->on_info;
		op->o_bd->be_search( op, &rs );
		op->o_bd->bd_info = (BackendInfo *)on;
		
		filter_free_x( op, op->ors_filter );
		op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
	}		
	ldap_pvt_thread_mutex_unlock( &rdli->rdli_mutex );
		
	return 0;
}

static int
rdynlist_db_close(
	BackendDB	*be,
	ConfigReply	*cr )
{
	slap_overinst           *on = (slap_overinst *) be->bd_info;

	Debug( LDAP_DEBUG_TRACE, "==> rdynlist_db_close\n", 0, 0, 0);

	if ( on->on_bi.bi_private ) {
		rdynlist_info_t		*rdli = on->on_bi.bi_private;
		rdynlist_entry_t	*rdle = rdli->rdli_entry,
					*rdle_next;
		rdynlist_filter_t	*rdlf, *rdlf_next;
		

		for ( rdle_next = rdle; rdle_next; rdle = rdle_next ) {
			rdle_next = rdle->rdle_next;
			
			ch_free( rdle->rdle_dn.bv_val );
			ch_free( rdle->rdle_ndn.bv_val );
			
			rdlf = rdle->rdle_filter;
			
			for ( rdlf_next = rdlf; rdlf_next; rdlf = rdlf_next ) {
				rdlf_next = rdlf->rdlf_next;
				
				filter_free( rdlf->rdlf_filter );
				ch_free( rdlf->rdlf_filterstr.bv_val );
				ch_free( rdlf->rdlf_dn.bv_val );
				ch_free( rdlf->rdlf_ndn.bv_val );	
				ch_free( rdlf );
			}

			ldap_pvt_thread_mutex_destroy( &rdle->rdle_mutex );
			ch_free( rdle );
		}
	}

	return 0;
}

static int
rdynlist_db_destroy(
	BackendDB	*be,
	ConfigReply	*cr )
{
	slap_overinst           *on = (slap_overinst *) be->bd_info;

	Debug( LDAP_DEBUG_TRACE, "==> rdynlist_db_destroy\n", 0, 0, 0);

	if ( on->on_bi.bi_private ) {
		rdynlist_info_t		*rdli = on->on_bi.bi_private;
		rdynlist_def_t		*rdld = rdli->rdli_def,
					*rdld_next;
		
		for ( rdld_next = rdld; rdld_next; rdld = rdld_next ) {
			rdld_next = rdld->rdld_next;
			
			ch_free( rdld );
		}

	ldap_pvt_thread_mutex_destroy( &rdli->rdli_mutex );
	ch_free( rdli );
	}

	return 0;
}

static slap_overinst	rdynlist = { { NULL } };

#if SLAPD_OVER_RDYNLIST == SLAPD_MOD_DYNAMIC
static
#endif /* SLAPD_OVER_RDYNLIST == SLAPD_MOD_DYNAMIC */
int
rdynlist_initialize(void)
{
        int     rc = 0;
	rdynlist.on_bi.bi_type = "rdynlist";

	rdynlist.on_bi.bi_db_open = rdynlist_db_open;
	rdynlist.on_bi.bi_db_close = rdynlist_db_close;
	rdynlist.on_bi.bi_db_destroy = rdynlist_db_destroy;

	rdynlist.on_bi.bi_op_add = rdynlist_add_entry;
	rdynlist.on_bi.bi_op_delete = rdynlist_delete_entry;
	rdynlist.on_bi.bi_op_modify = rdynlist_modify_entry;

	rdynlist.on_response = rdynlist_response;
	
	rdynlist.on_bi.bi_cf_ocs = rdlocs;

	rc = config_register_schema( rdlcfg, rdlocs );
	if ( rc ) {
		return rc;
	}

	return overlay_register( &rdynlist );
}

#if SLAPD_OVER_RDYNLIST == SLAPD_MOD_DYNAMIC
int
init_module( int argc, char *argv[] )
{
	return rdynlist_initialize();
}
#endif


#endif /* SLAPD_OVER_RDYNLIST */