From 0befe875e27e5595ad7af62e07854c33c79b56b9 Mon Sep 17 00:00:00 2001
From: William B <william.e.brown@adelaide.edu.au>
Date: Sun, 8 Feb 2015 09:42:59 +1030
Subject: [PATCH] Support for uniqueness plugin to enforce uniqueness on a set
 of attributes.

Add basic tests for attribute uniqueness with multiple attributes, and improve
 filter generation in cases with objectClasses
---
 dirsrvtests/suites/dynamic-plugins/plugin_tests.py |  83 ++++++++
 ldap/servers/plugins/uiduniq/uid.c                 | 225 +++++++++++++++------
 2 files changed, 244 insertions(+), 64 deletions(-)

diff --git a/dirsrvtests/suites/dynamic-plugins/plugin_tests.py b/dirsrvtests/suites/dynamic-plugins/plugin_tests.py
index 315547b..c4decd3 100644
--- a/dirsrvtests/suites/dynamic-plugins/plugin_tests.py
+++ b/dirsrvtests/suites/dynamic-plugins/plugin_tests.py
@@ -296,6 +296,7 @@ def test_attruniq(inst, args=None):
                                      'cn': 'user 1',
                                      'uid': 'user1',
                                      'mail': 'user1@example.com',
+                                     'mailAlternateAddress' : 'user1@alt.example.com',
                                      'userpassword': 'password'})))
     except ldap.LDAPError, e:
         log.error('test_attruniq: Failed to add test user' + USER1_DN + ': error ' + e.message['desc'])
@@ -346,6 +347,88 @@ def test_attruniq(inst, args=None):
         assert False
 
     ############################################################################
+    # Reconfigure plugin for mail and mailAlternateAddress
+    ############################################################################
+
+    try:
+        inst.modify_s('cn=' + PLUGIN_ATTR_UNIQUENESS + ',cn=plugins,cn=config',
+                      [(ldap.MOD_REPLACE, 'uniqueness-attribute-name', 'mail'), 
+                       (ldap.MOD_ADD, 'uniqueness-attribute-name',
+                        'mailAlternateAddress')])
+
+    except ldap.LDAPError, e:
+        log.error('test_attruniq: Failed to reconfigure plugin for "mail mailAlternateAddress": error ' + e.message['desc'])
+        assert False
+
+    ############################################################################
+    # Test plugin - Add an entry, that has a duplicate "mail" value
+    ############################################################################
+
+    try:
+        inst.add_s(Entry((USER2_DN, {'objectclass': "top extensibleObject".split(),
+                                 'sn': '2',
+                                 'cn': 'user 2',
+                                 'uid': 'user2',
+                                 'mail': 'user1@example.com',
+                                 'userpassword': 'password'})))
+    except ldap.CONSTRAINT_VIOLATION:
+        pass
+    else:
+        log.error('test_attruniq: Adding of 3rd entry(mail) incorrectly succeeded')
+        assert False
+
+    ############################################################################
+    # Test plugin - Add an entry, that has a duplicate "mailAlternateAddress" value
+    ############################################################################
+
+    try:
+        inst.add_s(Entry((USER2_DN, {'objectclass': "top extensibleObject".split(),
+                                 'sn': '2',
+                                 'cn': 'user 2',
+                                 'uid': 'user2',
+                                 'mailAlternateAddress': 'user1@alt.example.com',
+                                 'userpassword': 'password'})))
+    except ldap.CONSTRAINT_VIOLATION:
+        pass
+    else:
+        log.error('test_attruniq: Adding of 4th entry(mailAlternateAddress) incorrectly succeeded')
+        assert False
+
+    ############################################################################
+    # Test plugin - Add an entry, that has a duplicate "mail" value conflicting mailAlternateAddress
+    ############################################################################
+
+    try:
+        inst.add_s(Entry((USER2_DN, {'objectclass': "top extensibleObject".split(),
+                                 'sn': '2',
+                                 'cn': 'user 2',
+                                 'uid': 'user2',
+                                 'mail': 'user1@alt.example.com',
+                                 'userpassword': 'password'})))
+    except ldap.CONSTRAINT_VIOLATION:
+        pass
+    else:
+        log.error('test_attruniq: Adding of 5th entry(mailAlternateAddress) incorrectly succeeded')
+        assert False
+
+    ############################################################################
+    # Test plugin - Add an entry, that has a duplicate "mailAlternateAddress" conflicting mail
+    ############################################################################
+
+    try:
+        inst.add_s(Entry((USER2_DN, {'objectclass': "top extensibleObject".split(),
+                                 'sn': '2',
+                                 'cn': 'user 2',
+                                 'uid': 'user2',
+                                 'mailAlternateAddress': 'user1@example.com',
+                                 'userpassword': 'password'})))
+    except ldap.CONSTRAINT_VIOLATION:
+        pass
+    else:
+        log.error('test_attruniq: Adding of 6th entry(mail) incorrectly succeeded')
+        assert False
+
+    ############################################################################
     # Test plugin dependency
     ############################################################################
 
