From a139f701f1bc8804c85eb68a9316ab5f6d8740c7 Mon Sep 17 00:00:00 2001
From: Vladislav Bogdanov <bubble@hoster-ok.com>
Date: Mon, 4 May 2015 11:37:50 +0000
Subject: [PATCH] Implement URL-mode for dynlist overlay

---
 doc/man/man5/slapo-dynlist.5     |    9 +
 servers/slapd/overlays/dynlist.c |  648 ++++++++++++++++++++++++++++++++++----
 2 files changed, 600 insertions(+), 57 deletions(-)

diff --git a/doc/man/man5/slapo-dynlist.5 b/doc/man/man5/slapo-dynlist.5
index b565d18..d022547 100644
--- a/doc/man/man5/slapo-dynlist.5
+++ b/doc/man/man5/slapo-dynlist.5
@@ -58,6 +58,8 @@ occurrences, and it must appear after the
 directive.
 .TP
 .B dynlist\-attrset <group-oc> [<URI>] <URL-ad> [[<mapped-ad>:]<member-ad> ...]
+.TP
+.B dynlist\-urlset <group-oc> [<URI>] <URI-template> [[<mapped-ad>:]<member-ad> ...]
 The value 
 .B group\-oc
 is the name of the objectClass that triggers the dynamic expansion of the
@@ -78,6 +80,13 @@ in the URI is empty, no expansion occurs for that specific URI.
 It must be a subtype of \fIlabeledURI\fP.
 
 The value
