]> git.sur5r.net Git - openldap/commitdiff
Enhanced unique configuration
authorHoward Chu <hyc@openldap.org>
Thu, 22 Mar 2007 08:22:53 +0000 (08:22 +0000)
committerHoward Chu <hyc@openldap.org>
Thu, 22 Mar 2007 08:22:53 +0000 (08:22 +0000)
doc/man/man5/slapo-unique.5
servers/slapd/overlays/unique.c
tests/data/slapd-unique.conf
tests/scripts/test024-unique

index e25df849188eb7d4750eecd80d466b5772006fb6..93618b3d7af0a4414a322a7b4dd39abf1c00d835 100644 (file)
@@ -9,9 +9,9 @@ ETCDIR/slapd.conf
 .SH DESCRIPTION
 The Attribute Uniqueness overlay can be used with a backend database such as
 .BR slapd-bdb (5)
-to enforce the uniqueness of some or all attributes within a subtree. This
-subtree defaults to the base DN of the database for which the Uniqueness
-overlay is configured.
+to enforce the uniqueness of some or all attributes within a
+scope. This subtree defaults to all objects within the subtree of the
+database for which the Uniqueness overlay is configured.
 .LP
 Uniqueness is enforced by searching the subtree to ensure that the values of
 all attributes presented with an
@@ -19,7 +19,7 @@ all attributes presented with an
 .B modify
 or
 .B modrdn
-operation are unique within the subtree.
+operation are unique within the scope.
 For example, if uniqueness were enforced for the
 .B uid
 attribute, the subtree would be searched for any other records which also
@@ -35,18 +35,59 @@ They should appear after the
 .B overlay
 directive.
 .TP
-.B unique_base <basedn>
-Configure the subtree against which uniqueness searches will be invoked.
+.B unique_uri <[strict ][ignore ]URI[URI...]...>
+Configure the base, attributes, scope, and filter for uniqueness
+checking.  Multiple URIs may be specified within a domain, allowing complex selections of objects.  Multiple
+.B unique_uri
+statements or
+.B olcUniqueURI
+attributes will create independent domains, each with their own independent lists of URIs and ignore/strict settings.
+
+The LDAP URI syntax is a subset of
+.B RFC-4516,
+and takes the form:
+
+ldap:///[base dn]?[attributes...]?scope[?filter]
+
 The
-.B basedn
-defaults to the base DN of the database for which uniqueness is configured.
-.TP
-.B unique_ignore <attribute...>
-Configure one or more attributes for which uniqueness will not be enforced.
+.B base dn
+defaults to that of the back-end database.  Specified base dns must be within the subtree of the back-end database.
+
+If no
+.B attributes
+are specified, the URI applies to all non-operational attributes.
+
+The
+.B scope
+component is effectively mandatory, because LDAP URIs default to
+.B base
+scope, which is not valid for uniqueness, because groups of one object
+are always unique.  Scopes of
+.B sub
+(for subtree) and
+.B one
+for one-level are valid.
+
+The
+.B filter
+component causes the domain to apply uniqueness constraints only to
+matching objects.  e.g.
+.B ldap:///?cn?sub?(sn=e*)
+would require unique
+.B cn
+attributes for all objects in the subtree of the back-end database whose
+.B sn
+starts with an e.
+
+It is possible to assert uniqueness upon all non-operational
+attributes except those listed by prepending the keyword
+.B ignore
 If not configured, all non-operational (eg, system) attributes must be
 unique. Note that the
-.B unique_ignore
-list should generally contain the
+.B attributes
+list of an
+.B ignore
+URI should generally contain the
 .BR objectClass ,
 .BR dc ,
 .B ou
@@ -54,42 +95,55 @@ and
 .B o
 attributes, as these will generally not be unique, nor are they operational
 attributes.
+
+It is possible to set strict checking for the uniqueness domain by
+prepending the keyword
+.B strict.
+By default, uniqueness is not enforced
+for null values. Enabling
+.B strict
+mode extends the concept of uniqueness to include null values, such
+that only one attribute within a subtree will be allowed to have a
+null value.  Strictness applies to all URIs within a uniqueness
+domain, but some domains may be strict while others are not.
+.LP
+It is not possible to set both URIs and legacy slapo-unique configuration parameters simultaneously.  In general, the legacy configuration options control pieces of a single unfiltered subtree domain.
+.TP
+.B unique_base <basedn>
+This legacy configuration parameter should be converted to the
+.B base dn
+component of the above
+.B unique_uri
+style of parameter.
+.TP
+.B unique_ignore <attribute...>
+This legacy configuration parameter should be converted to a
+.B unique_uri
+parameter with
+.B ignore
+keyword as described above.
 .TP
 .B unique_attributes <attribute...>
-Specify one or more attributes for which uniqueness will be enforced.
-If not specified, all attributes which are not operational (eg, system
-attributes such as
-.B entryUUID )
-or specified via the
-.B unique_ignore
-directive above must be unique within the subtree.
+This legacy configuration parameter should be converted to a
+.B unique_uri
+parameter, as described above.
 .TP
 .B unique_strict
-By default, uniqueness is not enforced for null values. Enabling
-.B unique_strict
-mode extends the concept of uniqueness to include null values, such that
-only one attribute within a subtree will be allowed to have a null value.
+This legacy configuration parameter should be converted to a
+.B strict
+keyword prepended to a
+.B unique_uri
+parameter, as described above.
 .SH CAVEATS
 .LP
-The search key is generated with attributes that are non-operational, not
-on the
-.B unique_ignore
-list, and included in the
-.B unique_attributes
-list, in that order. This makes it possible to create interesting and
-unusable configurations. Usually only one of
-.B unique_ignore
-or
-.B unique_attributes
-should be configured; use
-.B unique_ignore
-if the majority of attributes should be unique, and use
-.B unique_attributes
-if only a small set of attributes should be unique.
+.B unique_uri
+cannot be used with the old-style of configuration, and vice versa.
+.B unique_uri
+can implement everything the older system can do, however.
 .LP
 Typical attributes for the
-.B unique_ignore
-directive are intentionally not hardcoded into the overlay to allow for
+.B ignore ldap:///...
+URIs are intentionally not hardcoded into the overlay to allow for
 maximum flexibility in meeting site-specific requirements.
 .SH FILES
 .TP
index 9d6eefbdbbcf3b35c96da76b30c4b653037a47bb..dd7c00514349f63439dfb32df4aa421ce6d92a80 100644 (file)
@@ -3,7 +3,7 @@
 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
  *
  * Copyright 2004-2007 The OpenLDAP Foundation.
- * Portions Copyright 2004 Symas Corporation.
+ * Portions Copyright 2004,2006-2007 Symas Corporation.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * top-level directory of the distribution or, alternatively, at
  * <http://www.OpenLDAP.org/license.html>.
  */
-/* ACKNOWLEDGEMENTS:
- * This work was initially developed by Symas Corp. for inclusion in
- * OpenLDAP Software.  This work was sponsored by Hewlett-Packard.
+/* ACKNOWLEDGEMENTS: 
+ * This work was initially developed by Symas Corporation for
+ * inclusion in OpenLDAP Software, with subsequent enhancements by
+ * Matthew Backes at Symas Corporation.  This work was sponsored by
+ * Hewlett-Packard.
  */
 
 #include "portable.h"
 #include "slap.h"
 #include "config.h"
 
+#define UNIQUE_DEFAULT_URI ("ldap:///??sub")
+
 static slap_overinst unique;
 
 typedef struct unique_attrs_s {
-       struct unique_attrs_s *next;            /* list of attrs */
+       struct unique_attrs_s *next;          /* list of attrs */
        AttributeDescription *attr;
 } unique_attrs;
 
