From 9f6f5491fe55d31cc5eceab59be6bcdc47ea4282 Mon Sep 17 00:00:00 2001
From: Pierangelo Masarati <ando@openldap.org>
Date: Sun, 25 Jul 2004 23:16:40 +0000
Subject: [PATCH] slightly rework user/operational attributes handling
 (including fixing a bug in the logic of the previous change to
 backend_operational()); cleanup; more improvements to slapo-rwm and
 back-relay

---
 doc/man/man5/slapd-relay.5               |  99 ++++++--
 doc/man/man5/slapo-rwm.5                 |  17 +-
 servers/slapd/attr.c                     |   3 +-
 servers/slapd/back-bdb/operational.c     |   2 +-
 servers/slapd/back-ldap/search.c         |   6 +-
 servers/slapd/back-ldap/unbind.c         |  16 +-
 servers/slapd/back-ldbm/operational.c    |   2 +-
 servers/slapd/back-meta/search.c         |   5 +-
 servers/slapd/back-monitor/operational.c |   2 +-
 servers/slapd/back-sql/compare.c         |   2 +-
 servers/slapd/back-sql/operational.c     |   2 +-
 servers/slapd/backend.c                  |  10 +-
 servers/slapd/overlays/rwm.c             | 116 +++++++--
 servers/slapd/overlays/rwm.h             |   7 +
 servers/slapd/overlays/rwmconf.c         |  18 +-
 servers/slapd/overlays/rwmdn.c           |  67 ++++--
 servers/slapd/overlays/rwmmap.c          | 294 ++++++++++++++++++++---
 servers/slapd/proto-slap.h               |   2 +
 servers/slapd/result.c                   |  21 +-
 servers/slapd/slap.h                     |  16 +-
 tests/data/relay.out                     |  69 +++++-
 tests/scripts/defines.sh                 |   2 +-
 tests/scripts/test030-relay              |  75 +++++-
 23 files changed, 722 insertions(+), 131 deletions(-)

diff --git a/doc/man/man5/slapd-relay.5 b/doc/man/man5/slapd-relay.5
index ad8466eae2..5a165f3b5b 100644
--- a/doc/man/man5/slapd-relay.5
+++ b/doc/man/man5/slapd-relay.5
@@ -17,34 +17,63 @@ It requires the
 .LP
 This backend and the above mentioned overlay are experimental.
 .SH CONFIGURATION
-These
+The following
 .B slapd.conf
-options apply to the relay backend database.
+directives apply to the relay backend database.
 That is, they must follow a "database relay" line and come before any
 subsequent "backend" or "database" lines.
 Other database options are described in the
 .BR slapd.conf (5)
-manual page.
+manual page; only the
+.B suffix
+directive is required by the 
+.I relay
+backend.
 .TP
 .B relay <real naming context> [massage]
 The naming context of the database that is presented 
 under a virtual naming context.
-The presence of the directive implies that one single database
+The presence of this directive implies that one specific database,
+i.e. the one serving the
+.BR "real naming context" ,
 will be presented under a virtual naming context.
 This directive automatically instantiates the 
-.B rwm
-.BR overlay .
+.IR "rwm overlay" .
 If the optional
 .B massage
 keyword is present, the suffix massaging is automatically
-configured as well.
+configured as well; otherwise, specific massaging instructions
+are required by means of the
+.I rewrite
+directives described in
+.BR slapo-rwm (5).
+
+.SH ACCESS RULES
+One important issue is that access rules are based on the identity
+that issued the operation.
+After massaging from the virtual to the real naming context, the
+frontend sees the operation as performed by the identty in the
+real naming context.
+Moreover, since
+.B back-relay
+bypasses the real database frontend operations by short-circuiting
+operations thru the internal backend API, the original database
+access rules do not apply but in selected cases, i.e. when the
+backend itself applies access control.
+As a consequence, the instances of the relay database must provide
+own access rules that are consistent with those of the original
+database, possibly adding further specific restrictions.
+So, access rules in the
+.B relay
+database must refer to identities in the real naming context.
+Examples are reported in the EXAMPLES section.
 
 .SH SCENARIOS
 .LP
 If no
 .B relay
 directive is given, the 
-.B relay
+.I relay
 database does not refer to any specific database, but the most
 appropriate one is looked-up after rewriting the request DN
 for the operation that is being handled.
@@ -52,7 +81,8 @@ for the operation that is being handled.
 This allows to write carefully crafted rewrite rules that
 cause some of the requests to be directed to one database, and
 some to another; e.g., authentication can be mapped to one 
-database, and searches to another and so.
+database, and searches to another, or different target databases
+can be selected based on the DN of the request, and so.
 .LP
 Another possibility is to map the same operation to different 
 databases based on details of the virtual naming context,
@@ -61,7 +91,7 @@ e.g. groups on one database and persons on another.
 .SH Caveats
 The
 .B rwm overlay
-is far from complete.
+is experimental.
 .LP
 .SH EXAMPLES
 To implement a plain virtual naming context mapping
@@ -84,6 +114,10 @@ that looks up the real naming context for each operation, use
           "dc=real,dc=naming,dc=context"
 .fi
 .LP
+This is useful, for instance, to relay different databases that
+share the terminal portion of the naming context (the one that
+is rewritten).
+.LP
 To implement the old-fashioned suffixalias, e.g. mapping
 the virtual to the real naming context, but not the results
 back from the real to the virtual naming context, use
@@ -95,11 +129,11 @@ back from the real to the virtual naming context, use
   rewriteEngine   on
   rewriteContext  default
   rewriteRule     "dc=virtual,dc=naming,dc=context"
-          "dc=real,dc=naming,dc=context" ":"
-  rewriteRule     searchFilter
-  rewriteRule     searchResult
-  rewriteRule     searchResultAttrDN
-  rewriteRule     matchedDN
+          "dc=real,dc=naming,dc=context" ":@"
+  rewriteContext  searchFilter
+  rewriteContext  searchEntryDN
+  rewriteContext  searchAttrDN
+  rewriteContext  matchedDN
 .fi
 .LP
 Note that the virtual database is bound to a single real database,
@@ -108,10 +142,45 @@ so the
 is automatically instantiated, but the rewrite rules 
 are written explicitly to map all the virtual to real 
 naming context data flow, but none of the real to virtual.
+.LP
+Access rules:
+.LP
+.nf
+  database        bdb
+  suffix          "dc=example,dc=com"
+  # skip...
+  access to dn.subtree="dc=example,dc=com"
+          by dn.exact="cn=Supervisor,dc=example,dc=com" write
+          by * read
+
+  database        relay
+  suffix          "o=Example,c=US"
+  relay           "dc=example,dc=com" massage
+  # skip ...
+  access to dn.subtree="o=Example,c=US"
+          by dn.exact="cn=Supervisor,dc=example,dc=com" write
+          by dn.exact="cn=Relay Supervisor,dc=example,dc=com" write
+          by * read
+.fi
+.LP
+Note that, in both databases, the identities (the 
+.B <who> 
+clause) are in the
+.BR "real naming context" ,
+i.e.
+.BR "`dc=example,dc=com'" ,
+while the targets (the 
+.B <what> 
+clause) are in the
+.B real
+and in the
+.BR "virtual naming context" ,
+respectively.
 .SH FILES
 .TP
 ETCDIR/slapd.conf
 default slapd configuration file
 .SH SEE ALSO
 .BR slapd.conf (5),
+.BR slapo-rwm (5),
 .BR slapd (8).
diff --git a/doc/man/man5/slapo-rwm.5 b/doc/man/man5/slapo-rwm.5
index 1541477ebe..658ee218da 100644
--- a/doc/man/man5/slapo-rwm.5
+++ b/doc/man/man5/slapo-rwm.5
@@ -52,6 +52,15 @@ with
 .IR "distinguishedName syntax" ,
 requires the knowledge of the attributeType syntax.
 See the REWRITING section for details.
+.LP
+Note that when mapping DN-valued attributes from local to remote,
+first the DN is rewritten, and then the attributeType is mapped;
+while mapping from remote to local, first the attributeType is mapped,
+and then the DN is rewritten.
+As such, it is important that the local attributeType is appropriately
+defined as using the distinguishedName syntax.
+Also, note that there are DN-related syntaxes, like nameAndOptionalUID,
+whose values are currenlty not rewritten.
 .SH SUFFIX MASSAGING
 A basic feature of the
 .B rwm
@@ -287,13 +296,13 @@ searchFilterAttrDN   search
 compareDN            compare
 compareAttrDN        compare AVA
 addDN                add
-addAttrDN            add AVA
+addAttrDN            add AVA (including "ref")
 modifyDN             modify
-modifyAttrDN         modify AVA
+modifyAttrDN         modify AVA (including "ref")
 modrDN               modrdn
 newSuperiorDN        modrdn
 deleteDN             delete
-exopPasswdDN         passwd exop DN if proxy
+exopPasswdDN         passwd exop DN
 .fi
 .RE
 .LP
@@ -308,6 +317,8 @@ searchAttrDN         search AVA (only if defined; defaults
                      attributes of search results)
 matchedDN            all ops (only if applicable; defaults
                      to searchEntryDN)
+referralDN           all ops (only if applicable; defaults
+                     to searchEntryDN)
 .fi
 .RE
 .LP