+.B URI-template
+is the LDAP URI (\fIldap:///<donor-DN>?[<attr-list>]?[<scope>]?[<filter>]\fP) with embedded templates.
+Templates are specified by the \fI@(DN-attr)\fP syntax and may appear as \fI<donor-DN>\fP and also
+in the \fI<filter>\fP.
+
+
+The value
 .B member-ad
 is optional; if present, the overlay behaves as a dynamic group: this
 attribute will list the DN of the entries resulting from the internal search.
diff --git a/servers/slapd/overlays/dynlist.c b/servers/slapd/overlays/dynlist.c
index 6293889..55f4958 100644
--- a/servers/slapd/overlays/dynlist.c
+++ b/servers/slapd/overlays/dynlist.c
@@ -38,6 +38,11 @@
 
 static AttributeDescription *ad_dgIdentity, *ad_dgAuthz;
 
+typedef enum {
+	DLI_MODE_ATTR,
+	DLI_MODE_URL
+} dynlist_mode_t;
+
 typedef struct dynlist_map_t {
 	AttributeDescription	*dlm_member_ad;
 	AttributeDescription	*dlm_mapped_ad;
@@ -46,7 +51,11 @@ typedef struct dynlist_map_t {
 
 typedef struct dynlist_info_t {
 	ObjectClass		*dli_oc;
-	AttributeDescription	*dli_ad;
+	dynlist_mode_t		dli_mode;
+	union {
+		AttributeDescription	*dli_ad;
+		LDAPURLDesc		*dli_ulud;
+	};
 	struct dynlist_map_t	*dli_dlm;
 	struct berval		dli_uri;
 	LDAPURLDesc		*dli_lud;
@@ -56,9 +65,19 @@ typedef struct dynlist_info_t {
 	struct dynlist_info_t	*dli_next;
 } dynlist_info_t;
 
+struct exp_pos { /* Expansion position */
+	char *b;
+	unsigned long len;
+};
+
+char template_format[] = "@()";
+
 #define DYNLIST_USAGE \
 	"\"dynlist-attrset <oc> [uri] <URL-ad> [[<mapped-ad>:]<member-ad> ...]\": "
 
+#define DYNLIST_URL_USAGE \
+	"\"dynlist-urlset <oc> [uri] <URL-template> [[<mapped-ad>:]<member-ad> ...]\": "
+
 static dynlist_info_t *
 dynlist_is_dynlist_next( Operation *op, SlapReply *rs, dynlist_info_t *old_dli )
 {
@@ -156,6 +175,148 @@ typedef struct dynlist_sc_t {
 	Entry		*dlc_e;
 } dynlist_sc_t;
 
+/* Lookup forward in dli list if ad is referenced in next entries */
+static int
+dynlist_ad_in_dli( Operation *op, SlapReply *rs, dynlist_info_t *dli, AttributeDescription *ad)
+{
+	dynlist_map_t	*dlm = NULL;
+	dynlist_info_t  *dli_tmp = NULL;
+
+#ifdef DEBUG_URL
+	fprintf (stderr, "Looking up if '%s' attribute's resolving is needed to satisfy request\n", ad->ad_cname.bv_val);
+#endif
+	/* Find dlm matching requested attribute */
+	for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+#ifdef DEBUG_URL
+		fprintf(stderr, "First look at DLM '%s%s%s'\n",
+			(dlm && dlm->dlm_mapped_ad) ? dlm->dlm_mapped_ad->ad_cname.bv_val : "",
+			(dlm && dlm->dlm_mapped_ad) ? ":" : "",
+			dlm->dlm_member_ad->ad_cname.bv_val	
+			);
+#endif
+		if ( ( dlm->dlm_mapped_ad && ad == dlm->dlm_mapped_ad ) || ad == dlm->dlm_member_ad ) {
+#ifdef DEBUG_URL
+			fprintf( stderr, "Looks good, attribute matched what we search for, continuing with it\n");
+#endif
+			break;
+#ifdef DEBUG_URL
+		} else {
+			fprintf( stderr, "No, not our attribute\n");
+#endif
+		}
+	}
+	
+	/* Nothing to do if current dli doesn't map requested attribute */
+	if ( !dlm ) {
+#ifdef DEBUG_URL
+		char *uri = ldap_url_desc2str( dli->dli_ulud );
+		if ( uri ) {
+			fprintf (stderr, "No matched DLM in DLI '%s', stop here\n", uri);
+			SLAP_FREE( uri );
+		}
+		return 0;
+#endif
+	}
+
+	for ( dli_tmp = dli;
+		dli_tmp;
+		dli_tmp = dynlist_is_dynlist_next( op, rs, dli_tmp ) )
+	{
+		int i;
+		dynlist_map_t	*dlm_tmp = NULL;
+		char *strings[3] = { dli_tmp->dli_ulud->lud_dn, dli_tmp->dli_ulud->lud_filter, NULL };
+
+#ifdef DEBUG_URL
+		char *uri = ldap_url_desc2str( dli->dli_ulud );
+		if ( uri ) {
+			fprintf (stderr, "Looking up '%s' in DLI '%s'\n", ad->ad_cname.bv_val, uri);
+			SLAP_FREE( uri );
+		}
+#endif
+		if (dli_tmp->dli_mode != DLI_MODE_URL) {
+#ifdef DEBUG_URL
+			fprintf (stderr, "DLI is not in URL mode\n");
+#endif
+			return 0;
+		}
+
+		/* First, lookup in member attributes */
+		for ( dlm_tmp = dli_tmp->dli_dlm; dlm_tmp; dlm_tmp = dlm_tmp->dlm_next ) {
+			if ( ad == dlm_tmp->dlm_member_ad ) {
+				if ( dlm_tmp->dlm_mapped_ad ) { 
+					if ( ad_inlist( dlm_tmp->dlm_mapped_ad, rs->sr_attrs ) ) {
+#ifdef DEBUG_URL
+						fprintf (stderr, "Matched mapped attribute in DLM's\n");
+#endif
+						return 1;
+					}
+				} else {
+					if ( ad_inlist( dlm_tmp->dlm_member_ad, rs->sr_attrs ) ) {
+#ifdef DEBUG_URL
+						fprintf (stderr, "Matched member attribute in DLM's\n");
+#endif
+						return 1;
+					}
+				}
+			}
+		}
+
+		/* Do not lookup in dli we started from for match in base URL and filter */
+		if ( dli_tmp == dli ) {
+#ifdef DEBUG_URL
+			fprintf (stderr, "Skipping lookup in templates for current DLI, continuing with next one\n");
+#endif
+			continue;
+		}
+
+		/* Then, lookup in template strings, both lud_dn and lud_filter */
+		for ( i = 0; i < 2; i++ ) {
+			char *b = strings[i],
+			     *e = b;
+
+			if ( strings[i] == NULL ) {
+				continue;
+			}
+#ifdef DEBUG_URL
+			fprintf (stderr, "Looking in template '%s'\n", strings[i]);
+#endif
+			while ( e != NULL && *e != '\0' &&
+				( b = index( e, template_format[0] ) ) != NULL &&
+				*( b + 1 ) == template_format[1] && *( b + 2 ) != '\0' )
+			{
+				AttributeDescription *ad_tmp = ( dlm->dlm_mapped_ad ) ? dlm->dlm_mapped_ad : dlm->dlm_member_ad;
+				
+				b = b + 2;
+
+				if ( ( e = index( b, template_format[2] ) ) != NULL &&
+					( e - b ) == ad_tmp->ad_cname.bv_len ) {
+					if ( strncasecmp( ad_tmp->ad_cname.bv_val, b, ad_tmp->ad_cname.bv_len ) == 0 ) {
+#ifdef DEBUG_URL
+						fprintf (stderr, "Template matched our DLM, recursing through DLM attributes\n");
+#endif
+						for ( dlm_tmp = dli_tmp->dli_dlm; dlm_tmp; dlm_tmp = dlm_tmp->dlm_next ) {
+							if ( dynlist_ad_in_dli( op, rs, dli_tmp, dlm_tmp->dlm_member_ad ) ) {
+								return 1;
+							}
+						}
+					}
+				}
+				e++;
+			}
+#ifdef DEBUG_URL
+			fprintf (stderr, "Template didn't match\n");
+#endif
+		}
+#ifdef DEBUG_URL
+		fprintf (stderr, "Continuing with next DLI\n");
+#endif
+	}
+#ifdef DEBUG_URL
+	fprintf (stderr, "Attribute's '%s' resolving is not needed\n\n", ad->ad_cname.bv_val);
+#endif
+	return 0;
+}
+
 static int
 dynlist_sc_update( Operation *op, SlapReply *rs )
 {
@@ -184,7 +345,6 @@ dynlist_sc_update( Operation *op, SlapReply *rs )
 	{
 		goto done;
 	}
-
 	/* if there is only one member_ad, and it's not mapped,
 	 * consider it as old-style member listing */
 	dlm = dlc->dlc_dli->dli_dlm;
@@ -240,10 +400,21 @@ dynlist_sc_update( Operation *op, SlapReply *rs )
 				}
 
 			} else {
-				if ( !userattrs && !ad_inlist( a->a_desc, rs->sr_attrs ) )
+#ifdef DEBUG_URL
+				fprintf( stderr, "Looking if to add '%s' to entry attributes\n", a->a_desc->ad_cname.bv_val );
+#endif
+				if ( !userattrs && !ad_inlist( a->a_desc, rs->sr_attrs ) &&
+					!( dlc->dlc_dli->dli_mode == DLI_MODE_URL &&
+						dynlist_ad_in_dli( op, rs, dlc->dlc_dli, a->a_desc ) ) )
 				{
+#ifdef DEBUG_URL
+					fprintf( stderr, "NO, not adding attribute '%s' to entry\n", a->a_desc->ad_cname.bv_val );
+#endif
 					continue;
 				}
+#ifdef DEBUG_URL
+				fprintf( stderr, "YES, adding attribute '%s' to entry\n", a->a_desc->ad_cname.bv_val );
+#endif
 			}
 		}
 
@@ -341,13 +512,241 @@ done:;
 
 	return 0;
 }
+
+static char *
+dynlist_expand_template( Operation *op, char *t, Entry *entry, int allow_dn)
+{
+	char		*b = t,
+			*e = t;
+	int 		n = 0,
+			i = 0;
+	unsigned long	result_len;
+	char		*result = NULL;
+	struct berval	*expansions = NULL;
+	struct exp_pos	*positions = NULL;
+
+	assert ( t != NULL );
 	
+	result_len = strlen(t) + 1;
+
+	while ( *b != '\0') { /* Quick scan for possible templates */
+		if ( *b == template_format[0] ) {
+			n++; /* We will expand no more than 'n' strings */
+		}
+		b++;
+	}
+	if (n == 0) { /* Shortcut for 'no expansion' case */
+		result = op->o_tmpcalloc( 1, result_len, op->o_tmpmemctx );
+		memcpy(result, t, result_len - 1);
+		return result;
+	}
+
+	expansions = op->o_tmpcalloc( n, sizeof( struct berval * ), op->o_tmpmemctx );
+	positions = op->o_tmpcalloc( n, sizeof( struct exp_pos ), op->o_tmpmemctx );
+
+	while ( e != NULL && *e != '\0' &&
+		( b = index( e, template_format[0] ) ) != NULL &&
+		*( b + 1 ) == template_format[1] && *( b + 2 ) != '\0' )
+	{
+		b = b + 2;
+		if ( ( e = index( b, template_format[2] ) ) != NULL && ( e - b ) > 1 ) {
+			struct berval		bv;
+
+			bv.bv_val = b;
+			bv.bv_len = e - b;
+			if ( bv.bv_len == 2 &&
+			    ( *( bv.bv_val ) == 'D' || *( bv.bv_val ) == 'd' ) &&
+			    ( *( bv.bv_val + 1 ) == 'N' || *( bv.bv_val + 1 ) == 'n' ) )
+			{
+				if ( allow_dn != 1 ) {
+#ifdef DEBUG_URL
+					fprintf( stderr, "DN expansion requested but not allowed\n");
+#endif
+					goto cleanup;
+				}
+				/* DN expansion is requested */
+				expansions[i] = entry->e_nname;
+				result_len += entry->e_nname.bv_len;
+			} else {
+				const char		*text = NULL;
+				AttributeDescription	*ad;
+				Attribute		*attr = NULL;
+
+				if ( slap_bv2ad( &bv, &ad, &text ) != LDAP_SUCCESS ) {
+					/* Invalid attribute name */
+					goto cleanup;
+				}
+				attr = attrs_find( entry->e_attrs, ad );
+				if ( attr == NULL ) {
+					/* Expandable template was found but it
+					   doesn't refer to valid attribute */
+#ifdef DEBUG_URL
+					char *p = calloc(1, bv.bv_len + 1);
+					memcpy(p, bv.bv_val, bv.bv_len);
+					fprintf( stderr, "Attribute '%s' not found in entry\n", p);
+					free(p);
+#endif
+					goto cleanup;
+				}
+				/* Only the first one */
+				expansions[i] = *(attr->a_nvals);
+				result_len += attr->a_nvals->bv_len;
+			}
+			positions[i].b = b - 2;
+			positions[i].len = e + 1 - positions[i].b;
+			result_len -= positions[i].len;
+			i++;
+			e++;
+		} else {
+			/* something weird with expansion, stop */
+			goto cleanup;
+		}
+	}
+	n = i;
+	result = op->o_tmpcalloc( 1, result_len, op->o_tmpmemctx );
+	b = t; /* Reuse vacant variables */
+	e = result;
+	i = 0;
+
+	while ( *( b ) != '\0' ) {
+		assert( i <= n );
+		if ( i < n && b == positions[i].b ) {
+			memcpy(e, expansions[i].bv_val, expansions[i].bv_len);
+			e += expansions[i].bv_len;
+			b += positions[i].len;
+			i++;
+		} else {
+			*(e++) = *(b++);
+		}
+		assert( e <= result + result_len);
+	}
+
+	*e = '\0';
+
+cleanup:
+	if ( expansions ) {
+		op->o_tmpfree( expansions, op->o_tmpmemctx );
+	}
+	if ( positions ) {
+		op->o_tmpfree( positions, op->o_tmpmemctx );
+	}
+
+	return result;
+}
+
+static BerVarray
+dynlist_expand_lud2urllist( Operation *op, LDAPURLDesc *lud, Entry *entry)
+{
+	BerVarray	list = NULL;
+	Attribute	*dn_attr = NULL;
+	LDAPURLDesc	tmp_lud;
+	struct berval	*dn_val;
+	int             dn_is_self = 0;
+	
+	if ( lud->lud_dn ) { /* This differs a bit from dynlist_expand_template(),
+				leave it as is for simplicity */
+		char	*b = NULL,
+			*e = NULL;
+
+		if ( ( b = index( lud->lud_dn, template_format[0] ) ) == lud->lud_dn &&
+		     *( b + 1 ) == template_format[1] && *( b + 2 ) != '\0' )
+		{
+			/* Partial expansion in search dn is not supported. Yet. */
+			b = b + 2;
+			if ( ( e = index( b, template_format[2] ) ) != NULL &&
+			     ( e - b ) > 1 && *( e + 1 ) == '\0' ) {
+				struct berval		bv;
+				const char		*text;
+				AttributeDescription	*ad = NULL;
+
+				bv.bv_val = b;
+				bv.bv_len = e - b;
+				if ( bv.bv_len == 2 &&
+				    ( *( bv.bv_val ) == 'D' || *( bv.bv_val ) == 'd' ) &&
+				    ( *( bv.bv_val + 1 ) == 'N' || *( bv.bv_val + 1 ) == 'n' ) )
+				{
+					dn_is_self = 1;
+				} else {
+					if ( slap_bv2ad( &bv, &ad, &text ) != LDAP_SUCCESS ) {
+						/* Invalid attribute name */
+#ifdef DEBUG_URL
+						fprintf( stderr, "Invalid attribute name\n");
+#endif
+						return NULL;
+					}
+					dn_attr = attrs_find( entry->e_attrs, ad );
+					if ( dn_attr == NULL ) {
+						/* Expandable template was found but it
+						   doesn't refer to valid attribute */
+#ifdef DEBUG_URL
+						fprintf( stderr, "Template doesn't refer to valid attribute\n");
+#endif
+						return NULL;
+					}
+				}
+			}
+		}
+	}
+
+	memcpy( &tmp_lud, lud, sizeof( *lud ) );
+	tmp_lud.lud_filter = NULL;
+
+	if ( lud->lud_filter != NULL && lud->lud_filter[0] != '\0' ) {
+		tmp_lud.lud_filter = dynlist_expand_template(op, lud->lud_filter, entry, 1);
+	}
+
+	if ( dn_attr == NULL) {
+		struct berval *bv = NULL;
+		char *uri = NULL;
+
+		if (dn_is_self) {
+			tmp_lud.lud_dn = op->o_tmpcalloc( 1, entry->e_nname.bv_len + 1, op->o_tmpmemctx );
+			memcpy( tmp_lud.lud_dn, entry->e_nname.bv_val, entry->e_nname.bv_len );
+		}
+		uri = ldap_url_desc2str( &tmp_lud );
+		if ( uri ) {
+			if ((bv = ber_str2bv_x( uri , 0, 1, NULL, op->o_tmpmemctx ))) {
+				ber_bvarray_add_x( &list, bv, op->o_tmpmemctx );
+			}
+//			fprintf( stderr, "no_dn dynlist_expand_lud2urllist(\"%s\")\n", uri );
+			SLAP_FREE( uri );
+		}
+		if (dn_is_self) {
+			op->o_tmpfree( tmp_lud.lud_dn, op->o_tmpmemctx );
+		}
+	} else {
+		/* Walk through values and add corresponding URL's */
+		for ( dn_val = dn_attr->a_nvals; !BER_BVISNULL( dn_val ); dn_val++ ) {
+			struct berval *bv = NULL;
+			char *uri = NULL;
+
+			tmp_lud.lud_dn = op->o_tmpcalloc( 1, dn_val->bv_len + 1, op->o_tmpmemctx );
+			memcpy( tmp_lud.lud_dn, dn_val->bv_val, dn_val->bv_len );
+			uri = ldap_url_desc2str( &tmp_lud );
+			if ( uri ) {
+				if ((bv = ber_str2bv_x( uri, 0, 1, NULL, op->o_tmpmemctx ))) {
+					ber_bvarray_add_x( &list, bv, op->o_tmpmemctx );
+				}
+//				fprintf( stderr, "dn dynlist_expand_lud2urllist(\"%s\")\n", uri );
+				SLAP_FREE( uri );
+			}
+			op->o_tmpfree( tmp_lud.lud_dn, op->o_tmpmemctx );
+		}
+	}
+
+	if ( tmp_lud.lud_filter ) {
+		op->o_tmpfree( tmp_lud.lud_filter, op->o_tmpmemctx );
+	}
+	return list;
+}
+
 static int
 dynlist_prepare_entry( Operation *op, SlapReply *rs, dynlist_info_t *dli )
 {
 	Attribute	*a, *id = NULL;
 	slap_callback	cb = { 0 };
 	Operation	o = *op;
+	BerVarray	urls;
 	struct berval	*url;
 	Entry		*e;
 	int		opattrs,
@@ -355,10 +754,19 @@ dynlist_prepare_entry( Operation *op, SlapReply *rs, dynlist_info_t *dli )
 	dynlist_sc_t	dlc = { 0 };
 	dynlist_map_t	*dlm;
 
-	a = attrs_find( rs->sr_entry->e_attrs, dli->dli_ad );
-	if ( a == NULL ) {
-		/* FIXME: error? */
-		return SLAP_CB_CONTINUE;
+	if (dli->dli_mode == DLI_MODE_URL) {
+		/* allocate temporary berval list with entries */
+		urls = dynlist_expand_lud2urllist(op, dli->dli_ulud, rs->sr_entry);
+		if ( urls == NULL ) {
+			return SLAP_CB_CONTINUE;
+		}
+	} else {
+		a = attrs_find( rs->sr_entry->e_attrs, dli->dli_ad );
+		if ( a == NULL ) {
+			/* FIXME: error? */
+			return SLAP_CB_CONTINUE;
+		}
+		urls = a->a_nvals;
 	}
 
 	opattrs = SLAP_OPATTRS( rs->sr_attr_flags );
@@ -367,11 +775,17 @@ dynlist_prepare_entry( Operation *op, SlapReply *rs, dynlist_info_t *dli )
 	/* Don't generate member list if it wasn't requested */
 	for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
 		AttributeDescription *ad = dlm->dlm_mapped_ad ? dlm->dlm_mapped_ad : dlm->dlm_member_ad;
-		if ( userattrs || ad_inlist( ad, rs->sr_attrs ) ) 
+		if ( userattrs || ad_inlist( ad, rs->sr_attrs ) ||
+		( dli->dli_mode == DLI_MODE_URL && dynlist_ad_in_dli( op, rs, dli, dlm->dlm_member_ad ) ) ) 
 			break;
 	}
-	if ( dli->dli_dlm && !dlm )
+
+	if ( dli->dli_dlm && !dlm ) {
+#ifdef DEBUG_URL
+		fprintf(stderr, "***Return on no dlm***\n");
+#endif
 		return SLAP_CB_CONTINUE;
+	}
 
 	if ( ad_dgIdentity && ( id = attrs_find( rs->sr_entry->e_attrs, ad_dgIdentity ))) {
 		Attribute *authz = NULL;
@@ -411,7 +825,10 @@ dynlist_prepare_entry( Operation *op, SlapReply *rs, dynlist_info_t *dli )
 	o.ors_tlimit = SLAP_NO_LIMIT;
 	o.ors_slimit = SLAP_NO_LIMIT;
 
-	for ( url = a->a_nvals; !BER_BVISNULL( url ); url++ ) {
+#ifdef DEBUG_URL
+	fprintf( stderr, "\n***Handling entry '%s'***\n", e->e_name.bv_val);
+#endif
+	for ( url = urls; !BER_BVISNULL( url ); url++ ) {
 		LDAPURLDesc	*lud = NULL;
 		int		i, j;
 		struct berval	dn;
@@ -463,19 +880,31 @@ dynlist_prepare_entry( Operation *op, SlapReply *rs, dynlist_info_t *dli )
 		if ( dli->dli_dlm && !dlm ) {
 			/* if ( lud->lud_attrs != NULL ),
 			 * the URL should be ignored */
+#ifdef DEBUG_URL
+			fprintf( stderr, "Requesting slap_anlist_no_attrs from '%s'\n", lud->lud_dn);
+#endif
 			o.ors_attrs = slap_anlist_no_attrs;
 
 		} else if ( lud->lud_attrs == NULL ) {
+#ifdef DEBUG_URL
+			fprintf( stderr, "Requesting current attribute list from '%s'\n", lud->lud_dn);
+#endif
 			o.ors_attrs = rs->sr_attrs;
 
 		} else {
 			for ( i = 0; lud->lud_attrs[i]; i++)
 				/* just count */ ;
 
+#ifdef DEBUG_URL
+			fprintf( stderr, "Requested %d attributes in URL '%s'\n", i, url->bv_val);
+#endif
 			o.ors_attrs = op->o_tmpcalloc( i + 1, sizeof( AttributeName ), op->o_tmpmemctx );
 			for ( i = 0, j = 0; lud->lud_attrs[i]; i++) {
 				const char	*text = NULL;
-	
+
+#ifdef DEBUG_URL
+				fprintf( stderr, "\nHandling attribute '%s'\n", lud->lud_attrs[i]);
+#endif
 				ber_str2bv( lud->lud_attrs[i], 0, 0, &o.ors_attrs[j].an_name );
 				o.ors_attrs[j].an_desc = NULL;
 				(void)slap_bv2ad( &o.ors_attrs[j].an_name, &o.ors_attrs[j].an_desc, &text );
@@ -485,6 +914,9 @@ dynlist_prepare_entry( Operation *op, SlapReply *rs, dynlist_info_t *dli )
 					if ( o.ors_attrs[j].an_desc != NULL &&
 							is_at_operational( o.ors_attrs[j].an_desc->ad_type ) )
 					{
+#ifdef DEBUG_URL
+						fprintf( stderr, "Continuing in case 1\n");
+#endif
 						continue;
 					}
 
@@ -507,6 +939,9 @@ dynlist_prepare_entry( Operation *op, SlapReply *rs, dynlist_info_t *dli )
 							}
 
 							if ( dlm == NULL ) {
+#ifdef DEBUG_URL
+								fprintf( stderr, "Continuing in case 3\n");
+#endif
 								continue;
 							}
 						}
@@ -514,7 +949,9 @@ dynlist_prepare_entry( Operation *op, SlapReply *rs, dynlist_info_t *dli )
 					} else {
 						if ( !userattrs && 
 								o.ors_attrs[j].an_desc != NULL &&
-								!ad_inlist( o.ors_attrs[j].an_desc, rs->sr_attrs ) )
+								!ad_inlist( o.ors_attrs[j].an_desc, rs->sr_attrs ) &&
+								!( dli->dli_mode == DLI_MODE_URL &&
+									dynlist_ad_in_dli( op, rs, dli, o.ors_attrs[j].an_desc ) ) )
 						{
 							/* lookup if mapped -- linear search,
 							 * not very efficient unless list
@@ -526,19 +963,27 @@ dynlist_prepare_entry( Operation *op, SlapReply *rs, dynlist_info_t *dli )
 							}
 
 							if ( dlm == NULL ) {
+#ifdef DEBUG_URL
+								fprintf( stderr, "Continuing in case 4\n");
+#endif
 								continue;
 							}
 						}
 					}
 				}
-
+#ifdef DEBUG_URL
+				fprintf( stderr, "Handled successfully\n");
+#endif
 				j++;
 			}
 
 			if ( j == 0 ) {
+#ifdef DEBUG_URL
+				fprintf( stderr, "No attributes handled\n");
+#endif
 				goto cleanup;
 			}
-		
+
 			BER_BVZERO( &o.ors_attrs[j].an_name );
 		}
 
@@ -560,6 +1005,7 @@ dynlist_prepare_entry( Operation *op, SlapReply *rs, dynlist_info_t *dli )
 		}
 		
 		o.o_bd = select_backend( &o.o_req_ndn, 1 );
+
 		if ( o.o_bd && o.o_bd->be_search ) {
 			SlapReply	r = { REP_SEARCH };
 			r.sr_attr_flags = slap_attr_flags( o.ors_attrs );
@@ -590,6 +1036,10 @@ cleanup:;
 		ldap_free_urldesc( lud );
 	}
 
+	if ( dli->dli_mode == DLI_MODE_URL && urls != NULL ) {
+		ber_bvarray_free_x( urls, op->o_tmpmemctx );
+	}
+
 	if ( e != rs->sr_entry ) {
 		rs_replace_entry( op, rs, (slap_overinst *)op->o_bd->bd_info, e );
 		rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED;
@@ -850,6 +1300,7 @@ dynlist_build_def_filter( dynlist_info_t *dli )
 
 enum {
 	DL_ATTRSET = 1,
+	DL_URLSET,
 	DL_ATTRPAIR,
 	DL_ATTRPAIR_COMPAT,
 	DL_LAST
@@ -867,6 +1318,14 @@ static ConfigTable dlcfg[] = {
 			"SYNTAX OMsDirectoryString "
 			"X-ORDERED 'VALUES' )",
 			NULL, NULL },
+	{ "dynlist-urlset", "group-oc> [uri] <URL-template> <[mapped:]member-ad> [...]",
+		3, 0, 0, ARG_MAGIC|DL_URLSET, dl_cfgen,
+		"( OLcfgOvAt:8.2 NAME 'olcDLURLSet' "
+			"DESC 'Dynamic list: <group objectClass>, <URL ldap_url_template>, <member attributeDescription>' "
+			"EQUALITY caseIgnoreMatch "
+			"SYNTAX OMsDirectoryString "
+			"X-ORDERED 'VALUES' )",
+			NULL, NULL },
 	{ "dynlist-attrpair", "member-ad> <URL-ad",
 		3, 3, 0, ARG_MAGIC|DL_ATTRPAIR, dl_cfgen,
 			NULL, NULL, NULL },
@@ -883,7 +1342,7 @@ static ConfigOCs dlocs[] = {
 		"NAME 'olcDynamicList' "
 		"DESC 'Dynamic list configuration' "
 		"SUP olcOverlayConfig "
-		"MAY olcDLattrSet )",
+		"MAY ( olcDLattrSet $ olcDLURLSet ) )",
 		Cft_Overlay, dlcfg, NULL, NULL },
 	{ NULL, 0, NULL }
 };
@@ -899,13 +1358,18 @@ dl_cfgen( ConfigArgs *c )
 	if ( c->op == SLAP_CONFIG_EMIT ) {
 		switch( c->type ) {
 		case DL_ATTRSET:
+		case DL_URLSET:
 			for ( i = 0; dli; i++, dli = dli->dli_next ) {
 				struct berval	bv;
 				char		*ptr = c->cr_msg;
 				dynlist_map_t	*dlm;
 
+				if ( ! ( ( c->type == DL_ATTRSET && dli->dli_mode == DLI_MODE_ATTR ) ||
+				      ( c->type == DL_URLSET && dli->dli_mode == DLI_MODE_URL ) ) ) {
+					continue;
+				}
+
 				assert( dli->dli_oc != NULL );
-				assert( dli->dli_ad != NULL );
 
 				/* FIXME: check buffer overflow! */
 				ptr += snprintf( c->cr_msg, sizeof( c->cr_msg ),
@@ -920,9 +1384,22 @@ dl_cfgen( ConfigArgs *c )
 					*ptr++ = '"';
 				}
 
-				*ptr++ = ' ';
-				ptr = lutil_strncopy( ptr, dli->dli_ad->ad_cname.bv_val,
-					dli->dli_ad->ad_cname.bv_len );
+				if ( c->type == DL_ATTRSET ) {
+					assert( dli->dli_ad != NULL );
+
+					*ptr++ = ' ';
+					ptr = lutil_strncopy( ptr, dli->dli_ad->ad_cname.bv_val,
+						dli->dli_ad->ad_cname.bv_len );
+				} else if ( c->type == DL_URLSET ) {
+					char *uri = NULL;
+					assert( dli->dli_ulud != NULL );
+					uri = ldap_url_desc2str( dli->dli_ulud );
+					if (uri) {
+						ptr += snprintf( ptr, sizeof( c->cr_msg ) - ( ptr - c->cr_msg ),
+								" %s", uri );
+						SLAP_FREE( uri );
+					}
+				}
 
 				for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
 					ptr[ 0 ] = ' ';
@@ -957,6 +1434,7 @@ dl_cfgen( ConfigArgs *c )
 	} else if ( c->op == LDAP_MOD_DELETE ) {
 		switch( c->type ) {
 		case DL_ATTRSET:
+		case DL_URLSET:
 			if ( c->valx < 0 ) {
 				dynlist_info_t	*dli_next;
 
@@ -982,6 +1460,10 @@ dl_cfgen( ConfigArgs *c )
 						filter_free( dli->dli_uri_filter );
 					}
 
+					if ( dli->dli_mode == DLI_MODE_URL && dli->dli_ulud != NULL ) {
+						ldap_free_urldesc( dli->dli_ulud );
+					}
+
 					ch_free( dli->dli_default_filter.bv_val );
 
 					while ( dlm != NULL ) {
@@ -1027,6 +1509,10 @@ dl_cfgen( ConfigArgs *c )
 					filter_free( dli->dli_uri_filter );
 				}
 
+				if ( dli->dli_mode == DLI_MODE_URL && dli->dli_ulud != NULL ) {
+					ldap_free_urldesc( dli->dli_ulud );
+				}
+
 				ch_free( dli->dli_default_filter.bv_val );
 
 				dlm = dli->dli_dlm;
@@ -1055,7 +1541,8 @@ dl_cfgen( ConfigArgs *c )
 	}
 
 	switch( c->type ) {
-	case DL_ATTRSET: {
+	case DL_ATTRSET:
+	case DL_URLSET: {
 		dynlist_info_t		**dlip,
 					*dli_next = NULL;
 		ObjectClass		*oc = NULL;
@@ -1066,21 +1553,24 @@ dl_cfgen( ConfigArgs *c )
 		Filter			*filter = NULL;
 		struct berval		uri = BER_BVNULL;
 		dynlist_map_t           *dlm = NULL, *dlml = NULL;
+		LDAPURLDesc		*ulud = NULL;
 		const char		*text;
 
 		oc = oc_find( c->argv[ 1 ] );
 		if ( oc == NULL ) {
-			snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
-				"unable to find ObjectClass \"%s\"",
+			snprintf( c->cr_msg, sizeof( c->cr_msg ),
+				"%sunable to find ObjectClass \"%s\"",
+				( c->type == DL_ATTRSET ) ? DYNLIST_USAGE : DYNLIST_URL_USAGE,
 				c->argv[ 1 ] );
 			Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
 				c->log, c->cr_msg, 0 );
 			return 1;
 		}
 
-		if ( strncasecmp( c->argv[ attridx ], "ldap://", STRLENOF("ldap://") ) == 0 ) {
+		if ( strncasecmp( c->argv[ attridx ], "ldap://", STRLENOF("ldap://") ) == 0 &&
+			( c->type == DL_ATTRSET || strncasecmp( c->argv[ attridx + 1 ], "ldap://", STRLENOF("ldap://") ) == 0 ) ) {
 			if ( ldap_url_parse( c->argv[ attridx ], &lud ) != LDAP_URL_SUCCESS ) {
-				snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+				snprintf( c->cr_msg, sizeof( c->cr_msg ), ( c->type == DL_ATTRSET ) ? DYNLIST_USAGE : DYNLIST_URL_USAGE
 					"unable to parse URI \"%s\"",
 					c->argv[ attridx ] );
 				rc = 1;
@@ -1093,7 +1583,7 @@ dl_cfgen( ConfigArgs *c )
 					lud->lud_host = NULL;
 
 				} else {
-					snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+					snprintf( c->cr_msg, sizeof( c->cr_msg ), ( c->type == DL_ATTRSET ) ? DYNLIST_USAGE : DYNLIST_URL_USAGE
 						"host not allowed in URI \"%s\"",
 						c->argv[ attridx ] );
 					rc = 1;
@@ -1102,7 +1592,7 @@ dl_cfgen( ConfigArgs *c )
 			}
 
 			if ( lud->lud_attrs != NULL ) {
-				snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+				snprintf( c->cr_msg, sizeof( c->cr_msg ), ( c->type == DL_ATTRSET ) ? DYNLIST_USAGE : DYNLIST_URL_USAGE
 					"attrs not allowed in URI \"%s\"",
 					c->argv[ attridx ] );
 				rc = 1;
@@ -1110,7 +1600,7 @@ dl_cfgen( ConfigArgs *c )
 			}
 
 			if ( lud->lud_exts != NULL ) {
-				snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+				snprintf( c->cr_msg, sizeof( c->cr_msg ), ( c->type == DL_ATTRSET ) ? DYNLIST_USAGE : DYNLIST_URL_USAGE
 					"extensions not allowed in URI \"%s\"",
 					c->argv[ attridx ] );
 				rc = 1;
@@ -1122,7 +1612,7 @@ dl_cfgen( ConfigArgs *c )
 				ber_str2bv( lud->lud_dn, 0, 0, &dn );
 				rc = dnNormalize( 0, NULL, NULL, &dn, &nbase, NULL );
 				if ( rc != LDAP_SUCCESS ) {
-					snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+					snprintf( c->cr_msg, sizeof( c->cr_msg ), ( c->type == DL_ATTRSET ) ? DYNLIST_USAGE : DYNLIST_URL_USAGE
 						"DN normalization failed in URI \"%s\"",
 						c->argv[ attridx ] );
 					goto done_uri;
@@ -1132,7 +1622,7 @@ dl_cfgen( ConfigArgs *c )
 			if ( lud->lud_filter != NULL && lud->lud_filter[ 0 ] != '\0' ) {
 				filter = str2filter( lud->lud_filter );
 				if ( filter == NULL ) {
-					snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+					snprintf( c->cr_msg, sizeof( c->cr_msg ), ( c->type == DL_ATTRSET ) ? DYNLIST_USAGE : DYNLIST_URL_USAGE
 						"filter parsing failed in URI \"%s\"",
 						c->argv[ attridx ] );
 					rc = 1;
@@ -1171,26 +1661,55 @@ done_uri:;
 			attridx++;
 		}
 
-		rc = slap_str2ad( c->argv[ attridx ], &ad, &text );
-		if ( rc != LDAP_SUCCESS ) {
-			snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
-				"unable to find AttributeDescription \"%s\"",
-				c->argv[ attridx ] );
-			Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
-				c->log, c->cr_msg, 0 );
-			rc = 1;
-			goto done_uri;
-		}
+		if ( c->type == DL_ATTRSET ) {
+			rc = slap_str2ad( c->argv[ attridx ], &ad, &text );
+			if ( rc != LDAP_SUCCESS ) {
+				snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+					"unable to find AttributeDescription \"%s\"",
+					c->argv[ attridx ] );
+				Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+					c->log, c->cr_msg, 0 );
+				rc = 1;
+				goto done_uri;
+			}
 
-		if ( !is_at_subtype( ad->ad_type, slap_schema.si_ad_labeledURI->ad_type ) ) {
-			snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
-				"AttributeDescription \"%s\" "
-				"must be a subtype of \"labeledURI\"",
-				c->argv[ attridx ] );
-			Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
-				c->log, c->cr_msg, 0 );
-			rc = 1;
-			goto done_uri;
+			if ( !is_at_subtype( ad->ad_type, slap_schema.si_ad_labeledURI->ad_type ) ) {
+				snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+					"AttributeDescription \"%s\" "
+					"must be a subtype of \"labeledURI\"",
+					c->argv[ attridx ] );
+				Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+					c->log, c->cr_msg, 0 );
+				rc = 1;
+				goto done_uri;
+			}
+		} else {
+			if ( ldap_url_parse( c->argv[ attridx ], &ulud ) != LDAP_URL_SUCCESS ) {
+				snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_URL_USAGE
+					"<URI-template> \"%s\" "
+					"is not a valid LDAP URL",
+					c->argv[ attridx ] );
+				Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+					c->log, c->cr_msg, 0 );
+				rc = 1;
+				goto done_uri;
+			}
+			if ( ulud->lud_host != NULL ) {
+				if ( ulud->lud_host[0] == '\0' ) {
+					ch_free( ulud->lud_host );
+					ulud->lud_host = NULL;
+
+				} else {
+					snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_URL_USAGE
+						"host not allowed in URI \"%s\"",
+						c->argv[ attridx ] );
+					ldap_free_urldesc( ulud );
+					rc = 1;
+					goto done_uri;
+				}
+			}
+
+			ulud->lud_port = 0;
 		}
 
 		attridx++;
@@ -1214,8 +1733,8 @@ done_uri:;
 				rc = slap_bv2ad( &bv, &mapped_ad, &text );
 				if ( rc != LDAP_SUCCESS ) {
 					snprintf( c->cr_msg, sizeof( c->cr_msg ),
-						DYNLIST_USAGE
-						"unable to find mapped AttributeDescription #%d \"%s\"\n",
+						"%sunable to find mapped AttributeDescription #%d \"%s\"\n",
+						( c->type == DL_ATTRSET ) ? DYNLIST_USAGE : DYNLIST_URL_USAGE,
 						i - 3, c->argv[ i ] );
 					Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
 						c->log, c->cr_msg, 0 );
@@ -1228,8 +1747,8 @@ done_uri:;
 			rc = slap_str2ad( arg, &member_ad, &text );
 			if ( rc != LDAP_SUCCESS ) {
 				snprintf( c->cr_msg, sizeof( c->cr_msg ),
-					DYNLIST_USAGE
-					"unable to find AttributeDescription #%d \"%s\"\n",
+					"%sunable to find AttributeDescription #%d \"%s\"\n",
+					( c->type == DL_ATTRSET ) ? DYNLIST_USAGE : DYNLIST_URL_USAGE,
 					i - 3, c->argv[ i ] );
 				Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
 					c->log, c->cr_msg, 0 );
@@ -1258,8 +1777,8 @@ done_uri:;
 			{
 				if ( *dlip == NULL ) {
 					snprintf( c->cr_msg, sizeof( c->cr_msg ),
-						DYNLIST_USAGE
-						"invalid index {%d}\n",
+						"%sinvalid index {%d}\n",
+						( c->type == DL_ATTRSET ) ? DYNLIST_USAGE : DYNLIST_URL_USAGE,
 						c->valx );
 					Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
 						c->log, c->cr_msg, 0 );
@@ -1279,7 +1798,13 @@ done_uri:;
 		*dlip = (dynlist_info_t *)ch_calloc( 1, sizeof( dynlist_info_t ) );
 
 		(*dlip)->dli_oc = oc;
-		(*dlip)->dli_ad = ad;
+		if ( c->type == DL_ATTRSET ) {
+			(*dlip)->dli_mode = DLI_MODE_ATTR;
+			(*dlip)->dli_ad = ad;
+		} else {
+			(*dlip)->dli_mode = DLI_MODE_URL;
+			(*dlip)->dli_ulud = ulud;
+		}
 		(*dlip)->dli_dlm = dlm;
 		(*dlip)->dli_next = dli_next;
 
@@ -1428,7 +1953,7 @@ dynlist_db_open(
 			dli->dli_oc = oc;
 		}
 
-		if ( dli->dli_ad == NULL ) {
+		if ( dli->dli_mode != DLI_MODE_URL && dli->dli_ad == NULL ) {
 			if ( ad == NULL ) {
 				rc = slap_str2ad( "memberURL", &ad, &text );
 				if ( rc != LDAP_SUCCESS ) {
@@ -1441,6 +1966,11 @@ dynlist_db_open(
 			}
 		
 			dli->dli_ad = ad;			
+		} else if ( dli->dli_mode == DLI_MODE_URL && dli->dli_ulud == NULL ) {
+			snprintf( cr->msg, sizeof( cr->msg),
+				"empty <URL-template>");
+			Debug( LDAP_DEBUG_ANY, "dynlist_db_open: %s.\n", cr->msg, 0, 0 );
+			return 1;
 		}
 
 		if ( BER_BVISNULL( &dli->dli_default_filter ) ) {
@@ -1509,6 +2039,10 @@ dynlist_db_destroy(
 				filter_free( dli->dli_uri_filter );
 			}
 
+			if ( dli->dli_mode == DLI_MODE_URL && dli->dli_ulud != NULL ) {
+				ldap_free_urldesc( dli->dli_ulud );
+			}
+
 			ch_free( dli->dli_default_filter.bv_val );
 
 			dlm = dli->dli_dlm;
@@ -1524,7 +2058,7 @@ dynlist_db_destroy(
 	return 0;
 }
 
-static slap_overinst	dynlist = { { NULL } };
+static slap_overinst	dynlist;
 #ifdef TAKEOVER_DYNGROUP
 static char		*obsolete_names[] = {
 	"dyngroup",
-- 
1.7.1

