[Freeipa-devel] [PATCH] 0118 add support for subdomains

Alexander Bokovoy abokovoy at redhat.com
Tue Oct 1 15:15:09 UTC 2013


On Mon, 30 Sep 2013, Alexander Bokovoy wrote:
>On Mon, 30 Sep 2013, Tomas Babej wrote:
>>On 09/28/2013 10:01 PM, Alexander Bokovoy wrote:
>>>On Fri, 27 Sep 2013, Sumit Bose wrote:
>>>>On Fri, Sep 27, 2013 at 03:53:08PM +0300, Alexander Bokovoy wrote:
>>>>>On Mon, 23 Sep 2013, Alexander Bokovoy wrote:
>>>>>>On Mon, 23 Sep 2013, Alexander Bokovoy wrote:
>>>>>>>On Mon, 23 Sep 2013, Alexander Bokovoy wrote:
>>>>>>>>On Mon, 23 Sep 2013, Martin Kosek wrote:
>>>>>>>>>>>However, we don't have trust type available so it 
>>>>>>>>>>>needs
>>>>>to discovered
>>>>>>>>>>>every time. This doesn't play well with the 
>>>>>>>>>>>framework, it
>>>>>is simply not
>>>>>>>>>>>expecting dynamic containers.
>>>>>>>>>>
>>>>>>>>>>This doesn't sound like a big obstacle to me. Right 
>>>>>>>>>>now
>>>>>the trust_type lookup
>>>>>>>>>>is done in trust_show.execute() for some reason, 
>>>>>>>>>>which is
>>>>>not the best place to
>>>>>>>>>>do it IMHO. Doing it in trust.get_dn() instead 
>>>>>>>>>>should
>>>>>simplify things enough to
>>>>>>>>>>make parent_object work.
>>>>>>>>>
>>>>>>>>>Yup, get_dn() is the method where object DN lookup 
>>>>>>>>>should
>>>>>be done. See for
>>>>>>>>>example host.py plugin get_dn method, we also do a 
>>>>>>>>>dynamic
>>>>>lookup for correct
>>>>>>>>>host name.
>>>>>>>>I'll see if that would work.
>>>>>>>>
>>>>>>>>>the best way to implement dynamic DN gathering is the
>>>>>get_dn() method. That
>>>>>>>>>way, it could be implemented in one place and all 
>>>>>>>>>commands
>>>>>could take advantage
>>>>>>>>>of it instead of re-implementing it several times in
>>>>>pre_callback - this is
>>>>>>>>>just hackish.
>>>>>>>>I'd suggest you look into the code. The commands use
>>>>>pre_callback for a
>>>>>>>>different purpose than implementing dynamic DN gathering.
>>>>>>>>
>>>>>>>>>I think it would have been very useful to have a 
>>>>>>>>>design
>>>>>page before sending a
>>>>>>>>>patch. It is then easier to make design decisions 
>>>>>>>>>without
>>>>>having to dig into
>>>>>>>>>the patch.
>>>>>>>>The design page is there for long time:
>>>>>>>>http://www.freeipa.org/page/V3/Transitive_Trusts
>>>>>>>Ok, here is new version of the patch and updated version of my 0117
>>>>>>>patch as Sumit noticed I've sent wrong version.
>>>>>>Ok, here is updated 0118 which fixes API.txt change for
>>>>>trustdomain_add
>>>>>>-- I renamed trustdomain_create to trustdomain_add but forgot to rerun
>>>>>>makeapi.
>>>>>New edition attached for all subdomain-related patches:
>>>>
>>>>I did some tests and all is working as expected.
>>>>
>>>>>
>>>>>freeipa-abbra-0117-ipaserver-dcerpc.py-populate-forest-trust-informatio-3.patch
>>>>>
>>>>> Use realmdomains to report name suffix routes at the time we 
>>>>>establish trust
>>>>>
>>>>>freeipa-abbra-0118-trusts-support-subdomains-in-a-forest-3.patch
>>>>> Introduce trustdomain-* commands to fetch list of domains associated
>>>>> with a forest trust and allow filtering them off
>>>>
>>>>We talked on irc that ipaNTSupportedEncryptionTypes in the filter
>>>>for the trusted domains should be replace by a different attribute.
>>>>Because of an error in ipasam the ipaNTSupportedEncryptionTypes is only
>>>>set in recent versions and might not be present in the 
>>>>directory trees of
>>>>older versions.
>>>Fixed in the attached patch 0118 version 4.
>>>
>>>Also attached first attempt to implement transiting through trusted
>>>domains, as patch 0123. In this patch we grant transition only if all
>>>three realms (client, transited realm, and server realm) match any of
>>>our trusted domains and our domain. This is probably a bit wider but it
>>>worked for me bidirectionally, from a child domain to a service in IPA,
>>>and from IPA realm to a service in a child domain of a forest trust.
>>>
>>>
>>>
>>>_______________________________________________
>>>Freeipa-devel mailing list
>>>Freeipa-devel at redhat.com
>>>https://www.redhat.com/mailman/listinfo/freeipa-devel
>>
>>Hi,
>>
>>here are my comments:
>>
>>*PATCH 117*
>>
>>+    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
>>+        """
>>
>>A nitpick typo: separte -> separate.
>Fixed.
>
>>
>>Also, there's trailing whitespace in the patch:
>>
>>Applying: ipaserver/dcerpc.py: populate forest trust information 
>>using realmdomains
>>/home/tbabej/dev/freeipa/.git/rebase-apply/patch:62: trailing whitespace.
>>       Only top level name and top level name exclusions are handled here.
>>/home/tbabej/dev/freeipa/.git/rebase-apply/patch:174: trailing whitespace.
>>
>>warning: 2 lines add whitespace errors.
>Fixed.
>
>>
>>
>>*PATCH 119*
>>
>>We also need to change the frontend tests that cover this functionality:
>>
>>======================================================================
>>FAIL: Test the ``ipalib.frontend.Command.args`` instance attribute.
>>----------------------------------------------------------------------
>>Traceback (most recent call last):
>> File "/usr/lib/python2.7/site-packages/nose/case.py", line 197, in 
>>runTest
>>   self.test(*self.arg)
>> File 
>>"/home/tbabej/dev/freeipa/ipatests/test_ipalib/test_frontend.py", 
>>line 283, in test_args
>>   assert str(e) == 'arg2: required argument after optional'
>>AssertionError
>>
>>See ipatests/test_ipalib/test_frontend.py, line 281:
>>
>>       # Test ValueError, required after optional:
>>       e = raises(ValueError, self.get_instance, args=('arg1?', 'arg2'))
>>       assert str(e) == 'arg2: required argument after optional'
>Ok, will fix. This patch is not essential, of course, so we can decide
>what to do with it later.
>
>>
>>
>>*PATCH 120*
>>
>>When I try to add a trust, I get internal error:
>>
>>echo $AD_PASSWORD | ipa trust-add --type=ad $AD_DOMAIN --admin 
>>Administrator --password
>>
>>[Wed Sep 25 10:28:53.978664 2013] [:error] [pid 7905] ipa: ERROR: 
>>non-public: IndexError: tuple index out of range
>>[Wed Sep 25 10:28:53.978702 2013] [:error] [pid 7905] Traceback 
>>(most recent call last):
>>[Wed Sep 25 10:28:53.978708 2013] [:error] [pid 7905]   File 
>>"/usr/lib/python2.7/site-packages/ipaserver/rpcserver.py", line 
>>333, in wsgi_execute
>>[Wed Sep 25 10:28:53.978713 2013] [:error] [pid 7905]     result = 
>>self.Command[name](*args, **options)
>>[Wed Sep 25 10:28:53.978720 2013] [:error] [pid 7905]   File 
>>"/usr/lib/python2.7/site-packages/ipalib/frontend.py", line 436, in 
>>__call__
>>[Wed Sep 25 10:28:53.978725 2013] [:error] [pid 7905]     ret = 
>>self.run(*args, **options)
>>[Wed Sep 25 10:28:53.978730 2013] [:error] [pid 7905]   File 
>>"/usr/lib/python2.7/site-packages/ipalib/frontend.py", line 755, in 
>>run
>>[Wed Sep 25 10:28:53.978734 2013] [:error] [pid 7905]     result = 
>>self.execute(*args, **options)
>>[Wed Sep 25 10:28:53.978739 2013] [:error] [pid 7905]   File 
>>"/usr/lib/python2.7/site-packages/ipalib/plugins/trust.py", line 
>>338, in execute
>>[Wed Sep 25 10:28:53.978744 2013] [:error] [pid 7905] 
>>self.add_range(range_name, dom_sid, *keys, **options)
>>[Wed Sep 25 10:28:53.978748 2013] [:error] [pid 7905]   File 
>>"/usr/lib/python2.7/site-packages/ipalib/plugins/trust.py", line 
>>549, in add_range
>>[Wed Sep 25 10:28:53.978755 2013] [:error] [pid 7905] quiet=True)
>>[Wed Sep 25 10:28:53.978759 2013] [:error] [pid 7905]   File 
>>"/usr/lib/python2.7/site-packages/ipaserver/dcerpc.py", line 507, 
>>in search_in_dc
>>[Wed Sep 25 10:28:53.978764 2013] [:error] [pid 7905]     info = 
>>self.__retrieve_trusted_domain_gc_list(domain)
>>[Wed Sep 25 10:28:53.978769 2013] [:error] [pid 7905]   File 
>>"/usr/lib/python2.7/site-packages/ipaserver/dcerpc.py", line 595, 
>>in __retrieve_trusted_domain_gc_list
>>[Wed Sep 25 10:28:53.978774 2013] [:error] [pid 7905] info['auth'] 
>>= self._domains[domain][2]
>>[Wed Sep 25 10:28:53.978778 2013] [:error] [pid 7905] IndexError: 
>>tuple index out of range
>>[Wed Sep 25 10:28:53.979248 2013] [:error] [pid 7905] ipa: INFO: 
>>admin at DOM006.TBAD.IPA.COM: trust_add(u'tbad.ipa.com', 
>>trust_type=u'ad', realm_admin=u'Administrator', 
>>realm_passwd=u'********', all=False, raw=False, version=u'2.65'): 
>>IndexError
>>
>>I think we need to do the following changes here:
>>
>>diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
>>index fa5c449..4ac0a5f 100644
>>--- a/ipaserver/dcerpc.py
>>+++ b/ipaserver/dcerpc.py
>>@@ -565,7 +565,6 @@ class DomainValidator(object):
>>        Returns dictionary with following keys
>>             name       -- NetBIOS name of the trusted domain
>>             dns_domain -- DNS name of the trusted domain
>>-             auth       -- encrypted credentials for trusted domain account
>>             gc         -- array of tuples (server, port) for 
>>Global Catalog
>>        """
>>        if domain in self._info:
>>@@ -592,7 +591,6 @@ class DomainValidator(object):
>>            self._domains = self.get_trusted_domains()
>>
>>        info = dict()
>>-        info['auth'] = self._domains[domain][2]
>>        servers = []
>>
>>        if result:
>>
>>After applying this fix, I get:
>>
>>tbabej at vm-006 freeipa]$ echo $AD_PASSWORD | ipa trust-add --type=ad 
>>$AD_DOMAIN --admin Administrator --password
>>ipa: ERROR: CIFS server communication error: code "-1073741811",
>>                 message "Unexpected information received" (both 
>>may be "None")
>>
>>I was unable to track this one down in a reasonable timeframe, I 
>>suggest we continue on IRC.
>I've fixed this. At the time we establish trust, there could be a race
>condition when cross-realm TGT is not yet ready so we cannot rely on it
>when fetching domains. As we have administrator's credentials here, I've
>added use of them in addition to Kerberos.
>
>
>I'll send new patchset shortly.
New patchset is attached.

