From 339b9c371ec07b5125fed23e4bac1cdca2790525 Mon Sep 17 00:00:00 2001
From: Pierangelo Masarati <ando@openldap.org>
Date: Fri, 6 Jan 2006 17:46:52 +0000
Subject: [PATCH] rfc2589 support (ITS#4293)

---
 clients/tools/Makefile.in          |   19 +-
 clients/tools/ldapexop.c           |  308 +++++
 configure.in                       |   17 +
 doc/man/man5/slapo-dds.5           |  272 ++++
 include/ldap.h                     |   34 +
 libraries/libldap/Makefile.in      |    4 +-
 libraries/libldap/dds.c            |  158 +++
 libraries/libldap_r/Makefile.in    |    4 +-
 servers/slapd/bconfig.c            |    1 +
 servers/slapd/controls.c           |    2 +
 servers/slapd/overlays/Makefile.in |    4 +
 servers/slapd/overlays/dds.c       | 1992 ++++++++++++++++++++++++++++
 servers/slapd/schema_prep.c        |    2 +-
 servers/slapd/slap.h               |    3 +-
 tests/data/dds.out                 |   70 +
 tests/data/slapd-dds.conf          |   88 ++
 tests/run.in                       |    2 +
 tests/scripts/conf.sh              |    1 +
 tests/scripts/defines.sh           |    4 +
 tests/scripts/test046-dds          |  529 ++++++++
 20 files changed, 3504 insertions(+), 10 deletions(-)
 create mode 100644 clients/tools/ldapexop.c
 create mode 100644 doc/man/man5/slapo-dds.5
 create mode 100644 libraries/libldap/dds.c
 create mode 100644 servers/slapd/overlays/dds.c
 create mode 100644 tests/data/dds.out
 create mode 100644 tests/data/slapd-dds.conf
 create mode 100755 tests/scripts/test046-dds

diff --git a/clients/tools/Makefile.in b/clients/tools/Makefile.in
index a51a47af6c..2c10318337 100644
--- a/clients/tools/Makefile.in
+++ b/clients/tools/Makefile.in
@@ -14,9 +14,11 @@
 ## <http://www.OpenLDAP.org/license.html>.
 
 SRCS	= ldapsearch.c ldapmodify.c ldapdelete.c ldapmodrdn.c \
-		ldappasswd.c ldapwhoami.c ldapcompare.c common.c
+		ldappasswd.c ldapwhoami.c ldapcompare.c \
+		ldapexop.c common.c
 OBJS	= ldapsearch.o ldapmodify.o ldapdelete.o ldapmodrdn.o \
-		ldappasswd.o ldapwhoami.o ldapcompare.o common.o
+		ldappasswd.o ldapwhoami.o ldapcompare.o \
+		ldapexop.o common.o
 
 LDAP_INCDIR= ../../include       
 LDAP_LIBDIR= ../../libraries
@@ -27,10 +29,10 @@ XLIBS =  $(LDAP_L)
 XXLIBS	= $(SECURITY_LIBS) $(LUTIL_LIBS)
 
 XSRCS	= ldsversion.c ldmversion.c lddversion.c ldrversion.c \
-	ldpversion.c ldwversion.c ldcversion.c
+	ldpversion.c ldwversion.c ldcversion.c ldeversion.c
 
 PROGRAMS = ldapsearch ldapmodify ldapdelete ldapmodrdn \
-	ldappasswd ldapwhoami ldapcompare
+	ldappasswd ldapwhoami ldapcompare ldapexop
 
 
 ldapsearch:	ldsversion.o
@@ -54,6 +56,9 @@ ldapwhoami:	ldwversion.o
 ldapcompare: ldcversion.o
 	$(LTLINK) -o $@ ldapcompare.o common.o ldcversion.o $(LIBS)
 
+ldapexop: ldeversion.o
+	$(LTLINK) -o $@ ldapexop.o common.o ldeversion.o $(LIBS)
+
 ldsversion.c: Makefile
 	@-$(RM) $@
 	$(MKVERSION) $(MKVOPTS) ldapsearch > $@
@@ -96,6 +101,12 @@ ldcversion.c: Makefile
 
 ldcversion.o: ldapcompare.o common.o $(XLIBS)
 
+ldeversion.c: Makefile
+	@-$(RM) $@
+	$(MKVERSION) $(MKVOPTS) ldapexop > $@
+
+ldeversion.o: ldapexop.o common.o $(XLIBS)
+
 install-local:	FORCE
 	-$(MKDIR) $(DESTDIR)$(bindir)
 	@(								\
diff --git a/clients/tools/ldapexop.c b/clients/tools/ldapexop.c
new file mode 100644
index 0000000000..9886d2d01e
--- /dev/null
+++ b/clients/tools/ldapexop.c
@@ -0,0 +1,308 @@
+/* ldapexop.c -- a tool for performing well-known extended operations */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005 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>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was originally developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software based, in part, on other client tools.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/stdlib.h>
+
+#include <ac/ctype.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+#include <ac/unistd.h>
+
+#include <ldap.h>
+#include "lutil.h"
+#include "lutil_ldap.h"
+#include "ldap_defaults.h"
+
+#include "common.h"
+
+
+void
+usage( void )
+{
+	fprintf( stderr, _("Issue LDAP extended operations\n\n"));
+	fprintf( stderr, _("usage: %s [options]\n"), prog);
+	tool_common_usage();
+	exit( EXIT_FAILURE );
+}
+
+
+const char options[] = ""
+	"d:D:e:h:H:InO:p:QR:U:vVw:WxX:y:Y:Z";
+
+int
+handle_private_option( int i )
+{
+	switch ( i ) {
+	default:
+		return 0;
+	}
+	return 1;
+}
+
+
+int
+main( int argc, char *argv[] )
+{
+	int		rc;
+	char		*user = NULL;
+
+	LDAP		*ld = NULL;
+
+	char		*matcheddn = NULL, *text = NULL, **refs = NULL;
+	int		id, code;
+	LDAPMessage	*res;
+
+	tool_init();
+	prog = lutil_progname( "ldapexop", argc, argv );
+
+	/* LDAPv3 only */
+	protocol = LDAP_VERSION3;
+
+	tool_args( argc, argv );
+
+	if ( argc - optind < 1 ) {
+		usage();
+	}
+
+	if ( pw_file || want_bindpw ) {
+		if ( pw_file ) {
+			rc = lutil_get_filed_password( pw_file, &passwd );
+			if( rc ) return EXIT_FAILURE;
+		} else {
+			passwd.bv_val = getpassphrase( _("Enter LDAP Password: ") );
+			passwd.bv_len = passwd.bv_val ? strlen( passwd.bv_val ) : 0;
+		}
+	}
+
+	ld = tool_conn_setup( 0, 0 );
+
+	tool_bind( ld );
+
+	argv += optind;
+	argc -= optind;
+
+	if ( strcasecmp( argv[ 0 ], "whoami" ) == 0 ) {
+		switch ( argc ) {
+		case 2:
+			user = argv[ 1 ];
+
+		case 1:
+			break;
+
+		default:
+			fprintf( stderr, "need [user]\n\n" );
+			usage();
+		}
+
+		if ( assertion || manageDSAit || noop ) {
+			fprintf( stderr, _("controls incompatible with WhoAmI exop\n\n") );
+			usage();
+		}
+
+		if ( authzid ) {
+			tool_server_controls( ld, NULL, 0 );
+		}
+
+		rc = ldap_whoami( ld, NULL, NULL, &id ); 
+		if ( rc != LDAP_SUCCESS ) {
+			tool_perror( "ldap_extended_operation", rc, NULL, NULL, NULL, NULL );
+			rc = EXIT_FAILURE;
+			goto skip;
+		}
+
+	} else if ( strcasecmp( argv[ 0 ], "cancel" ) == 0 ) {
+		int		cancelid;
+
+		switch ( argc ) {
+		case 2:
+			 if ( lutil_atoi( &cancelid, argv[ 1 ] ) != 0 || cancelid < 0 ) {
+				fprintf( stderr, "invalid cancelid=%s\n\n", argv[ 1 ] );
+				usage();
+			}
+			break;
+
+		default:
+			fprintf( stderr, "need cancelid\n\n" );
+			usage();
+		}
+
+		rc = ldap_cancel( ld, cancelid, NULL, NULL, &id );
+		if ( rc != LDAP_SUCCESS ) {
+			tool_perror( "ldap_cancel", rc, NULL, NULL, NULL, NULL );
+			rc = EXIT_FAILURE;
+			goto skip;
+		}
+
+	} else if ( strcasecmp( argv[ 0 ], "passwd" ) == 0 ) {
+		/* do we need this? */
+
+	} else if ( strcasecmp( argv[ 0 ], "refresh" ) == 0 ) {
+		int		ttl = 3600;
+		struct berval	dn;
+
+		switch ( argc ) {
+		case 3:
+			ttl = atoi( argv[ 2 ] );
+
+		case 2:
+			dn.bv_val = argv[ 1 ];
+			dn.bv_len = strlen( dn.bv_val );
+
+		case 1:
+			break;
+
+		default:
+			fprintf( stderr, _("need DN [ttl]\n\n") );
+			usage();
+		}
+		
+		if ( assertion || manageDSAit || noop || authzid ) {
+			tool_server_controls( ld, NULL, 0 );
+		}
+
+		rc = ldap_refresh( ld, &dn, ttl, NULL, NULL, &id ); 
+		if ( rc != LDAP_SUCCESS ) {
+			tool_perror( "ldap_extended_operation", rc, NULL, NULL, NULL, NULL );
+			rc = EXIT_FAILURE;
+			goto skip;
+		}
+
+	} else if ( tool_is_oid( argv[ 0 ] ) ) {
+		
+	} else {
+		fprintf( stderr, "unknown exop \"%s\"\n\n", argv[ 0 ] );
+		usage();
+	}
+
+	for ( ; ; ) {
+		struct timeval	tv;
+
+		if ( tool_check_abandon( ld, id ) ) {
+			return LDAP_CANCELLED;
+		}
+
+		tv.tv_sec = 0;
+		tv.tv_usec = 100000;
+
+		rc = ldap_result( ld, LDAP_RES_ANY, LDAP_MSG_ALL, &tv, &res );
+		if ( rc < 0 ) {
+			tool_perror( "ldap_result", rc, NULL, NULL, NULL, NULL );
+			rc = EXIT_FAILURE;
+			goto skip;
+		}
+
+		if ( rc != 0 ) {
+			break;
+		}
+	}
+
+	rc = ldap_parse_result( ld, res,
+		&code, &matcheddn, &text, &refs, NULL, 0 );
+	if ( rc == LDAP_SUCCESS ) {
+		rc = code;
+	}
+
+	if ( rc != LDAP_SUCCESS ) {
+		tool_perror( "ldap_parse_result", rc, NULL, matcheddn, text, refs );
+		rc = EXIT_FAILURE;
+		goto skip;
+	}
+
+	if ( strcasecmp( argv[ 0 ], "whoami" ) == 0 ) {
+		char		*retoid = NULL;
+		struct berval	*retdata = NULL;
+
+		rc = ldap_parse_extended_result( ld, res, &retoid, &retdata, 1 );
+
+		if ( rc != LDAP_SUCCESS ) {
+			tool_perror( "ldap_parse_extended_result", rc, NULL, NULL, NULL, NULL );
+			rc = EXIT_FAILURE;
+			goto skip;
+		}
+
+		if ( retdata != NULL ) {
+			if ( retdata->bv_len == 0 ) {
+				printf(_("anonymous\n") );
+			} else {
+				printf("%s\n", retdata->bv_val );
+			}
+		}
+
+		ber_memfree( retoid );
+		ber_bvfree( retdata );
+
+	} else if ( strcasecmp( argv[ 0 ], "cancel" ) == 0 ) {
+		/* no extended response; returns specific errors */
+		assert( 0 );
+
+	} else if ( strcasecmp( argv[ 0 ], "passwd" ) == 0 ) {
+
+	} else if ( strcasecmp( argv[ 0 ], "refresh" ) == 0 ) {
+		int	newttl;
+
+		rc = ldap_parse_refresh( ld, res, &newttl );
+
+		if ( rc != LDAP_SUCCESS ) {
+			tool_perror( "ldap_parse_refresh", rc, NULL, NULL, NULL, NULL );
+			rc = EXIT_FAILURE;
+			goto skip;
+		}
+
+		printf( "newttl=%d\n", newttl );
+
+	} else if ( tool_is_oid( argv[ 0 ] ) ) {
+		/* ... */
+	}
+
+	if( verbose || ( code != LDAP_SUCCESS ) || matcheddn || text || refs ) {
+		printf( _("Result: %s (%d)\n"), ldap_err2string( code ), code );
+
+		if( text && *text ) {
+			printf( _("Additional info: %s\n"), text );
+		}
+
+		if( matcheddn && *matcheddn ) {
+			printf( _("Matched DN: %s\n"), matcheddn );
+		}
+
+		if( refs ) {
+			int i;
+			for( i=0; refs[i]; i++ ) {
+				printf(_("Referral: %s\n"), refs[i] );
+			}
+		}
+	}
+
+	ber_memfree( text );
+	ber_memfree( matcheddn );
+	ber_memvfree( (void **) refs );
+
+skip:
+	/* disconnect from server */
+	tool_unbind( ld );
+	tool_destroy();
+
+	return code == LDAP_SUCCESS ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/configure.in b/configure.in
index d942521e2f..712ab422c9 100644
--- a/configure.in
+++ b/configure.in
@@ -339,6 +339,7 @@ OL_ARG_ENABLE(sql,[    --enable-sql	  enable sql backend],
 dnl ----------------------------------------------------------------
 dnl SLAPD Overlay Options
 Overlays="accesslog \
+	dds \
 	denyop \
 	dyngroup \
 	dynlist \
@@ -360,6 +361,8 @@ OL_ARG_ENABLE(overlays,[    --enable-overlays	  enable all available overlays],
 	--, [no yes mod])dnl
 OL_ARG_ENABLE(accesslog,[    --enable-accesslog	  In-Directory Access Logging overlay],
 	no, [no yes mod], ol_enable_overlays)
+OL_ARG_ENABLE(dds,[    --enable-dds  	  Dynamic Directory Services overlay],
+	no, [no yes mod], ol_enable_overlays)
 OL_ARG_ENABLE(denyop,[    --enable-denyop  	  Deny Operation overlay],
 	no, [no yes mod], ol_enable_overlays)
 OL_ARG_ENABLE(dyngroup,[    --enable-dyngroup	  Dynamic Group overlay],
@@ -614,6 +617,7 @@ BUILD_SHELL=no
 BUILD_SQL=no
 
 BUILD_ACCESSLOG=no
+BUILD_DDS=no
 BUILD_DENYOP=no
 BUILD_DYNGROUP=no
 BUILD_DYNLIST=no
@@ -2913,6 +2917,18 @@ if test "$ol_enable_accesslog" != no ; then
 	AC_DEFINE_UNQUOTED(SLAPD_OVER_ACCESSLOG,$MFLAG,[define for In-Directory Access Logging overlay])
 fi
 
+if test "$ol_enable_dds" != no ; then
+	BUILD_DDS=$ol_enable_dds
+	if test "$ol_enable_dds" = mod ; then
+		MFLAG=SLAPD_MOD_DYNAMIC
+		SLAPD_DYNAMIC_OVERLAYS="$SLAPD_DYNAMIC_OVERLAYS dds.la"
+	else
+		MFLAG=SLAPD_MOD_STATIC
+		SLAPD_STATIC_OVERLAYS="$SLAPD_STATIC_OVERLAYS dds.o"
+	fi
+	AC_DEFINE_UNQUOTED(SLAPD_OVER_DDS,$MFLAG,[define for Dynamic Directory Services overlay])
+fi
+
 if test "$ol_enable_denyop" != no ; then
 	BUILD_DENYOP=$ol_enable_denyop
 	if test "$ol_enable_denyop" = mod ; then
@@ -3130,6 +3146,7 @@ dnl backends
   AC_SUBST(BUILD_SQL)
 dnl overlays
   AC_SUBST(BUILD_ACCESSLOG)
+  AC_SUBST(BUILD_DDS)
   AC_SUBST(BUILD_DENYOP)
   AC_SUBST(BUILD_DYNGROUP)
   AC_SUBST(BUILD_DYNLIST)
diff --git a/doc/man/man5/slapo-dds.5 b/doc/man/man5/slapo-dds.5
new file mode 100644
index 0000000000..836a9032a5
--- /dev/null
+++ b/doc/man/man5/slapo-dds.5
@@ -0,0 +1,272 @@
+.TH SLAPO-DDS 5 "RELEASEDATE" "OpenLDAP LDVERSION"
+.\" Copyright 2005-2006 The OpenLDAP Foundation, All Rights Reserved.
+.\" Copying restrictions apply.  See the COPYRIGHT file.
+.\" $OpenLDAP$
+.SH NAME
+slapo-dds \- dds overlay
+.SH SYNOPSIS
+ETCDIR/slapd.conf
+.SH DESCRIPTION
+The
+.B dds
+overlay to
+.BR slapd (8)
+implements dynamic objects as per RFC 2589.
+The name 
+.B dds
+stands for
+Dynamic Dyrectory Services.
+It allows to define dynamic objects, characterized by the
+.B dynamicObject
+objectClass.
+Dynamic objects have a limited life, determined by a time-to-live (TTL)
+that can be refreshed by means of a specific 
+.B refresh
+extended operation.
+This operation allows to set the Client Refresh Period (CRP),
+namely the period between refreshes that is required to preserve the
+dynamic object from expiration.
+The expiration time is computed by adding the requested TTL to the 
+current time.
+When dynamic objects reach the end of their life without being
+further refreshed, they are automatically deleted; there is no guarantee
+of immediate deletion, but clients should not count over it.
+Dynamic objects can have subordinates, provided they also are dynamic
+objects.
+RFC 2589 does not specify what should the behavior of a dynamic 
+directory service be when a dynamic object with (dynamic) subordinates
+expires.
+In this implementation, the life of dynamic objects with subordinates
+is prolonged until all the dynamic subordinates expired.
+
+
+This 
+.BR slapd.conf (5)
+directive adds the 
+.B dds
+overlay to the current database:
+
+.TP
+.B overlay dds
+
+.LP
+The 
+.B dds
+overlay may be used with any backend that implements the 
+.BR add ,
+.BR modify ,
+.BR search ,
+and
+.BR delete
+operations.
+Since its use may result in many internal entry lookups, adds
+and deletes, it should be best used in conjunction with backends
+that have resonably good write performances.
+
+.LP 
+The config directives that are specific to the
+.B dds
+overlay are prefixed by
+.BR dds\- ,
+to avoid potential conflicts with directives specific to the underlying 
+database or to other stacked overlays.
+
+.TP
+.B dds\-max\-ttl <ttl>
+Specifies the max TTL value; this is the default TTL newly created
+dynamic objects receive, unless
+.B dds\-default\-ttl
+is set.
+When the client with a refresh exop requests a TTL higher than it,
+sizeLimitExceeded is returned.
+This value must be between 86400 (1 day, the default) and 31557600
+(1 year plus 6 hours, as per RFC 2589).
+
+.TP
+.B dds\-min\-ttl <ttl>
+Specifies the min TTL value; clients requesting a lower TTL by means
+of the refresh exop actually obtain this value as CRP.
+If set to 0 (the default), no lower limit is set.
+
+.TP
+.B dds\-default\-ttl <ttl>
+Specifies the default TTL value that newly created dynamic objects get.
+If set to 0 (the default), the
+.B dds\-max\-ttl
+is used.
+
+.TP
+.B dds\-interval <ttl>
+Specifies the interval between expiration checks; efaults to 1 hour.
+
+.TP
+.B dds\-tolerance <ttl>
+Specifies an extra time that is added to the timer that actually wakes up
+the thread that will delete an expired dynamic object.
+So the nominal life of the entry is that specified in the
+.B entryTtl
+attribute, but its life will actually be
+.BR " entryTtl + tolerance " .
+Note that there is no guarantee that the life of a dynamic object will be
+.I exactly
+the requested TTL; due to implementation details, it may be longer, which 
+is allowed by RFC 2589.
+By default, tolerance is 0.
+
+.TP
+.B dds\-max\-dynamicObjects <num>
+Specifies the maximum number of dynamic objects that can simultaneously exist
+within a naming context.
+This allows to limit the amount of resources (mostly in terms of runqueue size)
+that are used by dynamic objects.
+By default, no limit is set.
+
+.TP
+.B dds-state {TRUE|false}
+Specifies if the Dynamic Directory Services feature is enabled or not.
+By default it is; however, a proxy does not need to keep track of dynamic
+objects itself, it only needs to inform the frontend that support for
+dynamic objects is available.
+
+.SH ACCESS CONTROL
+The
+.B dds
+overlay restricts the refresh operation by requiring 
+.B manage
+access to the 
+.B entryTtl
+attribute (see
+.BR slapd.access (5)
+for details about the 
+.B manage
+access privilege).
+Since the
+.B entryTtl
+is an operational, NO-USER-MODIFICATION attribute, no direct write access
+to it is possible.
+So the 
+.B dds
+overlay turns refresh exops into an internal modification to the value 
+of the
+.B entryTtl
+attribute with the
+.B manageDIT
+control set.
+
+RFC 2589 recommends that anonymous clients should not be allowed to refresh
+a dynamic object.
+This cn be implemented by appropriately crafting access control to obtain 
+the desired effect.
+
+Example: restrict refresh to authenticated clients
+
+.RS
+.nf
+access to attrs=entryTtl
+	by users manage
+	by * read
+
+.fi
+.RE
+Example: restrict refresh to the creator of the dynamic object
+
+.RS
+.nf
+access to attrs=entryTtl
+	by dnattr=creatorsName manage
+	by * read
+
+.fi
+.RE
+Another suggested usage of dynamic objects is to implement dynamic meetings;
+in this case, all the participants to the meeting are allowed to refresh 
+the meeting object, but only the creator can delete it (otherwise it will
+be deleted when the TTL expires)
+
+Example: assuming \fIparticipant\fP is a valid DN-valued attribute, 
+allow users to start a meeting and to join it; restrict refresh 
+to the participants; restrict delete to the creator
+
+.RS
+.nf
+access to dn.base="cn=Meetings"
+		attrs=children
+	by users write
+
+access to dn.onelevel="cn=Meetings"
+		attrs=entry
+	by dnattr=creatorsName write
+	by * read
+
+access to dn.onelevel="cn=Meetings"
+		attrs=participant
+	by dnattr=creatorsName write
+	by users selfwrite
+	by * read
+
+access to dn.onelevel="cn=Meetings"
+		attrs=entryTtl
+	by dnattr=participant manage
+	by * read
+
+.fi
+.RE
+
+.SH REPLICATION
+This implementation of RFC 2589 provides a restricted interpretation of how
+dynamic objects replicate.  Only the master takes care of handling dynamic
+object expiration, while replicas simply see the dynamic object as a plain
+object.
+
+When using slurpd replication, one needs to explicitly exclude the 
+.B dynamicObject
+class and the
+.B entryTtl
+attribute.
+This implementation of RFC 2589 introduces a new operational attribute,
+.BR entryExpireTimestamp ,
+that contains the expiration timestamp.  This must be excluded from 
+replication as well.
+In
+.BR slapd.conf (5),
+add the following \fIexclusion list\fP to each
+.B replica 
+statement:
+
+.RS
+.nf
+replica ...
+	attrs!=@dynamicObject,entryTtl,entryExpireTimestamp
+.fi
+.RE
+
+When using syncrepl, the quick and dirty solution is to set 
+.B schemacheck=off
+and, optionally, exclude the operational attributes from replication, using
+
+.RS
+.nf
+syncrepl ...
+	exattrs=entryTtl,entryExpireTimestamp
+.fi
+.RE
+
+In any case the overlay must be either statically built in or run-time loaded 
+by the consumer, so that it is aware of the 
+.B entryExpireTimestamp
+operational attribute; however, it must not be configured in the shadow 
+database.
+Currently, there is no means to remove the 
+.B dynamicObject
+class from the entry; this may be seen as a feature, since it allows to see
+the dynamic properties of the object.
+
+.SH FILES
+.TP
+ETCDIR/slapd.conf
+default slapd configuration file
+.SH SEE ALSO
+.BR slapd.conf (5),
+.BR slapd (8).
+.SH AUTHOR
+Implemented by Pierangelo Masarati.
diff --git a/include/ldap.h b/include/ldap.h
index 79bc849421..e34b5576ca 100644
--- a/include/ldap.h
+++ b/include/ldap.h
@@ -323,6 +323,11 @@ typedef struct ldapcontrol {
 #define LDAP_EXOP_CANCEL		"1.3.6.1.1.8"				/* RFC 3909 */
 #define LDAP_EXOP_X_CANCEL		LDAP_EXOP_CANCEL
 
+#define	LDAP_EXOP_REFRESH		"1.3.6.1.4.1.1466.101.119.1"	/* RFC 2589 */
+#define	LDAP_TAG_EXOP_REFRESH_REQ_DN	((ber_tag_t) 0x80U)
+#define	LDAP_TAG_EXOP_REFRESH_REQ_TTL	((ber_tag_t) 0x81U)
+#define	LDAP_TAG_EXOP_REFRESH_RES_TTL	((ber_tag_t) 0x80U)
+
 /* various works in progress */
 #define LDAP_EXOP_WHO_AM_I		"1.3.6.1.4.1.4203.1.11.3"
 #define LDAP_EXOP_X_WHO_AM_I	LDAP_EXOP_WHO_AM_I
@@ -2137,5 +2142,34 @@ LDAP_F( const char * )
 ldap_passwordpolicy_err2txt LDAP_P(( LDAPPasswordPolicyError ));
 #endif /* LDAP_CONTROL_PASSWORDPOLICYREQUEST */
 
+/*
+ * LDAP Dynamic Directory Services Refresh RFC2589
+ *	in dds.c
+ */
+#define LDAP_API_FEATURE_REFRESH 1000
+
+LDAP_F( int )
+ldap_parse_refresh LDAP_P((
+	LDAP *ld,
+	LDAPMessage *res,
+	int *newttl ));
+
+LDAP_F( int )
+ldap_refresh LDAP_P(( LDAP *ld,
+	struct berval	*dn,
+	int ttl,
+	LDAPControl		**sctrls,
+	LDAPControl		**cctrls,
+	int				*msgidp ));
+
+LDAP_F( int )
+ldap_refresh_s LDAP_P((
+	LDAP *ld,
+	struct berval	*dn,
+	int ttl,
+	int *newttl,
+	LDAPControl **sctrls,
+	LDAPControl **cctrls ));
+
 LDAP_END_DECL
 #endif /* _LDAP_H */
diff --git a/libraries/libldap/Makefile.in b/libraries/libldap/Makefile.in
index 8166aaa90d..3e5028cf5b 100644
--- a/libraries/libldap/Makefile.in
+++ b/libraries/libldap/Makefile.in
@@ -26,7 +26,7 @@ SRCS	= bind.c open.c result.c error.c compare.c search.c \
 	request.c os-ip.c url.c sortctrl.c vlvctrl.c \
 	init.c options.c print.c string.c util-int.c schema.c \
 	charray.c tls.c os-local.c dnssrv.c utf-8.c utf-8-conv.c \
-	turn.c groupings.c txn.c ppolicy.c
+	turn.c groupings.c txn.c ppolicy.c dds.c
 
 OBJS	= bind.lo open.lo result.lo error.lo compare.lo search.lo \
 	controls.lo messages.lo references.lo extended.lo cyrus.lo \
@@ -37,7 +37,7 @@ OBJS	= bind.lo open.lo result.lo error.lo compare.lo search.lo \
 	request.lo os-ip.lo url.lo sortctrl.lo vlvctrl.lo \
 	init.lo options.lo print.lo string.lo util-int.lo schema.lo \
 	charray.lo tls.lo os-local.lo dnssrv.lo utf-8.lo utf-8-conv.lo \
-	turn.lo groupings.lo txn.lo ppolicy.lo
+	turn.lo groupings.lo txn.lo ppolicy.lo dds.lo
 
 LDAP_INCDIR= ../../include       
 LDAP_LIBDIR= ../../libraries
diff --git a/libraries/libldap/dds.c b/libraries/libldap/dds.c
new file mode 100644
index 0000000000..3ec8d65150
--- /dev/null
+++ b/libraries/libldap/dds.c
@@ -0,0 +1,158 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2006 The OpenLDAP Foundation.
+ * Portions Copyright 2005-2006 SysNet s.n.c.
+ * 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 developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/stdlib.h>
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "ldap-int.h"
+
+int
+ldap_parse_refresh( LDAP *ld, LDAPMessage *res, int *newttl )
+{
+	int		rc;
+	struct berval	*retdata = NULL;
+	ber_tag_t	tag;
+	BerElement	*ber;
+
+	assert( ld != NULL );
+	assert( LDAP_VALID( ld ) );
+	assert( res != NULL );
+	assert( newttl != NULL );
+
+	*newttl = 0;
+
+	rc = ldap_parse_extended_result( ld, res, NULL, &retdata, 0 );
+
+	if ( rc != LDAP_SUCCESS ) {
+		return rc;
+	}
+
+	if ( ld->ld_errno != LDAP_SUCCESS ) {
+		return ld->ld_errno;
+	}
+
+	if ( retdata == NULL ) {
+		rc = ld->ld_errno = LDAP_DECODING_ERROR;
+		return rc;
+	}
+
+	ber = ber_init( retdata );
+	if ( ber == NULL ) {
+		rc = ld->ld_errno = LDAP_NO_MEMORY;
+		goto done;
+	}
+
+	/* check the tag */
+	tag = ber_scanf( ber, "{i}", newttl );
+	ber_free( ber, 1 );
+
+	if ( tag != LDAP_TAG_EXOP_REFRESH_RES_TTL ) {
+		*newttl = 0;
+		rc = ld->ld_errno = LDAP_DECODING_ERROR;
+	}
+
+done:;
+	if ( retdata ) {
+		ber_bvfree( retdata );
+	}
+
+	return rc;
+}
+
+int
+ldap_refresh(
+	LDAP		*ld,
+	struct berval	*dn,
+	int		ttl,
+	LDAPControl	**sctrls,
+	LDAPControl	**cctrls,
+	int		*msgidp )
+{
+	struct berval	bv = { 0, NULL };
+        BerElement	*ber = NULL;
+	int		rc;
+
+	assert( ld != NULL );
+	assert( LDAP_VALID( ld ) );
+	assert( dn != NULL );
+	assert( msgidp != NULL );
+
+	*msgidp = -1;
+
+	ber = ber_alloc_t( LBER_USE_DER );
+
+	if ( ber == NULL ) {
+		ld->ld_errno = LDAP_NO_MEMORY;
+		return ld->ld_errno;
+	}
+
+	ber_printf( ber, "{tOtiN}",
+		LDAP_TAG_EXOP_REFRESH_REQ_DN, dn,
+		LDAP_TAG_EXOP_REFRESH_REQ_TTL, ttl );
+
+	rc = ber_flatten2( ber, &bv, 0 );
+
+	if ( rc < 0 ) {
+		ld->ld_errno = LDAP_ENCODING_ERROR;
+		return ld->ld_errno;
+	}
+
+	rc = ldap_extended_operation( ld, LDAP_EXOP_REFRESH, &bv,
+		sctrls, cctrls, msgidp );
+
+        ber_free( ber, 1 );
+
+	return rc;
+}
+
+int
+ldap_refresh_s(
+	LDAP		*ld,
+	struct berval	*dn,
+	int		ttl,
+	int		*newttl,
+	LDAPControl	**sctrls,
+	LDAPControl	**cctrls )
+{
+	int		rc;
+	int		msgid;
+	LDAPMessage	*res;
+
+	rc = ldap_refresh( ld, dn, ttl, sctrls, cctrls, &msgid );
+	if ( rc != LDAP_SUCCESS ) {
+		return rc;
+	}
+
+	if ( ldap_result( ld, msgid, LDAP_MSG_ALL, (struct timeval *)NULL, &res ) == -1 ) {
+		return ld->ld_errno;
+	}
+
+	rc = ldap_parse_refresh( ld, res, newttl );
+	if( rc != LDAP_SUCCESS ) {
+		ldap_msgfree( res );
+		return rc;
+	}
+
+	return( ldap_result2error( ld, res, 1 ) );
+}
+
diff --git a/libraries/libldap_r/Makefile.in b/libraries/libldap_r/Makefile.in
index 42929ae8cf..93f725a23a 100644
--- a/libraries/libldap_r/Makefile.in
+++ b/libraries/libldap_r/Makefile.in
@@ -28,7 +28,7 @@ XXSRCS    = apitest.c test.c \
 	request.c os-ip.c url.c sortctrl.c vlvctrl.c \
 	init.c options.c print.c string.c util-int.c schema.c \
 	charray.c tls.c os-local.c dnssrv.c utf-8.c utf-8-conv.c \
-	turn.c groupings.c txn.c ppolicy.c
+	turn.c groupings.c txn.c ppolicy.c dds.c
 SRCS	= threads.c rdwr.c tpool.c rq.c \
 	thr_posix.c thr_cthreads.c thr_thr.c thr_lwp.c thr_nt.c \
 	thr_pth.c thr_stub.c thr_debug.c
@@ -44,7 +44,7 @@ OBJS	= threads.lo rdwr.lo tpool.lo  rq.lo \
 	request.lo os-ip.lo url.lo sortctrl.lo vlvctrl.lo \
 	init.lo options.lo print.lo string.lo util-int.lo schema.lo \
 	charray.lo tls.lo os-local.lo dnssrv.lo utf-8.lo utf-8-conv.lo \
-	turn.lo groupings.lo txn.lo ppolicy.lo
+	turn.lo groupings.lo txn.lo ppolicy.lo dds.lo
 
 LDAP_INCDIR= ../../include       
 LDAP_LIBDIR= ../../libraries
diff --git a/servers/slapd/bconfig.c b/servers/slapd/bconfig.c
index 639eca1f66..9392830613 100644
--- a/servers/slapd/bconfig.c
+++ b/servers/slapd/bconfig.c
@@ -211,6 +211,7 @@ static OidRec OidMacros[] = {
  * OLcfgOv{Oc|At}:6			-> smbk5pwd
  * OLcfgOv{Oc|At}:7			-> distproc
  * OLcfgOv{Oc|At}:8			-> dynlist
+ * OLcfgOv{Oc|At}:9			-> dds
  */
 
 /* alphabetical ordering */
diff --git a/servers/slapd/controls.c b/servers/slapd/controls.c
index 08003bee1c..280b4f14b0 100644
--- a/servers/slapd/controls.c
+++ b/servers/slapd/controls.c
@@ -97,10 +97,12 @@ static int num_known_controls = 1;
 static char *proxy_authz_extops[] = {
 	LDAP_EXOP_MODIFY_PASSWD,
 	LDAP_EXOP_X_WHO_AM_I,
+	LDAP_EXOP_REFRESH,
 	NULL
 };
 
 static char *manageDSAit_extops[] = {
+	LDAP_EXOP_REFRESH,
 	NULL
 };
 
diff --git a/servers/slapd/overlays/Makefile.in b/servers/slapd/overlays/Makefile.in
index f30e4c25af..068b65fc05 100644
--- a/servers/slapd/overlays/Makefile.in
+++ b/servers/slapd/overlays/Makefile.in
@@ -15,6 +15,7 @@
 
 SRCS = overlays.c \
 	accesslog.c \
+	dds.c \
 	denyop.c \
 	dyngroup.c \
 	dynlist.c \
@@ -59,6 +60,9 @@ dynamic: $(PROGRAMS)
 accesslog.la : accesslog.lo
 	$(LTLINK_MOD) -module -o $@ accesslog.lo version.lo $(LINK_LIBS)
 
+dds.la : dds.lo
+	$(LTLINK_MOD) -module -o $@ dds.lo version.lo $(LINK_LIBS)
+
 denyop.la : denyop.lo
 	$(LTLINK_MOD) -module -o $@ denyop.lo version.lo $(LINK_LIBS)
 
diff --git a/servers/slapd/overlays/dds.c b/servers/slapd/overlays/dds.c
new file mode 100644
index 0000000000..7e0dabf83e
--- /dev/null
+++ b/servers/slapd/overlays/dds.c
@@ -0,0 +1,1992 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2006 The OpenLDAP Foundation.
+ * Portions Copyright 2005-2006 SysNet s.n.c.
+ * 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 Pierangelo Masarati for inclusion
+ * in OpenLDAP Software, sponsored by SysNet s.n.c.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_DDS
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "slap.h"
+#include "lutil.h"
+#include "ldap_rq.h"
+
+#include "config.h"
+
+#define	DDS_RF2589_MAX_TTL		(31557600)	/* 1 year + 6 hours */
+#define	DDS_RF2589_DEFAULT_TTL		(86400)		/* 1 day */
+#define	DDS_DEFAULT_INTERVAL		(3600)		/* 1 hour */
+
+typedef struct dds_info_t {
+	unsigned		di_flags;
+#define	DDS_FOFF		(0x1U)		/* is this really needed? */
+#define	DDS_SET(di, f)		( (di)->di_flags & (f) )
+
+#define DDS_OFF(di)		DDS_SET( (di), DDS_FOFF )
+
+	time_t			di_max_ttl;
+	time_t			di_min_ttl;
+	time_t			di_default_ttl;
+#define	DDS_DEFAULT_TTL(di)	\
+	( (di)->di_default_ttl ? (di)->di_default_ttl : (di)->di_max_ttl )
+
+	time_t			di_tolerance;
+
+	/* expire check interval and task */
+	time_t			di_interval;
+#define	DDS_INTERVAL(di)	\
+	( (di)->di_interval ? (di)->di_interval : DDS_DEFAULT_INTERVAL )
+	struct re_s		*di_expire_task;
+
+	/* allows to limit the maximum number of dynamic objects */
+	ldap_pvt_thread_mutex_t	di_mutex;
+	int			di_num_dynamicObjects;
+	int			di_max_dynamicObjects;
+
+	/* used to advertize the dynamicSubtrees in the root DSE,
+	 * and to select the database in the expiration task */
+	BerVarray		di_suffix;
+	BerVarray		di_nsuffix;
+} dds_info_t;
+
+static struct berval slap_EXOP_REFRESH = BER_BVC( LDAP_EXOP_REFRESH );
+static AttributeDescription	*ad_entryExpireTimestamp;
+
+/* list of expired DNs */
+typedef struct dds_expire_t {
+	struct berval		de_ndn;
+	struct dds_expire_t	*de_next;
+} dds_expire_t;
+
+typedef struct dds_cb_t {
+	dds_expire_t	*dc_ndnlist;
+} dds_cb_t;
+
+static int
+dds_expire_cb( Operation *op, SlapReply *rs )
+{
+	dds_cb_t	*dc = (dds_cb_t *)op->o_callback->sc_private;
+	dds_expire_t	*de;
+	int		rc;
+
+	switch ( rs->sr_type ) {
+	case REP_SEARCH:
+		/* alloc list and buffer for berval all in one */
+		de = op->o_tmpalloc( sizeof( dds_expire_t ) + rs->sr_entry->e_nname.bv_len + 1,
+			op->o_tmpmemctx );
+
+		de->de_next = dc->dc_ndnlist;
+		dc->dc_ndnlist = de;
+
+		de->de_ndn.bv_len = rs->sr_entry->e_nname.bv_len;
+		de->de_ndn.bv_val = (char *)&de[ 1 ];
+		AC_MEMCPY( de->de_ndn.bv_val, rs->sr_entry->e_nname.bv_val,
+			rs->sr_entry->e_nname.bv_len + 1 );
+		rc = 0;
+		break;
+
+	case REP_SEARCHREF:
+	case REP_RESULT:
+		rc = rs->sr_err;
+		break;
+
+	default:
+		assert( 0 );
+	}
+
+	return rc;
+}
+
+static int
+dds_expire( void *ctx, dds_info_t *di )
+{
+	Connection	conn = { 0 };
+	OperationBuffer opbuf;
+	Operation	*op;
+	slap_callback	sc = { 0 }, sc2 = { 0 };
+	dds_cb_t	dc = { 0 };
+	dds_expire_t	*de = NULL, **dep;
+	SlapReply	rs = { REP_RESULT };
+
+	time_t		expire;
+	char		tsbuf[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+	struct berval	ts;
+
+	int		ndeletes, ntotdeletes;
+
+	op = (Operation *)&opbuf;
+	connection_fake_init( &conn, op, ctx );
+
+	op->o_tag = LDAP_REQ_SEARCH;
+	memset( &op->oq_search, 0, sizeof( op->oq_search ) );
+
+	op->o_bd = select_backend( &di->di_nsuffix[ 0 ], 0, 0 );
+
+	op->o_req_dn = op->o_bd->be_suffix[ 0 ];
+	op->o_req_ndn = op->o_bd->be_nsuffix[ 0 ];
+
+	op->o_dn = op->o_bd->be_rootdn;
+	op->o_ndn = op->o_bd->be_rootndn;
+
+	op->ors_scope = LDAP_SCOPE_SUBTREE;
+	op->ors_tlimit = DDS_INTERVAL( di )/2 + 1;
+	op->ors_slimit = SLAP_NO_LIMIT;
+	op->ors_attrs = slap_anlist_no_attrs;
+
+	expire = slap_get_time() + di->di_tolerance;
+	ts.bv_val = tsbuf;
+	ts.bv_len = sizeof( tsbuf );
+	slap_timestamp( &expire, &ts );
+
+	op->ors_filterstr.bv_len = STRLENOF( "(&(objectClass=" ")(" "<=" "))" )
+		+ slap_schema.si_oc_dynamicObject->soc_cname.bv_len
+		+ ad_entryExpireTimestamp->ad_cname.bv_len
+		+ ts.bv_len;
+	op->ors_filterstr.bv_val = op->o_tmpalloc( op->ors_filterstr.bv_len + 1, op->o_tmpmemctx );
+	snprintf( op->ors_filterstr.bv_val, op->ors_filterstr.bv_len + 1,
+		"(&(objectClass=%s)(%s<=%s))",
+		slap_schema.si_oc_dynamicObject->soc_cname.bv_val,
+		ad_entryExpireTimestamp->ad_cname.bv_val, ts.bv_val );
+
+	op->ors_filter = str2filter_x( op, op->ors_filterstr.bv_val );
+	if ( op->ors_filter == NULL ) {
+		rs.sr_err = LDAP_OTHER;
+		goto done_search;
+	}
+	
+	op->o_callback = &sc;
+	sc.sc_response = dds_expire_cb;
+	sc.sc_private = &dc;
+
+	(void)op->o_bd->bd_info->bi_op_search( op, &rs );
+
+done_search:;
+	op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+	filter_free_x( op, op->ors_filter );
+
+	if ( rs.sr_err != LDAP_SUCCESS ) {
+		Log1( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+			"DDS expired objects lookup failed err=%d\n",
+			rs.sr_err );
+		goto done;
+	}
+
+	op->o_tag = LDAP_REQ_DELETE;
+	op->o_callback = &sc;
+	sc.sc_response = slap_replog_cb;
+	sc.sc_private = NULL;
+	sc.sc_next = &sc2;
+	sc2.sc_response = slap_null_cb;
+
+	for ( ntotdeletes = 0, ndeletes = 1; dc.dc_ndnlist != NULL  && ndeletes > 0; ) {
+		ndeletes = 0;
+
+		for ( dep = &dc.dc_ndnlist; *dep != NULL; ) {
+			de = *dep;
+
+			op->o_req_dn = de->de_ndn;
+			op->o_req_ndn = de->de_ndn;
+			(void)op->o_bd->bd_info->bi_op_delete( op, &rs );
+			switch ( rs.sr_err ) {
+			case LDAP_SUCCESS:
+				Log1( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO,
+					"DDS dn=\"%s\" expired.\n",
+					de->de_ndn.bv_val );
+				ndeletes++;
+				break;
+
+			case LDAP_NOT_ALLOWED_ON_NONLEAF:
+				Log1( LDAP_DEBUG_ANY, LDAP_LEVEL_NOTICE,
+					"DDS dn=\"%s\" is non-leaf; "
+					"deferring.\n",
+					de->de_ndn.bv_val );
+				dep = &de->de_next;
+				de = NULL;
+				break;
+	
+			default:
+				Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_NOTICE,
+					"DDS dn=\"%s\" err=%d; "
+					"deferring.\n",
+					de->de_ndn.bv_val, rs.sr_err );
+				break;
+			}
+	
+			if ( de != NULL ) {
+				*dep = de->de_next;
+				dep = &de->de_next;
+				op->o_tmpfree( de, op->o_tmpmemctx );
+			}
+		}
+
+		ntotdeletes += ndeletes;
+	}
+
+	rs.sr_err = LDAP_SUCCESS;
+
+	Log1( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO,
+		"DDS expired=%d\n", ntotdeletes );
+
+done:;
+	return rs.sr_err;
+}
+
+static void *
+dds_expire_fn( void *ctx, void *arg )
+{
+	struct re_s     *rtask = arg;
+	dds_info_t	*di = rtask->arg;
+
+	assert( di->di_expire_task == rtask );
+
+	(void)dds_expire( ctx, di );
+	
+	ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+	if ( ldap_pvt_runqueue_isrunning( &slapd_rq, rtask )) {
+		ldap_pvt_runqueue_stoptask( &slapd_rq, rtask );
+	}
+	ldap_pvt_runqueue_resched( &slapd_rq, rtask, 0 );
+	ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+	return NULL;
+}
+
+/* frees the callback */
+static int
+dds_freeit_cb( Operation *op, SlapReply *rs )
+{
+	op->o_tmpfree( op->o_callback, op->o_tmpmemctx );
+	op->o_callback = NULL;
+
+	return SLAP_CB_CONTINUE;
+}
+
+/* updates counter - installed on add/delete only if required */
+static int
+dds_counter_cb( Operation *op, SlapReply *rs )
+{
+	assert( rs->sr_type == REP_RESULT );
+
+	if ( rs->sr_err == LDAP_SUCCESS ) {
+		dds_info_t	*di = op->o_callback->sc_private;
+
+		ldap_pvt_thread_mutex_lock( &di->di_mutex );
+		switch ( op->o_tag ) {
+		case LDAP_REQ_DELETE:
+			assert( di->di_num_dynamicObjects > 0 );
+			di->di_num_dynamicObjects--;
+			break;
+
+		case LDAP_REQ_ADD:
+			assert( di->di_num_dynamicObjects < di->di_max_dynamicObjects );
+			di->di_num_dynamicObjects++;
+			break;
+
+		default:
+			assert( 0 );
+		}
+		ldap_pvt_thread_mutex_unlock( &di->di_mutex );
+	}
+
+	return dds_freeit_cb( op, rs );
+}
+
+static int
+dds_op_add( Operation *op, SlapReply *rs )
+{
+	slap_overinst	*on = (slap_overinst *)op->o_bd->bd_info;
+	dds_info_t	*di = on->on_bi.bi_private;
+	int		is_dynamicObject;
+
+	if ( DDS_OFF( di ) ) {
+		return SLAP_CB_CONTINUE;
+	}
+
+	is_dynamicObject = is_entry_dynamicObject( op->ora_e );
+
+	/* FIXME: do not allow this right now, pending clarification */
+	if ( is_dynamicObject ) {
+		rs->sr_err = LDAP_SUCCESS;
+
+		if ( is_entry_referral( op->ora_e ) ) {
+			rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION;
+			rs->sr_text = "a referral cannot be a dynamicObject";
+
+		} else if ( is_entry_alias( op->ora_e ) ) {
+			rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION;
+			rs->sr_text = "an alias cannot be a dynamicObject";
+		}
+
+		if ( rs->sr_err != LDAP_SUCCESS ) {
+			op->o_bd->bd_info = (BackendInfo *)on->on_info;
+			send_ldap_result( op, rs );
+			return rs->sr_err;
+		}
+	}
+
+	/* we don't allow dynamicObjects to have static subordinates */
+	if ( !dn_match( &op->o_req_ndn, &op->o_bd->be_nsuffix[ 0 ] ) ) {
+		struct berval	p_ndn;
+		Entry		*e = NULL;
+		int		rc;
+		BackendInfo	*bi = op->o_bd->bd_info;
+
+		dnParent( &op->o_req_ndn, &p_ndn );
+		op->o_bd->bd_info = (BackendInfo *)on->on_info;
+		rc = be_entry_get_rw( op, &p_ndn,
+			slap_schema.si_oc_dynamicObject, NULL, 0, &e );
+		if ( rc == LDAP_SUCCESS && e != NULL ) {
+			if ( !is_dynamicObject ) {
+#ifdef SLAP_ACL_HONOR_DISCLOSE
+				/* return referral only if "disclose"
+				 * is granted on the object */
+				if ( ! access_allowed( op, e,
+						slap_schema.si_ad_entry,
+						NULL, ACL_DISCLOSE, NULL ) )
+				{
+					rc = rs->sr_err = LDAP_NO_SUCH_OBJECT;
+					send_ldap_result( op, rs );
+
+				} else
+#endif /* SLAP_ACL_HONOR_DISCLOSE */
+				{
+					rc = rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+					send_ldap_error( op, rs, rc, "no static subordinate entries allowed for dynamicObject" );
+				}
+			}
+
+			be_entry_release_r( op, e );
+			if ( rc != LDAP_SUCCESS ) {
+				return rc;
+			}
+		}
+		op->o_bd->bd_info = bi;
+	}
+
+	/* handle dynamic object operational attr(s) */
+	if ( is_dynamicObject ) {
+		time_t		ttl, expire;
+		char		ttlbuf[] = "31557600";
+		char		tsbuf[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+		struct berval	bv;
+	
+		ldap_pvt_thread_mutex_lock( &di->di_mutex );
+		rs->sr_err = ( di->di_max_dynamicObjects && 
+			di->di_num_dynamicObjects >= di->di_max_dynamicObjects );
+		ldap_pvt_thread_mutex_unlock( &di->di_mutex );
+		if ( rs->sr_err ) {
+			op->o_bd->bd_info = (BackendInfo *)on->on_info;
+			send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+				"too many dynamicObjects in context" );
+			return rs->sr_err;
+		}
+
+		ttl = DDS_DEFAULT_TTL( di );
+
+		assert( ttl <= DDS_RF2589_MAX_TTL );
+
+		bv.bv_val = ttlbuf;
+		bv.bv_len = snprintf( ttlbuf, sizeof( ttlbuf ), "%ld", ttl );
+
+		/* FIXME: apparently, values in op->ora_e are malloc'ed
+		 * on the thread's slab; works fine by chance,
+		 * only because the attribute doesn't exist yet. */
+		assert( attr_find( op->ora_e->e_attrs, slap_schema.si_ad_entryTtl ) == NULL );
+		attr_merge_one( op->ora_e, slap_schema.si_ad_entryTtl, &bv, &bv );
+
+		expire = slap_get_time() + ttl;
+		bv.bv_val = tsbuf;
+		bv.bv_len = sizeof( tsbuf );
+		slap_timestamp( &expire, &bv );
+		assert( attr_find( op->ora_e->e_attrs, ad_entryExpireTimestamp ) == NULL );
+		attr_merge_one( op->ora_e, ad_entryExpireTimestamp, &bv, &bv );
+
+		/* if required, install counter callback */
+		if ( di->di_max_dynamicObjects > 0) {
+			slap_callback	*sc;
+
+			sc = op->o_tmpalloc( sizeof( slap_callback ), op->o_tmpmemctx );
+			sc->sc_cleanup = dds_freeit_cb;
+			sc->sc_response = dds_counter_cb;
+			sc->sc_private = di;
+			sc->sc_next = op->o_callback;
+
+			op->o_callback = sc;
+		}
+	}
+
+	return SLAP_CB_CONTINUE;
+}
+
+static int
+dds_op_delete( Operation *op, SlapReply *rs )
+{
+	slap_overinst	*on = (slap_overinst *)op->o_bd->bd_info;
+	dds_info_t	*di = on->on_bi.bi_private;
+
+	/* if required, install counter callback */
+	if ( !DDS_OFF( di ) && di->di_max_dynamicObjects > 0 ) {
+		Entry		*e = NULL;
+		BackendInfo	*bi = op->o_bd->bd_info;
+
+		op->o_bd->bd_info = (BackendInfo *)on->on_info;
+		rs->sr_err = be_entry_get_rw( op, &op->o_req_ndn,
+			slap_schema.si_oc_dynamicObject, NULL, 0, &e );
+
+		/* FIXME: couldn't the entry be added before deletion? */
+		if ( rs->sr_err == LDAP_SUCCESS && e != NULL ) {
+			slap_callback	*sc;
+	
+			be_entry_release_r( op, e );
+			e = NULL;
+	
+			sc = op->o_tmpalloc( sizeof( slap_callback ), op->o_tmpmemctx );
+			sc->sc_cleanup = dds_freeit_cb;
+			sc->sc_response = dds_counter_cb;
+			sc->sc_private = di;
+			sc->sc_next = op->o_callback;
+	
+			op->o_callback = sc;
+		}
+		op->o_bd->bd_info = bi;
+	}
+
+	return SLAP_CB_CONTINUE;
+}
+
+static int
+dds_op_modify( Operation *op, SlapReply *rs )
+{
+	slap_overinst	*on = (slap_overinst *)op->o_bd->bd_info;
+	dds_info_t	*di = (dds_info_t *)on->on_bi.bi_private;
+	Modifications	*mod;
+	Entry		*e = NULL;
+	BackendInfo	*bi = op->o_bd->bd_info;
+	int		was_dynamicObject = 0,
+			is_dynamicObject = 0;
+	struct berval	bv_entryTtl = BER_BVNULL;
+	time_t		entryTtl = 0;
+	char		textbuf[ SLAP_TEXT_BUFLEN ];
+
+	if ( DDS_OFF( di ) ) {
+		return SLAP_CB_CONTINUE;
+	}
+
+	/* bv_entryTtl stores the string representation of the entryTtl
+	 * across modifies for consistency checks of the final value;
+	 * the bv_val points to a static buffer; the bv_len is zero when
+	 * the attribute is deleted.
+	 * entryTtl stores the integer representation of the entryTtl;
+	 * its value is -1 when the attribute is deleted; it is 0 only
+	 * if no modifications of the entryTtl occurred, as an entryTtl
+	 * of 0 is invalid. */
+	bv_entryTtl.bv_val = textbuf;
+
+	op->o_bd->bd_info = (BackendInfo *)on->on_info;
+	rs->sr_err = be_entry_get_rw( op, &op->o_req_ndn,
+		slap_schema.si_oc_dynamicObject, slap_schema.si_ad_entryTtl, 0, &e );
+	if ( rs->sr_err == LDAP_SUCCESS && e != NULL ) {
+		Attribute	*a = attr_find( e->e_attrs, slap_schema.si_ad_entryTtl );
+
+		/* the value of the entryTtl is saved for later checks */
+		if ( a != NULL ) {
+			unsigned long	ttl;
+			int		rc;
+
+			bv_entryTtl.bv_len = a->a_nvals[ 0 ].bv_len;
+			AC_MEMCPY( bv_entryTtl.bv_val, a->a_nvals[ 0 ].bv_val, bv_entryTtl.bv_len );
+			bv_entryTtl.bv_val[ bv_entryTtl.bv_len ] = '\0';
+			rc = lutil_atoul( &ttl, bv_entryTtl.bv_val );
+			assert( rc == 0 );
+			entryTtl = (time_t)ttl;
+		}
+
+		be_entry_release_r( op, e );
+		e = NULL;
+		was_dynamicObject = is_dynamicObject = 1;
+	}
+	op->o_bd->bd_info = bi;
+
+	rs->sr_err = LDAP_SUCCESS;
+	for ( mod = op->orm_modlist; mod; mod = mod->sml_next ) {
+		if ( mod->sml_desc == slap_schema.si_ad_objectClass ) {
+			int		i;
+			ObjectClass	*oc;
+
+			switch ( mod->sml_op ) {
+			case LDAP_MOD_DELETE:
+				if ( mod->sml_values == NULL ) {
+					is_dynamicObject = 0;
+					break;
+				}
+	
+				for ( i = 0; !BER_BVISNULL( &mod->sml_values[ i ] ); i++ ) {
+					oc = oc_bvfind( &mod->sml_values[ i ] );
+					if ( oc == slap_schema.si_oc_dynamicObject ) {
+						is_dynamicObject = 0;
+						break;
+					}
+				}
+	
+				break;
+	
+			case LDAP_MOD_REPLACE:
+				if ( mod->sml_values == NULL ) {
+					is_dynamicObject = 0;
+					break;
+				}
+				/* fallthru */
+	
+			case LDAP_MOD_ADD:
+				for ( i = 0; !BER_BVISNULL( &mod->sml_values[ i ] ); i++ ) {
+					oc = oc_bvfind( &mod->sml_values[ i ] );
+					if ( oc == slap_schema.si_oc_dynamicObject ) {
+						is_dynamicObject = 1;
+						break;
+					}
+				}
+				break;
+			}
+
+		} else if ( mod->sml_desc == slap_schema.si_ad_entryTtl ) {
+			unsigned long	ttl;
+			int		rc;
+
+			switch ( mod->sml_op ) {
+			case LDAP_MOD_DELETE:
+				if ( mod->sml_values != NULL ) {
+					if ( BER_BVISEMPTY( &bv_entryTtl ) 
+						|| !bvmatch( &bv_entryTtl, &mod->sml_values[ 0 ] ) )
+					{
+#ifdef SLAP_ACL_HONOR_DISCLOSE
+						rs->sr_err = backend_attribute( op, NULL, &op->o_req_ndn, 
+							slap_schema.si_ad_entry, NULL, ACL_DISCLOSE );
+						if ( rs->sr_err == LDAP_INSUFFICIENT_ACCESS ) {
+							rs->sr_err = LDAP_NO_SUCH_OBJECT;
+
+						} else
+#endif /* SLAP_ACL_HONOR_DISCLOSE */
+						{
+							rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE;
+						}
+						goto done;
+					}
+				}
+				bv_entryTtl.bv_len = 0;
+				entryTtl = -1;
+				break;
+
+			case LDAP_MOD_REPLACE:
+				bv_entryTtl.bv_len = 0;
+				entryTtl = -1;
+				/* fallthru */
+
+			case SLAP_MOD_SOFTADD: /* FIXME? */
+			case LDAP_MOD_ADD:
+				assert( mod->sml_values != NULL );
+				assert( BER_BVISNULL( &mod->sml_values[ 1 ] ) );
+
+				if ( !BER_BVISEMPTY( &bv_entryTtl ) ) {
+#ifdef SLAP_ACL_HONOR_DISCLOSE
+					rs->sr_err = backend_attribute( op, NULL, &op->o_req_ndn, 
+						slap_schema.si_ad_entry, NULL, ACL_DISCLOSE );
+					if ( rs->sr_err == LDAP_INSUFFICIENT_ACCESS ) {
+						rs->sr_err = LDAP_NO_SUCH_OBJECT;
+
+					} else
+#endif /* SLAP_ACL_HONOR_DISCLOSE */
+					{
+						rs->sr_text = "attribute 'entryTtl' cannot have multiple values";
+						rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+					}
+					goto done;
+				}
+
+				rc = lutil_atoul( &ttl, mod->sml_values[ 0 ].bv_val );
+				assert( rc == 0 );
+				if ( ttl > DDS_RF2589_MAX_TTL ) {
+					rs->sr_err = LDAP_PROTOCOL_ERROR;
+					rs->sr_text = "invalid time-to-live for dynamicObject";
+					goto done;
+				}
+
+				if ( ttl <= 0 || ttl > di->di_max_ttl ) {
+					/* FIXME: I don't understand if this has to be an error,
+					 * or an indication that the requested Ttl has been
+					 * shortened to di->di_max_ttl >= 1 day */
+					rs->sr_err = LDAP_SIZELIMIT_EXCEEDED;
+					rs->sr_text = "time-to-live for dynamicObject exceeds administrative limit";
+					goto done;
+				}
+
+				entryTtl = (time_t)ttl;
+				bv_entryTtl.bv_len = mod->sml_values[ 0 ].bv_len;
+				AC_MEMCPY( bv_entryTtl.bv_val, mod->sml_values[ 0 ].bv_val, bv_entryTtl.bv_len );
+				bv_entryTtl.bv_val[ bv_entryTtl.bv_len ] = '\0';
+				break;
+
+			case LDAP_MOD_INCREMENT:
+				if ( BER_BVISEMPTY( &bv_entryTtl ) ) {
+#ifdef SLAP_ACL_HONOR_DISCLOSE
+					rs->sr_err = backend_attribute( op, NULL, &op->o_req_ndn, 
+						slap_schema.si_ad_entry, NULL, ACL_DISCLOSE );
+					if ( rs->sr_err == LDAP_INSUFFICIENT_ACCESS ) {
+						rs->sr_err = LDAP_NO_SUCH_OBJECT;
+
+					} else
+#endif /* SLAP_ACL_HONOR_DISCLOSE */
+					{
+						rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE;
+						rs->sr_text = "modify/increment: entryTtl: no such attribute";
+					}
+					goto done;
+				}
+
+				entryTtl++;
+				if ( entryTtl > DDS_RF2589_MAX_TTL ) {
+					rs->sr_err = LDAP_PROTOCOL_ERROR;
+					rs->sr_text = "invalid time-to-live for dynamicObject";
+
+				} else if ( entryTtl <= 0 || entryTtl > di->di_max_ttl ) {
+					/* FIXME: I don't understand if this has to be an error,
+					 * or an indication that the requested Ttl has been
+					 * shortened to di->di_max_ttl >= 1 day */
+					rs->sr_err = LDAP_SIZELIMIT_EXCEEDED;
+					rs->sr_text = "time-to-live for dynamicObject exceeds administrative limit";
+				}
+
+				if ( rs->sr_err != LDAP_SUCCESS ) {
+#ifdef SLAP_ACL_HONOR_DISCLOSE
+					rc = backend_attribute( op, NULL, &op->o_req_ndn, 
+						slap_schema.si_ad_entry, NULL, ACL_DISCLOSE );
+					if ( rc == LDAP_INSUFFICIENT_ACCESS ) {
+						rs->sr_text = NULL;
+						rs->sr_err = LDAP_NO_SUCH_OBJECT;
+
+					}
+#endif /* SLAP_ACL_HONOR_DISCLOSE */
+					goto done;
+				}
+
+				bv_entryTtl.bv_len = snprintf( textbuf, sizeof( textbuf ), "%ld", entryTtl );
+				break;
+
+			default:
+				assert( 0 );
+				break;
+			}
+
+		} else if ( mod->sml_desc == ad_entryExpireTimestamp ) {
+			/* should have been trapped earlier */
+			assert( mod->sml_flags & SLAP_MOD_INTERNAL );
+		}
+	}
+
+done:;
+	if ( rs->sr_err == LDAP_SUCCESS ) {
+		int	rc;
+
+		/* FIXME: this could be allowed when manageDIT is used...
+		 * in that case:
+		 *
+		 * TODO
+		 * 
+		 *	static => dynamic:
+		 *		entryTtl must be provided; add
+		 *		entryExpireTimestamp accordingly
+		 *
+		 *	dynamic => static:
+		 *		entryTtl must be removed; remove
+		 *		entryTimestamp accordingly
+		 *
+		 * ... but we need to make sure that there are no subordinate 
+		 * issues...
+		 */
+		rc = is_dynamicObject - was_dynamicObject;
+		if ( rc ) {
+#if 0 /* fix subordinate issues first */
+			if ( get_manageDIT( op ) ) {
+				switch ( rc ) {
+				case -1:
+					/* need to delete entryTtl to have a consistent entry */
+					if ( entryTtl != -1 ) {
+						rs->sr_text = "objectClass modification from dynamicObject to static entry requires entryTtl deletion";
+						rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION;
+					}
+					break;
+
+				case 1:
+					/* need to add entryTtl to have a consistent entry */
+					if ( entryTtl <= 0 ) {
+						rs->sr_text = "objectClass modification from static entry to dynamicObject requires entryTtl addition";
+						rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION;
+					}
+					break;
+				}
+
+			} else
+#endif
+			{
+				switch ( rc ) {
+				case -1:
+					rs->sr_text = "objectClass modification cannot turn dynamicObject into static entry";
+					break;
+
+				case 1:
+					rs->sr_text = "objectClass modification cannot turn static entry into dynamicObject";
+					break;
+				}
+				rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION;
+			}
+
+#ifdef SLAP_ACL_HONOR_DISCLOSE
+			if ( rc != LDAP_SUCCESS ) {
+				rc = backend_attribute( op, NULL, &op->o_req_ndn, 
+					slap_schema.si_ad_entry, NULL, ACL_DISCLOSE );
+				if ( rc == LDAP_INSUFFICIENT_ACCESS ) {
+					rs->sr_text = NULL;
+					rs->sr_err = LDAP_NO_SUCH_OBJECT;
+				}
+			}
+#endif /* SLAP_ACL_HONOR_DISCLOSE */
+		}
+	}
+
+	if ( rs->sr_err == LDAP_SUCCESS && entryTtl != 0 ) {
+		Modifications	*tmpmod = NULL, **modp;
+
+		for ( modp = &op->orm_modlist; *modp; modp = &(*modp)->sml_next )
+			;
+	
+		tmpmod = ch_calloc( 1, sizeof( Modifications ) );
+		tmpmod->sml_flags = SLAP_MOD_INTERNAL;
+		tmpmod->sml_type = ad_entryExpireTimestamp->ad_cname;
+		tmpmod->sml_desc = ad_entryExpireTimestamp;
+
+		*modp = tmpmod;
+
+		if ( entryTtl == -1 ) {
+			/* delete entryExpireTimestamp */
+			tmpmod->sml_op = LDAP_MOD_DELETE;
+
+		} else {
+			time_t		expire;
+			char		tsbuf[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+			struct berval	bv;
+
+			/* keep entryExpireTimestamp consistent
+			 * with entryTtl */
+			expire = slap_get_time() + entryTtl;
+			bv.bv_val = tsbuf;
+			bv.bv_len = sizeof( tsbuf );
+			slap_timestamp( &expire, &bv );
+
+			tmpmod->sml_op = LDAP_MOD_REPLACE;
+			value_add_one( &tmpmod->sml_values, &bv );
+		}
+	}
+
+	if ( rs->sr_err ) {
+		op->o_bd->bd_info = (BackendInfo *)on->on_info;
+		send_ldap_result( op, rs );
+		return rs->sr_err;
+	}
+
+	return SLAP_CB_CONTINUE;
+}
+
+static int
+dds_op_rename( Operation *op, SlapReply *rs )
+{
+	slap_overinst	*on = (slap_overinst *)op->o_bd->bd_info;
+	dds_info_t	*di = on->on_bi.bi_private;
+
+	if ( DDS_OFF( di ) ) {
+		return SLAP_CB_CONTINUE;
+	}
+
+	/* we don't allow dynamicObjects to have static subordinates */
+	if ( op->orr_nnewSup != NULL ) {
+		Entry		*e = NULL;
+		BackendInfo	*bi = op->o_bd->bd_info;
+		int		is_dynamicObject = 0,
+				rc;
+
+		rs->sr_err = LDAP_SUCCESS;
+
+		op->o_bd->bd_info = (BackendInfo *)on->on_info;
+		rc = be_entry_get_rw( op, &op->o_req_ndn,
+			slap_schema.si_oc_dynamicObject, NULL, 0, &e );
+		if ( rc == LDAP_SUCCESS && e != NULL ) {
+			be_entry_release_r( op, e );
+			e = NULL;
+			is_dynamicObject = 1;
+		}
+
+		rc = be_entry_get_rw( op, op->orr_nnewSup,
+			slap_schema.si_oc_dynamicObject, NULL, 0, &e );
+		if ( rc == LDAP_SUCCESS && e != NULL ) {
+			if ( !is_dynamicObject ) {
+#ifdef SLAP_ACL_HONOR_DISCLOSE
+				/* return referral only if "disclose"
+				 * is granted on the object */
+				if ( ! access_allowed( op, e,
+						slap_schema.si_ad_entry,
+						NULL, ACL_DISCLOSE, NULL ) )
+				{
+					rs->sr_err = LDAP_NO_SUCH_OBJECT;
+					send_ldap_result( op, rs );
+
+				} else
+#endif /* SLAP_ACL_HONOR_DISCLOSE */
+				{
+					send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION,
+						"static entry cannot have dynamicObject as newSuperior" );
+				}
+			}
+			be_entry_release_r( op, e );
+		}
+		op->o_bd->bd_info = bi;
+		if ( rs->sr_err != LDAP_SUCCESS ) {
+			return rs->sr_err;
+		}
+	}
+
+	return SLAP_CB_CONTINUE;
+}
+
+static int
+slap_parse_refresh(
+	struct berval	*in,
+	struct berval	*ndn,
+	time_t		*ttl,
+	const char	**text,
+	void		*ctx )
+{
+	int			rc = LDAP_SUCCESS;
+	ber_tag_t		tag;
+	ber_len_t		len = -1;
+	BerElementBuffer	berbuf;
+	BerElement		*ber = (BerElement *)&berbuf;
+	struct berval		reqdata = BER_BVNULL;
+	int			tmp;
+
+	*text = NULL;
+
+	if ( ndn ) {
+		BER_BVZERO( ndn );
+	}
+
+	if ( in == NULL || in->bv_len == 0 ) {
+		*text = "empty request data field in refresh exop";
+		return LDAP_PROTOCOL_ERROR;
+	}
+
+	ber_dupbv_x( &reqdata, in, ctx );
+
+	/* ber_init2 uses reqdata directly, doesn't allocate new buffers */
+	ber_init2( ber, &reqdata, 0 );
+
+	tag = ber_scanf( ber, "{" /*}*/ );
+
+	if ( tag == LBER_ERROR ) {
+		Log0( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR,
+			"slap_parse_refresh: decoding error.\n" );
+		goto decoding_error;
+	}
+
+	tag = ber_peek_tag( ber, &len );
+	if ( tag != LDAP_TAG_EXOP_REFRESH_REQ_DN ) {
+		Log0( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR,
+			"slap_parse_refresh: decoding error.\n" );
+		goto decoding_error;
+	}
+
+	if ( ndn ) {
+		struct berval	dn;
+
+		tag = ber_scanf( ber, "m", &dn );
+		if ( tag == LBER_ERROR ) {
+			Log0( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR,
+				"slap_parse_refresh: DN parse failed.\n" );
+			goto decoding_error;
+		}
+
+		rc = dnNormalize( 0, NULL, NULL, &dn, ndn, ctx );
+		if ( rc != LDAP_SUCCESS ) {
+			*text = "invalid DN in refresh exop request data";
+			goto done;
+		}
+
+	} else {
+		tag = ber_scanf( ber, "x" /* "m" */ );
+		if ( tag == LBER_DEFAULT ) {
+			goto decoding_error;
+		}
+	}
+
+	tag = ber_peek_tag( ber, &len );
+
+	if ( tag != LDAP_TAG_EXOP_REFRESH_REQ_TTL ) {
+		Log0( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR,
+			"slap_parse_refresh: decoding error.\n" );
+		goto decoding_error;
+	}
+
+	tag = ber_scanf( ber, "i", &tmp );
+	if ( tag == LBER_ERROR ) {
+		Log0( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR,
+			"slap_parse_refresh: TTL parse failed.\n" );
+		goto decoding_error;
+	}
+
+	if ( ttl ) {
+		*ttl = tmp;
+	}
+
+	tag = ber_peek_tag( ber, &len );
+
+	if ( len != 0 ) {
+decoding_error:;
+		Log1( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR,
+			"slap_parse_refresh: decoding error, len=%ld\n",
+			(long)len );
+		rc = LDAP_PROTOCOL_ERROR;
+		*text = "data decoding error";
+
+done:;
+		if ( ndn && !BER_BVISNULL( ndn ) ) {
+			slap_sl_free( ndn->bv_val, ctx );
+			BER_BVZERO( ndn );
+		}
+	}
+
+	if ( !BER_BVISNULL( &reqdata ) ) {
+		ber_memfree_x( reqdata.bv_val, ctx );
+	}
+
+	return rc;
+}
+
+static int
+dds_op_extended( Operation *op, SlapReply *rs )
+{
+	slap_overinst	*on = (slap_overinst *)op->o_bd->bd_info;
+	dds_info_t	*di = on->on_bi.bi_private;
+
+	if ( DDS_OFF( di ) ) {
+		return SLAP_CB_CONTINUE;
+	}
+
+	if ( bvmatch( &op->ore_reqoid, &slap_EXOP_REFRESH ) ) {
+		Entry		*e = NULL;
+		time_t		ttl;
+		BackendDB	db = *op->o_bd;
+		SlapReply	rs2 = { REP_RESULT };
+		Operation	op2 = *op;
+		slap_callback	sc = { 0 };
+		slap_callback	sc2 = { 0 };
+		Modifications	ttlmod = { { 0 } };
+		struct berval	ttlvalues[ 2 ];
+		char		ttlbuf[] = "31557600";
+
+		rs->sr_err = slap_parse_refresh( op->ore_reqdata, NULL, &ttl,
+			&rs->sr_text, NULL );
+		assert( rs->sr_err == LDAP_SUCCESS );
+
+		if ( ttl <= 0 || ttl > DDS_RF2589_MAX_TTL ) {
+			rs->sr_err = LDAP_PROTOCOL_ERROR;
+			rs->sr_text = "invalid time-to-live for dynamicObject";
+			return rs->sr_err;
+		}
+
+		if ( ttl > di->di_max_ttl ) {
+			/* FIXME: I don't understand if this has to be an error,
+			 * or an indication that the requested Ttl has been
+			 * shortened to di->di_max_ttl >= 1 day */
+			rs->sr_err = LDAP_SIZELIMIT_EXCEEDED;
+			rs->sr_text = "time-to-live for dynamicObject exceeds limit";
+			return rs->sr_err;
+		}
+
+		if ( di->di_min_ttl && ttl < di->di_min_ttl ) {
+			ttl = di->di_min_ttl;
+		}
+
+#ifndef SLAPD_MULTIMASTER
+		/* This does not apply to multi-master case */
+		if ( !( !SLAP_SHADOW( op->o_bd ) || be_isupdate( op ) ) ) {
+			/* we SHOULD return a referral in this case */
+			BerVarray defref = op->o_bd->be_update_refs
+				? op->o_bd->be_update_refs : default_referral; 
+
+			if ( defref != NULL ) {
+				rs->sr_ref = referral_rewrite( op->o_bd->be_update_refs,
+					NULL, NULL, LDAP_SCOPE_DEFAULT );
+				if ( rs->sr_ref ) {
+					rs->sr_flags |= REP_REF_MUSTBEFREED;
+				} else {
+					rs->sr_ref = defref;
+				}
+				rs->sr_err = LDAP_REFERRAL;
+
+			} else {
+				rs->sr_text = "shadow context; no update referral";
+				rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+			}
+
+			return rs->sr_err;
+		}
+#endif /* !SLAPD_MULTIMASTER */
+
+		assert( !BER_BVISNULL( &op->o_req_ndn ) );
+
+
+
+		/* check if exists but not dynamicObject */
+		op->o_bd->bd_info = (BackendInfo *)on->on_info;
+		rs->sr_err = be_entry_get_rw( op, &op->o_req_ndn,
+			slap_schema.si_oc_dynamicObject, NULL, 0, &e );
+		if ( rs->sr_err != LDAP_SUCCESS ) {
+			rs->sr_err = be_entry_get_rw( op, &op->o_req_ndn,
+				NULL, NULL, 0, &e );
+			if ( rs->sr_err == LDAP_SUCCESS && e != NULL ) {
+#ifdef SLAP_ACL_HONOR_DISCLOSE
+				/* return referral only if "disclose"
+				 * is granted on the object */
+				if ( ! access_allowed( op, e,
+						slap_schema.si_ad_entry,
+						NULL, ACL_DISCLOSE, NULL ) )
+				{
+					rs->sr_err = LDAP_NO_SUCH_OBJECT;
+
+				} else
+#endif /* SLAP_ACL_HONOR_DISCLOSE */
+				{
+					rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION;
+					rs->sr_text = "refresh operation only applies to dynamic objects";
+				}
+				be_entry_release_r( op, e );
+
+			} else {
+				rs->sr_err = LDAP_NO_SUCH_OBJECT;
+			}
+			return rs->sr_err;
+
+		} else if ( e != NULL ) {
+			be_entry_release_r( op, e );
+		}
+
+		/* we require manage privileges on the entryTtl,
+		 * and fake a manageDIT control */
+		op2.o_tag = LDAP_REQ_MODIFY;
+		op2.o_bd = &db;
+		db.bd_info = (BackendInfo *)on->on_info;
+		op2.o_callback = &sc;
+		sc.sc_response = slap_replog_cb;
+		sc.sc_next = &sc2;
+		sc2.sc_response = slap_null_cb;
+		op2.o_managedit = SLAP_CONTROL_CRITICAL;
+		op2.orm_modlist = &ttlmod;
+
+		ttlmod.sml_op = LDAP_MOD_REPLACE;
+		ttlmod.sml_flags = SLAP_MOD_MANAGING;
+		ttlmod.sml_desc = slap_schema.si_ad_entryTtl;
+		ttlmod.sml_values = ttlvalues;
+		ttlvalues[ 0 ].bv_val = ttlbuf;
+		ttlvalues[ 0 ].bv_len = snprintf( ttlbuf, sizeof( ttlbuf ), "%ld", ttl );
+		BER_BVZERO( &ttlvalues[ 1 ] );
+
+		/* the entryExpireTimestamp is added by modify */
+		rs->sr_err = op2.o_bd->be_modify( &op2, &rs2 );
+
+		if ( ttlmod.sml_next != NULL ) {
+			slap_mods_free( ttlmod.sml_next, 1 );
+		}
+
+		if ( rs->sr_err == LDAP_SUCCESS ) {
+			int			rc;
+			BerElementBuffer	berbuf;
+			BerElement		*ber = (BerElement *)&berbuf;
+
+			if ( rs->sr_err == LDAP_SUCCESS ) {
+				ber_init_w_nullc( ber, LBER_USE_DER );
+
+				rc = ber_printf( ber, "{tiN}", LDAP_TAG_EXOP_REFRESH_RES_TTL, (int)ttl );
+
+				if ( rc < 0 ) {
+					rs->sr_err = LDAP_OTHER;
+					rs->sr_text = "internal error";
+
+				} else {
+					(void)ber_flatten( ber, &rs->sr_rspdata );
+					rs->sr_rspoid = ch_strdup( slap_EXOP_REFRESH.bv_val );
+
+					Log3( LDAP_DEBUG_TRACE, LDAP_LEVEL_INFO,
+						"%s REFRESH dn=\"%s\" TTL=%ld\n",
+						op->o_log_prefix, op->o_req_ndn.bv_val, ttl );
+				}
+
+				ber_free_buf( ber );
+			}
+		}
+
+		return rs->sr_err;
+	}
+
+	return SLAP_CB_CONTINUE;
+}
+
+enum {
+	DDS_STATE = 1,
+	DDS_MAXTTL,
+	DDS_MINTTL,
+	DDS_DEFAULTTTL,
+	DDS_INTERVAL,
+	DDS_TOLERANCE,
+	DDS_MAXDYNAMICOBJS,
+
+	DDS_LAST
+};
+
+static ConfigDriver dds_cfgen;
+#if 0
+static ConfigLDAPadd dds_ldadd;
+static ConfigCfAdd dds_cfadd;
+#endif
+
+static ConfigTable dds_cfg[] = {
+	{ "dds-state", "on|off",
+		2, 2, 0, ARG_MAGIC|ARG_ON_OFF|DDS_STATE, dds_cfgen,
+		"( OLcfgOvAt:9.1 NAME 'olcDDSstate' "
+			"DESC 'RFC2589 Dynamic directory services state' "
+			"SYNTAX OMsBoolean "
+			"SINGLE-VALUE )", NULL, NULL },
+	{ "dds-max-ttl", "ttl",
+		2, 2, 0, ARG_MAGIC|DDS_MAXTTL, dds_cfgen,
+		"( OLcfgOvAt:9.2 NAME 'olcDDSmaxTtl' "
+			"DESC 'RFC2589 Dynamic directory services max TTL' "
+			"SYNTAX OMsDirectoryString "
+			"SINGLE-VALUE )", NULL, NULL },
+	{ "dds-min-ttl", "ttl",
+		2, 2, 0, ARG_MAGIC|DDS_MINTTL, dds_cfgen,
+		"( OLcfgOvAt:9.3 NAME 'olcDDSminTtl' "
+			"DESC 'RFC2589 Dynamic directory services min TTL' "
+			"SYNTAX OMsDirectoryString "
+			"SINGLE-VALUE )", NULL, NULL },
+	{ "dds-default-ttl", "ttl",
+		2, 2, 0, ARG_MAGIC|DDS_DEFAULTTTL, dds_cfgen,
+		"( OLcfgOvAt:9.4 NAME 'olcDDSdefaultTtl' "
+			"DESC 'RFC2589 Dynamic directory services default TTL' "
+			"SYNTAX OMsDirectoryString "
+			"SINGLE-VALUE )", NULL, NULL },
+	{ "dds-interval", "interval",
+		2, 2, 0, ARG_MAGIC|DDS_INTERVAL, dds_cfgen,
+		"( OLcfgOvAt:9.5 NAME 'olcDDSinterval' "
+			"DESC 'RFC2589 Dynamic directory services expiration "
+				"task run interval' "
+			"SYNTAX OMsDirectoryString "
+			"SINGLE-VALUE )", NULL, NULL },
+	{ "dds-tolerance", "ttl",
+		2, 2, 0, ARG_MAGIC|DDS_TOLERANCE, dds_cfgen,
+		"( OLcfgOvAt:9.6 NAME 'olcDDStolerance' "
+			"DESC 'RFC2589 Dynamic directory services additional "
+				"TTL in expiration scheduling' "
+			"SYNTAX OMsDirectoryString "
+			"SINGLE-VALUE )", NULL, NULL },
+	{ "dds-max-dynamicObjects", "num",
+		2, 2, 0, ARG_MAGIC|ARG_INT|DDS_MAXDYNAMICOBJS, dds_cfgen,
+		"( OLcfgOvAt:9.7 NAME 'olcDDSmaxDynamicObjects' "
+			"DESC 'RFC2589 Dynamic directory services max number of dynamic objects' "
+			"SYNTAX OMsInteger "
+			"SINGLE-VALUE )", NULL, NULL },
+	{ NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs dds_ocs[] = {
+	{ "( OLcfgOvOc:9.1 "
+		"NAME 'olcDDSConfig' "
+		"DESC 'RFC2589 Dynamic directory services configuration' "
+		"SUP olcOverlayConfig "
+		"MAY ( "
+			"olcDDSstate "
+			"$ olcDDSmaxTtl "
+			"$ olcDDSminTtl "
+			"$ olcDDSdefaultTtl "
+			"$ olcDDSinterval "
+			"$ olcDDStolerance "
+			"$ olcDDSmaxDynamicObjects "
+		" ) "
+		")", Cft_Overlay, dds_cfg, NULL, NULL /* dds_cfadd */ },
+	{ NULL, 0, NULL }
+};
+
+#if 0
+static int
+dds_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *ca )
+{
+	return LDAP_SUCCESS;
+}
+
+static int
+dds_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *ca )
+{
+	return 0;
+}
+#endif
+
+static int
+dds_cfgen( ConfigArgs *c )
+{
+	slap_overinst	*on = (slap_overinst *)c->bi;
+	dds_info_t	*di = on->on_bi.bi_private;
+	int		rc = 0;
+	unsigned long	t;
+
+
+	if ( c->op == SLAP_CONFIG_EMIT ) {
+		char		buf[ SLAP_TEXT_BUFLEN ];
+		struct berval	bv;
+
+		switch( c->type ) {
+		case DDS_STATE: 
+			c->value_int = !DDS_OFF( di );
+			break;
+
+		case DDS_MAXTTL: 
+			lutil_unparse_time( buf, sizeof( buf ), di->di_max_ttl );
+			ber_str2bv( buf, 0, 0, &bv );
+			value_add_one( &c->rvalue_vals, &bv );
+			break;
+
+		case DDS_MINTTL:
+			if ( di->di_min_ttl ) {
+				lutil_unparse_time( buf, sizeof( buf ), di->di_min_ttl );
+				ber_str2bv( buf, 0, 0, &bv );
+				value_add_one( &c->rvalue_vals, &bv );
+
+			} else {
+				rc = 1;
+			}
+			break;
+
+		case DDS_DEFAULTTTL:
+			if ( di->di_default_ttl ) {
+				lutil_unparse_time( buf, sizeof( buf ), di->di_default_ttl );
+				ber_str2bv( buf, 0, 0, &bv );
+				value_add_one( &c->rvalue_vals, &bv );
+
+			} else {
+				rc = 1;
+			}
+			break;
+
+		case DDS_INTERVAL:
+			if ( di->di_interval ) {
+				lutil_unparse_time( buf, sizeof( buf ), di->di_interval );
+				ber_str2bv( buf, 0, 0, &bv );
+				value_add_one( &c->rvalue_vals, &bv );
+
+			} else {
+				rc = 1;
+			}
+			break;
+
+		case DDS_TOLERANCE:
+			if ( di->di_tolerance ) {
+				lutil_unparse_time( buf, sizeof( buf ), di->di_tolerance );
+				ber_str2bv( buf, 0, 0, &bv );
+				value_add_one( &c->rvalue_vals, &bv );
+
+			} else {
+				rc = 1;
+			}
+			break;
+
+		case DDS_MAXDYNAMICOBJS:
+			if ( di->di_max_dynamicObjects > 0 ) {
+				c->value_int = di->di_max_dynamicObjects;
+
+			} else {
+				rc = 1;
+			}
+			break;
+
+		default:
+			rc = 1;
+			break;
+		}
+
+		return rc;
+
+	} else if ( c->op == LDAP_MOD_DELETE ) {
+		switch( c->type ) {
+		case DDS_STATE:
+			di->di_flags &= ~DDS_FOFF;
+			break;
+
+		case DDS_MAXTTL:
+			di->di_min_ttl = DDS_RF2589_DEFAULT_TTL;
+			break;
+
+		case DDS_MINTTL:
+			di->di_min_ttl = 0;
+			break;
+
+		case DDS_DEFAULTTTL:
+			di->di_default_ttl = 0;
+			break;
+
+		case DDS_INTERVAL:
+			di->di_interval = 0;
+			break;
+
+		case DDS_TOLERANCE:
+			di->di_tolerance = 0;
+			break;
+
+		case DDS_MAXDYNAMICOBJS:
+			di->di_max_dynamicObjects = 0;
+			break;
+
+		default:
+			rc = 1;
+			break;
+		}
+
+		return rc;
+	}
+
+	switch ( c->type ) {
+	case DDS_STATE:
+		if ( c->value_int ) {
+			di->di_flags &= ~DDS_FOFF;
+
+		} else {
+			di->di_flags |= DDS_FOFF;
+		}
+		break;
+
+	case DDS_MAXTTL:
+		if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) {
+			snprintf( c->msg, sizeof( c->msg),
+				"DDS unable to parse dds-max-ttl \"%s\"",
+				c->argv[ 1 ] );
+			Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+				"%s: %s.\n", c->log, c->msg );
+			return 1;
+		}
+
+		if ( t < DDS_RF2589_DEFAULT_TTL || t > DDS_RF2589_MAX_TTL ) {
+			snprintf( c->msg, sizeof( c->msg ),
+				"DDS invalid dds-max-ttl=%ld; must be between %d and %d",
+				t, DDS_RF2589_DEFAULT_TTL, DDS_RF2589_MAX_TTL );
+			Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+				"%s: %s.\n", c->log, c->msg );
+			return 1;
+		}
+
+		di->di_max_ttl = (time_t)t;
+		break;
+
+	case DDS_MINTTL:
+		if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) {
+			snprintf( c->msg, sizeof( c->msg),
+				"DDS unable to parse dds-min-ttl \"%s\"",
+				c->argv[ 1 ] );
+			Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+				"%s: %s.\n", c->log, c->msg );
+			return 1;
+		}
+
+		if ( t < 0 || t > DDS_RF2589_MAX_TTL ) {
+			snprintf( c->msg, sizeof( c->msg ),
+				"DDS invalid dds-min-ttl=%ld",
+				t );
+			Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+				"%s: %s.\n", c->log, c->msg );
+			return 1;
+		}
+
+		if ( t == 0 ) {
+			di->di_min_ttl = DDS_RF2589_DEFAULT_TTL;
+
+		} else {
+			di->di_min_ttl = (time_t)t;
+		}
+		break;
+
+	case DDS_DEFAULTTTL:
+		if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) {
+			snprintf( c->msg, sizeof( c->msg),
+				"DDS unable to parse dds-default-ttl \"%s\"",
+				c->argv[ 1 ] );
+			Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+				"%s: %s.\n", c->log, c->msg );
+			return 1;
+		}
+
+		if ( t < 0 || t > DDS_RF2589_MAX_TTL ) {
+			snprintf( c->msg, sizeof( c->msg ),
+				"DDS invalid dds-default-ttl=%ld",
+				t );
+			Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+				"%s: %s.\n", c->log, c->msg );
+			return 1;
+		}
+
+		if ( t == 0 ) {
+			di->di_default_ttl = DDS_RF2589_DEFAULT_TTL;
+
+		} else {
+			di->di_default_ttl = (time_t)t;
+		}
+		break;
+
+	case DDS_INTERVAL:
+		if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) {
+			snprintf( c->msg, sizeof( c->msg),
+				"DDS unable to parse dds-interval \"%s\"",
+				c->argv[ 1 ] );
+			Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+				"%s: %s.\n", c->log, c->msg );
+			return 1;
+		}
+
+		if ( t <= 0 ) {
+			snprintf( c->msg, sizeof( c->msg ),
+				"DDS invalid dds-interval=%ld",
+				t );
+			Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+				"%s: %s.\n", c->log, c->msg );
+			return 1;
+		}
+
+		if ( t < 60 ) {
+			Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_NOTICE,
+				"%s: dds-interval=%lu may be too small.\n",
+				c->log, t );
+		}
+
+		di->di_interval = (time_t)t;
+		if ( di->di_expire_task ) {
+			ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+			if ( ldap_pvt_runqueue_isrunning( &slapd_rq, di->di_expire_task ) ) {
+				ldap_pvt_runqueue_stoptask( &slapd_rq, di->di_expire_task );
+			}
+			di->di_expire_task->interval.tv_sec = DDS_INTERVAL( di );
+			ldap_pvt_runqueue_resched( &slapd_rq, di->di_expire_task, 0 );
+			ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+		}
+		break;
+
+	case DDS_TOLERANCE:
+		if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) {
+			snprintf( c->msg, sizeof( c->msg),
+				"DDS unable to parse dds-tolerance \"%s\"",
+				c->argv[ 1 ] );
+			Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+				"%s: %s.\n", c->log, c->msg );
+			return 1;
+		}
+
+		if ( t < 0 || t > DDS_RF2589_MAX_TTL ) {
+			snprintf( c->msg, sizeof( c->msg ),
+				"DDS invalid dds-tolerance=%ld",
+				t );
+			Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+				"%s: %s.\n", c->log, c->msg );
+			return 1;
+		}
+
+		di->di_tolerance = (time_t)t;
+		break;
+
+	case DDS_MAXDYNAMICOBJS:
+		if ( c->value_int < 0 ) {
+			snprintf( c->msg, sizeof( c->msg ),
+				"DDS invalid dds-max-dynamicObjects=%d", c->value_int );
+			Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+				"%s: %s.\n", c->log, c->msg );
+			return 1;
+		}
+		di->di_max_dynamicObjects = c->value_int;
+		break;
+
+	default:
+		rc = 1;
+		break;
+	}
+
+	return rc;
+}
+
+static int
+dds_db_init(
+	BackendDB	*be )
+{
+	slap_overinst	*on = (slap_overinst *)be->bd_info;
+	dds_info_t	*di;
+	BackendInfo	*bi = on->on_info->oi_orig;
+
+	if ( SLAP_ISGLOBALOVERLAY( be ) ) {
+		Log0( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+			"DDS cannot be used as global overlay.\n" );
+		return 1;
+	}
+
+	/* check support for required functions */
+	/* FIXME: some could be provided by other overlays in between */
+	if ( bi->bi_op_add == NULL			/* object creation */
+		|| bi->bi_op_delete == NULL		/* object deletion */
+		|| bi->bi_op_modify == NULL		/* object refresh */
+		|| bi->bi_op_search == NULL		/* object expiration */
+		|| bi->bi_entry_get_rw == NULL )	/* object type/existence checking */
+	{
+		Log1( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+			"DDS backend \"%s\" does not provide "
+			"required functionality.\n",
+			bi->bi_type );
+		return 1;
+	}
+
+	di = (dds_info_t *)ch_calloc( 1, sizeof( dds_info_t ) );
+	on->on_bi.bi_private = di;
+
+	di->di_max_ttl = DDS_RF2589_DEFAULT_TTL;
+	di->di_max_ttl = DDS_RF2589_DEFAULT_TTL;
+
+	ldap_pvt_thread_mutex_init( &di->di_mutex );
+
+	SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_DYNAMIC;
+
+	return 0;
+}
+
+/* adds dynamicSubtrees to root DSE */
+static int
+dds_entry_info( void *arg, Entry *e )
+{
+	dds_info_t	*di = (dds_info_t *)arg;
+
+	attr_merge( e, slap_schema.si_ad_dynamicSubtrees,
+		di->di_suffix, di->di_nsuffix );
+
+	return 0;
+}
+
+/* callback that counts the returned entries, since the search
+ * does not get to the point in slap_send_search_entries where
+ * the actual count occurs */
+static int
+dds_count_cb( Operation *op, SlapReply *rs )
+{
+	int	*nump = (int *)op->o_callback->sc_private;
+
+	switch ( rs->sr_type ) {
+	case REP_SEARCH:
+		(*nump)++;
+		break;
+
+	case REP_SEARCHREF:
+	case REP_RESULT:
+		break;
+
+	default:
+		assert( 0 );
+	}
+
+	return 0;
+}
+
+/* count dynamic objects existing in the database at startup */
+static int
+dds_count( void *ctx, BackendDB *be )
+{
+	slap_overinst	*on = (slap_overinst *)be->bd_info;
+	dds_info_t	*di = (dds_info_t *)on->on_bi.bi_private;
+	
+	Connection	conn = { 0 };
+	OperationBuffer opbuf;
+	Operation	*op;
+	slap_callback	sc = { 0 };
+	SlapReply	rs = { REP_RESULT };
+
+	op = (Operation *)&opbuf;
+	connection_fake_init( &conn, op, ctx );
+
+	op->o_tag = LDAP_REQ_SEARCH;
+	memset( &op->oq_search, 0, sizeof( op->oq_search ) );
+
+	op->o_bd = be;
+
+	op->o_req_dn = op->o_bd->be_suffix[ 0 ];
+	op->o_req_ndn = op->o_bd->be_nsuffix[ 0 ];
+
+	op->o_dn = op->o_bd->be_rootdn;
+	op->o_ndn = op->o_bd->be_rootndn;
+
+	op->ors_scope = LDAP_SCOPE_SUBTREE;
+	op->ors_tlimit = SLAP_NO_LIMIT;
+	op->ors_slimit = SLAP_NO_LIMIT;
+	op->ors_attrs = slap_anlist_no_attrs;
+
+	op->ors_filterstr.bv_len = STRLENOF( "(objectClass=" ")" )
+		+ slap_schema.si_oc_dynamicObject->soc_cname.bv_len;
+	op->ors_filterstr.bv_val = op->o_tmpalloc( op->ors_filterstr.bv_len + 1, op->o_tmpmemctx );
+	snprintf( op->ors_filterstr.bv_val, op->ors_filterstr.bv_len + 1,
+		"(objectClass=%s)",
+		slap_schema.si_oc_dynamicObject->soc_cname.bv_val );
+
+	op->ors_filter = str2filter_x( op, op->ors_filterstr.bv_val );
+	if ( op->ors_filter == NULL ) {
+		rs.sr_err = LDAP_OTHER;
+		goto done_search;
+	}
+	
+	op->o_callback = &sc;
+	sc.sc_response = dds_count_cb;
+	sc.sc_private = &di->di_num_dynamicObjects;
+
+	op->o_bd->bd_info = (BackendInfo *)on->on_info;
+	(void)op->o_bd->bd_info->bi_op_search( op, &rs );
+	op->o_bd->bd_info = (BackendInfo *)on;
+
+done_search:;
+	op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+	filter_free_x( op, op->ors_filter );
+
+	if ( rs.sr_err == LDAP_SUCCESS ) {
+		Log1( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO,
+			"DDS non-expired=%d\n",
+			di->di_num_dynamicObjects );
+
+	} else {
+		Log1( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+			"DDS non-expired objects lookup failed err=%d\n",
+			rs.sr_err );
+	}
+
+	return rs.sr_err;
+}
+
+static int
+dds_db_open(
+	BackendDB	*be )
+{
+	slap_overinst	*on = (slap_overinst *)be->bd_info;
+	dds_info_t	*di = on->on_bi.bi_private;
+	int		rc = 0;
+	void		*thrctx = ldap_pvt_thread_pool_context();
+
+	if ( DDS_OFF( di ) ) {
+		goto done;
+	}
+
+#ifndef SLAPD_MULTIMASTER
+	if ( SLAP_SHADOW( be ) ) {
+		Log1( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+			"DDS incompatible with shadow database \"%s\".\n",
+			be->be_suffix[ 0 ].bv_val );
+		return 1;
+	}
+#endif /* ! SLAPD_MULTIMASTER */
+
+	if ( di->di_max_ttl == 0 ) {
+		di->di_max_ttl = DDS_RF2589_DEFAULT_TTL;
+	}
+
+	if ( di->di_min_ttl == 0 ) {
+		di->di_max_ttl = DDS_RF2589_DEFAULT_TTL;
+	}
+
+	di->di_suffix = be->be_suffix;
+	di->di_nsuffix = be->be_nsuffix;
+
+	/* force deletion of expired entries... */
+	be->bd_info = (BackendInfo *)on->on_info;
+	rc = dds_expire( thrctx, di );
+	be->bd_info = (BackendInfo *)on;
+	if ( rc != LDAP_SUCCESS ) {
+		rc = 1;
+		goto done;
+	}
+
+	/* ... so that count, if required, is accurate */
+	if ( di->di_max_dynamicObjects > 0 ) {
+		rc = dds_count( thrctx, be );
+		if ( rc != LDAP_SUCCESS ) {
+			rc = 1;
+			goto done;
+		}
+	}
+
+	/* start expire task */
+	ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+	di->di_expire_task = ldap_pvt_runqueue_insert( &slapd_rq,
+		DDS_INTERVAL( di ),
+		dds_expire_fn, di, "dds_expire_fn",
+		be->be_suffix[ 0 ].bv_val );
+	ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+	/* register dinamicSubtrees root DSE info support */
+	rc = entry_info_register( dds_entry_info, (void *)di );
+
+done:;
+	ldap_pvt_thread_pool_context_reset( thrctx );
+
+	return rc;
+}
+
+static int
+dds_db_close(
+	BackendDB	*be )
+{
+	slap_overinst	*on = (slap_overinst *)be->bd_info;
+	dds_info_t	*di = on->on_bi.bi_private;
+
+	/* stop expire task */
+	if ( di && di->di_expire_task ) {
+		ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+		if ( ldap_pvt_runqueue_isrunning( &slapd_rq, di->di_expire_task ) ) {
+			ldap_pvt_runqueue_stoptask( &slapd_rq, di->di_expire_task );
+		}
+		ldap_pvt_runqueue_remove( &slapd_rq, di->di_expire_task );
+		ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+	}
+
+	(void)entry_info_unregister( dds_entry_info, (void *)di );
+
+	return 0;
+}
+
+static int
+dds_db_destroy(
+	BackendDB	*be )
+{
+	slap_overinst	*on = (slap_overinst *)be->bd_info;
+	dds_info_t	*di = on->on_bi.bi_private;
+
+	if ( di != NULL ) {
+		ldap_pvt_thread_mutex_destroy( &di->di_mutex );
+
+		free( di );
+	}
+
+	return 0;
+}
+
+static int
+slap_exop_refresh(
+		Operation	*op,
+		SlapReply	*rs )
+{
+	BackendDB		*bd = op->o_bd;
+
+	rs->sr_err = slap_parse_refresh( op->ore_reqdata, &op->o_req_ndn, NULL,
+		&rs->sr_text, op->o_tmpmemctx );
+	if ( rs->sr_err != LDAP_SUCCESS ) {
+		return rs->sr_err;
+	}
+
+	Log2( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO,
+		"%s REFRESH dn=\"%s\"\n",
+		op->o_log_prefix, op->o_req_ndn.bv_val );
+	op->o_req_dn = op->o_req_ndn;
+
+	op->o_bd = select_backend( &op->o_req_ndn, 0, 0 );
+	if ( !SLAP_DYNAMIC( op->o_bd ) ) {
+		send_ldap_error( op, rs, LDAP_UNAVAILABLE_CRITICAL_EXTENSION,
+			"backend does not support dynamic directory services" );
+		goto done;
+	}
+
+	rs->sr_err = backend_check_restrictions( op, rs,
+		(struct berval *)&slap_EXOP_REFRESH );
+	if ( rs->sr_err != LDAP_SUCCESS ) {
+		goto done;
+	}
+
+	if ( op->o_bd->be_extended == NULL ) {
+		send_ldap_error( op, rs, LDAP_UNAVAILABLE_CRITICAL_EXTENSION,
+			"backend does not support extended operations" );
+		goto done;
+	}
+
+	op->o_bd->be_extended( op, rs );
+
+done:;
+	if ( !BER_BVISNULL( &op->o_req_ndn ) ) {
+		op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx );
+		BER_BVZERO( &op->o_req_ndn );
+		BER_BVZERO( &op->o_req_dn );
+	}
+	op->o_bd = bd;
+
+        return rs->sr_err;
+}
+
+static slap_overinst dds;
+
+static int do_not_load_exop;
+static int do_not_replace_exop;
+static int do_not_load_schema;
+
+#if SLAPD_OVER_DDS == SLAPD_MOD_DYNAMIC
+static
+#endif /* SLAPD_OVER_DDS == SLAPD_MOD_DYNAMIC */
+int
+dds_initialize()
+{
+	int		rc = 0;
+	int		i, code;
+	const char	*err;
+
+	/* Make sure we don't exceed the bits reserved for userland */
+	config_check_userland( DDS_LAST );
+
+	if ( !do_not_load_schema ) {
+		static struct {
+			char			*name;
+			char			*desc;
+			AttributeDescription	**ad;
+		}		s_at[] = {
+#warning "FIXME: register OID!!!"
+			{ "entryExpireTimestamp", "( 1.3.6.1.4.1.4203.666.999999.0 "
+				"NAME ( 'entryExpireTimestamp' ) "
+				"DESC 'RFC2589 extension: expire time of a dynamic object, "
+					"computed as modifyTimestamp + entryTtl' "
+				"EQUALITY generalizedTimeMatch "
+				"ORDERING generalizedTimeOrderingMatch "
+				"SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+				"SINGLE-VALUE "
+				"NO-USER-MODIFICATION "
+				"USAGE dSAOperation )",
+				&ad_entryExpireTimestamp },
+			{ NULL }
+		};
+
+		for ( i = 0; s_at[ i ].name != NULL; i++ ) {
+			LDAPAttributeType	*at;
+
+			at = ldap_str2attributetype( s_at[ i ].desc,
+				&code, &err, LDAP_SCHEMA_ALLOW_ALL );
+			if ( !at ) {
+				fprintf( stderr, "dds_initialize: "
+					"AttributeType load failed: %s %s\n",
+					ldap_scherr2str( code ), err );
+				return code;
+			}
+
+			code = at_add( at, 0, NULL, &err );
+			ldap_memfree( at );
+			if ( code != LDAP_SUCCESS ) {
+				fprintf( stderr, "dds_initialize: "
+					"AttributeType load failed: %s %s\n",
+					scherr2str( code ), err );
+				return code;
+			}
+
+			code = slap_str2ad( s_at[ i ].name, s_at[ i ].ad, &err );
+			if ( code != LDAP_SUCCESS ) {
+				fprintf( stderr, "dds_initialize: "
+					"unable to find AttributeDescription "
+					"\"%s\": %d (%s)\n",
+					s_at[ i ].name, code, err );
+				return 1;
+			}
+		}
+	}
+
+	if ( !do_not_load_exop ) {
+		rc = load_extop2( (struct berval *)&slap_EXOP_REFRESH,
+			SLAP_EXOP_WRITES|SLAP_EXOP_HIDE, slap_exop_refresh,
+			!do_not_replace_exop );
+		if ( rc != LDAP_SUCCESS ) {
+			Log1( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+				"DDS unable to register refresh exop: %d.\n",
+				rc );
+			return rc;
+		}
+	}
+
+	dds.on_bi.bi_type = "dds";
+
+	dds.on_bi.bi_db_init = dds_db_init;
+	dds.on_bi.bi_db_open = dds_db_open;
+	dds.on_bi.bi_db_close = dds_db_close;
+	dds.on_bi.bi_db_destroy = dds_db_destroy;
+
+	dds.on_bi.bi_op_add = dds_op_add;
+	dds.on_bi.bi_op_delete = dds_op_delete;
+	dds.on_bi.bi_op_modify = dds_op_modify;
+	dds.on_bi.bi_op_modrdn = dds_op_rename;
+	dds.on_bi.bi_extended = dds_op_extended;
+
+	dds.on_bi.bi_cf_ocs = dds_ocs;
+
+	rc = config_register_schema( dds_cfg, dds_ocs );
+	if ( rc ) {
+		return rc;
+	}
+
+	return overlay_register( &dds );
+}
+
+#if SLAPD_OVER_DDS == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+	int	i;
+
+	for ( i = 0; i < argc; i++ ) {
+		char	*arg = argv[ i ];
+		int	no = 0;
+
+		if ( strncasecmp( arg, "no-", STRLENOF( "no-" ) ) == 0 ) {
+			arg += STRLENOF( "no-" );
+			no = 1;
+		}
+
+		if ( strcasecmp( arg, "exop" ) == 0 ) {
+			do_not_load_exop = no;
+
+		} else if ( strcasecmp( arg, "replace" ) == 0 ) {
+			do_not_replace_exop = no;
+
+		} else if ( strcasecmp( arg, "schema" ) == 0 ) {
+			do_not_load_schema = no;
+
+		} else {
+			Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+				"DDS unknown module arg[#%d]=\"%s\".\n",
+				i, argv[ i ] );
+			return 1;
+		}
+	}
+
+	return dds_initialize();
+}
+#endif /* SLAPD_OVER_DDS == SLAPD_MOD_DYNAMIC */
+
+#endif	/* defined(SLAPD_OVER_DDS) */
diff --git a/servers/slapd/schema_prep.c b/servers/slapd/schema_prep.c
index a703f1cb11..001af17ef5 100644
--- a/servers/slapd/schema_prep.c
+++ b/servers/slapd/schema_prep.c
@@ -894,7 +894,7 @@ static struct slap_schema_ad_map {
 			"DESC 'RFC2589: entry time-to-live' "
 			"SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE "
 			"NO-USER-MODIFICATION USAGE dSAOperation )",
-		dynamicAttribute, 0,
+		dynamicAttribute, SLAP_AT_MANAGEABLE,
 		NULL, NULL,
 		NULL, NULL, NULL, NULL, NULL,
 		offsetof(struct slap_internal_schema, si_ad_entryTtl) },
