From c94ac1c82ab51d8ac006b5a69c3973296bee0d4b Mon Sep 17 00:00:00 2001
From: Thierry Bordaz <tbordaz@redhat.com>
Date: Tue, 28 Apr 2015 19:35:34 +0200
Subject: [PATCH] Ticket 47927: Uniqueness plugin: should allow to exclude
 some subtrees from its scope

Bug Description:
	Support excluding subtrees in uniqueness plugin

Fix Description:
	Exclude subtrees from uniqueness check if they are specified in the
	plugin config.

	To exclude certain parts of the tree from uniqueness check,
	set
	    uniqueness-exclude-subtrees: cn=subtree,dc=example,dc=com
	in the plugin's configuration.

	Only new style configuration is supported for specifying excludes.

https://fedorahosted.org/389/ticket/47927

Reviewed by: ?

Platforms tested: F17

Flag Day: no

Doc impact: yes (new uniqueness attribute uniqueness-exclude-subtrees)
---
 dirsrvtests/tickets/ticket47823_test.py |  13 +-
 dirsrvtests/tickets/ticket47927_test.py | 300 ++++++++++++++++++++++++++++++++
 ldap/servers/plugins/uiduniq/uid.c      |  97 +++++++++--
 3 files changed, 389 insertions(+), 21 deletions(-)
 create mode 100644 dirsrvtests/tickets/ticket47927_test.py

