From 3fbba22833f599a6576f5ee03da0c2653c7137a7 Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy@redhat.com>
Date: Tue, 23 Feb 2016 13:38:36 +0200
Subject: [PATCH 1/2] WIP: support UPNs for trusted domain users

---
 daemons/ipa-kdb/ipa_kdb_mspac.c | 26 +++++++++++++++++---------
 ipalib/errors.py                | 16 ++++++++++++++++
 ipalib/plugins/trust.py         | 16 ++++++++++++----
 ipaserver/dcerpc.py             | 41 ++++++++++++++++++++++++++++++++++-------
 4 files changed, 79 insertions(+), 20 deletions(-)

diff --git a/daemons/ipa-kdb/ipa_kdb_mspac.c b/daemons/ipa-kdb/ipa_kdb_mspac.c
index 45721f0..6001ec2 100644
--- a/daemons/ipa-kdb/ipa_kdb_mspac.c
+++ b/daemons/ipa-kdb/ipa_kdb_mspac.c
@@ -2460,22 +2460,24 @@ krb5_error_code ipadb_mspac_get_trusted_domains(struct ipadb_context *ipactx)
             goto done;
         }
 
+        t[n].flat_name = NULL;
         ret = ipadb_ldap_attr_to_str(lc, le, "ipaNTFlatName",
                                      &t[n].flat_name);