+typedef struct unique_domain_uri_s {
+       struct unique_domain_uri_s *next;
+       struct berval *dn;
+       struct berval *ndn;
+       struct berval *filter;
+       struct unique_attrs_s *attrs;
+       int scope;
+} unique_domain_uri;
+
+typedef struct unique_domain_s {
+       struct unique_domain_s *next;
+       struct berval *domain_spec;
+       struct unique_domain_uri_s *uri;
+       char ignore;                          /* polarity of attributes */
+       char strict;                          /* null considered unique too */
+} unique_domain;
+
 typedef struct unique_data_s {
-       const char *message;                    /* breadcrumbs */
-       struct unique_attrs_s *attrs;           /* list of known attrs */
-       struct unique_attrs_s *ignore;          /* list of ignored attrs */
-       BerValue dn;                            /* base of "unique tree" */
-       char strict;                            /* null considered unique too */
+       struct unique_domain_s *domains;
+       struct unique_domain_s *legacy;
+       char legacy_strict_set;
 } unique_data;
 
 typedef struct unique_counter_s {
@@ -55,32 +74,47 @@ enum {
        UNIQUE_BASE = 1,
        UNIQUE_IGNORE,
        UNIQUE_ATTR,
-       UNIQUE_STRICT
+       UNIQUE_STRICT,
+       UNIQUE_URI
 };
 
-static ConfigDriver unique_cf_gen;
+static ConfigDriver unique_cf_base;
+static ConfigDriver unique_cf_attrs;
+static ConfigDriver unique_cf_strict;
+static ConfigDriver unique_cf_uri;
 
 static ConfigTable uniquecfg[] = {
        { "unique_base", "basedn", 2, 2, 0, ARG_DN|ARG_MAGIC|UNIQUE_BASE,
-         unique_cf_gen, "( OLcfgOvAt:10.1 NAME 'olcUniqueBase' "
+         unique_cf_base, "( OLcfgOvAt:10.1 NAME 'olcUniqueBase' "
          "DESC 'Subtree for uniqueness searches' "
+         "EQUALITY distinguishedNameMatch "
          "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL },
        { "unique_ignore", "attribute...", 2, 0, 0, ARG_MAGIC|UNIQUE_IGNORE,
-         unique_cf_gen, "( OLcfgOvAt:10.2 NAME 'olcUniqueIgnore' "
+         unique_cf_attrs, "( OLcfgOvAt:10.2 NAME 'olcUniqueIgnore' "
          "DESC 'Attributes for which uniqueness shall not be enforced' "
-         "EQUALITY caseIgnoreMatch "   /* Should use OID syntax */
+         "EQUALITY caseIgnoreMatch "
+         "ORDERING caseIgnoreOrderingMatch "
+         "SUBSTR caseIgnoreSubstringsMatch "
          "SYNTAX OMsDirectoryString )", NULL, NULL },
        { "unique_attributes", "attribute...", 2, 0, 0, ARG_MAGIC|UNIQUE_ATTR,
-         unique_cf_gen, "( OLcfgOvAt:10.3 NAME 'olcUniqueAttribute' "
+         unique_cf_attrs, "( OLcfgOvAt:10.3 NAME 'olcUniqueAttribute' "
          "DESC 'Attributes for which uniqueness shall be enforced' "
          "EQUALITY caseIgnoreMatch "
+         "ORDERING caseIgnoreOrderingMatch "
+         "SUBSTR caseIgnoreSubstringsMatch "
          "SYNTAX OMsDirectoryString )", NULL, NULL },
-       { "unique_strict", "on|off", 1, 2, 0,
-         ARG_ON_OFF|ARG_OFFSET|UNIQUE_STRICT,
-         (void *)offsetof(unique_data, strict),
-         "( OLcfgOvAt:10.4 NAME 'olcUniqueStrict' "
+       { "unique_strict", "on|off", 1, 2, 0, ARG_MAGIC|UNIQUE_STRICT,
+         unique_cf_strict, "( OLcfgOvAt:10.4 NAME 'olcUniqueStrict' "
          "DESC 'Enforce uniqueness of null values' "
+         "EQUALITY booleanMatch "
          "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+       { "unique_uri", "ldapuri", 2, 3, 0, ARG_MAGIC|UNIQUE_URI,
+         unique_cf_uri, "( OLcfgOvAt:10.5 NAME 'olcUniqueURI' "
+         "DESC 'List of keywords and LDAP URIs for a uniqueness domain' "
+         "EQUALITY caseExactMatch "
+         "ORDERING caseExactOrderingMatch "
+         "SUBSTR caseExactSubstringsMatch "
+         "SYNTAX OMsDirectoryString )", NULL, NULL },
        { NULL, NULL, 0, 0, 0, ARG_IGNORED }
 };
 
@@ -90,150 +124,606 @@ static ConfigOCs uniqueocs[] = {
          "DESC 'Attribute value uniqueness configuration' "
          "SUP olcOverlayConfig "
          "MAY ( olcUniqueBase $ olcUniqueIgnore $ "
-         "olcUniqueAttribute $ olcUniqueStrict ) )",
+         "olcUniqueAttribute $ olcUniqueStrict $ "
+         "olcUniqueURI ) )",
          Cft_Overlay, uniquecfg },
        { NULL, 0, NULL }
 };
 
+static void
+unique_free_domain_uri ( unique_domain_uri *uri )
+{
+       unique_domain_uri *next_uri = NULL;
+       unique_attrs *attr, *next_attr = NULL;
+
+       while ( uri ) {
+               next_uri = uri->next;
+               ber_bvfree ( uri->dn );
+               ber_bvfree ( uri->ndn );
+               ber_bvfree ( uri->filter );
+               attr = uri->attrs;
+               while ( attr ) {
+                       next_attr = attr->next;
+                       ch_free (attr);
+                       attr = next_attr;
+               }
+               ch_free ( uri );
+               uri = next_uri;
+       }
+}
+
+/* free an entire stack of domains */
+static void
+unique_free_domain ( unique_domain *domain )
+{
+       unique_domain *next_domain = NULL;
+
+       while ( domain ) {
+               next_domain = domain->next;
+               ber_bvfree ( domain->domain_spec );
+               unique_free_domain_uri ( domain->uri );
+               ch_free ( domain );
+               domain = next_domain;
+       }
+}
+
 static int
-unique_cf_gen( ConfigArgs *c )
+unique_new_domain_uri ( unique_domain_uri **urip,
+                       const LDAPURLDesc *url_desc,
+                       ConfigArgs *c )
 {
-       slap_overinst *on = (slap_overinst *)c->bi;
-       unique_data *ud = (unique_data *)on->on_bi.bi_private;
+       int i, rc = LDAP_SUCCESS;
+       unique_domain_uri *uri;
+       struct berval bv = {0, NULL};
        BackendDB *be = (BackendDB *)c->be;
-       unique_attrs *up, *pup, **pupp = NULL;
-       AttributeDescription *ad;
-       const char *text;
+       char ** attr_str;
+       AttributeDescription * ad;
+       const char * text;
+
+       uri = ch_calloc ( 1, sizeof ( unique_domain_uri ) );
+
+       if ( url_desc->lud_dn && url_desc->lud_dn[0] ) {
+               ber_str2bv( url_desc->lud_dn, 0, 1, &bv );
+               rc = dnPrettyNormal( NULL,
+                                    &bv,
+                                    uri->dn,
+                                    uri->ndn,
+                                    NULL );
+               if ( rc != LDAP_SUCCESS ) {
+                       snprintf( c->msg, sizeof( c->msg ),
+                                 "<%s> invalid DN %d (%s)",
+                                 url_desc->lud_dn, rc, ldap_err2string( rc ));
+                       rc = ARG_BAD_CONF;
+                       goto exit;
+               }
+
+               if ( !dnIsSuffix ( uri->ndn, &be->be_nsuffix[0] ) ) {
+                       sprintf ( c->msg,
+                                 "dn <%s> is not a suffix of backend base dn <%s>",
+                                 uri->dn->bv_val,
+                                 be->be_nsuffix[0].bv_val );
+                       rc = ARG_BAD_CONF;
+                       goto exit;
+               }
+       }
+
+       attr_str = url_desc->lud_attrs;
+       if ( attr_str ) {
+               for ( i=0; attr_str[i]; ++i ) {
+                       unique_attrs * attr;
+                       ad = NULL;
+                       if ( slap_str2ad ( attr_str[i], &ad, &text )
+                            == LDAP_SUCCESS) {
+                               attr = ch_calloc ( 1,
+                                                  sizeof ( unique_attrs ) );
+                               attr->attr = ad;
+                               attr->next = uri->attrs;
+                               uri->attrs = attr;
+                       } else {
+                               snprintf( c->msg, sizeof( c->msg ),
+                                         "unique: attribute: %s: %s",
+                                         attr_str[i], text );
+                               rc = ARG_BAD_CONF;
+                               goto exit;
+                       }
+               }
+       }
+
+       uri->scope = url_desc->lud_scope;
+       if ( !uri->scope ) {
+               snprintf( c->msg, sizeof( c->msg ),
+                         "unique: uri with base scope will always be unique");
+               rc = ARG_BAD_CONF;
+               goto exit;
+       }
+
+       if (url_desc->lud_filter) {
+               Filter * f;
+               uri->filter = ber_str2bv( url_desc->lud_filter, 0, 1, NULL);
+               f = str2filter( uri->filter->bv_val );
+               if ( !f ) {
+                       snprintf( c->msg, sizeof( c->msg ),
+                                 "unique: bad filter");
+                       rc = ARG_BAD_CONF;
+                       goto exit;
+               }
+               filter_free( f );
+       }
+exit:
+       if ( bv.bv_val ) ber_memfree ( bv.bv_val );
+       uri->next = *urip;
+       *urip = uri;
+       if ( rc ) {
+               Debug ( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+                       "%s: %s\n", c->log, c->msg, 0 );
+               unique_free_domain_uri ( uri );
+               *urip = NULL;
+       }
+       return rc;
+}
+
+static int
+unique_new_domain_uri_basic ( unique_domain_uri **urip,
+                             ConfigArgs *c )
+{
+       LDAPURLDesc *url_desc = NULL;
+       int rc;
+
+       rc = ldap_url_parse ( UNIQUE_DEFAULT_URI, &url_desc );
+       if ( rc ) return rc;
+       rc = unique_new_domain_uri ( urip, url_desc, c );
+       ldap_free_urldesc ( url_desc );
+       return rc;
+}
+
+/* if *domain is non-null, it's pushed down the stack.
+ * note that the entire stack is freed if there is an error,
+ * so build added domains in a separate stack before adding them
+ *
+ * domain_specs look like
+ *
+ * [strict ][ignore ]uri[[ uri]...]
+ * e.g. "ldap:///ou=foo,o=bar?uid?sub ldap:///ou=baz,o=bar?uid?sub"
+ *      "strict ldap:///ou=accounts,o=bar?uid,uidNumber?one"
+ *      etc
+ *
+ * so finally strictness is per-domain
+ * but so is ignore-state, and that would be better as a per-url thing
+ */
+static int
+unique_new_domain ( unique_domain **domainp,
+                   char *domain_spec,
+                   ConfigArgs *c )
+{
+       char *uri_start;
+       int rc = LDAP_SUCCESS;
+       int uri_err = 0;
+       unique_domain * domain;
+       LDAPURLDesc *url_desc, *url_descs = NULL;
+
+       Debug(LDAP_DEBUG_TRACE, "==> unique_new_domain <%s>\n",
+             domain_spec, 0, 0);
+
+       domain = ch_calloc ( 1, sizeof (unique_domain) );
+       domain->domain_spec = ber_str2bv( domain_spec, 0, 1, NULL );
+
+       uri_start = domain_spec;
+       if ( strncasecmp ( uri_start, "ignore ",
+                          STRLENOF( "ignore " ) ) == 0 ) {
+               domain->ignore = 1;
+               uri_start += STRLENOF( "ignore " );
+       }
+       if ( strncasecmp ( uri_start, "strict ",
+                          STRLENOF( "strict " ) ) == 0 ) {
+               domain->strict = 1;
+               uri_start += STRLENOF( "strict " );
+               if ( !domain->ignore
+                    && strncasecmp ( uri_start, "ignore ",
+                                     STRLENOF( "ignore " ) ) == 0 ) {
+                       domain->ignore = 1;
+                       uri_start += STRLENOF( "ignore " );
+               }
+       }
+       rc = ldap_url_parselist_ext ( &url_descs, uri_start, " ", 0 );
+       if ( rc ) {
+               snprintf( c->msg, sizeof( c->msg ),
+                         "<%s> invalid ldap urilist",
+                         uri_start );
+               rc = ARG_BAD_CONF;
+               goto exit;
+       }
+
+       for ( url_desc = url_descs;
+             url_desc;
+             url_desc = url_descs->lud_next ) {
+               rc = unique_new_domain_uri ( &domain->uri,
+                                            url_desc,
+                                            c );
+               if ( rc ) {
+                       rc = ARG_BAD_CONF;
+                       uri_err = 1;
+                       goto exit;
+               }
+       }
+
+exit:
+       if ( url_descs ) ldap_free_urldesc ( url_descs );
+       domain->next = *domainp;
+       *domainp = domain;
+       if ( rc ) {
+               Debug ( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+                       "%s: %s\n", c->log, c->msg, 0 );
+               unique_free_domain ( domain );
+               *domainp = NULL;
+       }
+       return rc;
+}
+
+static int
+unique_cf_base( ConfigArgs *c )
+{
+       BackendDB *be = (BackendDB *)c->be;
+       slap_overinst *on = (slap_overinst *)c->bi;
+       unique_data *private = (unique_data *) on->on_bi.bi_private;
+       unique_domain *domains = private->domains;
+       unique_domain *legacy = private->legacy;
        int rc = ARG_BAD_CONF;
-       int i;
 
        switch ( c->op ) {
        case SLAP_CONFIG_EMIT:
-               switch ( c->type ) {
-               case UNIQUE_BASE:
-                       if ( !BER_BVISEMPTY( &ud->dn )) {
-                               rc = value_add_one( &c->rvalue_vals, &ud->dn );
-                               if ( rc ) return rc;
-                               rc = value_add_one( &c->rvalue_nvals, &ud->dn );
-                               return rc;
-                       }
+               rc = 0;
+               if ( legacy && legacy->uri && legacy->uri->dn ) {
+                       rc = value_add_one ( &c->rvalue_vals,
+                                            legacy->uri->dn );
+                       if ( rc ) return rc;
+                       rc = value_add_one ( &c->rvalue_nvals,
+                                            legacy->uri->ndn );
+                       if ( rc ) return rc;
+               }
+               break;
+       case LDAP_MOD_DELETE:
+               assert ( legacy && legacy->uri && legacy->uri->dn );
+               rc = 0;
+               ber_bvfree ( legacy->uri->dn );
+               ber_bvfree ( legacy->uri->ndn );
+               legacy->uri->dn = NULL;
+               legacy->uri->ndn = NULL;
+               if ( !legacy->uri->attrs
+                    && !legacy->uri->dn ) {
+                       unique_free_domain_uri ( legacy->uri );
+                       legacy->uri = NULL;
+               }
+               if ( !legacy->uri && !private->legacy_strict_set ) {
+                       unique_free_domain ( legacy );
+                       private->legacy = legacy = NULL;
+               }
+               break;
+       case LDAP_MOD_ADD:
+       case SLAP_CONFIG_ADD:
+               if ( domains ) {
+                       sprintf ( c->msg,
+                                 "cannot set legacy attrs when URIs are present" );
+                       Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n",
+                               c->msg, NULL, NULL );
+                       rc = ARG_BAD_CONF;
                        break;
-               case UNIQUE_IGNORE:
-                       /* fallthrough to UNIQUE_ATTR */
-               case UNIQUE_ATTR:
-                       if ( c->type == UNIQUE_IGNORE ) up = ud->ignore;
-                       else up = ud->attrs;
-                       while ( up ) {
-                               value_add_one( &c->rvalue_vals,
-                                              &up->attr->ad_cname );
-                               up = up->next;
-                       }
-                       rc = 0;
+               }
+               if ( !dnIsSuffix ( &c->value_ndn,
+                                  &be->be_nsuffix[0] ) ) {
+                       sprintf ( c->msg,
+                                 "dn is not a suffix of backend base" );
+                       Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n",
+                               c->msg, NULL, NULL );
+                       rc = ARG_BAD_CONF;
                        break;
-               case UNIQUE_STRICT:
-                       /* handled via ARG_OFFSET */
-                       /* fallthrough to default */
-               default:
-                       abort ();
                }
+               if ( !legacy ) {
+                       unique_new_domain ( &private->legacy,
+                                           UNIQUE_DEFAULT_URI,
+                                           c );
+                       legacy = private->legacy;
+               }
+               if ( !legacy->uri )
+                       unique_new_domain_uri_basic ( &legacy->uri, c );
+               ber_bvfree ( legacy->uri->dn );
+               ber_bvfree ( legacy->uri->ndn );
+               legacy->uri->dn = ber_bvdup ( &c->value_dn );
+               legacy->uri->ndn = ber_bvdup ( &c->value_ndn );
+               rc = 0;
+               break;
+       default:
+               abort();
+       }
+
+       return rc;
+}
+
+static int
+unique_cf_attrs( ConfigArgs *c )
+{
+       slap_overinst *on = (slap_overinst *)c->bi;
+       unique_data *private = (unique_data *) on->on_bi.bi_private;
+       unique_domain *domains = private->domains;
+       unique_domain *legacy = private->legacy;
+       unique_attrs *new_attrs = NULL;
+       unique_attrs *attr, *next_attr, *reverse_attrs;
+       unique_attrs **attrp;
+       int rc = ARG_BAD_CONF;
+       int i;
+
+       switch ( c->op ) {
+       case SLAP_CONFIG_EMIT:
+               if ( legacy
+                    && (c->type == UNIQUE_IGNORE) == legacy->ignore
+                    && legacy->uri )
+                       for ( attr = legacy->uri->attrs;
+                             attr;
+                             attr = attr->next )
+                               value_add_one( &c->rvalue_vals,
+                                              &attr->attr->ad_cname );
+               rc = 0;
                break;
        case LDAP_MOD_DELETE:
-               switch ( c->type ) {
-               case UNIQUE_BASE:
-                       /* default to the base of our configured database */
-                       if ( ud->dn.bv_val ) ber_memfree ( ud->dn.bv_val );
-                       ber_dupbv( &ud->dn, &be->be_nsuffix[0] );
-                       rc = 0;
+               if ( legacy
+                    && (c->type == UNIQUE_IGNORE) == legacy->ignore
+                    && legacy->uri
+                    && legacy->uri->attrs) {
+                       if ( c->valx < 0 ) { /* delete all */
+                               for ( attr = legacy->uri->attrs;
+                                     attr;
+                                     attr = next_attr ) {
+                                       next_attr = attr->next;
+                                       ch_free ( attr );
+                               }
+                               legacy->uri->attrs = NULL;
+                       } else { /* delete by index */
+                               attrp = &legacy->uri->attrs;
+                               for ( i=0; i < c->valx; ++i )
+                                       attrp = &(*attrp)->next;
+                               attr = *attrp;
+                               *attrp = attr->next;
+                               ch_free (attr);
+                       }
+                       if ( !legacy->uri->attrs
+                            && !legacy->uri->dn ) {
+                               unique_free_domain_uri ( legacy->uri );
+                               legacy->uri = NULL;
+                       }
+                       if ( !legacy->uri && !private->legacy_strict_set ) {
+                               unique_free_domain ( legacy );
+                               private->legacy = legacy = NULL;
+                       }
+               }
+               rc = 0;
+               break;
+       case LDAP_MOD_ADD:
+       case SLAP_CONFIG_ADD:
+               if ( domains ) {
+                       sprintf ( c->msg,
+                                 "cannot set legacy attrs when URIs are present" );
+                       Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n",
+                               c->msg, NULL, NULL );
+                       rc = ARG_BAD_CONF;
+                       break;
+               }
+               if ( legacy
+                    && legacy->uri
+                    && legacy->uri->attrs
+                    && (c->type == UNIQUE_IGNORE) != legacy->ignore ) {
+                       sprintf ( c->msg,
+                                 "cannot set both attrs and ignore-attrs" );
+                       Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n",
+                               c->msg, NULL, NULL );
+                       rc = ARG_BAD_CONF;
                        break;
-               case UNIQUE_IGNORE:
-                       /* fallthrough to UNIQUE_ATTR */
-               case UNIQUE_ATTR:
-                       if ( c->type == UNIQUE_IGNORE ) pupp = &ud->ignore;
-                       else pupp = &ud->attrs;
-
-                       if ( c->valx < 0 ) {
-                               up = *pupp;
-                               *pupp = NULL;
-                               while ( up ) {
-                                       pup = up;
-                                       up = up->next;
-                                       ch_free ( pup );
+               }
+               if ( !legacy ) {
+                       unique_new_domain ( &private->legacy,
+                                           UNIQUE_DEFAULT_URI,
+                                           c );
+                       legacy = private->legacy;
+               }
+               if ( !legacy->uri )
+                       unique_new_domain_uri_basic ( &legacy->uri, c );
+               rc = 0;
+               for ( i=1; c->argv[i]; ++i ) {
+                       AttributeDescription * ad = NULL;
+                       const char * text;
+                       if ( slap_str2ad ( c->argv[i], &ad, &text )
+                            == LDAP_SUCCESS) {
+
+                               attr = ch_calloc ( 1,
+                                       sizeof ( unique_attrs ) );
+                               attr->attr = ad;
+                               attr->next = new_attrs;
+                               new_attrs = attr;
+                       } else {
+                               snprintf( c->msg, sizeof( c->msg ),
+                                         "unique: attribute: %s: %s",
+                                         c->argv[i], text );
+                               for ( attr = new_attrs;
+                                     attr;
+                                     attr=next_attr ) {
+                                       next_attr = attr->next;
+                                       ch_free ( attr );
                                }
+                               rc = ARG_BAD_CONF;
+                               break;
+                       }
+               }
+               if ( rc ) break;
+
+               /* (nconc legacy->uri->attrs (nreverse new_attrs)) */
+               reverse_attrs = NULL;
+               for ( attr = new_attrs;
+                     attr;
+                     attr = next_attr ) {
+                       next_attr = attr->next;
+                       attr->next = reverse_attrs;
+                       reverse_attrs = attr;
+               }
+               for ( attrp = &legacy->uri->attrs;
+                     *attrp;
+                     attrp = &(*attrp)->next ) ;
+               *attrp = reverse_attrs;
 
-                       } else {
+               legacy->ignore = ( c->type == UNIQUE_IGNORE );
+               break;
+       default:
+               abort();
+       }
 
-                               /* delete from linked list */
-                               for ( i=0; i < c->valx; ++i ) {
-                                       pupp = &(*pupp)->next;
-                               }
-                               up = *pupp;
-                               *pupp = (*pupp)->next;
+       if ( rc ) {
+               Debug ( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+                       "%s: %s\n", c->log, c->msg, 0 );
+       }
+       return rc;
+}
+
+static int
+unique_cf_strict( ConfigArgs *c )
+{
+       slap_overinst *on = (slap_overinst *)c->bi;
+       unique_data *private = (unique_data *) on->on_bi.bi_private;
+       unique_domain *domains = private->domains;
+       unique_domain *legacy = private->legacy;
+       int rc = ARG_BAD_CONF;
 
-                               /* AttributeDescriptions are global so
-                                * shouldn't be freed here... */
-                               ch_free ( up );
+       switch ( c->op ) {
+       case SLAP_CONFIG_EMIT:
+               /* We process the boolean manually instead of using
+                * ARG_ON_OFF so that we can three-state it;
+                * olcUniqueStrict is either TRUE, FALSE, or missing,
+                * and missing is necessary to add olcUniqueURIs...
+                */
+               if ( private->legacy_strict_set ) {
+                       struct berval bv;
+                       bv.bv_val = legacy->strict ? "TRUE" : "FALSE";
+                       bv.bv_len = legacy->strict ?
+                               STRLENOF("TRUE") :
+                               STRLENOF("FALSE");
+                       value_add_one ( &c->rvalue_vals, &bv );
+               }
+               rc = 0;
+               break;
+       case LDAP_MOD_DELETE:
+               if ( legacy ) {
+                       legacy->strict = 0;
+                       if ( ! legacy->uri ) {
+                               unique_free_domain ( legacy );
+                               private->legacy = NULL;
                        }
-                       rc = 0;
-                       break;
-               case UNIQUE_STRICT:
-                       /* handled via ARG_OFFSET */
-                       /* fallthrough to default */
-               default:
-                       abort ();
                }
+               private->legacy_strict_set = 0;
+               rc = 0;
                break;
-       case SLAP_CONFIG_ADD:
-               /* fallthrough to LDAP_MOD_ADD */
        case LDAP_MOD_ADD:
-               switch ( c->type ) {
-               case UNIQUE_BASE:
-                       if ( !dnIsSuffix ( &c->value_ndn,
-                                          &be->be_nsuffix[0] ) ) {
-                               sprintf ( c->msg, "%s dn is not a suffix of backend base",
-                                       c->argv[0] );
-                               Debug ( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
-                                       "%s: %s\n", c->log, c->msg, 0 );
-                               rc = ARG_BAD_CONF;
-                       }
-                       if ( ud->dn.bv_val ) ber_memfree ( ud->dn.bv_val );
-                       ud->dn = c->value_ndn;
-                       rc = 0;
+       case SLAP_CONFIG_ADD:
+               if ( domains ) {
+                       sprintf ( c->msg,
+                                 "cannot set legacy attrs when URIs are present" );
+                       Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n",
+                               c->msg, NULL, NULL );
+                       rc = ARG_BAD_CONF;
                        break;
-               case UNIQUE_IGNORE:
-                       /* fallthrough to UNIQUE_ATTR */
-               case UNIQUE_ATTR:
-                       rc = 0;
-                       for ( i=1; i < c->argc; ++i ) {
-                               ad = NULL;
-                               if ( slap_str2ad ( c->argv[i], &ad, &text )
-                                    == LDAP_SUCCESS) {
-
-                                       up = ch_malloc (
-                                               sizeof ( unique_attrs ) );
-                                       up->attr = ad;
-                                       if ( c->type == UNIQUE_IGNORE ) {
-                                               up->next = ud->ignore;
-                                               ud->ignore = up;
-                                       } else {
-                                               up->next = ud->attrs;
-                                               ud->attrs = up;
-                                       }
-                               } else {
-                                       snprintf( c->msg, sizeof( c->msg ),
-                                               "%s <%s>: %s", c->argv[0], c->argv[i], text );
-                                       Debug ( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
-                                               "%s: %s\n", c->log, c->msg, 0 );
-                                       rc = ARG_BAD_CONF;
-                               }
-                       }
+               }
+               if ( ! legacy ) {
+                       unique_new_domain ( &private->legacy,
+                                           UNIQUE_DEFAULT_URI,
+                                           c );
+                       legacy = private->legacy;
+               }
+               /* ... not using ARG_ON_OFF makes this necessary too */
+               assert ( c->argc == 2 );
+               legacy->strict = (strcasecmp ( c->argv[1], "TRUE" ) == 0);
+               private->legacy_strict_set = 1;
+               rc = 0;
+               break;
+       default:
+               abort();
+       }
+
+       return rc;
+}
+
+static int
+unique_cf_uri( ConfigArgs *c )
+{
+       slap_overinst *on = (slap_overinst *)c->bi;
+       unique_data *private = (unique_data *) on->on_bi.bi_private;
+       unique_domain *domains = private->domains;
+       unique_domain *legacy = private->legacy;
+       unique_domain *domain = NULL, **domainp = NULL;
+       int rc = ARG_BAD_CONF;
+       int i;
+
+       switch ( c->op ) {
+       case SLAP_CONFIG_EMIT:
+               for ( domain = domains;
+                     domain;
+                     domain = domain->next ) {
+                       rc = value_add_one ( &c->rvalue_vals,
+                                            domain->domain_spec );
+                       if ( rc ) break;
+                       rc = value_add_one ( &c->rvalue_nvals,
+                                            domain->domain_spec );
+                       if ( rc ) break;
+               }
+               break;
+       case LDAP_MOD_DELETE:
+               if ( c->valx < 0 ) { /* delete them all! */
+                       unique_free_domain ( domains );
+                       private->domains = NULL;
+               } else { /* delete just one */
+                       domainp = &private->domains;
+                       for ( i=0; i < c->valx && *domainp; ++i )
+                               domainp = &(*domainp)->next;
+
+                       /* If *domainp is null, we walked off the end
+                        * of the list.  This happens when back-config
+                        * and the overlay are out-of-sync, like when
+                        * rejecting changes before ITS#4752 gets
+                        * fixed.
+                        *
+                        * This should never happen, but will appear
+                        * if you backport this version of
+                        * slapo-unique without the config-undo fixes
+                        *
+                        * test024 Will hit this case in such a
+                        * situation.
+                        */
+                       assert (*domainp);
+
+                       domain = *domainp;
+                       *domainp = domain->next;
+                       domain->next = NULL;
+                       unique_free_domain ( domain );
+               }
+               rc = 0;
+               break;
+
+       case SLAP_CONFIG_ADD: /* fallthrough */
+       case LDAP_MOD_ADD:
+               if ( legacy ) {
+                       sprintf ( c->msg,
+                                 "cannot set Uri when legacy attrs are present" );
+                       Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n",
+                               c->msg, NULL, NULL );
+                       rc = ARG_BAD_CONF;
                        break;
-               case UNIQUE_STRICT:
-                       /* handled via ARG_OFFSET */
-                       /* fallthrough to default */
-               default:
-                       abort ();
                }
+               rc = 0;
+               if ( c->line ) rc = unique_new_domain ( &domain, c->line, c );
+               else rc = unique_new_domain ( &domain, c->argv[1], c );
+               if ( rc ) break;
+               assert ( domain->next == NULL );
+               for ( domainp = &private->domains;
+                     *domainp;
+                     domainp = &(*domainp)->next ) ;
+               *domainp = domain;
+
                break;
+
        default:
                abort ();
        }
@@ -248,64 +738,58 @@ unique_cf_gen( ConfigArgs *c )
 **
 */
 
-static int unique_db_init(
+static int
+unique_db_init(
        BackendDB       *be
 )
 {
        slap_overinst *on = (slap_overinst *)be->bd_info;
-       unique_data *ud   = ch_calloc(1,sizeof(unique_data));
+       unique_data **privatep = (unique_data **) &on->on_bi.bi_private;
 
-       /* Debug(LDAP_DEBUG_TRACE, "==> unique_init\n", 0, 0, 0); */
+       Debug(LDAP_DEBUG_TRACE, "==> unique_db_init\n", 0, 0, 0);
+
+       *privatep = ch_calloc ( 1, sizeof ( unique_data ) );
 
-       ud->message     = "_init";
-       on->on_bi.bi_private = ud;
        return 0;
 }
 
-static int unique_db_destroy(
+static int
+unique_db_destroy(
        BackendDB       *be
 )
 {
        slap_overinst *on = (slap_overinst *)be->bd_info;
+       unique_data **privatep = (unique_data **) &on->on_bi.bi_private;
+       unique_data *private = *privatep;
+
+       Debug(LDAP_DEBUG_TRACE, "==> unique_db_destroy\n", 0, 0, 0);
+
+       if ( private ) {
+               unique_domain *domains = private->domains;
+               unique_domain *legacy = private->legacy;
 
-       if ( on->on_bi.bi_private ) {
-               ch_free( on->on_bi.bi_private );
-               on->on_bi.bi_private = NULL;
+               unique_free_domain ( domains );
+               unique_free_domain ( legacy );
+               ch_free ( private );
+               *privatep = NULL;
        }
+
        return 0;
 }
 
-/*
-** mostly, just print the init message;
-**
-*/
-
 static int
 unique_open(
        BackendDB *be
 )
 {
-       slap_overinst *on       = (slap_overinst *)be->bd_info;
-       unique_data *ud         = on->on_bi.bi_private;
-       ud->message             = "_open";
-
        Debug(LDAP_DEBUG_TRACE, "unique_open: overlay initialized\n", 0, 0, 0);
 
-       if ( BER_BVISNULL( &ud->dn )) {
-               if ( BER_BVISNULL( &be->be_nsuffix[0] ))
-                       return -1;
-
-               /* default to the base of our configured database */
-               ber_dupbv(&ud->dn, &be->be_nsuffix[0]);
-       }
-       return(0);
+       return 0;
 }
 
 
 /*
-** foreach configured attribute:
-**     free it;
-** free our basedn;
+** Leave unique_data but wipe out config
 **
 */
 
@@ -315,27 +799,21 @@ unique_close(
 )
 {
        slap_overinst *on       = (slap_overinst *) be->bd_info;
-       unique_data *ud         = on->on_bi.bi_private;
-       unique_attrs *ii, *ij;
-       ud->message             = "_close";
+       unique_data **privatep = (unique_data **) &on->on_bi.bi_private;
+       unique_data *private = *privatep;
 
        Debug(LDAP_DEBUG_TRACE, "==> unique_close\n", 0, 0, 0);
 
-       for(ii = ud->attrs; ii; ii = ij) {
-               ij = ii->next;
-               ch_free(ii);
-       }
+       if ( private ) {
+               unique_domain *domains = private->domains;
+               unique_domain *legacy = private->legacy;
 
-       for(ii = ud->ignore; ii; ii = ij) {
-               ij = ii->next;
-               ch_free(ii);
+               unique_free_domain ( domains );
+               unique_free_domain ( legacy );
+               memset ( private, 0, sizeof ( unique_data ) );
        }
 
-       ch_free(ud->dn.bv_val);
-
-       memset( ud, 0, sizeof(*ud));
-
-       return(0);
+       return ( 0 );
 }
 
 
@@ -371,34 +849,31 @@ static int count_attr_cb(
        return(0);
 }
 
-static int count_filter_len(
-       unique_data *ud,
+/* count the length of one attribute ad
+ * (and all of its values b)
+ * in the proposed filter
+ */
+static int
+count_filter_len(
+       unique_domain *domain,
+       unique_domain_uri *uri,
        AttributeDescription *ad,
-       BerVarray b,
-       int ks
+       BerVarray b
 )
 {
-       unique_attrs *up;
+       unique_attrs *attr;
        int i;
+       int ks = 0;
 
        while ( !is_at_operational( ad->ad_type ) ) {
-               if ( ud->ignore ) {
-                       for ( up = ud->ignore; up; up = up->next ) {
-                               if (ad == up->attr ) {
-                                       break;
-                               }
-                       }
-                       if ( up ) {
-                               break;
-                       }
-               }
-               if ( ud->attrs ) {
-                       for ( up = ud->attrs; up; up = up->next ) {
-                               if ( ad == up->attr ) {
+               if ( uri->attrs ) {
+                       for ( attr = uri->attrs; attr; attr = attr->next ) {
+                               if ( ad == attr->attr ) {
                                        break;
                                }
                        }
-                       if ( !up ) {
+                       if ( ( domain->ignore && attr )
+                            || (!domain->ignore && !attr )) {
                                break;
                        }
                }
@@ -407,43 +882,37 @@ static int count_filter_len(
                                /* note: make room for filter escaping... */
                                ks += ( 3 * b[i].bv_len ) + ad->ad_cname.bv_len + STRLENOF( "(=)" );
                        }
-               } else if ( ud->strict ) {
+               } else if ( domain->strict ) {
                        ks += ad->ad_cname.bv_len + STRLENOF( "(=*)" ); /* (attr=*) */
                }
                break;
        }
+
        return ks;
 }
 
-static char *build_filter(
-       unique_data *ud,
+static char *
+build_filter(
+       unique_domain *domain,
+       unique_domain_uri *uri,
        AttributeDescription *ad,
        BerVarray b,
        char *kp,
        void *ctx
 )
 {
-       unique_attrs *up;
+       unique_attrs *attr;
        int i;
 
        while ( !is_at_operational( ad->ad_type ) ) {
-               if ( ud->ignore ) {
-                       for ( up = ud->ignore; up; up = up->next ) {
-                               if ( ad == up->attr ) {
+               if ( uri->attrs ) {
+                       for ( attr = uri->attrs; attr; attr = attr->next ) {
+                               if ( ad == attr->attr ) {
                                        break;
                                }
                        }
-                       if ( up ) {
-                               break;
-                       }
-               }
-               if ( ud->attrs ) {
-                       for ( up = ud->attrs; up; up = up->next ) {
-                               if ( ad == up->attr ) {
-                                       break;
-                               }
-                       }
-                       if ( !up ) {
+                       if ( ( domain->ignore && attr )
+                            || (!domain->ignore && !attr )) {
                                break;
                        }
                }
@@ -457,7 +926,7 @@ static char *build_filter(
                                        ber_memfree_x( bv.bv_val, ctx );
                                }
                        }
-               } else if ( ud->strict ) {
+               } else if ( domain->strict ) {
                        kp += sprintf( kp, "(%s=*)", ad->ad_cname.bv_val );
                }
                break;
@@ -465,20 +934,24 @@ static char *build_filter(
        return kp;
 }
 
-static int unique_search(
+static int
+unique_search(
        Operation *op,
        Operation *nop,
+       struct berval * dn,
+       int scope,
        SlapReply *rs,
        char *key
 )
 {
        slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
-       unique_data *ud = on->on_bi.bi_private;
        SlapReply nrs = { REP_RESULT };
        slap_callback cb = { NULL, NULL, NULL, NULL }; /* XXX */
        unique_counter uq = { NULL, 0 };
        int rc;
 
+       Debug(LDAP_DEBUG_TRACE, "==> unique_search %s\n", key, 0, 0);
+
        nop->ors_filter = str2filter_x(nop, key);
        ber_str2bv(key, 0, 0, &nop->ors_filterstr);
 
@@ -486,7 +959,7 @@ static int unique_search(
        cb.sc_private   = &uq;
        nop->o_callback = &cb;
        nop->o_tag      = LDAP_REQ_SEARCH;
-       nop->ors_scope  = LDAP_SCOPE_SUBTREE;
+       nop->ors_scope  = scope;
        nop->ors_deref  = LDAP_DEREF_NEVER;
        nop->ors_limit  = NULL;
        nop->ors_slimit = SLAP_NO_LIMIT;
@@ -496,7 +969,7 @@ static int unique_search(
 
        uq.ndn = &op->o_req_ndn;
 
-       nop->o_req_ndn  = ud->dn;
+       nop->o_req_ndn = *dn;
        nop->o_ndn = op->o_bd->be_rootndn;
 
        nop->o_bd = on->on_info->oi_origdb;
@@ -522,182 +995,303 @@ static int unique_search(
        return(SLAP_CB_CONTINUE);
 }
 
-#define ALLOC_EXTRA    16      /* extra slop */
-
-static int unique_add(
+static int
+unique_add(
        Operation *op,
        SlapReply *rs
 )
 {
        slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
-       unique_data *ud = on->on_bi.bi_private;
+       unique_data *private = (unique_data *) on->on_bi.bi_private;
+       unique_domain *domains = private->domains;
+       unique_domain *legacy = private->legacy;
+       unique_domain *domain;
        Operation nop = *op;
-
        Attribute *a;
        char *key, *kp;
-       int ks = 0;
-
-       Debug(LDAP_DEBUG_TRACE, "==> unique_add <%s>\n", op->o_req_dn.bv_val, 0, 0);
-
-       if ( !dnIsSuffix( &op->o_req_ndn, &ud->dn ))
-               return SLAP_CB_CONTINUE;
-
-/*
-** count everything first;
-** allocate some memory;
-** write the search key;
-**
-*/
-
-       if(!(a = op->ora_e->e_attrs)) {
-               op->o_bd->bd_info = (BackendInfo *) on->on_info;
-               send_ldap_error(op, rs, LDAP_INVALID_SYNTAX,
-                       "unique_add() got null op.ora_e.e_attrs");
-               return(rs->sr_err);
-       } else for(; a; a = a->a_next) {
-               ks = count_filter_len(ud, a->a_desc, a->a_vals, ks);
-       }
-
-       if ( !ks )
-               return SLAP_CB_CONTINUE;
-
-       ks += ALLOC_EXTRA;
-       key = op->o_tmpalloc(ks, op->o_tmpmemctx);
+       int rc = SLAP_CB_CONTINUE;
+
+       Debug(LDAP_DEBUG_TRACE, "==> unique_add <%s>\n",
+             op->o_req_dn.bv_val, 0, 0);
+
+       for ( domain = legacy ? legacy : domains;
+             domain;
+             domain = domain->next ) {
+               unique_domain_uri *uri;
+               int ks = 0;
+
+               for ( uri = domain->uri;
+                     uri;
+                     uri = uri->next ) {
+
+                       if ( uri->ndn
+                            && !dnIsSuffix( &op->o_req_ndn, uri->ndn ))
+                               continue;
+
+                       if(!(a = op->ora_e->e_attrs)) {
+                               op->o_bd->bd_info = (BackendInfo *) on->on_info;
+                               send_ldap_error(op, rs, LDAP_INVALID_SYNTAX,
+                                               "unique_add() got null op.ora_e.e_attrs");
+                               rc = rs->sr_err;
+                               break;
 
-       kp = key + sprintf(key, "(|");
+                       } else {
+                               for(; a; a = a->a_next) {
+                                       ks += count_filter_len ( domain,
+                                                                uri,
+                                                                a->a_desc,
+                                                                a->a_vals);
+                               }
+                       }
 
-       for(a = op->ora_e->e_attrs; a; a = a->a_next) {
-               kp = build_filter(ud, a->a_desc, a->a_vals, kp, op->o_tmpmemctx);
+                       /* skip this domain-uri if it isn't involved */
+                       if ( !ks ) continue;
+
+                       if ( uri->filter && uri->filter->bv_len )
+                               ks += uri->filter->bv_len + STRLENOF ("(&)");
+                       kp = key = op->o_tmpalloc(ks, op->o_tmpmemctx);
+
+                       if ( uri->filter && uri->filter->bv_len )
+                               kp += sprintf (kp, "(&%s", uri->filter->bv_val);
+                       kp += sprintf(kp, "(|");
+
+                       for(a = op->ora_e->e_attrs; a; a = a->a_next)
+                               kp = build_filter(domain,
+                                                 uri,
+                                                 a->a_desc,
+                                                 a->a_vals,
+                                                 kp,
+                                                 op->o_tmpmemctx);
+
+                       kp += sprintf(kp, ")");
+                       if ( uri->filter && uri->filter->bv_len )
+                               kp += sprintf (kp, ")");
+
+                       rc = unique_search ( op,
+                                            &nop,
+                                            uri->ndn ?
+                                            uri->ndn :
+                                            &op->o_bd->be_nsuffix[0],
+                                            uri->scope,
+                                            rs,
+                                            key);
+
+                       if ( rc != SLAP_CB_CONTINUE ) break;
+               }
+               if ( rc != SLAP_CB_CONTINUE ) break;
        }
 
-       sprintf(kp, ")");
-
-       Debug(LDAP_DEBUG_TRACE, "=> unique_add %s\n", key, 0, 0);
-
-       return unique_search(op, &nop, rs, key);
+       return rc;
 }
 
 
-static int unique_modify(
+static int
+unique_modify(
        Operation *op,
        SlapReply *rs
 )
 {
        slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
-       unique_data *ud = on->on_bi.bi_private;
+       unique_data *private = (unique_data *) on->on_bi.bi_private;
+       unique_domain *domains = private->domains;
+       unique_domain *legacy = private->legacy;
+       unique_domain *domain;
        Operation nop = *op;
-
        Modifications *m;
        char *key, *kp;
-       int ks = 0;
-
-       Debug(LDAP_DEBUG_TRACE, "==> unique_modify <%s>\n", op->o_req_dn.bv_val, 0, 0);
-
-       if ( !dnIsSuffix( &op->o_req_ndn, &ud->dn ))
-               return SLAP_CB_CONTINUE;
-
-/*
-** count everything first;
-** allocate some memory;
-** write the search key;
-**
-*/
-
-       if(!(m = op->orm_modlist)) {
-               op->o_bd->bd_info = (BackendInfo *) on->on_info;
-               send_ldap_error(op, rs, LDAP_INVALID_SYNTAX,
-                       "unique_modify() got null op.orm_modlist");
-               return(rs->sr_err);
-       } else for(; m; m = m->sml_next) {
-               if ((m->sml_op & LDAP_MOD_OP) == LDAP_MOD_DELETE) continue;
-               ks = count_filter_len(ud, m->sml_desc, m->sml_values, ks);
-       }
-
-       if ( !ks )
-               return SLAP_CB_CONTINUE;
-
-       ks += ALLOC_EXTRA;
-       key = op->o_tmpalloc(ks, op->o_tmpmemctx);
-
-       kp = key + sprintf(key, "(|");
+       int rc = SLAP_CB_CONTINUE;
+
+       Debug(LDAP_DEBUG_TRACE, "==> unique_modify <%s>\n",
+             op->o_req_dn.bv_val, 0, 0);
+
+       for ( domain = legacy ? legacy : domains;
+             domain;
+             domain = domain->next ) {
+               unique_domain_uri *uri;
+               int ks = 0;
+
+               for ( uri = domain->uri;
+                     uri;
+                     uri = uri->next ) {
+
+                       if ( uri->ndn
+                            && !dnIsSuffix( &op->o_req_ndn, uri->ndn ))
+                               continue;
+
+                       if ( !(m = op->orm_modlist) ) {
+                               op->o_bd->bd_info = (BackendInfo *) on->on_info;
+                               send_ldap_error(op, rs, LDAP_INVALID_SYNTAX,
+                                               "unique_modify() got null op.orm_modlist");
+                               rc = rs->sr_err;
+                               break;
 
-       for(m = op->orm_modlist; m; m = m->sml_next) {
-               if ((m->sml_op & LDAP_MOD_OP) == LDAP_MOD_DELETE) continue;
-               kp = build_filter(ud, m->sml_desc, m->sml_values, kp, op->o_tmpmemctx);
+                       } else
+                               for ( ; m; m = m->sml_next)
+                                       if ( (m->sml_op & LDAP_MOD_OP)
+                                            != LDAP_MOD_DELETE )
+                                               ks += count_filter_len
+                                                       ( domain,
+                                                         uri,
+                                                         m->sml_desc,
+                                                         m->sml_values);
+
+                       /* skip this domain-uri if it isn't involved */
+                       if ( !ks ) continue;
+
+                       if ( uri->filter && uri->filter->bv_len )
+                               ks += uri->filter->bv_len;
+                       key = op->o_tmpalloc(ks, op->o_tmpmemctx);
+
+                       if ( uri->filter && uri->filter->bv_len )
+                               kp += sprintf ("(&(%s)", uri->filter->bv_val);
+                       kp = key + sprintf(key, "(|");
+
+                       for(m = op->orm_modlist; m; m = m->sml_next)
+                               if ( (m->sml_op & LDAP_MOD_OP)
+                                    != LDAP_MOD_DELETE )
+                                       kp = build_filter ( domain,
+                                                           uri,
+                                                           m->sml_desc,
+                                                           m->sml_values,
+                                                           kp,
+                                                           op->o_tmpmemctx );
+
+                       kp += sprintf (kp, ")");
+                       if ( uri->filter && uri->filter->bv_len )
+                               kp += sprintf (kp, ")");
+
+                       rc = unique_search ( op,
+                                            &nop,
+                                            uri->ndn ?
+                                            uri->ndn :
+                                            &op->o_bd->be_nsuffix[0],
+                                            uri->scope,
+                                            rs,
+                                            key);
+
+                       if ( rc != SLAP_CB_CONTINUE ) break;
+               }
+               if ( rc != SLAP_CB_CONTINUE ) break;
        }
 
-       sprintf(kp, ")");
-
-       Debug(LDAP_DEBUG_TRACE, "=> unique_modify %s\n", key, 0, 0);
-
-       return unique_search(op, &nop, rs, key);
+       return rc;
 }
 
 
-static int unique_modrdn(
+static int
+unique_modrdn(
        Operation *op,
        SlapReply *rs
 )
 {
        slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
-       unique_data *ud = on->on_bi.bi_private;
+       unique_data *private = (unique_data *) on->on_bi.bi_private;
+       unique_domain *domains = private->domains;
+       unique_domain *legacy = private->legacy;
+       unique_domain *domain;
        Operation nop = *op;
-
        char *key, *kp;
-       int i, ks = 0;
        LDAPRDN newrdn;
        struct berval bv[2];
+       int rc = SLAP_CB_CONTINUE;
 
        Debug(LDAP_DEBUG_TRACE, "==> unique_modrdn <%s> <%s>\n",
                op->o_req_dn.bv_val, op->orr_newrdn.bv_val, 0);
 
-       if ( !dnIsSuffix( &op->o_req_ndn, &ud->dn ) && 
-               (!op->orr_nnewSup || !dnIsSuffix( op->orr_nnewSup, &ud->dn )))
-               return SLAP_CB_CONTINUE;
-
-       if(ldap_bv2rdn_x(&op->oq_modrdn.rs_newrdn, &newrdn,
-               (char **)&rs->sr_text, LDAP_DN_FORMAT_LDAP, op->o_tmpmemctx )) {
-               op->o_bd->bd_info = (BackendInfo *) on->on_info;
-               send_ldap_error(op, rs, LDAP_INVALID_SYNTAX,
-                       "unknown type(s) used in RDN");
-               return(rs->sr_err);
-       }
-       for(i = 0; newrdn[i]; i++) {
-               AttributeDescription *ad = NULL;
-               if ( slap_bv2ad( &newrdn[i]->la_attr, &ad, &rs->sr_text )) {
-                       ldap_rdnfree_x( newrdn, op->o_tmpmemctx );
-                       rs->sr_err = LDAP_INVALID_SYNTAX;
-                       send_ldap_result( op, rs );
-                       return(rs->sr_err);
-               }
-               newrdn[i]->la_private = ad;
-       }
+       for ( domain = legacy ? legacy : domains;
+             domain;
+             domain = domain->next ) {
+               unique_domain_uri *uri;
+               int ks = 0;
+
+               for ( uri = domain->uri;
+                     uri;
+                     uri = uri->next ) {
+                       int i;
+
+                       if ( uri->ndn
+                            && !dnIsSuffix( &op->o_req_ndn, uri->ndn )
+                            && (!op->orr_nnewSup
+                                || !dnIsSuffix( op->orr_nnewSup, uri->ndn )))
+                               continue;
+
+                       if ( ldap_bv2rdn_x ( &op->oq_modrdn.rs_newrdn,
+                                            &newrdn,
+                                            (char **)&rs->sr_text,
+                                            LDAP_DN_FORMAT_LDAP,
+                                            op->o_tmpmemctx ) ) {
+                               op->o_bd->bd_info = (BackendInfo *) on->on_info;
+                               send_ldap_error(op, rs, LDAP_INVALID_SYNTAX,
+                                               "unknown type(s) used in RDN");
+                               rc = rs->sr_err;
+                               break;
+                       }
 
-       bv[1].bv_val = NULL;
-       bv[1].bv_len = 0;
+                       rc = SLAP_CB_CONTINUE;
+                       for ( i=0; newrdn[i]; i++) {
+                               AttributeDescription *ad = NULL;
+                               if ( slap_bv2ad( &newrdn[i]->la_attr, &ad, &rs->sr_text )) {
+                                       ldap_rdnfree_x( newrdn, op->o_tmpmemctx );
+                                       rs->sr_err = LDAP_INVALID_SYNTAX;
+                                       send_ldap_result( op, rs );
+                                       rc = rs->sr_err;
+                                       break;
+                               }
+                               newrdn[i]->la_private = ad;
+                       }
+                       if ( rc != SLAP_CB_CONTINUE ) break;
 
-       for(i = 0; newrdn[i]; i++) {
-               bv[0] = newrdn[i]->la_value;
-               ks = count_filter_len(ud, newrdn[i]->la_private, bv, ks);
-       }
+                       bv[1].bv_val = NULL;
+                       bv[1].bv_len = 0;
 
-       if ( !ks )
-               return SLAP_CB_CONTINUE;
+                       for ( i=0; newrdn[i]; i++ ) {
+                               bv[0] = newrdn[i]->la_value;
+                               ks += count_filter_len ( domain,
+                                                        uri,
+                                                        newrdn[i]->la_private,
+                                                        bv);
+                       }
 
-       ks += ALLOC_EXTRA;
-       key = op->o_tmpalloc(ks, op->o_tmpmemctx);
-       kp = key + sprintf(key, "(|");
+                       /* skip this domain if it isn't involved */
+                       if ( !ks ) continue;
+
+                       if ( uri->filter && uri->filter->bv_len )
+                               ks += uri->filter->bv_len;
+                       key = op->o_tmpalloc(ks, op->o_tmpmemctx);
+
+                       if ( uri->filter && uri->filter->bv_len )
+                               kp += sprintf ("(&(%s)", uri->filter->bv_val);
+                       kp = key + sprintf(key, "(|");
+
+                       for ( i=0; newrdn[i]; i++) {
+                               bv[0] = newrdn[i]->la_value;
+                               kp = build_filter ( domain,
+                                                   uri,
+                                                   newrdn[i]->la_private,
+                                                   bv,
+                                                   kp,
+                                                   op->o_tmpmemctx);
+                       }
 
-       for(i = 0; newrdn[i]; i++) {
-               bv[0] = newrdn[i]->la_value;
-               kp = build_filter(ud, newrdn[i]->la_private, bv, kp, op->o_tmpmemctx);
-       }
+                       kp += sprintf(kp, ")");
+                       if ( uri->filter && uri->filter->bv_len )
+                               kp += sprintf (kp, ")");
 
-       sprintf(kp, ")");
+                       rc = unique_search ( op,
+                                            &nop,
+                                            uri->ndn ?
+                                            uri->ndn :
+                                            &op->o_bd->be_nsuffix[0],
+                                            uri->scope,
+                                            rs,
+                                            key);
 
-       Debug(LDAP_DEBUG_TRACE, "=> unique_modrdn %s\n", key, 0, 0);
+                       if ( rc != SLAP_CB_CONTINUE ) break;
+               }
+               if ( rc != SLAP_CB_CONTINUE ) break;
+       }
 
-       return unique_search(op, &nop, rs, key);
+       return rc;
 }
 
 /*
@@ -705,10 +1299,14 @@ static int unique_modrdn(
 ** it expects to be called automagically during dynamic module initialization
 */
 
-int unique_initialize() {
+int
+unique_initialize()
+{
        int rc;
 
        /* statically declared just after the #includes at top */
+       memset (&unique, 0, sizeof(unique));
+
        unique.on_bi.bi_type = "unique";
        unique.on_bi.bi_db_init = unique_db_init;
        unique.on_bi.bi_db_destroy = unique_db_destroy;
@@ -717,7 +1315,6 @@ int unique_initialize() {
        unique.on_bi.bi_op_add = unique_add;
        unique.on_bi.bi_op_modify = unique_modify;
        unique.on_bi.bi_op_modrdn = unique_modrdn;
-       unique.on_bi.bi_op_delete = NULL;
 
        unique.on_bi.bi_cf_ocs = uniqueocs;
        rc = config_register_schema( uniquecfg, uniqueocs );
index 1a12ffba63a7ac6ca8c75c53b94e78579dc8f4ee..9e8b11d09162c5a0cd333e68ddbf9c961035208a 100644 (file)
@@ -47,7 +47,14 @@ rootpw               secret
 #ldbm#index            cn,sn,uid       pres,eq,sub
 
 overlay                        unique
-unique_ignore          o cn sn ou objectClass
+
 unique_attributes      employeeNumber displayName
+unique_base            o=unique
+
+#unique_uri ldap:///?description?one
+#unique_uri ldap:///?employeeNumber,displayName?sub
 
 #monitor#database      monitor
+
+database config
+include                @TESTDIR@/configpw.conf
index 3e17bddd2546a41c85e96ff5d1e0290d6d76eeb8..4d0b0095b9c9634247b4f186c566066b1591273a 100755 (executable)
 echo "running defines.sh"
 . $SRCDIR/scripts/defines.sh
 
-if test $UNIQUE = uniqueno; then 
+if test $UNIQUE = uniqueno; then
        echo "Attribute Uniqueness overlay not available, test skipped"
        exit 0
-fi 
+fi
 
 mkdir -p $TESTDIR $DBDIR1
 
+$SLAPPASSWD -g -n >$CONFIGPWF
+echo "rootpw `$SLAPPASSWD -T $CONFIGPWF`" >$TESTDIR/configpw.conf
+
 echo "Running slapadd to build slapd database..."
 . $CONFFILTER $BACKEND $MONITORDB < $UNIQUECONF > $CONF1
-$SLAPADD -f $CONF1 -l $LDIFUNIQUE
+$SLAPADD -f $CONF1 -l $LDIFUNIQUE -d7
 RC=$?
 if test $RC != 0 ; then
        echo "slapadd failed ($RC)!"
@@ -33,7 +36,8 @@ if test $RC != 0 ; then
 fi
 
 echo "Starting slapd on TCP/IP port $PORT1..."
-$SLAPD -f $CONF1 -h $URI1 -d $LVL $TIMING > $LOG1 2>&1 &
+mkdir testrun/confdir
+$SLAPD -f $CONF1 -F testrun/confdir -h $URI1 -d $LVL $TIMING > $LOG1 2>&1 &
 PID=$!
 if test $WAIT != 0 ; then
     echo PID $PID
@@ -62,9 +66,6 @@ if test $RC != 0 ; then
 fi
 
 echo "Adding a unique record..."
-
-#$LDAPADD -D "$UNIQUEDN" -h $LOCALHOST -p $PORT1 -w $PASSWD > \
-#      $TESTOUT 2>&1 << EOTUNIQ1
 $LDAPADD -D "$UNIQUEDN" -h $LOCALHOST -p $PORT1 -w $PASSWD \
        > /dev/null << EOTUNIQ1
 dn: uid=dave,ou=users,o=unique
@@ -82,7 +83,6 @@ employeeNumber: 69
 employeeType: contractor
 givenName: Dave
 EOTUNIQ1
-
 RC=$?
 if test $RC != 0 ; then
        echo "ldapadd failed ($RC)!"
@@ -90,11 +90,180 @@ if test $RC != 0 ; then
        exit $RC
 fi
 
-#echo ----------------------
-#$LDAPSEARCH -S "" -b "o=unique" -h $LOCALHOST -p $PORT1
+echo "Adding a non-unique record..."
+$LDAPADD -D "$UNIQUEDN" -h $LOCALHOST -p $PORT1 -w $PASSWD > \
+        $TESTOUT 2>&1 << EOTUNIQ2
+dn: uid=bill,ou=users,o=unique
+objectClass: inetOrgPerson
+uid: bill
+sn: johnson
+cn: bill
+businessCategory: rtest
+carLicense: ABC123
+departmentNumber: 42
+displayName: Bill
+employeeNumber: 5150
+employeeType: contractor
+givenName: Bill
+EOTUNIQ2
+RC=$?
+if test $RC != 19 ; then
+       echo "unique check failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit -1
+fi
+
+echo Dynamically retrieving initial configuration...
+$LDAPSEARCH -S "" -b olcOverlay='{0}'unique,olcDatabase='{1}'$BACKEND,cn=config -D cn=config -y $CONFIGPWF -h $LOCALHOST -p $PORT1 -LLL | tr -d \\r >testrun/initial-config.ldif
+cat <<EOF >testrun/initial-reference.ldif
+dn: olcOverlay={0}unique,olcDatabase={1}$BACKEND,cn=config
+objectClass: olcOverlayConfig
+objectClass: olcUniqueConfig
+olcOverlay: {0}unique
+olcUniqueBase: o=unique
+olcUniqueAttribute: employeeNumber
+olcUniqueAttribute: displayName
+
+EOF
+diff testrun/initial-config.ldif testrun/initial-reference.ldif > /dev/null 2>&1
+RC=$?
+if test $RC != 0 ; then
+    echo "Initial configuration is not reported correctly."
+    test $KILLSERVERS != no && kill -HUP $KILLPIDS
+    exit -1
+fi
+
+echo Dynamically trying to add a URI with legacy attrs present...
+$LDAPMODIFY -D cn=config -h $LOCALHOST -p $PORT1 -y $CONFIGPWF \
+    > $TESTOUT 2>&1 <<EOF
+dn: olcOverlay={0}unique,olcDatabase={1}$BACKEND,cn=config
+changetype: modify
+add: olcUniqueURI
+olcUniqueURI: ldap:///?employeeNumber,displayName?sub
+EOF
+RC=$?
+if test $RC != 80 ; then
+       echo "legacy and unique_uri allowed together"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit -1
+fi
+
+echo Dynamically trying to add legacy ignored attrs with legacy attrs present...
+$LDAPMODIFY -D cn=config -h $LOCALHOST -p $PORT1 -y $CONFIGPWF \
+    > $TESTOUT 2>&1 <<EOF
+dn: olcOverlay={0}unique,olcDatabase={1}$BACKEND,cn=config
+changetype: modify
+add: olcUniqueIgnore
+olcUniqueIgnore: objectClass
+EOF
+RC=$?
+if test $RC != 80 ; then
+       echo "legacy attrs and legacy ignore attrs allowed together"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit -1
+fi
+
+echo Verifying initial configuration intact...
+$LDAPSEARCH -S "" -b olcOverlay='{0}'unique,olcDatabase='{1}'$BACKEND,cn=config -D cn=config -y $CONFIGPWF -h $LOCALHOST -p $PORT1 -LLL | tr -d \\r >testrun/initial-config-recheck.ldif
+diff testrun/initial-config-recheck.ldif testrun/initial-reference.ldif > /dev/null 2>&1
+RC=$?
+if test $RC != 0 ; then
+    echo "Initial configuration damaged by unsuccessful modifies."
+    test $KILLSERVERS != no && kill -HUP $KILLPIDS
+    exit -1
+fi
+
+echo Dynamically removing legacy base...
+$LDAPMODIFY -D cn=config -h $LOCALHOST -p $PORT1 -y $CONFIGPWF \
+    > $TESTOUT 2>&1 <<EOF
+dn: olcOverlay={0}unique,olcDatabase={1}$BACKEND,cn=config
+changetype: modify
+delete: olcUniqueBase
+EOF
+RC=$?
+if test $RC != 0 ; then
+       echo "base removal failed"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit -1
+fi
+
+echo Verifying base removal...
+$LDAPSEARCH -S "" -b olcOverlay='{0}'unique,olcDatabase='{1}'$BACKEND,cn=config -D cn=config -y $CONFIGPWF -h $LOCALHOST -p $PORT1 -LLL | tr -d \\r >testrun/baseremoval-config.ldif
+cat >testrun/baseremoval-reference.ldif <<EOF
+dn: olcOverlay={0}unique,olcDatabase={1}$BACKEND,cn=config
+objectClass: olcOverlayConfig
+objectClass: olcUniqueConfig
+olcOverlay: {0}unique
+olcUniqueAttribute: employeeNumber
+olcUniqueAttribute: displayName
+
+EOF
+diff testrun/baseremoval-config.ldif testrun/baseremoval-reference.ldif > /dev/null 2>&1
+RC=$?
+if test $RC != 0 ; then
+    echo "Configuration damaged by base removal"
+    test $KILLSERVERS != no && kill -HUP $KILLPIDS
+    exit -1
+fi
 
 echo "Adding a non-unique record..."
+$LDAPADD -D "$UNIQUEDN" -h $LOCALHOST -p $PORT1 -w $PASSWD > \
+        $TESTOUT 2>&1 << EOTUNIQ2
+dn: uid=bill,ou=users,o=unique
+objectClass: inetOrgPerson
+uid: bill
+sn: johnson
+cn: bill
+businessCategory: rtest
+carLicense: ABC123
+departmentNumber: 42
+displayName: Bill
+employeeNumber: 5150
+employeeType: contractor
+givenName: Bill
+EOTUNIQ2
+RC=$?
+if test $RC != 19 ; then
+       echo "unique check failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit -1
+fi
 
+echo Trying a legacy base outside of the backend...
+$LDAPMODIFY -D cn=config -h $LOCALHOST -p $PORT1 -y $CONFIGPWF \
+    > $TESTOUT 2>&1 <<EOF
+dn: olcOverlay={0}unique,olcDatabase={1}$BACKEND,cn=config
+changetype: modify
+add: olcUniqueBase
+olcUniqueBase: cn=config
+EOF
+RC=$?
+if test $RC != 80 ; then
+       echo "out of backend scope base allowed"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit -1
+fi
+
+echo "Adding and removing attrs..."
+$LDAPMODIFY -D cn=config -h $LOCALHOST -p $PORT1 -y $CONFIGPWF \
+    > $TESTOUT 2>&1 <<EOF
+dn: olcOverlay={0}unique,olcDatabase={1}$BACKEND,cn=config
+changetype: modify
+add: olcUniqueAttribute
+olcUniqueAttribute: description
+olcUniqueAttribute: telephoneNumber
+-
+delete: olcUniqueAttribute
+olcUniqueAttribute: displayName
+EOF
+RC=$?
+if test $RC != 0 ; then
+       echo "Unable to remove an attribute"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit -1
+fi
+
+echo "Verifying we removed the right attr..."
 $LDAPADD -D "$UNIQUEDN" -h $LOCALHOST -p $PORT1 -w $PASSWD > \
         $TESTOUT 2>&1 << EOTUNIQ2
 dn: uid=bill,ou=users,o=unique
@@ -110,7 +279,66 @@ employeeNumber: 5150
 employeeType: contractor
 givenName: Bill
 EOTUNIQ2
+RC=$?
+if test $RC != 19 ; then
+       echo "olcUniqueAttribtue single deletion hit the wrong value"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit -1
+fi
 
+echo Removing legacy config and adding URIs...
+$LDAPMODIFY -D cn=config -h $LOCALHOST -p $PORT1 -y $CONFIGPWF \
+    > $TESTOUT 2>&1 <<EOF
+dn: olcOverlay={0}unique,olcDatabase={1}$BACKEND,cn=config
+changetype: modify
+delete: olcUniqueAttribute
+-
+add: olcUniqueURI
+olcUniqueURI: ldap:///?employeeNumber,displayName?sub
+olcUniqueURI: ldap:///?description?one
+EOF
+RC=$?
+if test $RC != 0 ; then
+       echo "Reconfiguration to URIs failed"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit -1
+fi
+
+echo Dynamically retrieving second configuration...
+$LDAPSEARCH -S "" -b olcOverlay='{0}'unique,olcDatabase='{1}'$BACKEND,cn=config -D cn=config -y $CONFIGPWF -h $LOCALHOST -p $PORT1 -LLL | tr -d \\r >testrun/second-config.ldif
+cat >testrun/second-reference.ldif <<EOF
+dn: olcOverlay={0}unique,olcDatabase={1}$BACKEND,cn=config
+objectClass: olcOverlayConfig
+objectClass: olcUniqueConfig
+olcOverlay: {0}unique
+olcUniqueURI: ldap:///?employeeNumber,displayName?sub
+olcUniqueURI: ldap:///?description?one
+
+EOF
+diff testrun/second-config.ldif testrun/second-reference.ldif > /dev/null 2>&1
+RC=$?
+if test $RC != 0 ; then
+    echo "Second configuration is not reported correctly."
+    test $KILLSERVERS != no && kill -HUP $KILLPIDS
+    exit -1
+fi
+
+echo "Adding a non-unique record..."
+$LDAPADD -D "$UNIQUEDN" -h $LOCALHOST -p $PORT1 -w $PASSWD > \
+        $TESTOUT 2>&1 << EOTUNIQ2
+dn: uid=bill,ou=users,o=unique
+objectClass: inetOrgPerson
+uid: bill
+sn: johnson
+cn: bill
+businessCategory: rtest
+carLicense: ABC123
+departmentNumber: 42
+displayName: Bill
+employeeNumber: 5150
+employeeType: contractor
+givenName: Bill
+EOTUNIQ2
 RC=$?
 if test $RC != 19 ; then
        echo "unique check failed ($RC)!"
@@ -118,11 +346,222 @@ if test $RC != 19 ; then
        exit -1
 fi
 
+echo Dynamically trying to add legacy base
+$LDAPMODIFY -D cn=config -h $LOCALHOST -p $PORT1 -y $CONFIGPWF \
+    > $TESTOUT 2>&1 <<EOF
+dn: olcOverlay={0}unique,olcDatabase={1}$BACKEND,cn=config
+changetype: modify
+add: olcUniqueBase
+olcUniqueBase: o=unique
+EOF
+RC=$?
+if test $RC != 80 ; then
+       echo "legacy base allowed with URIs"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit -1
+fi
+
+echo Dynamically trying to add legacy attrs
+$LDAPMODIFY -D cn=config -h $LOCALHOST -p $PORT1 -y $CONFIGPWF \
+    > $TESTOUT 2>&1 <<EOF
+dn: olcOverlay={0}unique,olcDatabase={1}$BACKEND,cn=config
+changetype: modify
+add: olcUniqueAttribute
+olcUniqueAttribute: description
+EOF
+RC=$?
+if test $RC != 80 ; then
+       echo "legacy attributes allowed with URIs"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit -1
+fi
+
+echo Dynamically trying to add legacy strictness
+$LDAPMODIFY -D cn=config -h $LOCALHOST -p $PORT1 -y $CONFIGPWF \
+    > $TESTOUT 2>&1 <<EOF
+dn: olcOverlay={0}unique,olcDatabase={1}$BACKEND,cn=config
+changetype: modify
+add: olcUniqueStrict
+olcUniqueStrict: TRUE
+EOF
+RC=$?
+if test $RC != 80 ; then
+       echo "legacy strictness allowed with URIs"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit -1
+fi
+
+#echo ----------------------
+echo Dynamically trying a bad filter...
+$LDAPMODIFY -D cn=config -h $LOCALHOST -p $PORT1 -y $CONFIGPWF \
+    > $TESTOUT 2>&1 <<EOF
+dn: olcOverlay={0}unique,olcDatabase={1}$BACKEND,cn=config
+changetype: modify
+replace: olcUniqueURI
+olcUniqueURI: ldap:///?sn?sub?((cn=e*))
+EOF
+RC=$?
+if test $RC != 80 ; then
+       echo "bad filter allowed"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit -1
+fi
+
+echo Verifying second configuration intact...
+$LDAPSEARCH -S "" -b olcOverlay='{0}'unique,olcDatabase='{1}'$BACKEND,cn=config -D cn=config -y $CONFIGPWF -h $LOCALHOST -p $PORT1 -LLL | tr -d \\r >testrun/second-config-recheck.ldif
+diff testrun/second-config-recheck.ldif testrun/second-reference.ldif > /dev/null 2>&1
+RC=$?
+if test $RC != 0 ; then
+    echo "Second configuration damaged by rejected modifies."
+    test $KILLSERVERS != no && kill -HUP $KILLPIDS
+    exit -1
+fi
+
+#echo ----------------------
+echo Dynamically reconfiguring to use different URIs...
+$LDAPMODIFY -D cn=config -h $LOCALHOST -p $PORT1 -y $CONFIGPWF \
+    > $TESTOUT 2>&1 <<EOF
+dn: olcOverlay={0}unique,olcDatabase={1}$BACKEND,cn=config
+changetype: modify
+add: olcUniqueURI
+olcUniqueURI: ldap:///?sn?sub?(cn=e*)
+-
+delete: olcUniqueURI
+olcUniqueURI: ldap:///?description?one
+EOF
+RC=$?
+if test $RC != 0 ; then
+       echo "unable to reconfigure"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit -1
+fi
+
+echo Dynamically retrieving third configuration...
+$LDAPSEARCH -S "" -b olcOverlay='{0}'unique,olcDatabase='{1}'$BACKEND,cn=config -D cn=config -y $CONFIGPWF -h $LOCALHOST -p $PORT1 -LLL | tr -d \\r >testrun/third-config.ldif
+cat >testrun/third-reference.ldif <<EOF
+dn: olcOverlay={0}unique,olcDatabase={1}$BACKEND,cn=config
+objectClass: olcOverlayConfig
+objectClass: olcUniqueConfig
+olcOverlay: {0}unique
+olcUniqueURI: ldap:///?employeeNumber,displayName?sub
+olcUniqueURI: ldap:///?sn?sub?(cn=e*)
+
+EOF
+diff testrun/third-config.ldif testrun/third-reference.ldif > /dev/null 2>&1
+RC=$?
+if test $RC != 0 ; then
+    echo "Third configuration is not reported correctly."
+    test $KILLSERVERS != no && kill -HUP $KILLPIDS
+    exit -1
+fi
+
+echo "Adding a record unique in both domains if filtered..."
+
+$LDAPADD -D "$UNIQUEDN" -h $LOCALHOST -p $PORT1 -w $PASSWD > \
+        $TESTOUT 2>&1 << EOF
+dn: uid=edgar,ou=users,o=unique
+objectClass: inetOrgPerson
+uid: edgar
+sn: johnson
+cn: edgar
+EOF
+
+RC=$?
+if test $RC != 0 ; then
+       echo "unique check failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit -1
+fi
+
+echo "Adding a record unique in one domain, non-unique in the filtered domain..."
+
+$LDAPADD -D "$UNIQUEDN" -h $LOCALHOST -p $PORT1 -w $PASSWD > \
+        $TESTOUT 2>&1 << EOF
+dn: uid=elvis,ou=users,o=unique
+objectClass: inetOrgPerson
+uid: elvis
+sn: johnson
+cn: elvis
+EOF
+
+RC=$?
+if test $RC != 19 ; then
+       echo "unique check failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit -1
+fi
 
 #echo ----------------------
-#$LDAPSEARCH -S "" -b "o=unique" -h $LOCALHOST -p $PORT1
+echo Dynamically reconfiguring to use attribute-ignore URIs...
+$LDAPMODIFY -D cn=config -h $LOCALHOST -p $PORT1 -y $CONFIGPWF \
+    > $TESTOUT 2>&1 <<EOF
+dn: olcOverlay={0}unique,olcDatabase={1}$BACKEND,cn=config
+changetype: modify
+replace: olcUniqueURI
+olcUniqueURI: ignore ldap:///?objectClass,uid,cn,sn?sub
+EOF
+RC=$?
+if test $RC != 0 ; then
+       echo "unable to reconfigure"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit -1
+fi
 
+echo Dynamically retrieving fourth configuration...
+$LDAPSEARCH -S "" -b olcOverlay='{0}'unique,olcDatabase='{1}'$BACKEND,cn=config -D cn=config -y $CONFIGPWF -h $LOCALHOST -p $PORT1 -LLL | tr -d \\r >testrun/fourth-config.ldif
+cat >testrun/fourth-reference.ldif <<EOF
+dn: olcOverlay={0}unique,olcDatabase={1}$BACKEND,cn=config
+objectClass: olcOverlayConfig
+objectClass: olcUniqueConfig
+olcOverlay: {0}unique
+olcUniqueURI: ignore ldap:///?objectClass,uid,cn,sn?sub
 
+EOF
+diff testrun/fourth-config.ldif testrun/fourth-reference.ldif > /dev/null 2>&1
+RC=$?
+if test $RC != 0 ; then
+    echo "Fourth configuration is not reported correctly."
+    test $KILLSERVERS != no && kill -HUP $KILLPIDS
+    exit -1
+fi
+
+echo "Adding a record unique in the ignore-domain..."
+
+$LDAPADD -D "$UNIQUEDN" -h $LOCALHOST -p $PORT1 -w $PASSWD > \
+        $TESTOUT 2>&1 << EOF
+dn: uid=elvis,ou=users,o=unique
+objectClass: inetOrgPerson
+uid: elvis
+sn: johnson
+cn: elvis
+description: left the building
+EOF
+
+RC=$?
+if test $RC != 0 ; then
+       echo "unique check failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit -1
+fi
+
+echo "Adding a record non-unique in the ignore-domain..."
+
+$LDAPADD -D "$UNIQUEDN" -h $LOCALHOST -p $PORT1 -w $PASSWD > \
+        $TESTOUT 2>&1 << EOF
+dn: uid=harry,ou=users,o=unique
+objectClass: inetOrgPerson
+uid: harry
+sn: johnson
+cn: harry
+description: left the building
+EOF
+
+RC=$?
+if test $RC != 19 ; then
+       echo "unique check failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit -1
+fi
 
 test $KILLSERVERS != no && kill -HUP $KILLPIDS