diff --git a/ldap/servers/plugins/uiduniq/uid.c b/ldap/servers/plugins/uiduniq/uid.c
index 2120b4f..afcf0d7 100644
--- a/ldap/servers/plugins/uiduniq/uid.c
+++ b/ldap/servers/plugins/uiduniq/uid.c
@@ -69,7 +69,7 @@ int ldap_quote_filter_value(
       int *outLen);
 
 
-static int search_one_berval(Slapi_DN *baseDN, const char *attrName,
+static int search_one_berval(Slapi_DN *baseDN, const char **attrNames,
 		const struct berval *value, const char *requiredObjectClass, Slapi_DN *target);
 
 /*
@@ -100,7 +100,8 @@ pluginDesc = {
 };
 static void* plugin_identity = NULL;
 typedef struct attr_uniqueness_config {
-        char *attr;
+        const char **attrs;
+        char *attr_friendly;
         Slapi_DN **subtrees;
         PRBool unique_in_all_subtrees;
         char *top_entry_oc;
@@ -128,7 +129,9 @@ free_uniqueness_config(struct attr_uniqueness_config *config)
 {
         int i;
         
-        slapi_ch_free_string((char **) &config->attr);
+        for (i = 0; config->attrs && config->attrs[i]; i++) {
+                slapi_ch_free_string((char **) &(config->attrs[i]));
+        }
         for (i = 0; config->subtrees && config->subtrees[i]; i++) {
                 slapi_sdn_free(&config->subtrees[i]);
         }
@@ -183,6 +186,8 @@ uniqueness_entry_to_config(Slapi_PBlock *pb, Slapi_Entry *config_entry)
         char **argv = NULL;
         int rc = SLAPI_PLUGIN_SUCCESS;
         int i;
+        int attrLen = 0;
+        char *fp;
         int nb_subtrees = 0;
 
         if (config_entry == NULL) {
@@ -218,8 +223,18 @@ uniqueness_entry_to_config(Slapi_PBlock *pb, Slapi_Entry *config_entry)
                  */
 
                 /* Attribute name of the attribute we are going to check value uniqueness */
-                tmp_config->attr = slapi_entry_attr_get_charptr(config_entry, ATTR_UNIQUENESS_ATTRIBUTE_NAME);
-                
+                values = slapi_entry_attr_get_charray(config_entry, ATTR_UNIQUENESS_ATTRIBUTE_NAME);
+                if (values) {
+                        for (i = 0; values && values[i]; i++);
+                        tmp_config->attrs = (const char **) slapi_ch_calloc(i + 1, sizeof(char *));
+                        for (i = 0; values && values[i]; i++) {
+                                tmp_config->attrs[i] = slapi_ch_strdup(values[i]);
+                                slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "Adding attribute %s to uniqueness set\n", tmp_config->attrs[i]);
+                        }
+                        slapi_ch_array_free(values);
+                        values = NULL;
+                }
+
                 /* Subtrees where uniqueness is tested  */
                 values = slapi_entry_attr_get_charray(config_entry, ATTR_UNIQUENESS_SUBTREES);
                 if (values) {
@@ -283,7 +298,8 @@ uniqueness_entry_to_config(Slapi_PBlock *pb, Slapi_Entry *config_entry)
                         }
                         
                         /* Store attrName in the config */