-        if (ret) {
+        if (ret && ret != ENOENT) {
             ret = EINVAL;
             goto done;
         }
 
+        t[n].domain_sid = NULL;
         ret = ipadb_ldap_attr_to_str(lc, le, "ipaNTTrustedDomainSID",
                                      &t[n].domain_sid);
-        if (ret) {
+        if (ret && ret != ENOENT) {
             ret = EINVAL;
             goto done;
         }
 
         ret = string_to_sid(t[n].domain_sid, &t[n].domsid);
-        if (ret) {
+        if (ret && t[n].domain_sid != NULL) {
             ret = EINVAL;
             goto done;
         }
@@ -2806,6 +2808,7 @@ krb5_error_code ipadb_is_princ_from_trusted_realm(krb5_context kcontext,
 	struct ipadb_context *ipactx;
 	int i, j, length;
 	const char *name;
+	bool result = false;
 
 	if (test_realm == NULL || test_realm[0] == '\0') {
 		return KRB5_KDB_NOENTRY;
@@ -2827,12 +2830,17 @@ krb5_error_code ipadb_is_princ_from_trusted_realm(krb5_context kcontext,
 
 	/* Iterate through list of trusts and check if input realm belongs to any of the trust */
 	for(i = 0 ; i < ipactx->mspac->num_trusts ; i++) {
-		if ((strncasecmp(test_realm,
-				 ipactx->mspac->trusts[i].domain_name,
-				 size) == 0) ||
-		    (strncasecmp(test_realm,
-				 ipactx->mspac->trusts[i].flat_name,
-				 size) == 0)) {
+		result = strncasecmp(test_realm,
+				     ipactx->mspac->trusts[i].domain_name,
+				     size) == 0;
+
+                if (!result && (ipactx->mspac->trusts[i].flat_name != NULL)) {
+			result = strncasecmp(test_realm,
+					     ipactx->mspac->trusts[i].flat_name,
+					     size) == 0;
+		}
+
+		if (result) {
 			/* return the realm if caller supplied a place for it */
 			if (trusted_realm != NULL) {
 				name = (ipactx->mspac->trusts[i].parent_name != NULL) ?
diff --git a/ipalib/errors.py b/ipalib/errors.py
index 52b7700..650fa5c 100644
--- a/ipalib/errors.py
+++ b/ipalib/errors.py
@@ -1372,6 +1372,22 @@ class InvalidDomainLevelError(ExecutionError):
     errno = 4032
     format = _('%(reason)s')
 
+class AlwaysActive(ExecutionError):
+    """
+    **4032** Raised when an entry is made inactive but it should always be active
+
+    For example:
+
+    >>> raise AlwaysActive()
+    Traceback (most recent call last):
+      ...
+    AlwaysActive: This entry cannot be disabled or enabled, it is always enabled
+
+    """
+
+    errno = 4009
+    format = _('This entry cannot be disabled or enabled, it is always enabled')
+
 class BuiltinError(ExecutionError):
     """
     **4100** Base class for builtin execution errors (*4100 - 4199*).
diff --git a/ipalib/plugins/trust.py b/ipalib/plugins/trust.py
index 19d8b53..31365d8 100644
--- a/ipalib/plugins/trust.py
+++ b/ipalib/plugins/trust.py
@@ -1459,7 +1459,9 @@ class trustdomain_find(LDAPSearch):
         trust_dn = self.obj.get_dn(args[0], trust_type=u'ad')
         trust_entry = ldap.get_entry(trust_dn)
         for entry in entries:
-            sid = entry['ipanttrusteddomainsid'][0]
+            sid = entry.get('ipanttrusteddomainsid', [None])[0]
+            if sid is None:
+                continue
 
             blacklist = trust_entry.get('ipantsidblacklistincoming')
             if blacklist is None:
@@ -1560,7 +1562,7 @@ def add_new_domains_from_trust(myapi, trustinstance, trust_entry, domains, **opt
             res = myapi.Command.trustdomain_add(trust_name, name, **dom)
             result.append(res['result'])
 
-            if idrange_type != u'ipa-ad-trust-posix':
+            if idrange_type != u'ipa-ad-trust-posix' and dom.get('ipanttrusteddomainsid', False):
                 range_name = name.upper() + '_id_range'
                 dom['range_type'] = u'ipa-ad-trust'
                 add_range(myapi, trustinstance, range_name, dom['ipanttrusteddomainsid'],
@@ -1645,7 +1647,10 @@ class trustdomain_enable(LDAPQuery):
         dn = self.obj.get_dn(keys[0], keys[1], trust_type=u'ad')
         try:
             entry = ldap.get_entry(dn)
-            sid = entry['ipanttrusteddomainsid'][0]
+            sid = entry.get('ipanttrusteddomainsid', [None])[0]
+            # UPN is always active
+            if sid is None:
+                raise errors.AlreadyActive()
             if sid in trust_entry['ipantsidblacklistincoming']:
                 trust_entry['ipantsidblacklistincoming'].remove(sid)
                 ldap.update_entry(trust_entry)
@@ -1686,7 +1691,10 @@ class trustdomain_disable(LDAPQuery):
         dn = self.obj.get_dn(keys[0], keys[1], trust_type=u'ad')
         try:
             entry = ldap.get_entry(dn)
-            sid = entry['ipanttrusteddomainsid'][0]
+            sid = entry.get('ipanttrusteddomainsid', [None])[0]
+            # UPN is always active
+            if sid is None:
+                raise errors.AlwaysActive()
             if not (sid in trust_entry['ipantsidblacklistincoming']):
                 trust_entry['ipantsidblacklistincoming'].append(sid)
                 ldap.update_entry(trust_entry)
diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
index 6b62b5f..8ceaa0e 100644
--- a/ipaserver/dcerpc.py
+++ b/ipaserver/dcerpc.py
@@ -1189,7 +1189,8 @@ def fetch_domains(api, mydomain, trustdomain, creds=None, server=None):
     def communicate(td):
         td.init_lsa_pipe(td.info['dc'])
         netr_pipe = netlogon.netlogon(td.binding, td.parm, td.creds)
-        domains = netr_pipe.netr_DsrEnumerateDomainTrusts(td.binding, 1)
+        # domains = netr_pipe.netr_DsrEnumerateDomainTrusts(td.binding, 1)
+        domains = netr_pipe.netr_DsRGetForestTrustInformation(td.info['dc'], '', 0)
         return domains
 
     domains = None
@@ -1217,6 +1218,7 @@ def fetch_domains(api, mydomain, trustdomain, creds=None, server=None):
         raise assess_dcerpc_exception(message=str(e))
 
     td.info['dc'] = unicode(result.pdc_dns_name)
+    td.info['name'] = unicode(result.dns_domain)
     if type(creds) is bool:
         # Rely on existing Kerberos credentials in the environment
         td.creds = credentials.Credentials()
@@ -1247,15 +1249,40 @@ def fetch_domains(api, mydomain, trustdomain, creds=None, server=None):
         return None
 
     result = []
-    for t in domains.array:
-        if (not (t.trust_flags & trust_flags['NETR_TRUST_FLAG_PRIMARY']) and
-            (t.trust_flags & trust_flags['NETR_TRUST_FLAG_IN_FOREST'])):
+    # netr_DsRGetForestTrustInformation returns two types of entries:
+    # domain information  -- name, NetBIOS name, SID of the domain
+    # top level name info -- a name suffix associated with the forest
+    # We should ignore forest root name/name suffix as it is already part
+    # of trust information for IPA purposes and only add what's inside the forest
+    for t in domains.entries:
+        if t.type == lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
+            tname = unicode(t.forest_trust_data.dns_domain_name.string)
+            if tname == trustdomain:
+                continue
             res = dict()
-            res['cn'] = unicode(t.dns_name)
-            res['ipantflatname'] = unicode(t.netbios_name)
-            res['ipanttrusteddomainsid'] = unicode(t.sid)
+            res['cn'] = tname
+            res['ipantflatname'] = unicode(t.forest_trust_data.netbios_domain_name.string)
+            res['ipanttrusteddomainsid'] = unicode(t.forest_trust_data.domain_sid)
             res['ipanttrustpartner'] = res['cn']
             result.append(res)
+        elif t.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
+            tname = unicode(t.forest_trust_data.string)
+            if tname == trustdomain:
+                continue
+            res = dict()
+            res['cn'] = tname
+            res['ipanttrustpartner'] = res['cn']
+            result.append(res)
+
+#    for t in domains.array:
+#        if (not (t.trust_flags & trust_flags['NETR_TRUST_FLAG_PRIMARY']) 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
 
 
-- 
2.5.0

