From cab82ebda706bbad258e218e90fe3d70ebf5ea21 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Wed, 21 Jan 2015 14:44:44 +0100 Subject: [PATCH] samba-tool: add 'domain trust *' commands Available subcommands: create - Create a domain or forest trust. delete - Delete a domain trust. list - List domain trusts. namespaces - Manage forest trust namespaces. show - Show trusted domain details. validate - Validate a domain trust. Signed-off-by: Stefan Metzmacher Reviewed-by: Andrew Bartlett --- docs-xml/manpages/samba-tool.8.xml | 35 + python/samba/netcmd/domain.py | 2206 ++++++++++++++++++++++++++++++++++++ 2 files changed, 2241 insertions(+) diff --git a/docs-xml/manpages/samba-tool.8.xml b/docs-xml/manpages/samba-tool.8.xml index 9cb304b994c..b3235f9ac24 100644 --- a/docs-xml/manpages/samba-tool.8.xml +++ b/docs-xml/manpages/samba-tool.8.xml @@ -253,6 +253,41 @@ Promote an existing domain member or NT4 PDC to an AD DC. + + domain trust + Domain and forest trust management. + + + + domain trust create <replaceable>DOMAIN</replaceable> <replaceable>options</replaceable> [options] + Create a domain or forest trust. + + + + domain trust delete <replaceable>DOMAIN</replaceable> <replaceable>options</replaceable> [options] + Delete a domain trust. + + + + domain trust list <replaceable>options</replaceable> [options] + List domain trusts. + + + + domain trust namespaces [<replaceable>DOMAIN</replaceable>] <replaceable>options</replaceable> [options] + Manage forest trust namespaces. + + + + domain trust show <replaceable>DOMAIN</replaceable> <replaceable>options</replaceable> [options] + Show trusted domain details. + + + + domain trust validate <replaceable>DOMAIN</replaceable> <replaceable>options</replaceable> [options] + Validate a domain trust. + + drs Manage Directory Replication Services (DRS). diff --git a/python/samba/netcmd/domain.py b/python/samba/netcmd/domain.py index f8e32f00b58..f0710f281f0 100644 --- a/python/samba/netcmd/domain.py +++ b/python/samba/netcmd/domain.py @@ -27,15 +27,23 @@ import ldb import string import os import sys +import ctypes +import random import tempfile import logging +from getpass import getpass from samba.net import Net, LIBNET_JOIN_AUTOMATIC import samba.ntacls from samba.join import join_RODC, join_DC, join_subdomain from samba.auth import system_session from samba.samdb import SamDB +from samba.ndr import ndr_unpack, ndr_pack, ndr_print from samba.dcerpc import drsuapi +from samba.dcerpc import drsblobs +from samba.dcerpc import lsa +from samba.dcerpc import netlogon from samba.dcerpc import security +from samba.dcerpc import nbt from samba.dcerpc.samr import DOMAIN_PASSWORD_COMPLEX, DOMAIN_PASSWORD_STORE_CLEARTEXT from samba.netcmd import ( Command, @@ -1460,6 +1468,2203 @@ class cmd_domain_samba3upgrade(cmd_domain_classicupgrade): hidden = True +class LocalDCCredentialsOptions(options.CredentialsOptions): + def __init__(self, parser): + options.CredentialsOptions.__init__(self, parser, special_name="local-dc") + +class DomainTrustCommand(Command): + """List domain trusts.""" + + def __init__(self): + Command.__init__(self) + self.local_lp = None + + self.local_server = None + self.local_binding_string = None + self.local_creds = None + + self.remote_server = None + self.remote_binding_string = None + self.remote_creds = None + + WERR_OK = 0x00000000 + WERR_INVALID_FUNCTION = 0x00000001 + WERR_NERR_ACFNOTLOADED = 0x000008B3 + + NT_STATUS_NOT_FOUND = 0xC0000225 + NT_STATUS_OBJECT_NAME_NOT_FOUND = 0xC0000034 + NT_STATUS_INVALID_PARAMETER = 0xC000000D + NT_STATUS_INVALID_INFO_CLASS = 0xC0000003 + NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE = 0xC002002E + + def _uint32(self, v): + return ctypes.c_uint32(v).value + + def check_runtime_error(self, runtime, val): + if runtime is None: + return False + + err32 = self._uint32(runtime[0]) + if err32 == val: + return True + + return False + + class LocalRuntimeError(CommandError): + def __init__(exception_self, self, runtime, message): + err32 = self._uint32(runtime[0]) + errstr = runtime[1] + msg = "LOCAL_DC[%s]: %s - ERROR(0x%08X) - %s" % ( + self.local_server, message, err32, errstr) + CommandError.__init__(exception_self, msg) + + class RemoteRuntimeError(CommandError): + def __init__(exception_self, self, runtime, message): + err32 = self._uint32(runtime[0]) + errstr = runtime[1] + msg = "REMOTE_DC[%s]: %s - ERROR(0x%08X) - %s" % ( + self.remote_server, message, err32, errstr) + CommandError.__init__(exception_self, msg) + + class LocalLdbError(CommandError): + def __init__(exception_self, self, ldb_error, message): + errval = ldb_error[0] + errstr = ldb_error[1] + msg = "LOCAL_DC[%s]: %s - ERROR(%d) - %s" % ( + self.local_server, message, errval, errstr) + CommandError.__init__(exception_self, msg) + + def setup_local_server(self, sambaopts, localdcopts): + if self.local_server is not None: + return self.local_server + + lp = sambaopts.get_loadparm() + + local_server = localdcopts.ipaddress + if local_server is None: + server_role = lp.server_role() + if server_role != "ROLE_ACTIVE_DIRECTORY_DC": + raise CommandError("Invalid server_role %s" % (server_role)) + local_server = lp.get('netbios name') + local_transport = "ncalrpc" + local_binding_options = "" + local_binding_options += ",auth_type=ncalrpc_as_system" + local_ldap_url = None + local_creds = None + else: + local_transport = "ncacn_np" + local_binding_options = "" + local_ldap_url = "ldap://%s" % local_server + local_creds = localdcopts.get_credentials(lp) + + self.local_lp = lp + + self.local_server = local_server + self.local_binding_string = "%s:%s[%s]" % (local_transport, local_server, local_binding_options) + self.local_ldap_url = local_ldap_url + self.local_creds = local_creds + return self.local_server + + def new_local_lsa_connection(self): + return lsa.lsarpc(self.local_binding_string, self.local_lp, self.local_creds) + + def new_local_netlogon_connection(self): + return netlogon.netlogon(self.local_binding_string, self.local_lp, self.local_creds) + + def new_local_ldap_connection(self): + return SamDB(url=self.local_ldap_url, + session_info=system_session(), + credentials=self.local_creds, + lp=self.local_lp) + + def setup_remote_server(self, credopts, domain, + require_pdc=True, + require_writable=True): + + if require_pdc: + assert require_writable + + if self.remote_server is not None: + return self.remote_server + + self.remote_server = "__unknown__remote_server__.%s" % domain + assert self.local_server is not None + + remote_creds = credopts.get_credentials(self.local_lp) + remote_server = credopts.ipaddress + remote_binding_options = "" + + # TODO: we should also support NT4 domains + # we could use local_netlogon.netr_DsRGetDCNameEx2() with the remote domain name + # and delegate NBT or CLDAP to the local netlogon server + try: + remote_net = Net(remote_creds, self.local_lp, server=remote_server) + remote_flags = nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS + if require_writable: + remote_flags |= nbt.NBT_SERVER_WRITABLE + if require_pdc: + remote_flags |= nbt.NBT_SERVER_PDC + remote_info = remote_net.finddc(flags=remote_flags, domain=domain, address=remote_server) + except Exception: + raise CommandError("Failed to find a writeable DC for domain '%s'" % domain) + flag_map = { + nbt.NBT_SERVER_PDC: "PDC", + nbt.NBT_SERVER_GC: "GC", + nbt.NBT_SERVER_LDAP: "LDAP", + nbt.NBT_SERVER_DS: "DS", + nbt.NBT_SERVER_KDC: "KDC", + nbt.NBT_SERVER_TIMESERV: "TIMESERV", + nbt.NBT_SERVER_CLOSEST: "CLOSEST", + nbt.NBT_SERVER_WRITABLE: "WRITABLE", + nbt.NBT_SERVER_GOOD_TIMESERV: "GOOD_TIMESERV", + nbt.NBT_SERVER_NDNC: "NDNC", + nbt.NBT_SERVER_SELECT_SECRET_DOMAIN_6: "SELECT_SECRET_DOMAIN_6", + nbt.NBT_SERVER_FULL_SECRET_DOMAIN_6: "FULL_SECRET_DOMAIN_6", + nbt.NBT_SERVER_ADS_WEB_SERVICE: "ADS_WEB_SERVICE", + nbt.NBT_SERVER_DS_8: "DS_8", + nbt.NBT_SERVER_HAS_DNS_NAME: "HAS_DNS_NAME", + nbt.NBT_SERVER_IS_DEFAULT_NC: "IS_DEFAULT_NC", + nbt.NBT_SERVER_FOREST_ROOT: "FOREST_ROOT", + } + server_type_string = self.generic_bitmap_to_string(flag_map, + remote_info.server_type, names_only=True) + self.outf.write("RemoteDC Netbios[%s] DNS[%s] ServerType[%s]\n" % ( + remote_info.pdc_name, + remote_info.pdc_dns_name, + server_type_string)) + + self.remote_server = remote_info.pdc_dns_name + self.remote_binding_string="ncacn_np:%s[%s]" % (self.remote_server, remote_binding_options) + self.remote_creds = remote_creds + return self.remote_server + + def new_remote_lsa_connection(self): + return lsa.lsarpc(self.remote_binding_string, self.local_lp, self.remote_creds) + + def new_remote_netlogon_connection(self): + return netlogon.netlogon(self.remote_binding_string, self.local_lp, self.remote_creds) + + def get_lsa_info(self, conn, policy_access): + objectAttr = lsa.ObjectAttribute() + objectAttr.sec_qos = lsa.QosInfo() + + policy = conn.OpenPolicy2(''.decode('utf-8'), + objectAttr, policy_access) + + info = conn.QueryInfoPolicy2(policy, lsa.LSA_POLICY_INFO_DNS) + + return (policy, info) + + def get_netlogon_dc_info(self, conn, server): + info = conn.netr_DsRGetDCNameEx2(server, + None, 0, None, None, None, + netlogon.DS_RETURN_DNS_NAME) + return info + + def netr_DomainTrust_to_name(self, t): + if t.trust_type == lsa.LSA_TRUST_TYPE_DOWNLEVEL: + return t.netbios_name + + return t.dns_name + + def netr_DomainTrust_to_type(self, a, t): + primary = None + primary_parent = None + for _t in a: + if _t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY: + primary = _t + if not _t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT: + primary_parent = a[_t.parent_index] + break + + if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST: + if t is primary_parent: + return "Parent" + + if t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT: + return "TreeRoot" + + parent = a[t.parent_index] + if parent is primary: + return "Child" + + return "Shortcut" + + if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE: + return "Forest" + + return "External" + + def netr_DomainTrust_to_transitive(self, t): + if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST: + return "Yes" + + if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE: + return "No" + + if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE: + return "Yes" + + return "No" + + def netr_DomainTrust_to_direction(self, t): + if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND and \ + t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND: + return "BOTH" + + if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND: + return "INCOMING" + + if t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND: + return "OUTGOING" + + return "INVALID" + + def generic_enum_to_string(self, e_dict, v, names_only=False): + try: + w = e_dict[v] + except KeyError: + v32 = self._uint32(v) + w = "__unknown__%08X__" % v32 + + r = "0x%x (%s)" % (v, w) + return r; + + def generic_bitmap_to_string(self, b_dict, v, names_only=False): + + s = [] + + c = v + for b in sorted(b_dict.keys()): + if not (c & b): + continue + c &= ~b + s += [b_dict[b]] + + if c != 0: + c32 = self._uint32(c) + s += ["__unknown_%08X__" % c32] + + w = ",".join(s) + if names_only: + return w + r = "0x%x (%s)" % (v, w) + return r; + + def trustType_string(self, v): + types = { + lsa.LSA_TRUST_TYPE_DOWNLEVEL : "DOWNLEVEL", + lsa.LSA_TRUST_TYPE_UPLEVEL : "UPLEVEL", + lsa.LSA_TRUST_TYPE_MIT : "MIT", + lsa.LSA_TRUST_TYPE_DCE : "DCE", + } + return self.generic_enum_to_string(types, v) + + def trustDirection_string(self, v): + directions = { + lsa.LSA_TRUST_DIRECTION_INBOUND | + lsa.LSA_TRUST_DIRECTION_OUTBOUND : "BOTH", + lsa.LSA_TRUST_DIRECTION_INBOUND : "INBOUND", + lsa.LSA_TRUST_DIRECTION_OUTBOUND : "OUTBOUND", + } + return self.generic_enum_to_string(directions, v) + + def trustAttributes_string(self, v): + attributes = { + lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE : "NON_TRANSITIVE", + lsa.LSA_TRUST_ATTRIBUTE_UPLEVEL_ONLY : "UPLEVEL_ONLY", + lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN : "QUARANTINED_DOMAIN", + lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE : "FOREST_TRANSITIVE", + lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION : "CROSS_ORGANIZATION", + lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST : "WITHIN_FOREST", + lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL : "TREAT_AS_EXTERNAL", + lsa.LSA_TRUST_ATTRIBUTE_USES_RC4_ENCRYPTION : "USES_RC4_ENCRYPTION", + } + return self.generic_bitmap_to_string(attributes, v) + + def kerb_EncTypes_string(self, v): + enctypes = { + security.KERB_ENCTYPE_DES_CBC_CRC : "DES_CBC_CRC", + security.KERB_ENCTYPE_DES_CBC_MD5 : "DES_CBC_MD5", + security.KERB_ENCTYPE_RC4_HMAC_MD5 : "RC4_HMAC_MD5", + security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96 : "AES128_CTS_HMAC_SHA1_96", + security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96 : "AES256_CTS_HMAC_SHA1_96", + security.KERB_ENCTYPE_FAST_SUPPORTED : "FAST_SUPPORTED", + security.KERB_ENCTYPE_COMPOUND_IDENTITY_SUPPORTED : "COMPOUND_IDENTITY_SUPPORTED", + security.KERB_ENCTYPE_CLAIMS_SUPPORTED : "CLAIMS_SUPPORTED", + security.KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED : "RESOURCE_SID_COMPRESSION_DISABLED", + } + return self.generic_bitmap_to_string(enctypes, v) + + def entry_tln_status(self, e_flags, ): + if e_flags == 0: + return "Status[Enabled]" + + flags = { + lsa.LSA_TLN_DISABLED_NEW : "Disabled-New", + lsa.LSA_TLN_DISABLED_ADMIN : "Disabled", + lsa.LSA_TLN_DISABLED_CONFLICT : "Disabled-Conflicting", + } + return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True) + + def entry_dom_status(self, e_flags): + if e_flags == 0: + return "Status[Enabled]" + + flags = { + lsa.LSA_SID_DISABLED_ADMIN : "Disabled-SID", + lsa.LSA_SID_DISABLED_CONFLICT : "Disabled-SID-Conflicting", + lsa.LSA_NB_DISABLED_ADMIN : "Disabled-NB", + lsa.LSA_NB_DISABLED_CONFLICT : "Disabled-NB-Conflicting", + } + return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True) + + def write_forest_trust_info(self, fti, tln=None, collisions=None): + if tln is not None: + tln_string = " TDO[%s]" % tln + else: + tln_string = "" + + self.outf.write("Namespaces[%d]%s:\n" % ( + len(fti.entries), tln_string)) + + for i in xrange(0, len(fti.entries)): + e = fti.entries[i] + + flags = e.flags + collision_string = "" + + if collisions is not None: + for c in collisions.entries: + if c.index != i: + continue + flags = c.flags + collision_string = " Collision[%s]" % (c.name.string) + + d = e.forest_trust_data + if e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME: + self.outf.write("TLN: %-32s DNS[*.%s]%s\n" % ( + self.entry_tln_status(flags), + d.string, collision_string)) + elif e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX: + self.outf.write("TLN_EX: %-29s DNS[*.%s]\n" % ( + "", d.string)) + elif e.type == lsa.LSA_FOREST_TRUST_DOMAIN_INFO: + self.outf.write("DOM: %-32s DNS[%s] Netbios[%s] SID[%s]%s\n" % ( + self.entry_dom_status(flags), + d.dns_domain_name.string, + d.netbios_domain_name.string, + d.domain_sid, collision_string)) + return + +class cmd_domain_trust_list(DomainTrustCommand): + """List domain trusts.""" + + synopsis = "%prog [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "versionopts": options.VersionOptions, + "localdcopts": LocalDCCredentialsOptions, + } + + takes_options = [ + ] + + def run(self, sambaopts=None, versionopts=None, localdcopts=None): + + local_server = self.setup_local_server(sambaopts, localdcopts) + try: + local_netlogon = self.new_local_netlogon_connection() + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "failed to connect netlogon server") + + try: + local_netlogon_trusts = local_netlogon.netr_DsrEnumerateDomainTrusts(local_server, + netlogon.NETR_TRUST_FLAG_IN_FOREST | + netlogon.NETR_TRUST_FLAG_OUTBOUND | + netlogon.NETR_TRUST_FLAG_INBOUND) + except RuntimeError as error: + if self.check_runtime_error(error, self.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE): + # TODO: we could implement a fallback to lsa.EnumTrustDom() + raise CommandError("LOCAL_DC[%s]: netr_DsrEnumerateDomainTrusts not supported." % ( + self.local_server)) + raise self.LocalRuntimeError(self, error, "netr_DsrEnumerateDomainTrusts failed") + + a = local_netlogon_trusts.array + for t in a: + if t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY: + continue + self.outf.write("%-14s %-15s %-19s %s\n" % ( + "Type[%s]" % self.netr_DomainTrust_to_type(a, t), + "Transitive[%s]" % self.netr_DomainTrust_to_transitive(t), + "Direction[%s]" % self.netr_DomainTrust_to_direction(t), + "Name[%s]" % self.netr_DomainTrust_to_name(t))) + return + +class cmd_domain_trust_show(DomainTrustCommand): + """Show trusted domain details.""" + + synopsis = "%prog NAME [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "versionopts": options.VersionOptions, + "localdcopts": LocalDCCredentialsOptions, + } + + takes_options = [ + ] + + takes_args = ["domain"] + + def run(self, domain, sambaopts=None, versionopts=None, localdcopts=None): + + local_server = self.setup_local_server(sambaopts, localdcopts) + try: + local_lsa = self.new_local_lsa_connection() + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "failed to connect lsa server") + + try: + local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION + (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access) + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS") + + self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % ( + local_lsa_info.name.string, + local_lsa_info.dns_domain.string, + local_lsa_info.sid)) + + lsaString = lsa.String() + lsaString.string = domain + try: + local_tdo_full = local_lsa.QueryTrustedDomainInfoByName(local_policy, + lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO) + local_tdo_info = local_tdo_full.info_ex + local_tdo_posix = local_tdo_full.posix_offset + except RuntimeError as error: + if self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND): + raise CommandError("trusted domain object does not exist for domain [%s]" % domain) + + raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(FULL_INFO) failed") + + try: + local_tdo_enctypes = local_lsa.QueryTrustedDomainInfoByName(local_policy, + lsaString, lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES) + except RuntimeError as error: + if self.check_runtime_error(error, self.NT_STATUS_INVALID_PARAMETER): + error = None + if self.check_runtime_error(error, self.NT_STATUS_INVALID_INFO_CLASS): + error = None + + if error is not None: + raise self.LocalRuntimeError(self, error, + "QueryTrustedDomainInfoByName(SUPPORTED_ENCRYPTION_TYPES) failed") + + local_tdo_enctypes = lsa.TrustDomainInfoSupportedEncTypes() + local_tdo_enctypes.enc_types = 0 + + try: + local_tdo_forest = None + if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE: + local_tdo_forest = local_lsa.lsaRQueryForestTrustInformation(local_policy, + lsaString, lsa.LSA_FOREST_TRUST_DOMAIN_INFO) + except RuntimeError as error: + if self.check_runtime_error(error, self.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE): + error = None + if self.check_runtime_error(error, self.NT_STATUS_NOT_FOUND): + error = None + if error is not None: + raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation failed") + + local_tdo_forest = lsa.ForestTrustInformation() + local_tdo_forest.count = 0 + local_tdo_forest.entries = [] + + self.outf.write("TrusteDomain:\n\n"); + self.outf.write("NetbiosName: %s\n" % local_tdo_info.netbios_name.string) + if local_tdo_info.netbios_name.string != local_tdo_info.domain_name.string: + self.outf.write("DnsName: %s\n" % local_tdo_info.domain_name.string) + self.outf.write("SID: %s\n" % local_tdo_info.sid) + self.outf.write("Type: %s\n" % self.trustType_string(local_tdo_info.trust_type)) + self.outf.write("Direction: %s\n" % self.trustDirection_string(local_tdo_info.trust_direction)) + self.outf.write("Attributes: %s\n" % self.trustAttributes_string(local_tdo_info.trust_attributes)) + posix_offset_u32 = ctypes.c_uint32(local_tdo_posix.posix_offset).value + posix_offset_i32 = ctypes.c_int32(local_tdo_posix.posix_offset).value + self.outf.write("PosixOffset: 0x%08X (%d)\n" % (posix_offset_u32, posix_offset_i32)) + self.outf.write("kerb_EncTypes: %s\n" % self.kerb_EncTypes_string(local_tdo_enctypes.enc_types)) + + if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE: + self.write_forest_trust_info(local_tdo_forest, + tln=local_tdo_info.domain_name.string) + + return + +class cmd_domain_trust_create(DomainTrustCommand): + """Create a domain or forest trust.""" + + synopsis = "%prog DOMAIN [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "versionopts": options.VersionOptions, + "credopts": options.CredentialsOptions, + "localdcopts": LocalDCCredentialsOptions, + } + + takes_options = [ + Option("--type", type="choice", metavar="TYPE", + choices=["external", "forest"], + help="The type of the trust: 'external' or 'forest'.", + dest='trust_type', + default="external"), + Option("--direction", type="choice", metavar="DIRECTION", + choices=["incoming", "outgoing", "both"], + help="The trust direction: 'incoming', 'outgoing' or 'both'.", + dest='trust_direction', + default="both"), + Option("--create-location", type="choice", metavar="LOCATION", + choices=["local", "both"], + help="Where to create the trusted domain object: 'local' or 'both'.", + dest='create_location', + default="both"), + Option("--cross-organisation", action="store_true", + help="The related domains does not belong to the same organisation.", + dest='cross_organisation', + default=False), + Option("--quarantined", type="choice", metavar="yes|no", + choices=["yes", "no", None], + help="Special SID filtering rules are applied to the trust. " + "With --type=external the default is yes. " + "With --type=forest the default is no.", + dest='quarantined_arg', + default=None), + Option("--not-transitive", action="store_true", + help="The forest trust is not transitive.", + dest='not_transitive', + default=False), + Option("--treat-as-external", action="store_true", + help="The treat the forest trust as external.", + dest='treat_as_external', + default=False), + Option("--no-aes-keys", action="store_false", + help="The trust uses aes kerberos keys.", + dest='use_aes_keys', + default=True), + Option("--skip-validation", action="store_false", + help="Skip validation of the trust.", + dest='validate', + default=True), + ] + + takes_args = ["domain"] + + def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None, + trust_type=None, trust_direction=None, create_location=None, + cross_organisation=False, quarantined_arg=None, + not_transitive=False, treat_as_external=False, + use_aes_keys=False, validate=True): + + lsaString = lsa.String() + + quarantined = False + if quarantined_arg is None: + if trust_type == 'external': + quarantined = True + elif quarantined_arg == 'yes': + quarantined = True + + if trust_type != 'forest': + if not_transitive: + raise CommandError("--not-transitive requires --type=forest") + if treat_as_external: + raise CommandError("--treat-as-external requires --type=forest") + + enc_types = None + if use_aes_keys: + enc_types = lsa.TrustDomainInfoSupportedEncTypes() + enc_types.enc_types = security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96 + enc_types.enc_types |= security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96 + + local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION + local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN + local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET + + local_trust_info = lsa.TrustDomainInfoInfoEx() + local_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL + local_trust_info.trust_direction = 0 + if trust_direction == "both": + local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND + local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND + elif trust_direction == "incoming": + local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND + elif trust_direction == "outgoing": + local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND + local_trust_info.trust_attributes = 0 + if cross_organisation: + local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION + if quarantined: + local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN + if trust_type == "forest": + local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE + if not_transitive: + local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE + if treat_as_external: + local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL + + def get_password(name): + password = None + while True: + if password is not None and password is not '': + return password + password = getpass("New %s Password: " % name) + passwordverify = getpass("Retype %s Password: " % name) + if not password == passwordverify: + password = None + self.outf.write("Sorry, passwords do not match.\n") + + def string_to_array(string): + blob = [0] * len(string) + + for i in range(len(string)): + blob[i] = ord(string[i]) + + return blob + + incoming_secret = None + outgoing_secret = None + remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION + if create_location == "local": + if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND: + incoming_password = get_password("Incoming Trust") + incoming_secret = string_to_array(incoming_password.encode('utf-16-le')) + if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND: + outgoing_password = get_password("Outgoing Trust") + outgoing_secret = string_to_array(outgoing_password.encode('utf-16-le')) + + remote_trust_info = None + else: + # We use 240 random bytes. + # Windows uses 28 or 240 random bytes. I guess it's + # based on the trust type external vs. forest. + # + # The initial trust password can be up to 512 bytes + # while the versioned passwords used for periodic updates + # can only be up to 498 bytes, as netr_ServerPasswordSet2() + # needs to pass the NL_PASSWORD_VERSION structure within the + # 512 bytes and a 2 bytes confounder is required. + # + def random_trust_secret(length, use_aes_keys=True): + secret = [0] * length + + pw1 = samba.generate_random_password(length/2, length/2) + if not use_aes_keys: + # With arcfour-hmac-md5 we have to use valid utf16 + # in order to generate the correct pre-auth key + # based on a utf8 password. + # + # We can remove this once our client libraries + # support using the correct NTHASH. + return string_to_array(pw1.encode('utf-16-le')) + + # We mix characters from generate_random_password + # with random numbers from random.randint() + for i in range(len(secret)): + if len(pw1) > i: + secret[i] = ord(pw1[i]) + else: + secret[i] = random.randint(0, 255) + + return secret + + if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND: + incoming_secret = random_trust_secret(240, use_aes_keys=use_aes_keys) + if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND: + outgoing_secret = random_trust_secret(240, use_aes_keys=use_aes_keys) + + remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN + remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET + + remote_trust_info = lsa.TrustDomainInfoInfoEx() + remote_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL + remote_trust_info.trust_direction = 0 + if trust_direction == "both": + remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND + remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND + elif trust_direction == "incoming": + remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND + elif trust_direction == "outgoing": + remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND + remote_trust_info.trust_attributes = 0 + if cross_organisation: + remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION + if quarantined: + remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN + if trust_type == "forest": + remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE + if not_transitive: + remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE + if treat_as_external: + remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL + + local_server = self.setup_local_server(sambaopts, localdcopts) + try: + local_lsa = self.new_local_lsa_connection() + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "failed to connect lsa server") + + try: + (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access) + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS") + + self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % ( + local_lsa_info.name.string, + local_lsa_info.dns_domain.string, + local_lsa_info.sid)) + + try: + remote_server = self.setup_remote_server(credopts, domain) + except RuntimeError as error: + raise self.RemoteRuntimeError(self, error, "failed to locate remote server") + + try: + remote_lsa = self.new_remote_lsa_connection() + except RuntimeError as error: + raise self.RemoteRuntimeError(self, error, "failed to connect lsa server") + + try: + (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access) + except RuntimeError as error: + raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS") + + self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % ( + remote_lsa_info.name.string, + remote_lsa_info.dns_domain.string, + remote_lsa_info.sid)) + + local_trust_info.domain_name.string = remote_lsa_info.dns_domain.string + local_trust_info.netbios_name.string = remote_lsa_info.name.string + local_trust_info.sid = remote_lsa_info.sid + + if remote_trust_info: + remote_trust_info.domain_name.string = local_lsa_info.dns_domain.string + remote_trust_info.netbios_name.string = local_lsa_info.name.string + remote_trust_info.sid = local_lsa_info.sid + + try: + lsaString.string = local_trust_info.domain_name.string + local_old_netbios = local_lsa.QueryTrustedDomainInfoByName(local_policy, + lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO) + raise CommandError("TrustedDomain %s already exist'" % lsaString.string) + except RuntimeError as error: + if not self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND): + raise self.LocalRuntimeError(self, error, + "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % ( + lsaString.string)) + + try: + lsaString.string = local_trust_info.netbios_name.string + local_old_dns = local_lsa.QueryTrustedDomainInfoByName(local_policy, + lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO) + raise CommandError("TrustedDomain %s already exist'" % lsaString.string) + except RuntimeError as error: + if not self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND): + raise self.LocalRuntimeError(self, error, + "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % ( + lsaString.string)) + + if remote_trust_info: + try: + lsaString.string = remote_trust_info.domain_name.string + remote_old_netbios = remote_lsa.QueryTrustedDomainInfoByName(remote_policy, + lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO) + raise CommandError("TrustedDomain %s already exist'" % lsaString.string) + except RuntimeError as error: + if not self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND): + raise self.RemoteRuntimeError(self, error, + "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % ( + lsaString.string)) + + try: + lsaString.string = remote_trust_info.netbios_name.string + remote_old_dns = remote_lsa.QueryTrustedDomainInfoByName(remote_policy, + lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO) + raise CommandError("TrustedDomain %s already exist'" % lsaString.string) + except RuntimeError as error: + if not self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND): + raise self.RemoteRuntimeError(self, error, + "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % ( + lsaString.string)) + + try: + local_netlogon = self.new_local_netlogon_connection() + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "failed to connect netlogon server") + + try: + local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server) + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info") + + if remote_trust_info: + try: + remote_netlogon = self.new_remote_netlogon_connection() + except RuntimeError as error: + raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server") + + try: + remote_netlogon_info = self.get_netlogon_dc_info(remote_netlogon, remote_server) + except RuntimeError as error: + raise self.RemoteRuntimeError(self, error, "failed to get netlogon dc info") + + def arcfour_encrypt(key, data): + from Crypto.Cipher import ARC4 + c = ARC4.new(key) + return c.encrypt(data) + + def generate_AuthInOutBlob(secret, update_time): + if secret is None: + blob = drsblobs.trustAuthInOutBlob() + blob.count = 0 + + return blob + + clear = drsblobs.AuthInfoClear() + clear.size = len(secret) + clear.password = secret + + info = drsblobs.AuthenticationInformation() + info.LastUpdateTime = samba.unix2nttime(update_time) + info.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR + info.AuthInfo = clear + + array = drsblobs.AuthenticationInformationArray() + array.count = 1 + array.array = [info] + + blob = drsblobs.trustAuthInOutBlob() + blob.count = 1 + blob.current = array + + return blob + + def generate_AuthInfoInternal(session_key, incoming=None, outgoing=None): + confounder = [0] * 512 + for i in range(len(confounder)): + confounder[i] = random.randint(0, 255) + + trustpass = drsblobs.trustDomainPasswords() + + trustpass.confounder = confounder + trustpass.outgoing = outgoing + trustpass.incoming = incoming + + trustpass_blob = ndr_pack(trustpass) + + encrypted_trustpass = arcfour_encrypt(session_key, trustpass_blob) + + auth_blob = lsa.DATA_BUF2() + auth_blob.size = len(encrypted_trustpass) + auth_blob.data = string_to_array(encrypted_trustpass) + + auth_info = lsa.TrustDomainInfoAuthInfoInternal() + auth_info.auth_blob = auth_blob + + return auth_info + + update_time = samba.current_unix_time() + incoming_blob = generate_AuthInOutBlob(incoming_secret, update_time) + outgoing_blob = generate_AuthInOutBlob(outgoing_secret, update_time) + + local_tdo_handle = None + remote_tdo_handle = None + + local_auth_info = generate_AuthInfoInternal(local_lsa.session_key, + incoming=incoming_blob, + outgoing=outgoing_blob) + if remote_trust_info: + remote_auth_info = generate_AuthInfoInternal(remote_lsa.session_key, + incoming=outgoing_blob, + outgoing=incoming_blob) + + try: + if remote_trust_info: + self.outf.write("Creating remote TDO.\n") + current_request = { "location": "remote", "name": "CreateTrustedDomainEx2"} + remote_tdo_handle = remote_lsa.CreateTrustedDomainEx2(remote_policy, + remote_trust_info, + remote_auth_info, + lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS) + self.outf.write("Remote TDO created.\n") + if enc_types: + self.outf.write("Setting supported encryption types on remote TDO.\n") + current_request = { "location": "remote", "name": "SetInformationTrustedDomain"} + remote_lsa.SetInformationTrustedDomain(remote_tdo_handle, + lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES, + enc_types) + + self.outf.write("Creating local TDO.\n") + current_request = { "location": "local", "name": "CreateTrustedDomainEx2"} + local_tdo_handle = local_lsa.CreateTrustedDomainEx2(local_policy, + local_trust_info, + local_auth_info, + lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS) + self.outf.write("Local TDO created\n") + if enc_types: + self.outf.write("Setting supported encryption types on local TDO.\n") + current_request = { "location": "local", "name": "SetInformationTrustedDomain"} + local_lsa.SetInformationTrustedDomain(local_tdo_handle, + lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES, + enc_types) + except RuntimeError as error: + self.outf.write("Error: %s failed %sly - cleaning up\n" % ( + current_request['name'], current_request['location'])) + if remote_tdo_handle: + self.outf.write("Deleting remote TDO.\n") + remote_lsa.DeleteObject(remote_tdo_handle) + remote_tdo_handle = None + if local_tdo_handle: + self.outf.write("Deleting local TDO.\n") + local_lsa.DeleteObject(local_tdo_handle) + local_tdo_handle = None + if current_request['location'] is "remote": + raise self.RemoteRuntimeError(self, error, "%s" % ( + current_request['name'])) + raise self.LocalRuntimeError(self, error, "%s" % ( + current_request['name'])) + + if validate: + if local_trust_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE: + self.outf.write("Setup local forest trust information...\n") + try: + # get all information about the remote trust + # this triggers netr_GetForestTrustInformation to the remote domain + # and lsaRSetForestTrustInformation() locally, but new top level + # names are disabled by default. + local_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc, + remote_lsa_info.dns_domain.string, + netlogon.DS_GFTI_UPDATE_TDO) + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed") + + try: + # here we try to enable all top level names + local_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy, + remote_lsa_info.dns_domain, + lsa.LSA_FOREST_TRUST_DOMAIN_INFO, + local_forest_info, + 0) + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed") + + self.write_forest_trust_info(local_forest_info, + tln=remote_lsa_info.dns_domain.string, + collisions=local_forest_collision) + + if remote_trust_info: + self.outf.write("Setup remote forest trust information...\n") + try: + # get all information about the local trust (from the perspective of the remote domain) + # this triggers netr_GetForestTrustInformation to our domain. + # and lsaRSetForestTrustInformation() remotely, but new top level + # names are disabled by default. + remote_forest_info = remote_netlogon.netr_DsRGetForestTrustInformation(remote_netlogon_info.dc_unc, + local_lsa_info.dns_domain.string, + netlogon.DS_GFTI_UPDATE_TDO) + except RuntimeError as error: + raise self.RemoteRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed") + + try: + # here we try to enable all top level names + remote_forest_collision = remote_lsa.lsaRSetForestTrustInformation(remote_policy, + local_lsa_info.dns_domain, + lsa.LSA_FOREST_TRUST_DOMAIN_INFO, + remote_forest_info, + 0) + except RuntimeError as error: + raise self.RemoteRuntimeError(self, error, "lsaRSetForestTrustInformation() failed") + + self.write_forest_trust_info(remote_forest_info, + tln=local_lsa_info.dns_domain.string, + collisions=remote_forest_collision) + + if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND: + self.outf.write("Validating outgoing trust...\n") + try: + local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_netlogon_info.dc_unc, + netlogon.NETLOGON_CONTROL_TC_VERIFY, + 2, + remote_lsa_info.dns_domain.string) + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed") + + local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0]) + local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0]) + + if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED: + local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % ( + local_trust_verify.trusted_dc_name, + local_trust_verify.tc_connection_status[1], + local_trust_verify.pdc_connection_status[1]) + else: + local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % ( + local_trust_verify.trusted_dc_name, + local_trust_verify.tc_connection_status[1], + local_trust_verify.pdc_connection_status[1]) + + if local_trust_status != self.WERR_OK or local_conn_status != self.WERR_OK: + raise CommandError(local_validation) + else: + self.outf.write("OK: %s\n" % local_validation) + + if remote_trust_info: + if remote_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND: + self.outf.write("Validating incoming trust...\n") + try: + remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_netlogon_info.dc_unc, + netlogon.NETLOGON_CONTROL_TC_VERIFY, + 2, + local_lsa_info.dns_domain.string) + except RuntimeError as error: + raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed") + + remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0]) + remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0]) + + if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED: + remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % ( + remote_trust_verify.trusted_dc_name, + remote_trust_verify.tc_connection_status[1], + remote_trust_verify.pdc_connection_status[1]) + else: + remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % ( + remote_trust_verify.trusted_dc_name, + remote_trust_verify.tc_connection_status[1], + remote_trust_verify.pdc_connection_status[1]) + + if remote_trust_status != self.WERR_OK or remote_conn_status != self.WERR_OK: + raise CommandError(remote_validation) + else: + self.outf.write("OK: %s\n" % remote_validation) + + if remote_tdo_handle is not None: + try: + remote_lsa.Close(remote_tdo_handle) + except RuntimeError as error: + pass + remote_tdo_handle = None + if local_tdo_handle is not None: + try: + local_lsa.Close(local_tdo_handle) + except RuntimeError as error: + pass + local_tdo_handle = None + + self.outf.write("Success.\n") + return + +class cmd_domain_trust_delete(DomainTrustCommand): + """Delete a domain trust.""" + + synopsis = "%prog DOMAIN [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "versionopts": options.VersionOptions, + "credopts": options.CredentialsOptions, + "localdcopts": LocalDCCredentialsOptions, + } + + takes_options = [ + Option("--delete-location", type="choice", metavar="LOCATION", + choices=["local", "both"], + help="Where to delete the trusted domain object: 'local' or 'both'.", + dest='delete_location', + default="both"), + ] + + takes_args = ["domain"] + + def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None, + delete_location=None): + + local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION + local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN + local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET + + if delete_location == "local": + remote_policy_access = None + else: + remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION + remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN + remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET + + local_server = self.setup_local_server(sambaopts, localdcopts) + try: + local_lsa = self.new_local_lsa_connection() + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "failed to connect lsa server") + + try: + (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access) + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS") + + self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % ( + local_lsa_info.name.string, + local_lsa_info.dns_domain.string, + local_lsa_info.sid)) + + local_tdo_info = None + local_tdo_handle = None + remote_tdo_info = None + remote_tdo_handle = None + + lsaString = lsa.String() + try: + lsaString.string = domain + local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy, + lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX) + except RuntimeError as error: + if self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND): + raise CommandError("Failed to find trust for domain '%s'" % domain) + raise self.RemoteRuntimeError(self, error, "failed to locate remote server") + + + if remote_policy_access is not None: + try: + remote_server = self.setup_remote_server(credopts, domain) + except RuntimeError as error: + raise self.RemoteRuntimeError(self, error, "failed to locate remote server") + + try: + remote_lsa = self.new_remote_lsa_connection() + except RuntimeError as error: + raise self.RemoteRuntimeError(self, error, "failed to connect lsa server") + + try: + (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access) + except RuntimeError as error: + raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS") + + self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % ( + remote_lsa_info.name.string, + remote_lsa_info.dns_domain.string, + remote_lsa_info.sid)) + + if remote_lsa_info.sid != local_tdo_info.sid or \ + remote_lsa_info.name.string != local_tdo_info.netbios_name.string or \ + remote_lsa_info.dns_domain.string != local_tdo_info.domain_name.string: + raise CommandError("LocalTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % ( + local_tdo_info.netbios_name.string, + local_tdo_info.domain_name.string, + local_tdo_info.sid)) + + try: + lsaString.string = local_lsa_info.dns_domain.string + remote_tdo_info = remote_lsa.QueryTrustedDomainInfoByName(remote_policy, + lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX) + except RuntimeError as error: + if not self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND): + raise self.RemoteRuntimeError(self, error, "QueryTrustedDomainInfoByName(%s)" % ( + lsaString.string)) + pass + + if remote_tdo_info is not None: + if local_lsa_info.sid != remote_tdo_info.sid or \ + local_lsa_info.name.string != remote_tdo_info.netbios_name.string or \ + local_lsa_info.dns_domain.string != remote_tdo_info.domain_name.string: + raise CommandError("RemoteTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % ( + remote_tdo_info.netbios_name.string, + remote_tdo_info.domain_name.string, + remote_tdo_info.sid)) + + if local_tdo_info is not None: + try: + lsaString.string = local_tdo_info.domain_name.string + local_tdo_handle = local_lsa.OpenTrustedDomainByName(local_policy, + lsaString, + security.SEC_STD_DELETE) + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % ( + lsaString.string)) + + local_lsa.DeleteObject(local_tdo_handle) + local_tdo_handle = None + + if remote_tdo_info is not None: + try: + lsaString.string = remote_tdo_info.domain_name.string + remote_tdo_handle = remote_lsa.OpenTrustedDomainByName(remote_policy, + lsaString, + security.SEC_STD_DELETE) + except RuntimeError as error: + raise self.RemoteRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % ( + lsaString.string)) + + if remote_tdo_handle is not None: + try: + remote_lsa.DeleteObject(remote_tdo_handle) + remote_tdo_handle = None + self.outf.write("RemoteTDO deleted.\n") + except RuntimeError as error: + self.outf.write("%s\n" % self.RemoteRuntimeError(self, error, "DeleteObject() failed")) + + if local_tdo_handle is not None: + try: + local_lsa.DeleteObject(local_tdo_handle) + local_tdo_handle = None + self.outf.write("LocalTDO deleted.\n") + except RuntimeError as error: + self.outf.write("%s\n" % self.LocalRuntimeError(self, error, "DeleteObject() failed")) + + return + +class cmd_domain_trust_validate(DomainTrustCommand): + """Validate a domain trust.""" + + synopsis = "%prog DOMAIN [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "versionopts": options.VersionOptions, + "credopts": options.CredentialsOptions, + "localdcopts": LocalDCCredentialsOptions, + } + + takes_options = [ + Option("--validate-location", type="choice", metavar="LOCATION", + choices=["local", "both"], + help="Where to validate the trusted domain object: 'local' or 'both'.", + dest='validate_location', + default="both"), + ] + + takes_args = ["domain"] + + def run(self, domain, sambaopts=None, versionopts=None, credopts=None, localdcopts=None, + validate_location=None): + + local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION + + local_server = self.setup_local_server(sambaopts, localdcopts) + try: + local_lsa = self.new_local_lsa_connection() + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "failed to connect lsa server") + + try: + (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access) + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS") + + self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % ( + local_lsa_info.name.string, + local_lsa_info.dns_domain.string, + local_lsa_info.sid)) + + try: + lsaString = lsa.String() + lsaString.string = domain + local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy, + lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX) + except RuntimeError as error: + if self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND): + raise CommandError("trusted domain object does not exist for domain [%s]" % domain) + + raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed") + + self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % ( + local_tdo_info.netbios_name.string, + local_tdo_info.domain_name.string, + local_tdo_info.sid)) + + try: + local_netlogon = self.new_local_netlogon_connection() + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "failed to connect netlogon server") + + try: + local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_server, + netlogon.NETLOGON_CONTROL_TC_VERIFY, + 2, + local_tdo_info.domain_name.string) + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed") + + local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0]) + local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0]) + + if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED: + local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % ( + local_trust_verify.trusted_dc_name, + local_trust_verify.tc_connection_status[1], + local_trust_verify.pdc_connection_status[1]) + else: + local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % ( + local_trust_verify.trusted_dc_name, + local_trust_verify.tc_connection_status[1], + local_trust_verify.pdc_connection_status[1]) + + if local_trust_status != self.WERR_OK or local_conn_status != self.WERR_OK: + raise CommandError(local_validation) + else: + self.outf.write("OK: %s\n" % local_validation) + + try: + server = local_trust_verify.trusted_dc_name.replace('\\', '') + domain_and_server = "%s\\%s" % (local_tdo_info.domain_name.string, server) + local_trust_rediscover = local_netlogon.netr_LogonControl2Ex(local_server, + netlogon.NETLOGON_CONTROL_REDISCOVER, + 2, + domain_and_server) + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed") + + local_conn_status = self._uint32(local_trust_rediscover.tc_connection_status[0]) + local_rediscover = "LocalRediscover: DC[%s] CONNECTION[%s]" % ( + local_trust_rediscover.trusted_dc_name, + local_trust_rediscover.tc_connection_status[1]) + + if local_conn_status != self.WERR_OK: + raise CommandError(local_rediscover) + else: + self.outf.write("OK: %s\n" % local_rediscover) + + if validate_location != "local": + try: + remote_server = self.setup_remote_server(credopts, domain, require_pdc=False) + except RuntimeError as error: + raise self.RemoteRuntimeError(self, error, "failed to locate remote server") + + try: + remote_netlogon = self.new_remote_netlogon_connection() + except RuntimeError as error: + raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server") + + try: + remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_server, + netlogon.NETLOGON_CONTROL_TC_VERIFY, + 2, + local_lsa_info.dns_domain.string) + except RuntimeError as error: + raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed") + + remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0]) + remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0]) + + if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED: + remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % ( + remote_trust_verify.trusted_dc_name, + remote_trust_verify.tc_connection_status[1], + remote_trust_verify.pdc_connection_status[1]) + else: + remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % ( + remote_trust_verify.trusted_dc_name, + remote_trust_verify.tc_connection_status[1], + remote_trust_verify.pdc_connection_status[1]) + + if remote_trust_status != self.WERR_OK or remote_conn_status != self.WERR_OK: + raise CommandError(remote_validation) + else: + self.outf.write("OK: %s\n" % remote_validation) + + try: + server = remote_trust_verify.trusted_dc_name.replace('\\', '') + domain_and_server = "%s\\%s" % (local_lsa_info.dns_domain.string, server) + remote_trust_rediscover = remote_netlogon.netr_LogonControl2Ex(remote_server, + netlogon.NETLOGON_CONTROL_REDISCOVER, + 2, + domain_and_server) + except RuntimeError as error: + raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed") + + remote_conn_status = self._uint32(remote_trust_rediscover.tc_connection_status[0]) + + remote_rediscover = "RemoteRediscover: DC[%s] CONNECTION[%s]" % ( + remote_trust_rediscover.trusted_dc_name, + remote_trust_rediscover.tc_connection_status[1]) + + if remote_conn_status != self.WERR_OK: + raise CommandError(remote_rediscover) + else: + self.outf.write("OK: %s\n" % remote_rediscover) + + return + +class cmd_domain_trust_namespaces(DomainTrustCommand): + """Manage forest trust namespaces.""" + + synopsis = "%prog [DOMAIN] [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "versionopts": options.VersionOptions, + "localdcopts": LocalDCCredentialsOptions, + } + + takes_options = [ + Option("--refresh", type="choice", metavar="check|store", + choices=["check", "store", None], + help="List and maybe store refreshed forest trust information: 'check' or 'store'.", + dest='refresh', + default=None), + Option("--enable-all", action="store_true", + help="Try to update disabled entries, not allowed with --refresh=check.", + dest='enable_all', + default=False), + Option("--enable-tln", action="append", metavar='DNSDOMAIN', + help="Enable a top level name entry. Can be specified multiple times.", + dest='enable_tln', + default=[]), + Option("--disable-tln", action="append", metavar='DNSDOMAIN', + help="Disable a top level name entry. Can be specified multiple times.", + dest='disable_tln', + default=[]), + Option("--add-tln-ex", action="append", metavar='DNSDOMAIN', + help="Add a top level exclusion entry. Can be specified multiple times.", + dest='add_tln_ex', + default=[]), + Option("--delete-tln-ex", action="append", metavar='DNSDOMAIN', + help="Delete a top level exclusion entry. Can be specified multiple times.", + dest='delete_tln_ex', + default=[]), + Option("--enable-nb", action="append", metavar='NETBIOSDOMAIN', + help="Enable a netbios name in a domain entry. Can be specified multiple times.", + dest='enable_nb', + default=[]), + Option("--disable-nb", action="append", metavar='NETBIOSDOMAIN', + help="Disable a netbios name in a domain entry. Can be specified multiple times.", + dest='disable_nb', + default=[]), + Option("--enable-sid", action="append", metavar='DOMAINSID', + help="Enable a SID in a domain entry. Can be specified multiple times.", + dest='enable_sid_str', + default=[]), + Option("--disable-sid", action="append", metavar='DOMAINSID', + help="Disable a SID in a domain entry. Can be specified multiple times.", + dest='disable_sid_str', + default=[]), + Option("--add-upn-suffix", action="append", metavar='DNSDOMAIN', + help="Add a new uPNSuffixes attribute for the local forest. Can be specified multiple times.", + dest='add_upn', + default=[]), + Option("--delete-upn-suffix", action="append", metavar='DNSDOMAIN', + help="Delete an existing uPNSuffixes attribute of the local forest. Can be specified multiple times.", + dest='delete_upn', + default=[]), + Option("--add-spn-suffix", action="append", metavar='DNSDOMAIN', + help="Add a new msDS-SPNSuffixes attribute for the local forest. Can be specified multiple times.", + dest='add_spn', + default=[]), + Option("--delete-spn-suffix", action="append", metavar='DNSDOMAIN', + help="Delete an existing msDS-SPNSuffixes attribute of the local forest. Can be specified multiple times.", + dest='delete_spn', + default=[]), + ] + + takes_args = ["domain?"] + + def run(self, domain=None, sambaopts=None, localdcopts=None, versionopts=None, + refresh=None, enable_all=False, + enable_tln=[], disable_tln=[], add_tln_ex=[], delete_tln_ex=[], + enable_sid_str=[], disable_sid_str=[], enable_nb=[], disable_nb=[], + add_upn=[], delete_upn=[], add_spn=[], delete_spn=[]): + + require_update = False + + if domain is None: + if refresh == "store": + raise CommandError("--refresh=%s not allowed without DOMAIN" % refresh) + + if enable_all: + raise CommandError("--enable-all not allowed without DOMAIN") + + if len(enable_tln) > 0: + raise CommandError("--enable-tln not allowed without DOMAIN") + if len(disable_tln) > 0: + raise CommandError("--disable-tln not allowed without DOMAIN") + + if len(add_tln_ex) > 0: + raise CommandError("--add-tln-ex not allowed without DOMAIN") + if len(delete_tln_ex) > 0: + raise CommandError("--delete-tln-ex not allowed without DOMAIN") + + if len(enable_nb) > 0: + raise CommandError("--enable-nb not allowed without DOMAIN") + if len(disable_nb) > 0: + raise CommandError("--disable-nb not allowed without DOMAIN") + + if len(enable_sid_str) > 0: + raise CommandError("--enable-sid not allowed without DOMAIN") + if len(disable_sid_str) > 0: + raise CommandError("--disable-sid not allowed without DOMAIN") + + if len(add_upn) > 0: + for n in add_upn: + if not n.startswith("*."): + continue + raise CommandError("value[%s] specified for --add-upn-suffix should not include with '*.'" % n) + require_update = True + if len(delete_upn) > 0: + for n in delete_upn: + if not n.startswith("*."): + continue + raise CommandError("value[%s] specified for --delete-upn-suffix should not include with '*.'" % n) + require_update = True + for a in add_upn: + for d in delete_upn: + if a.lower() != d.lower(): + continue + raise CommandError("value[%s] specified for --add-upn-suffix and --delete-upn-suffix" % a) + + if len(add_spn) > 0: + for n in add_spn: + if not n.startswith("*."): + continue + raise CommandError("value[%s] specified for --add-spn-suffix should not include with '*.'" % n) + require_update = True + if len(delete_spn) > 0: + for n in delete_spn: + if not n.startswith("*."): + continue + raise CommandError("value[%s] specified for --delete-spn-suffix should not include with '*.'" % n) + require_update = True + for a in add_spn: + for d in delete_spn: + if a.lower() != d.lower(): + continue + raise CommandError("value[%s] specified for --add-spn-suffix and --delete-spn-suffix" % a) + else: + if len(add_upn) > 0: + raise CommandError("--add-upn-suffix not allowed together with DOMAIN") + if len(delete_upn) > 0: + raise CommandError("--delete-upn-suffix not allowed together with DOMAIN") + if len(add_spn) > 0: + raise CommandError("--add-spn-suffix not allowed together with DOMAIN") + if len(delete_spn) > 0: + raise CommandError("--delete-spn-suffix not allowed together with DOMAIN") + + if refresh is not None: + if refresh == "store": + require_update = True + + if enable_all and refresh != "store": + raise CommandError("--enable-all not allowed together with --refresh=%s" % refresh) + + if len(enable_tln) > 0: + raise CommandError("--enable-tln not allowed together with --refresh") + if len(disable_tln) > 0: + raise CommandError("--disable-tln not allowed together with --refresh") + + if len(add_tln_ex) > 0: + raise CommandError("--add-tln-ex not allowed together with --refresh") + if len(delete_tln_ex) > 0: + raise CommandError("--delete-tln-ex not allowed together with --refresh") + + if len(enable_nb) > 0: + raise CommandError("--enable-nb not allowed together with --refresh") + if len(disable_nb) > 0: + raise CommandError("--disable-nb not allowed together with --refresh") + + if len(enable_sid_str) > 0: + raise CommandError("--enable-sid not allowed together with --refresh") + if len(disable_sid_str) > 0: + raise CommandError("--disable-sid not allowed together with --refresh") + else: + if enable_all: + require_update = True + + if len(enable_tln) > 0: + raise CommandError("--enable-tln not allowed together with --enable-all") + + if len(enable_nb) > 0: + raise CommandError("--enable-nb not allowed together with --enable-all") + + if len(enable_sid) > 0: + raise CommandError("--enable-sid not allowed together with --enable-all") + + if len(enable_tln) > 0: + require_update = True + if len(disable_tln) > 0: + require_update = True + for e in enable_tln: + for d in disable_tln: + if e.lower() != d.lower(): + continue + raise CommandError("value[%s] specified for --enable-tln and --disable-tln" % e) + + if len(add_tln_ex) > 0: + for n in add_tln_ex: + if not n.startswith("*."): + continue + raise CommandError("value[%s] specified for --add-tln-ex should not include with '*.'" % n) + require_update = True + if len(delete_tln_ex) > 0: + for n in delete_tln_ex: + if not n.startswith("*."): + continue + raise CommandError("value[%s] specified for --delete-tln-ex should not include with '*.'" % n) + require_update = True + for a in add_tln_ex: + for d in delete_tln_ex: + if a.lower() != d.lower(): + continue + raise CommandError("value[%s] specified for --add-tln-ex and --delete-tln-ex" % a) + + if len(enable_nb) > 0: + require_update = True + if len(disable_nb) > 0: + require_update = True + for e in enable_nb: + for d in disable_nb: + if e.upper() != d.upper(): + continue + raise CommandError("value[%s] specified for --enable-nb and --disable-nb" % e) + + enable_sid = [] + for s in enable_sid_str: + try: + sid = security.dom_sid(s) + except TypeError as error: + raise CommandError("value[%s] specified for --enable-sid is not a valid SID" % s) + enable_sid.append(sid) + disable_sid = [] + for s in disable_sid_str: + try: + sid = security.dom_sid(s) + except TypeError as error: + raise CommandError("value[%s] specified for --disable-sid is not a valid SID" % s) + disable_sid.append(sid) + if len(enable_sid) > 0: + require_update = True + if len(disable_sid) > 0: + require_update = True + for e in enable_sid: + for d in disable_sid: + if e != d: + continue + raise CommandError("value[%s] specified for --enable-sid and --disable-sid" % e) + + local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION + if require_update: + local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN + + local_server = self.setup_local_server(sambaopts, localdcopts) + try: + local_lsa = self.new_local_lsa_connection() + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "failed to connect lsa server") + + try: + (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access) + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS") + + self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % ( + local_lsa_info.name.string, + local_lsa_info.dns_domain.string, + local_lsa_info.sid)) + + if domain is None: + try: + local_netlogon = self.new_local_netlogon_connection() + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "failed to connect netlogon server") + + try: + local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server) + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info") + + if local_netlogon_info.domain_name != local_netlogon_info.forest_name: + raise CommandError("The local domain [%s] is not the forest root [%s]" % ( + local_netlogon_info.domain_name, + local_netlogon_info.forest_name)) + + try: + # get all information about our own forest + own_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc, + None, 0) + except RuntimeError as error: + if self.check_runtime_error(error, self.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE): + raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % ( + self.local_server)) + + if self.check_runtime_error(error, self.WERR_INVALID_FUNCTION): + raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % ( + self.local_server)) + + if self.check_runtime_error(error, self.WERR_NERR_ACFNOTLOADED): + raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % ( + self.local_server)) + + raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed") + + self.outf.write("Own forest trust information...\n") + self.write_forest_trust_info(own_forest_info, + tln=local_lsa_info.dns_domain.string) + + try: + local_samdb = self.new_local_ldap_connection() + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "failed to connect to SamDB") + + local_partitions_dn = "CN=Partitions,%s" % str(local_samdb.get_config_basedn()) + attrs = ['uPNSuffixes', 'msDS-SPNSuffixes'] + try: + msgs = local_samdb.search(base=local_partitions_dn, + scope=ldb.SCOPE_BASE, + expression="(objectClass=crossRefContainer)", + attrs=attrs) + stored_msg = msgs[0] + except ldb.LdbError as error: + raise self.LocalLdbError(self, error, "failed to search partition dn") + + stored_upn_vals = [] + if 'uPNSuffixes' in stored_msg: + stored_upn_vals.extend(stored_msg['uPNSuffixes']) + + stored_spn_vals = [] + if 'msDS-SPNSuffixes' in stored_msg: + stored_spn_vals.extend(stored_msg['msDS-SPNSuffixes']) + + self.outf.write("Stored uPNSuffixes attributes[%d]:\n" % len(stored_upn_vals)) + for v in stored_upn_vals: + self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v)) + self.outf.write("Stored msDS-SPNSuffixes attributes[%d]:\n" % len(stored_spn_vals)) + for v in stored_spn_vals: + self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v)) + + if not require_update: + return + + replace_upn = False + update_upn_vals = [] + update_upn_vals.extend(stored_upn_vals) + + replace_spn = False + update_spn_vals = [] + update_spn_vals.extend(stored_spn_vals) + + for upn in add_upn: + idx = None + for i in xrange(0, len(update_upn_vals)): + v = update_upn_vals[i] + if v.lower() != upn.lower(): + continue + idx = i + break + if idx is not None: + raise CommandError("Entry already present for value[%s] specified for --add-upn-suffix" % upn) + update_upn_vals.append(upn) + replace_upn = True + + for upn in delete_upn: + idx = None + for i in xrange(0, len(update_upn_vals)): + v = update_upn_vals[i] + if v.lower() != upn.lower(): + continue + idx = i + break + if idx is None: + raise CommandError("Entry not found for value[%s] specified for --delete-upn-suffix" % upn) + + update_upn_vals.pop(idx) + replace_upn = True + + for spn in add_spn: + idx = None + for i in xrange(0, len(update_spn_vals)): + v = update_spn_vals[i] + if v.lower() != spn.lower(): + continue + idx = i + break + if idx is not None: + raise CommandError("Entry already present for value[%s] specified for --add-spn-suffix" % spn) + update_spn_vals.append(spn) + replace_spn = True + + for spn in delete_spn: + idx = None + for i in xrange(0, len(update_spn_vals)): + v = update_spn_vals[i] + if v.lower() != spn.lower(): + continue + idx = i + break + if idx is None: + raise CommandError("Entry not found for value[%s] specified for --delete-spn-suffix" % spn) + + update_spn_vals.pop(idx) + replace_spn = True + + self.outf.write("Update uPNSuffixes attributes[%d]:\n" % len(update_upn_vals)) + for v in update_upn_vals: + self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v)) + self.outf.write("Update msDS-SPNSuffixes attributes[%d]:\n" % len(update_spn_vals)) + for v in update_spn_vals: + self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v)) + + update_msg = ldb.Message() + update_msg.dn = stored_msg.dn + + if replace_upn: + update_msg['uPNSuffixes'] = ldb.MessageElement(update_upn_vals, + ldb.FLAG_MOD_REPLACE, + 'uPNSuffixes') + if replace_spn: + update_msg['msDS-SPNSuffixes'] = ldb.MessageElement(update_spn_vals, + ldb.FLAG_MOD_REPLACE, + 'msDS-SPNSuffixes') + try: + local_samdb.modify(update_msg) + except ldb.LdbError as error: + raise self.LocalLdbError(self, error, "failed to update partition dn") + + try: + stored_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc, + None, 0) + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed") + + self.outf.write("Stored forest trust information...\n") + self.write_forest_trust_info(stored_forest_info, + tln=local_lsa_info.dns_domain.string) + return + + try: + lsaString = lsa.String() + lsaString.string = domain + local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy, + lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX) + except RuntimeError as error: + if self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND): + raise CommandError("trusted domain object does not exist for domain [%s]" % domain) + + raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed") + + self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % ( + local_tdo_info.netbios_name.string, + local_tdo_info.domain_name.string, + local_tdo_info.sid)) + + if not local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE: + raise CommandError("trusted domain object for domain [%s] is not marked as FOREST_TRANSITIVE." % domain) + + if refresh is not None: + try: + local_netlogon = self.new_local_netlogon_connection() + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "failed to connect netlogon server") + + try: + local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server) + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info") + + lsa_update_check = 1 + if refresh == "store": + netlogon_update_tdo = netlogon.DS_GFTI_UPDATE_TDO + if enable_all: + lsa_update_check = 0 + else: + netlogon_update_tdo = 0 + + try: + # get all information about the remote trust + # this triggers netr_GetForestTrustInformation to the remote domain + # and lsaRSetForestTrustInformation() locally, but new top level + # names are disabled by default. + fresh_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc, + local_tdo_info.domain_name.string, + netlogon_update_tdo) + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed") + + try: + fresh_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy, + local_tdo_info.domain_name, + lsa.LSA_FOREST_TRUST_DOMAIN_INFO, + fresh_forest_info, + lsa_update_check) + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed") + + self.outf.write("Fresh forest trust information...\n") + self.write_forest_trust_info(fresh_forest_info, + tln=local_tdo_info.domain_name.string, + collisions=fresh_forest_collision) + + if refresh == "store": + try: + lsaString = lsa.String() + lsaString.string = local_tdo_info.domain_name.string + stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy, + lsaString, + lsa.LSA_FOREST_TRUST_DOMAIN_INFO) + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed") + + self.outf.write("Stored forest trust information...\n") + self.write_forest_trust_info(stored_forest_info, + tln=local_tdo_info.domain_name.string) + + return + + # + # The none --refresh path + # + + try: + lsaString = lsa.String() + lsaString.string = local_tdo_info.domain_name.string + local_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy, + lsaString, + lsa.LSA_FOREST_TRUST_DOMAIN_INFO) + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed") + + self.outf.write("Local forest trust information...\n") + self.write_forest_trust_info(local_forest_info, + tln=local_tdo_info.domain_name.string) + + if not require_update: + return + + entries = [] + entries.extend(local_forest_info.entries) + update_forest_info = lsa.ForestTrustInformation() + update_forest_info.count = len(entries) + update_forest_info.entries = entries + + if enable_all: + for i in xrange(0, len(update_forest_info.entries)): + r = update_forest_info.entries[i] + if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME: + continue + if update_forest_info.entries[i].flags == 0: + continue + update_forest_info.entries[i].time = 0 + update_forest_info.entries[i].flags &= ~lsa.LSA_TLN_DISABLED_MASK + for i in xrange(0, len(update_forest_info.entries)): + r = update_forest_info.entries[i] + if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO: + continue + if update_forest_info.entries[i].flags == 0: + continue + update_forest_info.entries[i].time = 0 + update_forest_info.entries[i].flags &= ~lsa.LSA_NB_DISABLED_MASK + update_forest_info.entries[i].flags &= ~lsa.LSA_SID_DISABLED_MASK + + for tln in enable_tln: + idx = None + for i in xrange(0, len(update_forest_info.entries)): + r = update_forest_info.entries[i] + if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME: + continue + if r.forest_trust_data.string.lower() != tln.lower(): + continue + idx = i + break + if idx is None: + raise CommandError("Entry not found for value[%s] specified for --enable-tln" % tln) + if not update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_MASK: + raise CommandError("Entry found for value[%s] specified for --enable-tln is already enabled" % tln) + update_forest_info.entries[idx].time = 0 + update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK + + for tln in disable_tln: + idx = None + for i in xrange(0, len(update_forest_info.entries)): + r = update_forest_info.entries[i] + if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME: + continue + if r.forest_trust_data.string.lower() != tln.lower(): + continue + idx = i + break + if idx is None: + raise CommandError("Entry not found for value[%s] specified for --disable-tln" % tln) + if update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_ADMIN: + raise CommandError("Entry found for value[%s] specified for --disable-tln is already disabled" % tln) + update_forest_info.entries[idx].time = 0 + update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK + update_forest_info.entries[idx].flags |= lsa.LSA_TLN_DISABLED_ADMIN + + for tln_ex in add_tln_ex: + idx = None + for i in xrange(0, len(update_forest_info.entries)): + r = update_forest_info.entries[i] + if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX: + continue + if r.forest_trust_data.string.lower() != tln_ex.lower(): + continue + idx = i + break + if idx is not None: + raise CommandError("Entry already present for value[%s] specified for --add-tln-ex" % tln_ex) + + tln_dot = ".%s" % tln_ex.lower() + idx = None + for i in xrange(0, len(update_forest_info.entries)): + r = update_forest_info.entries[i] + if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME: + continue + r_dot = ".%s" % r.forest_trust_data.string.lower() + if tln_dot == r_dot: + raise CommandError("TLN entry present for value[%s] specified for --add-tln-ex" % tln_ex) + if not tln_dot.endswith(r_dot): + continue + idx = i + break + + if idx is None: + raise CommandError("No TLN parent present for value[%s] specified for --add-tln-ex" % tln_ex) + + r = lsa.ForestTrustRecord() + r.type = lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX + r.flags = 0 + r.time = 0 + r.forest_trust_data.string = tln_ex + + entries = [] + entries.extend(update_forest_info.entries) + entries.insert(idx + 1, r) + update_forest_info.count = len(entries) + update_forest_info.entries = entries + + for tln_ex in delete_tln_ex: + idx = None + for i in xrange(0, len(update_forest_info.entries)): + r = update_forest_info.entries[i] + if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX: + continue + if r.forest_trust_data.string.lower() != tln_ex.lower(): + continue + idx = i + break + if idx is None: + raise CommandError("Entry not found for value[%s] specified for --delete-tln-ex" % tln_ex) + + entries = [] + entries.extend(update_forest_info.entries) + entries.pop(idx) + update_forest_info.count = len(entries) + update_forest_info.entries = entries + + for nb in enable_nb: + idx = None + for i in xrange(0, len(update_forest_info.entries)): + r = update_forest_info.entries[i] + if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO: + continue + if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper(): + continue + idx = i + break + if idx is None: + raise CommandError("Entry not found for value[%s] specified for --enable-nb" % nb) + if not update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_MASK: + raise CommandError("Entry found for value[%s] specified for --enable-nb is already enabled" % nb) + update_forest_info.entries[idx].time = 0 + update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK + + for nb in disable_nb: + idx = None + for i in xrange(0, len(update_forest_info.entries)): + r = update_forest_info.entries[i] + if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO: + continue + if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper(): + continue + idx = i + break + if idx is None: + raise CommandError("Entry not found for value[%s] specified for --delete-nb" % nb) + if update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_ADMIN: + raise CommandError("Entry found for value[%s] specified for --disable-nb is already disabled" % nb) + update_forest_info.entries[idx].time = 0 + update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK + update_forest_info.entries[idx].flags |= lsa.LSA_NB_DISABLED_ADMIN + + for sid in enable_sid: + idx = None + for i in xrange(0, len(update_forest_info.entries)): + r = update_forest_info.entries[i] + if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO: + continue + if r.forest_trust_data.domain_sid != sid: + continue + idx = i + break + if idx is None: + raise CommandError("Entry not found for value[%s] specified for --enable-sid" % sid) + if not update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_MASK: + raise CommandError("Entry found for value[%s] specified for --enable-sid is already enabled" % nb) + update_forest_info.entries[idx].time = 0 + update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK + + for sid in disable_sid: + idx = None + for i in xrange(0, len(update_forest_info.entries)): + r = update_forest_info.entries[i] + if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO: + continue + if r.forest_trust_data.domain_sid != sid: + continue + idx = i + break + if idx is None: + raise CommandError("Entry not found for value[%s] specified for --delete-sid" % sid) + if update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_ADMIN: + raise CommandError("Entry found for value[%s] specified for --disable-sid is already disabled" % nb) + update_forest_info.entries[idx].time = 0 + update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK + update_forest_info.entries[idx].flags |= lsa.LSA_SID_DISABLED_ADMIN + + try: + update_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy, + local_tdo_info.domain_name, + lsa.LSA_FOREST_TRUST_DOMAIN_INFO, + update_forest_info, 0) + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed") + + self.outf.write("Updated forest trust information...\n") + self.write_forest_trust_info(update_forest_info, + tln=local_tdo_info.domain_name.string, + collisions=update_forest_collision) + + try: + lsaString = lsa.String() + lsaString.string = local_tdo_info.domain_name.string + stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy, + lsaString, + lsa.LSA_FOREST_TRUST_DOMAIN_INFO) + except RuntimeError as error: + raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed") + + self.outf.write("Stored forest trust information...\n") + self.write_forest_trust_info(stored_forest_info, + tln=local_tdo_info.domain_name.string) + return + +class cmd_domain_trust(SuperCommand): + """Domain and forest trust management.""" + + subcommands = {} + subcommands["list"] = cmd_domain_trust_list() + subcommands["show"] = cmd_domain_trust_show() + subcommands["create"] = cmd_domain_trust_create() + subcommands["delete"] = cmd_domain_trust_delete() + subcommands["validate"] = cmd_domain_trust_validate() + subcommands["namespaces"] = cmd_domain_trust_namespaces() class cmd_domain(SuperCommand): """Domain management.""" @@ -1476,3 +3681,4 @@ class cmd_domain(SuperCommand): subcommands["passwordsettings"] = cmd_domain_passwordsettings() subcommands["classicupgrade"] = cmd_domain_classicupgrade() subcommands["samba3upgrade"] = cmd_domain_samba3upgrade() + subcommands["trust"] = cmd_domain_trust() -- 2.11.4.GIT