On Tue, 07 Jul 2015, Alexander Bokovoy wrote:
Hi,

attached are patches to introduce one-way trust support and few more to
fix currently outstanding trust-related bugs.

More details are in the commit messages.

For oddjobd-activated helper, if you want to test the one-way trust
setup, you need to put SELinux into permissive. We have bugs for both
Fedora and RHEL to add the policy
(https://bugzilla.redhat.com/show_bug.cgi?id=1238163 for RHEL7), it is
in works.
Attached is a rebase of two patches that modified VERSION as it
conflicted with last minute push that Tomas did.

--
/ Alexander Bokovoy
From 07bd53b528abd39aac6f11f47eec38ff5a73c5e3 Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <aboko...@redhat.com>
Date: Thu, 28 May 2015 11:49:58 +0000
Subject: [PATCH 05/11] trusts: pass AD DC hostname if specified explicitly

Fixes https://bugzilla.redhat.com/show_bug.cgi?id=1222047
---
 API.txt                 |  3 ++-
 VERSION                 |  4 ++--
 ipalib/plugins/trust.py |  9 ++++++++-
 ipaserver/dcerpc.py     | 10 +++++++---
 4 files changed, 19 insertions(+), 7 deletions(-)

diff --git a/API.txt b/API.txt
index 99fa528..a76458b 100644
--- a/API.txt
+++ b/API.txt
@@ -4998,10 +4998,11 @@ output: Output('result', <type 'dict'>, None)
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: ListOfPrimaryKeys('value', None, None)
 command: trust_fetch_domains
-args: 1,4,4
+args: 1,5,4
 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: Str('realm_server?', cli_name='server')
 option: Flag('rights', autofill=True, default=False)
 option: Str('version?', exclude='webui')
 output: Output('count', <type 'int'>, None)
diff --git a/VERSION b/VERSION
index 2d9ad26..bf68be2 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=139
-# Last change: edewata - added ipaVaultPublicKey attribute
+IPA_API_VERSION_MINOR=140
+# Last change: ab - trusts: pass AD DC hostname if specified explicitly
diff --git a/ipalib/plugins/trust.py b/ipalib/plugins/trust.py
index 5b884ca..13ac52d 100644
--- a/ipalib/plugins/trust.py
+++ b/ipalib/plugins/trust.py
@@ -1302,9 +1302,10 @@ def fetch_domains_from_trust(self, trustinstance, 
trust_entry, **options):
             sp.insert(0, trustinstance.remote_domain.info['name'])
         creds = u"{name}%{password}".format(name="\\".join(sp),
                                             password=password)
+    server = options.get('realm_server', None)
     domains = ipaserver.dcerpc.fetch_domains(self.api,
                                              trustinstance.local_flatname,
-                                             trust_name, creds=creds)
+                                             trust_name, creds=creds, 
server=server)
     result = []
     if not domains:
         return result
@@ -1342,6 +1343,12 @@ class trust_fetch_domains(LDAPRetrieve):
     __doc__ = _('Refresh list of the domains associated with the trust')
 
     has_output = output.standard_list_of_entries