diff --git a/servers/slapd/attr.c b/servers/slapd/attr.c
index afee05201a..762c90cb6f 100644
--- a/servers/slapd/attr.c
+++ b/servers/slapd/attr.c
@@ -44,8 +44,9 @@
 void
 attr_free( Attribute *a )
 {
+	if ( a->a_nvals && a->a_nvals != a->a_vals )
+		ber_bvarray_free( a->a_nvals );
 	ber_bvarray_free( a->a_vals );
-	if (a->a_nvals != a->a_vals) ber_bvarray_free( a->a_nvals );
 	free( a );
 }
 
diff --git a/servers/slapd/back-bdb/operational.c b/servers/slapd/back-bdb/operational.c
index 5b72c32f03..3ff7aa2bc9 100644
--- a/servers/slapd/back-bdb/operational.c
+++ b/servers/slapd/back-bdb/operational.c
@@ -100,7 +100,7 @@ bdb_operational(
 	for ( ap = &rs->sr_operational_attrs; *ap; ap = &(*ap)->a_next )
 		/* just count */ ;
 
-	if ( rs->sr_opattrs == SLAP_OPATTRS ||
+	if ( SLAP_OPATTRS( rs->sr_attr_flags ) ||
 			ad_inlist( slap_schema.si_ad_hasSubordinates, rs->sr_attrs ) )
 	{
 		int	hasSubordinates;
diff --git a/servers/slapd/back-ldap/search.c b/servers/slapd/back-ldap/search.c
index 5d83ca441e..753d7fa6ab 100644
--- a/servers/slapd/back-ldap/search.c
+++ b/servers/slapd/back-ldap/search.c
@@ -41,8 +41,6 @@ ldap_build_entry( Operation *op, LDAPMessage *e, Entry *ent,
 	 struct berval *bdn, int flags );
 #define LDAP_BUILD_ENTRY_PRIVATE	0x01
 
-static struct berval dummy = BER_BVNULL;
-
 int
 ldap_back_search(
     Operation	*op,
@@ -200,7 +198,7 @@ fail:;
 					ent.e_attrs = a->a_next;
 
 					v = a->a_vals;
-					if ( a->a_vals != &dummy ) {
+					if ( a->a_vals != &slap_dummy_bv ) {
 						ber_bvarray_free(a->a_vals);
 					}
 					if ( a->a_nvals != v ) {
@@ -453,7 +451,7 @@ ldap_build_entry(
 			 * values result filter
 			 */
 			if ( private ) {
-				attr->a_vals = &dummy;
+				attr->a_vals = &slap_dummy_bv;
 			} else {
 				attr->a_vals = ch_malloc( sizeof( struct berval ) );
 				BER_BVZERO( &attr->a_vals[0] );
diff --git a/servers/slapd/back-ldap/unbind.c b/servers/slapd/back-ldap/unbind.c
index d2d416a569..66e7139409 100644
--- a/servers/slapd/back-ldap/unbind.c
+++ b/servers/slapd/back-ldap/unbind.c
@@ -38,7 +38,7 @@ ldap_back_conn_destroy(
 )
 {
 	struct ldapinfo	*li = (struct ldapinfo *) be->be_private;
-	struct ldapconn *lc, lc_curr;
+	struct ldapconn *lc = NULL, lc_curr;
 
 #ifdef NEW_LOGGING
 	LDAP_LOG( BACK_LDAP, INFO,
@@ -56,6 +56,13 @@ ldap_back_conn_destroy(
 	lc = avl_delete( &li->conntree, (caddr_t)&lc_curr, ldap_back_conn_cmp );
 	ldap_pvt_thread_mutex_unlock( &li->conn_mutex );
 
+#ifdef ENABLE_REWRITE
+	/*
+	 * Cleanup rewrite session
+	 */
+	rewrite_session_delete( li->rwmap.rwm_rw, conn );
+#endif /* ENABLE_REWRITE */
+
 	if (lc) {
 #ifdef NEW_LOGGING
 		LDAP_LOG( BACK_LDAP, DETAIL1, 
@@ -67,13 +74,6 @@ ldap_back_conn_destroy(
 			lc->conn->c_connid, 0, 0 );
 #endif
 
-#ifdef ENABLE_REWRITE
-		/*
-		 * Cleanup rewrite session
-		 */
-		rewrite_session_delete( li->rwmap.rwm_rw, conn );
-#endif /* ENABLE_REWRITE */
-
 		/*
 		 * Needs a test because the handler may be corrupted,
 		 * and calling ldap_unbind on a corrupted header results
diff --git a/servers/slapd/back-ldbm/operational.c b/servers/slapd/back-ldbm/operational.c
index e3070e5e19..de927c72bd 100644
--- a/servers/slapd/back-ldbm/operational.c
+++ b/servers/slapd/back-ldbm/operational.c
@@ -60,7 +60,7 @@ ldbm_back_operational(
 	for ( ap = &rs->sr_operational_attrs; *ap; ap = &(*ap)->a_next )
 		/* just count */ ;
 
-	if ( rs->sr_opattrs == SLAP_OPATTRS ||
+	if ( SLAP_OPATTRS( rs->sr_attr_flags ) ||
 			ad_inlist( slap_schema.si_ad_hasSubordinates, rs->sr_attrs ) )
 	{
 		int	hs;
diff --git a/servers/slapd/back-meta/search.c b/servers/slapd/back-meta/search.c
index 1a2bf3d85d..91311fbe51 100644
--- a/servers/slapd/back-meta/search.c
+++ b/servers/slapd/back-meta/search.c
@@ -556,7 +556,6 @@ meta_send_entry(
 	Entry 			ent = {0};
 	BerElement 		ber = *e->lm_ber;
 	Attribute 		*attr, **attrp;
-	struct berval 		dummy = BER_BVNULL;
 	struct berval 		*bv, bdn;
 	const char 		*text;
 	dncookie		dc;
@@ -652,7 +651,7 @@ meta_send_entry(
 
 		if ( ber_scanf( &ber, "[W]", &attr->a_vals ) == LBER_ERROR 
 				|| attr->a_vals == NULL ) {
-			attr->a_vals = &dummy;
+			attr->a_vals = &slap_dummy_bv;
 
 		} else if ( attr->a_desc == slap_schema.si_ad_objectClass
 				|| attr->a_desc == slap_schema.si_ad_structuralObjectClass ) {
@@ -724,7 +723,7 @@ meta_send_entry(
 	while ( ent.e_attrs ) {
 		attr = ent.e_attrs;
 		ent.e_attrs = attr->a_next;
-		if ( attr->a_vals != &dummy ) {
+		if ( attr->a_vals != &slap_dummy_bv ) {
 			ber_bvarray_free( attr->a_vals );
 		}
 		free( attr );
diff --git a/servers/slapd/back-monitor/operational.c b/servers/slapd/back-monitor/operational.c
index 3fc3d9689c..6c78297a85 100644
--- a/servers/slapd/back-monitor/operational.c
+++ b/servers/slapd/back-monitor/operational.c
@@ -46,7 +46,7 @@ monitor_back_operational(
 	for ( ap = &rs->sr_operational_attrs; *ap; ap = &(*ap)->a_next )
 		/* just count */ ;
 
-	if ( rs->sr_opattrs == SLAP_OPATTRS ||
+	if ( SLAP_OPATTRS( rs->sr_attr_flags ) ||
 			ad_inlist( slap_schema.si_ad_hasSubordinates, rs->sr_attrs ) )
 	{
 		int			hs;
diff --git a/servers/slapd/back-sql/compare.c b/servers/slapd/back-sql/compare.c
index 4a32a0e279..b5a4582a81 100644
--- a/servers/slapd/back-sql/compare.c
+++ b/servers/slapd/back-sql/compare.c
@@ -95,7 +95,7 @@ backsql_compare( Operation *op, SlapReply *rs )
 
 		nrs.sr_attrs = anlist;
 		nrs.sr_entry = &user_entry;
-		nrs.sr_opattrs = SLAP_OPATTRS_NO;
+		nrs.sr_attr_flags = SLAP_OPATTRS_NO;
 		nrs.sr_operational_attrs = NULL;
 
 		rs->sr_err = backsql_operational( op, &nrs );
diff --git a/servers/slapd/back-sql/operational.c b/servers/slapd/back-sql/operational.c
index 0f66e1495c..7f70185734 100644
--- a/servers/slapd/back-sql/operational.c
+++ b/servers/slapd/back-sql/operational.c
@@ -49,7 +49,7 @@ backsql_operational(
 	for ( ap = &rs->sr_operational_attrs; *ap; ap = &(*ap)->a_next )
 		/* just count */ ;
 
-	if ( ( rs->sr_opattrs == SLAP_OPATTRS || ad_inlist( slap_schema.si_ad_hasSubordinates, rs->sr_attrs ) ) 
+	if ( ( SLAP_OPATTRS( rs->sr_attr_flags ) || ad_inlist( slap_schema.si_ad_hasSubordinates, rs->sr_attrs ) ) 
 			&& attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_hasSubordinates ) == NULL ) {
 		
 		rc = backsql_get_db_conn( op, &dbh );
diff --git a/servers/slapd/backend.c b/servers/slapd/backend.c
index 59a1267f67..3524370b4e 100644
--- a/servers/slapd/backend.c
+++ b/servers/slapd/backend.c
@@ -1502,19 +1502,25 @@ int backend_operational(
 	 * and the backend supports specific operational attributes, 
 	 * add them to the attribute list
 	 */
-	if ( rs->sr_opattrs == SLAP_OPATTRS || ( op->ors_attrs &&
+	if ( SLAP_OPATTRS( rs->sr_attr_flags ) || ( op->ors_attrs &&
 		ad_inlist( slap_schema.si_ad_subschemaSubentry, op->ors_attrs )) ) {
 		*ap = slap_operational_subschemaSubentry( op->o_bd );
 
 		ap = &(*ap)->a_next;
 	}
 
-	if ( ( rs->sr_opattrs == SLAP_OPATTRS || op->ors_attrs ) && op->o_bd &&
+	if ( ( SLAP_OPATTRS( rs->sr_attr_flags ) || op->ors_attrs ) && op->o_bd &&
 		op->o_bd->be_operational != NULL )
 	{
+		Attribute	*a;
+		
+		a = rs->sr_operational_attrs;
 		rs->sr_operational_attrs = NULL;
 		rc = op->o_bd->be_operational( op, rs );
 		*ap = rs->sr_operational_attrs;
+		if ( a != NULL ) {
+			rs->sr_operational_attrs = a;
+		}
 
 		for ( ; *ap; ap = &(*ap)->a_next )
 			/* just count them */ ;
diff --git a/servers/slapd/overlays/rwm.c b/servers/slapd/overlays/rwm.c
index f5c05dcd27..f1bf2fdce7 100644
--- a/servers/slapd/overlays/rwm.c
+++ b/servers/slapd/overlays/rwm.c
@@ -40,7 +40,7 @@ rwm_op_dn_massage( Operation *op, SlapReply *rs, void *cookie )
 	dncookie		dc;
 
 	/*
-	 * Rewrite the bind dn if needed
+	 * Rewrite the dn if needed
 	 */
 	dc.rwmap = rwmap;
 #ifdef ENABLE_REWRITE
@@ -73,14 +73,11 @@ rwm_op_dn_massage( Operation *op, SlapReply *rs, void *cookie )
 	op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx );
 	if ( dnp ) {
 		op->o_tmpfree( op->o_req_dn.bv_val, op->o_tmpmemctx );
-	}
-
-	op->o_req_ndn = ndn;
-	if ( dnp ) {
 		op->o_req_dn = dn;
 	} else {
 		op->o_req_dn = ndn;
 	}
+	op->o_req_ndn = ndn;
 
 	return LDAP_SUCCESS;
 }
@@ -110,8 +107,8 @@ rwm_add( Operation *op, SlapReply *rs )
 	}
 
 	if ( olddn != op->o_req_dn.bv_val ) {
-		ber_memfree( op->ora_e->e_name.bv_val );
-		ber_memfree( op->ora_e->e_nname.bv_val );
+		ch_free( op->ora_e->e_name.bv_val );
+		ch_free( op->ora_e->e_nname.bv_val );
 
 		ber_dupbv( &op->ora_e->e_name, &op->o_req_dn );
 		ber_dupbv( &op->ora_e->e_nname, &op->o_req_ndn );
@@ -151,8 +148,23 @@ rwm_add( Operation *op, SlapReply *rs )
 			if ( rc ) {
 				goto cleanup_attr;
 			}
+
+		} else if ( (*ap)->a_desc == slap_schema.si_ad_ref ) {
+#ifdef ENABLE_REWRITE
+			rc = rwm_referral_rewrite( op, rs, "addAttrDN",
+					(*ap)->a_vals,
+					(*ap)->a_nvals ? &(*ap)->a_nvals : NULL );
+#else
+			rc = 1;
+			rc = rwm_referral_rewrite( op, rs, &rc, (*ap)->a_vals,
+					(*ap)->a_nvals ? &(*ap)->a_nvals : NULL );
+#endif
+			if ( rc != LDAP_SUCCESS ) {
+				goto cleanup_attr;
+			}
 		}
 
+
 next_attr:;
 		ap = &(*ap)->a_next;
 		continue;
@@ -261,13 +273,18 @@ rwm_compare( Operation *op, SlapReply *rs )
 		}
 		if ( op->orc_ava->aa_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName )
 		{
+			struct berval	*mapped_valsp[2];
+			
+			mapped_valsp[0] = &mapped_vals[0];
+			mapped_valsp[1] = &mapped_vals[1];
+
 			mapped_vals[0] = op->orc_ava->aa_value;
 
 #ifdef ENABLE_REWRITE
-			rc = rwm_dnattr_rewrite( op, rs, "compareAttrDN", mapped_vals, NULL );
+			rc = rwm_dnattr_rewrite( op, rs, "compareAttrDN", NULL, mapped_valsp );
 #else
 			rc = 1;
-			rc = rwm_dnattr_rewrite( op, rs, &rc, mapped_vals, NULL );
+			rc = rwm_dnattr_rewrite( op, rs, &rc, NULL, mapped_valsp );
 #endif
 
 			if ( rc != LDAP_SUCCESS ) {
@@ -276,10 +293,7 @@ rwm_compare( Operation *op, SlapReply *rs )
 				return -1;
 			}
 
-			if ( mapped_vals[0].bv_val != op->orc_ava->aa_value.bv_val ) {
-				free( op->orc_ava->aa_value.bv_val );
-				op->orc_ava->aa_value = mapped_vals[0];
-			}
+			op->orc_ava->aa_value = mapped_vals[0];
 		}
 	}
 
@@ -397,7 +411,7 @@ rwm_modify( Operation *op, SlapReply *rs )
 						slap_schema.si_syn_distinguishedName )
 				{
 #ifdef ENABLE_REWRITE
-					rc = rwm_dnattr_rewrite( op, rs, "modifyDN",
+					rc = rwm_dnattr_rewrite( op, rs, "modifyAttrDN",
 							(*mlp)->sml_values,
 							(*mlp)->sml_nvalues ? &(*mlp)->sml_nvalues : NULL );
 #else
@@ -406,6 +420,20 @@ rwm_modify( Operation *op, SlapReply *rs )
 							(*mlp)->sml_values,
 							(*mlp)->sml_nvalues ? &(*mlp)->sml_nvalues : NULL );
 #endif
+				} else if ( (*mlp)->sml_desc == slap_schema.si_ad_ref ) {
+#ifdef ENABLE_REWRITE
+					rc = rwm_referral_rewrite( op, rs, "modifyAttrDN",
+							(*mlp)->sml_values,
+							(*mlp)->sml_nvalues ? &(*mlp)->sml_nvalues : NULL );
+#else
+					rc = 1;
+					rc = rwm_referral_rewrite( op, rs, &rc,
+							(*mlp)->sml_values,
+							(*mlp)->sml_nvalues ? &(*mlp)->sml_nvalues : NULL );
+#endif
+					if ( rc != LDAP_SUCCESS ) {
+						goto cleanup_mod;
+					}
 				}
 
 				if ( rc != LDAP_SUCCESS ) {
@@ -470,6 +498,9 @@ rwm_modrdn( Operation *op, SlapReply *rs )
 		}
 	}
 
+	/*
+	 * Rewrite the dn, if needed
+ 	 */
 #ifdef ENABLE_REWRITE
 	rc = rwm_op_dn_massage( op, rs, "renameDN" );
 #else
@@ -539,7 +570,7 @@ rwm_search( Operation *op, SlapReply *rs )
 	}
 
 	/*
-	 * Rewrite the bind dn if needed
+	 * Rewrite the dn if needed
 	 */
 	dc.rwmap = rwmap;
 #ifdef ENABLE_REWRITE
@@ -698,13 +729,12 @@ rwm_attrs( Operation *op, SlapReply *rs, Attribute** a_first )
 	Attribute		**ap;
 
 	/*
-	 * Rewrite the dn of the result, if needed
+	 * Rewrite the dn attrs, if needed
 	 */
 	dc.rwmap = rwmap;
 #ifdef ENABLE_REWRITE
 	dc.conn = op->o_conn;
 	dc.rs = NULL; 
-	dc.ctx = "searchAttrDN";
 #else
 	dc.tofrom = 0;
 	dc.normalized = 0;
@@ -728,11 +758,13 @@ rwm_attrs( Operation *op, SlapReply *rs, Attribute** a_first )
 		int			last;
 		Attribute		*a;
 
-		if ( rs->sr_opattrs == SLAP_OPATTRS && is_at_operational( (*ap)->a_desc->ad_type ) )
+		if ( SLAP_OPATTRS( rs->sr_attr_flags ) && is_at_operational( (*ap)->a_desc->ad_type ) )
 		{
 			/* go on */ ;
 			
-		} else if ( op->ors_attrs != NULL && !ad_inlist( (*ap)->a_desc, op->ors_attrs ) )
+		} else if ( op->ors_attrs != NULL && 
+				!SLAP_USERATTRS( rs->sr_attr_flags ) && 
+				!ad_inlist( (*ap)->a_desc, op->ors_attrs ) )
 		{
 			goto cleanup_attr;
 		}
@@ -781,7 +813,7 @@ rwm_attrs( Operation *op, SlapReply *rs, Attribute** a_first )
 					 * the value is replaced by
 					 * ch_alloc'ed memory
 					 */
-					free( bv[0].bv_val );
+					ch_free( bv[0].bv_val );
 					ber_dupbv( &bv[0], &mapped );
 				}
 			}
@@ -801,10 +833,22 @@ rwm_attrs( Operation *op, SlapReply *rs, Attribute** a_first )
 		} else if ( (*ap)->a_desc->ad_type->sat_syntax ==
 				slap_schema.si_syn_distinguishedName )
 		{
+#ifdef ENABLE_REWRITE
+			dc.ctx = "searchAttrDN";
+#endif
 			rc = rwm_dnattr_result_rewrite( &dc, (*ap)->a_vals );
 			if ( rc != LDAP_SUCCESS ) {
 				goto cleanup_attr;
 			}
+
+		} else if ( (*ap)->a_desc == slap_schema.si_ad_ref ) {
+#ifdef ENABLE_REWRITE
+			dc.ctx = "searchAttrDN";
+#endif
+			rc = rwm_referral_result_rewrite( &dc, (*ap)->a_vals );
+			if ( rc != LDAP_SUCCESS ) {
+				goto cleanup_attr;
+			}
 		}
 
 		if ( m != NULL ) {
@@ -882,12 +926,13 @@ rwm_send_entry( Operation *op, SlapReply *rs )
 	 */
 	rc = rwm_dn_massage( &dc, &e->e_name, &dn, &ndn );
 	if ( rc != LDAP_SUCCESS ) {
+		rc = 1;
 		goto fail;
 	}
 
 	if ( e->e_name.bv_val != dn.bv_val ) {
-		free( e->e_name.bv_val );
-		free( e->e_nname.bv_val );
+		ch_free( e->e_name.bv_val );
+		ch_free( e->e_nname.bv_val );
 
 		e->e_name = dn;
 		e->e_nname = ndn;
@@ -899,7 +944,7 @@ rwm_send_entry( Operation *op, SlapReply *rs )
 	/* FIXME: the entries are in the remote mapping form;
 	 * so we need to select those attributes we are willing
 	 * to return, and remap them accordingly */
-	rwm_attrs( op, rs, &e->e_attrs );
+	(void)rwm_attrs( op, rs, &e->e_attrs );
 
 	rs->sr_entry = e;
 	rs->sr_flags = flags;
@@ -1060,6 +1105,10 @@ rwm_m_config(
 static int
 rwm_response( Operation *op, SlapReply *rs )
 {
+	slap_overinst		*on = (slap_overinst *)op->o_bd->bd_info;
+	struct ldaprwmap	*rwmap = 
+			(struct ldaprwmap *)on->on_bi.bi_private;
+
 	int		rc;
 
 	if ( op->o_tag == LDAP_REQ_SEARCH && rs->sr_type == REP_SEARCH ) {
@@ -1083,6 +1132,27 @@ rwm_response( Operation *op, SlapReply *rs )
 	case LDAP_REQ_MODIFY:
 	case LDAP_REQ_COMPARE:
 	case LDAP_REQ_EXTENDED:
+		if ( rs->sr_ref ) {
+			dncookie		dc;
+
+			/*
+			 * Rewrite the dn of the referrals, if needed
+			 */
+			dc.rwmap = rwmap;
+#ifdef ENABLE_REWRITE
+			dc.conn = op->o_conn;
+			dc.rs = NULL; 
+			dc.ctx = "referralDN";
+#else
+			dc.tofrom = 0;
+			dc.normalized = 0;
+#endif
+			rc = rwm_referral_result_rewrite( &dc, rs->sr_ref );
+			if ( rc != LDAP_SUCCESS ) {
+				rc = 1;
+				break;
+			}
+		}
 		rc = rwm_matched( op, rs );
 		break;
 
diff --git a/servers/slapd/overlays/rwm.h b/servers/slapd/overlays/rwm.h
index 9d3cf31f41..3bdee4f656 100644
--- a/servers/slapd/overlays/rwm.h
+++ b/servers/slapd/overlays/rwm.h
@@ -156,7 +156,14 @@ extern int rwm_dnattr_rewrite(
 	void			*cookie,
 	BerVarray		a_vals,
 	BerVarray		*pa_nvals );
+extern int rwm_referral_rewrite(
+	Operation		*op,
+	SlapReply		*rs,
+	void			*cookie,
+	BerVarray		a_vals,
+	BerVarray		*pa_nvals );
 extern int rwm_dnattr_result_rewrite( dncookie *dc, BerVarray a_vals );
+extern int rwm_referral_result_rewrite( dncookie *dc, BerVarray a_vals );
 
 LDAP_END_DECL
 
diff --git a/servers/slapd/overlays/rwmconf.c b/servers/slapd/overlays/rwmconf.c
index be1e7f870e..f252298826 100644
--- a/servers/slapd/overlays/rwmconf.c
+++ b/servers/slapd/overlays/rwmconf.c
@@ -244,9 +244,12 @@ rwm_suffix_massage_regexize( const char *s )
 			p = r + 1, i++ )
 		;
 
-	res = ch_calloc( sizeof( char ), strlen( s ) + 4 + 4*i + 1 );
+	res = ch_calloc( sizeof( char ), strlen( s )
+			+ STRLENOF( "((.+),)?" )
+			+ STRLENOF( "[ ]?" ) * i
+			+ STRLENOF( "$" ) + 1 );
 
-	ptr = lutil_strcopy( res, "(.*)" );
+	ptr = lutil_strcopy( res, "((.+),)?" );
 	for ( i = 0, p = s;
 			( r = strchr( p, ',' ) ) != NULL;
 			p = r + 1 , i++ ) {
@@ -257,7 +260,9 @@ rwm_suffix_massage_regexize( const char *s )
 			r++;
 		}
 	}
-	lutil_strcopy( ptr, p );
+	ptr = lutil_strcopy( ptr, p );
+	ptr[0] = '$';
+	ptr[1] = '\0';
 
 	return res;
 }
@@ -333,6 +338,13 @@ rwm_suffix_massage_config(
 	rargv[ 4 ] = NULL;
 	rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
 
+	rargv[ 0 ] = "rewriteContext";
+	rargv[ 1 ] = "referralDN";
+	rargv[ 2 ] = "alias";
+	rargv[ 3 ] = "searchEntryDN";
+	rargv[ 4 ] = NULL;
+	rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+
 	rargv[ 0 ] = "rewriteContext";
 	rargv[ 1 ] = "searchAttrDN";
 	rargv[ 2 ] = "alias";
diff --git a/servers/slapd/overlays/rwmdn.c b/servers/slapd/overlays/rwmdn.c
index 85c929b2c3..4892e4365e 100644
--- a/servers/slapd/overlays/rwmdn.c
+++ b/servers/slapd/overlays/rwmdn.c
@@ -48,6 +48,12 @@ rwm_dn_massage(
 	int		rc = 0;
 	struct berval	mdn;
 
+	assert( in );
+
+	if ( dn == NULL && ndn == NULL ) {
+		return LDAP_OTHER;
+	}
+
 	rc = rewrite_session( dc->rwmap->rwm_rw, dc->ctx,
 			( in->bv_len ? in->bv_val : "" ), 
 			dc->conn, &mdn.bv_val );
@@ -74,9 +80,13 @@ rwm_dn_massage(
 		} else {
 			/* we assume the input string is already in pretty form,
 			 * and that the normalized version is already available */
-			*dn = *in;
-			if ( ndn != NULL ) {
-				BER_BVZERO( ndn );
+			if ( dn ) {
+				*dn = *in;
+				if ( ndn ) {
+					BER_BVZERO( ndn );
+				}
+			} else {
+				*ndn = *in;
 			}
 			rc = LDAP_SUCCESS;
 		}
@@ -84,11 +94,11 @@ rwm_dn_massage(
 #ifdef NEW_LOGGING
 		LDAP_LOG( BACK_LDAP, DETAIL1, 
 			"[rw] %s: \"%s\" -> \"%s\"\n",
-			dc->ctx, in->bv_val, dn->bv_val );
+			dc->ctx, in->bv_val, dn ? dn->bv_val : ndn->bv_val );
 #else /* !NEW_LOGGING */
 		Debug( LDAP_DEBUG_ARGS,
 			"[rw] %s: \"%s\" -> \"%s\"\n",
-			dc->ctx, in->bv_val, dn->bv_val );		
+			dc->ctx, in->bv_val, dn ? dn->bv_val : ndn->bv_val );
 #endif /* !NEW_LOGGING */
 		break;
  		
@@ -131,15 +141,29 @@ rwm_dn_massage(
 			normal = BER_BVNULL,
 			*in = tmpin;
 
-	assert( dn );
+	if ( dn == NULL && ndn == NULL ) {
+		return LDAP_OTHER;
+	}
 
 	if ( in == NULL || BER_BVISNULL( in ) ) {
-		dn->bv_val = NULL;
-		dn->bv_len = 0;
+		if ( dn ) {
+			BER_BVZERO( dn );
+		}
+		if ( ndn ) {
+			BER_BVZERO( ndn );
+		}
 		return LDAP_SUCCESS;
 	}
+
 	if ( dc->rwmap == NULL || dc->rwmap->rwm_suffix_massage == NULL ) {
-		*dn = *in;
+		if ( dn ) {
+			*dn = *in;
+			if ( ndn ) {
+				BER_BVZERO( ndn );
+			}
+		} else {
+			*ndn = *in;
+		}
 		return LDAP_SUCCESS;
 	}
 
@@ -170,7 +194,7 @@ rwm_dn_massage(
 			return rc;
 		}
 
-		if ( dc->normalized && !BER_BVISNULL( &normal) ) {
+		if ( dc->normalized && !BER_BVISNULL( &normal ) ) {
 			in = &normal;
 
 		} else if ( !dc->normalized && !BER_BVISNULL( &pretty ) ) {
@@ -197,20 +221,31 @@ rwm_dn_massage(
 		}
 
 		if ( !strcmp( dc->rwmap->rwm_suffix_massage[i+src].bv_val, &in->bv_val[diff] ) ) {
-			dn->bv_len = diff + dc->rwmap->rwm_suffix_massage[i+dst].bv_len;
-			dn->bv_val = ch_malloc( dn->bv_len + 1 );
-			strncpy( dn->bv_val, in->bv_val, diff );
-			strcpy( &dn->bv_val[diff], dc->rwmap->rwm_suffix_massage[i+dst].bv_val );
+			struct berval	*out;
+
+			if ( dn ) {
+				out = dn;
+			} else {
+				out = ndn;
+			}
+			out->bv_len = diff + dc->rwmap->rwm_suffix_massage[i+dst].bv_len;
+			out->bv_val = ch_malloc( out->bv_len + 1 );
+			strncpy( out->bv_val, in->bv_val, diff );
+			strcpy( &out->bv_val[diff], dc->rwmap->rwm_suffix_massage[i+dst].bv_val );
 #ifdef NEW_LOGGING
 			LDAP_LOG ( BACK_LDAP, ARGS, 
 				"rwm_dn_massage: converted \"%s\" to \"%s\"\n",
-				in->bv_val, dn->bv_val, 0 );
+				in->bv_val, out->bv_val, 0 );
 #else
 			Debug( LDAP_DEBUG_ARGS,
 				"rwm_dn_massage:"
 				" converted \"%s\" to \"%s\"\n",
-				in->bv_val, dn->bv_val, 0 );
+				in->bv_val, out->bv_val, 0 );
 #endif
+			if ( dn && ndn ) {
+				rc = dnNormalize( 0, NULL, NULL, dn, ndn, NULL );
+			}
+
 			break;
 		}
 	}
diff --git a/servers/slapd/overlays/rwmmap.c b/servers/slapd/overlays/rwmmap.c
index 526d399128..89543c4453 100644
--- a/servers/slapd/overlays/rwmmap.c
+++ b/servers/slapd/overlays/rwmmap.c
@@ -163,6 +163,10 @@ rwm_map_attrnames(
 {
 	int		i, j;
 
+	assert( anp );
+
+	*anp = NULL;
+
 	if ( an == NULL ) {
 		return LDAP_SUCCESS;
 	}
@@ -384,7 +388,7 @@ map_attr_value(
 		fdc.ctx = "searchFilterAttrDN";
 #endif
 
-		rc = rwm_dn_massage( &fdc, value, &vtmp, NULL );
+		rc = rwm_dn_massage( &fdc, value, NULL, &vtmp );
 		switch ( rc ) {
 		case LDAP_SUCCESS:
 			if ( vtmp.bv_val != value->bv_val ) {
@@ -413,7 +417,7 @@ map_attr_value(
 	filter_escape_value( &vtmp, mapped_value );
 
 	if ( freeval ) {
-		ber_memfree( vtmp.bv_val );
+		ch_free( vtmp.bv_val );
 	}
 	
 	return 0;
@@ -453,12 +457,12 @@ rwm_int_filter_map_rewrite(
 		}
 
 		fstr->bv_len = atmp.bv_len + vtmp.bv_len + STRLENOF( "(=)" );
-		fstr->bv_val = malloc( fstr->bv_len + 1 );
+		fstr->bv_val = ch_malloc( fstr->bv_len + 1 );
 
 		snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=%s)",
 			atmp.bv_val, vtmp.bv_val );
 
-		ber_memfree( vtmp.bv_val );
+		ch_free( vtmp.bv_val );
 		break;
 
 	case LDAP_FILTER_GE:
@@ -469,12 +473,12 @@ rwm_int_filter_map_rewrite(
 		}
 
 		fstr->bv_len = atmp.bv_len + vtmp.bv_len + STRLENOF( "(>=)" );
-		fstr->bv_val = malloc( fstr->bv_len + 1 );
+		fstr->bv_val = ch_malloc( fstr->bv_len + 1 );
 
 		snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s>=%s)",
 			atmp.bv_val, vtmp.bv_val );
 
-		ber_memfree( vtmp.bv_val );
+		ch_free( vtmp.bv_val );
 		break;
 
 	case LDAP_FILTER_LE:
@@ -485,12 +489,12 @@ rwm_int_filter_map_rewrite(
 		}
 
 		fstr->bv_len = atmp.bv_len + vtmp.bv_len + STRLENOF( "(<=)" );
-		fstr->bv_val = malloc( fstr->bv_len + 1 );
+		fstr->bv_val = ch_malloc( fstr->bv_len + 1 );
 
 		snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s<=%s)",
 			atmp.bv_val, vtmp.bv_val );
 
-		ber_memfree( vtmp.bv_val );
+		ch_free( vtmp.bv_val );
 		break;
 
 	case LDAP_FILTER_APPROX:
@@ -501,12 +505,12 @@ rwm_int_filter_map_rewrite(
 		}
 
 		fstr->bv_len = atmp.bv_len + vtmp.bv_len + STRLENOF( "(~=)" );
-		fstr->bv_val = malloc( fstr->bv_len + 1 );
+		fstr->bv_val = ch_malloc( fstr->bv_len + 1 );
 
 		snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s~=%s)",
 			atmp.bv_val, vtmp.bv_val );
 
-		ber_memfree( vtmp.bv_val );
+		ch_free( vtmp.bv_val );
 		break;
 
 	case LDAP_FILTER_SUBSTRINGS:
@@ -519,7 +523,7 @@ rwm_int_filter_map_rewrite(
 		/* cannot be a DN ... */
 
 		fstr->bv_len = atmp.bv_len + STRLENOF( "(=*)" );
-		fstr->bv_val = malloc( fstr->bv_len + 128 );
+		fstr->bv_val = ch_malloc( fstr->bv_len + 128 );
 
 		snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)",
 			atmp.bv_val );
@@ -536,7 +540,7 @@ rwm_int_filter_map_rewrite(
 				/* "(attr=" */ "%s*)",
 				vtmp.bv_val );
 
-			ber_memfree( vtmp.bv_val );
+			ch_free( vtmp.bv_val );
 		}
 
 		if ( f->f_sub_any != NULL ) {
@@ -550,7 +554,7 @@ rwm_int_filter_map_rewrite(
 				snprintf( &fstr->bv_val[len - 1], vtmp.bv_len + 3,
 					/* "(attr=[init]*[any*]" */ "%s*)",
 					vtmp.bv_val );
-				ber_memfree( vtmp.bv_val );
+				ch_free( vtmp.bv_val );
 			}
 		}
 
@@ -566,7 +570,7 @@ rwm_int_filter_map_rewrite(
 				/* "(attr=[init*][any*]" */ "%s)",
 				vtmp.bv_val );
 
-			ber_memfree( vtmp.bv_val );
+			ch_free( vtmp.bv_val );
 		}
 
 		break;
@@ -579,7 +583,7 @@ rwm_int_filter_map_rewrite(
 		}
 
 		fstr->bv_len = atmp.bv_len + STRLENOF( "(=*)" );
-		fstr->bv_val = malloc( fstr->bv_len + 1 );
+		fstr->bv_val = ch_malloc( fstr->bv_len + 1 );
 
 		snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)",
 			atmp.bv_val );
@@ -589,7 +593,7 @@ rwm_int_filter_map_rewrite(
 	case LDAP_FILTER_OR:
 	case LDAP_FILTER_NOT:
 		fstr->bv_len = STRLENOF( "(%)" );
-		fstr->bv_val = malloc( fstr->bv_len + 128 );
+		fstr->bv_val = ch_malloc( fstr->bv_len + 128 );
 
 		snprintf( fstr->bv_val, fstr->bv_len + 1, "(%c)",
 			f->f_choice == LDAP_FILTER_AND ? '&' :
@@ -632,7 +636,7 @@ rwm_int_filter_map_rewrite(
 			( f->f_mr_dnattrs ? STRLENOF( ":dn" ) : 0 ) +
 			( f->f_mr_rule_text.bv_len ? f->f_mr_rule_text.bv_len + 1 : 0 ) +
 			vtmp.bv_len + STRLENOF( "(:=)" );
-		fstr->bv_val = malloc( fstr->bv_len + 1 );
+		fstr->bv_val = ch_malloc( fstr->bv_len + 1 );
 
 		snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s%s%s%s:=%s)",
 			atmp.bv_val,
@@ -640,8 +644,9 @@ rwm_int_filter_map_rewrite(
 			!BER_BVISEMPTY( &f->f_mr_rule_text ) ? ":" : "",
 			!BER_BVISEMPTY( &f->f_mr_rule_text ) ? f->f_mr_rule_text.bv_val : "",
 			vtmp.bv_val );
-		ber_memfree( vtmp.bv_val );
-		} break;
+		ch_free( vtmp.bv_val );
+		break;
+	}
 
 	case SLAPD_FILTER_COMPUTED:
 		switch ( f->f_result ) {
@@ -701,7 +706,7 @@ rwm_filter_map_rewrite(
 	case REWRITE_REGEXEC_OK:
 		if ( !BER_BVISNULL( fstr ) ) {
 			fstr->bv_len = strlen( fstr->bv_val );
-			free( ftmp.bv_val );
+			ch_free( ftmp.bv_val );
 
 		} else {
 			*fstr = ftmp;
@@ -748,7 +753,7 @@ rwm_filter_map_rewrite(
  * routines may be macros with args
  */
 int
-rwm_dnattr_rewrite(
+rwm_referral_rewrite(
 	Operation		*op,
 	SlapReply		*rs,
 	void			*cookie,
@@ -762,12 +767,12 @@ rwm_dnattr_rewrite(
 	int			i, last;
 
 	dncookie		dc;
-	struct berval		dn, ndn, *pndn = NULL;
+	struct berval		dn, ndn, *ndnp = NULL;
 
 	assert( a_vals );
 
 	/*
-	 * Rewrite the bind dn if needed
+	 * Rewrite the dn if needed
 	 */
 	dc.rwmap = rwmap;
 #ifdef ENABLE_REWRITE
@@ -781,7 +786,7 @@ rwm_dnattr_rewrite(
 
 	for ( last = 0; !BER_BVISNULL( &a_vals[last] ); last++ );
 	if ( pa_nvals != NULL ) {
-		pndn = &ndn;
+		ndnp = &ndn;
 
 		if ( *pa_nvals == NULL ) {
 			*pa_nvals = ch_malloc( last * sizeof(struct berval) );
@@ -791,9 +796,22 @@ rwm_dnattr_rewrite(
 	last--;
 
 	for ( i = 0; !BER_BVISNULL( &a_vals[i] ); i++ ) {
+		struct berval	olddn, oldval;
 		int		rc;
+		LDAPURLDesc	*ludp;
+
+		oldval = a_vals[i];
+		rc = ldap_url_parse( oldval.bv_val, &ludp );
+		if ( rc != LDAP_URL_SUCCESS ) {
+			/* leave attr untouched if massage failed */
+			if ( pa_nvals && BER_BVISNULL( &(*pa_nvals)[i] ) ) {
+				ber_dupbv( &(*pa_nvals)[i], &oldval );
+			}
+			continue;
+		}
+		ber_str2bv( ludp->lud_dn, 0, 0, &olddn );
 
-		rc = rwm_dn_massage( &dc, &a_vals[i], &dn, pndn );
+		rc = rwm_dn_massage( &dc, &olddn, &dn, ndnp );
 		switch ( rc ) {
 		case LDAP_UNWILLING_TO_PERFORM:
 			/*
@@ -816,21 +834,168 @@ rwm_dnattr_rewrite(
 			break;
 		
 		case LDAP_SUCCESS:
-			if ( !BER_BVISNULL( &dn ) && dn.bv_val != a_vals[i].bv_val ) {
-				ch_free( a_vals[i].bv_val );
-				a_vals[i] = dn;
+			if ( !BER_BVISNULL( &dn ) && dn.bv_val != olddn.bv_val ) {
+				char	*newurl;
+
+				ludp->lud_dn = dn.bv_val;
+				newurl = ldap_url_desc2str( ludp );
+				if ( newurl == NULL ) {
+					/* FIXME: leave attr untouched
+					 * even if ldap_url_desc2str failed... */
+					break;
+				}
+
+				ber_str2bv( newurl, 0, 1, &a_vals[i] );
+				LDAP_FREE( newurl );
+
 				if ( pa_nvals ) {
+					ludp->lud_dn = ndn.bv_val;
+					newurl = ldap_url_desc2str( ludp );
+					if ( newurl == NULL ) {
+						/* FIXME: leave attr untouched
+						 * even if ldap_url_desc2str failed... */
+						ch_free( a_vals[i].bv_val );
+						a_vals[i] = oldval;
+						break;
+					}
+
 					if ( !BER_BVISNULL( &(*pa_nvals)[i] ) ) {
 						ch_free( (*pa_nvals)[i].bv_val );
 					}
-					(*pa_nvals)[i] = *pndn;
+					ber_str2bv( newurl, 0, 1, &(*pa_nvals)[i] );
+					LDAP_FREE( newurl );
 				}
+
+				ch_free( oldval.bv_val );
+				ludp->lud_dn = olddn.bv_val;
 			}
 			break;
 
 		default:
 			/* leave attr untouched if massage failed */
 			if ( pa_nvals && BER_BVISNULL( &(*pa_nvals)[i] ) ) {
+				ber_dupbv( &(*pa_nvals)[i], &a_vals[i] );
+			}
+			break;
+		}
+		ldap_free_urldesc( ludp );
+	}
+	
+	return 0;
+}
+
+/*
+ * I don't like this much, but we need two different
+ * functions because different heap managers may be
+ * in use in back-ldap/meta to reduce the amount of
+ * calls to malloc routines, and some of the free()
+ * routines may be macros with args
+ */
+int
+rwm_dnattr_rewrite(
+	Operation		*op,
+	SlapReply		*rs,
+	void			*cookie,
+	BerVarray		a_vals,
+	BerVarray		*pa_nvals )
+{
+	slap_overinst		*on = (slap_overinst *) op->o_bd->bd_info;
+	struct ldaprwmap	*rwmap = 
+			(struct ldaprwmap *)on->on_bi.bi_private;
+
+	int			i, last;
+
+	dncookie		dc;
+	struct berval		dn, *dnp = NULL, ndn, *ndnp = NULL;
+	BerVarray		in;
+
+	if ( a_vals ) {
+		in = a_vals;
+		dnp = &dn;
+
+	} else {
+		if ( pa_nvals == NULL || *pa_nvals == NULL ) {
+			return LDAP_OTHER;
+		}
+		in = *pa_nvals;
+	}
+
+	/*
+	 * Rewrite the dn if needed
+	 */
+	dc.rwmap = rwmap;
+#ifdef ENABLE_REWRITE
+	dc.conn = op->o_conn;
+	dc.rs = rs;
+	dc.ctx = (char *)cookie;
+#else
+	dc.tofrom = ((int *)cookie)[0];
+	dc.normalized = 0;
+#endif
+
+	for ( last = 0; !BER_BVISNULL( &in[last] ); last++ );
+	if ( pa_nvals != NULL ) {
+		ndnp = &ndn;
+
+		if ( *pa_nvals == NULL ) {
+			*pa_nvals = ch_malloc( last * sizeof(struct berval) );
+			memset( *pa_nvals, 0, last * sizeof(struct berval) );
+		}
+	}
+	last--;
+
+	for ( i = 0; !BER_BVISNULL( &in[i] ); i++ ) {
+		int		rc;
+
+		rc = rwm_dn_massage( &dc, &in[i], dnp, ndnp );
+		switch ( rc ) {
+		case LDAP_UNWILLING_TO_PERFORM:
+			/*
+			 * FIXME: need to check if it may be considered 
+			 * legal to trim values when adding/modifying;
+			 * it should be when searching (e.g. ACLs).
+			 */
+			ch_free( in[i].bv_val );
+			if (last > i ) {
+				in[i] = in[last];
+				if ( a_vals && pa_nvals ) {
+					(*pa_nvals)[i] = (*pa_nvals)[last];
+				}
+			}
+			BER_BVZERO( &in[last] );
+			if ( a_vals && pa_nvals ) {
+				BER_BVZERO( &(*pa_nvals)[last] );
+			}
+			last--;
+			break;
+		
+		case LDAP_SUCCESS:
+			if ( a_vals ) {
+				if ( !BER_BVISNULL( &dn ) && dn.bv_val != a_vals[i].bv_val ) {
+					ch_free( a_vals[i].bv_val );
+					a_vals[i] = dn;
+
+					if ( pa_nvals ) {
+						if ( !BER_BVISNULL( &(*pa_nvals)[i] ) ) {
+							ch_free( (*pa_nvals)[i].bv_val );
+						}
+						(*pa_nvals)[i] = ndn;
+					}
+				}
+				
+			} else {
+				assert( ndnp != NULL );
+
+				if ( !BER_BVISNULL( &ndn ) && ndn.bv_val != (*pa_nvals)[i].bv_val ) {
+					ch_free( (*pa_nvals)[i].bv_val );
+					(*pa_nvals)[i] = ndn;
+				}
+			}
+			break;
+
+		default:
+			/* leave attr untouched if massage failed */
+			if ( a_vals && pa_nvals && BER_BVISNULL( &(*pa_nvals)[i] ) ) {
 				dnNormalize( 0, NULL, NULL, &a_vals[i], &(*pa_nvals)[i], NULL );
 			}
 			break;
@@ -840,6 +1005,73 @@ rwm_dnattr_rewrite(
 	return 0;
 }
 
+int
+rwm_referral_result_rewrite(
+	dncookie		*dc,
+	BerVarray		a_vals
+)
+{
+	int		i, last;
+
+	for ( last = 0; !BER_BVISNULL( &a_vals[last] ); last++ );
+	last--;
+
+	for ( i = 0; !BER_BVISNULL( &a_vals[i] ); i++ ) {
+		struct berval	dn, olddn;
+		int		rc;
+		LDAPURLDesc	*ludp;
+
+		rc = ldap_url_parse( a_vals[i].bv_val, &ludp );
+		if ( rc != LDAP_URL_SUCCESS ) {
+			/* leave attr untouched if massage failed */
+			continue;
+		}
+
+		ber_str2bv( ludp->lud_dn, 0, 0, &olddn );
+		
+		rc = rwm_dn_massage( dc, &olddn, &dn, NULL );
+		switch ( rc ) {
+		case LDAP_UNWILLING_TO_PERFORM:
+			/*
+			 * FIXME: need to check if it may be considered 
+			 * legal to trim values when adding/modifying;
+			 * it should be when searching (e.g. ACLs).
+			 */
+			ch_free( &a_vals[i].bv_val );
+			if ( last > i ) {
+				a_vals[i] = a_vals[last];
+			}
+			BER_BVZERO( &a_vals[last] );
+			last--;
+			break;
+
+		default:
+			/* leave attr untouched if massage failed */
+			if ( !BER_BVISNULL( &dn ) && olddn.bv_val != dn.bv_val ) {
+				char	*newurl;
+
+				ludp->lud_dn = dn.bv_val;
+				newurl = ldap_url_desc2str( ludp );
+				if ( newurl == NULL ) {
+					/* FIXME: leave attr untouched
+					 * even if ldap_url_desc2str failed... */
+					break;
+				}
+
+				ch_free( a_vals[i].bv_val );
+				ber_str2bv( newurl, 0, 1, &a_vals[i] );
+				LDAP_FREE( newurl );
+				ludp->lud_dn = olddn.bv_val;
+			}
+			break;
+		}
+
+		ldap_free_urldesc( ludp );
+	}
+
+	return 0;
+}
+
 int
 rwm_dnattr_result_rewrite(
 	dncookie		*dc,
@@ -863,7 +1095,7 @@ rwm_dnattr_result_rewrite(
 			 * legal to trim values when adding/modifying;
 			 * it should be when searching (e.g. ACLs).
 			 */
-			LBER_FREE( &a_vals[i].bv_val );
+			ch_free( &a_vals[i].bv_val );
 			if ( last > i ) {
 				a_vals[i] = a_vals[last];
 			}
@@ -874,7 +1106,7 @@ rwm_dnattr_result_rewrite(
 		default:
 			/* leave attr untouched if massage failed */
 			if ( !BER_BVISNULL( &dn ) && a_vals[i].bv_val != dn.bv_val ) {
-				LBER_FREE( a_vals[i].bv_val );
+				ch_free( a_vals[i].bv_val );
 				a_vals[i] = dn;
 			}
 			break;
diff --git a/servers/slapd/proto-slap.h b/servers/slapd/proto-slap.h
index ca6b1ce69e..eee6982e23 100644
--- a/servers/slapd/proto-slap.h
+++ b/servers/slapd/proto-slap.h
@@ -975,6 +975,8 @@ LDAP_SLAPD_F (int) str2result LDAP_P(( char *s,
 	int *code, char **matched, char **info ));
 LDAP_SLAPD_F (int) slap_map_api2result LDAP_P(( SlapReply *rs ));
 
+LDAP_SLAPD_V( const struct berval ) slap_dummy_bv;
+
 /*
  * root_dse.c
  */
diff --git a/servers/slapd/result.c b/servers/slapd/result.c
index 446497206c..579526c1f2 100644
--- a/servers/slapd/result.c
+++ b/servers/slapd/result.c
@@ -41,6 +41,8 @@
 #include "slapi/slapi.h"
 #endif
 
+const struct berval slap_dummy_bv = BER_BVNULL;
+
 int slap_null_cb( Operation *op, SlapReply *rs )
 {
 	return 0;
@@ -784,8 +786,15 @@ slap_send_search_entry( Operation *op, SlapReply *rs )
 	/* FIXME: maybe we could se this flag at the operation level;
 	 * however, in principle the caller of send_search_entry() may
 	 * change the attribute list at each call */
-	rs->sr_opattrs = ( rs->sr_attrs == NULL ) ? SLAP_OPATTRS_NO
-		: ( an_find( rs->sr_attrs, &AllOper ) ? SLAP_OPATTRS : SLAP_OPATTRS_NO );
+	if ( rs->sr_attrs == NULL ) {
+		rs->sr_attr_flags = ( SLAP_OPATTRS_NO | SLAP_USERATTRS_YES );
+
+	} else {
+		rs->sr_attr_flags |= an_find( rs->sr_attrs, &AllOper ) ?
+			SLAP_OPATTRS_YES : SLAP_OPATTRS_NO;
+		rs->sr_attr_flags |= an_find( rs->sr_attrs, &AllUser ) ?
+			SLAP_USERATTRS_YES : SLAP_USERATTRS_NO;
+	}
 
 	rc = backend_operational( op, rs );
 	if ( rc ) {
@@ -967,7 +976,7 @@ slap_send_search_entry( Operation *op, SlapReply *rs )
 		} else {
 			/* specific attrs requested */
 			if ( is_at_operational( desc->ad_type ) ) {
-				if ( rs->sr_opattrs != SLAP_OPATTRS &&
+				if ( !SLAP_OPATTRS( rs->sr_attr_flags ) &&
 						!ad_inlist( desc, rs->sr_attrs ) )
 				{
 					continue;
@@ -1177,7 +1186,7 @@ slap_send_search_entry( Operation *op, SlapReply *rs )
 		} else {
 			/* specific attrs requested */
 			if( is_at_operational( desc->ad_type ) ) {
-				if ( rs->sr_opattrs != SLAP_OPATTRS && 
+				if ( !SLAP_OPATTRS( rs->sr_attr_flags ) && 
 						!ad_inlist( desc, rs->sr_attrs ) )
 				{
 					continue;
@@ -1295,7 +1304,7 @@ slap_send_search_entry( Operation *op, SlapReply *rs )
 		ctx.cac_attrs = rs->sr_attrs;
 		ctx.cac_attrsonly = op->ors_attrsonly;
 		ctx.cac_userattrs = userattrs;
-		ctx.cac_opattrs = rs->sr_opattrs;
+		ctx.cac_opattrs = rs->sr_attr_flags;
 		ctx.cac_acl_state = acl_state;
 		ctx.cac_private = (void *)ber;
 
@@ -1433,7 +1442,7 @@ error_return:;
 		attrs_free( rs->sr_operational_attrs );
 		rs->sr_operational_attrs = NULL;
 	}
-	rs->sr_opattrs = SLAP_OPATTRS_UNDEFINED;
+	rs->sr_attr_flags = SLAP_ATTRS_UNDEFINED;
 
 	/* FIXME: I think rs->sr_type should be explicitly set to
 	 * REP_SEARCH here. That's what it was when we entered this
diff --git a/servers/slapd/slap.h b/servers/slapd/slap.h
index c42930f022..d78df940a0 100644
--- a/servers/slapd/slap.h
+++ b/servers/slapd/slap.h
@@ -1687,10 +1687,16 @@ typedef struct rep_extended_s {
 
 typedef struct rep_search_s {
 	Entry *r_entry;
-	int r_opattrs;
-#define SLAP_OPATTRS_UNDEFINED	(0)
-#define SLAP_OPATTRS_NO		(-1)
-#define SLAP_OPATTRS		(1)
+	int r_attr_flags;
+#define SLAP_ATTRS_UNDEFINED	(0)
+#define SLAP_OPATTRS_NO		(0x01)
+#define SLAP_OPATTRS_YES	(0x02)
+#define SLAP_USERATTRS_NO	(0x10)
+#define SLAP_USERATTRS_YES	(0x20)
+#define SLAP_OPATTRS_MASK(f)	( (f) & (SLAP_OPATTRS_NO|SLAP_OPATTRS_YES) )
+#define SLAP_OPATTRS(f)		( (f) & SLAP_OPATTRS_YES )
+#define SLAP_USERATTRS_MASK(f)	( (f) & (SLAP_USERATTRS_NO|SLAP_USERATTRS_YES) )
+#define SLAP_USERATTRS(f)	( (f) & SLAP_USERATTRS_YES )
 	Attribute *r_operational_attrs;
 	AttributeName *r_attrs;
 	int r_nentries;
@@ -1722,7 +1728,7 @@ typedef struct slap_rep {
 #define	sr_attrs sr_un.sru_search.r_attrs
 #define	sr_entry sr_un.sru_search.r_entry
 #define	sr_operational_attrs sr_un.sru_search.r_operational_attrs
-#define sr_opattrs sr_un.sru_search.r_opattrs
+#define sr_attr_flags sr_un.sru_search.r_attr_flags
 #define	sr_v2ref sr_un.sru_search.r_v2ref
 #define	sr_nentries sr_un.sru_search.r_nentries
 #define	sr_rspoid sr_un.sru_extended.r_rspoid
diff --git a/tests/data/relay.out b/tests/data/relay.out
index 3f93a013e3..3d545eaa32 100644
--- a/tests/data/relay.out
+++ b/tests/data/relay.out
@@ -2031,7 +2031,8 @@ homePhone: +1 313 555 8421
 pager: +1 313 555 2844
 facsimileTelephoneNumber: +1 313 555 9700
 telephoneNumber: +1 313 555 5331
-description: Just added self in o=Beispiel,c=DE virtual naming context
+description: Just added self to seeAlso in o=Beispiel,c=DE virtual naming cont
+ ext
 
 dn: cn=Added User,ou=Alumni Association,ou=People,o=Esempio,c=IT
 objectClass: OpenLDAPperson
@@ -2042,5 +2043,69 @@ seeAlso: cn=All Staff,ou=Groups,o=Esempio,c=IT
 homePhone: +49 1234567890
 drink: Beer
 mail: auser@mail.alumni.example.com
-telephoneNumber: +1 313 555 4178
+telephoneNumber: +49 1234-567-890
+description: Just added in o=Beispiel,c=DE naming context
+
+
+dn: ou=Referrals,dc=example,dc=com
+objectClass: referral
+objectClass: extensibleObject
+ou: Referrals
+description: Just added as ldap://localhost:9011/ou=Referrals,o=Beispiel,c=DE
+description: ...and modified as ldap://localhost:9012/ou=Referrals,o=Beispiel,
+ c=DE
+ref: ldap://localhost:9012/ou=Referrals,dc=example,dc=com??base
+
+dn: ou=Referrals,o=Example,c=US
+objectClass: referral
+objectClass: extensibleObject
+ou: Referrals
+description: Just added as ldap://localhost:9011/ou=Referrals,o=Beispiel,c=DE
+description: ...and modified as ldap://localhost:9012/ou=Referrals,o=Beispiel,
+ c=DE
+ref: ldap://localhost:9012/ou=Referrals,o=Example,c=US??base
+
+dn: ou=Referrals,o=Esempio,c=IT
+objectClass: referral
+objectClass: extensibleObject
+ou: Referrals
+description: Just added as ldap://localhost:9011/ou=Referrals,o=Beispiel,c=DE
+description: ...and modified as ldap://localhost:9012/ou=Referrals,o=Beispiel,
+ c=DE
+ref: ldap://localhost:9012/ou=Referrals,o=Esempio,c=IT??base
+
+dn: cn=Barbara Jensen,ou=Information Technology Division,ou=People,o=Example,c
+ =US
+seeAlso: cn=All Staff,ou=Groups,o=Example,c=US
+
+dn: cn=Bjorn Jensen,ou=Information Technology Division,ou=People,o=Example,c=U
+ S
+seeAlso: cn=All Staff,ou=Groups,o=Example,c=US
+
+dn: cn=Dorothy Stevens,ou=Alumni Association,ou=People,o=Example,c=US
+seeAlso: cn=All Staff,ou=Groups,o=Example,c=US
+
+dn: cn=James A Jones 1,ou=Alumni Association,ou=People,o=Example,c=US
+seeAlso: cn=All Staff,ou=Groups,o=Example,c=US
+
+dn: cn=James A Jones 2,ou=Information Technology Division,ou=People,o=Example,
+ c=US
+seeAlso: cn=All Staff,ou=Groups,o=Example,c=US
+
+dn: cn=Jane Q. Doe,ou=Information Technology Division,ou=People,o=Example,c=US
+seeAlso: cn=All Staff,ou=Groups,o=Example,c=US
+
+dn: cn=Jennifer Smith,ou=Alumni Association,ou=People,o=Example,c=US
+seeAlso: cn=All Staff,ou=Groups,o=Example,c=US
+
+dn: cn=John P. Doe,ou=Information Technology Division,ou=People,o=Example,c=US
+seeAlso: cn=All Staff,ou=Groups,o=Example,c=US
+
+dn: cn=Ursula Hampster,ou=Alumni Association,ou=People,o=Example,c=US
+seeAlso: cn=All Staff,ou=Groups,o=Example,c=US
+seeAlso: cn=Ursula Hampster,ou=Alumni Association,ou=People,o=Example,c=US
+
+dn: cn=Added User,ou=Alumni Association,ou=People,o=Example,c=US
+seeAlso: cn=All Staff,ou=Groups,o=Example,c=US
+
 
diff --git a/tests/scripts/defines.sh b/tests/scripts/defines.sh
index f38b534375..ec50ee8ddd 100755
--- a/tests/scripts/defines.sh
+++ b/tests/scripts/defines.sh
@@ -120,7 +120,7 @@ PORT4=9014
 PORT5=9015
 PORT6=9016
 URI1="ldap://${LOCALHOST}:$PORT1/"
-URI2="ldap://${LOCALHOST}:$PORT2"
+URI2="ldap://${LOCALHOST}:$PORT2/"
 URI3="ldap://${LOCALHOST}:$PORT3/"
 URI4="ldap://${LOCALHOST}:$PORT4/"
 URI5="ldap://${LOCALHOST}:$PORT5/"
diff --git a/tests/scripts/test030-relay b/tests/scripts/test030-relay
index 807ca64956..4aef13abfa 100755
--- a/tests/scripts/test030-relay
+++ b/tests/scripts/test030-relay
@@ -114,7 +114,7 @@ fi
 BASEDN="o=Beispiel,c=DE"
 echo "modifying database \"$BASEDN\"..."
 $LDAPMODIFY -v -D "cn=Manager,$BASEDN" -h $LOCALHOST -p $PORT1 -w $PASSWD \
-	>> $TESTOUT 2>&1 << EOMODS
+	-e manageDSAit >> $TESTOUT 2>&1 << EOMODS
 dn: cn=Added User,ou=Alumni Association,ou=People,$BASEDN
 changetype: add
 objectClass: OpenLDAPperson
@@ -125,7 +125,8 @@ seealso: cn=All Staff,ou=Groups,$BASEDN
 homephone: +49 1234567890
 drink: Beer
 mail: auser@mail.alumni.example.com
-telephonenumber: +1 313 555 4178
+telephonenumber: +49 1234-567-890
+description: Just added in o=Beispiel,c=DE naming context
 
 dn: cn=Ursula Hampster,ou=Alumni Association,ou=People,$BASEDN
 changetype: modify
@@ -133,7 +134,7 @@ add: seeAlso
 seeAlso: cn=Ursula Hampster,ou=Alumni Association,ou=People,$BASEDN
 -
 add: description
-description: Just added self in $BASEDN virtual naming context
+description: Just added self to seeAlso in $BASEDN virtual naming context
 -
 
 dn: cn=Mark Elliot,ou=Alumni Association,ou=People,$BASEDN
@@ -150,6 +151,22 @@ newrdn: cn=Jane Q. Doe
 deleteoldrdn: 1
 newsuperior: ou=Information Technology Division,ou=People,$BASEDN
 
+dn: ou=Referrals,$BASEDN
+changetype: add
+objectclass: referral
+objectclass: extensibleObject
+ou: Referrals
+ref: ${URI1}ou=Referrals,$BASEDN
+description: Just added as ${URI1}ou=Referrals,$BASEDN
+
+dn: ou=Referrals,$BASEDN
+changetype: modify
+replace: ref
+ref: ${URI2}ou=Referrals,$BASEDN
+-
+add: description
+description: ...and modified as ${URI2}ou=Referrals,$BASEDN
+-
 EOMODS
 
 if test $RC != 0 ; then
@@ -168,6 +185,57 @@ if test $RC != 0 ; then
 	exit $RC
 fi
 
+FILTER="(objectClass=referral)"
+echo "searching filter=\"$FILTER\""
+echo "	attrs=\"'*' ref\"..."
+
+BASEDN="dc=example,dc=com"
+echo "	base=\"$BASEDN\"..."
+$LDAPSEARCH -h $LOCALHOST -p $PORT1 -b "$BASEDN" "$FILTER" "*" ref \
+	-e manageDSAit >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+	echo "Search failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+BASEDN="o=Example,c=US"
+echo "	base=\"$BASEDN\"..."
+$LDAPSEARCH -h $LOCALHOST -p $PORT1 -b "$BASEDN" "$FILTER" "*" ref \
+	-e manageDSAit >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+	echo "Search failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+BASEDN="o=Esempio,c=IT"
+echo "	base=\"$BASEDN\"..."
+$LDAPSEARCH -h $LOCALHOST -p $PORT1 -b "$BASEDN" "$FILTER" "*" ref \
+	-e manageDSAit >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+	echo "Search failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
+BASEDN="o=Example,c=US"
+FILTER="(seeAlso=cn=all staff,ou=Groups,$BASEDN)"
+echo "searching filter=\"$FILTER\""
+echo "	attrs=\"seeAlso\"..."
+echo "	base=\"$BASEDN\""
+$LDAPSEARCH -h $LOCALHOST -p $PORT1 -b "$BASEDN" "$FILTER" seeAlso \
+	>> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+	echo "Search failed ($RC)!"
+	test $KILLSERVERS != no && kill -HUP $KILLPIDS
+	exit $RC
+fi
+
 echo "Filtering ldapsearch results..."
 . $LDIFFILTER < $SEARCHOUT > $SEARCHFLT
 echo "Filtering original ldif used to create database..."
@@ -193,6 +261,7 @@ if test $RC != 0 ; then
 	exit $RC
 fi
 
+BASEDN="o=Beispiel,c=DE"
 echo "binding with newly changed password to database \"$BASEDN\"..."
 $LDAPWHOAMI -h $LOCALHOST -p $PORT1 \
 	-D "cn=Added User,ou=Alumni Association,ou=People,$BASEDN" \
-- 
GitLab