[Freeipa-devel] [PATCH] 0117 ipaserver/dcerpc.py: populate forest trust information

Alexander Bokovoy abokovoy at redhat.com
Thu Sep 12 13:14:01 UTC 2013


Hi!

Attached patch does the magic of enabling all domains associated with
our realm in AD when we establish the trust relationship.

LsarSetForestTrustInformation RPC call is used to set the forest trust
information. Currently only top level names are exposed as we don't have
any domain name/SID/NetBIOS exclusion support yet. I decided to avoid
updating full TDO object as there is some problem with memory handling
of a domain sid object in lsa.ForestTrustRecord Python binding for that
type -- even assigned value gets immediately destroyed unless I
ndr_print the record.

The patch also moves string_to_array() helper to the top level as it is
useful for quite few RPC calls where instead of a structured record one
needs to use ndr_pack() and string_to_array() of the result. While it is
not used right now, there will be updates coming related to subdomains
handling that will require it anyway.

https://fedorahosted.org/freeipa/ticket/3919


-- 
/ Alexander Bokovoy
-------------- next part --------------
>From a1ae5d0c7635e76d083bb89db0fa705d2102686e Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy at redhat.com>
Date: Wed, 11 Sep 2013 21:34:55 +0300
Subject: [PATCH 4/4] ipaserver/dcerpc.py: populate forest trust information
 using realmdomains

Use realmdomains information to prepopulate forest trust info. As result,
all additional domains should now be enabled from the beginning, unless they
really conflict with existing DNS domains on AD side.

https://fedorahosted.org/freeipa/ticket/3919
---
 ipaserver/dcerpc.py | 113 +++++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 95 insertions(+), 18 deletions(-)

diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
index bd8f5aa..c24230b 100644
--- a/ipaserver/dcerpc.py
+++ b/ipaserver/dcerpc.py
@@ -39,7 +39,7 @@ import uuid
 from samba import param
 from samba import credentials
 from samba.dcerpc import security, lsa, drsblobs, nbt, netlogon
-from samba.ndr import ndr_pack
+from samba.ndr import ndr_pack, ndr_print
 from samba import net
 import samba
 import random
@@ -684,6 +684,12 @@ class DomainValidator(object):
         self._info[domain] = info
         return info
 
+def string_to_array(what):
+    blob = [0] * len(what)
+
+    for i in range(len(what)):
+        blob[i] = ord(what[i])
+    return blob
 
 class TrustDomainInstance(object):
 
@@ -698,6 +704,7 @@ class TrustDomainInstance(object):
         self._pipe = None
         self._policy_handle = None
         self.read_only = False
+        self.ftinfo_records = None
 
     def __gen_lsa_connection(self, binding):
        if self.creds is None:
@@ -827,12 +834,6 @@ class TrustDomainInstance(object):
         def arcfour_encrypt(key, data):
             c = RC4.RC4(key)
             return c.update(data)
-        def string_to_array(what):
-            blob = [0] * len(what)
-
-            for i in range(len(what)):
-                blob[i] = ord(what[i])
-            return blob
 
         password_blob = string_to_array(trustdom_secret.encode('utf-16-le'))
 
@@ -876,6 +877,53 @@ class TrustDomainInstance(object):
         self.auth_info = auth_info
 
 
+    def generate_ftinfo(self, another_domain):
+        """
+        Generates TrustDomainInfoFullInfo2Internal structure
+        This structure allows to pass information about all domains associated
+        with the another domain's realm.
+
+        Only top level name and top level name exclusions are handled here. 
+        """
+        if not another_domain.ftinfo_records:
+            return
+
+        ftinfo_records = []
+        info = lsa.ForestTrustInformation()
+
+        for rec in another_domain.ftinfo_records:
+            record = lsa.ForestTrustRecord()
+            record.flags = 0
+            record.time = rec['rec_time']
+            record.type = rec['rec_type']
+            record.forest_trust_data.string = rec['rec_name']
+            ftinfo_records.append(record)
+
+        info.count = len(ftinfo_records)
+        info.entries = ftinfo_records
+        return info
+
+    def update_ftinfo(self, another_domain):
+        """
+        Updates forest trust information in this forest corresponding
+        to the another domain's information.
+        """
+        try:
+            if another_domain.ftinfo_records:
+                ftinfo = self.generate_ftinfo(another_domain)
+                # Set forest trust information -- we do it only against AD DC as
+                # smbd already has the information about itself
+                ldname = lsa.StringLarge()
+                ldname.string = another_domain.info['dns_domain']
+                collision_info = self._pipe.lsaRSetForestTrustInformation(self._policy_handle,
+                                                                          ldname,
+                                                                          lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
+                                                                          ftinfo, 0)
+                if collision_info:
+                    root_logger.error("When setting forest trust information, got collision info back:\n%s" % (ndr_print(collision_info)))
+        except RuntimeError, e:
+            # We can ignore the error here -- setting up name suffix routes may fail
+            pass
 
     def establish_trust(self, another_domain, trustdom_secret):
         """
@@ -883,6 +931,12 @@ class TrustDomainInstance(object):
         Input: another_domain -- instance of TrustDomainInstance, initialized with #retrieve call
                trustdom_secret -- shared secred used for the trust
         """
