diff options
author | Andrew Bartlett <abartlet@samba.org> | 2022-09-15 17:10:24 +1200 |
---|---|---|
committer | Jule Anger <janger@samba.org> | 2022-10-07 08:48:17 +0000 |
commit | bac9532f0a98ad54d4ad00f94bcbf13c797f823d (patch) | |
tree | 3b8571418540c5f639ac5b5b84f81e665693e38b /python/samba | |
parent | 79283760616bdd1ae811f8c407d601dfc48f019e (diff) | |
download | samba-bac9532f0a98ad54d4ad00f94bcbf13c797f823d.tar.gz |
python-drs: Add client-side debug and fallback for GET_ANC
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
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>
(cherry picked from commit bff2bc9c7d69ec2fbe9339c2353a0a846182f1ea)
Diffstat (limited to 'python/samba')
-rw-r--r-- | python/samba/drs_utils.py | 47 | ||||
-rw-r--r-- | python/samba/join.py | 54 |
2 files changed, 90 insertions, 11 deletions
diff --git a/python/samba/drs_utils.py b/python/samba/drs_utils.py index a71da6eedd3..6399e5f7fbc 100644 --- a/python/samba/drs_utils.py +++ b/python/samba/drs_utils.py @@ -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 diff --git a/python/samba/join.py b/python/samba/join.py index 97561323f21..650bb5a08ae 100644 --- a/python/samba/join.py +++ b/python/samba/join.py @@ -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 |