-                        tmp_config->attr = slapi_ch_strdup(attrName);
+                        tmp_config->attrs = (const char **) slapi_ch_calloc(1, sizeof(char *));
+                        tmp_config->attrs[0] = slapi_ch_strdup(attrName);
                         argc--;
                         argv++; /* First argument was attribute name and remaining are subtrees */
                         
@@ -323,7 +339,8 @@ uniqueness_entry_to_config(Slapi_PBlock *pb, Slapi_Entry *config_entry)
                          *  - requiredObjectClass 
                          */
                         /* Store attrName in the config */
-                        tmp_config->attr = slapi_ch_strdup(attrName);
+                        tmp_config->attrs = (const char **) slapi_ch_calloc(1, sizeof(char *));
+                        tmp_config->attrs[1] = slapi_ch_strdup(attrName);
                         
                         /* There is no subtrees */
                         tmp_config->subtrees = NULL;
@@ -340,12 +357,25 @@ uniqueness_entry_to_config(Slapi_PBlock *pb, Slapi_Entry *config_entry)
         }
         
         /* Time to check that the new configuration is valid */
-        if (tmp_config->attr == NULL) {
+        /* Check that we have 1 or more value */
+        if (tmp_config->attrs == NULL) {
                 slapi_log_error( SLAPI_LOG_FATAL, plugin_name, "Config info: attribute name not defined \n");
                 rc = SLAPI_PLUGIN_FAILURE;
                 goto done;
         }
-        
+        /* If the config is valid, prepare the friendly string for error messages */
+        for (i = 0; tmp_config->attrs && (tmp_config->attrs)[i]; i++) {
+            attrLen += strlen((tmp_config->attrs)[i]) + 1;
+        }
+        tmp_config->attr_friendly = (char *) slapi_ch_calloc(attrLen, sizeof(char *));
+        fp = tmp_config->attr_friendly;
+        for (i = 0; tmp_config->attrs && (tmp_config->attrs)[i]; i++) {
+            strcpy(fp, (tmp_config->attrs)[i] );
+            fp += strlen((tmp_config->attrs)[i]);
+            strcpy(fp, " ");
+            fp++;
+        }
+
         if (tmp_config->subtrees == NULL) {
                 /* Uniqueness is enforced on entries matching objectclass */
                 if (tmp_config->subtree_entries_oc == NULL) {
@@ -407,20 +437,39 @@ uid_op_error(int internal_error)
  */
 
 static char *
-create_filter(const char *attribute, const struct berval *value, const char *requiredObjectClass)
+create_filter(const char **attributes, const struct berval *value, const char *requiredObjectClass)
 {
   char *filter;
   char *fp;
   char *max;
-  int attrLen;
+  int  *attrLen;
+  int totalAttrLen = 0;
+  int attrCount = 0;
   int valueLen;
   int classLen = 0;
   int filterLen;
+  int i = 0;
 
-  PR_ASSERT(attribute);
+  PR_ASSERT(attributes);
 
   /* Compute the length of the required buffer */
-  attrLen = strlen(attribute);
+  for (attrCount = 0; attributes && attributes[attrCount]; attrCount++);
+  attrCount == attrCount + 1;
+  attrLen = (int *) slapi_ch_calloc(attrCount, sizeof(int));
+  for (i = 0; attributes && attributes[i]; i++) {
+      attrLen[i] += strlen(attributes[i]);
+      totalAttrLen += attrLen[i];
+  }
+
+  /* if attrCount is 1, attrLen is already corect for usage.*/
+  if (attrCount > 1) {
+    /* Filter will be (|(attr=value)(attr=value)) */
+    /* 3 for the (| ) */
+    /* 3 for each attr for (=) not in attr or value */
+    totalAttrLen += 3 + (attrCount * 3);
+  } else {
+    totalAttrLen += 3;
+  }
 
   if (ldap_quote_filter_value(value->bv_val, 
 	value->bv_len, 0, 0, &valueLen))
@@ -428,10 +477,10 @@ create_filter(const char *attribute, const struct berval *value, const char *req
 
   if (requiredObjectClass) {
     classLen = strlen(requiredObjectClass);
-    /* "(&(objectClass=)())" == 19 */
-    filterLen = attrLen + 1 + valueLen + classLen + 19 + 1;
+    /* "(&(objectClass=)<Filter here>)" == 17 */
+    filterLen = totalAttrLen + 1 + (valueLen * attrCount) + classLen + 17 + 1;
   } else {
-    filterLen = attrLen + 1 + valueLen + 1;
+    filterLen = totalAttrLen + 1 + (valueLen * attrCount) + 1;
   }
 
   /* Allocate the buffer */
@@ -446,25 +495,51 @@ create_filter(const char *attribute, const struct berval *value, const char *req
     strcpy(fp, requiredObjectClass);
     fp += classLen;
     *fp++ = ')';
-    *fp++ = '(';
   }
 
-  /* Place attribute name in filter */
-  strcpy(fp, attribute);
-  fp += attrLen;
+  if (attrCount == 1) {
+      *fp++ = '(';
+      /* Place attribute name in filter */
+      strcpy(fp, attributes[0]);
+      fp += attrLen[0];
 
-  /* Place comparison operator */
-  *fp++ = '=';
+      /* Place comparison operator */
+      *fp++ = '=';
 
-  /* Place value in filter */
-  if (ldap_quote_filter_value(value->bv_val, value->bv_len,
-    fp, max-fp, &valueLen)) { slapi_ch_free((void**)&filter); return 0; }
-  fp += valueLen;
+      /* Place value in filter */
+      if (ldap_quote_filter_value(value->bv_val, value->bv_len,
+        fp, max-fp, &valueLen)) { slapi_ch_free((void**)&filter); return 0; }
+      fp += valueLen;
+      *fp++ = ')';
+  } else {
+      strcpy(fp, "(|");
+      fp += 2;
+
+      for (i = 0; attributes && attributes[i]; i++) {
+          strcpy(fp, "(");
+          fp += 1;
+          /* Place attribute name in filter */
+          strcpy(fp, attributes[i]);
+          fp += attrLen[i];
+
+          /* Place comparison operator */
+          *fp++ = '=';
+
+          /* Place value in filter */
+          if (ldap_quote_filter_value(value->bv_val, value->bv_len,
+            fp, max-fp, &valueLen)) { slapi_ch_free((void**)&filter); return 0; }
+          fp += valueLen;
+
+          strcpy(fp, ")");
+          fp += 1;
+      }
+      strcpy(fp, ")");
+      fp += 1;
+  }
 
   /* Close AND expression if a requiredObjectClass was set */
   if (requiredObjectClass) {
     *fp++ = ')';
-    *fp++ = ')';
   }
 
   /* Terminate */
@@ -490,15 +565,16 @@ create_filter(const char *attribute, const struct berval *value, const char *req
  *   LDAP_OPERATIONS_ERROR - a server failure.
  */
 static int
-search(Slapi_DN *baseDN, const char *attrName, Slapi_Attr *attr,
+search(Slapi_DN *baseDN, const char **attrNames, Slapi_Attr *attr,
   struct berval **values, const char *requiredObjectClass,
   Slapi_DN *target)
 {
   int result;
 
 #ifdef DEBUG
+    /* Fix this later to print all the attr names */
     slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
-                    "SEARCH baseDN=%s attr=%s target=%s\n", slapi_sdn_get_dn(baseDN), attrName, 
+                    "SEARCH baseDN=%s attr=%s target=%s\n", slapi_sdn_get_dn(baseDN), attrNames[0], 
                     target?slapi_sdn_get_dn(target):"None");
 #endif
 
@@ -524,7 +600,7 @@ search(Slapi_DN *baseDN, const char *attrName, Slapi_Attr *attr,
 		vhint != -1 && LDAP_SUCCESS == result;
 		vhint = slapi_attr_next_value( attr, vhint, &v ))
 	{
-	  result = search_one_berval(baseDN, attrName,
+	  result = search_one_berval(baseDN, attrNames,
 					slapi_value_get_berval(v),
 					requiredObjectClass, target);
 	}
@@ -533,7 +609,7 @@ search(Slapi_DN *baseDN, const char *attrName, Slapi_Attr *attr,
   {
 	for (;*values != NULL && LDAP_SUCCESS == result; values++)
 	{
-	  result = search_one_berval(baseDN, attrName, *values, requiredObjectClass,
+	  result = search_one_berval(baseDN, attrNames, *values, requiredObjectClass,
 					target);
 	}
   }
@@ -548,7 +624,7 @@ search(Slapi_DN *baseDN, const char *attrName, Slapi_Attr *attr,
 
 
 static int
-search_one_berval(Slapi_DN *baseDN, const char *attrName,
+search_one_berval(Slapi_DN *baseDN, const char **attrNames,
 		const struct berval *value, const char *requiredObjectClass,
 		Slapi_DN *target)
 {
@@ -572,7 +648,7 @@ search_one_berval(Slapi_DN *baseDN, const char *attrName,
       static char *attrs[] = { "1.1", 0 };
 
       /* Create the filter - this needs to be freed */
-      filter = create_filter(attrName, value, requiredObjectClass);
+      filter = create_filter(attrNames, value, requiredObjectClass);
 
 #ifdef DEBUG
       slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
@@ -656,7 +732,7 @@ search_one_berval(Slapi_DN *baseDN, const char *attrName,
  *   LDAP_OPERATIONS_ERROR - a server failure.
  */
 static int
-searchAllSubtrees(Slapi_DN **subtrees, const char *attrName,
+searchAllSubtrees(Slapi_DN **subtrees, const char **attrNames,
   Slapi_Attr *attr, struct berval **values, const char *requiredObjectClass,
   Slapi_DN *dn, PRBool unique_in_all_subtrees)
 {
@@ -697,7 +773,7 @@ searchAllSubtrees(Slapi_DN **subtrees, const char *attrName,
      * worry about that here.
      */
     if (unique_in_all_subtrees || slapi_sdn_issuffix(dn, sufdn)) {
-      result = search(sufdn, attrName, attr, values, requiredObjectClass, dn);
+      result = search(sufdn, attrNames, attr, values, requiredObjectClass, dn);
       if (result) break;
     }
   }
@@ -785,7 +861,7 @@ getArguments(Slapi_PBlock *pb, char **attrName, char **markerObjectClass,
  *   LDAP_OPERATIONS_ERROR - a server failure.
  */
 static int
-findSubtreeAndSearch(Slapi_DN *parentDN, const char *attrName, Slapi_Attr *attr,
+findSubtreeAndSearch(Slapi_DN *parentDN, const char **attrNames, Slapi_Attr *attr,
   struct berval **values, const char *requiredObjectClass, Slapi_DN *target,
   const char *markerObjectClass)
 {
@@ -804,7 +880,7 @@ findSubtreeAndSearch(Slapi_DN *parentDN, const char *attrName, Slapi_Attr *attr,
            * Do the search.   There is no entry that is allowed
            * to have the attribute already.
            */
-          result = search(curpar, attrName, attr, values, requiredObjectClass,
+          result = search(curpar, attrNames, attr, values, requiredObjectClass,
                           target);
           break;
         }
@@ -827,7 +903,8 @@ preop_add(Slapi_PBlock *pb)
 {
   int result;
   char *errtext = NULL;
-  char *attrName = NULL;
+  const char **attrNames = NULL;
+  char * attr_friendly = NULL;
 
 #ifdef DEBUG
   slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name, "ADD begin\n");
@@ -848,6 +925,8 @@ preop_add(Slapi_PBlock *pb)
     Slapi_Entry *e;
     Slapi_Attr *attr;
     struct attr_uniqueness_config *config = NULL;
+    int uniqueAttrCount = 0;
+    int i = 0;
 
         /*
          * If this is a replication update, just be a noop.
@@ -868,9 +947,10 @@ preop_add(Slapi_PBlock *pb)
         /*
          * Get the arguments
          */
-        attrName = config->attr;
+        attrNames = config->attrs;
         markerObjectClass = config->top_entry_oc;
         requiredObjectClass = config->subtree_entries_oc;
+        attr_friendly = config->attr_friendly;
 
     /*
      * Get the target DN for this add operation
@@ -889,8 +969,13 @@ preop_add(Slapi_PBlock *pb)
         err = slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &e);
         if (err) { result = uid_op_error(52); break; }
 
-        err = slapi_entry_attr_find(e, attrName, &attr);
-        if (err) break;  /* no unique attribute */
+        for (i = 0; attrNames && attrNames[i]; i++) {
+            err = slapi_entry_attr_find(e, attrNames[i], &attr);
+            if (!err) uniqueAttrCount++;
+        }
+        if (uniqueAttrCount == 0) {
+          break;
+        }
 
         /*
          * Check if it contains the required object class
@@ -912,13 +997,13 @@ preop_add(Slapi_PBlock *pb)
         if (NULL != markerObjectClass)
         {
           /* Subtree defined by location of marker object class */
-                result = findSubtreeAndSearch(sdn, attrName, attr, NULL,
+                result = findSubtreeAndSearch(sdn, attrNames, attr, NULL,
                                               requiredObjectClass, sdn,
                                               markerObjectClass);
         } else
         {
           /* Subtrees listed on invocation line */
-          result = searchAllSubtrees(config->subtrees, attrName, attr, NULL,
+          result = searchAllSubtrees(config->subtrees, attrNames, attr, NULL,
                                      requiredObjectClass, sdn, config->unique_in_all_subtrees);
         }
   END
@@ -929,7 +1014,7 @@ preop_add(Slapi_PBlock *pb)
       "ADD result %d\n", result);
 
     if (result == LDAP_CONSTRAINT_VIOLATION) {
-      errtext = slapi_ch_smprintf(moreInfo, attrName);
+      errtext = slapi_ch_smprintf(moreInfo, attr_friendly);
     } else {
       errtext = slapi_ch_strdup("Error checking for attribute uniqueness.");
     }
@@ -971,8 +1056,9 @@ preop_modify(Slapi_PBlock *pb)
   LDAPMod **checkmods = NULL;
   int checkmodsCapacity = 0;
   char *errtext = NULL;
-  char *attrName = NULL;
+  const char **attrNames = NULL;
   struct attr_uniqueness_config *config = NULL;
+  char *attr_friendly = NULL;
 
 #ifdef DEBUG
     slapi_log_error(SLAPI_LOG_PLUGIN, plugin_name,
@@ -989,6 +1075,7 @@ preop_modify(Slapi_PBlock *pb)
     LDAPMod *mod;
     Slapi_DN *sdn = NULL;
     int isupdatedn;
+    int i = 0;
 
     /*
      * If this is a replication update, just be a noop.
@@ -1009,9 +1096,10 @@ preop_modify(Slapi_PBlock *pb)
     /*
      * Get the arguments
      */
-    attrName = config->attr;
+    attrNames = config->attrs;
     markerObjectClass = config->top_entry_oc;
     requiredObjectClass = config->subtree_entries_oc;
+    attr_friendly = config->attr_friendly;
 
         
     err = slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods);
@@ -1032,14 +1120,16 @@ preop_modify(Slapi_PBlock *pb)
     for(;mods && *mods;mods++)
     {
         mod = *mods;
-        if ((slapi_attr_type_cmp(mod->mod_type, attrName, 1) == 0) && /* mod contains target attr */
-            (mod->mod_op & LDAP_MOD_BVALUES) && /* mod is bval encoded (not string val) */
-            (mod->mod_bvalues && mod->mod_bvalues[0]) && /* mod actually contains some values */
-            (SLAPI_IS_MOD_ADD(mod->mod_op) || /* mod is add */
-             SLAPI_IS_MOD_REPLACE(mod->mod_op))) /* mod is replace */
-        {
-          addMod(&checkmods, &checkmodsCapacity, &modcount, mod);
-        }
+        for (i = 0; attrNames && attrNames[i]; i++) {
+          if ((slapi_attr_type_cmp(mod->mod_type, attrNames[i], 1) == 0) && /* mod contains target attr */
+              (mod->mod_op & LDAP_MOD_BVALUES) && /* mod is bval encoded (not string val) */
+              (mod->mod_bvalues && mod->mod_bvalues[0]) && /* mod actually contains some values */
+              (SLAPI_IS_MOD_ADD(mod->mod_op) || /* mod is add */
+               SLAPI_IS_MOD_REPLACE(mod->mod_op))) /* mod is replace */
+          {
+            addMod(&checkmods, &checkmodsCapacity, &modcount, mod);
+          }
+       }
     }
     if (modcount == 0) {
         break; /* no mods to check, we are done */
@@ -1071,13 +1161,13 @@ preop_modify(Slapi_PBlock *pb)
         if (NULL != markerObjectClass)
         {
             /* Subtree defined by location of marker object class */
-            result = findSubtreeAndSearch(sdn, attrName, NULL, 
+            result = findSubtreeAndSearch(sdn, attrNames, NULL, 
                                           mod->mod_bvalues, requiredObjectClass,
                                           sdn, markerObjectClass);
         } else
         {
             /* Subtrees listed on invocation line */
-            result = searchAllSubtrees(config->subtrees, attrName, NULL,
+            result = searchAllSubtrees(config->subtrees, attrNames, NULL,
                                        mod->mod_bvalues, requiredObjectClass, sdn, config->unique_in_all_subtrees);
         }
     }
@@ -1091,7 +1181,7 @@ preop_modify(Slapi_PBlock *pb)
       "MODIFY result %d\n", result);
 
     if (result == LDAP_CONSTRAINT_VIOLATION) {
-      errtext = slapi_ch_smprintf(moreInfo, attrName);
+      errtext = slapi_ch_smprintf(moreInfo, attr_friendly);
     } else {
       errtext = slapi_ch_strdup("Error checking for attribute uniqueness.");
     }
@@ -1119,7 +1209,7 @@ preop_modrdn(Slapi_PBlock *pb)
   Slapi_Entry *e = NULL;
   Slapi_Value *sv_requiredObjectClass = NULL;
   char *errtext = NULL;
-  char *attrName = NULL;
+  const char **attrNames = NULL;
   struct attr_uniqueness_config *config = NULL;
 
 #ifdef DEBUG
@@ -1137,6 +1227,8 @@ preop_modrdn(Slapi_PBlock *pb)
     int deloldrdn = 0;
     int isupdatedn;
     Slapi_Attr *attr;
+    int i = 0;
+    int uniqueAttrCount = 0;
 
         /*
          * If this is a replication update, just be a noop.
@@ -1157,7 +1249,7 @@ preop_modrdn(Slapi_PBlock *pb)
         /*
          * Get the arguments
          */
-        attrName = config->attr;
+        attrNames = config->attrs;
         markerObjectClass = config->top_entry_oc;
         requiredObjectClass = config->subtree_entries_oc;
 
@@ -1214,8 +1306,13 @@ preop_modrdn(Slapi_PBlock *pb)
         /*
          * Find any unique attribute data in the new RDN
          */
-        err = slapi_entry_attr_find(e, attrName, &attr);
-        if (err) break;  /* no UID attribute */
+        for (i = 0; attrNames && attrNames[i]; i++) {
+            err = slapi_entry_attr_find(e, attrNames[i], &attr);
+            if (!err) uniqueAttrCount++;
+        }
+        if (uniqueAttrCount == 0) {
+          break;
+        }
 
         /*
          * Check if it has the required object class
@@ -1231,13 +1328,13 @@ preop_modrdn(Slapi_PBlock *pb)
         if (NULL != markerObjectClass)
         {
           /* Subtree defined by location of marker object class */
-                result = findSubtreeAndSearch(slapi_entry_get_sdn(e), attrName, attr, NULL,
+                result = findSubtreeAndSearch(slapi_entry_get_sdn(e), attrNames, attr, NULL,
                                               requiredObjectClass, sdn,
                                               markerObjectClass);
         } else
         {
           /* Subtrees listed on invocation line */
-          result = searchAllSubtrees(config->subtrees, attrName, attr, NULL,
+          result = searchAllSubtrees(config->subtrees, attrNames, attr, NULL,
                                      requiredObjectClass, sdn, config->unique_in_all_subtrees);
         }
   END
@@ -1251,7 +1348,7 @@ preop_modrdn(Slapi_PBlock *pb)
       "MODRDN result %d\n", result);
 
     if (result == LDAP_CONSTRAINT_VIOLATION) {
-      errtext = slapi_ch_smprintf(moreInfo, attrName);
+      errtext = slapi_ch_smprintf(moreInfo, config->attr_friendly);
     } else {
       errtext = slapi_ch_strdup("Error checking for attribute uniqueness.");
     }
-- 
1.9.3