+        if self.info['name'] == another_domain.info['name']:
+            # Check that NetBIOS names do not clash
+            raise errors.ValidationError(name=u'AD Trust Setup',
+                    error=_('the IPA server and the remote domain cannot share the same '
+                            'NetBIOS name: %s') % self.info['name'])
+
         self.generate_auth(trustdom_secret)
 
         info = lsa.TrustDomainInfoInfoEx()
@@ -893,12 +947,6 @@ class TrustDomainInstance(object):
         info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
         info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
 
-        if self.info['name'] == info.netbios_name.string:
-            # Check that NetBIOS names do not clash
-            raise errors.ValidationError(name=u'AD Trust Setup',
-                    error=_('the IPA server and the remote domain cannot share the same '
-                            'NetBIOS name: %s') % self.info['name'])
-
         try:
             dname = lsa.String()
             dname.string = another_domain.info['dns_domain']
@@ -911,12 +959,14 @@ class TrustDomainInstance(object):
         except RuntimeError, (num, message):
             raise assess_dcerpc_exception(num=num, message=message)
 
+        self.update_ftinfo(another_domain)
+
+        # We should use proper trustdom handle in order to modify the
+        # trust settings. Samba insists this has to be done with LSA
+        # OpenTrustedDomain* calls, it is not enough to have a handle
+        # returned by the CreateTrustedDomainEx2 call.
+        trustdom_handle = self._pipe.OpenTrustedDomainByName(self._policy_handle, dname, security.SEC_FLAG_MAXIMUM_ALLOWED)
         try:
-            # We should use proper trustdom handle in order to modify the
-            # trust settings. Samba insists this has to be done with LSA
-            # OpenTrustedDomain* calls, it is not enough to have a handle
-            # returned by the CreateTrustedDomainEx2 call.
-            trustdom_handle = self._pipe.OpenTrustedDomainByName(self._policy_handle, dname, security.SEC_FLAG_MAXIMUM_ALLOWED)
             infoclass = lsa.TrustDomainInfoSupportedEncTypes()
             infoclass.enc_types = security.KERB_ENCTYPE_RC4_HMAC_MD5
             infoclass.enc_types |= security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96
@@ -1016,6 +1066,32 @@ class TrustDomainJoins(object):
         # Otherwise, use anonymously obtained data
         self.remote_domain = rd
 
+    def get_realmdomains(self):
+        """
+        Generate list of records for forest trust information about
+        our realm domains. Note that the list generated currently
+        includes only top level domains, no exclusion domains, and no TDO objects
+        as we handle the latter in a separte way
+        """
+        if self.local_domain.read_only:
+            return
+
+	self.local_domain.ftinfo_records = []
+
+        realm_domains = self.api.Command.realmdomains_show()['result']
+        trustconfig = self.api.Command.trustconfig_show()['result']
+        # Use realmdomains' modification timestamp to judge records last update time
+        (dn, entry_attrs) = self.api.Backend.ldap2.get_entry(realm_domains['dn'], ['modifyTimestamp'])
+        # Convert the timestamp to Windows 64-bit timestamp format
+        trust_timestamp = long(time.mktime(time.strptime(entry_attrs['modifytimestamp'][0][:14], "%Y%m%d%H%M%S"))*1e7+116444736000000000)
+        
+        for dom in realm_domains['associateddomain']:
+            ftinfo = dict()
+            ftinfo['rec_name'] = dom
+            ftinfo['rec_time'] = trust_timestamp
+            ftinfo['rec_type'] = lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME
+            self.local_domain.ftinfo_records.append(ftinfo)
+
     def join_ad_full_credentials(self, realm, realm_server, realm_admin, realm_passwd):
         if not self.configured:
             return None
@@ -1030,6 +1106,7 @@ class TrustDomainJoins(object):
 
         if not self.remote_domain.read_only:
             trustdom_pass = samba.generate_random_password(128, 128)
+            self.get_realmdomains()
             self.remote_domain.establish_trust(self.local_domain, trustdom_pass)
             self.local_domain.establish_trust(self.remote_domain, trustdom_pass)
             result = self.remote_domain.verify_trust(self.local_domain)
-- 
1.8.3.1



More information about the Freeipa-devel mailing list