+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
+
+ 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.args[0])
+ if err32 == val:
+ return True
+
+ return False
+
+ class LocalRuntimeError(CommandError):
+ def __init__(exception_self, self, runtime, message):
+ err32 = self._uint32(runtime.args[0])
+ errstr = runtime.args[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.args[0])
+ errstr = runtime.args[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.args[0]
+ errstr = ldb_error.args[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 NTSTATUSError as error:
+ raise CommandError("Failed to find a writeable DC for domain '%s': %s" %
+ (domain, error[1]))
+ 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_unc(self, conn, server, domain):
+ try:
+ info = conn.netr_DsRGetDCNameEx2(server,
+ None, 0, None, None, None,
+ netlogon.DS_RETURN_DNS_NAME)
+ return info.dc_unc
+ except RuntimeError:
+ return conn.netr_GetDcName(server, domain)
+
+ 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, e in enumerate(fti.entries):
+
+ 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, werror.WERR_RPC_S_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 NTSTATUSError as error:
+ if self.check_runtime_error(error, ntstatus.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 NTSTATUSError as error:
+ if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_PARAMETER):
+ error = None
+ if self.check_runtime_error(error, ntstatus.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, ntstatus.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE):
+ error = None
+ if self.check_runtime_error(error, ntstatus.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("TrustedDomain:\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")
+
+ 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_byte_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_byte_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):
+ pw = samba.generate_random_machine_password(length//2, length//2)
+ return string_to_byte_array(pw.encode('utf-16-le'))
+
+ if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
+ incoming_secret = random_trust_secret(240)
+ if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
+ outgoing_secret = random_trust_secret(240)
+
+ 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 NTSTATUSError as error:
+ if not self.check_runtime_error(error, ntstatus.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 NTSTATUSError as error:
+ if not self.check_runtime_error(error, ntstatus.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 NTSTATUSError as error:
+ if not self.check_runtime_error(error, ntstatus.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 NTSTATUSError as error:
+ if not self.check_runtime_error(error, ntstatus.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_dc_unc = self.get_netlogon_dc_unc(remote_netlogon,
+ remote_server, domain)
+ except RuntimeError as error:
+ raise self.RemoteRuntimeError(self, error, "failed to get netlogon dc info")
+
+ 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_byte_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_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 != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
+ 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_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 != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
+ 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 NTSTATUSError as error:
+ if self.check_runtime_error(error, ntstatus.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 NTSTATUSError as error:
+ if not self.check_runtime_error(error, ntstatus.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 NTSTATUSError as error:
+ if self.check_runtime_error(error, ntstatus.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 != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
+ 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 != werror.WERR_SUCCESS:
+ 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 != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
+ 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 != werror.WERR_SUCCESS:
+ 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_str) > 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, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
+ raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
+ self.local_server))
+
+ if self.check_runtime_error(error, werror.WERR_INVALID_FUNCTION):
+ raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
+ self.local_server))
+
+ if self.check_runtime_error(error, werror.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:
+ for i, v in enumerate(update_upn_vals):
+ if v.lower() == upn.lower():
+ 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, v in enumerate(update_upn_vals):
+ 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:
+ for i, v in enumerate(update_spn_vals):
+ if v.lower() == spn.lower():
+ 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, v in enumerate(update_spn_vals):
+ 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 NTSTATUSError as error:
+ if self.check_runtime_error(error, ntstatus.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, r in enumerate(update_forest_info.entries):
+ 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, r in enumerate(update_forest_info.entries):
+ 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, r in enumerate(update_forest_info.entries):
+ 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, r in enumerate(update_forest_info.entries):
+ 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, r in enumerate(update_forest_info.entries):
+ 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, r in enumerate(update_forest_info.entries):
+ 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, r in enumerate(update_forest_info.entries):
+ 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, r in enumerate(update_forest_info.entries):
+ 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, r in enumerate(update_forest_info.entries):
+ 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, r in enumerate(update_forest_info.entries):
+ 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, r in enumerate(update_forest_info.entries):
+ 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_tombstones_expunge(Command):
+ """Expunge tombstones from the database.
+
+This command expunges tombstones from the database."""
+ synopsis = "%prog NC [NC [...]] [options]"
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H"),
+ Option("--current-time",
+ help="The current time to evaluate the tombstone lifetime from, expressed as YYYY-MM-DD",
+ type=str),
+ Option("--tombstone-lifetime", help="Number of days a tombstone should be preserved for", type=int),
+ ]
+
+ takes_args = ["nc*"]
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "credopts": options.CredentialsOptions,
+ "versionopts": options.VersionOptions,
+ }
+
+ def run(self, *ncs, **kwargs):
+ sambaopts = kwargs.get("sambaopts")
+ credopts = kwargs.get("credopts")
+ versionpts = kwargs.get("versionopts")
+ H = kwargs.get("H")
+ current_time_string = kwargs.get("current_time")
+ tombstone_lifetime = kwargs.get("tombstone_lifetime")
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+ samdb = SamDB(url=H, session_info=system_session(),
+ credentials=creds, lp=lp)
+
+ if current_time_string is not None:
+ current_time_obj = time.strptime(current_time_string, "%Y-%m-%d")
+ current_time = long(time.mktime(current_time_obj))
+
+ else:
+ current_time = long(time.time())
+
+ if len(ncs) == 0:
+ res = samdb.search(expression="", base="", scope=ldb.SCOPE_BASE,
+ attrs=["namingContexts"])
+
+ ncs = []
+ for nc in res[0]["namingContexts"]:
+ ncs.append(str(nc))
+ else:
+ ncs = list(ncs)
+
+ started_transaction = False
+ try:
+ samdb.transaction_start()
+ started_transaction = True
+ (removed_objects,
+ removed_links) = samdb.garbage_collect_tombstones(ncs,
+ current_time=current_time,
+ tombstone_lifetime=tombstone_lifetime)
+
+ except Exception as err:
+ if started_transaction:
+ samdb.transaction_cancel()
+ raise CommandError("Failed to expunge / garbage collect tombstones", err)
+
+ samdb.transaction_commit()
+
+ self.outf.write("Removed %d objects and %d links successfully\n"
+ % (removed_objects, removed_links))
+
+
+
+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_tombstones(SuperCommand):
+ """Domain tombstone and recycled object management."""
+
+ subcommands = {}
+ subcommands["expunge"] = cmd_domain_tombstones_expunge()
+
+class ldif_schema_update:
+ """Helper class for applying LDIF schema updates"""
+
+ def __init__(self):
+ self.is_defunct = False
+ self.unknown_oid = None
+ self.dn = None
+ self.ldif = ""
+
+ def can_ignore_failure(self, error):
+ """Checks if we can safely ignore failure to apply an LDIF update"""
+ (num, errstr) = error.args
+
+ # Microsoft has marked objects as defunct that Samba doesn't know about
+ if num == ldb.ERR_NO_SUCH_OBJECT and self.is_defunct:
+ print("Defunct object %s doesn't exist, skipping" % self.dn)
+ return True
+ elif self.unknown_oid is not None:
+ print("Skipping unknown OID %s for object %s" %(self.unknown_oid, self.dn))
+ return True
+
+ return False
+
+ def apply(self, samdb):
+ """Applies a single LDIF update to the schema"""
+
+ try:
+ try:
+ samdb.modify_ldif(self.ldif, controls=['relax:0'])
+ except ldb.LdbError as e:
+ if e.args[0] == ldb.ERR_INVALID_ATTRIBUTE_SYNTAX:
+
+ # REFRESH after a failed change
+
+ # Otherwise the OID-to-attribute mapping in
+ # _apply_updates_in_file() won't work, because it
+ # can't lookup the new OID in the schema
+ samdb.set_schema_update_now()
+
+ samdb.modify_ldif(self.ldif, controls=['relax:0'])
+ else:
+ raise
+ except ldb.LdbError as e:
+ if self.can_ignore_failure(e):
+ return 0
+ else:
+ print("Exception: %s" % e)
+ print("Encountered while trying to apply the following LDIF")
+ print("----------------------------------------------------")
+ print("%s" % self.ldif)
+
+ raise
+
+ return 1
+
+class cmd_domain_schema_upgrade(Command):
+ """Domain schema upgrading"""
+
+ synopsis = "%prog [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H"),
+ Option("-q", "--quiet", help="Be quiet", action="store_true"), #unused
+ Option("-v", "--verbose", help="Be verbose", action="store_true"),
+ Option("--schema", type="choice", metavar="SCHEMA",
+ choices=["2012", "2012_R2"],
+ help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
+ default="2012_R2"),
+ Option("--ldf-file", type=str, default=None,
+ help="Just apply the schema updates in the adprep/.LDF file(s) specified"),
+ Option("--base-dir", type=str, default=None,
+ help="Location of ldf files Default is ${SETUPDIR}/adprep.")
+ ]
+
+ def _apply_updates_in_file(self, samdb, ldif_file):
+ """
+ Applies a series of updates specified in an .LDIF file. The .LDIF file
+ is based on the adprep Schema updates provided by Microsoft.
+ """
+ count = 0
+ ldif_op = ldif_schema_update()
+
+ # parse the file line by line and work out each update operation to apply
+ for line in ldif_file:
+
+ line = line.rstrip()
+
+ # the operations in the .LDIF file are separated by blank lines. If
+ # we hit a blank line, try to apply the update we've parsed so far
+ if line == '':
+
+ # keep going if we haven't parsed anything yet
+ if ldif_op.ldif == '':
+ continue
+
+ # Apply the individual change
+ count += ldif_op.apply(samdb)
+
+ # start storing the next operation from scratch again
+ ldif_op = ldif_schema_update()
+ continue
+
+ # replace the placeholder domain name in the .ldif file with the real domain
+ if line.upper().endswith('DC=X'):
+ line = line[:-len('DC=X')] + str(samdb.get_default_basedn())
+ elif line.upper().endswith('CN=X'):
+ line = line[:-len('CN=X')] + str(samdb.get_default_basedn())
+
+ values = line.split(':')
+
+ if values[0].lower() == 'dn':
+ ldif_op.dn = values[1].strip()
+
+ # replace the Windows-specific operation with the Samba one
+ if values[0].lower() == 'changetype':
+ line = line.lower().replace(': ntdsschemaadd',
+ ': add')
+ line = line.lower().replace(': ntdsschemamodify',
+ ': modify')
+
+ if values[0].lower() in ['rdnattid', 'subclassof',
+ 'systemposssuperiors',
+ 'systemmaycontain',
+ 'systemauxiliaryclass']:
+ _, value = values
+
+ # The Microsoft updates contain some OIDs we don't recognize.
+ # Query the DB to see if we can work out the OID this update is
+ # referring to. If we find a match, then replace the OID with
+ # the ldapDisplayname
+ if '.' in value:
+ res = samdb.search(base=samdb.get_schema_basedn(),
+ expression="(|(attributeId=%s)(governsId=%s))" %
+ (value, value),
+ attrs=['ldapDisplayName'])
+
+ if len(res) != 1:
+ ldif_op.unknown_oid = value
+ else:
+ display_name = res[0]['ldapDisplayName'][0]
+ line = line.replace(value, ' ' + display_name)
+
+ # Microsoft has marked objects as defunct that Samba doesn't know about
+ if values[0].lower() == 'isdefunct' and values[1].strip().lower() == 'true':
+ ldif_op.is_defunct = True
+
+ # Samba has added the showInAdvancedViewOnly attribute to all objects,
+ # so rather than doing an add, we need to do a replace
+ if values[0].lower() == 'add' and values[1].strip().lower() == 'showinadvancedviewonly':
+ line = 'replace: showInAdvancedViewOnly'
+
+ # Add the line to the current LDIF operation (including the newline
+ # we stripped off at the start of the loop)
+ ldif_op.ldif += line + '\n'
+
+ return count
+
+
+ def _apply_update(self, samdb, update_file, base_dir):
+ """Wrapper function for parsing an LDIF file and applying the updates"""
+
+ print("Applying %s updates..." % update_file)
+
+ ldif_file = None
+ try:
+ ldif_file = open(os.path.join(base_dir, update_file))
+
+ count = self._apply_updates_in_file(samdb, ldif_file)
+
+ finally:
+ if ldif_file:
+ ldif_file.close()
+
+ print("%u changes applied" % count)
+
+ return count
+
+ def run(self, **kwargs):
+ from samba.ms_schema_markdown import read_ms_markdown
+ from samba.schema import Schema
+
+ updates_allowed_overriden = False
+ sambaopts = kwargs.get("sambaopts")
+ credopts = kwargs.get("credopts")
+ versionpts = kwargs.get("versionopts")
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+ H = kwargs.get("H")
+ target_schema = kwargs.get("schema")
+ ldf_files = kwargs.get("ldf_file")
+ base_dir = kwargs.get("base_dir")
+
+ temp_folder = None
+
+ samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
+
+ # we're not going to get far if the config doesn't allow schema updates
+ if lp.get("dsdb:schema update allowed") is None:
+ lp.set("dsdb:schema update allowed", "yes")
+ print("Temporarily overriding 'dsdb:schema update allowed' setting")
+ updates_allowed_overriden = True
+
+ own_dn = ldb.Dn(samdb, samdb.get_dsServiceName())
+ master = get_fsmo_roleowner(samdb, str(samdb.get_schema_basedn()),
+ 'schema')
+ if own_dn != master:
+ raise CommandError("This server is not the schema master.")
+
+ # if specific LDIF files were specified, just apply them
+ if ldf_files:
+ schema_updates = ldf_files.split(",")
+ else:
+ schema_updates = []
+
+ # work out the version of the target schema we're upgrading to
+ end = Schema.get_version(target_schema)
+
+ # work out the version of the schema we're currently using
+ res = samdb.search(base=samdb.get_schema_basedn(),
+ scope=ldb.SCOPE_BASE, attrs=['objectVersion'])
+
+ if len(res) != 1:
+ raise CommandError('Could not determine current schema version')
+ start = int(res[0]['objectVersion'][0]) + 1
+
+ diff_dir = setup_path("adprep/WindowsServerDocs")
+ if base_dir is None:
+ # Read from the Schema-Updates.md file
+ temp_folder = tempfile.mkdtemp()
+
+ update_file = setup_path("adprep/WindowsServerDocs/Schema-Updates.md")
+
+ try:
+ read_ms_markdown(update_file, temp_folder)
+ except Exception as e:
+ print("Exception in markdown parsing: %s" % e)
+ shutil.rmtree(temp_folder)
+ raise CommandError('Failed to upgrade schema')
+
+ base_dir = temp_folder
+
+ for version in range(start, end + 1):
+ update = 'Sch%d.ldf' % version
+ schema_updates.append(update)
+
+ # Apply patches if we parsed the Schema-Updates.md file
+ diff = os.path.abspath(os.path.join(diff_dir, update + '.diff'))
+ if temp_folder and os.path.exists(diff):
+ try:
+ p = subprocess.Popen(['patch', update, '-i', diff],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, cwd=temp_folder)
+ except (OSError, IOError):
+ shutil.rmtree(temp_folder)
+ raise CommandError("Failed to upgrade schema. Check if 'patch' is installed.")
+
+ stdout, stderr = p.communicate()
+
+ if p.returncode:
+ print("Exception in patch: %s\n%s" % (stdout, stderr))
+ shutil.rmtree(temp_folder)
+ raise CommandError('Failed to upgrade schema')
+
+ print("Patched %s using %s" % (update, diff))
+
+ if base_dir is None:
+ base_dir = setup_path("adprep")
+
+ samdb.transaction_start()
+ count = 0
+ error_encountered = False
+
+ try:
+ # Apply the schema updates needed to move to the new schema version
+ for ldif_file in schema_updates:
+ count += self._apply_update(samdb, ldif_file, base_dir)
+
+ if count > 0:
+ samdb.transaction_commit()
+ print("Schema successfully updated")
+ else:
+ print("No changes applied to schema")
+ samdb.transaction_cancel()
+ except Exception as e:
+ print("Exception: %s" % e)
+ print("Error encountered, aborting schema upgrade")
+ samdb.transaction_cancel()
+ error_encountered = True
+
+ if updates_allowed_overriden:
+ lp.set("dsdb:schema update allowed", "no")
+
+ if temp_folder:
+ shutil.rmtree(temp_folder)
+
+ if error_encountered:
+ raise CommandError('Failed to upgrade schema')
+
+class cmd_domain_functional_prep(Command):
+ """Domain functional level preparation"""
+
+ synopsis = "%prog [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option("-H", "--URL", help="LDB URL for database or target server", type=str,
+ metavar="URL", dest="H"),
+ Option("-q", "--quiet", help="Be quiet", action="store_true"),
+ Option("-v", "--verbose", help="Be verbose", action="store_true"),
+ Option("--function-level", type="choice", metavar="FUNCTION_LEVEL",
+ choices=["2008_R2", "2012", "2012_R2"],
+ help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
+ default="2012_R2"),
+ Option("--forest-prep", action="store_true",
+ help="Run the forest prep (by default, both the domain and forest prep are run)."),
+ Option("--domain-prep", action="store_true",
+ help="Run the domain prep (by default, both the domain and forest prep are run).")
+ ]
+
+ def run(self, **kwargs):
+ updates_allowed_overriden = False
+ sambaopts = kwargs.get("sambaopts")
+ credopts = kwargs.get("credopts")
+ versionpts = kwargs.get("versionopts")
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+ H = kwargs.get("H")
+ target_level = string_version_to_constant[kwargs.get("function_level")]
+ forest_prep = kwargs.get("forest_prep")
+ domain_prep = kwargs.get("domain_prep")
+
+ samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
+
+ # we're not going to get far if the config doesn't allow schema updates
+ if lp.get("dsdb:schema update allowed") is None:
+ lp.set("dsdb:schema update allowed", "yes")
+ print("Temporarily overriding 'dsdb:schema update allowed' setting")
+ updates_allowed_overriden = True
+
+ if forest_prep is None and domain_prep is None:
+ forest_prep = True
+ domain_prep = True
+
+ own_dn = ldb.Dn(samdb, samdb.get_dsServiceName())
+ if forest_prep:
+ master = get_fsmo_roleowner(samdb, str(samdb.get_schema_basedn()),
+ 'schema')
+ if own_dn != master:
+ raise CommandError("This server is not the schema master.")
+
+ if domain_prep:
+ domain_dn = samdb.domain_dn()
+ infrastructure_dn = "CN=Infrastructure," + domain_dn
+ master = get_fsmo_roleowner(samdb, infrastructure_dn,
+ 'infrastructure')
+ if own_dn != master:
+ raise CommandError("This server is not the infrastructure master.")
+
+ if forest_prep:
+ samdb.transaction_start()
+ error_encountered = False
+ try:
+ from samba.forest_update import ForestUpdate
+ forest = ForestUpdate(samdb, fix=True)
+
+ forest.check_updates_iterator([53, 79, 80, 81, 82, 83])
+ forest.check_updates_functional_level(target_level,
+ DS_DOMAIN_FUNCTION_2008_R2,
+ update_revision=True)
+
+ samdb.transaction_commit()
+ except Exception as e:
+ print("Exception: %s" % e)
+ samdb.transaction_cancel()
+ error_encountered = True
+
+ if domain_prep:
+ samdb.transaction_start()
+ error_encountered = False
+ try:
+ from samba.domain_update import DomainUpdate
+
+ domain = DomainUpdate(samdb, fix=True)
+ domain.check_updates_functional_level(target_level,
+ DS_DOMAIN_FUNCTION_2008,
+ update_revision=True)
+
+ samdb.transaction_commit()
+ except Exception as e:
+ print("Exception: %s" % e)
+ samdb.transaction_cancel()
+ error_encountered = True
+
+ if updates_allowed_overriden:
+ lp.set("dsdb:schema update allowed", "no")
+
+ if error_encountered:
+ raise CommandError('Failed to perform functional prep')