1. Added test update for ipalib/frontend.py changes
2. Used LDAPQuery as base for trustdomain_enable|disable commands as
   suggested by Honza.
3. Fixed issues with removal of trust account password authentication
4. Added support to use AD administrator credentials when fetching
   subdomains information when we establish trust as Kerberos will not
   be available for cross-realm operations yet.
5. Patch 0123 is not part of the patchset and should not be committed,
   we will discuss exact semantics of transition checks with MIT
   Kerberos upstream first.
6. Fixed few error paths and dead-end cases like attempt to disable root
   domain of the trust (renders trust dead) or enabling it (it is always
   enabled).
7. Made clear that deleting root domain of the trust is not possible,
   use trust-del instead.
8. Removed whitespaces where saw.



-- 
/ Alexander Bokovoy
-------------- next part --------------
>From 690d7a71b053aef2eca5b7def0770f37a9659b4e 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 1/7] 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..cc9e7be 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

-------------- next part --------------
>From a36e53eab287e5eda88cd28d10c681b93af9d68b Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy at redhat.com>
Date: Wed, 18 Sep 2013 17:04:19 +0200
Subject: [PATCH 2/7] trusts: support subdomains in a forest

Add IPA CLI to manage trust domains.

ipa trust-fetch-domains <trust>      -- fetch list of subdomains from AD side and add new ones to IPA
ipa trustdomain-find <trust>         -- show all available domains
ipa trustdomain-del <trust> <domain> -- remove domain from IPA view about <trust>
ipa trustdomain-enable <trust> <domain> -- allow users from trusted domain to access resources in IPA
ipa trustdomain-disable <trust> <domain> -- disable access to resources in IPA from trusted domain

By default all discovered trust domains are allowed to access IPA resources

IPA KDC needs also information for authentication paths to subdomains in case they
are not hierarchical under AD forest trust root. This information is managed via capaths
section in krb5.conf. SSSD should be able to generate it once
ticket https://fedorahosted.org/sssd/ticket/2093 is resolved.

part of https://fedorahosted.org/freeipa/ticket/3909
---
 API.txt                 |  80 +++++++++++
 ipalib/plugins/trust.py | 346 +++++++++++++++++++++++++++++++++++++++++-------
 ipaserver/dcerpc.py     |  54 ++++++++
 3 files changed, 429 insertions(+), 51 deletions(-)

