[SCM] Samba Shared Repository - branch master updated

Andrew Bartlett abartlet at samba.org
Tue Mar 14 07:17:02 UTC 2023


The branch, master has been updated
       via  b4a6c054ec6 selftest: Use setUpClass() to reduce "make test TESTS=large_ldap" time
       via  cad96f59a08 lib/ldb: Avoid allocation and memcpy() for every wildcard match candidate
       via  4fa0242b9d3 python:netcmd: Decode return value of find_netbios() from bytes into string
       via  bfc33b47bb4 dsdb: Avoid ERROR(ldb): uncaught exception - Deleted target CN=NTDS Settings... in join
       via  2d41bcce83a selftest/drs: Demonstrate ERROR(ldb): uncaught exception - Deleted target CN=NTDS Settings... in join
       via  5a7a28cc458 tsocket: Increase tcp_user_timeout max_loops
      from  7ee725f2860 idmap_hash: remember new domain sids in idmap_hash_sid_to_id()

https://git.samba.org/?p=samba.git;a=shortlog;h=master


- Log -----------------------------------------------------------------
commit b4a6c054ec6acefacd22cb7230a783d20cb07c05
Author: Andrew Bartlett <abartlet at samba.org>
Date:   Mon Mar 13 17:20:00 2023 +1300

    selftest: Use setUpClass() to reduce "make test TESTS=large_ldap" time
    
    This reduces the elapsed time to 6m from 20m on my laptop.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=15332
    
    Signed-off-by: Andrew Bartlett <abartlet at samba.org>
    Reviewed-by: Joseph Sutton <josephsutton at catalyst.net.nz>
    
    Autobuild-User(master): Andrew Bartlett <abartlet at samba.org>
    Autobuild-Date(master): Tue Mar 14 07:16:04 UTC 2023 on atb-devel-224

commit cad96f59a08192df927fb1df4e9787c7f70991a2
Author: Andrew Bartlett <abartlet at samba.org>
Date:   Mon Mar 13 14:25:56 2023 +1300

    lib/ldb: Avoid allocation and memcpy() for every wildcard match candidate
    
    The value can be quite large, the allocation will take much
    longer than the actual match and is repeated per candidate
    record.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=15331
    
    Signed-off-by: Andrew Bartlett <abartlet at samba.org>
    Reviewed-by: Joseph Sutton <josephsutton at catalyst.net.nz>

