summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Bartlett <abartlet@samba.org>2023-03-09 17:02:35 +1300
committerJule Anger <janger@samba.org>2023-03-30 15:10:10 +0000
commiteaff4ef61624276b16dc0dcb11868e2778d2e46f (patch)
tree0177366e5a524c2615e0977246e89d03a5ac04e5
parent3ecdec683b60cf100b1c031841b709c91191c8f2 (diff)
downloadsamba-eaff4ef61624276b16dc0dcb11868e2778d2e46f.tar.gz
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@samba.org> Reviewed-by: Joseph Sutton <josephsutton@catalyst.net.nz> (cherry picked from commit 2d41bcce83a976b85636c92d6fc38c63fdde5431)
-rw-r--r--selftest/knownfail.d/replicate_against_deleted1
-rw-r--r--source4/dsdb/pydsdb.c2
-rw-r--r--source4/dsdb/samdb/samdb.h2
-rw-r--r--source4/torture/drs/python/ridalloc_exop.py135
4 files changed, 140 insertions, 0 deletions
diff --git a/selftest/knownfail.d/replicate_against_deleted b/selftest/knownfail.d/replicate_against_deleted
new file mode 100644
index 00000000000..9caa5346a45
--- /dev/null
+++ b/selftest/knownfail.d/replicate_against_deleted
@@ -0,0 +1 @@
+samba4.drs.ridalloc_exop.python\(.*\).ridalloc_exop.DrsReplicaSyncTestCase.test_replicate_against_deleted_objects_transaction
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/samdb.h b/source4/dsdb/samdb/samdb.h
index f22b4d99972..5cae2681ed0 100644
--- a/source4/dsdb/samdb/samdb.h
+++ b/source4/dsdb/samdb/samdb.h
@@ -360,6 +360,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/torture/drs/python/ridalloc_exop.py b/source4/torture/drs/python/ridalloc_exop.py
index 8efdd6a0603..0d46eee542b 100644
--- a/source4/torture/drs/python/ridalloc_exop.py
+++ b/source4/torture/drs/python/ridalloc_exop.py
@@ -46,6 +46,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):
@@ -676,3 +677,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@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@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")
+ 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_group2, [test_user5])
+
+ # Delete the user locally
+ new_ldb.deleteuser(test_user5)
+
+ # Confirm replication copes with a link to a locally deleted user
+ repl.replicate(self.ldb_dc1.domain_dn(),
+ source_dsa_invocation_id,
+ destination_dsa_guid)
+
+ finally:
+ self._test_force_demote(self.dnsname_dc1, "RIDALLOCTEST9")
+ shutil.rmtree(targetdir, ignore_errors=True)