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