commit 4fa0242b9d34decd8dbd813be40655a593df3db9
Author: Andreas Schneider <asn at samba.org>
Date:   Fri Mar 10 09:08:48 2023 +0100

    python:netcmd: Decode return value of find_netbios() from bytes into string
    
    ERROR(<class 'TypeError'>): uncaught exception - replace() argument 1 must be
    str, not bytes
      File "bin/python/samba/netcmd/__init__.py", line 230, in _run
        return self.run(*args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^
      File "bin/python/samba/netcmd/ldapcmp.py", line 966, in run
        if b1.diff(b2):
           ^^^^^^^^^^^
      File "bin/python/samba/netcmd/ldapcmp.py", line 790, in diff
        if object1 == object2:
           ^^^^^^^^^^^^^^^^^^
      File "bin/python/samba/netcmd/ldapcmp.py", line 557, in __eq__
        return self.cmp_attrs(other)
               ^^^^^^^^^^^^^^^^^^^^^
      File "bin/python/samba/netcmd/ldapcmp.py", line 656, in cmp_attrs
        p = [self.fix_domain_netbios(j) for j in m]
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "bin/python/samba/netcmd/ldapcmp.py", line 656, in <listcomp>
        p = [self.fix_domain_netbios(j) for j in m]
             ^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "bin/python/samba/netcmd/ldapcmp.py", line 542, in fix_domain_netbios
        res = res.replace(self.con.domain_netbios.lower(), self.con.domain_netbios.upper())
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    
    BUGS: https://bugzilla.samba.org/show_bug.cgi?id=15330
    
    Signed-off-by: Andreas Schneider <asn at samba.org>
    Reviewed-by: Andrew Bartlett <abartlet at samba.org>

commit bfc33b47bb428233e100f75e7a725ac52179f823
Author: Andrew Bartlett <abartlet at samba.org>
Date:   Thu Mar 9 20:25:06 2023 +1300

    dsdb: Avoid ERROR(ldb): uncaught exception - Deleted target CN=NTDS Settings... in join
    
    "samba-tool domain join" uses the replication API in a strange way, perhaps no longer
    required, except that we often still have folks upgrading from very old Samba versions.
    
    When deferring the writing out to the DB of link replication to the very end, there
    is a greater opportunity for the deletion of an object to have been sent with the
    other objects, and have the link applied later.
    
    This tells the repl_meta_data code to behave as if GET_TGT had been sent at the
    time the link was returned, allowing a link to a deleted object to be silently
    discarded.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=15329
    
    Signed-off-by: Andrew Bartlett <abartlet at samba.org>
    Reviewed-by: Joseph Sutton <josephsutton at catalyst.net.nz>

commit 2d41bcce83a976b85636c92d6fc38c63fdde5431
Author: Andrew Bartlett <abartlet at samba.org>
Date:   Thu Mar 9 17:02:35 2023 +1300

    selftest/drs: Demonstrate ERROR(ldb): uncaught exception - Deleted target CN=NTDS Settings... in join
    
    "samba-tool domain join" uses the replication API in a strange way, perhaps no longer
    required, except that we often still have folks upgrading from very old Samba versions.
    
    By deferring the writing out to the DB of link replication to the very end, we have a
    better chance that all the objects required are present, however the situation may
    have changed during the cycle, and a link could still be sent, pointing to a deleted
    object.
    
    We currently fail in this situation.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=15329
    
    Signed-off-by: Andrew Bartlett <abartlet at samba.org>
    Reviewed-by: Joseph Sutton <josephsutton at catalyst.net.nz>

commit 5a7a28cc45870949fc11d30586a06c309aa517dc
Author: Andrew Bartlett <abartlet at samba.org>
Date:   Thu Mar 9 10:06:26 2023 +1300

    tsocket: Increase tcp_user_timeout max_loops
    
    Often, on rackspace GitLab CI runners, we get:
    
    UNEXPECTED(failure): samba.unittests.tsocket_tstream.test_tstream_more_tcp_user_timeout_spin(none)
    REASON: Exception: Exception: 0xf == 0xf
    ../../lib/tsocket/tests/test_tstream.c:405: error: Failure!
    
    This allows us more spins before we fail the test.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=15328
    Signed-off-by: Andrew Bartlett <abartlet at samba.org>
    Reviewed-by: Joseph Sutton <josephsutton at catalyst.net.nz>

-----------------------------------------------------------------------

Summary of changes:
 lib/ldb/common/ldb_match.c                      |  60 +++++++++--
 lib/tsocket/tests/test_tstream.c                |   2 +-
 python/samba/join.py                            |  19 ++++
 python/samba/netcmd/ldapcmp.py                  |   2 +-
 source4/dsdb/pydsdb.c                           |   2 +
 source4/dsdb/samdb/ldb_modules/repl_meta_data.c |  13 ++-
 source4/dsdb/samdb/samdb.h                      |   2 +
 source4/dsdb/tests/python/large_ldap.py         |  69 ++++++------
 source4/torture/drs/python/ridalloc_exop.py     | 135 ++++++++++++++++++++++++
 9 files changed, 258 insertions(+), 46 deletions(-)


Changeset truncated at 500 lines:

diff --git a/lib/ldb/common/ldb_match.c b/lib/ldb/common/ldb_match.c
index 2f4d41f3441..51376871b4c 100644
--- a/lib/ldb/common/ldb_match.c
+++ b/lib/ldb/common/ldb_match.c
@@ -34,6 +34,7 @@
 
 #include "ldb_private.h"
 #include "dlinklist.h"
+#include "ldb_handlers.h"
 
 /*
   check if the scope matches in a search result
@@ -259,20 +260,42 @@ static int ldb_wildcard_compare(struct ldb_context *ldb,
 		return LDB_SUCCESS;
 	}
 
-	if (a->syntax->canonicalise_fn(ldb, ldb, &value, &val) != 0) {
-		return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
+	/* No need to just copy this value for a binary match */
+	if (a->syntax->canonicalise_fn != ldb_handler_copy) {
+		if (a->syntax->canonicalise_fn(ldb, ldb, &value, &val) != 0) {
+			return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
+		}
+
+		/*
+		 * Only set save_p if we allocate (call
+		 * a->syntax->canonicalise_fn()), as we
+		 * talloc_free(save_p) below to clean up
+		 */
+		save_p = val.data;
+	} else {
+		val = value;
 	}
 
-	save_p = val.data;
 	cnk.data = NULL;
 
 	if ( ! tree->u.substring.start_with_wildcard ) {
+		uint8_t *cnk_to_free = NULL;
 
 		chunk = tree->u.substring.chunks[c];
-		if (a->syntax->canonicalise_fn(ldb, ldb, chunk, &cnk) != 0) goto mismatch;
+		/* No need to just copy this value for a binary match */
+		if (a->syntax->canonicalise_fn != ldb_handler_copy) {
+			if (a->syntax->canonicalise_fn(ldb, ldb, chunk, &cnk) != 0) {
+				goto mismatch;
+			}
+
+			cnk_to_free = cnk.data;
+		} else {
+			cnk = *chunk;
+		}
 
 		/* This deals with wildcard prefix searches on binary attributes (eg objectGUID) */
 		if (cnk.length > val.length) {
+			TALLOC_FREE(cnk_to_free);
 			goto mismatch;
 		}
 		/*
@@ -280,32 +303,47 @@ static int ldb_wildcard_compare(struct ldb_context *ldb,
 		 * we can cope with this.
 		 */
 		if (cnk.length == 0) {
+			TALLOC_FREE(cnk_to_free);
+			goto mismatch;
+		}
+
+		if (memcmp((char *)val.data, (char *)cnk.data, cnk.length) != 0) {
+			TALLOC_FREE(cnk_to_free);
 			goto mismatch;
 		}
 
-		if (memcmp((char *)val.data, (char *)cnk.data, cnk.length) != 0) goto mismatch;
 		val.length -= cnk.length;
 		val.data += cnk.length;
 		c++;
-		talloc_free(cnk.data);
+		TALLOC_FREE(cnk_to_free);
 		cnk.data = NULL;
 	}
 
 	while (tree->u.substring.chunks[c]) {
 		uint8_t *p;
+		uint8_t *cnk_to_free = NULL;
 
 		chunk = tree->u.substring.chunks[c];
-		if(a->syntax->canonicalise_fn(ldb, ldb, chunk, &cnk) != 0) {
-			goto mismatch;
+		/* No need to just copy this value for a binary match */
+		if (a->syntax->canonicalise_fn != ldb_handler_copy) {
+			if (a->syntax->canonicalise_fn(ldb, ldb, chunk, &cnk) != 0) {
+				goto mismatch;
+			}
+
+			cnk_to_free = cnk.data;
+		} else {
+			cnk = *chunk;
 		}
 		/*
 		 * Empty strings are returned as length 0. Ensure
 		 * we can cope with this.
 		 */
 		if (cnk.length == 0) {
+			TALLOC_FREE(cnk_to_free);
 			goto mismatch;
 		}
 		if (cnk.length > val.length) {
+			TALLOC_FREE(cnk_to_free);
 			goto mismatch;
 		}
 
@@ -320,6 +358,8 @@ static int ldb_wildcard_compare(struct ldb_context *ldb,
 			cmp = memcmp(p,
 				     cnk.data,
 				     cnk.length);
+			TALLOC_FREE(cnk_to_free);
+
 			if (cmp != 0) {
 				goto mismatch;
 			}
@@ -331,15 +371,16 @@ static int ldb_wildcard_compare(struct ldb_context *ldb,
 			p = memmem((const void *)val.data, val.length,
 				   (const void *)cnk.data, cnk.length);
 			if (p == NULL) {
+				TALLOC_FREE(cnk_to_free);
 				goto mismatch;
 			}
 			/* move val to the end of the match */
 			p += cnk.length;
 			val.length -= (p - val.data);
 			val.data = p;
+			TALLOC_FREE(cnk_to_free);
 		}
 		c++;
-		TALLOC_FREE(cnk.data);
 	}
 
 	talloc_free(save_p);
@@ -349,7 +390,6 @@ static int ldb_wildcard_compare(struct ldb_context *ldb,
 mismatch:
 	*matched = false;
 	talloc_free(save_p);
-	talloc_free(cnk.data);
 	return LDB_SUCCESS;
 }
 
diff --git a/lib/tsocket/tests/test_tstream.c b/lib/tsocket/tests/test_tstream.c
index a920e671cda..47008bb8bf8 100644
--- a/lib/tsocket/tests/test_tstream.c
+++ b/lib/tsocket/tests/test_tstream.c
@@ -322,7 +322,7 @@ static void test_tstream_server_spin_client_tcp_user_timeout(struct socket_pair
 	rc = write(sp->socket_client, TEST_STRING, sizeof(TEST_STRING));
 	assert_return_code(rc, errno);
 	sp->expected_errno = ETIMEDOUT;
-	sp->max_loops = 15;
+	sp->max_loops = 30;
 }
 
 static void test_tstream_server_spin_client_both_timer(struct tevent_context *ev,
diff --git a/python/samba/join.py b/python/samba/join.py
index 70b3c9729b0..6c1ab3be7b4 100644
--- a/python/samba/join.py
+++ b/python/samba/join.py
@@ -50,6 +50,7 @@ import tempfile
 from collections import OrderedDict
 from samba.common import get_string
 from samba.netcmd import CommandError
+from samba import dsdb
 
 
 class DCJoinException(Exception):
@@ -937,6 +938,10 @@ class DCJoinContext(object):
         """Replicate the SAM."""
 
         ctx.logger.info("Starting replication")
+
+        # A global transaction is started so that linked attributes
+        # are applied at the very end, once all partitions are
+        # replicated.  This helps get all cross-partition links.
         ctx.local_samdb.transaction_start()
         try:
             source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id())
@@ -1057,7 +1062,21 @@ class DCJoinContext(object):
             ctx.local_samdb.transaction_cancel()
             raise
         else:
+
+            # This is a special case, we have completed a full
+            # replication so if a link comes to us that points to a
+            # deleted object, and we asked for all objects already, we
+            # just have to ignore it, the chance to re-try the
+            # replication with GET_TGT has long gone.  This can happen
+            # if the object is deleted and sent to us after the link
+            # was sent, as we are processing all links in the
+            # transaction_commit().
+            if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
+                ctx.local_samdb.set_opaque_integer(dsdb.DSDB_FULL_JOIN_REPLICATION_COMPLETED_OPAQUE_NAME,
+                                                   1)
             ctx.local_samdb.transaction_commit()
+            ctx.local_samdb.set_opaque_integer(dsdb.DSDB_FULL_JOIN_REPLICATION_COMPLETED_OPAQUE_NAME,
+                                               0)
             ctx.logger.info("Committed SAM database")
 
         # A large replication may have caused our LDB connection to the
diff --git a/python/samba/netcmd/ldapcmp.py b/python/samba/netcmd/ldapcmp.py
index fb49cbe1ae5..ff26e96332d 100644
--- a/python/samba/netcmd/ldapcmp.py
+++ b/python/samba/netcmd/ldapcmp.py
@@ -121,7 +121,7 @@ class LDAPBase(object):
 
         for x in res:
             if "nETBIOSName" in x:
-                return x["nETBIOSName"][0]
+                return x["nETBIOSName"][0].decode()
 
     def object_exists(self, object_dn):
         res = None
diff --git a/source4/dsdb/pydsdb.c b/source4/dsdb/pydsdb.c
index 804007e9e86..fd7cda21a43 100644
--- a/source4/dsdb/pydsdb.c
+++ b/source4/dsdb/pydsdb.c
@@ -1768,5 +1768,7 @@ MODULE_INIT_FUNC(dsdb)
 	ADD_DSDB_STRING(DS_GUID_SCHEMA_CLASS_MANAGED_SERVICE_ACCOUNT);
 	ADD_DSDB_STRING(DS_GUID_SCHEMA_CLASS_USER);
 
+	ADD_DSDB_STRING(DSDB_FULL_JOIN_REPLICATION_COMPLETED_OPAQUE_NAME);
+
 	return m;
 }
diff --git a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c
index c1ea5ad90f8..175a02d3ba7 100644
--- a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c
+++ b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c
@@ -7533,6 +7533,16 @@ static int replmd_allow_missing_target(struct ldb_module *module,
 						  source_dn,
 						  target_dn);
 	if (is_in_same_nc) {
+		/*
+		 * We allow the join.py code to point out that all
+		 * replication is completed, so failing now would just
+		 * trigger errors, rather than trigger a GET_TGT
+		 */
+		int *finished_full_join_ptr =
+			talloc_get_type(ldb_get_opaque(ldb,
+						       DSDB_FULL_JOIN_REPLICATION_COMPLETED_OPAQUE_NAME),
+					int);
+		bool finished_full_join = finished_full_join_ptr && *finished_full_join_ptr;
 
 		/*
 		 * if the target is already be up-to-date there's no point in
@@ -7540,7 +7550,8 @@ static int replmd_allow_missing_target(struct ldb_module *module,
 		 * on a one-way link was deleted. We ignore the link rather
 		 * than failing the replication cycle completely
 		 */
-		if (dsdb_repl_flags & DSDB_REPL_FLAG_TARGETS_UPTODATE) {
+		if (finished_full_join
+		    || dsdb_repl_flags & DSDB_REPL_FLAG_TARGETS_UPTODATE) {
 			*ignore_link = true;
 			DBG_WARNING("%s is %s "
 				    "but up to date. Ignoring link from %s\n",
diff --git a/source4/dsdb/samdb/samdb.h b/source4/dsdb/samdb/samdb.h
index d76cbeba841..cbbb766ea22 100644
--- a/source4/dsdb/samdb/samdb.h
+++ b/source4/dsdb/samdb/samdb.h
@@ -371,6 +371,8 @@ struct dsdb_extended_dn_store_format {
 
 #define DSDB_OPAQUE_PARTITION_MODULE_MSG_OPAQUE_NAME "DSDB_OPAQUE_PARTITION_MODULE_MSG"
 
+#define DSDB_FULL_JOIN_REPLICATION_COMPLETED_OPAQUE_NAME "DSDB_FULL_JOIN_REPLICATION_COMPLETED"
+
 #define DSDB_ACL_CHECKS_DIRSYNC_FLAG 0x1
 #define DSDB_SAMDB_MINIMUM_ALLOWED_RID   1000
 
diff --git a/source4/dsdb/tests/python/large_ldap.py b/source4/dsdb/tests/python/large_ldap.py
index 0805119a700..13729052e65 100644
--- a/source4/dsdb/tests/python/large_ldap.py
+++ b/source4/dsdb/tests/python/large_ldap.py
@@ -66,30 +66,32 @@ creds = credopts.get_credentials(lp)
 
 class ManyLDAPTest(samba.tests.TestCase):
 
-    def setUp(self):
-        super(ManyLDAPTest, self).setUp()
-        self.ldb = SamDB(url, credentials=creds, session_info=system_session(lp), lp=lp)
-        self.base_dn = self.ldb.domain_dn()
-        self.OU_NAME_MANY="many_ou" + format(random.randint(0, 99999), "05")
-        self.ou_dn = ldb.Dn(self.ldb, "ou=" + self.OU_NAME_MANY + "," + str(self.base_dn))
-
-        samba.tests.delete_force(self.ldb, self.ou_dn,
+    @classmethod
+    def setUpClass(cls):
+        super().setUpClass()
+        cls.ldb = SamDB(url, credentials=creds, session_info=system_session(lp), lp=lp)
+        cls.base_dn = self.ldb.domain_dn()
+        cls.OU_NAME_MANY="many_ou" + format(random.randint(0, 99999), "05")
+        cls.ou_dn = ldb.Dn(self.ldb, "ou=" + self.OU_NAME_MANY + "," + str(self.base_dn))
+
+        samba.tests.delete_force(cls.ldb, cls.ou_dn,
                                  controls=['tree_delete:1'])
 
-        self.ldb.add({
-            "dn": self.ou_dn,
+        cls.ldb.add({
+            "dn": cls.ou_dn,
             "objectclass": "organizationalUnit",
-            "ou": self.OU_NAME_MANY})
+            "ou": cls.OU_NAME_MANY})
 
         for x in range(2000):
-            ou_name = self.OU_NAME_MANY + str(x)
-            self.ldb.add({
-                "dn": "ou=" + ou_name + "," + str(self.ou_dn),
+            ou_name = cls.OU_NAME_MANY + str(x)
+            cls.ldb.add({
+                "dn": "ou=" + ou_name + "," + str(cls.ou_dn),
                 "objectclass": "organizationalUnit",
                 "ou": ou_name})
 
-    def tearDown(self):
-        samba.tests.delete_force(self.ldb, self.ou_dn,
+    @classmethod
+    def tearDownClass(cls):
+        samba.tests.delete_force(cls.ldb, self.ou_dn,
                                  controls=['tree_delete:1'])
 
     def test_unindexed_iterator_search(self):
@@ -117,34 +119,35 @@ class ManyLDAPTest(samba.tests.TestCase):
 
 class LargeLDAPTest(samba.tests.TestCase):
 
-    def setUp(self):
-        super(LargeLDAPTest, self).setUp()
-        self.ldb = SamDB(url, credentials=creds, session_info=system_session(lp), lp=lp)
-        self.base_dn = self.ldb.domain_dn()
-        self.USER_NAME = "large_user" + format(random.randint(0, 99999), "05") + "-"
-        self.OU_NAME="large_user_ou" + format(random.randint(0, 99999), "05")
-        self.ou_dn = ldb.Dn(self.ldb, "ou=" + self.OU_NAME + "," + str(self.base_dn))
+    @classmethod
+    def setUpClass(cls):
+        cls.ldb = SamDB(url, credentials=creds, session_info=system_session(lp), lp=lp)
+        cls.base_dn = cls.ldb.domain_dn()
+        cls.USER_NAME = "large_user" + format(random.randint(0, 99999), "05") + "-"
+        cls.OU_NAME="large_user_ou" + format(random.randint(0, 99999), "05")
+        cls.ou_dn = ldb.Dn(cls.ldb, "ou=" + cls.OU_NAME + "," + str(cls.base_dn))
 
-        samba.tests.delete_force(self.ldb, self.ou_dn,
+        samba.tests.delete_force(cls.ldb, cls.ou_dn,
                                  controls=['tree_delete:1'])
 
-        self.ldb.add({
-            "dn": self.ou_dn,
+        cls.ldb.add({
+            "dn": cls.ou_dn,
             "objectclass": "organizationalUnit",
-            "ou": self.OU_NAME})
+            "ou": cls.OU_NAME})
 
         for x in range(200):
-            user_name = self.USER_NAME + format(x, "03")
-            self.ldb.add({
-                "dn": "cn=" + user_name + "," + str(self.ou_dn),
+            user_name = cls.USER_NAME + format(x, "03")
+            cls.ldb.add({
+                "dn": "cn=" + user_name + "," + str(cls.ou_dn),
                 "objectclass": "user",
                 "sAMAccountName": user_name,
                 "jpegPhoto": b'a' * (2 * 1024 * 1024)})
 
-    def tearDown(self):
+    @classmethod
+    def tearDownClass(cls):
         # Remake the connection for tear-down (old Samba drops the socket)
-        self.ldb = SamDB(url, credentials=creds, session_info=system_session(lp), lp=lp)
-        samba.tests.delete_force(self.ldb, self.ou_dn,
+        cls.ldb = SamDB(url, credentials=creds, session_info=system_session(lp), lp=lp)
+        samba.tests.delete_force(cls.ldb, cls.ou_dn,
                                  controls=['tree_delete:1'])
 
     def test_unindexed_iterator_search(self):
diff --git a/source4/torture/drs/python/ridalloc_exop.py b/source4/torture/drs/python/ridalloc_exop.py
index 76b36854adf..d6d0082eccd 100644
--- a/source4/torture/drs/python/ridalloc_exop.py
+++ b/source4/torture/drs/python/ridalloc_exop.py
@@ -43,6 +43,7 @@ from samba.auth import system_session, admin_session
 from samba.dbchecker import dbcheck
 from samba.ndr import ndr_pack
 from samba.dcerpc import security
+from samba import drs_utils, dsdb
 
 
 class DrsReplicaSyncTestCase(drs_base.DrsBaseTestCase):
@@ -665,3 +666,137 @@ class DrsReplicaSyncTestCase(drs_base.DrsBaseTestCase):
         finally:
             self._test_force_demote(fsmo_owner['dns_name'], "RIDALLOCTEST7")
             shutil.rmtree(targetdir, ignore_errors=True)
+
+    def test_replicate_against_deleted_objects_transaction(self):
+        """Not related to RID allocation, but uses the infrastructure here.
+        Do a join, create a link between two objects remotely, but
+        remove the target locally.  Show that we need to set a magic
+        opaque if there is an outer transaction.
+
+        """
+        fsmo_dn = ldb.Dn(self.ldb_dc1, "CN=RID Manager$,CN=System," + self.ldb_dc1.domain_dn())
+        (fsmo_owner, fsmo_not_owner) = self._determine_fSMORoleOwner(fsmo_dn)
+
+        test_user4 = "ridalloctestuser4"
+        test_group = "ridalloctestgroup1"
+
+        self.ldb_dc1.newuser(test_user4, "P at ssword!")
+
+        self.addCleanup(self.ldb_dc1.deleteuser, test_user4)
+
+        self.ldb_dc1.newgroup(test_group)
+        self.addCleanup(self.ldb_dc1.deletegroup, test_group)
+
+        targetdir = self._test_join(self.dnsname_dc1, "RIDALLOCTEST8")
+        try:
+            # Connect to the database
+            ldb_url = "tdb://%s" % os.path.join(targetdir, "private/sam.ldb")
+            lp = self.get_loadparm()
+
+            new_ldb = SamDB(ldb_url,
+                            session_info=system_session(lp), lp=lp)
+
+            destination_dsa_guid = misc.GUID(new_ldb.get_ntds_GUID())
+
+            repl = drs_utils.drs_Replicate(f'ncacn_ip_tcp:{self.dnsname_dc1}[seal]',
+                                           lp,
+                                           self.get_credentials(),
+                                           new_ldb,
+                                           destination_dsa_guid)
+
+            source_dsa_invocation_id = misc.GUID(self.ldb_dc1.invocation_id)
+
+            # Add the link on the remote DC
+            self.ldb_dc1.add_remove_group_members(test_group, [test_user4])
+
+            # Starting a transaction overrides, currently the logic
+            # inside repl.replicatate to retry with GET_TGT which in
+            # turn tells the repl_meta_data module that the most up to
+            # date info is already available
+            new_ldb.transaction_start()
+            repl.replicate(self.ldb_dc1.domain_dn(),
+                           source_dsa_invocation_id,
+                           destination_dsa_guid)
+
+            # Delete the user locally, before applying the links.
+            # This simulates getting the delete in the replciation
+            # stream.
+            new_ldb.deleteuser(test_user4)
+
+            # This fails as the user has been deleted locally but a remote link is sent
+            self.assertRaises(ldb.LdbError, new_ldb.transaction_commit)
+
+            new_ldb.transaction_start()
+            repl.replicate(self.ldb_dc1.domain_dn(),
+                           source_dsa_invocation_id,
+                           destination_dsa_guid)
+
+            # Delete the user locally (the previous transaction
+            # doesn't apply), before applying the links.  This
+            # simulates getting the delete in the replciation stream.
+            new_ldb.deleteuser(test_user4)
+
+            new_ldb.set_opaque_integer(dsdb.DSDB_FULL_JOIN_REPLICATION_COMPLETED_OPAQUE_NAME,
+                                       1)
+
+            # This should now work
+            try:
+                new_ldb.transaction_commit()
+            except ldb.LdbError as e:
+                self.fail(f"Failed to replicate despite setting opaque with {e.args[1]}")
+
+        finally:
+            self._test_force_demote(self.dnsname_dc1, "RIDALLOCTEST8")
+            shutil.rmtree(targetdir, ignore_errors=True)
+
+    def test_replicate_against_deleted_objects_normal(self):
+        """Not related to RID allocation, but uses the infrastructure here.
+        Do a join, create a link between two objects remotely, but
+        remove the target locally.  .
+
+        """
+        fsmo_dn = ldb.Dn(self.ldb_dc1, "CN=RID Manager$,CN=System," + self.ldb_dc1.domain_dn())
+        (fsmo_owner, fsmo_not_owner) = self._determine_fSMORoleOwner(fsmo_dn)
+
+        test_user5 = "ridalloctestuser5"
+        test_group2 = "ridalloctestgroup2"
+
+        self.ldb_dc1.newuser(test_user5, "P at ssword!")
+        self.addCleanup(self.ldb_dc1.deleteuser, test_user5)
+
+        self.ldb_dc1.newgroup(test_group2)
+        self.addCleanup(self.ldb_dc1.deletegroup, test_group2)
+
+        targetdir = self._test_join(self.dnsname_dc1, "RIDALLOCTEST9")
+        try:
+            # Connect to the database
+            ldb_url = "tdb://%s" % os.path.join(targetdir, "private/sam.ldb")


-- 
Samba Shared Repository



More information about the samba-cvs mailing list