diff --git a/dirsrvtests/tickets/ticket47823_test.py b/dirsrvtests/tickets/ticket47823_test.py
index 317ebf5..e685fbf 100644
--- a/dirsrvtests/tickets/ticket47823_test.py
+++ b/dirsrvtests/tickets/ticket47823_test.py
@@ -59,11 +59,11 @@ def topology(request):
     '''
     global installation_prefix
 
-    if installation_prefix:
-        args_instance[SER_DEPLOYED_DIR] = installation_prefix
 
-    standalone = DirSrv(verbose=False)
 
+    standalone = DirSrv(verbose=False)
+    if installation_prefix:
+        args_instance[SER_DEPLOYED_DIR] = installation_prefix
     # Args for the standalone instance
     args_instance[SER_HOST] = HOST_STANDALONE
     args_instance[SER_PORT] = PORT_STANDALONE
@@ -629,6 +629,7 @@ def test_ticket47823_invalid_config_1(topology):
     topology.standalone.getEntry(config.dn, ldap.SCOPE_BASE, "(objectclass=nsSlapdPlugin)", ALL_CONFIG_ATTRS)
 
     # Check the server did not restart
+    topology.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE, 'nsslapd-errorlog-level', '65536')])
     try:
         topology.standalone.restart(timeout=5)
         ent = topology.standalone.getEntry(config.dn, ldap.SCOPE_BASE, "(objectclass=nsSlapdPlugin)", ALL_CONFIG_ATTRS)
@@ -640,7 +641,7 @@ def test_ticket47823_invalid_config_1(topology):
             pass
 
     # Check the expected error message
-    regex = re.compile("Config info: attribute name not defined")
+    regex = re.compile("Config fail: unable to parse old style")
     res = _pattern_errorlog(topology.standalone.errorlog_file, regex)
     if not res:
         # be sure to restore a valid config before assert
@@ -732,6 +733,7 @@ def test_ticket47823_invalid_config_3(topology):
     topology.standalone.getEntry(config.dn, ldap.SCOPE_BASE, "(objectclass=nsSlapdPlugin)", ALL_CONFIG_ATTRS)
 
     # Check the server did not restart
+    topology.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE, 'nsslapd-errorlog-level', '65536')])
     try:
         topology.standalone.restart(timeout=5)
         ent = topology.standalone.getEntry(config.dn, ldap.SCOPE_BASE, "(objectclass=nsSlapdPlugin)", ALL_CONFIG_ATTRS)
@@ -743,7 +745,7 @@ def test_ticket47823_invalid_config_3(topology):
             pass
 
     # Check the expected error message
-    regex = re.compile("Config info: objectclass for subtree entries is not defined")
+    regex = re.compile("Config fail: unable to parse old style")
     res = _pattern_errorlog(topology.standalone.errorlog_file, regex)
     if not res:
         # be sure to restore a valid config before assert
@@ -925,6 +927,7 @@ def test_ticket47823_invalid_config_7(topology):
     # create an invalid config without arg0
     config = _build_config(topology, attr_name='cn', subtree_1="this_is dummy DN", subtree_2="an other=dummy DN", type_config='new', across_subtrees=False)
 
+    topology.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE, 'nsslapd-errorlog-level', '65536')])
     # replace 'cn' uniqueness entry
     try:
         topology.standalone.delete_s(config.dn)
diff --git a/dirsrvtests/tickets/ticket47927_test.py b/dirsrvtests/tickets/ticket47927_test.py
new file mode 100644
index 0000000..ee1f130
--- /dev/null
+++ b/dirsrvtests/tickets/ticket47927_test.py
@@ -0,0 +1,300 @@
+import os
+import sys
+import time
+import ldap
+import logging
+import pytest
+from lib389 import DirSrv, Entry, tools, tasks
+from lib389.tools import DirSrvTools
+from lib389._constants import *
+from lib389.properties import *
+from lib389.tasks import *
+from lib389.utils import *
+
+logging.getLogger(__name__).setLevel(logging.DEBUG)
+log = logging.getLogger(__name__)
+
+installation1_prefix = None
+
+EXCLUDED_CONTAINER_CN = "excluded_container"
+EXCLUDED_CONTAINER_DN = "cn=%s,%s" % (EXCLUDED_CONTAINER_CN, SUFFIX)
+
+EXCLUDED_BIS_CONTAINER_CN = "excluded_bis_container"
+EXCLUDED_BIS_CONTAINER_DN = "cn=%s,%s" % (EXCLUDED_BIS_CONTAINER_CN, SUFFIX)
+
+ENFORCED_CONTAINER_CN = "enforced_container"
+ENFORCED_CONTAINER_DN = "cn=%s,%s" % (ENFORCED_CONTAINER_CN, SUFFIX)
+
+USER_1_CN = "test_1"
+USER_1_DN = "cn=%s,%s" % (USER_1_CN, ENFORCED_CONTAINER_DN)
+USER_2_CN = "test_2"
+USER_2_DN = "cn=%s,%s" % (USER_2_CN, ENFORCED_CONTAINER_DN)
+USER_3_CN = "test_3"
+USER_3_DN = "cn=%s,%s" % (USER_3_CN, EXCLUDED_CONTAINER_DN)
+USER_4_CN = "test_4"
+USER_4_DN = "cn=%s,%s" % (USER_4_CN, EXCLUDED_BIS_CONTAINER_DN)
+
+
+class TopologyStandalone(object):
+    def __init__(self, standalone):
+        standalone.open()
+        self.standalone = standalone
+
+
+@pytest.fixture(scope="module")
+def topology(request):
+    global installation1_prefix
+
+    # Creating standalone instance ...
+    standalone = DirSrv(verbose=False)
+    if installation1_prefix:
+        args_instance[SER_DEPLOYED_DIR] = installation1_prefix
+    args_instance[SER_HOST] = HOST_STANDALONE
+    args_instance[SER_PORT] = PORT_STANDALONE
+    args_instance[SER_SERVERID_PROP] = SERVERID_STANDALONE
+    args_instance[SER_CREATION_SUFFIX] = DEFAULT_SUFFIX
+    args_standalone = args_instance.copy()
+    standalone.allocate(args_standalone)
+    instance_standalone = standalone.exists()
+    if instance_standalone:
+        standalone.delete()
+    standalone.create()
+    standalone.open()
+
+    # Clear out the tmp dir
+    standalone.clearTmpDir(__file__)
+
+    return TopologyStandalone(standalone)
+
+def test_ticket47927_init(topology):
+    topology.standalone.plugins.enable(name=PLUGIN_ATTR_UNIQUENESS)
+    try:
+        topology.standalone.modify_s('cn=' + PLUGIN_ATTR_UNIQUENESS + ',cn=plugins,cn=config',
+                      [(ldap.MOD_REPLACE, 'uniqueness-attribute-name', 'telephonenumber'),
+                       (ldap.MOD_REPLACE, 'uniqueness-subtrees', DEFAULT_SUFFIX),
+                      ])
+    except ldap.LDAPError, e:
+        log.fatal('test_ticket47927: Failed to configure plugin for "telephonenumber": error ' + e.message['desc'])
+        assert False
+    topology.standalone.restart(timeout=120)
+
+    topology.standalone.add_s(Entry((EXCLUDED_CONTAINER_DN, {'objectclass': "top nscontainer".split(),
+                                                             'cn': EXCLUDED_CONTAINER_CN})))
+    topology.standalone.add_s(Entry((EXCLUDED_BIS_CONTAINER_DN, {'objectclass': "top nscontainer".split(),
+                                                                 'cn': EXCLUDED_BIS_CONTAINER_CN})))
+    topology.standalone.add_s(Entry((ENFORCED_CONTAINER_DN, {'objectclass': "top nscontainer".split(),
+                                                             'cn': ENFORCED_CONTAINER_CN})))
+
+        # adding an entry on a stage with a different 'cn'
+    topology.standalone.add_s(Entry((USER_1_DN, {
+                                    'objectclass': "top person".split(),
+                                    'sn':           USER_1_CN,
+                                    'cn':           USER_1_CN})))
+        # adding an entry on a stage with a different 'cn'
+    topology.standalone.add_s(Entry((USER_2_DN, {
+                                    'objectclass': "top person".split(),
+                                    'sn':           USER_2_CN,
+                                    'cn':           USER_2_CN})))
+    topology.standalone.add_s(Entry((USER_3_DN, {
+                                    'objectclass': "top person".split(),
+                                    'sn':           USER_3_CN,
+                                    'cn':           USER_3_CN})))
+    topology.standalone.add_s(Entry((USER_4_DN, {
+                                    'objectclass': "top person".split(),
+                                    'sn':           USER_4_CN,
+                                    'cn':           USER_4_CN})))
+    
+def test_ticket47927_one(topology):
+    '''
+    Check that uniqueness is enforce on all SUFFIX
+    '''
+    UNIQUE_VALUE='1234'
+    try:
+        topology.standalone.modify_s(USER_1_DN,
+                      [(ldap.MOD_REPLACE, 'telephonenumber', UNIQUE_VALUE)])
+    except ldap.LDAPError, e:
+        log.fatal('test_ticket47927_one: Failed to set the telephonenumber for %s: %s' % (USER_1_DN, e.message['desc']))
+        assert False
+
+    # we expect to fail because user1 is in the scope of the plugin
+    try:
+        topology.standalone.modify_s(USER_2_DN,
+                      [(ldap.MOD_REPLACE, 'telephonenumber', UNIQUE_VALUE)])
+        log.fatal('test_ticket47927_one: unexpected success to set the telephonenumber for %s' % (USER_2_DN))
+        assert False
+    except ldap.LDAPError, e:
+        log.fatal('test_ticket47927_one: Failed (expected) to set the telephonenumber for %s: %s' % (USER_2_DN, e.message['desc']))
+        pass
+
+
+    # we expect to fail because user1 is in the scope of the plugin
+    try:
+        topology.standalone.modify_s(USER_3_DN,
+                      [(ldap.MOD_REPLACE, 'telephonenumber', UNIQUE_VALUE)])
+        log.fatal('test_ticket47927_one: unexpected success to set the telephonenumber for %s' % (USER_3_DN))
+        assert False
+    except ldap.LDAPError, e:
+        log.fatal('test_ticket47927_one: Failed (expected) to set the telephonenumber for %s: %s' % (USER_3_DN, e.message['desc']))
+        pass
+
+
+def test_ticket47927_two(topology):
+    '''
+    Exclude the EXCLUDED_CONTAINER_DN from the uniqueness plugin
+    '''
+    try:
+        topology.standalone.modify_s('cn=' + PLUGIN_ATTR_UNIQUENESS + ',cn=plugins,cn=config',
+                      [(ldap.MOD_REPLACE, 'uniqueness-exclude-subtrees', EXCLUDED_CONTAINER_DN)])
+    except ldap.LDAPError, e:
+        log.fatal('test_ticket47927_two: Failed to configure plugin for to exclude %s: error %s' % (EXCLUDED_CONTAINER_DN, e.message['desc']))
+        assert False
+    topology.standalone.restart(timeout=120)
+
+def test_ticket47927_three(topology):
+    '''
+    Check that uniqueness is enforced on full SUFFIX except EXCLUDED_CONTAINER_DN
+    First case: it exists an entry (with the same attribute value) in the scope
+    of the plugin and we set the value in an entry that is in an excluded scope
+    '''
+    UNIQUE_VALUE='9876'
+    try:
+        topology.standalone.modify_s(USER_1_DN,
+                      [(ldap.MOD_REPLACE, 'telephonenumber', UNIQUE_VALUE)])
+    except ldap.LDAPError, e:
+        log.fatal('test_ticket47927_three: Failed to set the telephonenumber ' + e.message['desc'])
+        assert False
+    
+    # we should not be allowed to set this value (because user1 is in the scope)
+    try:
+        topology.standalone.modify_s(USER_2_DN,
+                      [(ldap.MOD_REPLACE, 'telephonenumber', UNIQUE_VALUE)])
+        log.fatal('test_ticket47927_three: unexpected success to set the telephonenumber for %s' % (USER_2_DN))
+        assert False
+    except ldap.LDAPError, e:
+        log.fatal('test_ticket47927_three: Failed (expected) to set the telephonenumber for %s: %s' % (USER_2_DN , e.message['desc']))
+
+
+    # USER_3_DN is in EXCLUDED_CONTAINER_DN so update should be successful
+    try:
+        topology.standalone.modify_s(USER_3_DN,
+                      [(ldap.MOD_REPLACE, 'telephonenumber', UNIQUE_VALUE)])
+        log.fatal('test_ticket47927_three: success to set the telephonenumber for %s' % (USER_3_DN))
+    except ldap.LDAPError, e:
+        log.fatal('test_ticket47927_three: Failed (unexpected) to set the telephonenumber for %s: %s' % (USER_3_DN, e.message['desc']))
+        assert False
+
+
+def test_ticket47927_four(topology):
+    '''
+    Check that uniqueness is enforced on full SUFFIX except EXCLUDED_CONTAINER_DN
+    Second case: it exists an entry (with the same attribute value) in an excluded scope
+    of the plugin and we set the value in an entry is in the scope
+    '''
+    UNIQUE_VALUE='1111'
+    # USER_3_DN is in EXCLUDED_CONTAINER_DN so update should be successful
+    try:
+        topology.standalone.modify_s(USER_3_DN,
+                      [(ldap.MOD_REPLACE, 'telephonenumber', UNIQUE_VALUE)])
+        log.fatal('test_ticket47927_four: success to set the telephonenumber for %s' % USER_3_DN)
+    except ldap.LDAPError, e:
+        log.fatal('test_ticket47927_four: Failed (unexpected) to set the telephonenumber for %s: %s' % (USER_3_DN, e.message['desc']))
+        assert False
+
+
+    # we should be allowed to set this value (because user3 is excluded from scope)
+    try:
+        topology.standalone.modify_s(USER_1_DN,
+                      [(ldap.MOD_REPLACE, 'telephonenumber', UNIQUE_VALUE)])
+    except ldap.LDAPError, e:
+        log.fatal('test_ticket47927_four: Failed to set the telephonenumber for %s: %s' % (USER_1_DN, e.message['desc']))
+        assert False
+
+    # we should not be allowed to set this value (because user1 is in the scope)
+    try:
+        topology.standalone.modify_s(USER_2_DN,
+                      [(ldap.MOD_REPLACE, 'telephonenumber', UNIQUE_VALUE)])
+        log.fatal('test_ticket47927_four: unexpected success to set the telephonenumber %s' % USER_2_DN)
+        assert False
+    except ldap.LDAPError, e:
+        log.fatal('test_ticket47927_four: Failed (expected) to set the telephonenumber for %s: %s' % (USER_2_DN, e.message['desc']))
+        pass
+
+def test_ticket47927_five(topology):
+    '''
+    Exclude the EXCLUDED_BIS_CONTAINER_DN from the uniqueness plugin
+    '''
+    try:
+        topology.standalone.modify_s('cn=' + PLUGIN_ATTR_UNIQUENESS + ',cn=plugins,cn=config',
+                      [(ldap.MOD_ADD, 'uniqueness-exclude-subtrees', EXCLUDED_BIS_CONTAINER_DN)])
+    except ldap.LDAPError, e:
+        log.fatal('test_ticket47927_five: Failed to configure plugin for to exclude %s: error %s' % (EXCLUDED_BIS_CONTAINER_DN, e.message['desc']))
+        assert False
+    topology.standalone.restart(timeout=120)
+    topology.standalone.getEntry('cn=' + PLUGIN_ATTR_UNIQUENESS + ',cn=plugins,cn=config', ldap.SCOPE_BASE)
+
+def test_ticket47927_six(topology):
+    '''
+    Check that uniqueness is enforced on full SUFFIX except EXCLUDED_CONTAINER_DN
+    and EXCLUDED_BIS_CONTAINER_DN
+    First case: it exists an entry (with the same attribute value) in the scope
+    of the plugin and we set the value in an entry that is in an excluded scope
+    '''
+    UNIQUE_VALUE='222'
+    try:
+        topology.standalone.modify_s(USER_1_DN,
+                      [(ldap.MOD_REPLACE, 'telephonenumber', UNIQUE_VALUE)])
+    except ldap.LDAPError, e:
+        log.fatal('test_ticket47927_six: Failed to set the telephonenumber ' + e.message['desc'])
+        assert False
+    
+    # we should not be allowed to set this value (because user1 is in the scope)
+    try:
+        topology.standalone.modify_s(USER_2_DN,
+                      [(ldap.MOD_REPLACE, 'telephonenumber', UNIQUE_VALUE)])
+        log.fatal('test_ticket47927_six: unexpected success to set the telephonenumber for %s' % (USER_2_DN))
+        assert False
+    except ldap.LDAPError, e:
+        log.fatal('test_ticket47927_six: Failed (expected) to set the telephonenumber for %s: %s' % (USER_2_DN , e.message['desc']))
+
+
+    # USER_3_DN is in EXCLUDED_CONTAINER_DN so update should be successful
+    try:
+        topology.standalone.modify_s(USER_3_DN,
+                      [(ldap.MOD_REPLACE, 'telephonenumber', UNIQUE_VALUE)])
+        log.fatal('test_ticket47927_six: success to set the telephonenumber for %s' % (USER_3_DN))
+    except ldap.LDAPError, e:
+        log.fatal('test_ticket47927_six: Failed (unexpected) to set the telephonenumber for %s: %s' % (USER_3_DN, e.message['desc']))
+        assert False
+    # USER_4_DN is in EXCLUDED_CONTAINER_DN so update should be successful
+    try:
+        topology.standalone.modify_s(USER_4_DN,
+                      [(ldap.MOD_REPLACE, 'telephonenumber', UNIQUE_VALUE)])
+        log.fatal('test_ticket47927_six: success to set the telephonenumber for %s' % (USER_4_DN))
+    except ldap.LDAPError, e:
+        log.fatal('test_ticket47927_six: Failed (unexpected) to set the telephonenumber for %s: %s' % (USER_4_DN, e.message['desc']))
+        assert False
+
+
+def test_ticket47927_final(topology):
+    topology.standalone.delete()
+    log.info('Testcase PASSED')
+
+
+def run_isolated():
+    global installation1_prefix
+    installation1_prefix = None
+
+    topo = topology(True)
+    test_ticket47927_init(topo)
+    test_ticket47927_one(topo)
+    test_ticket47927_two(topo)
+    test_ticket47927_three(topo)
+    test_ticket47927_four(topo)
+    test_ticket47927_five(topo)
+    test_ticket47927_six(topo)
+    test_ticket47927_final(topo)
+
+
+if __name__ == '__main__':
+    run_isolated()
+
diff --git a/ldap/servers/plugins/uiduniq/uid.c b/ldap/servers/plugins/uiduniq/uid.c
index b9c4e72..5b57828 100644
--- a/ldap/servers/plugins/uiduniq/uid.c
+++ b/ldap/servers/plugins/uiduniq/uid.c
@@ -70,7 +70,7 @@ int ldap_quote_filter_value(
 
 
 static int search_one_berval(Slapi_DN *baseDN, const char **attrNames,
-		const struct berval *value, const char *requiredObjectClass, Slapi_DN *target);
+		const struct berval *value, const char *requiredObjectClass, Slapi_DN *target, Slapi_DN **excludes);
 
 /*
  * ISSUES:
@@ -103,6 +103,7 @@ typedef struct attr_uniqueness_config {
         const char **attrs;
         char *attr_friendly;
         Slapi_DN **subtrees;
+        Slapi_DN **exclude_subtrees;
         PRBool unique_in_all_subtrees;
         char *top_entry_oc;
         char *subtree_entries_oc;
@@ -111,6 +112,7 @@ typedef struct attr_uniqueness_config {
 
 #define ATTR_UNIQUENESS_ATTRIBUTE_NAME      "uniqueness-attribute-name"
 #define ATTR_UNIQUENESS_SUBTREES            "uniqueness-subtrees"
+#define ATTR_UNIQUENESS_EXCLUDE_SUBTREES    "uniqueness-exclude-subtrees"
 #define ATTR_UNIQUENESS_ACROSS_ALL_SUBTREES "uniqueness-across-all-subtrees"
 #define ATTR_UNIQUENESS_TOP_ENTRY_OC        "uniqueness-top-entry-oc"
 #define ATTR_UNIQUENESS_SUBTREE_ENTRIES_OC  "uniqueness-subtree-entries-oc"
@@ -135,7 +137,11 @@ free_uniqueness_config(struct attr_uniqueness_config *config)
         for (i = 0; config->subtrees && config->subtrees[i]; i++) {
                 slapi_sdn_free(&config->subtrees[i]);
         }
+        for (i = 0; config->exclude_subtrees && config->exclude_subtrees[i]; i++) {
+                slapi_sdn_free(&config->exclude_subtrees[i]);
+        }
         slapi_ch_free((void **) &config->subtrees);
+        slapi_ch_free((void **) &config->exclude_subtrees);
         slapi_ch_free_string((char **) &config->top_entry_oc);
         slapi_ch_free_string((char **) &config->subtree_entries_oc);       
 }
@@ -147,6 +153,7 @@ free_uniqueness_config(struct attr_uniqueness_config *config)
  * uniqueness-attribute-name: uid
  * uniqueness-subtrees: dc=people,dc=example,dc=com
  * uniqueness-subtrees: dc=sales, dc=example,dc=com
+ * uniqueness-exclude-subtrees: dc=machines, dc=examples, dc=com
  * uniqueness-across-all-subtrees: on
  * 
  * or
@@ -256,6 +263,27 @@ uniqueness_entry_to_config(Slapi_PBlock *pb, Slapi_Entry *config_entry)
                         values = NULL;
                 }
 
+                /* Subtrees where uniqueness is explicitly ignored */
+                values = slapi_entry_attr_get_charray(config_entry, ATTR_UNIQUENESS_EXCLUDE_SUBTREES);
+                if (values) {
+                        for (i = 0; values && values[i]; i++);
+                        /* slapi_ch_calloc never returns NULL unless the 2 args are 0 or negative. */
+                        tmp_config->exclude_subtrees = (Slapi_DN **) slapi_ch_calloc(i + 1, sizeof (Slapi_DN *));
+                        /* copy the valid subtree DN into the config */
+                        for (i = 0, nb_subtrees = 0; values && values[i]; i++) {
+                                if (slapi_dn_syntax_check(pb, values[i], 1)) { /* syntax check failed */
+                                        slapi_log_error(SLAPI_LOG_FATAL, plugin_name, "Config info: Invalid DN (skipped): %s\n", values[i]);
+                                        continue;
+                                }
+                                tmp_config->exclude_subtrees[nb_subtrees] = slapi_sdn_new_dn_byval(values[i]);
+                                nb_subtrees++;
+
+                        }
+
+                        slapi_ch_array_free(values);
+                        values = NULL;
+                }
+
                 /* Uniqueness may be enforced accross all the subtrees, by default it is not */
                 tmp_config->unique_in_all_subtrees = slapi_entry_attr_get_bool(config_entry, ATTR_UNIQUENESS_ACROSS_ALL_SUBTREES);
                 
@@ -376,6 +404,7 @@ uniqueness_entry_to_config(Slapi_PBlock *pb, Slapi_Entry *config_entry)
             fp++;
         }
 
+        /* Only ensure subtrees are set, no need to check excluded subtrees as setting exclusion without actual subtrees make little sense */
         if (tmp_config->subtrees == NULL) {
                 /* Uniqueness is enforced on entries matching objectclass */
                 if (tmp_config->subtree_entries_oc == NULL) {
@@ -569,7 +598,7 @@ create_filter(const char **attributes, const struct berval *value, const char *r
 static int
 search(Slapi_DN *baseDN, const char **attrNames, Slapi_Attr *attr,
   struct berval **values, const char *requiredObjectClass,
-  Slapi_DN *target)
+  Slapi_DN *target, Slapi_DN **excludes)
 {
   int result;
 
@@ -604,7 +633,7 @@ search(Slapi_DN *baseDN, const char **attrNames, Slapi_Attr *attr,
 	{
 	  result = search_one_berval(baseDN, attrNames,
 					slapi_value_get_berval(v),
-					requiredObjectClass, target);
+					requiredObjectClass, target, excludes);
 	}
   }
   else
@@ -612,7 +641,7 @@ search(Slapi_DN *baseDN, const char **attrNames, Slapi_Attr *attr,
 	for (;*values != NULL && LDAP_SUCCESS == result; values++)
 	{
 	  result = search_one_berval(baseDN, attrNames, *values, requiredObjectClass,
-					target);
+					target, excludes);
 	}
   }
 
@@ -628,7 +657,7 @@ search(Slapi_DN *baseDN, const char **attrNames, Slapi_Attr *attr,
 static int
 search_one_berval(Slapi_DN *baseDN, const char **attrNames,
 		const struct berval *value, const char *requiredObjectClass,
-		Slapi_DN *target)
+		Slapi_DN *target, Slapi_DN **excludes)
 {
 	int result;
     char *filter;
@@ -695,8 +724,28 @@ search_one_berval(Slapi_DN *baseDN, const char **attrNames,
          */
         if (!target || slapi_sdn_compare(slapi_entry_get_sdn(*entries), target) != 0)
         {
+          int i;
           result = LDAP_CONSTRAINT_VIOLATION;
-          break;
+          if (excludes == NULL || *excludes == NULL)
+          {
+            break;
+          }
+
+          /* Do the same check for excluded subtrees as resulted entries may have matched them */
+          for (i = 0;excludes && excludes[i]; i++)
+          {
+            Slapi_DN *entry_dn = slapi_entry_get_sdn(*entries);
+            if (slapi_sdn_issuffix(entry_dn, excludes[i]))
+            {
+              result = LDAP_SUCCESS;
+              break;
+            }
+          }
+
+          if (result == LDAP_CONSTRAINT_VIOLATION)
+          {
+            break;
+          }
         }
       }
 
@@ -734,7 +783,7 @@ search_one_berval(Slapi_DN *baseDN, const char **attrNames,
  *   LDAP_OPERATIONS_ERROR - a server failure.
  */
 static int
-searchAllSubtrees(Slapi_DN **subtrees, const char **attrNames,
+searchAllSubtrees(Slapi_DN **subtrees, Slapi_DN **exclude_subtrees, const char **attrNames,
   Slapi_Attr *attr, struct berval **values, const char *requiredObjectClass,
   Slapi_DN *dn, PRBool unique_in_all_subtrees)
 {
@@ -762,6 +811,22 @@ searchAllSubtrees(Slapi_DN **subtrees, const char **attrNames,
                   return result;
           }
   }
+
+  /* If DN is in the excluded subtrees, we should ignore it in any case, not only
+   * in the case of uniqueness in all subtrees. */
+  if (exclude_subtrees != NULL)
+  {
+          PRBool in_a_subtree = PR_FALSE;
+          for (i = 0;exclude_subtrees && exclude_subtrees[i]; i++) {
+                  if (slapi_sdn_issuffix(dn, exclude_subtrees[i])) {
+                          in_a_subtree = PR_TRUE;
+                          break;
+                  }
+          }
+          if (in_a_subtree) {
+                  return result;
+          }
+  }
   
   /*
    * For each DN in the managed list, do uniqueness checking if
@@ -775,7 +840,7 @@ searchAllSubtrees(Slapi_DN **subtrees, const char **attrNames,
      * worry about that here.
      */
     if (unique_in_all_subtrees || slapi_sdn_issuffix(dn, sufdn)) {
-      result = search(sufdn, attrNames, attr, values, requiredObjectClass, dn);
+      result = search(sufdn, attrNames, attr, values, requiredObjectClass, dn, exclude_subtrees);
       if (result) break;
     }
   }
@@ -865,7 +930,7 @@ getArguments(Slapi_PBlock *pb, char **attrName, char **markerObjectClass,
 static int
 findSubtreeAndSearch(Slapi_DN *parentDN, const char **attrNames, Slapi_Attr *attr,
   struct berval **values, const char *requiredObjectClass, Slapi_DN *target,
-  const char *markerObjectClass)
+  const char *markerObjectClass, Slapi_DN **excludes)
 {
   int result = LDAP_SUCCESS;
   Slapi_PBlock *spb = NULL;
@@ -883,7 +948,7 @@ findSubtreeAndSearch(Slapi_DN *parentDN, const char **attrNames, Slapi_Attr *att
            * to have the attribute already.
            */
           result = search(curpar, attrNames, attr, values, requiredObjectClass,
-                          target);
+                          target, excludes);
           break;
         }
         newpar = slapi_sdn_new();
@@ -995,11 +1060,11 @@ preop_add(Slapi_PBlock *pb)
                   /* Subtree defined by location of marker object class */
                         result = findSubtreeAndSearch(sdn, attrNames, attr, NULL,
                                                       requiredObjectClass, sdn,
-                                                      markerObjectClass);
+                                                      markerObjectClass, config->exclude_subtrees);
                 } else
                 {
                   /* Subtrees listed on invocation line */
-                  result = searchAllSubtrees(config->subtrees, attrNames, attr, NULL,
+                  result = searchAllSubtrees(config->subtrees, config->exclude_subtrees, attrNames, attr, NULL,
                                              requiredObjectClass, sdn, config->unique_in_all_subtrees);
                 }
                 if (result != LDAP_SUCCESS) {
@@ -1164,11 +1229,11 @@ preop_modify(Slapi_PBlock *pb)
             /* Subtree defined by location of marker object class */
             result = findSubtreeAndSearch(sdn, attrNames, NULL, 
                                           mod->mod_bvalues, requiredObjectClass,
-                                          sdn, markerObjectClass);
+                                          sdn, markerObjectClass, config->exclude_subtrees);
         } else
         {
             /* Subtrees listed on invocation line */
-            result = searchAllSubtrees(config->subtrees, attrNames, NULL,
+            result = searchAllSubtrees(config->subtrees, config->exclude_subtrees, attrNames, NULL,
                                        mod->mod_bvalues, requiredObjectClass, sdn, config->unique_in_all_subtrees);
         }
     }
@@ -1326,11 +1391,11 @@ preop_modrdn(Slapi_PBlock *pb)
                   /* Subtree defined by location of marker object class */
                         result = findSubtreeAndSearch(slapi_entry_get_sdn(e), attrNames, attr, NULL,
                                                       requiredObjectClass, sdn,
-                                                      markerObjectClass);
+                                                      markerObjectClass, config->exclude_subtrees);
                 } else
                 {
                   /* Subtrees listed on invocation line */
-                  result = searchAllSubtrees(config->subtrees, attrNames, attr, NULL,
+                  result = searchAllSubtrees(config->subtrees, config->exclude_subtrees, attrNames, attr, NULL,
                                              requiredObjectClass, sdn, config->unique_in_all_subtrees);
                 }
                 if (result != LDAP_SUCCESS) {
-- 
1.7.11.7

