]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
python-drs: Add client-side debug and fallback for GET_ANC
authorAndrew Bartlett <abartlet@samba.org>
Thu, 15 Sep 2022 05:10:24 +0000 (17:10 +1200)
committerAndrew Bartlett <abartlet@samba.org>
Tue, 4 Oct 2022 02:48:37 +0000 (02:48 +0000)
Samba 4.5 and earlier will fail to do GET_ANC correctly and will not
replicate non-critical parents of objects with isCriticalSystemObject=TRUE
when DRSUAPI_DRS_CRITICAL_ONLY is set.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=15189

Signed-off-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
python/samba/drs_utils.py
python/samba/join.py
selftest/knownfail.d/samba-4.5-emulation

index a71da6eedd3554f150366b8d4fce9e418f898fd4..6399e5f7fbcb9b1c8124e448d9916a3020ece712 100644 (file)
@@ -204,6 +204,44 @@ class drs_Replicate(object):
                 supports_ext & DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V10 and
                 (req.more_flags & drsuapi.DRSUAPI_DRS_GET_TGT) == 0)
 
+    @staticmethod
+    def _should_calculate_missing_anc_locally(error_code, req):
+        # If the error indicates we fail to resolve the parent object
+        # for a new object, then we assume we are replicating from a
+        # buggy server (Samba 4.5 and earlier) that doesn't really
+        # understand how to implement GET_ANC
+
+        return ((error_code == werror.WERR_DS_DRA_MISSING_PARENT) and
+                (req.replica_flags & drsuapi.DRSUAPI_DRS_GET_ANC) != 0)
+
+
+    def _calculate_missing_anc_locally(self, ctr):
+        self.guids_seen = set()
+
+        # walk objects in ctr, add to guid_seen as we see them
+        # note if an object doesn't have a parent
+
+        object_to_check = ctr.first_object
+
+        while True:
+            if object_to_check is None:
+                break
+
+            self.guids_seen.add(str(object_to_check.object.identifier.guid))
+
+            if object_to_check.parent_object_guid is not None \
+               and object_to_check.parent_object_guid \
+               != misc.GUID("00000000-0000-0000-0000-000000000000") \
+               and str(object_to_check.parent_object_guid) not in self.guids_seen:
+                obj_dn = ldb.Dn(self.samdb, object_to_check.object.identifier.dn)
+                parent_dn = obj_dn.parent()
+                print(f"Object {parent_dn} with "
+                      f"GUID {object_to_check.parent_object_guid} "
+                      "was not sent by the server in this chunk")
+
+            object_to_check = object_to_check.next_object
+
+
     def process_chunk(self, level, ctr, schema, req_level, req, first_chunk):
         '''Processes a single chunk of received replication data'''
         # pass the replication into the py_net.c python bindings for processing
@@ -326,8 +364,13 @@ class drs_Replicate(object):
                     # of causing the DC to restart the replication from scratch)
                     first_chunk = True
                     continue
-                else:
-                    raise e
+
+                if self._should_calculate_missing_anc_locally(e.args[0],
+                                                              req):
+                    print("Missing parent object - calculating missing objects locally")
+
+                    self._calculate_missing_anc_locally(ctr)
+                raise e
 
             first_chunk = False
             num_objects += ctr.object_count
index 97561323f21d8594d8f9679d004541508b3f966c..650bb5a08aed11296312249441eb32df122e860f 100644 (file)
@@ -968,17 +968,53 @@ class DCJoinContext(object):
                            destination_dsa_guid, rodc=ctx.RODC,
                            replica_flags=ctx.replica_flags)
             if not ctx.subdomain:
-                # Replicate first the critical object for the basedn
-                if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
-                    print("Replicating critical objects from the base DN of the domain")
-                    ctx.domain_replica_flags |= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
+                # Replicate first the critical objects for the basedn
+
+                # We do this to match Windows.  The default case is to
+                # do a critical objects replication, then a second
+                # with all objects.
+
+                print("Replicating critical objects from the base DN of the domain")
+                try:
                     repl.replicate(ctx.base_dn, source_dsa_invocation_id,
                                    destination_dsa_guid, rodc=ctx.RODC,
-                                   replica_flags=ctx.domain_replica_flags)
-                    ctx.domain_replica_flags ^= drsuapi.DRSUAPI_DRS_CRITICAL_ONLY
-                repl.replicate(ctx.base_dn, source_dsa_invocation_id,
-                               destination_dsa_guid, rodc=ctx.RODC,
-                               replica_flags=ctx.domain_replica_flags)
+                                   replica_flags=ctx.domain_replica_flags | drsuapi.DRSUAPI_DRS_CRITICAL_ONLY)
+                except WERRORError as e:
+
+                    if e.args[0] == werror.WERR_DS_DRA_MISSING_PARENT:
+                        ctx.logger.warning("First pass of replication with "
+                                           "DRSUAPI_DRS_CRITICAL_ONLY "
+                                           "not possible due to a missing parent object.  "
+                                           "This is typical of a Samba "
+                                           "4.5 or earlier server. "
+                                           "We will replicate the all objects instead.")
+                    else:
+                        raise
+
+                # Now replicate all the objects in the domain (unless
+                # we were run with --critical-only).
+                #
+                # Doing the replication of users as a second pass
+                # matches more closely the Windows behaviour, which is
+                # actually to do this on first startup.
+                #
+                # Use --critical-only if you want that (but you don't
+                # really, it is better to see any errors here).
+                if not ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
+                    try:
+                        repl.replicate(ctx.base_dn, source_dsa_invocation_id,
+                                       destination_dsa_guid, rodc=ctx.RODC,
+                                       replica_flags=ctx.domain_replica_flags)
+                    except WERRORError as e:
+
+                        if e.args[0] == werror.WERR_DS_DRA_MISSING_PARENT and \
+                           ctx.domain_replica_flags & drsuapi.DRSUAPI_DRS_CRITICAL_ONLY:
+                            ctx.logger.warning("Replication with DRSUAPI_DRS_CRITICAL_ONLY "
+                                               "failed due to a missing parent object.  "
+                                               "This may be a Samba 4.5 or earlier server "
+                                               "and is not compatible with --critical-only")
+                        raise
+
             print("Done with always replicated NC (base, config, schema)")
 
             # At this point we should already have an entry in the ForestDNS
index 37baa41822cf92e641eabe2e56115a45850c0581..1fc79361e40673ed47a8a5d41944aa7e74a72857 100644 (file)
@@ -2,4 +2,3 @@
 samba4.drs.getnc_exop.python\(chgdcpass\).getnc_exop.DrsReplicaSyncTestCase.test_FSMONotOwner\(chgdcpass\)
 # This fails because GET_ANC is now poorly implemented (matching Samba 4.5)
 ^samba4.drs.getnc_exop.python\(chgdcpass\).getnc_exop.DrsReplicaSyncTestCase.test_link_utdv_hwm\(chgdcpass\)
-^samba4.drs.samba_tool_drs_critical.python\(chgdcpass\).samba_tool_drs_critical.SambaToolDrsTests.test_samba_tool_drs_clone_dc_critical_object_chain\(chgdcpass:local\)
\ No newline at end of file