diff --git a/API.txt b/API.txt
index 761d1d1..d9242eb 100644
--- a/API.txt
+++ b/API.txt
@@ -3423,6 +3423,15 @@ option: Str('version?', exclude='webui')
 output: Output('result', <type 'dict'>, None)
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: Output('value', <type 'unicode'>, None)
+command: trust_fetch_domains
+args: 1,4,2
+arg: Str('cn', attribute=True, cli_name='realm', multivalue=False, primary_key=True, query=True, required=True)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Str('version?', exclude='webui')
+output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list of LDAP entries', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 command: trust_find
 args: 1,11,4
 arg: Str('criteria?', noextrawhitespace=False)
@@ -3497,6 +3506,77 @@ option: Str('version?', exclude='webui')
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: Output('value', <type 'unicode'>, None)
+command: trustdomain_add
+args: 2,9,3
+arg: Str('trustcn', cli_name='trust', query=True, required=True)
+arg: Str('cn', cli_name='domain')
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('ipantflatname', attribute=True, cli_name='flat_name', multivalue=False, required=False)
+option: Str('ipanttrusteddomainsid', attribute=True, cli_name='sid', multivalue=False, required=False)
+option: Str('ipanttrustpartner', attribute=True, cli_name='ipanttrustpartner', multivalue=False, required=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: StrEnum('trust_type', autofill=True, cli_name='type', default=u'ad', values=(u'ad',))
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('value', <type 'unicode'>, None)
+command: trustdomain_del
+args: 2,2,3
+arg: Str('trustcn', cli_name='trust', query=True, required=True)
+arg: Str('cn', cli_name='domain')
+option: Flag('continue', autofill=True, cli_name='continue', default=False)
+option: Str('version?', exclude='webui')
+output: Output('result', <type 'dict'>, None)
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('value', <type 'unicode'>, None)
+command: trustdomain_disable
+args: 2,1,1
+arg: Str('trustcn', cli_name='trust', query=True, required=True)
+arg: Str('cn', cli_name='domain')
+option: Str('version?', exclude='webui')
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+command: trustdomain_enable
+args: 2,1,1
+arg: Str('trustcn', cli_name='trust', query=True, required=True)
+arg: Str('cn', cli_name='domain')
+option: Str('version?', exclude='webui')
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+command: trustdomain_find
+args: 2,8,4
+arg: Str('trustcn', cli_name='trust', query=True, required=True)
+arg: Str('criteria?', noextrawhitespace=False)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('ipantflatname', attribute=True, autofill=False, cli_name='flat_name', multivalue=False, query=True, required=False)
+option: Str('ipanttrusteddomainsid', attribute=True, autofill=False, cli_name='sid', multivalue=False, query=True, required=False)
+option: Str('ipanttrustpartner', attribute=True, autofill=False, cli_name='ipanttrustpartner', multivalue=False, query=True, required=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Int('sizelimit?', autofill=False, minvalue=0)
+option: Int('timelimit?', autofill=False, minvalue=0)
+option: Str('version?', exclude='webui')
+output: Output('count', <type 'int'>, None)
+output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list of LDAP entries', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('truncated', <type 'bool'>, None)
+command: trustdomain_mod
+args: 2,11,3
+arg: Str('trustcn', cli_name='trust', query=True, required=True)
+arg: Str('cn', cli_name='domain')
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('delattr*', cli_name='delattr', exclude='webui')
+option: Str('ipantflatname', attribute=True, autofill=False, cli_name='flat_name', multivalue=False, required=False)
+option: Str('ipanttrusteddomainsid', attribute=True, autofill=False, cli_name='sid', multivalue=False, required=False)
+option: Str('ipanttrustpartner', attribute=True, autofill=False, cli_name='ipanttrustpartner', multivalue=False, required=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Str('setattr*', cli_name='setattr', exclude='webui')
+option: StrEnum('trust_type', autofill=True, cli_name='type', default=u'ad', values=(u'ad',))
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('value', <type 'unicode'>, None)
 command: user_add
 args: 1,35,3
 arg: Str('uid', attribute=True, cli_name='login', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', primary_key=True, required=True)
diff --git a/ipalib/plugins/trust.py b/ipalib/plugins/trust.py
index 3c117b4..de98451 100644
--- a/ipalib/plugins/trust.py
+++ b/ipalib/plugins/trust.py
@@ -181,6 +181,13 @@ def trust_status_string(level):
     string = _trust_status_dict.get(level, _trust_type_dict_unknown)
     return unicode(string)
 
+def make_trust_dn(env, trust_type, dn):
+    assert isinstance(dn, DN)
+    if trust_type:
+        container_dn = DN(('cn', trust_type), env.container_trusts, env.basedn)
+        return DN(dn, container_dn)
+    return dn
+
 class trust(LDAPObject):
     """
     Trust object.
@@ -195,7 +202,8 @@ class trust(LDAPObject):
         'ipantauthtrustoutgoing', 'ipanttrustauthincoming', 'ipanttrustforesttrustinfo',
         'ipanttrustposixoffset', 'ipantsupportedencryptiontypes' ]
     search_display_attributes = ['cn', 'ipantflatname',
-                                 'ipanttrusteddomainsid', 'ipanttrusttype' ]
+                                 'ipanttrusteddomainsid', 'ipanttrusttype',
+                                 'ipantsidblacklistincoming', 'ipantsidblacklistoutgoing' ]
 
     label = _('Trusts')
     label_singular = _('Trust')
@@ -241,12 +249,26 @@ class trust(LDAPObject):
                     raise errors.ValidationError(name=attr,
                             error=_("invalid SID: %(value)s") % dict(value=value))
 
-def make_trust_dn(env, trust_type, dn):
-    assert isinstance(dn, DN)
-    if trust_type in trust.trust_types:
-        container_dn = DN(('cn', trust_type), env.container_trusts, env.basedn)
-        return DN(dn[0], container_dn)
-    return dn
+    def get_dn(self, *keys, **kwargs):
+        sdn = map(lambda x: ('cn', x), keys)
+        sdn.reverse()
+        trust_type = kwargs.get('trust_type')
+        if trust_type is None:
+            ldap = self.backend
+            filter = ldap.make_filter({'objectclass': ['ipaNTTrustedDomain'], 'cn': [keys[-1]]})
+            filter = ldap.combine_filters((filter, "ipaNTSIDBlacklistIncoming=*"), rules=ldap.MATCH_ALL)
+            try:
+                result = ldap.get_entries(DN(self.container_dn, self.env.basedn),
+                                          ldap.SCOPE_SUBTREE, filter, [''])
+            except errors.NotFound:
+                trust_type = u'ad'
+            else:
+                if len(result) > 1:
+                    raise errors.OnlyOneValueAllowed(attr='trust domain')
+                return result[0].dn
+
+        dn=make_trust_dn(self.env, trust_type, DN(*sdn))
+        return dn
 
 class trust_add(LDAPCreate):
     __doc__ = _('''
@@ -576,10 +598,13 @@ sides.
     def execute_ad(self, full_join, *keys, **options):
         # Join domain using full credentials and with random trustdom
         # secret (will be generated by the join method)
-        try:
-            api.Command['trust_show'](keys[-1])
+
+        # First see if the trust is already in place
+        # Force retrieval of the trust object by not passing trust_type
+        dn = self.obj.get_dn(keys[-1])
+        if dn:
             summary = _('Re-established trust to domain "%(value)s"')
-        except errors.NotFound:
+        else:
             summary = self.msg_summary
 
         # 1. Full access to the remote domain. Use admin credentials and
@@ -652,14 +677,6 @@ class trust_del(LDAPDelete):
 
     msg_summary = _('Deleted trust "%(value)s"')
 
-    def pre_callback(self, ldap, dn, *keys, **options):
-        assert isinstance(dn, DN)
-        try:
-            result = self.api.Command.trust_show(keys[-1])
-        except errors.NotFound, e:
-            self.obj.handle_not_found(*keys)
-        return result['result']['dn']
-
 class trust_mod(LDAPUpdate):
     __doc__ = _("""
     Modify a trust (for future use).
@@ -673,16 +690,10 @@ class trust_mod(LDAPUpdate):
 
     def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
         assert isinstance(dn, DN)
-        result = None
-        try:
-            result = self.api.Command.trust_show(keys[-1])
-        except errors.NotFound, e:
-            self.obj.handle_not_found(*keys)
 
         self.obj.validate_sid_blacklists(entry_attrs)
 
-        # TODO: we found the trust object, now modify it
-        return result['result']['dn']
+        return dn
 
 class trust_find(LDAPSearch):
     __doc__ = _('Search for trusts.')
@@ -696,8 +707,10 @@ class trust_find(LDAPSearch):
     # Since all trusts types are stored within separate containers under 'cn=trusts',
     # search needs to be done on a sub-tree scope
     def pre_callback(self, ldap, filters, attrs_list, base_dn, scope, *args, **options):
-        assert isinstance(base_dn, DN)
-        return (filters, base_dn, ldap.SCOPE_SUBTREE)
+        # list only trust, not trust domains
+        trust_filter = '(ipaNTSIDBlacklistIncoming=*)'
+        filter = ldap.combine_filters((filters, trust_filter), rules=ldap.MATCH_ALL)
+        return (filter, base_dn, ldap.SCOPE_SUBTREE)
 
     def post_callback(self, ldap, entries, truncated, *args, **options):
         if options.get('pkey_only', False):
@@ -718,30 +731,6 @@ class trust_show(LDAPRetrieve):
     has_output_params = LDAPRetrieve.has_output_params + trust_output_params +\
                         (Str('ipanttrusttype'), Str('ipanttrustdirection'))
 
-    def execute(self, *keys, **options):
-        error = None
-        result = None
-        for trust_type in trust.trust_types:
-            options['trust_show_type'] = trust_type
-            try:
-                result = super(trust_show, self).execute(*keys, **options)
-            except errors.NotFound, e:
-                result = None
-                error = e
-            if result:
-                break
-        if error or not result:
-            self.obj.handle_not_found(*keys)
-
-        return result
-
-    def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
-        assert isinstance(dn, DN)
-        if 'trust_show_type' in options:
-            return make_trust_dn(self.env, options['trust_show_type'], dn)
-
-        return dn
-
     def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
 
         # Translate ipanttrusttype to trusttype
@@ -1078,3 +1067,258 @@ class sidgen_was_run(Command):
         return dict(result=True)
 
 api.register(sidgen_was_run)
+
+class trustdomain(LDAPObject):
+    """
+    Object representing a domain of the AD trust.
+    """
+    parent_object = 'trust'
+    trust_type_idx = {'2':u'ad'}
+    object_name = _('trust domain')
+    object_name_plural = _('trust domains')
+    object_class = ['ipaNTTrustedDomain']
+    default_attributes = ['cn', 'ipantflatname', 'ipanttrusteddomainsid', 'ipanttrustpartner']
+    search_display_attributes = ['cn', 'ipantflatname', 'ipanttrusteddomainsid', ]
+
+    label = _('Trusted domains')
+    label_singular = _('Trusted domain')
+
+    # We do not add this argument implicitly via takes_args in the object.
+    # Rather, it is added explicitly in the commands that require second arg
+    # because first argument will be inherited from the 'trust' parent object
+    trustdomain_args = (
+        Str('cn',
+            label=_('Domain name'),
+            cli_name='domain',
+        ),
+    )
+
+    takes_params = (
+        Str('ipantflatname?',
+            cli_name='flat_name',
+            label=_('Domain NetBIOS name'),
+        ),
+        Str('ipanttrusteddomainsid?',
+            cli_name='sid',
+            label=_('Domain Security Identifier'),
+        ),
+        Str('ipanttrustpartner?',
+            label=_('Trusted domain partner'),
+            flags=['no_display', 'no_option'],
+        ),
+    )
+
+    # LDAPObject.get_dn() only passes all but last element of keys and no kwargs
+    # to the parent object's get_dn() no matter what you pass to it. Make own get_dn()
+    # as we really need all elements to construct proper dn.
+    def get_dn(self, *keys, **kwargs):
+        sdn = map(lambda x: ('cn', x), keys)
+        sdn.reverse()
+        trust_type = kwargs.get('trust_type')
+        if not trust_type:
+            trust_type=u'ad'
+
+        dn=make_trust_dn(self.env, trust_type, DN(*sdn))
+        return dn
+api.register(trustdomain)
+
+class trustdomain_find(LDAPSearch):
+    __doc__ = _('Search domains of the trust')
+
+    has_output_params = LDAPSearch.has_output_params + trustdomain.trustdomain_args
+    def pre_callback(self, ldap, filters, attrs_list, base_dn, scope, *args, **options):
+        return (filters, base_dn, ldap.SCOPE_SUBTREE)
+api.register(trustdomain_find)
+
+class trustdomain_mod(LDAPUpdate):
+    __doc__ = _('Modify trustdomain of the trust')
+
+    NO_CLI = True
+    takes_args = trustdomain.trustdomain_args
+    takes_options = LDAPUpdate.takes_options + (_trust_type_option,)
+api.register(trustdomain_mod)
+
+class trustdomain_add(LDAPCreate):
+    __doc__ = _('Allow access from the trusted domain')
+    NO_CLI = True
+
+    takes_args = trustdomain.trustdomain_args
+    takes_options = LDAPCreate.takes_options + (_trust_type_option,)
+    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+        if 'ipanttrustpartner' in options:
+            entry_attrs['ipanttrustpartner'] = [options['ipanttrustpartner']]
+        return dn
+api.register(trustdomain_add)
+
+class trustdomain_del(LDAPDelete):
+    __doc__ = _('Remove infromation about the domain associated with the trust.')
+
+    msg_summary = _('Removed information about the trusted domain "%(value)s"')
+
+    takes_args = trustdomain.trustdomain_args
+
+    def execute(self, *keys, **options):
+        # Note that pre-/post- callback handling for LDAPDelete is causing pre_callback
+        # to always receive empty keys. We need to catch the case when root domain is being deleted
+        if keys[0].lower() == keys[1].lower():
+            raise errors.ValidationError(name='trustdomain_del',
+                error=_("cannot delete root domain of the trust, use trust-del to delete the trust itself"))
+        result = super(trustdomain_del, self).execute(*keys, **options)
+        result['value'] = keys[1]
+        return result
+
+
+api.register(trustdomain_del)
+
+
+def fetch_domains_from_trust(self, trustinstance, trust_entry):
+    trust_name = trust_entry['cn'][0]
+    domains = ipaserver.dcerpc.fetch_domains(self.api, trustinstance.local_flatname, trust_name)
+    result = []
+    if not domains:
+        return None
+
+    for dom in domains:
+        dom['trust_type'] = u'ad'
+        try:
+            name = dom['cn']
+            del dom['cn']
+            res = self.api.Command.trustdomain_add(trust_name, name, **dom)
+            result.append(res['result'])
+        except errors.DuplicateEntry:
+            # Ignore updating duplicate entries
+            pass
+    return result
+
+class trust_fetch_domains(LDAPRetrieve):
+    __doc__ = _('Refresh list of the domains associated with the trust')
+
+    has_output = (
+        output.ListOfEntries('result'),
+        output.summary
+    )
+
+    def execute(self, *keys, **options):
+        if not _bindings_installed:
+            raise errors.NotFound(
+                name=_('AD Trust setup'),
+                reason=_(
+                    'Cannot perform join operation without Samba 4 support '
+                    'installed. Make sure you have installed server-trust-ad '
+                    'sub-package of IPA'
+                )
+            )
+        trust = self.api.Command.trust_show(keys[0], raw=True)['result']
+
+        trustinstance = ipaserver.dcerpc.TrustDomainJoins(self.api)
+        if not trustinstance.configured:
+            raise errors.NotFound(
+                name=_('AD Trust setup'),
+                reason=_(
+                    'Cannot perform join operation without own domain '
+                    'configured. Make sure you have run ipa-adtrust-install '
+                    'on the IPA server first'
+                )
+            )
+        domains = fetch_domains_from_trust(self, trustinstance, trust)
+        result = dict(result=[])
+        if not domains:
+            result['summary'] = unicode(_('No trust domains were detected during refresh'))
+            return result
+
+        if len(domains) > 0:
+            result['summary'] = unicode(_('List of trust domains successfully refreshed'))
+        else:
+            result['summary'] = unicode(_('No new trust domains were found'))
+
+        result['result'] = domains
+        return result
+api.register(trust_fetch_domains)
+
+class trustdomain_enable(LDAPQuery):
+    __doc__ = _('Allow use of IPA resources by the domain of the trust')
+
+    has_output = (
+        output.summary,
+    )
+
+    takes_args = trustdomain.trustdomain_args
+
+    def execute(self, *keys, **options):
+        ldap = self.api.Backend.ldap2
+
+        if keys[0].lower() == keys[1].lower():
+            raise errors.ValidationError(name='trustdomain_enable',
+                error=_("Root domain of the trust is always enabled for the existing trust"))
+        try:
+            trust_dn = self.obj.get_dn(keys[0], trust_type=u'ad')
+            trust_entry = ldap.get_entry(trust_dn)
+        except errors.NotFound:
+            raise errors.ValidationError(name=_('AD trust'), error=_('Valid trust name is required'))
+
+        result = dict()
+        result['trust'] = unicode(keys[0])
+        result['domain'] = unicode(keys[1])
+        dn = self.obj.get_dn(keys[0], keys[1], trust_type=u'ad')
+        try:
+            entry = ldap.get_entry(dn)
+            sid = entry['ipanttrusteddomainsid'][0]
+            if sid in trust_entry['ipantsidblacklistincoming']:
+                trust_entry['ipantsidblacklistincoming'].remove(sid)
+                result['action'] = _('is allowed')
+                ldap.update_entry(trust_entry)
+            else:
+                result['action'] = _('is already allowed')
+            result['summary'] = _('Domain %(domain)s of trust %(trust)s %(action)s to access IPA resources')
+        except errors.NotFound:
+            raise errors.ValidationError(name=_('AD trust'), error=_('Valid trust domain name is required'))
+        except errors.EmptyModlist:
+            result['summary'] = _('No changes were done')
+
+        return dict(summary=result['summary'] % result)
+
+api.register(trustdomain_enable)
+
+class trustdomain_disable(LDAPQuery):
+    __doc__ = _('Disable use of IPA resources by the domain of the trust')
+
+    has_output = (
+        output.summary,
+    )
+
+    takes_args = trustdomain.trustdomain_args
+
+    def execute(self, *keys, **options):
+        ldap = self.api.Backend.ldap2
+
+        if keys[0].lower() == keys[1].lower():
+            raise errors.ValidationError(name='trustdomain_disable',
+                error=_("cannot disable root domain of the trust, use trust-del to delete the trust itself"))
+        try:
+            trust_dn = self.obj.get_dn(keys[0], trust_type=u'ad')
+            trust_entry = ldap.get_entry(trust_dn)
+        except errors.NotFound:
+            raise errors.ValidationError(name=_('AD trust'), error=_('Valid trust name is required'))
+
+        result = dict()
+        result['trust'] = unicode(keys[0])
+        result['domain'] = unicode(keys[1])
+        dn = self.obj.get_dn(keys[0], keys[1], trust_type=u'ad')
+        try:
+            entry = ldap.get_entry(dn)
+            sid = entry['ipanttrusteddomainsid'][0]
+            if not (sid in trust_entry['ipantsidblacklistincoming']):
+                trust_entry['ipantsidblacklistincoming'].append(sid)
+                result['action'] = _('is not allowed')
+                ldap.update_entry(trust_entry)
+            else:
+                result['action'] = _('is already not allowed')
+            result['summary'] = _('Domain %(domain)s of trust %(trust)s %(action)s to access IPA resources')
+        except errors.NotFound:
+            raise errors.ValidationError(name=_('AD trust'), error=_('Valid trust domain name is required'))
+        except errors.EmptyModlist:
+            result['summary'] = _('No changes were done')
+
+        return dict(summary=result['summary'] % result)
+
+api.register(trustdomain_disable)
diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
index cc9e7be..1c4f4a6 100644
--- a/ipaserver/dcerpc.py
+++ b/ipaserver/dcerpc.py
@@ -1002,6 +1002,60 @@ class TrustDomainInstance(object):
             return True
         return False
 
+def fetch_domains(api, mydomain, trustdomain):
+    trust_flags = dict(
+                NETR_TRUST_FLAG_IN_FOREST = 0x00000001,
+                NETR_TRUST_FLAG_OUTBOUND  = 0x00000002,
+                NETR_TRUST_FLAG_TREEROOT  = 0x00000004,
+                NETR_TRUST_FLAG_PRIMARY   = 0x00000008,
+                NETR_TRUST_FLAG_NATIVE    = 0x00000010,
+                NETR_TRUST_FLAG_INBOUND   = 0x00000020,
+                NETR_TRUST_FLAG_MIT_KRB5  = 0x00000080,
+                NETR_TRUST_FLAG_AES       = 0x00000100)
+
+    trust_attributes = dict(
+                NETR_TRUST_ATTRIBUTE_NON_TRANSITIVE     = 0x00000001,
+                NETR_TRUST_ATTRIBUTE_UPLEVEL_ONLY       = 0x00000002,
+                NETR_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN = 0x00000004,
+                NETR_TRUST_ATTRIBUTE_FOREST_TRANSITIVE  = 0x00000008,
+                NETR_TRUST_ATTRIBUTE_CROSS_ORGANIZATION = 0x00000010,
+                NETR_TRUST_ATTRIBUTE_WITHIN_FOREST      = 0x00000020,
+                NETR_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL  = 0x00000040)
+
+    domval = DomainValidator(api)
+    (ccache_name, principal) = domval.kinit_as_http(trustdomain)
+    if ccache_name:
+        with installutils.private_ccache(path=ccache_name):
+            td = TrustDomainInstance('')
+            td.parm.set('workgroup', mydomain)
+            td.creds = credentials.Credentials()
+            td.creds.set_kerberos_state(credentials.MUST_USE_KERBEROS)
+            td.creds.guess(td.parm)
+            netrc = net.Net(creds=td.creds, lp=td.parm)
+            try:
+                result = netrc.finddc(domain=trustdomain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS)
+            except RuntimeError, e:
+                raise assess_dcerpc_exception(message=str(e))
+            if not result:
+                return None
+            td.retrieve(unicode(result.pdc_dns_name))
+
+            netr_pipe = netlogon.netlogon(td.binding, td.parm, td.creds)
+            domains = netr_pipe.netr_DsrEnumerateDomainTrusts(td.binding, 1)
+
+            result = []
+            for t in domains.array:
+                if ((t.trust_attributes & trust_attributes['NETR_TRUST_ATTRIBUTE_WITHIN_FOREST']) and
+                    (t.trust_flags & trust_flags['NETR_TRUST_FLAG_IN_FOREST'])):
+                    res = dict()
+                    res['cn'] = unicode(t.dns_name)
+                    res['ipantflatname'] = unicode(t.netbios_name)
+                    res['ipanttrusteddomainsid'] = unicode(t.sid)
+                    res['ipanttrustpartner'] = res['cn']
+                    result.append(res)
+            return result
+
+
 class TrustDomainJoins(object):
     def __init__(self, api):
         self.api = api
-- 
1.8.3.1

-------------- next part --------------
>From 78d1dfe476bc859d694f152b3cf36a071418d683 Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy at redhat.com>
Date: Thu, 26 Sep 2013 16:44:37 +0200
Subject: [PATCH 3/7] frontend: report arguments errors with better detail

When reporting argument errors, show also a context -- what is processed,
what is the name of the command.
---
 ipalib/frontend.py                    | 3 ++-
 ipatests/test_ipalib/test_frontend.py | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/ipalib/frontend.py b/ipalib/frontend.py
index cac3e3b..f478ef0 100644
--- a/ipalib/frontend.py
+++ b/ipalib/frontend.py
@@ -869,7 +869,8 @@ class Command(HasParam):
         for arg in args():
             if optional and arg.required:
                 raise ValueError(
-                    '%s: required argument after optional' % arg.name
+                    '%s: required argument after optional in %s arguments %s' % (arg.name,
+                    self.name, map(lambda x: x.param_spec, args()))
                 )
             if multivalue:
                 raise ValueError(
diff --git a/ipatests/test_ipalib/test_frontend.py b/ipatests/test_ipalib/test_frontend.py
index 310d7a5..ce943a7 100644
--- a/ipatests/test_ipalib/test_frontend.py
+++ b/ipatests/test_ipalib/test_frontend.py
@@ -280,7 +280,7 @@ class test_Command(ClassChecker):
 
         # Test ValueError, required after optional:
         e = raises(ValueError, self.get_instance, args=('arg1?', 'arg2'))
-        assert str(e) == 'arg2: required argument after optional'
+        assert str(e) == "arg2: required argument after optional in %s arguments ['arg1?', 'arg2']" % (self.get_instance().name)
 
          # Test ValueError, scalar after multivalue:
         e = raises(ValueError, self.get_instance, args=('arg1+', 'arg2'))
-- 
1.8.3.1

-------------- next part --------------
>From fd9ecb2d7a843cec122fb28c9deb1b5764271bde Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy at redhat.com>
Date: Fri, 27 Sep 2013 12:36:59 +0200
Subject: [PATCH 4/7] ipaserver/dcerpc: remove use of trust account
 authentication

Since FreeIPA KDC supports adding MS-PAC to HTTP/ipa.server principal,
it is possible to use it when talking to the trusted AD DC.

Remove support for authenticating as trust account because it should not
really be used other than within Samba.
---
 ipalib/plugins/trust.py |  1 -
 ipaserver/dcerpc.py     | 76 ++++---------------------------------------------
 2 files changed, 6 insertions(+), 71 deletions(-)

diff --git a/ipalib/plugins/trust.py b/ipalib/plugins/trust.py
index de98451..9c3ff62 100644
--- a/ipalib/plugins/trust.py
+++ b/ipalib/plugins/trust.py
@@ -538,7 +538,6 @@ sides.
                                                           None,
                                                           SCOPE_SUBTREE,
                                                           basedn=info_dn,
-                                                          use_http=True,
                                                           quiet=True)
 
                 if info_list:
diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
index 1c4f4a6..2b0da45 100644
--- a/ipaserver/dcerpc.py
+++ b/ipaserver/dcerpc.py
@@ -165,8 +165,7 @@ class DomainValidator(object):
                 base_dn=cn_trust,
                 attrs_list=[self.ATTR_TRUSTED_SID,
                             self.ATTR_FLATNAME,
-                            self.ATTR_TRUST_PARTNER,
-                            self.ATTR_TRUST_AUTHOUT]
+                            self.ATTR_TRUST_PARTNER]
                 )
 
             # We need to use case-insensitive dictionary since we use
@@ -185,18 +184,8 @@ class DomainValidator(object):
                                  "attribute: %s", dn, e)
                     continue
 
-                trust_authout = entry.get(self.ATTR_TRUST_AUTHOUT, [None])[0]
-
-                # We were able to read all Trusted domain attributes but the
-                # secret User is not member of trust admins group
-                if trust_authout is None:
-                    raise errors.ACIError(
-                        info=_('communication with trusted domains is allowed '
-                               'for Trusts administrator group members only'))
-
                 result[trust_partner] = (flatname_normalized,
-                                         security.dom_sid(trusted_sid),
-                                         trust_authout)
+                                         security.dom_sid(trusted_sid))
             return result
         except errors.NotFound, e:
             return []