+    takes_options = LDAPRetrieve.takes_options + (
+        Str('realm_server?',
+            cli_name='server',
+            label=_('Domain controller for the Active Directory domain 
(optional)'),
+        ),
+    )
 
     def execute(self, *keys, **options):
         if not _bindings_installed:
diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
index 725b2cd..753e10e 100644
--- a/ipaserver/dcerpc.py
+++ b/ipaserver/dcerpc.py
@@ -1046,7 +1046,7 @@ class TrustDomainInstance(object):
         return False
 
 
-def fetch_domains(api, mydomain, trustdomain, creds=None):
+def fetch_domains(api, mydomain, trustdomain, creds=None, server=None):
     trust_flags = dict(
                 NETR_TRUST_FLAG_IN_FOREST = 0x00000001,
                 NETR_TRUST_FLAG_OUTBOUND  = 0x00000002,
@@ -1087,8 +1087,12 @@ def fetch_domains(api, mydomain, trustdomain, 
creds=None):
     cr.set_workstation(domain_validator.flatname)
     netrc = net.Net(creds=cr, lp=td.parm)
     try:
-        result = netrc.finddc(domain=trustdomain,
-                              flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS)
+        if server:
+            result = netrc.finddc(address=server,
+                                  flags=nbt.NBT_SERVER_LDAP | 
nbt.NBT_SERVER_DS)
+        else:
+            result = netrc.finddc(domain=trustdomain,
+                                  flags=nbt.NBT_SERVER_LDAP | 
nbt.NBT_SERVER_DS)
     except RuntimeError, e:
         raise assess_dcerpc_exception(message=str(e))
 
-- 
2.4.3

From 850566818840e5aa37a08ff0cc50d503d78c3b63 Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <aboko...@redhat.com>
Date: Fri, 5 Jun 2015 12:57:02 +0000
Subject: [PATCH 08/11] trusts: add support for one-way trust and switch to it
 by default

One-way trust is the default now, use 'trust add --two-way ' to
force bidirectional trust

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

In case of one-way trust we cannot authenticate using cross-realm TGT
against an AD DC. We have to use trusted domain object from within AD
domain and access to this object is limited to avoid compromising the whole
trust configuration.

Instead, IPA framework can call out to oddjob daemon and ask it to
run the script which can have access to the TDO object. This script
(com.redhat.idm.trust-fetch-domains) is using cifs/ipa.master principal
to retrieve TDO object credentials from IPA LDAP if needed and then
authenticate against AD DCs using the TDO object credentials.

The script pulls the trust topology out of AD DCs and updates IPA LDAP
store. Then IPA framework can pick the updated data from the IPA LDAP
under normal access conditions.

Part of https://fedorahosted.org/freeipa/ticket/4546
---
 API.txt                                            |   3 +-
 VERSION                                            |   4 +-
 freeipa.spec.in                                    |  14 +-
 install/Makefile.am                                |   1 +
 install/configure.ac                               |   1 +
 install/oddjob/Makefile.am                         |  28 +++
 install/oddjob/com.redhat.idm.trust-fetch-domains  | 198 +++++++++++++++++++++
 .../etc/dbus-1/system.d/oddjob-ipa-trust.conf      |  40 +++++
 .../etc/oddjobd.conf.d/oddjobd-ipa-trust.conf      |  21 +++
 ipalib/plugins/trust.py                            | 141 +++++++++++----
 ipaserver/dcerpc.py                                |  44 +++--
 11 files changed, 443 insertions(+), 52 deletions(-)
 create mode 100644 install/oddjob/Makefile.am
 create mode 100755 install/oddjob/com.redhat.idm.trust-fetch-domains
 create mode 100644 install/oddjob/etc/dbus-1/system.d/oddjob-ipa-trust.conf
 create mode 100644 install/oddjob/etc/oddjobd.conf.d/oddjobd-ipa-trust.conf

diff --git a/API.txt b/API.txt
index a76458b..5108bed 100644
--- a/API.txt
+++ b/API.txt
@@ -4971,11 +4971,12 @@ arg: Str('cn', attribute=True, cli_name='name', 
multivalue=False, primary_key=Tr
 option: Str('version?', exclude='webui')
 output: Output('result', None, None)
 command: trust_add
-args: 1,13,3
+args: 1,14,3
 arg: Str('cn', attribute=True, cli_name='realm', multivalue=False, 
primary_key=True, required=True)
 option: Str('addattr*', cli_name='addattr', exclude='webui')
 option: Flag('all', autofill=True, cli_name='all', default=False, 
exclude='webui')
 option: Int('base_id?', cli_name='base_id')
+option: Bool('bidirectional?', cli_name='two_way', default=False)
 option: Int('range_size?', cli_name='range_size')
 option: StrEnum('range_type?', cli_name='range_type', 
values=(u'ipa-ad-trust-posix', u'ipa-ad-trust'))
 option: Flag('raw', autofill=True, cli_name='raw', default=False, 
exclude='webui')
diff --git a/VERSION b/VERSION
index bf68be2..e7f087e 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=140
-# Last change: ab - trusts: pass AD DC hostname if specified explicitly
+IPA_API_VERSION_MINOR=141
+# Last change: ab - trusts: add support for one-way trust and switch to it by 
default
diff --git a/freeipa.spec.in b/freeipa.spec.in
index 8fee33b..415a875 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -205,6 +205,7 @@ Requires: samba >= %{samba_version}
 Requires: samba-winbind
 Requires: libsss_idmap
 Requires: libsss_nss_idmap-python
+Requires: oddjob
 %if (0%{?fedora} >= 22)
 Requires: python-sss
 %endif
@@ -583,6 +584,8 @@ fi
 %post server-trust-ad
 %{_sbindir}/update-alternatives --install 
%{_libdir}/krb5/plugins/libkrb5/winbind_krb5_locator.so \
         winbind_krb5_locator.so /dev/null 90
+/bin/systemctl reload-or-try-restart dbus
+/bin/systemctl reload-or-try-restart oddjobd
 
 %posttrans server-trust-ad
 python2 -c "import sys; from ipaserver.install import installutils; sys.exit(0 
if installutils.is_ipa_configured() else 1);" > /dev/null 2>&1
@@ -595,6 +598,8 @@ fi
 %preun server-trust-ad
 if [ $1 -eq 0 ]; then
     %{_sbindir}/update-alternatives --remove winbind_krb5_locator.so /dev/null
+    /bin/systemctl reload-or-try-restart dbus
+    /bin/systemctl reload-or-try-restart oddjobd
 fi
 
 %endif # ONLY_CLIENT
@@ -832,6 +837,9 @@ fi
 %attr(755,root,root) %{plugin_dir}/libipa_otp_counter.so
 %attr(755,root,root) %{plugin_dir}/libipa_otp_lasttoken.so
 %attr(755,root,root) %{plugin_dir}/libtopology.so
+%attr(755,root,root) %{plugin_dir}/libipa_sidgen.so
+%attr(755,root,root) %{plugin_dir}/libipa_sidgen_task.so
+%attr(755,root,root) %{plugin_dir}/libipa_extdom_extop.so
 %dir %{_localstatedir}/lib/ipa
 %attr(700,root,root) %dir %{_localstatedir}/lib/ipa/backup
 %attr(700,root,root) %dir %{_localstatedir}/lib/ipa/sysrestore
@@ -866,15 +874,15 @@ fi
 
 %files server-trust-ad
 %{_sbindir}/ipa-adtrust-install
-%attr(755,root,root) %{plugin_dir}/libipa_extdom_extop.so
 %{_usr}/share/ipa/smb.conf.empty
 %attr(755,root,root) %{_libdir}/samba/pdb/ipasam.so
-%attr(755,root,root) %{plugin_dir}/libipa_sidgen.so
-%attr(755,root,root) %{plugin_dir}/libipa_sidgen_task.so
 %{_mandir}/man1/ipa-adtrust-install.1.gz
 %{python_sitelib}/ipaserver/dcerpc*
 %{python_sitelib}/ipaserver/install/adtrustinstance*
 %ghost %{_libdir}/krb5/plugins/libkrb5/winbind_krb5_locator.so
+%{_sysconfdir}/dbus-1/system.d/oddjob-ipa-trust.conf
+%{_sysconfdir}/oddjobd.conf.d/oddjobd-ipa-trust.conf
+%%attr(755,root,root) %{_libexecdir}/ipa/com.redhat.idm.trust-fetch-domains
 
 %endif # ONLY_CLIENT
 
diff --git a/install/Makefile.am b/install/Makefile.am
index c07f571..ac52ad3 100644
--- a/install/Makefile.am
+++ b/install/Makefile.am
@@ -17,6 +17,7 @@ SUBDIRS =                     \
         po                     \
         restart_scripts                \
         wsgi                   \
+        oddjob                 \
        $(NULL)
 
 install-exec-local:
diff --git a/install/configure.ac b/install/configure.ac
index 57f4219..cf19758 100644
--- a/install/configure.ac
+++ b/install/configure.ac
@@ -103,6 +103,7 @@ AC_CONFIG_FILES([
     po/Makefile
     restart_scripts/Makefile
     wsgi/Makefile
+    oddjob/Makefile
 ])
 
 AC_OUTPUT
diff --git a/install/oddjob/Makefile.am b/install/oddjob/Makefile.am
new file mode 100644
index 0000000..9dde10c
--- /dev/null
+++ b/install/oddjob/Makefile.am
@@ -0,0 +1,28 @@
+NULL =
+
+oddjobdir = $(libexecdir)/ipa
+oddjobconfdir = $(sysconfdir)/oddjobd.conf.d
+dbusconfdir = $(sysconfdir)/dbus-1/system.d
+
+oddjob_SCRIPTS =                               \
+       com.redhat.idm.trust-fetch-domains      \
+       $(NULL)
+
+dbusconf_DATA =                                                \
+       etc/dbus-1/system.d/oddjob-ipa-trust.conf       \
+       $(NULL)
+
+oddjobconf_DATA =                                      \
+       etc/oddjobd.conf.d/oddjobd-ipa-trust.conf       \
+       $(NULL)
+
+
+#EXTRA_DIST =                          \
+#      $(oddjob_SCRIPTS)               \
+#      $(dbusconf_DATA)                \
+#      $(oddjobconf_DATA)              \
+#      $(NULL)
+
+MAINTAINERCLEANFILES =                 \
+       *~                              \
+       Makefile.in
diff --git a/install/oddjob/com.redhat.idm.trust-fetch-domains 
b/install/oddjob/com.redhat.idm.trust-fetch-domains
new file mode 100755
index 0000000..2571dd0
--- /dev/null
+++ b/install/oddjob/com.redhat.idm.trust-fetch-domains
@@ -0,0 +1,198 @@
+#!/usr/bin/python2
+
+from ipaserver import dcerpc
+from ipaserver.install.installutils import is_ipa_configured, ScriptError
+from ipapython import config, ipautil
+from ipalib import api, errors
+from ipapython.dn import DN
+from ipalib.config import Env
+from ipalib.constants import DEFAULT_CONFIG
+from ipalib.krb_utils import KRB5_CCache
+import sys
+import os, pwd
+import krbV
+import time
+
+# This version is different from the original in ipapyton.ipautil
+# in the fact that it returns a krbV.CCache object.
+def kinit_keytab(principal, keytab, ccache_name, attempts=1):
+    errors_to_retry = {krbV.KRB5KDC_ERR_SVC_UNAVAILABLE,
+                       krbV.KRB5_KDC_UNREACH}
+    for attempt in range(1, attempts + 1):
+        try:
+            krbcontext = krbV.default_context()
+            ktab = krbV.Keytab(name=keytab, context=krbcontext)
+            princ = krbV.Principal(name=principal, context=krbcontext)
+            ccache = krbV.CCache(name=ccache_name, context=krbcontext,
+                                 primary_principal=princ)
+            ccache.init(princ)
+            ccache.init_creds_keytab(keytab=ktab, principal=princ)
+            return ccache
+        except krbV.Krb5Error as e:
+            if e.args[0] not in errors_to_retry:
+                raise
+            if attempt == attempts:
+                raise
+            time.sleep(5)
+
+def retrieve_keytab(api, ccache_name, oneway_keytab_name, oneway_principal):
+    getkeytab_args = ["/usr/sbin/ipa-getkeytab",
+                      "-s", api.env.host,
+                      "-p", oneway_principal,
+                      "-k", oneway_keytab_name,
+                      "-r"]
+    (stdout, stderr, retcode) = ipautil.run(getkeytab_args,
+                                            env={'KRB5CCNAME': ccache_name, 
'LANG': 'C'},
+                                            raiseonerr=False)
+    # Make sure SSSD is able to read the keytab
+    sssd = pwd.getpwnam('sssd')
+    os.chown(oneway_keytab_name, sssd[2], sssd[3])
+
+
+def parse_options():
+    usage = "%prog <trusted domain name>\n"
+    parser = config.IPAOptionParser(usage=usage,
+                                    formatter=config.IPAFormatter())
+
+    parser.add_option("-d", "--debug", action="store_true", dest="debug",
+                      help="Display debugging information")
+
+    options, args = parser.parse_args()
+    safe_options = parser.get_safe_opts(options)
+
+    return safe_options, options, args
+
+
+if not is_ipa_configured():
+    # LSB status code 6: program is not configured
+    raise ScriptError("IPA is not configured " +
+                      "(see man pages of ipa-server-install for help)", 6)
+
+if not os.getegid() == 0:
+    # LSB status code 4: user had insufficient privilege
+    raise ScriptError("You must be root to run ipactl.", 4)
+
+safe_options, options, args = parse_options()
+
+if len(args) != 1:
+    # LSB status code 2: invalid or excess argument(s)
+    raise ScriptError("You must specify trusted domain name", 2)
+
+trusted_domain = unicode(args[0].lower())
+
+env = Env()
+env._bootstrap(context='server', debug=options.debug, log=None)
+env._finalize_core(**dict(DEFAULT_CONFIG))
+
+# Initialize the API with the proper debug level
+api.bootstrap(context='server', debug=env.debug, log=None)
+api.finalize()
+
+# Only import trust plugin after api is initialized or internal imports
+# within the plugin will not work
+from ipalib.plugins import trust
+
+# We have to dance with two different credentials caches:
+# ccache_name         --  for cifs/ipa.master@IPA.REALM to communicate with 
LDAP
+# oneway_ccache_name  --  for IPA$@AD.REALM to communicate with AD DCs
+#
+# ccache_name may not exist, we'll have to initialize it from Samba's keytab
+#
+# oneway_ccache_name may not exist either but to initialize it, we need
+# to check if oneway_keytab_name keytab exists and fetch it first otherwise.
+#
+# to fetch oneway_keytab_name keytab, we need to initialize ccache_name ccache 
first
+# and retrieve our own NetBIOS domain name and use cifs/ipa.master@IPA.REALM to
+# retrieve the keys to oneway_keytab_name.
+
+keytab_name = '/etc/samba/samba.keytab'
+oneway_keytab_name = '/var/lib/sss/keytabs/' + trusted_domain + '.keytab'
+
+principal = str('cifs/' + api.env.host)
+
+oneway_ccache_name = '/var/run/ipa/krb5cc_oddjob_trusts_fetch'
+ccache_name = '/var/run/ipa/krb5cc_oddjob_trusts'
+
+# Standard sequence:
+# - check if ccache exists
+#   - if not, initialize it from Samba's keytab
+# - check if ccache contains valid TGT
+#   - if not, initialize it from Samba's keytab
+# - refer the correct ccache object for further use
+#
+if not os.path.isfile(ccache_name):
+    ccache = kinit_keytab(principal, keytab_name, ccache_name)
+
+ccache_check = KRB5_CCache(ccache_name)
+if not ccache_check.credential_is_valid(principal):
+    ccache = kinit_keytab(principal, keytab_name, ccache_name)
+else:
+    ccache = ccache_check.ccache
+
+old_ccache = os.environ.get('KRB5CCNAME')
+api.Backend.ldap2.connect(ccache)
+
+own_trust_dn = DN(('cn', api.env.domain),('cn','ad'), ('cn', 'etc'), 
api.env.basedn)
+own_trust_entry = api.Backend.ldap2.get_entry(own_trust_dn, ['ipantflatname'])
+own_trust_flatname = own_trust_entry['ipantflatname'][0].upper()
+
+oneway_principal = str('%s$@%s' % (own_trust_flatname, trusted_domain.upper()))
+
+# If keytab does not exist, retrieve it
+if not os.path.isfile(oneway_keytab_name):
+    retrieve_keytab(api, ccache_name, oneway_keytab_name, oneway_principal)
+
+oneway_ccache = None
+try:
+    # The keytab may have stale key material (from older trust-add run)
+    if not os.path.isfile(oneway_ccache_name):
+        oneway_ccache = kinit_keytab(oneway_principal, oneway_keytab_name, 
oneway_ccache_name)
+except krbV.Krb5Error as e:
+    # If there was failure on using keytab, assume it is stale and retrieve 
again
+    retrieve_keytab(api, ccache_name, oneway_keytab_name, oneway_principal)
+
+if oneway_ccache:
+    # There wasn existing ccache, validate its content
+    oneway_ccache_check = KRB5_CCache(oneway_ccache_name)
+    if not oneway_ccache_check.credential_is_valid(oneway_principal):
+        # If credentials were invalid, obtain them again
+        oneway_ccache = kinit_keytab(oneway_principal, oneway_keytab_name, 
oneway_ccache_name)
+    else:
+        oneway_ccache = oneway_ccache_check.ccache
+else:
+    oneway_ccache = kinit_keytab(oneway_principal, oneway_keytab_name, 
oneway_ccache_name)
+
+# We are done: we have ccache with TDO credentials and can fetch domains
+ipa_domain = api.env.domain
+os.environ['KRB5CCNAME'] = oneway_ccache_name
+domains = dcerpc.fetch_domains(api, ipa_domain, trusted_domain, creds=True)
+
+if domains:
+    # trust range must exist by the time fetch_domains_from_trust is called
+    range_name = unicode(trusted_domain.upper() + '_id_range')
+    old_range = api.Command.idrange_show(range_name, raw=True)['result']
+    idrange_type = old_range['iparangetype'][0]
+
+    result = []
+    for dom in domains:
+        dom['trust_type'] = u'ad'
+        try:
+            name = dom['cn']
+            del dom['cn']
+
+            res = api.Command.trustdomain_add(trusted_domain, name, **dom)
+            result.append(res['result'])
+
+            if idrange_type != u'ipa-ad-trust-posix':
+                range_name = name.upper() + '_id_range'
+                dom['range_type'] = u'ipa-ad-trust'
+                trust.add_range(range_name, dom['ipanttrusteddomainsid'],
+                                trusted_domain, name, **dom)
+        except errors.DuplicateEntry:
+            # Ignore updating duplicate entries
+            pass
+
+if old_ccache:
+   os.environ['KRB5CCNAME'] = old_ccache
+
+sys.exit(0)
diff --git a/install/oddjob/etc/dbus-1/system.d/oddjob-ipa-trust.conf 
b/install/oddjob/etc/dbus-1/system.d/oddjob-ipa-trust.conf
new file mode 100644
index 0000000..2e4c136
--- /dev/null
+++ b/install/oddjob/etc/dbus-1/system.d/oddjob-ipa-trust.conf
@@ -0,0 +1,40 @@
+<!DOCTYPE busconfig PUBLIC
+ "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd";>
+
+<busconfig>
+ <!-- Only root can own (provide) the com.redhat.idm.trust service
+       on the system bus. -->
+  <policy user="root">
+    <allow own="com.redhat.idm.trust"/>
+    <allow send_destination="com.redhat.idm.trust"
+           send_path="/"
+           send_interface="com.redhat.idm.trust"
+           send_member="fetch_domains"/>
+  </policy>
+
+  <!-- Allow anyone to call the introspection methods of the "/" object
+       provided by the com.redhat.idm.trust service. -->
+  <policy context="default">
+    <allow send_destination="com.redhat.idm.trust"
+           send_path="/"
+           send_interface="org.freedesktop.DBus.Introspectable"
+           send_member="Introspect"/>
+    <allow send_destination="com.redhat.idm.trust"
+           send_path="/"
+           send_interface="org.freedesktop.DBus.Properties"
+           send_member="GetAll"/>
+    <allow send_destination="com.redhat.idm.trust"
+           send_path="/"
+           send_interface="org.freedesktop.DBus.Properties"
+           send_member="Get"/>
+  </policy>
+
+  <policy user="apache">
+    <allow send_destination="com.redhat.idm.trust"
+           send_path="/"
+           send_interface="com.redhat.idm.trust"
+           send_member="fetch_domains"/>
+  </policy>
+
+</busconfig>
diff --git a/install/oddjob/etc/oddjobd.conf.d/oddjobd-ipa-trust.conf 
b/install/oddjob/etc/oddjobd.conf.d/oddjobd-ipa-trust.conf
new file mode 100644
index 0000000..17817de
--- /dev/null
+++ b/install/oddjob/etc/oddjobd.conf.d/oddjobd-ipa-trust.conf
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<oddjobconfig>
+  <service name="com.redhat.idm.trust">
+    <allow user="root"/>
+    <allow user="apache"/>
+    <object name="/">
+      <interface name="org.freedesktop.DBus.Introspectable">
+        <allow min_uid="0" max_uid="0"/>
+        <!-- <method name="Introspect"/> -->
+      </interface>
+      <interface name="com.redhat.idm.trust">
+        <method name="fetch_domains">
+          <helper exec="/usr/libexec/ipa/com.redhat.idm.trust-fetch-domains"
+                 arguments="1"
+                  argument_passing_method="cmdline"
+                 prepend_user_name="no"/>
+        </method>
+      </interface>
+    </object>
+  </service>
+</oddjobconfig>
diff --git a/ipalib/plugins/trust.py b/ipalib/plugins/trust.py
index 13ac52d..9fbaf25 100644
--- a/ipalib/plugins/trust.py
+++ b/ipalib/plugins/trust.py
@@ -22,6 +22,7 @@ from ipalib.plugable import Registry
 from ipalib.plugins.baseldap import *
 from ipalib.plugins.dns import dns_container_exists
 from ipapython.ipautil import realm_to_suffix
+from ipapython.ipa_log_manager import root_logger
 from ipalib import api, Str, StrEnum, Password, Bool, _, ngettext
 from ipalib import Command
 from ipalib import errors
@@ -43,6 +44,8 @@ except Exception, e:
 if api.env.in_server and api.env.context in ['lite', 'server']:
     try:
         import ipaserver.dcerpc #pylint: disable=F0401
+        from ipaserver.dcerpc import TRUST_ONEWAY, TRUST_BIDIRECTIONAL
+        import dbus, dbus.mainloop.glib
         _bindings_installed = True
     except ImportError:
         _bindings_installed = False
@@ -161,6 +164,8 @@ _trust_type_option = StrEnum('trust_type',
 
 DEFAULT_RANGE_SIZE = 200000
 
+DBUS_IFACE_TRUST = 'com.redhat.idm.trust'
+
 def trust_type_string(level):
     """
     Returns a string representing a type of the trust. The original field is 
an enum:
@@ -191,7 +196,7 @@ def make_trust_dn(env, trust_type, dn):
         return DN(dn, container_dn)
     return dn
 
-def add_range(self, range_name, dom_sid, *keys, **options):
+def add_range(myapi, range_name, dom_sid, *keys, **options):
     """
     First, we try to derive the parameters of the ID range based on the
     information contained in the Active Directory.
@@ -224,7 +229,7 @@ def add_range(self, range_name, dom_sid, *keys, **options):
                   + basedn
 
         # Get the domain validator
-        domain_validator = ipaserver.dcerpc.DomainValidator(self.api)
+        domain_validator = ipaserver.dcerpc.DomainValidator(myapi)
         if not domain_validator.is_configured():
             raise errors.NotFound(
                 reason=_('Cannot search in trusted domains without own '
@@ -251,10 +256,10 @@ def add_range(self, range_name, dom_sid, *keys, 
**options):
 
         if not info_list:
             # We were unable to gain UNIX specific info from the AD
-            self.log.debug("Unable to gain POSIX info from the AD")
+            root_logger.debug("Unable to gain POSIX info from the AD")
         else:
             if all(attr in info for attr in required_msSFU_attrs):
-                self.log.debug("Able to gain POSIX info from the AD")
+                root_logger.debug("Able to gain POSIX info from the AD")
                 range_type = u'ipa-ad-trust-posix'
 
                 max_uid = info.get('msSFU30MaxUidNumber')
@@ -288,16 +293,43 @@ def add_range(self, range_name, dom_sid, *keys, 
**options):
         ) * DEFAULT_RANGE_SIZE
 
     # Finally, add new ID range
-    self.api.Command['idrange_add'](range_name,
-                                    ipabaseid=base_id,
-                                    ipaidrangesize=range_size,
-                                    ipabaserid=0,
-                                    iparangetype=range_type,
-                                    ipanttrusteddomainsid=dom_sid)
+    myapi.Command['idrange_add'](range_name,
+                                 ipabaseid=base_id,
+                                 ipaidrangesize=range_size,
+                                 ipabaserid=0,
+                                 iparangetype=range_type,
+                                 ipanttrusteddomainsid=dom_sid)
 
     # Return the values that were generated inside this function
     return range_type, range_size, base_id
 
+def fetch_trusted_domains_over_dbus(myapi, log, forest_name):
+    if not _bindings_installed:
+        return
+    # Calling oddjobd-activated service via DBus has some quirks:
+    # - Oddjobd registers multiple canonical names on the same address
+    # - python-dbus only follows name owner changes when mainloop is in use
+    # See https://fedorahosted.org/oddjob/ticket/2 for details
+    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+    try:
+        _ret = 0
+        _stdout = ''
+        _stderr = ''
+        bus = dbus.SystemBus()
+        intf = bus.get_object(DBUS_IFACE_TRUST,"/", 
follow_name_owner_changes=True)
+        fetch_domains_method = intf.get_dbus_method('fetch_domains', 
dbus_interface=DBUS_IFACE_TRUST)
+        (_ret, _stdout, _stderr) = fetch_domains_method(forest_name)
+    except dbus.DBusException, e:
+        log.error('Failed to call %(iface)s.fetch_domains helper.'
+                       'DBus exception is %(exc)s.' % 
dict(iface=DBUS_IFACE_TRUST, exc=str(e)))
+        if _ret != 0:
+            log.error('Helper was called for forest %(forest)s, return code is 
%(ret)d' % dict(forest=forest_name, ret=_ret))
+            log.error('Standard output from the helper:\n%s---\n' % (_stdout))
+            log.error('Error output from the helper:\n%s--\n' % (_stderr))
+        raise errors.ServerCommandError(server=myapi.env.host,
+                                        error=_('Fetching domains from trusted 
forest failed. '
+                                                'See details in the 
error_log'))
+    return
 
 @register()
 class trust(LDAPObject):
@@ -463,6 +495,12 @@ sides.
                  .format(vals=', '.join(range_types.keys())))),
             values=tuple(range_types.keys()),
         ),
+        Bool('bidirectional?',
+             label=_('Two-way trust'),
+             cli_name='two_way',
+             doc=(_('Establish bi-directional trust. By default trust is 
inbound one-way only.')),
+             default=False,
+        ),
     )
 
     msg_summary = _('Added Active Directory trust for realm "%(value)s"')
@@ -478,7 +516,7 @@ sides.
             # Store the created range type, since for POSIX trusts no
             # ranges for the subdomains should be added, POSIX attributes
             # provide a global mapping across all subdomains
-            (created_range_type, _, _) = add_range(self, range_name, dom_sid,
+            (created_range_type, _, _) = add_range(self.api, range_name, 
dom_sid,
                                                    *keys, **options)
         else:
             created_range_type = old_range['result']['iparangetype'][0]
@@ -486,19 +524,35 @@ sides.
         trust_filter = "cn=%s" % result['value']
         ldap = self.obj.backend
         (trusts, truncated) = ldap.find_entries(
-                         base_dn=DN(api.env.container_trusts, api.env.basedn),
+                         base_dn=DN(self.api.env.container_trusts, 
self.api.env.basedn),
                          filter=trust_filter)
 
         result['result'] = entry_to_dict(trusts[0], **options)
 
         # Fetch topology of the trust forest -- we need always to do it
         # for AD trusts, regardless of the type of idranges associated with it
-        # Note that fetch_domains_from_trust will add needed ranges for
+        # Note that add_new_domains_from_trust will add needed ranges for
         # the algorithmic ID mapping case.
         if (options.get('trust_type') == u'ad' and
             options.get('trust_secret') is None):
-            domains = fetch_domains_from_trust(self, self.trustinstance,
+            if options.get('bidirectional') == True:
+                # Bidirectional trust allows us to use cross-realm TGT, so we 
can
+                # run the call under original user's credentials
+                res = fetch_domains_from_trust(self.api, self.trustinstance,
                                                result['result'], **options)
+                domains = add_new_domains_from_trust(self.api, 
self.trustinstance,
+                                                     result['result'], res, 
**options)
+            else:
+                # One-way trust is more complex. We don't have cross-realm TGT
+                # and cannot use IPA principals to authenticate against AD.
+                # Instead, we have to use our trusted domain object's (TDO)
+                # account in AD. Access to the credentials is limited and IPA
+                # framework cannot access it directly.  Instead, we call out to
+                # oddjobd-activated higher privilege process that will use TDO
+                # object credentials to authenticate to AD with Kerberos,
+                # run DCE RPC calls to do discovery and will call
+                # add_new_domains_from_trust() on its own.
+                fetch_trusted_domains_over_dbus(self.api, self.log, 
result['value'])
 
         # Format the output into human-readable values
         result['result']['trusttype'] = [trust_type_string(
@@ -570,7 +624,7 @@ sides.
         # If domain name and realm does not match, IPA server is not be able
         # to establish trust with Active Directory.
 
-        realm_not_matching_domain = (api.env.domain.upper() != api.env.realm)
+        realm_not_matching_domain = (self.api.env.domain.upper() != 
self.api.env.realm)
 
         if options['trust_type'] == u'ad' and realm_not_matching_domain:
             raise errors.ValidationError(
@@ -627,7 +681,7 @@ sides.
         range_type = options.get('range_type')
 
         try:
-            old_range = api.Command['idrange_show'](range_name, raw=True)
+            old_range = self.api.Command['idrange_show'](range_name, raw=True)
         except errors.NotFound:
             old_range = None
 
@@ -699,6 +753,9 @@ sides.
         except errors.NotFound:
             dn = None
 
+        trust_type = TRUST_ONEWAY
+        if options.get('bidirectional', False):
+            trust_type = TRUST_BIDIRECTIONAL
         # 1. Full access to the remote domain. Use admin credentials and
         # generate random trustdom password to do work on both sides
         if full_join:
@@ -707,14 +764,15 @@ sides.
                     keys[-1],
                     self.realm_server,
                     self.realm_admin,
-                    self.realm_passwd
+                    self.realm_passwd,
+                    trust_type
                 )
             except errors.NotFound:
                 error_message=_("Unable to resolve domain controller for '%s' 
domain. ") % (keys[-1])
                 instructions=[]
                 if dns_container_exists(self.obj.backend):
                     try:
-                        dns_zone = api.Command.dnszone_show(keys[-1])['result']
+                        dns_zone = 
self.api.Command.dnszone_show(keys[-1])['result']
                         if ('idnsforwardpolicy' in dns_zone) and 
dns_zone['idnsforwardpolicy'][0] == u'only':
                             instructions.append(_("Forward policy is defined 
for it in IPA DNS, "
                                                    "perhaps forwarder points 
to incorrect host?"))
@@ -755,7 +813,8 @@ sides.
             result = self.trustinstance.join_ad_ipa_half(
                 keys[-1],
                 self.realm_server,
-                options['trust_secret']
+                options['trust_secret'],
+                trust_type
             )
             ret = dict(
                 value=pkey_to_value(
@@ -940,7 +999,7 @@ class trustconfig(LDAPObject):
                     group,
                     ['posixgroup'],
                     [''],
-                    DN(api.env.container_group, api.env.basedn))
+                    DN(self.api.env.container_group, self.api.env.basedn))
             except errors.NotFound:
                 self.api.Object['group'].handle_not_found(group)
             else:
@@ -1066,11 +1125,11 @@ class adtrust_is_enabled(Command):
         ldap = self.api.Backend.ldap2
         adtrust_dn = DN(
             ('cn', 'ADTRUST'),
-            ('cn', api.env.host),
+            ('cn', self.api.env.host),
             ('cn', 'masters'),
             ('cn', 'ipa'),
             ('cn', 'etc'),
-            api.env.basedn
+            self.api.env.basedn
         )
 
         try:
@@ -1281,7 +1340,7 @@ class trustdomain_del(LDAPDelete):
                 raise errors.ValidationError(name='domain',
                     error=_("cannot delete root domain of the trust, use 
trust-del to delete the trust itself"))
             try:
-                res = api.Command.trustdomain_enable(keys[0], domain)
+                res = self.api.Command.trustdomain_enable(keys[0], domain)
             except errors.AlreadyActive:
                 pass
         result = super(trustdomain_del, self).execute(*keys, **options)
@@ -1291,7 +1350,7 @@ class trustdomain_del(LDAPDelete):
 
 
 
-def fetch_domains_from_trust(self, trustinstance, trust_entry, **options):
+def fetch_domains_from_trust(myapi, trustinstance, trust_entry, **options):
     trust_name = trust_entry['cn'][0]
     creds = None
     password = options.get('realm_passwd', None)
@@ -1303,16 +1362,20 @@ def fetch_domains_from_trust(self, trustinstance, 
trust_entry, **options):
         creds = u"{name}%{password}".format(name="\\".join(sp),
                                             password=password)
     server = options.get('realm_server', None)
-    domains = ipaserver.dcerpc.fetch_domains(self.api,
+    domains = ipaserver.dcerpc.fetch_domains(myapi,
                                              trustinstance.local_flatname,
                                              trust_name, creds=creds, 
server=server)
+    return domains
+
+def add_new_domains_from_trust(myapi, trustinstance, trust_entry, domains, 
**options):
     result = []
     if not domains:
         return result
 
-    # trust range must exist by the time fetch_domains_from_trust is called
+    trust_name = trust_entry['cn'][0]
+    # trust range must exist by the time add_new_domains_from_trust is called
     range_name = trust_name.upper() + '_id_range'
-    old_range = api.Command.idrange_show(range_name, raw=True)['result']
+    old_range = myapi.Command.idrange_show(range_name, raw=True)['result']
     idrange_type = old_range['iparangetype'][0]
 
     for dom in domains:
@@ -1325,13 +1388,13 @@ def fetch_domains_from_trust(self, trustinstance, 
trust_entry, **options):
             if 'raw' in options:
                 dom['raw'] = options['raw']
 
-            res = self.api.Command.trustdomain_add(trust_name, name, **dom)
+            res = myapi.Command.trustdomain_add(trust_name, name, **dom)
             result.append(res['result'])
 
             if idrange_type != u'ipa-ad-trust-posix':
                 range_name = name.upper() + '_id_range'
                 dom['range_type'] = u'ipa-ad-trust'
-                add_range(self, range_name, dom['ipanttrusteddomainsid'],
+                add_range(myapi, range_name, dom['ipanttrusteddomainsid'],
                           trust_name, name, **dom)
         except errors.DuplicateEntry:
             # Ignore updating duplicate entries
@@ -1362,6 +1425,17 @@ class trust_fetch_domains(LDAPRetrieve):
             )
         trust = self.api.Command.trust_show(keys[0], raw=True)['result']
 
+        result = dict()
+        result['result'] = []
+        result['count'] = 0
+        result['truncated'] = False
+
+        # For one-way trust fetch over DBus. we don't get the list in this 
case.
+        if trust['ipanttrustdirection'] & TRUST_BIDIRECTIONAL != 
TRUST_BIDIRECTIONAL:
+            fetch_trusted_domains_over_dbus(self.api, self.log, keys[0])
+            result['summary'] = unicode(_('List of trust domains successfully 
refreshed. Use trustdomain-find command to list them.'))
+            return result
+
         trustinstance = ipaserver.dcerpc.TrustDomainJoins(self.api)
         if not trustinstance.configured:
             raise errors.NotFound(
@@ -1372,8 +1446,8 @@ class trust_fetch_domains(LDAPRetrieve):
                     'on the IPA server first'
                 )
             )
-        domains = fetch_domains_from_trust(self, trustinstance, trust)
-        result = dict()
+        res = fetch_domains_from_trust(self.api, trustinstance, trust, 
**options)
+        domains = add_new_domains_from_trust(self.api, trustinstance, trust, 
res, **options)
 
         if len(domains) > 0:
             result['summary'] = unicode(_('List of trust domains successfully 
refreshed'))
@@ -1382,7 +1456,6 @@ class trust_fetch_domains(LDAPRetrieve):
 
         result['result'] = domains
         result['count'] = len(domains)
-        result['truncated'] = False
         return result
 
 
@@ -1413,7 +1486,7 @@ class trustdomain_enable(LDAPQuery):
                 trust_entry['ipantsidblacklistincoming'].remove(sid)
                 ldap.update_entry(trust_entry)
                 # Force MS-PAC cache re-initialization on KDC side
-                domval = ipaserver.dcerpc.DomainValidator(api)
+                domval = ipaserver.dcerpc.DomainValidator(self.api)
                 (ccache_name, principal) = domval.kinit_as_http(keys[0])
             else:
                 raise errors.AlreadyActive()
@@ -1453,7 +1526,7 @@ class trustdomain_disable(LDAPQuery):
                 trust_entry['ipantsidblacklistincoming'].append(sid)
                 ldap.update_entry(trust_entry)
                 # Force MS-PAC cache re-initialization on KDC side
-                domval = ipaserver.dcerpc.DomainValidator(api)
+                domval = ipaserver.dcerpc.DomainValidator(self.api)
                 (ccache_name, principal) = domval.kinit_as_http(keys[0])
             else:
                 raise errors.AlreadyInactive()
diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
index 753e10e..b11233d 100644
--- a/ipaserver/dcerpc.py
+++ b/ipaserver/dcerpc.py
@@ -66,6 +66,10 @@ The code in this module relies heavily on samba4-python 
package
 and Samba4 python bindings.
 """)
 
+# Both constants can be used as masks against trust direction
+# because bi-directional has two lower bits set.
+TRUST_ONEWAY        = 1
+TRUST_BIDIRECTIONAL = 3
 
 def is_sid_valid(sid):
     try:
@@ -949,7 +953,7 @@ class TrustDomainInstance(object):
             # We can ignore the error here -- setting up name suffix routes 
may fail
             pass
 
-    def establish_trust(self, another_domain, trustdom_secret):
+    def establish_trust(self, another_domain, trustdom_secret, 
trust_type='bidirectional'):
         """
         Establishes trust between our and another domain
         Input: another_domain -- instance of TrustDomainInstance, initialized 
with #retrieve call
@@ -967,7 +971,9 @@ class TrustDomainInstance(object):
         info.domain_name.string = another_domain.info['dns_domain']
         info.netbios_name.string = another_domain.info['name']
         info.sid = security.dom_sid(another_domain.info['sid'])
-        info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | 
lsa.LSA_TRUST_DIRECTION_OUTBOUND
+        info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND
+        if trust_type == TRUST_BIDIRECTIONAL:
+            info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
         info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
         info.trust_attributes = 0
 
@@ -1005,7 +1011,8 @@ class TrustDomainInstance(object):
             pass
 
         try:
-            info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
+            info = self._pipe.QueryTrustedDomainInfo(trustdom_handle, 
lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
+            info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
             self._pipe.SetInformationTrustedDomain(trustdom_handle, 
lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX, info)
         except RuntimeError, e:
             root_logger.error('unable to set trust to transitive: %s' % 
(str(e)))
@@ -1014,10 +1021,10 @@ class TrustDomainInstance(object):
             self.update_ftinfo(another_domain)
 
     def verify_trust(self, another_domain):
-        def retrieve_netlogon_info_2(domain, function_code, data):
+        def retrieve_netlogon_info_2(logon_server, domain, function_code, 
data):
             try:
                 netr_pipe = netlogon.netlogon(domain.binding, domain.parm, 
domain.creds)
-                result = netr_pipe.netr_LogonControl2Ex(logon_server=None,
+                result = 
netr_pipe.netr_LogonControl2Ex(logon_server=logon_server,
                                            function_code=function_code,
                                            level=2,
                                            data=data
@@ -1026,7 +1033,7 @@ class TrustDomainInstance(object):
             except RuntimeError, (num, message):
                 raise assess_dcerpc_exception(num=num, message=message)
 
-        result = retrieve_netlogon_info_2(self,
+        result = retrieve_netlogon_info_2(None, self,
                                           netlogon.NETLOGON_CONTROL_TC_VERIFY,
                                           another_domain.info['dns_domain'])
         if (result and (result.flags and 
netlogon.NETLOGON_VERIFY_STATUS_RETURNED)):
@@ -1098,6 +1105,7 @@ def fetch_domains(api, mydomain, trustdomain, creds=None, 
server=None):
 
     td.info['dc'] = unicode(result.pdc_dns_name)
     if creds is None:
+        # Attempt to authenticate as HTTP/ipa.master and use cross-forest trust
         domval = DomainValidator(api)
         (ccache_name, principal) = domval.kinit_as_http(trustdomain)
         td.creds = credentials.Credentials()
@@ -1107,7 +1115,15 @@ def fetch_domains(api, mydomain, trustdomain, 
creds=None, server=None):
                 td.creds.guess(td.parm)
                 td.creds.set_workstation(domain_validator.flatname)
                 domains = communicate(td)
+    elif type(creds) is bool:
+        # Rely on existing Kerberos credentials in the environment
+        td.creds = credentials.Credentials()
+        td.creds.set_kerberos_state(credentials.MUST_USE_KERBEROS)
+        td.creds.guess(td.parm)
+        td.creds.set_workstation(domain_validator.flatname)
+        domains = communicate(td)
     else:
+        # Assume we've got credentials as a string user%password
         td.creds = credentials.Credentials()
         td.creds.set_kerberos_state(credentials.DONT_USE_KERBEROS)
         td.creds.guess(td.parm)
@@ -1220,7 +1236,7 @@ class TrustDomainJoins(object):
             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):
+    def join_ad_full_credentials(self, realm, realm_server, realm_admin, 
realm_passwd, trust_type):
         if not self.configured:
             return None
 
@@ -1238,13 +1254,17 @@ 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)
+            self.remote_domain.establish_trust(self.local_domain, 
trustdom_pass, trust_type)
+            self.local_domain.establish_trust(self.remote_domain, 
trustdom_pass, trust_type)
+            # if trust is inbound, we don't need to verify it because AD DC 
will respond
+            # with WERR_NO_SUCH_DOMAIN -- in only does verification for 
outbound trusts.
+            result = True
+            if trust_type == TRUST_BIDIRECTIONAL:
+                result = self.remote_domain.verify_trust(self.local_domain)
             return dict(local=self.local_domain, remote=self.remote_domain, 
verified=result)
         return None
 
-    def join_ad_ipa_half(self, realm, realm_server, trustdom_passwd):
+    def join_ad_ipa_half(self, realm, realm_server, trustdom_passwd, 
trust_type):
         if not self.configured:
             return None
 
@@ -1254,5 +1274,5 @@ class TrustDomainJoins(object):
         if self.remote_domain.info['dns_domain'] != 
self.remote_domain.info['dns_forest']:
             raise 
errors.NotAForestRootError(forest=self.remote_domain.info['dns_forest'], 
domain=self.remote_domain.info['dns_domain'])
 
-        self.local_domain.establish_trust(self.remote_domain, trustdom_passwd)
+        self.local_domain.establish_trust(self.remote_domain, trustdom_passwd, 
trust_type)
         return dict(local=self.local_domain, remote=self.remote_domain, 
verified=False)
-- 
2.4.3

Attachment: signature.asc
Description: PGP signature

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to