From e5471e66c6a718ffa28433813b8a8d7896b16d9e Mon Sep 17 00:00:00 2001 From: Alexander Bokovoy Date: Mon, 7 Jan 2019 15:28:29 +0200 Subject: [PATCH] ipaserver/dcerpc: fix exclusion entry with a forest trust domain info returned When looking through the topology of a trusted forest, we should support all types of forest trust records. Since Samba Python bindings parse the data into a typed structure, a type of the record has to be taken into account or there will be type mismatch when accessing elements of the union: typedef [switch_type(lsa_ForestTrustRecordType)] union { [case(LSA_FOREST_TRUST_TOP_LEVEL_NAME)] lsa_StringLarge top_level_name; [case(LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX)] lsa_StringLarge top_level_name_ex; [case(LSA_FOREST_TRUST_DOMAIN_INFO)] lsa_ForestTrustDomainInfo domain_info; [default] lsa_ForestTrustBinaryData data; } lsa_ForestTrustData; typedef struct { lsa_ForestTrustRecordFlags flags; lsa_ForestTrustRecordType type; NTTIME_hyper time; [switch_is(type)] lsa_ForestTrustData forest_trust_data; } lsa_ForestTrustRecord; typedef [public] struct { [range(0,4000)] uint32 count; [size_is(count)] lsa_ForestTrustRecord **entries; } lsa_ForestTrustInformation; Each entry in the lsa_ForestTrustInformation has forest_trust_data member but its content depends on the value of a type member (forest_trust_data is a union of all possible structures). Previously we assumed only TLN or TLN exclusion record which were of the same type (lsa_StringLarge). Access to forest_trust_data.string fails when forest_trust_data's type is lsa_ForestTrustDomainInfo as it has no string member. Fix the code by properly accessing the dns_domain_name from the lsa_ForestTrustDomainInfo structure. Fixes: https://pagure.io/freeipa/issue/7828 Reviewed-By: Christian Heimes --- ipaserver/dcerpc.py | 64 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py index 125493657..51a8e82e7 100644 --- a/ipaserver/dcerpc.py +++ b/ipaserver/dcerpc.py @@ -51,6 +51,7 @@ from samba.dcerpc import security, lsa, drsblobs, nbt, netlogon from samba.ndr import ndr_pack, ndr_print from samba import net from samba import arcfour_encrypt +from samba import ntstatus import samba import ldap as _ldap @@ -1105,6 +1106,25 @@ class TrustDomainInstance(object): original forest. """ + def domain_name_from_ftinfo(ftinfo): + """ + Returns a domain name string from a ForestTrustRecord + + :param ftinfo: LSA ForestTrustRecord to parse + """ + if ftinfo.type == lsa.LSA_FOREST_TRUST_DOMAIN_INFO: + return ftinfo.forest_trust_data.dns_domain_name.string + elif ftinfo.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME: + return ftinfo.forest_trust_data.string + elif ftinfo.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX: + # We should ignore TLN exclusion record because it + # is already an exclusion so we aren't going to + # change anything here + return None + else: + # Ignore binary blobs we don't know about + return None + # List of entries for unsolved conflicts result = [] @@ -1145,18 +1165,26 @@ class TrustDomainInstance(object): e1.time = e.time e1.forest_trust_data = e.forest_trust_data + # We either have a domain struct, a TLN name, + # or a TLN exclusion name in the list. + # The rest we should skip, those are binary blobs + dns_domain_name = domain_name_from_ftinfo(e) + # Search for a match in the topology of another domain # if there is a match, we have to convert a record # into a TLN exclusion to allow its routing to the # another domain for r in another_domain.ftinfo_records: - if r['rec_name'] == e.forest_trust_data.string: + # r['rec_name'] cannot be None, thus we can ignore + # the case when dns_domain_name is None + if r['rec_name'] == dns_domain_name: is_our_record = True # Convert e1 into an exclusion record e1.type = lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX e1.flags = 0 e1.time = trust_timestamp + e1.forest_trust_data.string = dns_domain_name break entries.append(e1) @@ -1180,11 +1208,29 @@ class TrustDomainInstance(object): # Update the forest trust information now ldname = lsa.StringLarge() ldname.string = rec.name.string - cninfo = self._pipe.lsaRSetForestTrustInformation( - self._policy_handle, - ldname, - lsa.LSA_FOREST_TRUST_DOMAIN_INFO, - fti, 0) + cninfo = None + try: + cninfo = self._pipe.lsaRSetForestTrustInformation( + self._policy_handle, + ldname, + lsa.LSA_FOREST_TRUST_DOMAIN_INFO, + fti, 0) + except samba.NTSTATUSError as error: + # Handle NT_STATUS_INVALID_PARAMETER separately + if ntstatus.NT_STATUS_INVALID_PARAMETER == error.args[0]: + result.append(rec) + logger.error("Unable to resolve conflict for " + "DNS domain %s in the forest %s " + "for in-forest domain %s. Trust cannot " + "be established unless this conflict " + "is fixed manually.", + another_domain.info['dns_domain'], + self.info['dns_domain'], + rec.name.string) + else: + raise assess_dcerpc_error(error) + + if cninfo: result.append(rec) logger.error("When defining exception for DNS " @@ -1213,9 +1259,9 @@ class TrustDomainInstance(object): # Otherwise, raise TrustTopologyConflictError() exception domains = [x.name.string for x in result] raise errors.TrustTopologyConflictError( - target=self.info['dns_domain'], - conflict=another_domain.info['dns_domain'], - domains=domains) + forest=self.info['dns_domain'], + conflict=another_domain.info['dns_domain'], + domains=domains) -- 2.20.1