@@ -462,43 +451,6 @@ class DomainValidator(object):
         ]
         return u'S-%d-%d-%s' % ( sid_rev_num, ia, '-'.join([str(s) for s in subs]),)
 
-    def __extract_trusted_auth(self, info):
-        """
-        Returns in clear trusted domain account credentials
-        """
-        clear = None
-        auth = drsblobs.trustAuthInOutBlob()
-        auth.__ndr_unpack__(info['auth'])
-        auth_array = auth.current.array[0]
-        if auth_array.AuthType == lsa.TRUST_AUTH_TYPE_CLEAR:
-            clear = ''.join(map(chr, auth_array.AuthInfo.password)).decode('utf-16-le')
-        return clear
-
-    def __kinit_as_trusted_account(self, info, password):
-        """
-        Initializes ccache with trusted domain account credentials.
-
-        Applies session code defaults for ccache directory and naming prefix.
-        Session code uses krbccache_prefix+<pid>, we use
-        krbccache_prefix+<TD>+<domain netbios name> so there is no clash
-
-        Returns tuple (ccache name, principal) where (None, None) signifes an error
-        on ccache initialization
-        """
-        ccache_name = os.path.join(krbccache_dir, "%sTD%s" % (krbccache_prefix, info['name'][0]))
-        principal = '%s$@%s' % (self.flatname, info['dns_domain'].upper())
-        (stdout, stderr, returncode) = ipautil.run(['/usr/bin/kinit', principal],
-                                                   env={'KRB5CCNAME':ccache_name},
-                                                   stdin=password, raiseonerr=False)
-        if returncode == 0:
-            return (ccache_name, principal)
-        else:
-            if returncode == 1:
-                raise errors.ACIError(
-                   info=_("KDC for %(domain)s denied trust account for IPA domain with a message '%(message)s'") %
-                        dict(domain=info['dns_domain'],message=stderr.strip()))
-            return (None, None)
-
     def kinit_as_http(self, domain):
         """
         Initializes ccache with http service credentials.
@@ -544,13 +496,10 @@ class DomainValidator(object):
             return (None, None)
 
     def search_in_dc(self, domain, filter, attrs, scope, basedn=None,
-                     use_http=False, quiet=False):
+                     quiet=False):
         """
         Perform LDAP search in a trusted domain `domain' Domain Controller.
         Returns resulting entries or None.
-
-        If use_http is set to True, the search is conducted using
-        HTTP service credentials.
         """
 
         entries = None
@@ -565,7 +514,6 @@ class DomainValidator(object):
         for (host, port) in info['gc']:
             entries = self.__search_in_dc(info, host, port, filter, attrs,
                                           scope, basedn=basedn,
-                                          use_http=use_http,
                                           quiet=quiet)
             if entries:
                 break
@@ -573,22 +521,13 @@ class DomainValidator(object):
         return entries
 
     def __search_in_dc(self, info, host, port, filter, attrs, scope,
-                       basedn=None, use_http=False, quiet=False):
+                       basedn=None, quiet=False):
         """
         Actual search in AD LDAP server, using SASL GSSAPI authentication
         Returns LDAP result or None.
         """
 
-        if use_http:
-            (ccache_name, principal) = self.kinit_as_http(info['dns_domain'])
-        else:
-            auth = self.__extract_trusted_auth(info)
-
-            if not auth:
-                return None
-
-            (ccache_name, principal) = self.__kinit_as_trusted_account(info,
-                                                                       auth)
+        (ccache_name, principal) = self.kinit_as_http(info['dns_domain'])
 
         if ccache_name:
             with installutils.private_ccache(path=ccache_name):
@@ -626,7 +565,6 @@ class DomainValidator(object):
         Returns dictionary with following keys
              name       -- NetBIOS name of the trusted domain
              dns_domain -- DNS name of the trusted domain
-             auth       -- encrypted credentials for trusted domain account
              gc         -- array of tuples (server, port) for Global Catalog
         """
         if domain in self._info:
@@ -653,7 +591,6 @@ class DomainValidator(object):
             self._domains = self.get_trusted_domains()
 
         info = dict()
-        info['auth'] = self._domains[domain][2]
         servers = []
 
         if result:
@@ -1125,7 +1062,7 @@ class TrustDomainJoins(object):
         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
+        as we handle the latter in a separate way
         """
         if self.local_domain.read_only:
             return
@@ -1133,7 +1070,6 @@ class TrustDomainJoins(object):
 	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
-- 
1.8.3.1

-------------- next part --------------
>From e9e5d68ab07886598a666d0c40b33709a4d2d7da Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy at redhat.com>
Date: Fri, 27 Sep 2013 12:39:57 +0200
Subject: [PATCH 5/7] trust: integrate subdomains support into trust-add

---
 ipalib/plugins/trust.py | 23 ++++++++++++--
 ipaserver/dcerpc.py     | 80 +++++++++++++++++++++++++++++--------------------
 2 files changed, 67 insertions(+), 36 deletions(-)

diff --git a/ipalib/plugins/trust.py b/ipalib/plugins/trust.py
index 9c3ff62..15d8fd3 100644
--- a/ipalib/plugins/trust.py
+++ b/ipalib/plugins/trust.py
@@ -345,7 +345,20 @@ sides.
                          base_dn = DN(api.env.container_trusts, api.env.basedn),
                          filter = trust_filter)
 
+
         result['result'] = entry_to_dict(trusts[0][1], **options)
+        if options.get('trust_type') == u'ad':
+            domains = fetch_domains_from_trust(self, self.trustinstance, result['result'], **options)
+            if domains and len(domains) > 0:
+                for dom in domains:
+                    range_name = dom['cn'][0].upper() + '_id_range'
+                    range_type=options.get('range_type', u'ipa-ad-trust')
+                    dom_sid = dom['ipanttrusteddomainsid'][0]
+                    try:
+                        self.add_range(range_name, dom_sid, range_type=range_type)
+                    except errors.DuplicateEntry:
+                        pass
+
         result['result']['trusttype'] = [trust_type_string(result['result']['ipanttrusttype'][0])]
         result['result']['trustdirection'] = [trust_direction_string(result['result']['ipanttrustdirection'][0])]
         result['result']['truststatus'] = [trust_status_string(result['verified'])]
@@ -433,7 +446,7 @@ sides.
         except errors.NotFound:
             old_range = None
 
-        if options.get('type') == u'ad':
+        if options.get('trust_type') == u'ad':
             if range_type and range_type not in (u'ipa-ad-trust',
                                                  u'ipa-ad-trust-posix'):
                 raise errors.ValidationError(
@@ -1170,9 +1183,13 @@ class trustdomain_del(LDAPDelete):
 api.register(trustdomain_del)
 
 
-def fetch_domains_from_trust(self, trustinstance, trust_entry):
+def fetch_domains_from_trust(self, trustinstance, trust_entry, **options):
     trust_name = trust_entry['cn'][0]
-    domains = ipaserver.dcerpc.fetch_domains(self.api, trustinstance.local_flatname, trust_name)
+    creds = None
+    password = options.get('realm_password', None)
+    if password:
+        creds = u"%s%%%s" % (options.get('realm_admin'), password)
+    domains = ipaserver.dcerpc.fetch_domains(self.api, trustinstance.local_flatname, trust_name, creds=creds)
     result = []
     if not domains:
         return None
diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
index 2b0da45..86bb428 100644
--- a/ipaserver/dcerpc.py
+++ b/ipaserver/dcerpc.py
@@ -939,7 +939,8 @@ class TrustDomainInstance(object):
             return True
         return False
 
-def fetch_domains(api, mydomain, trustdomain):
+
+def fetch_domains(api, mydomain, trustdomain, creds=None):
     trust_flags = dict(
                 NETR_TRUST_FLAG_IN_FOREST = 0x00000001,
                 NETR_TRUST_FLAG_OUTBOUND  = 0x00000002,
@@ -959,38 +960,51 @@ def fetch_domains(api, mydomain, trustdomain):
                 NETR_TRUST_ATTRIBUTE_WITHIN_FOREST      = 0x00000020,
                 NETR_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL  = 0x00000040)
 
-    domval = DomainValidator(api)
-    (ccache_name, principal) = domval.kinit_as_http(trustdomain)
-    if ccache_name:
-        with installutils.private_ccache(path=ccache_name):
-            td = TrustDomainInstance('')
-            td.parm.set('workgroup', mydomain)
-            td.creds = credentials.Credentials()
-            td.creds.set_kerberos_state(credentials.MUST_USE_KERBEROS)
-            td.creds.guess(td.parm)
-            netrc = net.Net(creds=td.creds, lp=td.parm)
-            try:
-                result = netrc.finddc(domain=trustdomain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS)
-            except RuntimeError, e:
-                raise assess_dcerpc_exception(message=str(e))
-            if not result:
-                return None
-            td.retrieve(unicode(result.pdc_dns_name))
-
-            netr_pipe = netlogon.netlogon(td.binding, td.parm, td.creds)
-            domains = netr_pipe.netr_DsrEnumerateDomainTrusts(td.binding, 1)
-
-            result = []
-            for t in domains.array:
-                if ((t.trust_attributes & trust_attributes['NETR_TRUST_ATTRIBUTE_WITHIN_FOREST']) and
-                    (t.trust_flags & trust_flags['NETR_TRUST_FLAG_IN_FOREST'])):
-                    res = dict()
-                    res['cn'] = unicode(t.dns_name)
-                    res['ipantflatname'] = unicode(t.netbios_name)
-                    res['ipanttrusteddomainsid'] = unicode(t.sid)
-                    res['ipanttrustpartner'] = res['cn']
-                    result.append(res)
-            return result
+    def communicate(td):
+        td.creds.guess(td.parm)
+        netrc = net.Net(creds=td.creds, lp=td.parm)
+        try:
+            result = netrc.finddc(domain=trustdomain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS)
+        except RuntimeError, e:
+            raise assess_dcerpc_exception(message=str(e))
+        if not result:
+            return None
+        td.retrieve(unicode(result.pdc_dns_name))
+
+        netr_pipe = netlogon.netlogon(td.binding, td.parm, td.creds)
+        domains = netr_pipe.netr_DsrEnumerateDomainTrusts(td.binding, 1)
+        return domains
+
+    domains = None
+    td = TrustDomainInstance('')
+    td.parm.set('workgroup', mydomain)
+    td.creds = credentials.Credentials()
+    if creds is None:
+        domval = DomainValidator(api)
+        (ccache_name, principal) = domval.kinit_as_http(trustdomain)
+        td.creds.set_kerberos_state(credentials.MUST_USE_KERBEROS)
+        if ccache_name:
+            with installutils.private_ccache(path=ccache_name):
+                domains = communicate(td)
+    else:
+        td.creds.set_kerberos_state(credentials.DONT_USE_KERBEROS)
+        td.creds.parse_string(creds)
+        domains = communicate(td)
+
+    if domains is None:
+        return None
+
+    result = []
+    for t in domains.array:
+        if ((t.trust_attributes & trust_attributes['NETR_TRUST_ATTRIBUTE_WITHIN_FOREST']) and
+            (t.trust_flags & trust_flags['NETR_TRUST_FLAG_IN_FOREST'])):
+            res = dict()
+            res['cn'] = unicode(t.dns_name)
+            res['ipantflatname'] = unicode(t.netbios_name)
+            res['ipanttrusteddomainsid'] = unicode(t.sid)
+            res['ipanttrustpartner'] = res['cn']
+            result.append(res)
+    return result
 
 
 class TrustDomainJoins(object):
-- 
1.8.3.1

-------------- next part --------------
>From 38f3dbe18e1a141b964737124d89204ddb86d51b Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy at redhat.com>
Date: Fri, 27 Sep 2013 14:00:22 +0200
Subject: [PATCH 6/7] ipasam: for subdomains pick up defaults for missing
 values

We don't store trust type, attributes, and direction for subdomains
of the existing trust. Since trust is always forest level, these parameters
can be added as defaults when they are missing.
---
 daemons/ipa-sam/ipa_sam.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/daemons/ipa-sam/ipa_sam.c b/daemons/ipa-sam/ipa_sam.c
index a535c0f..59ddcef 100644
--- a/daemons/ipa-sam/ipa_sam.c
+++ b/daemons/ipa-sam/ipa_sam.c
@@ -2026,6 +2026,10 @@ static bool fill_pdb_trusted_domain(TALLOC_CTX *mem_ctx,
 	if (!res) {
 		return false;
 	}
+	if (td->trust_direction == 0) {
+		/* attribute wasn't present, set default value */
+		td->trust_direction = LSA_TRUST_DIRECTION_INBOUND | LSA_TRUST_DIRECTION_OUTBOUND;
+	}
 
 	res = get_uint32_t_from_ldap_msg(ldap_state, entry,
 					 LDAP_ATTRIBUTE_TRUST_ATTRIBUTES,
@@ -2033,6 +2037,10 @@ static bool fill_pdb_trusted_domain(TALLOC_CTX *mem_ctx,
 	if (!res) {
 		return false;
 	}
+	if (td->trust_attributes == 0) {
+		/* attribute wasn't present, set default value */
+		td->trust_attributes = LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE;
+	}
 
 	res = get_uint32_t_from_ldap_msg(ldap_state, entry,
 					 LDAP_ATTRIBUTE_TRUST_TYPE,
@@ -2040,6 +2048,10 @@ static bool fill_pdb_trusted_domain(TALLOC_CTX *mem_ctx,
 	if (!res) {
 		return false;
 	}
+	if (td->trust_type == 0) {
+		/* attribute wasn't present, set default value */
+		td->trust_type = LSA_TRUST_TYPE_UPLEVEL;
+	}
 
 	td->trust_posix_offset = talloc_zero(td, uint32_t);
 	if (td->trust_posix_offset == NULL) {
-- 
1.8.3.1



More information about the Freeipa-devel mailing list