diff --git a/CHANGES b/CHANGES
index 5d97e5c634d7a305fda8be9b351425d1a492710d..7746eb81718ed71f3c3aa89d355f321422b55070 100644
--- a/CHANGES
+++ b/CHANGES
@@ -46,6 +46,7 @@ OpenLDAP 2.4.12 Engineering
 	Build Environment
 		Fixed ODBC library detection (ITS#5602)
 		Added BDB 4.7 support (ITS#5523)
+		Added slapo-collect overlay with enhancements(ITS#5659)
 	Documentation
 		Added slapd-ldap(5), slapd-meta(5) noundeffilter (ITS#5614)
 		Fixed slapd-ldap(5), slapd-meta(5), slapo-pcache(5) schema requirements (ITS#5680)
diff --git a/configure.in b/configure.in
index 90bd03bec53e42d13a5e480cb102610ac5eefce7..0f28c8cd8178b95611ca20bd80d881fb1aef2fd8 100644
--- a/configure.in
+++ b/configure.in
@@ -333,6 +333,7 @@ dnl ----------------------------------------------------------------
 dnl SLAPD Overlay Options
 Overlays="accesslog \
 	auditlog \
+	collect \
 	constraint \
 	dds \
 	dyngroup \
@@ -358,6 +359,8 @@ OL_ARG_ENABLE(accesslog,[    --enable-accesslog	  In-Directory Access Logging ov
 	no, [no yes mod], ol_enable_overlays)
 OL_ARG_ENABLE(auditlog,[    --enable-auditlog	  Audit Logging overlay],
 	no, [no yes mod], ol_enable_overlays)
+OL_ARG_ENABLE(collect,[    --enable-collect	  Collect overlay],
+	no, [no yes mod], ol_enable_overlays)
 OL_ARG_ENABLE(constraint,[    --enable-constraint	  Attribute Constraint overlay],
 	no, [no yes mod], ol_enable_overlays)
 OL_ARG_ENABLE(dds,[    --enable-dds  	  Dynamic Directory Services overlay],
@@ -2758,6 +2761,18 @@ if test "$ol_enable_auditlog" != no ; then
 	AC_DEFINE_UNQUOTED(SLAPD_OVER_AUDITLOG,$MFLAG,[define for Audit Logging overlay])
 fi
 
+if test "$ol_enable_collect" != no ; then
+        BUILD_COLLECT=$ol_enable_collect
+        if test "$ol_enable_collect" = mod ; then
+                MFLAG=SLAPD_MOD_DYNAMIC
+                SLAPD_DYNAMIC_OVERLAYS="$SLAPD_DYNAMIC_OVERLAYS collect.la"
+        else
+                MFLAG=SLAPD_MOD_STATIC
+                SLAPD_STATIC_OVERLAYS="$SLAPD_STATIC_OVERLAYS collect.o"
+        fi
+        AC_DEFINE_UNQUOTED(SLAPD_OVER_COLLECT,$MFLAG,[define for Collect overlay])
+fi
+
 if test "$ol_enable_constraint" != no ; then
 	BUILD_CONSTRAINT=$ol_enable_constraint
 	if test "$ol_enable_constraint" = mod ; then
@@ -2995,6 +3010,7 @@ dnl backends
 dnl overlays
   AC_SUBST(BUILD_ACCESSLOG)
   AC_SUBST(BUILD_AUDITLOG)
+  AC_SUBST(BUILD_COLLECT)
   AC_SUBST(BUILD_CONSTRAINT)
   AC_SUBST(BUILD_DDS)
   AC_SUBST(BUILD_DENYOP)
diff --git a/include/portable.hin b/include/portable.hin
index 787d083ca8684935afe8d21f244147aef121edaa..fbdb2b848dab2906231f7cf5ef3f984f94bd11ba 100644
--- a/include/portable.hin
+++ b/include/portable.hin
@@ -957,6 +957,9 @@
 /* define for Audit Logging overlay */
 #undef SLAPD_OVER_AUDITLOG
 
+/* define for Collect overlay */
+#undef SLAPD_OVER_COLLECT
+
 /* define for Attribute Constraint overlay */
 #undef SLAPD_OVER_CONSTRAINT
 
diff --git a/servers/slapd/overlays/Makefile.in b/servers/slapd/overlays/Makefile.in
index 63249e38462ef6facf919140c687c3b8cb2751d8..07b4b1ebb46befbacde63728b6b28178be0faa2d 100644
--- a/servers/slapd/overlays/Makefile.in
+++ b/servers/slapd/overlays/Makefile.in
@@ -22,6 +22,7 @@ SRCS = overlays.c \
 	dynlist.c \
 	memberof.c \
 	pcache.c \
+	collect.c \
 	ppolicy.c \
 	refint.c \
 	retcode.c \
@@ -83,6 +84,9 @@ memberof.la : memberof.lo
 pcache.la : pcache.lo
 	$(LTLINK_MOD) -module -o $@ pcache.lo version.lo $(LINK_LIBS)
 
+collect.la : collect.lo
+	$(LTLINK_MOD) -module -o $@ collect.lo version.lo $(LINK_LIBS)
+
 ppolicy.la : ppolicy.lo
 	$(LTLINK_MOD) -module -o $@ ppolicy.lo version.lo $(LINK_LIBS) $(MODULES_LIBS)
 
diff --git a/servers/slapd/overlays/collect.c b/servers/slapd/overlays/collect.c
index fb8ef94f8eaf0716093daddea7e3d04c7e40049f..00834d3c06b546c102f07a832405987c09e9d6a6 100644
--- a/servers/slapd/overlays/collect.c
+++ b/servers/slapd/overlays/collect.c
@@ -43,14 +43,58 @@
 typedef struct collect_info {
 	struct collect_info *ci_next;
 	struct berval ci_dn;
-	AttributeDescription *ci_ad;
+	int ci_ad_num;
+	AttributeDescription *ci_ad[];
 } collect_info;
 
+/*
+ * inserts a collect_info into on->on_bi.bi_private taking into account
+ * order. this means longer dn's (i.e. more specific dn's) will be found
+ * first when searching, allowing some limited overlap of dn's
+ */
+static void
+insert_ordered( slap_overinst *on, collect_info *ci ) {
+	collect_info *find = on->on_bi.bi_private;
+	collect_info *prev = NULL;
+	int found = 0;
+
+	while (!found) {
+		if (find == NULL) {
+			if (prev == NULL) {
+				/* base case - empty list */
+				on->on_bi.bi_private = ci;
+				ci->ci_next = NULL;
+			} else {
+				/* final case - end of list */
+				prev->ci_next = ci;
+				ci->ci_next = NULL;
+			}
+			found = 1;
+		} else if (find->ci_dn.bv_len <= ci->ci_dn.bv_len) { 
+			/* insert into list here */
+			if (prev == NULL) {
+				/* entry is head of list */
+				ci->ci_next = on->on_bi.bi_private;
+				on->on_bi.bi_private = ci;
+			} else {
+				/* entry is not head of list */
+				prev->ci_next = ci;
+				ci->ci_next = find;
+			}
+			found = 1;
+		} else {
+			/* keep looking */
+			prev = find;
+			find = find->ci_next;
+		}
+	}
+}
+
 static int
 collect_cf( ConfigArgs *c )
 {
 	slap_overinst *on = (slap_overinst *)c->bi;
-	int rc = 1;
+	int rc = 1, idx;
 
 	switch( c->op ) {
 	case SLAP_CONFIG_EMIT:
@@ -58,14 +102,34 @@ collect_cf( ConfigArgs *c )
 		collect_info *ci;
 		for ( ci = on->on_bi.bi_private; ci; ci = ci->ci_next ) {
 			struct berval bv;
+			char *ptr;
 			int len;
 
-			bv.bv_len = ci->ci_dn.bv_len + STRLENOF("\"\" ") +
-				ci->ci_ad->ad_cname.bv_len;
+			/* calculate the length & malloc memory */
+			bv.bv_len = ci->ci_dn.bv_len + STRLENOF("\"\" ");
+			for (idx=0; idx<ci->ci_ad_num; idx++) {
+				bv.bv_len += ci->ci_ad[idx]->ad_cname.bv_len;
+				if (idx<(ci->ci_ad_num-1)) { 
+					bv.bv_len++;
+				}
+			}
 			bv.bv_val = ch_malloc( bv.bv_len + 1 );
-			len = snprintf( bv.bv_val, bv.bv_len + 1, "\"%s\" %s",
-				ci->ci_dn.bv_val, ci->ci_ad->ad_cname.bv_val );
-			assert( len == bv.bv_len );
+
+			/* copy the value and update len */
+			len = snprintf( bv.bv_val, bv.bv_len + 1, "\"%s\" ", 
+				ci->ci_dn.bv_val);
+			ptr = bv.bv_val + len;
+			for (idx=0; idx<ci->ci_ad_num; idx++) {
+				ptr = lutil_strncopy( ptr,
+					ci->ci_ad[idx]->ad_cname.bv_val,
+					ci->ci_ad[idx]->ad_cname.bv_len);
+				if (idx<(ci->ci_ad_num-1)) {
+					*ptr++ = ',';
+				}
+			}
+			*ptr = '\0';
+			bv.bv_len = ptr - bv.bv_val;
+
 			ber_bvarray_add( &c->rvalue_vals, &bv );
 			rc = 0;
 		}
@@ -98,8 +162,21 @@ collect_cf( ConfigArgs *c )
 		collect_info *ci;
 		struct berval bv, dn;
 		const char *text;
-		AttributeDescription *ad = NULL;
+		int idx, count=0;
+		char *arg;
 
+		/* count delimiters in attribute argument */
+		arg = strtok(c->argv[2], ",");
+		while (arg!=NULL) {
+			count++;
+			arg = strtok(NULL, ",");
+		}
+
+		/* allocate config info with room for attribute array */
+		ci = ch_malloc( sizeof( collect_info ) +
+			( sizeof (AttributeDescription *) * (count + 1)));
+
+		/* validate and normalize dn */
 		ber_str2bv( c->argv[1], 0, 0, &bv );
 		if ( dnNormalize( 0, NULL, NULL, &bv, &dn, NULL ) ) {
 			snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s invalid DN: \"%s\"",
@@ -108,22 +185,38 @@ collect_cf( ConfigArgs *c )
 				"%s: %s\n", c->log, c->cr_msg, 0 );
 			return ARG_BAD_CONF;
 		}
-		if ( slap_str2ad( c->argv[2], &ad, &text ) ) {
-			snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s attribute description unknown: \"%s\"",
-				c->argv[0], c->argv[2] );
-			Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
-				"%s: %s\n", c->log, c->cr_msg, 0 );
-			return ARG_BAD_CONF;
+
+		/* load attribute description for attribute list */
+		arg = c->argv[2];
+		for( idx=0; idx<count; idx++) {
+			ci->ci_ad[idx] = NULL;
+
+			if ( slap_str2ad( arg, &ci->ci_ad[idx], &text ) ) {
+				snprintf( c->cr_msg, sizeof( c->cr_msg ), 
+					"%s attribute description unknown: \"%s\"",
+					c->argv[0], arg);
+				Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+					"%s: %s\n", c->log, c->cr_msg, 0 );
+				return ARG_BAD_CONF;
+			}
+			while(*arg!='\0') {
+				arg++; /* skip to end of argument */
+			}
+			if (idx<count-1) {
+				arg++; /* skip inner delimiters */
+			}
 		}
 
 		/* The on->on_bi.bi_private pointer can be used for
 		 * anything this instance of the overlay needs.
 		 */
-		ci = ch_malloc( sizeof( collect_info ));
-		ci->ci_ad = ad;
+		ci->ci_ad[count] = NULL;
+		ci->ci_ad_num = count;
 		ci->ci_dn = dn;
-		ci->ci_next = on->on_bi.bi_private;
-		on->on_bi.bi_private = ci;
+
+		/* creates list of ci's ordered by dn length */ 
+		insert_ordered ( on, ci );
+
 		rc = 0;
 		}
 	}
@@ -166,6 +259,48 @@ collect_destroy(
 	return 0;
 }
 
+static int
+collect_modify( Operation *op, SlapReply *rs)
+{
+	slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+	collect_info *ci = on->on_bi.bi_private;
+	Modifications *ml;
+	char errMsg[100];
+	int rc, idx;
+
+	for ( ml = op->orm_modlist; ml != NULL; ml = ml->sml_next) {
+		for (; ci; ci=ci->ci_next ) {
+			/* Is this entry an ancestor of this collectinfo ? */
+			if (!dnIsSuffix(&op->o_req_ndn, &ci->ci_dn)) {
+				/* this collectinfo does not match */
+				continue;
+			}
+
+			/* Is this entry the same as the template DN ? */
+			if ( dn_match(&op->o_req_ndn, &ci->ci_dn)) {
+				/* all changes in this ci are allowed */
+				continue;
+			}
+
+			/* check for collect attributes - disallow modify if present */
+			for(idx=0; idx<ci->ci_ad_num; idx++) {
+				if (ml->sml_desc == ci->ci_ad[idx]) {
+					rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+					snprintf( errMsg, sizeof( errMsg ), 
+						"cannot change virtual attribute '%s'",
+						ci->ci_ad[idx]->ad_cname.bv_val);
+					rs->sr_text = errMsg;
+					send_ldap_result( op, rs );
+					return rs->sr_err;
+				}
+			}
+		}
+
+	}
+
+	return SLAP_CB_CONTINUE;
+}
+
 static int
 collect_response( Operation *op, SlapReply *rs )
 {
@@ -181,35 +316,51 @@ collect_response( Operation *op, SlapReply *rs )
 		op->o_bd->bd_info = (BackendInfo *)on->on_info;
 
 		for (; ci; ci=ci->ci_next ) {
-			BerVarray vals = NULL;
+			int idx=0;
+
+			/* Is this entry an ancestor of this collectinfo ? */
+			if (!dnIsSuffix(&rs->sr_entry->e_nname, &ci->ci_dn)) {
+				/* collectinfo does not match */
+				continue;
+			}
 
-			/* Is our configured entry an ancestor of this one? */
-			if ( !dnIsSuffix( &rs->sr_entry->e_nname, &ci->ci_dn ))
+			/* Is this entry the same as the template DN ? */
+			if ( dn_match(&rs->sr_entry->e_nname, &ci->ci_dn)) {
+				/* dont apply change to parent */
 				continue;
+			}
+
+			/* The current entry may live in a cache, so
+			* don't modify it directly. Make a copy and
+			* work with that instead.
+			*/
+			if ( !( rs->sr_flags & REP_ENTRY_MODIFIABLE )) {
+				rs->sr_entry = entry_dup( rs->sr_entry );
+				rs->sr_flags |= REP_ENTRY_MODIFIABLE |
+					REP_ENTRY_MUSTBEFREED;
+			}
 
-			/* Extract the values of the desired attribute from
-			 * the ancestor entry
-			 */
-			rc = backend_attribute( op, NULL, &ci->ci_dn, ci->ci_ad, &vals, ACL_READ );
-
-			/* If there are any values, merge them into the
-			 * current entry
-			 */
-			if ( vals ) {
-				/* The current entry may live in a cache, so
-				 * don't modify it directly. Make a copy and
-				 * work with that instead.
-				 */
-				if ( !( rs->sr_flags & REP_ENTRY_MODIFIABLE )) {
-					rs->sr_entry = entry_dup( rs->sr_entry );
-					rs->sr_flags |= REP_ENTRY_MODIFIABLE |
-						REP_ENTRY_MUSTBEFREED;
+			/* Loop for each attribute in this collectinfo */
+			for(idx=0; idx<ci->ci_ad_num; idx++) {
+				BerVarray vals = NULL;
+
+				/* Extract the values of the desired attribute from
+			 	 * the ancestor entry */
+				rc = backend_attribute( op, NULL, &ci->ci_dn, 
+					ci->ci_ad[idx], &vals, ACL_READ );
+
+				/* If there are any values, merge them into the
+			 	 * current search result
+			 	 */
+				if ( vals ) {
+					attr_merge( rs->sr_entry, ci->ci_ad[idx], 
+						vals, NULL );
+					ber_bvarray_free_x( vals, op->o_tmpmemctx );
 				}
-				attr_merge( rs->sr_entry, ci->ci_ad, vals, NULL );
-				ber_bvarray_free_x( vals, op->o_tmpmemctx );
 			}
 		}
 	}
+
 	/* Default is to just fall through to the normal processing */
 	return SLAP_CB_CONTINUE;
 }
@@ -221,6 +372,7 @@ int collect_initialize() {
 
 	collect.on_bi.bi_type = "collect";
 	collect.on_bi.bi_db_destroy = collect_destroy;
+	collect.on_bi.bi_op_modify = collect_modify;
 	collect.on_response = collect_response;
 
 	collect.on_bi.bi_cf_ocs = collectocs;