diff --git a/servers/slapd/slap.h b/servers/slapd/slap.h
index f880a0bd27..2b310fcd17 100644
--- a/servers/slapd/slap.h
+++ b/servers/slapd/slap.h
@@ -1713,6 +1713,7 @@ struct slap_backend_db {
 #define SLAP_DBFLAG_GLUE_ADVERTISE	0x0080U /* advertise in rootDSE */
 #define SLAP_DBFLAG_OVERLAY		0x0100U	/* this db struct is an overlay */
 #define	SLAP_DBFLAG_GLOBAL_OVERLAY	0x0200U	/* this db struct is a global overlay */
+#define SLAP_DBFLAG_DYNAMIC		0x0400U /* this db allows dynamicObjects */
 #define SLAP_DBFLAG_SHADOW		0x8000U /* a shadow */
 #define SLAP_DBFLAG_SYNC_SHADOW		0x1000U /* a sync shadow */
 #define SLAP_DBFLAG_SLURP_SHADOW	0x2000U /* a slurp shadow */
@@ -2165,7 +2166,7 @@ struct slap_backend_info {
 #define SLAP_ALIASES(be)	(SLAP_BFLAGS(be) & SLAP_BFLAG_ALIASES)
 #define SLAP_REFERRALS(be)	(SLAP_BFLAGS(be) & SLAP_BFLAG_REFERRALS)
 #define SLAP_SUBENTRIES(be)	(SLAP_BFLAGS(be) & SLAP_BFLAG_SUBENTRIES)
-#define SLAP_DYNAMIC(be)	(SLAP_BFLAGS(be) & SLAP_BFLAG_DYNAMIC)
+#define SLAP_DYNAMIC(be)	((SLAP_BFLAGS(be) & SLAP_BFLAG_DYNAMIC) || (SLAP_DBFLAGS(be) & SLAP_DBFLAG_DYNAMIC))
 #define SLAP_NOLASTMODCMD(be)	(SLAP_BFLAGS(be) & SLAP_BFLAG_NOLASTMODCMD)
 #define SLAP_LASTMODCMD(be)	(!SLAP_NOLASTMODCMD(be))
 
diff --git a/tests/data/dds.out b/tests/data/dds.out
new file mode 100644
index 0000000000..1f580b4c10
--- /dev/null
+++ b/tests/data/dds.out
@@ -0,0 +1,70 @@
+# [1] Searching the dynamic portion of the database...
+dn: cn=Dynamic Object,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: dynamicObject
+cn: Dynamic Object
+sn: Object
+entryTtl: 120
+userPassword:: ZHluYW1pYw==
+
+dn: cn=Subordinate Dynamic Object,cn=Dynamic Object,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: dynamicObject
+cn: Subordinate Dynamic Object
+sn: Object
+userPassword:: ZHluYW1pYw==
+entryTtl: 3600
+
+# [2] Searching the dynamic portion of the database...
+dn: cn=Dynamic Object,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: dynamicObject
+cn: Dynamic Object
+sn: Object
+entryTtl: 120
+userPassword:: ZHluYW1pYw==
+
+dn: cn=Renamed Dynamic Object,cn=Dynamic Object,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: dynamicObject
+sn: Object
+userPassword:: ZHluYW1pYw==
+entryTtl: 3600
+cn: Renamed Dynamic Object
+
+# [3] Searching the dynamic portion of the database...
+dn: cn=Dynamic Object,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: dynamicObject
+cn: Dynamic Object
+sn: Object
+userPassword:: ZHluYW1pYw==
+entryTtl: 120
+
+dn: cn=Renamed Dynamic Object,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: dynamicObject
+sn: Object
+userPassword:: ZHluYW1pYw==
+entryTtl: 3600
+cn: Renamed Dynamic Object
+
+# [4] Searching the dynamic portion of the database...
+dn: cn=Renamed Dynamic Object,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: dynamicObject
+sn: Object
+userPassword:: ZHluYW1pYw==
+entryTtl: 3600
+cn: Renamed Dynamic Object
+
+# [5] Searching the dynamic portion of the database...
+dn: cn=Renamed Dynamic Object,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: dynamicObject
+sn: Object
+userPassword:: ZHluYW1pYw==
+cn: Renamed Dynamic Object
+entryTtl: 10
+
+# [6] Searching the dynamic portion of the database...
diff --git a/tests/data/slapd-dds.conf b/tests/data/slapd-dds.conf
new file mode 100644
index 0000000000..19e583e02d
--- /dev/null
+++ b/tests/data/slapd-dds.conf
@@ -0,0 +1,88 @@
+# stand-alone slapd config -- for testing (with indexing)
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2005 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>.
+
+include		@SCHEMADIR@/core.schema
+include		@SCHEMADIR@/cosine.schema
+include		@SCHEMADIR@/inetorgperson.schema
+include		@SCHEMADIR@/openldap.schema
+include		@SCHEMADIR@/nis.schema
+include		@DATADIR@/test.schema
+
+#
+pidfile		@TESTDIR@/slapd.1.pid
+argsfile	@TESTDIR@/slapd.1.args
+
+#mod#modulepath	../servers/slapd/back-@BACKEND@/
+#mod#moduleload	back_@BACKEND@.la
+#monitormod#modulepath ../servers/slapd/back-monitor/
+#monitormod#moduleload back_monitor.la
+#ddsmod#modulepath ../servers/slapd/overlays/
+#ddsmod#moduleload dds.la
+
+#######################################################################
+# database definitions
+#######################################################################
+
+database	@BACKEND@
+suffix		"dc=example,dc=com"
+directory	@TESTDIR@/db.1.a
+rootdn		"cn=Manager,dc=example,dc=com"
+rootpw		secret
+#bdb#index		objectClass	eq
+#bdb#index		cn,sn,uid	pres,eq,sub
+#bdb#index		entryExpireTimestamp	eq
+#hdb#index		objectClass	eq
+#hdb#index		cn,sn,uid	pres,eq,sub
+#hdb#index		entryExpireTimestamp	eq
+#ldbm#index		objectClass	eq
+#ldbm#index		cn,sn,uid	pres,eq,sub
+#ldbm#index		entryExpireTimestamp	eq
+
+overlay		dds
+dds-max-ttl	1d
+dds-min-ttl	10s
+dds-default-ttl	1h
+dds-interval	5s
+dds-tolerance	1s
+
+# This is to test the meeting feature
+access to attrs=userPassword
+	by self write
+	by * read
+
+access to dn.base="ou=Groups,dc=example,dc=com"
+                attrs=children
+        by users write
+
+access to dn.onelevel="ou=Groups,dc=example,dc=com"
+                attrs=entry
+        by dnattr=creatorsName write
+        by * read
+
+access to dn.onelevel="ou=Groups,dc=example,dc=com"
+                attrs=member
+        by dnattr=creatorsName write
+        by users selfwrite
+        by * read
+
+access to dn.onelevel="ou=Groups,dc=example,dc=com"
+                attrs=entryTtl
+        by dnattr=member manage
+        by * read
+
+access to *
+	by * read
+
+#monitor#database	monitor
diff --git a/tests/run.in b/tests/run.in
index 7bd775a0b5..6faeed7fd8 100644
--- a/tests/run.in
+++ b/tests/run.in
@@ -35,6 +35,7 @@ AC_sql=sql@BUILD_SQL@
 
 # overlays
 AC_accesslog=accesslog@BUILD_ACCESSLOG@
+AC_dds=dds@BUILD_DDS@
 AC_dynlist=dynlist@BUILD_DYNLIST@
 AC_pcache=pcache@BUILD_PROXYCACHE@
 AC_ppolicy=ppolicy@BUILD_PPOLICY@
@@ -56,6 +57,7 @@ AC_THREADS=threads@BUILD_THREAD@
 export AC_bdb AC_hdb AC_ldap AC_ldbm AC_meta AC_monitor AC_relay AC_sql \
 	AC_accesslog AC_dynlist AC_pcache AC_ppolicy AC_refint AC_retcode \
 	AC_rwm AC_unique AC_syncprov AC_translucent AC_valsort \
+	AC_dds \
 	AC_WITH_SASL AC_WITH_TLS AC_WITH_MODULES_ENABLED AC_ACI_ENABLED \
 	AC_THREADS
 
diff --git a/tests/scripts/conf.sh b/tests/scripts/conf.sh
index ddce73cdb7..7bef25a2a5 100755
--- a/tests/scripts/conf.sh
+++ b/tests/scripts/conf.sh
@@ -42,6 +42,7 @@ sed -e "s/@BACKEND@/${BACKEND}/"			\
 	-e "s/^#${AC_sql}#//"				\
 		-e "s/^#${RDBMS}#//"			\
 	-e "s/^#${AC_accesslog}#//"			\
+	-e "s/^#${AC_dds}#//"				\
 	-e "s/^#${AC_dynlist}#//"			\
 	-e "s/^#${AC_pcache}#//"			\
 	-e "s/^#${AC_ppolicy}#//"			\
diff --git a/tests/scripts/defines.sh b/tests/scripts/defines.sh
index d3f3254651..a77439c2b7 100755
--- a/tests/scripts/defines.sh
+++ b/tests/scripts/defines.sh
@@ -21,6 +21,7 @@ BACKSQL=${AC_sql-sqlno}
 RDBMS=${SLAPD_USE_SQL-rdbmsno}
 RDBMSWRITE=${SLAPD_USE_SQLWRITE-no}
 ACCESSLOG=${AC_accesslog-accesslogno}
+DDS=${AC_dds-ddsno}
 DYNLIST=${AC_dynlist-dynlistno}
 PROXYCACHE=${AC_pcache-pcacheno}
 PPOLICY=${AC_ppolicy-ppolicyno}
@@ -107,6 +108,7 @@ VALSORTCONF=$DATADIR/slapd-valsort.conf
 DYNLISTCONF=$DATADIR/slapd-dynlist.conf
 RSLAVECONF=$DATADIR/slapd-repl-slave-remote.conf
 PLSRSLAVECONF=$DATADIR/slapd-syncrepl-slave-persist-ldap.conf
+DDSCONF=$DATADIR/slapd-dds.conf
 
 CONF1=$TESTDIR/slapd.1.conf
 CONF2=$TESTDIR/slapd.2.conf
@@ -149,6 +151,7 @@ LDAPADD="$CLIENTDIR/ldapmodify -a $TOOLPROTO $TOOLARGS"
 LDAPMODRDN="$CLIENTDIR/ldapmodrdn $TOOLPROTO $TOOLARGS"
 LDAPWHOAMI="$CLIENTDIR/ldapwhoami $TOOLARGS"
 LDAPCOMPARE="$CLIENTDIR/ldapcompare $TOOLARGS"
+LDAPEXOP="$CLIENTDIR/ldapexop $TOOLARGS"
 SLAPDTESTER=$PROGDIR/slapd-tester
 LVL=${SLAPD_DEBUG-261}
 LOCALHOST=localhost
@@ -297,6 +300,7 @@ MANAGEOUT=$DATADIR/manage.out
 SUBTREERENAMEOUT=$DATADIR/subtree-rename.out
 ACIOUT=$DATADIR/aci.out
 DYNLISTOUT=$DATADIR/dynlist.out
+DDSOUT=$DATADIR/dds.out
 
 # Just in case we linked the binaries dynamically
 LD_LIBRARY_PATH=`pwd`/../libraries:${LD_LIBRARY_PATH} export LD_LIBRARY_PATH
diff --git a/tests/scripts/test046-dds b/tests/scripts/test046-dds
new file mode 100755
index 0000000000..34d96d9bff
--- /dev/null
+++ b/tests/scripts/test046-dds
@@ -0,0 +1,529 @@
+#! /bin/sh
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2005 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 $DDS = ddsno; then 
+	echo "Dynamic Directory Services overlay not available, test skipped"
+	exit 0
+fi 
+
+mkdir -p $TESTDIR $DBDIR1
+
+echo "Running slapadd to build slapd database..."
+. $CONFFILTER $BACKEND $MONITORDB < $MCONF > $ADDCONF
+$SLAPADD -f $ADDCONF -l $LDIFORDERED
+RC=$?
+if test $RC != 0 ; then
+	echo "slapadd failed ($RC)!"
+	exit $RC
+fi
+
+echo "Running slapindex to index slapd database..."
+. $CONFFILTER $BACKEND $MONITORDB < $DDSCONF > $CONF1
+$SLAPINDEX -f $CONF1
+RC=$?
+if test $RC != 0 ; then
+	echo "warning: slapindex failed ($RC)"
+	echo "  assuming no indexing support"
+fi
+
+echo "Starting slapd on TCP/IP port $PORT1..."
+$SLAPD -f $CONF1 -h $URI1 -d $LVL $TIMING > $LOG1 2>&1 &
+PID=$!
+if test $WAIT != 0 ; then
+    echo PID $PID
+    read foo
+fi
+KILLPIDS="$PID"
+
+sleep 1
+
+echo "Testing slapd searching..."
+for i in 0 1 2 3 4 5; do
+	$LDAPSEARCH -s base -b "$MONITOR" -h $LOCALHOST -p $PORT1 \
+		'(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 > $SEARCHOUT
+
+echo "Creating a dynamic entry..."
+$LDAPADD -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \
+	>> $TESTOUT 2>&1 << EOMODS
+dn: cn=Dynamic Object,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: dynamicObject
+cn: Dynamic Object
+sn: Object
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+	echo "ldapadd failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+echo "Refreshing the newly created dynamic entry..."
+$LDAPEXOP -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \
+	"refresh" "cn=Dynamic Object,dc=example,dc=com" "120" \
+	>> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+	echo "ldapexop failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+echo "Modifying the newly created dynamic entry..."
+$LDAPMODIFY -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \
+	>> $TESTOUT 2>&1 << EOMODS
+dn: cn=Dynamic Object,dc=example,dc=com
+changetype: modify
+add: userPassword
+userPassword: dynamic
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+	echo "ldapadd failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+echo "Binding as the newly created dynamic entry..."
+$LDAPWHOAMI -h $LOCALHOST -p $PORT1 \
+	-D "cn=Dynamic Object,dc=example,dc=com" -w dynamic
+RC=$?
+if test $RC != 0 ; then
+	echo "ldapwhoami failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+echo "Creating a dynamic entry subordinate to another..."
+$LDAPADD -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \
+	>> $TESTOUT 2>&1 << EOMODS
+dn: cn=Subordinate Dynamic Object,cn=Dynamic Object,dc=example,dc=com
+objectClass: inetOrgPerson
+objectClass: dynamicObject
+cn: Subordinate Dynamic Object
+sn: Object
+userPassword: dynamic
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+	echo "ldapadd failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+SEARCH=0
+
+SEARCH=`expr $SEARCH + 1`
+echo "# [$SEARCH] Searching the dynamic portion of the database..." >> $SEARCHOUT
+$LDAPSEARCH -S "" -b "$BASEDN" -h $LOCALHOST -p $PORT1 \
+	'(objectClass=dynamicObject)' '*' entryTtl \
+	>> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+	echo "ldapsearch failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+echo "Creating a static entry subordinate to a dynamic one (should fail)..."
+$LDAPADD -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \
+	>> $TESTOUT 2>&1 << EOMODS
+dn: cn=Subordinate Static Object,cn=Dynamic Object,dc=example,dc=com
+objectClass: inetOrgPerson
+cn: Subordinate Static Object
+sn: Object
+userPassword: static
+EOMODS
+RC=$?
+case $RC in
+0)
+	echo "ldapadd should have failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit -1
+	;;
+19)
+	echo "ldapadd failed ($RC)"
+	;;
+*)
+	echo "ldapadd failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+	;;
+esac
+
+echo "Turning a static into a dynamic entry (should fail)..."
+$LDAPMODIFY -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \
+	>> $TESTOUT 2>&1 << EOMODS
+dn: ou=People,dc=example,dc=com
+changetype: modify
+add: objectClass
+objectClass: dynamicObject
+EOMODS
+RC=$?
+case $RC in
+0)
+	echo "ldapmodify should have failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit -1
+	;;
+65)
+	echo "ldapmodify failed ($RC)"
+	;;
+*)
+	echo "ldapmodify failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+	;;
+esac
+
+echo "Turning a dynamic into a static entry (should fail)..."
+$LDAPMODIFY -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \
+	>> $TESTOUT 2>&1 << EOMODS
+dn: cn=Dynamic Object,dc=example,dc=com
+changetype: modify
+delete: objectClass
+objectClass: dynamicObject
+EOMODS
+RC=$?
+case $RC in
+0)
+	echo "ldapmodify should have failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit -1
+	;;
+65)
+	echo "ldapmodify failed ($RC)"
+	;;
+*)
+	echo "ldapmodify failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+	;;
+esac
+
+echo "Renaming a dynamic entry..."
+$LDAPMODIFY -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \
+	>> $TESTOUT 2>&1 << EOMODS
+dn: cn=Subordinate Dynamic Object,cn=Dynamic Object,dc=example,dc=com
+changetype: modrdn
+newrdn: cn=Renamed Dynamic Object
+deleteoldrdn: 1
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+	echo "ldapmodrdn failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+SEARCH=`expr $SEARCH + 1`
+echo "# [$SEARCH] Searching the dynamic portion of the database..." >> $SEARCHOUT
+$LDAPSEARCH -S "" -b "$BASEDN" -h $LOCALHOST -p $PORT1 \
+	'(objectClass=dynamicObject)' '*' entryTtl \
+	>> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+	echo "ldapsearch failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+echo "Refreshing the initial dynamic entry to make it expire earlier than the subordinate..."
+$LDAPEXOP -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \
+	"refresh" "cn=Dynamic Object,dc=example,dc=com" "1" \
+	>> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+	echo "ldapexop failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+SLEEP=10
+echo "Waiting $SLEEP seconds to force an expiration conflict..."
+sleep $SLEEP
+
+echo "Re-vitalizing the initial dynamic entry..."
+$LDAPEXOP -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \
+	"refresh" "cn=Dynamic Object,dc=example,dc=com" "120" \
+	>> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+	echo "ldapexop failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+echo "Re-renaming the subordinate dynamic entry (new superior)..."
+$LDAPMODIFY -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \
+	>> $TESTOUT 2>&1 << EOMODS
+dn: cn=Renamed Dynamic Object,cn=Dynamic Object,dc=example,dc=com
+changetype: modrdn
+newrdn: cn=Renamed Dynamic Object
+deleteoldrdn: 1
+newsuperior: dc=example,dc=com
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+	echo "ldapmodrdn failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+SEARCH=`expr $SEARCH + 1`
+echo "# [$SEARCH] Searching the dynamic portion of the database..." >> $SEARCHOUT
+$LDAPSEARCH -S "" -b "$BASEDN" -h $LOCALHOST -p $PORT1 \
+	'(objectClass=dynamicObject)' '*' entryTtl \
+	>> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+	echo "ldapsearch failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+echo "Deleting a dynamic entry..."
+$LDAPMODIFY -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \
+	>> $TESTOUT 2>&1 << EOMODS
+dn: cn=Dynamic Object,dc=example,dc=com
+changetype: delete
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+	echo "ldapdelete failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+SEARCH=`expr $SEARCH + 1`
+echo "# [$SEARCH] Searching the dynamic portion of the database..." >> $SEARCHOUT
+$LDAPSEARCH -S "" -b "$BASEDN" -h $LOCALHOST -p $PORT1 \
+	'(objectClass=dynamicObject)' '*' entryTtl \
+	>> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+	echo "ldapsearch failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+echo "Refreshing the remaining dynamic entry..."
+$LDAPEXOP -D $MANAGERDN -w $PASSWD -h $LOCALHOST -p $PORT1 \
+	"refresh" "cn=Renamed Dynamic Object,dc=example,dc=com" "1" \
+	>> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+	echo "ldapexop failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+SEARCH=`expr $SEARCH + 1`
+echo "# [$SEARCH] Searching the dynamic portion of the database..." >> $SEARCHOUT
+$LDAPSEARCH -S "" -b "$BASEDN" -h $LOCALHOST -p $PORT1 \
+	'(objectClass=dynamicObject)' '*' entryTtl \
+	>> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+	echo "ldapsearch failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+SLEEP=15
+echo "Waiting $SLEEP seconds for remaining entry to expire..."
+sleep $SLEEP
+
+SEARCH=`expr $SEARCH + 1`
+echo "# [$SEARCH] Searching the dynamic portion of the database..." >> $SEARCHOUT
+$LDAPSEARCH -S "" -b "$BASEDN" -h $LOCALHOST -p $PORT1 \
+	'(objectClass=dynamicObject)' '*' entryTtl \
+	>> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+	echo "ldapsearch failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+# Meeting
+MEETINGDN="cn=Meeting,ou=Groups,dc=example,dc=com"
+echo "Creating a meeting as $BJORNSDN..."
+$LDAPMODIFY -D "$BJORNSDN" -w bjorn -h $LOCALHOST -p $PORT1 \
+	>> $TESTOUT 2>&1 << EOMODS
+dn: $MEETINGDN
+changetype: add
+objectClass: groupOfNames
+objectClass: dynamicObject
+cn: Meeting
+member: $BJORNSDN
+
+dn: $MEETINGDN
+changetype: modify
+add: member
+member: $JAJDN
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+	echo "ldapmodify failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+echo "Refreshing the meeting as $BJORNSDN..."
+$LDAPEXOP -D "$BJORNSDN" -w bjorn -h $LOCALHOST -p $PORT1 \
+	"refresh" "$MEETINGDN" "120" \
+	>> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+	echo "ldapexop failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+echo "Joining the meeting as $BABSDN..."
+$LDAPMODIFY -D "$BABSDN" -w bjensen -h $LOCALHOST -p $PORT1 \
+	>> $TESTOUT 2>&1 << EOMODS
+dn: $MEETINGDN
+changetype: modify
+add: member
+member: $BABSDN
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+	echo "ldapmodify failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+echo "Trying to add a member as $BABSDN (should fail)..."
+$LDAPMODIFY -D "$BABSDN" -w bjensen -h $LOCALHOST -p $PORT1 \
+	>> $TESTOUT 2>&1 << EOMODS
+dn: $MEETINGDN
+changetype: modify
+add: member
+member: $MELLIOTDN
+EOMODS
+RC=$?
+case $RC in
+0)
+	echo "ldapmodify should have failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+	;;
+50)
+	echo "ldapmodify failed ($RC)"
+	;;
+*)
+	echo "ldapmodify failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+	;;
+esac
+
+echo "Refreshing the meeting as $BABSDN..."
+$LDAPEXOP -D "$BABSDN" -w bjensen -h $LOCALHOST -p $PORT1 \
+	"refresh" "$MEETINGDN" "180" \
+	>> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+	echo "ldapexop failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+echo "Trying to refresh the meeting anonymously (should fail)..."
+$LDAPEXOP -h $LOCALHOST -p $PORT1 \
+	"refresh" "$MEETINGDN" "240" \
+	>> $TESTOUT 2>&1
+RC=$?
+if test $RC = 0 ; then
+	echo "ldapexop should have failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+echo "Trying to delete the meeting as $BABSDN (should fail)..."
+$LDAPMODIFY -D "$BABSDN" -w bjensen -h $LOCALHOST -p $PORT1 \
+	>> $TESTOUT 2>&1 << EOMODS
+dn: $MEETINGDN
+changetype: delete
+EOMODS
+RC=$?
+case $RC in
+0)
+	echo "ldapdelete should have failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+	;;
+50)
+	echo "ldapdelete failed ($RC)"
+	;;
+*)
+	echo "ldapdelete failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+	;;
+esac
+
+echo "Deleting the meeting as $BJORNSDN..."
+$LDAPMODIFY -D "$BJORNSDN" -w bjorn -h $LOCALHOST -p $PORT1 \
+	>> $TESTOUT 2>&1 << EOMODS
+dn: $MEETINGDN
+changetype: delete
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+	echo "ldapdelete failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+test $KILLSERVERS != no && kill -HUP $KILLPIDS
+
+LDIF=$DDSOUT
+
+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"
+exit 0
-- 
GitLab