3 # Copyright Matthias Dieter Wallnoefer 2009
4 # Copyright Andrew Kroeger 2009
5 # Copyright Jelmer Vernooij 2007-2012
6 # Copyright Giampaolo Lauria 2011
7 # Copyright Matthieu Patou <mat@matws.net> 2011
8 # Copyright Andrew Bartlett 2008-2015
9 # Copyright Stefan Metzmacher 2012
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import samba.getopt as options
37 from samba import ntstatus
38 from samba import NTSTATUSError
39 from samba import werror
40 from getpass import getpass
41 from samba.net import Net, LIBNET_JOIN_AUTOMATIC
43 from samba.join import join_RODC, join_DC, join_subdomain
44 from samba.auth import system_session
45 from samba.samdb import SamDB
46 from samba.ndr import ndr_unpack, ndr_pack, ndr_print
47 from samba.dcerpc import drsuapi
48 from samba.dcerpc import drsblobs
49 from samba.dcerpc import lsa
50 from samba.dcerpc import netlogon
51 from samba.dcerpc import security
52 from samba.dcerpc import nbt
53 from samba.dcerpc import misc
54 from samba.dcerpc.samr import DOMAIN_PASSWORD_COMPLEX, DOMAIN_PASSWORD_STORE_CLEARTEXT
55 from samba.netcmd import (
61 from samba.netcmd.fsmo import get_fsmo_roleowner
62 from samba.netcmd.common import netcmd_get_domain_infos_via_cldap
63 from samba.samba3 import Samba3
64 from samba.samba3 import param as s3param
65 from samba.upgrade import upgrade_from_samba3
66 from samba.drs_utils import (
67 sendDsReplicaSync, drsuapi_connect, drsException,
69 from samba import remove_dc, arcfour_encrypt, string_to_byte_array
71 from samba.dsdb import (
72 DS_DOMAIN_FUNCTION_2000,
73 DS_DOMAIN_FUNCTION_2003,
74 DS_DOMAIN_FUNCTION_2003_MIXED,
75 DS_DOMAIN_FUNCTION_2008,
76 DS_DOMAIN_FUNCTION_2008_R2,
77 DS_DOMAIN_FUNCTION_2012,
78 DS_DOMAIN_FUNCTION_2012_R2,
79 DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL,
80 DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL,
81 UF_WORKSTATION_TRUST_ACCOUNT,
82 UF_SERVER_TRUST_ACCOUNT,
83 UF_TRUSTED_FOR_DELEGATION,
84 UF_PARTIAL_SECRETS_ACCOUNT
87 from samba.provision import (
90 DEFAULT_MIN_PWD_LENGTH,
94 from samba.provision.common import (
100 string_version_to_constant = {
101 "2012": DS_DOMAIN_FUNCTION_2012,
102 "2012_R2": DS_DOMAIN_FUNCTION_2012_R2,
105 def get_testparm_var(testparm, smbconf, varname):
106 errfile = open(os.devnull, 'w')
107 p = subprocess.Popen([testparm, '-s', '-l',
108 '--parameter-name=%s' % varname, smbconf],
109 stdout=subprocess.PIPE, stderr=errfile)
110 (out,err) = p.communicate()
112 lines = out.split('\n')
114 return lines[0].strip()
118 import samba.dckeytab
120 cmd_domain_export_keytab = None
122 class cmd_domain_export_keytab(Command):
123 """Dump Kerberos keys of the domain into a keytab."""
125 synopsis = "%prog <keytab> [options]"
127 takes_optiongroups = {
128 "sambaopts": options.SambaOptions,
129 "credopts": options.CredentialsOptions,
130 "versionopts": options.VersionOptions,
134 Option("--principal", help="extract only this principal", type=str),
137 takes_args = ["keytab"]
139 def run(self, keytab, credopts=None, sambaopts=None, versionopts=None, principal=None):
140 lp = sambaopts.get_loadparm()
142 net.export_keytab(keytab=keytab, principal=principal)
145 class cmd_domain_info(Command):
146 """Print basic info about a domain and the DC passed as parameter."""
148 synopsis = "%prog <ip_address> [options]"
153 takes_optiongroups = {
154 "sambaopts": options.SambaOptions,
155 "credopts": options.CredentialsOptions,
156 "versionopts": options.VersionOptions,
159 takes_args = ["address"]
161 def run(self, address, credopts=None, sambaopts=None, versionopts=None):
162 lp = sambaopts.get_loadparm()
164 res = netcmd_get_domain_infos_via_cldap(lp, None, address)
166 raise CommandError("Invalid IP address '" + address + "'!")
167 self.outf.write("Forest : %s\n" % res.forest)
168 self.outf.write("Domain : %s\n" % res.dns_domain)
169 self.outf.write("Netbios domain : %s\n" % res.domain_name)
170 self.outf.write("DC name : %s\n" % res.pdc_dns_name)
171 self.outf.write("DC netbios name : %s\n" % res.pdc_name)
172 self.outf.write("Server site : %s\n" % res.server_site)
173 self.outf.write("Client site : %s\n" % res.client_site)
176 class cmd_domain_provision(Command):
177 """Provision a domain."""
179 synopsis = "%prog [options]"
181 takes_optiongroups = {
182 "sambaopts": options.SambaOptions,
183 "versionopts": options.VersionOptions,
187 Option("--interactive", help="Ask for names", action="store_true"),
188 Option("--domain", type="string", metavar="DOMAIN",
189 help="NetBIOS domain name to use"),
190 Option("--domain-guid", type="string", metavar="GUID",
191 help="set domainguid (otherwise random)"),
192 Option("--domain-sid", type="string", metavar="SID",
193 help="set domainsid (otherwise random)"),
194 Option("--ntds-guid", type="string", metavar="GUID",
195 help="set NTDS object GUID (otherwise random)"),
196 Option("--invocationid", type="string", metavar="GUID",
197 help="set invocationid (otherwise random)"),
198 Option("--host-name", type="string", metavar="HOSTNAME",
199 help="set hostname"),
200 Option("--host-ip", type="string", metavar="IPADDRESS",
201 help="set IPv4 ipaddress"),
202 Option("--host-ip6", type="string", metavar="IP6ADDRESS",
203 help="set IPv6 ipaddress"),
204 Option("--site", type="string", metavar="SITENAME",
205 help="set site name"),
206 Option("--adminpass", type="string", metavar="PASSWORD",
207 help="choose admin password (otherwise random)"),
208 Option("--krbtgtpass", type="string", metavar="PASSWORD",
209 help="choose krbtgt password (otherwise random)"),
210 Option("--machinepass", type="string", metavar="PASSWORD",
211 help="choose machine password (otherwise random)"),
212 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
213 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
214 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
215 "BIND9_FLATFILE uses bind9 text database to store zone information, "
216 "BIND9_DLZ uses samba4 AD to store zone information, "
217 "NONE skips the DNS setup entirely (not recommended)",
218 default="SAMBA_INTERNAL"),
219 Option("--dnspass", type="string", metavar="PASSWORD",
220 help="choose dns password (otherwise random)"),
221 Option("--ldapadminpass", type="string", metavar="PASSWORD",
222 help="choose password to set between Samba and its LDAP backend (otherwise random)"),
223 Option("--root", type="string", metavar="USERNAME",
224 help="choose 'root' unix username"),
225 Option("--nobody", type="string", metavar="USERNAME",
226 help="choose 'nobody' user"),
227 Option("--users", type="string", metavar="GROUPNAME",
228 help="choose 'users' group"),
229 Option("--quiet", help="Be quiet", action="store_true"),
230 Option("--blank", action="store_true",
231 help="do not add users or groups, just the structure"),
232 Option("--ldap-backend-type", type="choice", metavar="LDAP-BACKEND-TYPE",
233 help="Test initialisation support for unsupported LDAP backend type (fedora-ds or openldap) DO NOT USE",
234 choices=["fedora-ds", "openldap"]),
235 Option("--server-role", type="choice", metavar="ROLE",
236 choices=["domain controller", "dc", "member server", "member", "standalone"],
237 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
238 default="domain controller"),
239 Option("--function-level", type="choice", metavar="FOR-FUN-LEVEL",
240 choices=["2000", "2003", "2008", "2008_R2"],
241 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2008_R2 Native.",
243 Option("--base-schema", type="choice", metavar="BASE-SCHEMA",
244 choices=["2008_R2", "2008_R2_old", "2012", "2012_R2"],
245 help="The base schema files to use. Default is (Windows) 2008_R2.",
247 Option("--next-rid", type="int", metavar="NEXTRID", default=1000,
248 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
249 Option("--partitions-only",
250 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"),
251 Option("--targetdir", type="string", metavar="DIR",
252 help="Set target directory"),
253 Option("--ol-mmr-urls", type="string", metavar="LDAPSERVER",
254 help="List of LDAP-URLS [ ldap://<FQHN>:<PORT>/ (where <PORT> has to be different than 389!) ] separated with comma (\",\") for use with OpenLDAP-MMR (Multi-Master-Replication), e.g.: \"ldap://s4dc1:9000,ldap://s4dc2:9000\""),
255 Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
256 Option("--plaintext-secrets", action="store_true",
257 help="Store secret/sensitive values as plain text on disk" +
258 "(default is to encrypt secret/ensitive values)"),
262 Option("--ldap-dryrun-mode", help="Configure LDAP backend, but do not run any binaries and exit early. Used only for the test environment. DO NOT USE",
263 action="store_true"),
264 Option("--slapd-path", type="string", metavar="SLAPD-PATH",
265 help="Path to slapd for LDAP backend [e.g.:'/usr/local/libexec/slapd']. Required for Setup with LDAP-Backend. OpenLDAP Version >= 2.4.17 should be used."),
266 Option("--ldap-backend-extra-port", type="int", metavar="LDAP-BACKEND-EXTRA-PORT", help="Additional TCP port for LDAP backend server (to use for replication)"),
267 Option("--ldap-backend-forced-uri", type="string", metavar="LDAP-BACKEND-FORCED-URI",
268 help="Force the LDAP backend connection to be to a particular URI. Use this ONLY for 'existing' backends, or when debugging the interaction with the LDAP backend and you need to intercept the LDA"),
269 Option("--ldap-backend-nosync", help="Configure LDAP backend not to call fsync() (for performance in test environments)", action="store_true"),
273 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
274 Option("--use-xattrs", type="choice", choices=["yes","no","auto"],
275 metavar="[yes|no|auto]",
276 help="Define if we should use the native fs capabilities or a tdb file for "
277 "storing attributes likes ntacl when --use-ntvfs is set. "
278 "auto tries to make an inteligent guess based on the user rights and system capabilities",
282 if os.getenv('TEST_LDAP', "no") == "yes":
283 takes_options.extend(openldap_options)
285 if samba.is_ntvfs_fileserver_built():
286 takes_options.extend(ntvfs_options)
290 def run(self, sambaopts=None, versionopts=None,
313 ldap_backend_type=None,
317 partitions_only=None,
324 ldap_backend_nosync=None,
325 ldap_backend_extra_port=None,
326 ldap_backend_forced_uri=None,
327 ldap_dryrun_mode=None,
329 plaintext_secrets=False):
331 self.logger = self.get_logger("provision")
333 self.logger.setLevel(logging.WARNING)
335 self.logger.setLevel(logging.INFO)
337 lp = sambaopts.get_loadparm()
338 smbconf = lp.configfile
340 if dns_forwarder is not None:
341 suggested_forwarder = dns_forwarder
343 suggested_forwarder = self._get_nameserver_ip()
344 if suggested_forwarder is None:
345 suggested_forwarder = "none"
347 if len(self.raw_argv) == 1:
351 from getpass import getpass
354 def ask(prompt, default=None):
355 if default is not None:
356 print "%s [%s]: " % (prompt, default),
358 print "%s: " % (prompt,),
359 return sys.stdin.readline().rstrip("\n") or default
362 default = socket.getfqdn().split(".", 1)[1].upper()
365 realm = ask("Realm", default)
366 if realm in (None, ""):
367 raise CommandError("No realm set!")
370 default = realm.split(".")[0]
373 domain = ask("Domain", default)
375 raise CommandError("No domain set!")
377 server_role = ask("Server Role (dc, member, standalone)", "dc")
379 dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
380 if dns_backend in (None, ''):
381 raise CommandError("No DNS backend set!")
383 if dns_backend == "SAMBA_INTERNAL":
384 dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
385 if dns_forwarder.lower() in (None, 'none'):
386 suggested_forwarder = None
390 adminpassplain = getpass("Administrator password: ")
391 issue = self._adminpass_issue(adminpassplain)
393 self.errf.write("%s.\n" % issue)
395 adminpassverify = getpass("Retype password: ")
396 if not adminpassplain == adminpassverify:
397 self.errf.write("Sorry, passwords do not match.\n")
399 adminpass = adminpassplain
403 realm = sambaopts._lp.get('realm')
405 raise CommandError("No realm set!")
407 raise CommandError("No domain set!")
410 issue = self._adminpass_issue(adminpass)
412 raise CommandError(issue)
414 self.logger.info("Administrator password will be set randomly!")
416 if function_level == "2000":
417 dom_for_fun_level = DS_DOMAIN_FUNCTION_2000
418 elif function_level == "2003":
419 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
420 elif function_level == "2008":
421 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
422 elif function_level == "2008_R2":
423 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
425 if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
426 dns_forwarder = suggested_forwarder
428 samdb_fill = FILL_FULL
430 samdb_fill = FILL_NT4SYNC
431 elif partitions_only:
432 samdb_fill = FILL_DRS
434 if targetdir is not None:
435 if not os.path.isdir(targetdir):
440 if use_xattrs == "yes":
442 elif use_xattrs == "auto" and use_ntvfs == False:
444 elif use_ntvfs == False:
445 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
446 "Please re-run with --use-xattrs omitted.")
447 elif use_xattrs == "auto" and not lp.get("posix:eadb"):
449 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
451 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
454 samba.ntacls.setntacl(lp, file.name,
455 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
458 self.logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. ")
463 self.logger.info("not using extended attributes to store ACLs and other metadata. If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
464 if ldap_backend_type == "existing":
465 if ldap_backend_forced_uri is not None:
466 self.logger.warn("You have specified to use an existing LDAP server as the backend, please make sure an LDAP server is running at %s" % ldap_backend_forced_uri)
468 self.logger.info("You have specified to use an existing LDAP server as the backend, please make sure an LDAP server is running at the default location")
470 if ldap_backend_forced_uri is not None:
471 self.logger.warn("You have specified to use an fixed URI %s for connecting to your LDAP server backend. This is NOT RECOMMENDED, as our default communiation over ldapi:// is more secure and much less")
473 if domain_sid is not None:
474 domain_sid = security.dom_sid(domain_sid)
476 session = system_session()
478 result = provision(self.logger,
479 session, smbconf=smbconf, targetdir=targetdir,
480 samdb_fill=samdb_fill, realm=realm, domain=domain,
481 domainguid=domain_guid, domainsid=domain_sid,
483 hostip=host_ip, hostip6=host_ip6,
484 sitename=site, ntdsguid=ntds_guid,
485 invocationid=invocationid, adminpass=adminpass,
486 krbtgtpass=krbtgtpass, machinepass=machinepass,
487 dns_backend=dns_backend, dns_forwarder=dns_forwarder,
488 dnspass=dnspass, root=root, nobody=nobody,
490 serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
491 backend_type=ldap_backend_type,
492 ldapadminpass=ldapadminpass, ol_mmr_urls=ol_mmr_urls, slapd_path=slapd_path,
493 useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
494 use_rfc2307=use_rfc2307, skip_sysvolacl=False,
495 ldap_backend_extra_port=ldap_backend_extra_port,
496 ldap_backend_forced_uri=ldap_backend_forced_uri,
497 nosync=ldap_backend_nosync, ldap_dryrun_mode=ldap_dryrun_mode,
498 base_schema=base_schema,
499 plaintext_secrets=plaintext_secrets)
501 except ProvisioningError, e:
502 raise CommandError("Provision failed", e)
504 result.report_logger(self.logger)
506 def _get_nameserver_ip(self):
507 """Grab the nameserver IP address from /etc/resolv.conf."""
509 RESOLV_CONF="/etc/resolv.conf"
511 if not path.isfile(RESOLV_CONF):
512 self.logger.warning("Failed to locate %s" % RESOLV_CONF)
517 handle = open(RESOLV_CONF, 'r')
519 if not line.startswith('nameserver'):
521 # we want the last non-space continuous string of the line
522 return line.strip().split()[-1]
524 if handle is not None:
527 self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
529 def _adminpass_issue(self, adminpass):
530 """Returns error string for a bad administrator password,
531 or None if acceptable"""
533 if len(adminpass.decode('utf-8')) < DEFAULT_MIN_PWD_LENGTH:
534 return "Administrator password does not meet the default minimum" \
535 " password length requirement (%d characters)" \
536 % DEFAULT_MIN_PWD_LENGTH
537 elif not samba.check_password_quality(adminpass):
538 return "Administrator password does not meet the default" \
544 class cmd_domain_dcpromo(Command):
545 """Promote an existing domain member or NT4 PDC to an AD DC."""
547 synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
549 takes_optiongroups = {
550 "sambaopts": options.SambaOptions,
551 "versionopts": options.VersionOptions,
552 "credopts": options.CredentialsOptions,
556 Option("--server", help="DC to join", type=str),
557 Option("--site", help="site to join", type=str),
558 Option("--targetdir", help="where to store provision", type=str),
559 Option("--domain-critical-only",
560 help="only replicate critical domain objects",
561 action="store_true"),
562 Option("--machinepass", type=str, metavar="PASSWORD",
563 help="choose machine password (otherwise random)"),
564 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
565 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
566 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
567 "BIND9_DLZ uses samba4 AD to store zone information, "
568 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
569 default="SAMBA_INTERNAL"),
570 Option("--quiet", help="Be quiet", action="store_true"),
571 Option("--verbose", help="Be verbose", action="store_true")
575 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
578 if samba.is_ntvfs_fileserver_built():
579 takes_options.extend(ntvfs_options)
582 takes_args = ["domain", "role?"]
584 def run(self, domain, role=None, sambaopts=None, credopts=None,
585 versionopts=None, server=None, site=None, targetdir=None,
586 domain_critical_only=False, parent_domain=None, machinepass=None,
587 use_ntvfs=False, dns_backend=None,
588 quiet=False, verbose=False):
589 lp = sambaopts.get_loadparm()
590 creds = credopts.get_credentials(lp)
591 net = Net(creds, lp, server=credopts.ipaddress)
593 logger = self.get_logger()
595 logger.setLevel(logging.DEBUG)
597 logger.setLevel(logging.WARNING)
599 logger.setLevel(logging.INFO)
601 netbios_name = lp.get("netbios name")
607 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
608 site=site, netbios_name=netbios_name, targetdir=targetdir,
609 domain_critical_only=domain_critical_only,
610 machinepass=machinepass, use_ntvfs=use_ntvfs,
611 dns_backend=dns_backend,
612 promote_existing=True)
614 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
615 site=site, netbios_name=netbios_name, targetdir=targetdir,
616 domain_critical_only=domain_critical_only,
617 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
618 promote_existing=True)
620 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
623 class cmd_domain_join(Command):
624 """Join domain as either member or backup domain controller."""
626 synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
628 takes_optiongroups = {
629 "sambaopts": options.SambaOptions,
630 "versionopts": options.VersionOptions,
631 "credopts": options.CredentialsOptions,
635 Option("--server", help="DC to join", type=str),
636 Option("--site", help="site to join", type=str),
637 Option("--targetdir", help="where to store provision", type=str),
638 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
639 Option("--domain-critical-only",
640 help="only replicate critical domain objects",
641 action="store_true"),
642 Option("--machinepass", type=str, metavar="PASSWORD",
643 help="choose machine password (otherwise random)"),
644 Option("--adminpass", type="string", metavar="PASSWORD",
645 help="choose adminstrator password when joining as a subdomain (otherwise random)"),
646 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
647 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
648 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
649 "BIND9_DLZ uses samba4 AD to store zone information, "
650 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
651 default="SAMBA_INTERNAL"),
652 Option("--plaintext-secrets", action="store_true",
653 help="Store secret/sensitive values as plain text on disk" +
654 "(default is to encrypt secret/ensitive values)"),
655 Option("--quiet", help="Be quiet", action="store_true"),
656 Option("--verbose", help="Be verbose", action="store_true")
660 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
663 if samba.is_ntvfs_fileserver_built():
664 takes_options.extend(ntvfs_options)
666 takes_args = ["domain", "role?"]
668 def run(self, domain, role=None, sambaopts=None, credopts=None,
669 versionopts=None, server=None, site=None, targetdir=None,
670 domain_critical_only=False, parent_domain=None, machinepass=None,
671 use_ntvfs=False, dns_backend=None, adminpass=None,
672 quiet=False, verbose=False, plaintext_secrets=False):
673 lp = sambaopts.get_loadparm()
674 creds = credopts.get_credentials(lp)
675 net = Net(creds, lp, server=credopts.ipaddress)
678 site = "Default-First-Site-Name"
680 logger = self.get_logger()
682 logger.setLevel(logging.DEBUG)
684 logger.setLevel(logging.WARNING)
686 logger.setLevel(logging.INFO)
688 netbios_name = lp.get("netbios name")
693 if role is None or role == "MEMBER":
694 (join_password, sid, domain_name) = net.join_member(
695 domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
696 machinepass=machinepass)
698 self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
700 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
701 site=site, netbios_name=netbios_name, targetdir=targetdir,
702 domain_critical_only=domain_critical_only,
703 machinepass=machinepass, use_ntvfs=use_ntvfs,
704 dns_backend=dns_backend,
705 plaintext_secrets=plaintext_secrets)
707 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
708 site=site, netbios_name=netbios_name, targetdir=targetdir,
709 domain_critical_only=domain_critical_only,
710 machinepass=machinepass, use_ntvfs=use_ntvfs,
711 dns_backend=dns_backend,
712 plaintext_secrets=plaintext_secrets)
713 elif role == "SUBDOMAIN":
715 logger.info("Administrator password will be set randomly!")
717 netbios_domain = lp.get("workgroup")
718 if parent_domain is None:
719 parent_domain = ".".join(domain.split(".")[1:])
720 join_subdomain(logger=logger, server=server, creds=creds, lp=lp, dnsdomain=domain,
721 parent_domain=parent_domain, site=site,
722 netbios_name=netbios_name, netbios_domain=netbios_domain,
723 targetdir=targetdir, machinepass=machinepass,
724 use_ntvfs=use_ntvfs, dns_backend=dns_backend,
726 plaintext_secrets=plaintext_secrets)
728 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role)
731 class cmd_domain_demote(Command):
732 """Demote ourselves from the role of Domain Controller."""
734 synopsis = "%prog [options]"
737 Option("--server", help="writable DC to write demotion changes on", type=str),
738 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
739 metavar="URL", dest="H"),
740 Option("--remove-other-dead-server", help="Dead DC (name or NTDS GUID) "
741 "to remove ALL references to (rather than this DC)", type=str),
742 Option("--quiet", help="Be quiet", action="store_true"),
743 Option("--verbose", help="Be verbose", action="store_true"),
746 takes_optiongroups = {
747 "sambaopts": options.SambaOptions,
748 "credopts": options.CredentialsOptions,
749 "versionopts": options.VersionOptions,
752 def run(self, sambaopts=None, credopts=None,
753 versionopts=None, server=None,
754 remove_other_dead_server=None, H=None,
755 verbose=False, quiet=False):
756 lp = sambaopts.get_loadparm()
757 creds = credopts.get_credentials(lp)
758 net = Net(creds, lp, server=credopts.ipaddress)
760 logger = self.get_logger()
762 logger.setLevel(logging.DEBUG)
764 logger.setLevel(logging.WARNING)
766 logger.setLevel(logging.INFO)
768 if remove_other_dead_server is not None:
769 if server is not None:
770 samdb = SamDB(url="ldap://%s" % server,
771 session_info=system_session(),
772 credentials=creds, lp=lp)
774 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
776 remove_dc.remove_dc(samdb, logger, remove_other_dead_server)
777 except remove_dc.DemoteException as err:
778 raise CommandError("Demote failed: %s" % err)
781 netbios_name = lp.get("netbios name")
782 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
784 res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
786 raise CommandError("Unable to search for servers")
789 raise CommandError("You are the latest server in the domain")
793 if str(e["name"]).lower() != netbios_name.lower():
794 server = e["dnsHostName"]
797 ntds_guid = samdb.get_ntds_GUID()
798 msg = samdb.search(base=str(samdb.get_config_basedn()),
799 scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
801 if len(msg) == 0 or "options" not in msg[0]:
802 raise CommandError("Failed to find options on %s" % ntds_guid)
805 dsa_options = int(str(msg[0]['options']))
807 res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
808 controls=["search_options:1:2"])
811 raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res))
813 self.errf.write("Using %s as partner server for the demotion\n" %
815 (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
817 self.errf.write("Deactivating inbound replication\n")
822 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
823 dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
824 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
828 self.errf.write("Asking partner server %s to synchronize from us\n"
830 for part in (samdb.get_schema_basedn(),
831 samdb.get_config_basedn(),
832 samdb.get_root_basedn()):
833 nc = drsuapi.DsReplicaObjectIdentifier()
836 req1 = drsuapi.DsReplicaSyncRequest1()
837 req1.naming_context = nc;
838 req1.options = drsuapi.DRSUAPI_DRS_WRIT_REP
839 req1.source_dsa_guid = misc.GUID(ntds_guid)
842 drsuapiBind.DsReplicaSync(drsuapi_handle, 1, req1)
843 except RuntimeError as (werr, string):
844 if werr == werror.WERR_DS_DRA_NO_REPLICA:
848 "Error while replicating out last local changes from '%s' for demotion, "
849 "re-enabling inbound replication\n" % part)
850 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
851 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
853 raise CommandError("Error while sending a DsReplicaSync for partition '%s'" % str(part), string)
855 remote_samdb = SamDB(url="ldap://%s" % server,
856 session_info=system_session(),
857 credentials=creds, lp=lp)
859 self.errf.write("Changing userControl and container\n")
860 res = remote_samdb.search(base=str(remote_samdb.domain_dn()),
861 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
862 netbios_name.upper(),
863 attrs=["userAccountControl"])
865 uac = int(str(res[0]["userAccountControl"]))
868 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
870 "Error while demoting, re-enabling inbound replication\n")
871 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
872 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
874 raise CommandError("Error while changing account control", e)
877 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
879 "Error while demoting, re-enabling inbound replication")
880 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
881 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
883 raise CommandError("Unable to find object with samaccountName = %s$"
884 " in the remote dc" % netbios_name.upper())
888 uac &= ~(UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION|UF_PARTIAL_SECRETS_ACCOUNT)
889 uac |= UF_WORKSTATION_TRUST_ACCOUNT
894 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
895 ldb.FLAG_MOD_REPLACE,
896 "userAccountControl")
898 remote_samdb.modify(msg)
900 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
902 "Error while demoting, re-enabling inbound replication")
903 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
904 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
907 raise CommandError("Error while changing account control", e)
909 parent = msg.dn.parent()
910 dc_name = res[0].dn.get_rdn_value()
911 rdn = "CN=%s" % dc_name
913 # Let's move to the Computer container
917 computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.domain_dn()))
918 res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
921 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
922 scope=ldb.SCOPE_ONELEVEL)
923 while(len(res) != 0 and i < 100):
925 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
926 scope=ldb.SCOPE_ONELEVEL)
929 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
931 "Error while demoting, re-enabling inbound replication\n")
932 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
933 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
939 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
940 ldb.FLAG_MOD_REPLACE,
941 "userAccountControl")
943 remote_samdb.modify(msg)
945 raise CommandError("Unable to find a slot for renaming %s,"
946 " all names from %s-1 to %s-%d seemed used" %
947 (str(dc_dn), rdn, rdn, i - 9))
949 newrdn = "%s-%d" % (rdn, i)
952 newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
953 remote_samdb.rename(dc_dn, newdn)
955 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
957 "Error while demoting, re-enabling inbound replication\n")
958 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
959 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
965 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
966 ldb.FLAG_MOD_REPLACE,
967 "userAccountControl")
969 remote_samdb.modify(msg)
970 raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
973 server_dsa_dn = samdb.get_serverName()
974 domain = remote_samdb.get_root_basedn()
977 req1 = drsuapi.DsRemoveDSServerRequest1()
978 req1.server_dn = str(server_dsa_dn)
979 req1.domain_dn = str(domain)
982 drsuapiBind.DsRemoveDSServer(drsuapi_handle, 1, req1)
983 except RuntimeError as (werr, string):
984 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
986 "Error while demoting, re-enabling inbound replication\n")
987 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
988 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
994 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
995 ldb.FLAG_MOD_REPLACE,
996 "userAccountControl")
997 remote_samdb.modify(msg)
998 remote_samdb.rename(newdn, dc_dn)
999 if werr == werror.WERR_DS_DRA_NO_REPLICA:
1000 raise CommandError("The DC %s is not present on (already removed from) the remote server: " % server_dsa_dn, e)
1002 raise CommandError("Error while sending a removeDsServer of %s: " % server_dsa_dn, e)
1004 remove_dc.remove_sysvol_references(remote_samdb, logger, dc_name)
1006 # These are objects under the computer account that should be deleted
1007 for s in ("CN=Enterprise,CN=NTFRS Subscriptions",
1008 "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
1009 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
1010 "CN=NTFRS Subscriptions"):
1012 remote_samdb.delete(ldb.Dn(remote_samdb,
1013 "%s,%s" % (s, str(newdn))))
1014 except ldb.LdbError, l:
1017 self.errf.write("Demote successful\n")
1020 class cmd_domain_level(Command):
1021 """Raise domain and forest function levels."""
1023 synopsis = "%prog (show|raise <options>) [options]"
1025 takes_optiongroups = {
1026 "sambaopts": options.SambaOptions,
1027 "credopts": options.CredentialsOptions,
1028 "versionopts": options.VersionOptions,
1032 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1033 metavar="URL", dest="H"),
1034 Option("--quiet", help="Be quiet", action="store_true"),
1035 Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1036 help="The forest function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)"),
1037 Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1038 help="The domain function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)")
1041 takes_args = ["subcommand"]
1043 def run(self, subcommand, H=None, forest_level=None, domain_level=None,
1044 quiet=False, credopts=None, sambaopts=None, versionopts=None):
1045 lp = sambaopts.get_loadparm()
1046 creds = credopts.get_credentials(lp, fallback_machine=True)
1048 samdb = SamDB(url=H, session_info=system_session(),
1049 credentials=creds, lp=lp)
1051 domain_dn = samdb.domain_dn()
1053 res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
1054 scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
1055 assert len(res_forest) == 1
1057 res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1058 attrs=["msDS-Behavior-Version", "nTMixedDomain"])
1059 assert len(res_domain) == 1
1061 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
1062 scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
1063 attrs=["msDS-Behavior-Version"])
1064 assert len(res_dc_s) >= 1
1066 # default values, since "msDS-Behavior-Version" does not exist on Windows 2000 AD
1067 level_forest = DS_DOMAIN_FUNCTION_2000
1068 level_domain = DS_DOMAIN_FUNCTION_2000
1070 if "msDS-Behavior-Version" in res_forest[0]:
1071 level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
1072 if "msDS-Behavior-Version" in res_domain[0]:
1073 level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
1074 level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
1077 for msg in res_dc_s:
1078 if "msDS-Behavior-Version" in msg:
1079 if min_level_dc is None or int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
1080 min_level_dc = int(msg["msDS-Behavior-Version"][0])
1082 min_level_dc = DS_DOMAIN_FUNCTION_2000
1083 # well, this is the least
1086 if level_forest < DS_DOMAIN_FUNCTION_2000 or level_domain < DS_DOMAIN_FUNCTION_2000:
1087 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
1088 if min_level_dc < DS_DOMAIN_FUNCTION_2000:
1089 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
1090 if level_forest > level_domain:
1091 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
1092 if level_domain > min_level_dc:
1093 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
1095 if subcommand == "show":
1096 self.message("Domain and forest function level for domain '%s'" % domain_dn)
1097 if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1098 self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1099 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1100 self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1101 if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1102 self.message("\nATTENTION: You run SAMBA 4 on a lowest function level of a DC lower than Windows 2003. This isn't supported! Please step-up or upgrade the concerning DC(s)!")
1106 if level_forest == DS_DOMAIN_FUNCTION_2000:
1108 elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
1109 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1110 elif level_forest == DS_DOMAIN_FUNCTION_2003:
1112 elif level_forest == DS_DOMAIN_FUNCTION_2008:
1114 elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
1116 elif level_forest == DS_DOMAIN_FUNCTION_2012:
1118 elif level_forest == DS_DOMAIN_FUNCTION_2012_R2:
1121 outstr = "higher than 2012 R2"
1122 self.message("Forest function level: (Windows) " + outstr)
1124 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1125 outstr = "2000 mixed (NT4 DC support)"
1126 elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
1128 elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
1129 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1130 elif level_domain == DS_DOMAIN_FUNCTION_2003:
1132 elif level_domain == DS_DOMAIN_FUNCTION_2008:
1134 elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
1136 elif level_domain == DS_DOMAIN_FUNCTION_2012:
1138 elif level_domain == DS_DOMAIN_FUNCTION_2012_R2:
1141 outstr = "higher than 2012 R2"
1142 self.message("Domain function level: (Windows) " + outstr)
1144 if min_level_dc == DS_DOMAIN_FUNCTION_2000:
1146 elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
1148 elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
1150 elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
1152 elif min_level_dc == DS_DOMAIN_FUNCTION_2012:
1154 elif min_level_dc == DS_DOMAIN_FUNCTION_2012_R2:
1157 outstr = "higher than 2012 R2"
1158 self.message("Lowest function level of a DC: (Windows) " + outstr)
1160 elif subcommand == "raise":
1163 if domain_level is not None:
1164 if domain_level == "2003":
1165 new_level_domain = DS_DOMAIN_FUNCTION_2003
1166 elif domain_level == "2008":
1167 new_level_domain = DS_DOMAIN_FUNCTION_2008
1168 elif domain_level == "2008_R2":
1169 new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
1170 elif domain_level == "2012":
1171 new_level_domain = DS_DOMAIN_FUNCTION_2012
1172 elif domain_level == "2012_R2":
1173 new_level_domain = DS_DOMAIN_FUNCTION_2012_R2
1175 if new_level_domain <= level_domain and level_domain_mixed == 0:
1176 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
1177 if new_level_domain > min_level_dc:
1178 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
1180 # Deactivate mixed/interim domain support
1181 if level_domain_mixed != 0:
1182 # Directly on the base DN
1184 m.dn = ldb.Dn(samdb, domain_dn)
1185 m["nTMixedDomain"] = ldb.MessageElement("0",
1186 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1190 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
1191 m["nTMixedDomain"] = ldb.MessageElement("0",
1192 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1195 except ldb.LdbError, (enum, emsg):
1196 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1199 # Directly on the base DN
1201 m.dn = ldb.Dn(samdb, domain_dn)
1202 m["msDS-Behavior-Version"]= ldb.MessageElement(
1203 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1204 "msDS-Behavior-Version")
1208 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
1209 + ",CN=Partitions,%s" % samdb.get_config_basedn())
1210 m["msDS-Behavior-Version"]= ldb.MessageElement(
1211 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1212 "msDS-Behavior-Version")
1215 except ldb.LdbError, (enum, emsg):
1216 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1219 level_domain = new_level_domain
1220 msgs.append("Domain function level changed!")
1222 if forest_level is not None:
1223 if forest_level == "2003":
1224 new_level_forest = DS_DOMAIN_FUNCTION_2003
1225 elif forest_level == "2008":
1226 new_level_forest = DS_DOMAIN_FUNCTION_2008
1227 elif forest_level == "2008_R2":
1228 new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
1229 elif forest_level == "2012":
1230 new_level_forest = DS_DOMAIN_FUNCTION_2012
1231 elif forest_level == "2012_R2":
1232 new_level_forest = DS_DOMAIN_FUNCTION_2012_R2
1234 if new_level_forest <= level_forest:
1235 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1236 if new_level_forest > level_domain:
1237 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1240 m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
1241 m["msDS-Behavior-Version"]= ldb.MessageElement(
1242 str(new_level_forest), ldb.FLAG_MOD_REPLACE,
1243 "msDS-Behavior-Version")
1245 msgs.append("Forest function level changed!")
1246 msgs.append("All changes applied successfully!")
1247 self.message("\n".join(msgs))
1249 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
1252 class cmd_domain_passwordsettings(Command):
1253 """Set password settings.
1255 Password complexity, password lockout policy, history length,
1256 minimum password length, the minimum and maximum password age) on
1257 a Samba AD DC server.
1259 Use against a Windows DC is possible, but group policy will override it.
1262 synopsis = "%prog (show|set <options>) [options]"
1264 takes_optiongroups = {
1265 "sambaopts": options.SambaOptions,
1266 "versionopts": options.VersionOptions,
1267 "credopts": options.CredentialsOptions,
1271 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1272 metavar="URL", dest="H"),
1273 Option("--quiet", help="Be quiet", action="store_true"),
1274 Option("--complexity", type="choice", choices=["on","off","default"],
1275 help="The password complexity (on | off | default). Default is 'on'"),
1276 Option("--store-plaintext", type="choice", choices=["on","off","default"],
1277 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1278 Option("--history-length",
1279 help="The password history length (<integer> | default). Default is 24.", type=str),
1280 Option("--min-pwd-length",
1281 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1282 Option("--min-pwd-age",
1283 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1284 Option("--max-pwd-age",
1285 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1286 Option("--account-lockout-duration",
1287 help="The the length of time an account is locked out after exeeding the limit on bad password attempts (<integer in mins> | default). Default is 30 mins.", type=str),
1288 Option("--account-lockout-threshold",
1289 help="The number of bad password attempts allowed before locking out the account (<integer> | default). Default is 0 (never lock out).", type=str),
1290 Option("--reset-account-lockout-after",
1291 help="After this time is elapsed, the recorded number of attempts restarts from zero (<integer> | default). Default is 30.", type=str),
1294 takes_args = ["subcommand"]
1296 def run(self, subcommand, H=None, min_pwd_age=None, max_pwd_age=None,
1297 quiet=False, complexity=None, store_plaintext=None, history_length=None,
1298 min_pwd_length=None, account_lockout_duration=None, account_lockout_threshold=None,
1299 reset_account_lockout_after=None, credopts=None, sambaopts=None,
1301 lp = sambaopts.get_loadparm()
1302 creds = credopts.get_credentials(lp)
1304 samdb = SamDB(url=H, session_info=system_session(),
1305 credentials=creds, lp=lp)
1307 domain_dn = samdb.domain_dn()
1308 res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1309 attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1310 "minPwdAge", "maxPwdAge", "lockoutDuration", "lockoutThreshold",
1311 "lockOutObservationWindow"])
1312 assert(len(res) == 1)
1314 pwd_props = int(res[0]["pwdProperties"][0])
1315 pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
1316 cur_min_pwd_len = int(res[0]["minPwdLength"][0])
1318 cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1319 if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000:
1322 cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1323 cur_account_lockout_threshold = int(res[0]["lockoutThreshold"][0])
1325 if int(res[0]["lockoutDuration"][0]) == -0x8000000000000000:
1326 cur_account_lockout_duration = 0
1328 cur_account_lockout_duration = abs(int(res[0]["lockoutDuration"][0])) / (1e7 * 60)
1329 cur_reset_account_lockout_after = abs(int(res[0]["lockOutObservationWindow"][0])) / (1e7 * 60)
1330 except Exception, e:
1331 raise CommandError("Could not retrieve password properties!", e)
1333 if subcommand == "show":
1334 self.message("Password informations for domain '%s'" % domain_dn)
1336 if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
1337 self.message("Password complexity: on")
1339 self.message("Password complexity: off")
1340 if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
1341 self.message("Store plaintext passwords: on")
1343 self.message("Store plaintext passwords: off")
1344 self.message("Password history length: %d" % pwd_hist_len)
1345 self.message("Minimum password length: %d" % cur_min_pwd_len)
1346 self.message("Minimum password age (days): %d" % cur_min_pwd_age)
1347 self.message("Maximum password age (days): %d" % cur_max_pwd_age)
1348 self.message("Account lockout duration (mins): %d" % cur_account_lockout_duration)
1349 self.message("Account lockout threshold (attempts): %d" % cur_account_lockout_threshold)
1350 self.message("Reset account lockout after (mins): %d" % cur_reset_account_lockout_after)
1351 elif subcommand == "set":
1354 m.dn = ldb.Dn(samdb, domain_dn)
1356 if complexity is not None:
1357 if complexity == "on" or complexity == "default":
1358 pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
1359 msgs.append("Password complexity activated!")
1360 elif complexity == "off":
1361 pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
1362 msgs.append("Password complexity deactivated!")
1364 if store_plaintext is not None:
1365 if store_plaintext == "on" or store_plaintext == "default":
1366 pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1367 msgs.append("Plaintext password storage for changed passwords activated!")
1368 elif store_plaintext == "off":
1369 pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
1370 msgs.append("Plaintext password storage for changed passwords deactivated!")
1372 if complexity is not None or store_plaintext is not None:
1373 m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
1374 ldb.FLAG_MOD_REPLACE, "pwdProperties")
1376 if history_length is not None:
1377 if history_length == "default":
1380 pwd_hist_len = int(history_length)
1382 if pwd_hist_len < 0 or pwd_hist_len > 24:
1383 raise CommandError("Password history length must be in the range of 0 to 24!")
1385 m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
1386 ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
1387 msgs.append("Password history length changed!")
1389 if min_pwd_length is not None:
1390 if min_pwd_length == "default":
1393 min_pwd_len = int(min_pwd_length)
1395 if min_pwd_len < 0 or min_pwd_len > 14:
1396 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1398 m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
1399 ldb.FLAG_MOD_REPLACE, "minPwdLength")
1400 msgs.append("Minimum password length changed!")
1402 if min_pwd_age is not None:
1403 if min_pwd_age == "default":
1406 min_pwd_age = int(min_pwd_age)
1408 if min_pwd_age < 0 or min_pwd_age > 998:
1409 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1412 min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
1414 m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
1415 ldb.FLAG_MOD_REPLACE, "minPwdAge")
1416 msgs.append("Minimum password age changed!")
1418 if max_pwd_age is not None:
1419 if max_pwd_age == "default":
1422 max_pwd_age = int(max_pwd_age)
1424 if max_pwd_age < 0 or max_pwd_age > 999:
1425 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1428 if max_pwd_age == 0:
1429 max_pwd_age_ticks = -0x8000000000000000
1431 max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
1433 m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
1434 ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1435 msgs.append("Maximum password age changed!")
1437 if account_lockout_duration is not None:
1438 if account_lockout_duration == "default":
1439 account_lockout_duration = 30
1441 account_lockout_duration = int(account_lockout_duration)
1443 if account_lockout_duration < 0 or account_lockout_duration > 99999:
1444 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1447 if account_lockout_duration == 0:
1448 account_lockout_duration_ticks = -0x8000000000000000
1450 account_lockout_duration_ticks = -int(account_lockout_duration * (60 * 1e7))
1452 m["lockoutDuration"] = ldb.MessageElement(str(account_lockout_duration_ticks),
1453 ldb.FLAG_MOD_REPLACE, "lockoutDuration")
1454 msgs.append("Account lockout duration changed!")
1456 if account_lockout_threshold is not None:
1457 if account_lockout_threshold == "default":
1458 account_lockout_threshold = 0
1460 account_lockout_threshold = int(account_lockout_threshold)
1462 m["lockoutThreshold"] = ldb.MessageElement(str(account_lockout_threshold),
1463 ldb.FLAG_MOD_REPLACE, "lockoutThreshold")
1464 msgs.append("Account lockout threshold changed!")
1466 if reset_account_lockout_after is not None:
1467 if reset_account_lockout_after == "default":
1468 reset_account_lockout_after = 30
1470 reset_account_lockout_after = int(reset_account_lockout_after)
1472 if reset_account_lockout_after < 0 or reset_account_lockout_after > 99999:
1473 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1476 if reset_account_lockout_after == 0:
1477 reset_account_lockout_after_ticks = -0x8000000000000000
1479 reset_account_lockout_after_ticks = -int(reset_account_lockout_after * (60 * 1e7))
1481 m["lockOutObservationWindow"] = ldb.MessageElement(str(reset_account_lockout_after_ticks),
1482 ldb.FLAG_MOD_REPLACE, "lockOutObservationWindow")
1483 msgs.append("Duration to reset account lockout after changed!")
1485 if max_pwd_age > 0 and min_pwd_age >= max_pwd_age:
1486 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
1489 raise CommandError("You must specify at least one option to set. Try --help")
1491 msgs.append("All changes applied successfully!")
1492 self.message("\n".join(msgs))
1494 raise CommandError("Wrong argument '%s'!" % subcommand)
1497 class cmd_domain_classicupgrade(Command):
1498 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1500 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1501 the testparm utility from your classic installation (with --testparm).
1504 synopsis = "%prog [options] <classic_smb_conf>"
1506 takes_optiongroups = {
1507 "sambaopts": options.SambaOptions,
1508 "versionopts": options.VersionOptions
1512 Option("--dbdir", type="string", metavar="DIR",
1513 help="Path to samba classic DC database directory"),
1514 Option("--testparm", type="string", metavar="PATH",
1515 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1516 Option("--targetdir", type="string", metavar="DIR",
1517 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1518 Option("--quiet", help="Be quiet", action="store_true"),
1519 Option("--verbose", help="Be verbose", action="store_true"),
1520 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
1521 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1522 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1523 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1524 "BIND9_DLZ uses samba4 AD to store zone information, "
1525 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1526 default="SAMBA_INTERNAL")
1530 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
1531 action="store_true"),
1532 Option("--use-xattrs", type="choice", choices=["yes","no","auto"],
1533 metavar="[yes|no|auto]",
1534 help="Define if we should use the native fs capabilities or a tdb file for "
1535 "storing attributes likes ntacl when --use-ntvfs is set. "
1536 "auto tries to make an inteligent guess based on the user rights and system capabilities",
1539 if samba.is_ntvfs_fileserver_built():
1540 takes_options.extend(ntvfs_options)
1542 takes_args = ["smbconf"]
1544 def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
1545 quiet=False, verbose=False, use_xattrs="auto", sambaopts=None, versionopts=None,
1546 dns_backend=None, use_ntvfs=False):
1548 if not os.path.exists(smbconf):
1549 raise CommandError("File %s does not exist" % smbconf)
1551 if testparm and not os.path.exists(testparm):
1552 raise CommandError("Testparm utility %s does not exist" % testparm)
1554 if dbdir and not os.path.exists(dbdir):
1555 raise CommandError("Directory %s does not exist" % dbdir)
1557 if not dbdir and not testparm:
1558 raise CommandError("Please specify either dbdir or testparm")
1560 logger = self.get_logger()
1562 logger.setLevel(logging.DEBUG)
1564 logger.setLevel(logging.WARNING)
1566 logger.setLevel(logging.INFO)
1568 if dbdir and testparm:
1569 logger.warning("both dbdir and testparm specified, ignoring dbdir.")
1572 lp = sambaopts.get_loadparm()
1574 s3conf = s3param.get_context()
1577 s3conf.set("realm", sambaopts.realm)
1579 if targetdir is not None:
1580 if not os.path.isdir(targetdir):
1584 if use_xattrs == "yes":
1586 elif use_xattrs == "auto" and use_ntvfs == False:
1588 elif use_ntvfs == False:
1589 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
1590 "Please re-run with --use-xattrs omitted.")
1591 elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
1593 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
1595 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
1598 samba.ntacls.setntacl(lp, tmpfile.name,
1599 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1602 # FIXME: Don't catch all exceptions here
1603 logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. "
1604 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1608 # Set correct default values from dbdir or testparm
1611 paths["state directory"] = dbdir
1612 paths["private dir"] = dbdir
1613 paths["lock directory"] = dbdir
1614 paths["smb passwd file"] = dbdir + "/smbpasswd"
1616 paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
1617 paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
1618 paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
1619 paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
1620 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1621 # "state directory", instead make use of "lock directory"
1622 if len(paths["state directory"]) == 0:
1623 paths["state directory"] = paths["lock directory"]
1626 s3conf.set(p, paths[p])
1628 # load smb.conf parameters
1629 logger.info("Reading smb.conf")
1630 s3conf.load(smbconf)
1631 samba3 = Samba3(smbconf, s3conf)
1633 logger.info("Provisioning")
1634 upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
1635 useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
1638 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
1639 __doc__ = cmd_domain_classicupgrade.__doc__
1641 # This command is present for backwards compatibility only,
1642 # and should not be shown.
1646 class LocalDCCredentialsOptions(options.CredentialsOptions):
1647 def __init__(self, parser):
1648 options.CredentialsOptions.__init__(self, parser, special_name="local-dc")
1650 class DomainTrustCommand(Command):
1651 """List domain trusts."""
1654 Command.__init__(self)
1655 self.local_lp = None
1657 self.local_server = None
1658 self.local_binding_string = None
1659 self.local_creds = None
1661 self.remote_server = None
1662 self.remote_binding_string = None
1663 self.remote_creds = None
1665 def _uint32(self, v):
1666 return ctypes.c_uint32(v).value
1668 def check_runtime_error(self, runtime, val):
1672 err32 = self._uint32(runtime[0])
1678 class LocalRuntimeError(CommandError):
1679 def __init__(exception_self, self, runtime, message):
1680 err32 = self._uint32(runtime[0])
1682 msg = "LOCAL_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1683 self.local_server, message, err32, errstr)
1684 CommandError.__init__(exception_self, msg)
1686 class RemoteRuntimeError(CommandError):
1687 def __init__(exception_self, self, runtime, message):
1688 err32 = self._uint32(runtime[0])
1690 msg = "REMOTE_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1691 self.remote_server, message, err32, errstr)
1692 CommandError.__init__(exception_self, msg)
1694 class LocalLdbError(CommandError):
1695 def __init__(exception_self, self, ldb_error, message):
1696 errval = ldb_error[0]
1697 errstr = ldb_error[1]
1698 msg = "LOCAL_DC[%s]: %s - ERROR(%d) - %s" % (
1699 self.local_server, message, errval, errstr)
1700 CommandError.__init__(exception_self, msg)
1702 def setup_local_server(self, sambaopts, localdcopts):
1703 if self.local_server is not None:
1704 return self.local_server
1706 lp = sambaopts.get_loadparm()
1708 local_server = localdcopts.ipaddress
1709 if local_server is None:
1710 server_role = lp.server_role()
1711 if server_role != "ROLE_ACTIVE_DIRECTORY_DC":
1712 raise CommandError("Invalid server_role %s" % (server_role))
1713 local_server = lp.get('netbios name')
1714 local_transport = "ncalrpc"
1715 local_binding_options = ""
1716 local_binding_options += ",auth_type=ncalrpc_as_system"
1717 local_ldap_url = None
1720 local_transport = "ncacn_np"
1721 local_binding_options = ""
1722 local_ldap_url = "ldap://%s" % local_server
1723 local_creds = localdcopts.get_credentials(lp)
1727 self.local_server = local_server
1728 self.local_binding_string = "%s:%s[%s]" % (local_transport, local_server, local_binding_options)
1729 self.local_ldap_url = local_ldap_url
1730 self.local_creds = local_creds
1731 return self.local_server
1733 def new_local_lsa_connection(self):
1734 return lsa.lsarpc(self.local_binding_string, self.local_lp, self.local_creds)
1736 def new_local_netlogon_connection(self):
1737 return netlogon.netlogon(self.local_binding_string, self.local_lp, self.local_creds)
1739 def new_local_ldap_connection(self):
1740 return SamDB(url=self.local_ldap_url,
1741 session_info=system_session(),
1742 credentials=self.local_creds,
1745 def setup_remote_server(self, credopts, domain,
1747 require_writable=True):
1750 assert require_writable
1752 if self.remote_server is not None:
1753 return self.remote_server
1755 self.remote_server = "__unknown__remote_server__.%s" % domain
1756 assert self.local_server is not None
1758 remote_creds = credopts.get_credentials(self.local_lp)
1759 remote_server = credopts.ipaddress
1760 remote_binding_options = ""
1762 # TODO: we should also support NT4 domains
1763 # we could use local_netlogon.netr_DsRGetDCNameEx2() with the remote domain name
1764 # and delegate NBT or CLDAP to the local netlogon server
1766 remote_net = Net(remote_creds, self.local_lp, server=remote_server)
1767 remote_flags = nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS
1768 if require_writable:
1769 remote_flags |= nbt.NBT_SERVER_WRITABLE
1771 remote_flags |= nbt.NBT_SERVER_PDC
1772 remote_info = remote_net.finddc(flags=remote_flags, domain=domain, address=remote_server)
1774 raise CommandError("Failed to find a writeable DC for domain '%s'" % domain)
1776 nbt.NBT_SERVER_PDC: "PDC",
1777 nbt.NBT_SERVER_GC: "GC",
1778 nbt.NBT_SERVER_LDAP: "LDAP",
1779 nbt.NBT_SERVER_DS: "DS",
1780 nbt.NBT_SERVER_KDC: "KDC",
1781 nbt.NBT_SERVER_TIMESERV: "TIMESERV",
1782 nbt.NBT_SERVER_CLOSEST: "CLOSEST",
1783 nbt.NBT_SERVER_WRITABLE: "WRITABLE",
1784 nbt.NBT_SERVER_GOOD_TIMESERV: "GOOD_TIMESERV",
1785 nbt.NBT_SERVER_NDNC: "NDNC",
1786 nbt.NBT_SERVER_SELECT_SECRET_DOMAIN_6: "SELECT_SECRET_DOMAIN_6",
1787 nbt.NBT_SERVER_FULL_SECRET_DOMAIN_6: "FULL_SECRET_DOMAIN_6",
1788 nbt.NBT_SERVER_ADS_WEB_SERVICE: "ADS_WEB_SERVICE",
1789 nbt.NBT_SERVER_DS_8: "DS_8",
1790 nbt.NBT_SERVER_HAS_DNS_NAME: "HAS_DNS_NAME",
1791 nbt.NBT_SERVER_IS_DEFAULT_NC: "IS_DEFAULT_NC",
1792 nbt.NBT_SERVER_FOREST_ROOT: "FOREST_ROOT",
1794 server_type_string = self.generic_bitmap_to_string(flag_map,
1795 remote_info.server_type, names_only=True)
1796 self.outf.write("RemoteDC Netbios[%s] DNS[%s] ServerType[%s]\n" % (
1797 remote_info.pdc_name,
1798 remote_info.pdc_dns_name,
1799 server_type_string))
1801 self.remote_server = remote_info.pdc_dns_name
1802 self.remote_binding_string="ncacn_np:%s[%s]" % (self.remote_server, remote_binding_options)
1803 self.remote_creds = remote_creds
1804 return self.remote_server
1806 def new_remote_lsa_connection(self):
1807 return lsa.lsarpc(self.remote_binding_string, self.local_lp, self.remote_creds)
1809 def new_remote_netlogon_connection(self):
1810 return netlogon.netlogon(self.remote_binding_string, self.local_lp, self.remote_creds)
1812 def get_lsa_info(self, conn, policy_access):
1813 objectAttr = lsa.ObjectAttribute()
1814 objectAttr.sec_qos = lsa.QosInfo()
1816 policy = conn.OpenPolicy2(''.decode('utf-8'),
1817 objectAttr, policy_access)
1819 info = conn.QueryInfoPolicy2(policy, lsa.LSA_POLICY_INFO_DNS)
1821 return (policy, info)
1823 def get_netlogon_dc_info(self, conn, server):
1824 info = conn.netr_DsRGetDCNameEx2(server,
1825 None, 0, None, None, None,
1826 netlogon.DS_RETURN_DNS_NAME)
1829 def netr_DomainTrust_to_name(self, t):
1830 if t.trust_type == lsa.LSA_TRUST_TYPE_DOWNLEVEL:
1831 return t.netbios_name
1835 def netr_DomainTrust_to_type(self, a, t):
1837 primary_parent = None
1839 if _t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
1841 if not _t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1842 primary_parent = a[_t.parent_index]
1845 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1846 if t is primary_parent:
1849 if t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1852 parent = a[t.parent_index]
1853 if parent is primary:
1858 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1863 def netr_DomainTrust_to_transitive(self, t):
1864 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1867 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE:
1870 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1875 def netr_DomainTrust_to_direction(self, t):
1876 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND and \
1877 t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1880 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND:
1883 if t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1888 def generic_enum_to_string(self, e_dict, v, names_only=False):
1892 v32 = self._uint32(v)
1893 w = "__unknown__%08X__" % v32
1895 r = "0x%x (%s)" % (v, w)
1898 def generic_bitmap_to_string(self, b_dict, v, names_only=False):
1903 for b in sorted(b_dict.keys()):
1910 c32 = self._uint32(c)
1911 s += ["__unknown_%08X__" % c32]
1916 r = "0x%x (%s)" % (v, w)
1919 def trustType_string(self, v):
1921 lsa.LSA_TRUST_TYPE_DOWNLEVEL : "DOWNLEVEL",
1922 lsa.LSA_TRUST_TYPE_UPLEVEL : "UPLEVEL",
1923 lsa.LSA_TRUST_TYPE_MIT : "MIT",
1924 lsa.LSA_TRUST_TYPE_DCE : "DCE",
1926 return self.generic_enum_to_string(types, v)
1928 def trustDirection_string(self, v):
1930 lsa.LSA_TRUST_DIRECTION_INBOUND |
1931 lsa.LSA_TRUST_DIRECTION_OUTBOUND : "BOTH",
1932 lsa.LSA_TRUST_DIRECTION_INBOUND : "INBOUND",
1933 lsa.LSA_TRUST_DIRECTION_OUTBOUND : "OUTBOUND",
1935 return self.generic_enum_to_string(directions, v)
1937 def trustAttributes_string(self, v):
1939 lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE : "NON_TRANSITIVE",
1940 lsa.LSA_TRUST_ATTRIBUTE_UPLEVEL_ONLY : "UPLEVEL_ONLY",
1941 lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN : "QUARANTINED_DOMAIN",
1942 lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE : "FOREST_TRANSITIVE",
1943 lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION : "CROSS_ORGANIZATION",
1944 lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST : "WITHIN_FOREST",
1945 lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL : "TREAT_AS_EXTERNAL",
1946 lsa.LSA_TRUST_ATTRIBUTE_USES_RC4_ENCRYPTION : "USES_RC4_ENCRYPTION",
1948 return self.generic_bitmap_to_string(attributes, v)
1950 def kerb_EncTypes_string(self, v):
1952 security.KERB_ENCTYPE_DES_CBC_CRC : "DES_CBC_CRC",
1953 security.KERB_ENCTYPE_DES_CBC_MD5 : "DES_CBC_MD5",
1954 security.KERB_ENCTYPE_RC4_HMAC_MD5 : "RC4_HMAC_MD5",
1955 security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96 : "AES128_CTS_HMAC_SHA1_96",
1956 security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96 : "AES256_CTS_HMAC_SHA1_96",
1957 security.KERB_ENCTYPE_FAST_SUPPORTED : "FAST_SUPPORTED",
1958 security.KERB_ENCTYPE_COMPOUND_IDENTITY_SUPPORTED : "COMPOUND_IDENTITY_SUPPORTED",
1959 security.KERB_ENCTYPE_CLAIMS_SUPPORTED : "CLAIMS_SUPPORTED",
1960 security.KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED : "RESOURCE_SID_COMPRESSION_DISABLED",
1962 return self.generic_bitmap_to_string(enctypes, v)
1964 def entry_tln_status(self, e_flags, ):
1966 return "Status[Enabled]"
1969 lsa.LSA_TLN_DISABLED_NEW : "Disabled-New",
1970 lsa.LSA_TLN_DISABLED_ADMIN : "Disabled",
1971 lsa.LSA_TLN_DISABLED_CONFLICT : "Disabled-Conflicting",
1973 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
1975 def entry_dom_status(self, e_flags):
1977 return "Status[Enabled]"
1980 lsa.LSA_SID_DISABLED_ADMIN : "Disabled-SID",
1981 lsa.LSA_SID_DISABLED_CONFLICT : "Disabled-SID-Conflicting",
1982 lsa.LSA_NB_DISABLED_ADMIN : "Disabled-NB",
1983 lsa.LSA_NB_DISABLED_CONFLICT : "Disabled-NB-Conflicting",
1985 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
1987 def write_forest_trust_info(self, fti, tln=None, collisions=None):
1989 tln_string = " TDO[%s]" % tln
1993 self.outf.write("Namespaces[%d]%s:\n" % (
1994 len(fti.entries), tln_string))
1996 for i in xrange(0, len(fti.entries)):
2000 collision_string = ""
2002 if collisions is not None:
2003 for c in collisions.entries:
2007 collision_string = " Collision[%s]" % (c.name.string)
2009 d = e.forest_trust_data
2010 if e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
2011 self.outf.write("TLN: %-32s DNS[*.%s]%s\n" % (
2012 self.entry_tln_status(flags),
2013 d.string, collision_string))
2014 elif e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
2015 self.outf.write("TLN_EX: %-29s DNS[*.%s]\n" % (
2017 elif e.type == lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
2018 self.outf.write("DOM: %-32s DNS[%s] Netbios[%s] SID[%s]%s\n" % (
2019 self.entry_dom_status(flags),
2020 d.dns_domain_name.string,
2021 d.netbios_domain_name.string,
2022 d.domain_sid, collision_string))
2025 class cmd_domain_trust_list(DomainTrustCommand):
2026 """List domain trusts."""
2028 synopsis = "%prog [options]"
2030 takes_optiongroups = {
2031 "sambaopts": options.SambaOptions,
2032 "versionopts": options.VersionOptions,
2033 "localdcopts": LocalDCCredentialsOptions,
2039 def run(self, sambaopts=None, versionopts=None, localdcopts=None):
2041 local_server = self.setup_local_server(sambaopts, localdcopts)
2043 local_netlogon = self.new_local_netlogon_connection()
2044 except RuntimeError as error:
2045 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2048 local_netlogon_trusts = local_netlogon.netr_DsrEnumerateDomainTrusts(local_server,
2049 netlogon.NETR_TRUST_FLAG_IN_FOREST |
2050 netlogon.NETR_TRUST_FLAG_OUTBOUND |
2051 netlogon.NETR_TRUST_FLAG_INBOUND)
2052 except RuntimeError as error:
2053 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
2054 # TODO: we could implement a fallback to lsa.EnumTrustDom()
2055 raise CommandError("LOCAL_DC[%s]: netr_DsrEnumerateDomainTrusts not supported." % (
2057 raise self.LocalRuntimeError(self, error, "netr_DsrEnumerateDomainTrusts failed")
2059 a = local_netlogon_trusts.array
2061 if t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
2063 self.outf.write("%-14s %-15s %-19s %s\n" % (
2064 "Type[%s]" % self.netr_DomainTrust_to_type(a, t),
2065 "Transitive[%s]" % self.netr_DomainTrust_to_transitive(t),
2066 "Direction[%s]" % self.netr_DomainTrust_to_direction(t),
2067 "Name[%s]" % self.netr_DomainTrust_to_name(t)))
2070 class cmd_domain_trust_show(DomainTrustCommand):
2071 """Show trusted domain details."""
2073 synopsis = "%prog NAME [options]"
2075 takes_optiongroups = {
2076 "sambaopts": options.SambaOptions,
2077 "versionopts": options.VersionOptions,
2078 "localdcopts": LocalDCCredentialsOptions,
2084 takes_args = ["domain"]
2086 def run(self, domain, sambaopts=None, versionopts=None, localdcopts=None):
2088 local_server = self.setup_local_server(sambaopts, localdcopts)
2090 local_lsa = self.new_local_lsa_connection()
2091 except RuntimeError as error:
2092 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2095 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2096 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2097 except RuntimeError as error:
2098 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2100 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2101 local_lsa_info.name.string,
2102 local_lsa_info.dns_domain.string,
2103 local_lsa_info.sid))
2105 lsaString = lsa.String()
2106 lsaString.string = domain
2108 local_tdo_full = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2109 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2110 local_tdo_info = local_tdo_full.info_ex
2111 local_tdo_posix = local_tdo_full.posix_offset
2112 except NTSTATUSError as error:
2113 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2114 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2116 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(FULL_INFO) failed")
2119 local_tdo_enctypes = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2120 lsaString, lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES)
2121 except NTSTATUSError as error:
2122 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_PARAMETER):
2124 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_INFO_CLASS):
2127 if error is not None:
2128 raise self.LocalRuntimeError(self, error,
2129 "QueryTrustedDomainInfoByName(SUPPORTED_ENCRYPTION_TYPES) failed")
2131 local_tdo_enctypes = lsa.TrustDomainInfoSupportedEncTypes()
2132 local_tdo_enctypes.enc_types = 0
2135 local_tdo_forest = None
2136 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2137 local_tdo_forest = local_lsa.lsaRQueryForestTrustInformation(local_policy,
2138 lsaString, lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
2139 except RuntimeError as error:
2140 if self.check_runtime_error(error, ntstatus.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE):
2142 if self.check_runtime_error(error, ntstatus.NT_STATUS_NOT_FOUND):
2144 if error is not None:
2145 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation failed")
2147 local_tdo_forest = lsa.ForestTrustInformation()
2148 local_tdo_forest.count = 0
2149 local_tdo_forest.entries = []
2151 self.outf.write("TrusteDomain:\n\n");
2152 self.outf.write("NetbiosName: %s\n" % local_tdo_info.netbios_name.string)
2153 if local_tdo_info.netbios_name.string != local_tdo_info.domain_name.string:
2154 self.outf.write("DnsName: %s\n" % local_tdo_info.domain_name.string)
2155 self.outf.write("SID: %s\n" % local_tdo_info.sid)
2156 self.outf.write("Type: %s\n" % self.trustType_string(local_tdo_info.trust_type))
2157 self.outf.write("Direction: %s\n" % self.trustDirection_string(local_tdo_info.trust_direction))
2158 self.outf.write("Attributes: %s\n" % self.trustAttributes_string(local_tdo_info.trust_attributes))
2159 posix_offset_u32 = ctypes.c_uint32(local_tdo_posix.posix_offset).value
2160 posix_offset_i32 = ctypes.c_int32(local_tdo_posix.posix_offset).value
2161 self.outf.write("PosixOffset: 0x%08X (%d)\n" % (posix_offset_u32, posix_offset_i32))
2162 self.outf.write("kerb_EncTypes: %s\n" % self.kerb_EncTypes_string(local_tdo_enctypes.enc_types))
2164 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2165 self.write_forest_trust_info(local_tdo_forest,
2166 tln=local_tdo_info.domain_name.string)
2170 class cmd_domain_trust_create(DomainTrustCommand):
2171 """Create a domain or forest trust."""
2173 synopsis = "%prog DOMAIN [options]"
2175 takes_optiongroups = {
2176 "sambaopts": options.SambaOptions,
2177 "versionopts": options.VersionOptions,
2178 "credopts": options.CredentialsOptions,
2179 "localdcopts": LocalDCCredentialsOptions,
2183 Option("--type", type="choice", metavar="TYPE",
2184 choices=["external", "forest"],
2185 help="The type of the trust: 'external' or 'forest'.",
2187 default="external"),
2188 Option("--direction", type="choice", metavar="DIRECTION",
2189 choices=["incoming", "outgoing", "both"],
2190 help="The trust direction: 'incoming', 'outgoing' or 'both'.",
2191 dest='trust_direction',
2193 Option("--create-location", type="choice", metavar="LOCATION",
2194 choices=["local", "both"],
2195 help="Where to create the trusted domain object: 'local' or 'both'.",
2196 dest='create_location',
2198 Option("--cross-organisation", action="store_true",
2199 help="The related domains does not belong to the same organisation.",
2200 dest='cross_organisation',
2202 Option("--quarantined", type="choice", metavar="yes|no",
2203 choices=["yes", "no", None],
2204 help="Special SID filtering rules are applied to the trust. "
2205 "With --type=external the default is yes. "
2206 "With --type=forest the default is no.",
2207 dest='quarantined_arg',
2209 Option("--not-transitive", action="store_true",
2210 help="The forest trust is not transitive.",
2211 dest='not_transitive',
2213 Option("--treat-as-external", action="store_true",
2214 help="The treat the forest trust as external.",
2215 dest='treat_as_external',
2217 Option("--no-aes-keys", action="store_false",
2218 help="The trust uses aes kerberos keys.",
2219 dest='use_aes_keys',
2221 Option("--skip-validation", action="store_false",
2222 help="Skip validation of the trust.",
2227 takes_args = ["domain"]
2229 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2230 trust_type=None, trust_direction=None, create_location=None,
2231 cross_organisation=False, quarantined_arg=None,
2232 not_transitive=False, treat_as_external=False,
2233 use_aes_keys=False, validate=True):
2235 lsaString = lsa.String()
2238 if quarantined_arg is None:
2239 if trust_type == 'external':
2241 elif quarantined_arg == 'yes':
2244 if trust_type != 'forest':
2246 raise CommandError("--not-transitive requires --type=forest")
2247 if treat_as_external:
2248 raise CommandError("--treat-as-external requires --type=forest")
2252 enc_types = lsa.TrustDomainInfoSupportedEncTypes()
2253 enc_types.enc_types = security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96
2254 enc_types.enc_types |= security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96
2256 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2257 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2258 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2260 local_trust_info = lsa.TrustDomainInfoInfoEx()
2261 local_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2262 local_trust_info.trust_direction = 0
2263 if trust_direction == "both":
2264 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2265 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2266 elif trust_direction == "incoming":
2267 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2268 elif trust_direction == "outgoing":
2269 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2270 local_trust_info.trust_attributes = 0
2271 if cross_organisation:
2272 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2274 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2275 if trust_type == "forest":
2276 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2278 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2279 if treat_as_external:
2280 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2282 def get_password(name):
2285 if password is not None and password is not '':
2287 password = getpass("New %s Password: " % name)
2288 passwordverify = getpass("Retype %s Password: " % name)
2289 if not password == passwordverify:
2291 self.outf.write("Sorry, passwords do not match.\n")
2293 incoming_secret = None
2294 outgoing_secret = None
2295 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2296 if create_location == "local":
2297 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2298 incoming_password = get_password("Incoming Trust")
2299 incoming_secret = string_to_byte_array(incoming_password.encode('utf-16-le'))
2300 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2301 outgoing_password = get_password("Outgoing Trust")
2302 outgoing_secret = string_to_byte_array(outgoing_password.encode('utf-16-le'))
2304 remote_trust_info = None
2306 # We use 240 random bytes.
2307 # Windows uses 28 or 240 random bytes. I guess it's
2308 # based on the trust type external vs. forest.
2310 # The initial trust password can be up to 512 bytes
2311 # while the versioned passwords used for periodic updates
2312 # can only be up to 498 bytes, as netr_ServerPasswordSet2()
2313 # needs to pass the NL_PASSWORD_VERSION structure within the
2314 # 512 bytes and a 2 bytes confounder is required.
2316 def random_trust_secret(length):
2317 pw = samba.generate_random_machine_password(length/2, length/2)
2318 return string_to_byte_array(pw.encode('utf-16-le'))
2320 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2321 incoming_secret = random_trust_secret(240)
2322 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2323 outgoing_secret = random_trust_secret(240)
2325 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2326 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2328 remote_trust_info = lsa.TrustDomainInfoInfoEx()
2329 remote_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2330 remote_trust_info.trust_direction = 0
2331 if trust_direction == "both":
2332 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2333 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2334 elif trust_direction == "incoming":
2335 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2336 elif trust_direction == "outgoing":
2337 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2338 remote_trust_info.trust_attributes = 0
2339 if cross_organisation:
2340 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2342 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2343 if trust_type == "forest":
2344 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2346 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2347 if treat_as_external:
2348 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2350 local_server = self.setup_local_server(sambaopts, localdcopts)
2352 local_lsa = self.new_local_lsa_connection()
2353 except RuntimeError as error:
2354 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2357 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2358 except RuntimeError as error:
2359 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2361 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2362 local_lsa_info.name.string,
2363 local_lsa_info.dns_domain.string,
2364 local_lsa_info.sid))
2367 remote_server = self.setup_remote_server(credopts, domain)
2368 except RuntimeError as error:
2369 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2372 remote_lsa = self.new_remote_lsa_connection()
2373 except RuntimeError as error:
2374 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2377 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2378 except RuntimeError as error:
2379 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2381 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2382 remote_lsa_info.name.string,
2383 remote_lsa_info.dns_domain.string,
2384 remote_lsa_info.sid))
2386 local_trust_info.domain_name.string = remote_lsa_info.dns_domain.string
2387 local_trust_info.netbios_name.string = remote_lsa_info.name.string
2388 local_trust_info.sid = remote_lsa_info.sid
2390 if remote_trust_info:
2391 remote_trust_info.domain_name.string = local_lsa_info.dns_domain.string
2392 remote_trust_info.netbios_name.string = local_lsa_info.name.string
2393 remote_trust_info.sid = local_lsa_info.sid
2396 lsaString.string = local_trust_info.domain_name.string
2397 local_old_netbios = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2398 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2399 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2400 except NTSTATUSError as error:
2401 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2402 raise self.LocalRuntimeError(self, error,
2403 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2407 lsaString.string = local_trust_info.netbios_name.string
2408 local_old_dns = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2409 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2410 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2411 except NTSTATUSError as error:
2412 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2413 raise self.LocalRuntimeError(self, error,
2414 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2417 if remote_trust_info:
2419 lsaString.string = remote_trust_info.domain_name.string
2420 remote_old_netbios = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2421 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2422 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2423 except NTSTATUSError as error:
2424 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2425 raise self.RemoteRuntimeError(self, error,
2426 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2430 lsaString.string = remote_trust_info.netbios_name.string
2431 remote_old_dns = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2432 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2433 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2434 except NTSTATUSError as error:
2435 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2436 raise self.RemoteRuntimeError(self, error,
2437 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2441 local_netlogon = self.new_local_netlogon_connection()
2442 except RuntimeError as error:
2443 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2446 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
2447 except RuntimeError as error:
2448 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
2450 if remote_trust_info:
2452 remote_netlogon = self.new_remote_netlogon_connection()
2453 except RuntimeError as error:
2454 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2457 remote_netlogon_info = self.get_netlogon_dc_info(remote_netlogon, remote_server)
2458 except RuntimeError as error:
2459 raise self.RemoteRuntimeError(self, error, "failed to get netlogon dc info")
2461 def generate_AuthInOutBlob(secret, update_time):
2463 blob = drsblobs.trustAuthInOutBlob()
2468 clear = drsblobs.AuthInfoClear()
2469 clear.size = len(secret)
2470 clear.password = secret
2472 info = drsblobs.AuthenticationInformation()
2473 info.LastUpdateTime = samba.unix2nttime(update_time)
2474 info.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
2475 info.AuthInfo = clear
2477 array = drsblobs.AuthenticationInformationArray()
2479 array.array = [info]
2481 blob = drsblobs.trustAuthInOutBlob()
2483 blob.current = array
2487 def generate_AuthInfoInternal(session_key, incoming=None, outgoing=None):
2488 confounder = [0] * 512
2489 for i in range(len(confounder)):
2490 confounder[i] = random.randint(0, 255)
2492 trustpass = drsblobs.trustDomainPasswords()
2494 trustpass.confounder = confounder
2495 trustpass.outgoing = outgoing
2496 trustpass.incoming = incoming
2498 trustpass_blob = ndr_pack(trustpass)
2500 encrypted_trustpass = arcfour_encrypt(session_key, trustpass_blob)
2502 auth_blob = lsa.DATA_BUF2()
2503 auth_blob.size = len(encrypted_trustpass)
2504 auth_blob.data = string_to_byte_array(encrypted_trustpass)
2506 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
2507 auth_info.auth_blob = auth_blob
2511 update_time = samba.current_unix_time()
2512 incoming_blob = generate_AuthInOutBlob(incoming_secret, update_time)
2513 outgoing_blob = generate_AuthInOutBlob(outgoing_secret, update_time)
2515 local_tdo_handle = None
2516 remote_tdo_handle = None
2518 local_auth_info = generate_AuthInfoInternal(local_lsa.session_key,
2519 incoming=incoming_blob,
2520 outgoing=outgoing_blob)
2521 if remote_trust_info:
2522 remote_auth_info = generate_AuthInfoInternal(remote_lsa.session_key,
2523 incoming=outgoing_blob,
2524 outgoing=incoming_blob)
2527 if remote_trust_info:
2528 self.outf.write("Creating remote TDO.\n")
2529 current_request = { "location": "remote", "name": "CreateTrustedDomainEx2"}
2530 remote_tdo_handle = remote_lsa.CreateTrustedDomainEx2(remote_policy,
2533 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2534 self.outf.write("Remote TDO created.\n")
2536 self.outf.write("Setting supported encryption types on remote TDO.\n")
2537 current_request = { "location": "remote", "name": "SetInformationTrustedDomain"}
2538 remote_lsa.SetInformationTrustedDomain(remote_tdo_handle,
2539 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2542 self.outf.write("Creating local TDO.\n")
2543 current_request = { "location": "local", "name": "CreateTrustedDomainEx2"}
2544 local_tdo_handle = local_lsa.CreateTrustedDomainEx2(local_policy,
2547 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2548 self.outf.write("Local TDO created\n")
2550 self.outf.write("Setting supported encryption types on local TDO.\n")
2551 current_request = { "location": "local", "name": "SetInformationTrustedDomain"}
2552 local_lsa.SetInformationTrustedDomain(local_tdo_handle,
2553 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2555 except RuntimeError as error:
2556 self.outf.write("Error: %s failed %sly - cleaning up\n" % (
2557 current_request['name'], current_request['location']))
2558 if remote_tdo_handle:
2559 self.outf.write("Deleting remote TDO.\n")
2560 remote_lsa.DeleteObject(remote_tdo_handle)
2561 remote_tdo_handle = None
2562 if local_tdo_handle:
2563 self.outf.write("Deleting local TDO.\n")
2564 local_lsa.DeleteObject(local_tdo_handle)
2565 local_tdo_handle = None
2566 if current_request['location'] is "remote":
2567 raise self.RemoteRuntimeError(self, error, "%s" % (
2568 current_request['name']))
2569 raise self.LocalRuntimeError(self, error, "%s" % (
2570 current_request['name']))
2573 if local_trust_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2574 self.outf.write("Setup local forest trust information...\n")
2576 # get all information about the remote trust
2577 # this triggers netr_GetForestTrustInformation to the remote domain
2578 # and lsaRSetForestTrustInformation() locally, but new top level
2579 # names are disabled by default.
2580 local_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
2581 remote_lsa_info.dns_domain.string,
2582 netlogon.DS_GFTI_UPDATE_TDO)
2583 except RuntimeError as error:
2584 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2587 # here we try to enable all top level names
2588 local_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
2589 remote_lsa_info.dns_domain,
2590 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2593 except RuntimeError as error:
2594 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2596 self.write_forest_trust_info(local_forest_info,
2597 tln=remote_lsa_info.dns_domain.string,
2598 collisions=local_forest_collision)
2600 if remote_trust_info:
2601 self.outf.write("Setup remote forest trust information...\n")
2603 # get all information about the local trust (from the perspective of the remote domain)
2604 # this triggers netr_GetForestTrustInformation to our domain.
2605 # and lsaRSetForestTrustInformation() remotely, but new top level
2606 # names are disabled by default.
2607 remote_forest_info = remote_netlogon.netr_DsRGetForestTrustInformation(remote_netlogon_info.dc_unc,
2608 local_lsa_info.dns_domain.string,
2609 netlogon.DS_GFTI_UPDATE_TDO)
2610 except RuntimeError as error:
2611 raise self.RemoteRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2614 # here we try to enable all top level names
2615 remote_forest_collision = remote_lsa.lsaRSetForestTrustInformation(remote_policy,
2616 local_lsa_info.dns_domain,
2617 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2620 except RuntimeError as error:
2621 raise self.RemoteRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2623 self.write_forest_trust_info(remote_forest_info,
2624 tln=local_lsa_info.dns_domain.string,
2625 collisions=remote_forest_collision)
2627 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2628 self.outf.write("Validating outgoing trust...\n")
2630 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_netlogon_info.dc_unc,
2631 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2633 remote_lsa_info.dns_domain.string)
2634 except RuntimeError as error:
2635 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2637 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2638 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2640 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2641 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2642 local_trust_verify.trusted_dc_name,
2643 local_trust_verify.tc_connection_status[1],
2644 local_trust_verify.pdc_connection_status[1])
2646 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2647 local_trust_verify.trusted_dc_name,
2648 local_trust_verify.tc_connection_status[1],
2649 local_trust_verify.pdc_connection_status[1])
2651 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2652 raise CommandError(local_validation)
2654 self.outf.write("OK: %s\n" % local_validation)
2656 if remote_trust_info:
2657 if remote_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2658 self.outf.write("Validating incoming trust...\n")
2660 remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_netlogon_info.dc_unc,
2661 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2663 local_lsa_info.dns_domain.string)
2664 except RuntimeError as error:
2665 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2667 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2668 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2670 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2671 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2672 remote_trust_verify.trusted_dc_name,
2673 remote_trust_verify.tc_connection_status[1],
2674 remote_trust_verify.pdc_connection_status[1])
2676 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2677 remote_trust_verify.trusted_dc_name,
2678 remote_trust_verify.tc_connection_status[1],
2679 remote_trust_verify.pdc_connection_status[1])
2681 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
2682 raise CommandError(remote_validation)
2684 self.outf.write("OK: %s\n" % remote_validation)
2686 if remote_tdo_handle is not None:
2688 remote_lsa.Close(remote_tdo_handle)
2689 except RuntimeError as error:
2691 remote_tdo_handle = None
2692 if local_tdo_handle is not None:
2694 local_lsa.Close(local_tdo_handle)
2695 except RuntimeError as error:
2697 local_tdo_handle = None
2699 self.outf.write("Success.\n")
2702 class cmd_domain_trust_delete(DomainTrustCommand):
2703 """Delete a domain trust."""
2705 synopsis = "%prog DOMAIN [options]"
2707 takes_optiongroups = {
2708 "sambaopts": options.SambaOptions,
2709 "versionopts": options.VersionOptions,
2710 "credopts": options.CredentialsOptions,
2711 "localdcopts": LocalDCCredentialsOptions,
2715 Option("--delete-location", type="choice", metavar="LOCATION",
2716 choices=["local", "both"],
2717 help="Where to delete the trusted domain object: 'local' or 'both'.",
2718 dest='delete_location',
2722 takes_args = ["domain"]
2724 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2725 delete_location=None):
2727 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2728 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2729 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2731 if delete_location == "local":
2732 remote_policy_access = None
2734 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2735 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2736 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2738 local_server = self.setup_local_server(sambaopts, localdcopts)
2740 local_lsa = self.new_local_lsa_connection()
2741 except RuntimeError as error:
2742 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2745 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2746 except RuntimeError as error:
2747 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2749 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2750 local_lsa_info.name.string,
2751 local_lsa_info.dns_domain.string,
2752 local_lsa_info.sid))
2754 local_tdo_info = None
2755 local_tdo_handle = None
2756 remote_tdo_info = None
2757 remote_tdo_handle = None
2759 lsaString = lsa.String()
2761 lsaString.string = domain
2762 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2763 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2764 except NTSTATUSError as error:
2765 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2766 raise CommandError("Failed to find trust for domain '%s'" % domain)
2767 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2770 if remote_policy_access is not None:
2772 remote_server = self.setup_remote_server(credopts, domain)
2773 except RuntimeError as error:
2774 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2777 remote_lsa = self.new_remote_lsa_connection()
2778 except RuntimeError as error:
2779 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2782 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2783 except RuntimeError as error:
2784 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2786 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2787 remote_lsa_info.name.string,
2788 remote_lsa_info.dns_domain.string,
2789 remote_lsa_info.sid))
2791 if remote_lsa_info.sid != local_tdo_info.sid or \
2792 remote_lsa_info.name.string != local_tdo_info.netbios_name.string or \
2793 remote_lsa_info.dns_domain.string != local_tdo_info.domain_name.string:
2794 raise CommandError("LocalTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2795 local_tdo_info.netbios_name.string,
2796 local_tdo_info.domain_name.string,
2797 local_tdo_info.sid))
2800 lsaString.string = local_lsa_info.dns_domain.string
2801 remote_tdo_info = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2802 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2803 except NTSTATUSError as error:
2804 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2805 raise self.RemoteRuntimeError(self, error, "QueryTrustedDomainInfoByName(%s)" % (
2809 if remote_tdo_info is not None:
2810 if local_lsa_info.sid != remote_tdo_info.sid or \
2811 local_lsa_info.name.string != remote_tdo_info.netbios_name.string or \
2812 local_lsa_info.dns_domain.string != remote_tdo_info.domain_name.string:
2813 raise CommandError("RemoteTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2814 remote_tdo_info.netbios_name.string,
2815 remote_tdo_info.domain_name.string,
2816 remote_tdo_info.sid))
2818 if local_tdo_info is not None:
2820 lsaString.string = local_tdo_info.domain_name.string
2821 local_tdo_handle = local_lsa.OpenTrustedDomainByName(local_policy,
2823 security.SEC_STD_DELETE)
2824 except RuntimeError as error:
2825 raise self.LocalRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2828 local_lsa.DeleteObject(local_tdo_handle)
2829 local_tdo_handle = None
2831 if remote_tdo_info is not None:
2833 lsaString.string = remote_tdo_info.domain_name.string
2834 remote_tdo_handle = remote_lsa.OpenTrustedDomainByName(remote_policy,
2836 security.SEC_STD_DELETE)
2837 except RuntimeError as error:
2838 raise self.RemoteRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2841 if remote_tdo_handle is not None:
2843 remote_lsa.DeleteObject(remote_tdo_handle)
2844 remote_tdo_handle = None
2845 self.outf.write("RemoteTDO deleted.\n")
2846 except RuntimeError as error:
2847 self.outf.write("%s\n" % self.RemoteRuntimeError(self, error, "DeleteObject() failed"))
2849 if local_tdo_handle is not None:
2851 local_lsa.DeleteObject(local_tdo_handle)
2852 local_tdo_handle = None
2853 self.outf.write("LocalTDO deleted.\n")
2854 except RuntimeError as error:
2855 self.outf.write("%s\n" % self.LocalRuntimeError(self, error, "DeleteObject() failed"))
2859 class cmd_domain_trust_validate(DomainTrustCommand):
2860 """Validate a domain trust."""
2862 synopsis = "%prog DOMAIN [options]"
2864 takes_optiongroups = {
2865 "sambaopts": options.SambaOptions,
2866 "versionopts": options.VersionOptions,
2867 "credopts": options.CredentialsOptions,
2868 "localdcopts": LocalDCCredentialsOptions,
2872 Option("--validate-location", type="choice", metavar="LOCATION",
2873 choices=["local", "both"],
2874 help="Where to validate the trusted domain object: 'local' or 'both'.",
2875 dest='validate_location',
2879 takes_args = ["domain"]
2881 def run(self, domain, sambaopts=None, versionopts=None, credopts=None, localdcopts=None,
2882 validate_location=None):
2884 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2886 local_server = self.setup_local_server(sambaopts, localdcopts)
2888 local_lsa = self.new_local_lsa_connection()
2889 except RuntimeError as error:
2890 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2893 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2894 except RuntimeError as error:
2895 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2897 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2898 local_lsa_info.name.string,
2899 local_lsa_info.dns_domain.string,
2900 local_lsa_info.sid))
2903 lsaString = lsa.String()
2904 lsaString.string = domain
2905 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2906 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2907 except NTSTATUSError as error:
2908 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2909 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2911 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
2913 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
2914 local_tdo_info.netbios_name.string,
2915 local_tdo_info.domain_name.string,
2916 local_tdo_info.sid))
2919 local_netlogon = self.new_local_netlogon_connection()
2920 except RuntimeError as error:
2921 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2924 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_server,
2925 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2927 local_tdo_info.domain_name.string)
2928 except RuntimeError as error:
2929 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2931 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2932 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2934 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2935 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2936 local_trust_verify.trusted_dc_name,
2937 local_trust_verify.tc_connection_status[1],
2938 local_trust_verify.pdc_connection_status[1])
2940 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2941 local_trust_verify.trusted_dc_name,
2942 local_trust_verify.tc_connection_status[1],
2943 local_trust_verify.pdc_connection_status[1])
2945 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2946 raise CommandError(local_validation)
2948 self.outf.write("OK: %s\n" % local_validation)
2951 server = local_trust_verify.trusted_dc_name.replace('\\', '')
2952 domain_and_server = "%s\\%s" % (local_tdo_info.domain_name.string, server)
2953 local_trust_rediscover = local_netlogon.netr_LogonControl2Ex(local_server,
2954 netlogon.NETLOGON_CONTROL_REDISCOVER,
2957 except RuntimeError as error:
2958 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
2960 local_conn_status = self._uint32(local_trust_rediscover.tc_connection_status[0])
2961 local_rediscover = "LocalRediscover: DC[%s] CONNECTION[%s]" % (
2962 local_trust_rediscover.trusted_dc_name,
2963 local_trust_rediscover.tc_connection_status[1])
2965 if local_conn_status != werror.WERR_SUCCESS:
2966 raise CommandError(local_rediscover)
2968 self.outf.write("OK: %s\n" % local_rediscover)
2970 if validate_location != "local":
2972 remote_server = self.setup_remote_server(credopts, domain, require_pdc=False)
2973 except RuntimeError as error:
2974 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2977 remote_netlogon = self.new_remote_netlogon_connection()
2978 except RuntimeError as error:
2979 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2982 remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_server,
2983 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2985 local_lsa_info.dns_domain.string)
2986 except RuntimeError as error:
2987 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2989 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2990 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2992 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2993 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2994 remote_trust_verify.trusted_dc_name,
2995 remote_trust_verify.tc_connection_status[1],
2996 remote_trust_verify.pdc_connection_status[1])
2998 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2999 remote_trust_verify.trusted_dc_name,
3000 remote_trust_verify.tc_connection_status[1],
3001 remote_trust_verify.pdc_connection_status[1])
3003 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
3004 raise CommandError(remote_validation)
3006 self.outf.write("OK: %s\n" % remote_validation)
3009 server = remote_trust_verify.trusted_dc_name.replace('\\', '')
3010 domain_and_server = "%s\\%s" % (local_lsa_info.dns_domain.string, server)
3011 remote_trust_rediscover = remote_netlogon.netr_LogonControl2Ex(remote_server,
3012 netlogon.NETLOGON_CONTROL_REDISCOVER,
3015 except RuntimeError as error:
3016 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
3018 remote_conn_status = self._uint32(remote_trust_rediscover.tc_connection_status[0])
3020 remote_rediscover = "RemoteRediscover: DC[%s] CONNECTION[%s]" % (
3021 remote_trust_rediscover.trusted_dc_name,
3022 remote_trust_rediscover.tc_connection_status[1])
3024 if remote_conn_status != werror.WERR_SUCCESS:
3025 raise CommandError(remote_rediscover)
3027 self.outf.write("OK: %s\n" % remote_rediscover)
3031 class cmd_domain_trust_namespaces(DomainTrustCommand):
3032 """Manage forest trust namespaces."""
3034 synopsis = "%prog [DOMAIN] [options]"
3036 takes_optiongroups = {
3037 "sambaopts": options.SambaOptions,
3038 "versionopts": options.VersionOptions,
3039 "localdcopts": LocalDCCredentialsOptions,
3043 Option("--refresh", type="choice", metavar="check|store",
3044 choices=["check", "store", None],
3045 help="List and maybe store refreshed forest trust information: 'check' or 'store'.",
3048 Option("--enable-all", action="store_true",
3049 help="Try to update disabled entries, not allowed with --refresh=check.",
3052 Option("--enable-tln", action="append", metavar='DNSDOMAIN',
3053 help="Enable a top level name entry. Can be specified multiple times.",
3056 Option("--disable-tln", action="append", metavar='DNSDOMAIN',
3057 help="Disable a top level name entry. Can be specified multiple times.",
3060 Option("--add-tln-ex", action="append", metavar='DNSDOMAIN',
3061 help="Add a top level exclusion entry. Can be specified multiple times.",
3064 Option("--delete-tln-ex", action="append", metavar='DNSDOMAIN',
3065 help="Delete a top level exclusion entry. Can be specified multiple times.",
3066 dest='delete_tln_ex',
3068 Option("--enable-nb", action="append", metavar='NETBIOSDOMAIN',
3069 help="Enable a netbios name in a domain entry. Can be specified multiple times.",
3072 Option("--disable-nb", action="append", metavar='NETBIOSDOMAIN',
3073 help="Disable a netbios name in a domain entry. Can be specified multiple times.",
3076 Option("--enable-sid", action="append", metavar='DOMAINSID',
3077 help="Enable a SID in a domain entry. Can be specified multiple times.",
3078 dest='enable_sid_str',
3080 Option("--disable-sid", action="append", metavar='DOMAINSID',
3081 help="Disable a SID in a domain entry. Can be specified multiple times.",
3082 dest='disable_sid_str',
3084 Option("--add-upn-suffix", action="append", metavar='DNSDOMAIN',
3085 help="Add a new uPNSuffixes attribute for the local forest. Can be specified multiple times.",
3088 Option("--delete-upn-suffix", action="append", metavar='DNSDOMAIN',
3089 help="Delete an existing uPNSuffixes attribute of the local forest. Can be specified multiple times.",
3092 Option("--add-spn-suffix", action="append", metavar='DNSDOMAIN',
3093 help="Add a new msDS-SPNSuffixes attribute for the local forest. Can be specified multiple times.",
3096 Option("--delete-spn-suffix", action="append", metavar='DNSDOMAIN',
3097 help="Delete an existing msDS-SPNSuffixes attribute of the local forest. Can be specified multiple times.",
3102 takes_args = ["domain?"]
3104 def run(self, domain=None, sambaopts=None, localdcopts=None, versionopts=None,
3105 refresh=None, enable_all=False,
3106 enable_tln=[], disable_tln=[], add_tln_ex=[], delete_tln_ex=[],
3107 enable_sid_str=[], disable_sid_str=[], enable_nb=[], disable_nb=[],
3108 add_upn=[], delete_upn=[], add_spn=[], delete_spn=[]):
3110 require_update = False
3113 if refresh == "store":
3114 raise CommandError("--refresh=%s not allowed without DOMAIN" % refresh)
3117 raise CommandError("--enable-all not allowed without DOMAIN")
3119 if len(enable_tln) > 0:
3120 raise CommandError("--enable-tln not allowed without DOMAIN")
3121 if len(disable_tln) > 0:
3122 raise CommandError("--disable-tln not allowed without DOMAIN")
3124 if len(add_tln_ex) > 0:
3125 raise CommandError("--add-tln-ex not allowed without DOMAIN")
3126 if len(delete_tln_ex) > 0:
3127 raise CommandError("--delete-tln-ex not allowed without DOMAIN")
3129 if len(enable_nb) > 0:
3130 raise CommandError("--enable-nb not allowed without DOMAIN")
3131 if len(disable_nb) > 0:
3132 raise CommandError("--disable-nb not allowed without DOMAIN")
3134 if len(enable_sid_str) > 0:
3135 raise CommandError("--enable-sid not allowed without DOMAIN")
3136 if len(disable_sid_str) > 0:
3137 raise CommandError("--disable-sid not allowed without DOMAIN")
3139 if len(add_upn) > 0:
3141 if not n.startswith("*."):
3143 raise CommandError("value[%s] specified for --add-upn-suffix should not include with '*.'" % n)
3144 require_update = True
3145 if len(delete_upn) > 0:
3146 for n in delete_upn:
3147 if not n.startswith("*."):
3149 raise CommandError("value[%s] specified for --delete-upn-suffix should not include with '*.'" % n)
3150 require_update = True
3152 for d in delete_upn:
3153 if a.lower() != d.lower():
3155 raise CommandError("value[%s] specified for --add-upn-suffix and --delete-upn-suffix" % a)
3157 if len(add_spn) > 0:
3159 if not n.startswith("*."):
3161 raise CommandError("value[%s] specified for --add-spn-suffix should not include with '*.'" % n)
3162 require_update = True
3163 if len(delete_spn) > 0:
3164 for n in delete_spn:
3165 if not n.startswith("*."):
3167 raise CommandError("value[%s] specified for --delete-spn-suffix should not include with '*.'" % n)
3168 require_update = True
3170 for d in delete_spn:
3171 if a.lower() != d.lower():
3173 raise CommandError("value[%s] specified for --add-spn-suffix and --delete-spn-suffix" % a)
3175 if len(add_upn) > 0:
3176 raise CommandError("--add-upn-suffix not allowed together with DOMAIN")
3177 if len(delete_upn) > 0:
3178 raise CommandError("--delete-upn-suffix not allowed together with DOMAIN")
3179 if len(add_spn) > 0:
3180 raise CommandError("--add-spn-suffix not allowed together with DOMAIN")
3181 if len(delete_spn) > 0:
3182 raise CommandError("--delete-spn-suffix not allowed together with DOMAIN")
3184 if refresh is not None:
3185 if refresh == "store":
3186 require_update = True
3188 if enable_all and refresh != "store":
3189 raise CommandError("--enable-all not allowed together with --refresh=%s" % refresh)
3191 if len(enable_tln) > 0:
3192 raise CommandError("--enable-tln not allowed together with --refresh")
3193 if len(disable_tln) > 0:
3194 raise CommandError("--disable-tln not allowed together with --refresh")
3196 if len(add_tln_ex) > 0:
3197 raise CommandError("--add-tln-ex not allowed together with --refresh")
3198 if len(delete_tln_ex) > 0:
3199 raise CommandError("--delete-tln-ex not allowed together with --refresh")
3201 if len(enable_nb) > 0:
3202 raise CommandError("--enable-nb not allowed together with --refresh")
3203 if len(disable_nb) > 0:
3204 raise CommandError("--disable-nb not allowed together with --refresh")
3206 if len(enable_sid_str) > 0:
3207 raise CommandError("--enable-sid not allowed together with --refresh")
3208 if len(disable_sid_str) > 0:
3209 raise CommandError("--disable-sid not allowed together with --refresh")
3212 require_update = True
3214 if len(enable_tln) > 0:
3215 raise CommandError("--enable-tln not allowed together with --enable-all")
3217 if len(enable_nb) > 0:
3218 raise CommandError("--enable-nb not allowed together with --enable-all")
3220 if len(enable_sid_str) > 0:
3221 raise CommandError("--enable-sid not allowed together with --enable-all")
3223 if len(enable_tln) > 0:
3224 require_update = True
3225 if len(disable_tln) > 0:
3226 require_update = True
3227 for e in enable_tln:
3228 for d in disable_tln:
3229 if e.lower() != d.lower():
3231 raise CommandError("value[%s] specified for --enable-tln and --disable-tln" % e)
3233 if len(add_tln_ex) > 0:
3234 for n in add_tln_ex:
3235 if not n.startswith("*."):
3237 raise CommandError("value[%s] specified for --add-tln-ex should not include with '*.'" % n)
3238 require_update = True
3239 if len(delete_tln_ex) > 0:
3240 for n in delete_tln_ex:
3241 if not n.startswith("*."):
3243 raise CommandError("value[%s] specified for --delete-tln-ex should not include with '*.'" % n)
3244 require_update = True
3245 for a in add_tln_ex:
3246 for d in delete_tln_ex:
3247 if a.lower() != d.lower():
3249 raise CommandError("value[%s] specified for --add-tln-ex and --delete-tln-ex" % a)
3251 if len(enable_nb) > 0:
3252 require_update = True
3253 if len(disable_nb) > 0:
3254 require_update = True
3256 for d in disable_nb:
3257 if e.upper() != d.upper():
3259 raise CommandError("value[%s] specified for --enable-nb and --disable-nb" % e)
3262 for s in enable_sid_str:
3264 sid = security.dom_sid(s)
3265 except TypeError as error:
3266 raise CommandError("value[%s] specified for --enable-sid is not a valid SID" % s)
3267 enable_sid.append(sid)
3269 for s in disable_sid_str:
3271 sid = security.dom_sid(s)
3272 except TypeError as error:
3273 raise CommandError("value[%s] specified for --disable-sid is not a valid SID" % s)
3274 disable_sid.append(sid)
3275 if len(enable_sid) > 0:
3276 require_update = True
3277 if len(disable_sid) > 0:
3278 require_update = True
3279 for e in enable_sid:
3280 for d in disable_sid:
3283 raise CommandError("value[%s] specified for --enable-sid and --disable-sid" % e)
3285 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
3287 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
3289 local_server = self.setup_local_server(sambaopts, localdcopts)
3291 local_lsa = self.new_local_lsa_connection()
3292 except RuntimeError as error:
3293 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
3296 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
3297 except RuntimeError as error:
3298 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
3300 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
3301 local_lsa_info.name.string,
3302 local_lsa_info.dns_domain.string,
3303 local_lsa_info.sid))
3307 local_netlogon = self.new_local_netlogon_connection()
3308 except RuntimeError as error:
3309 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3312 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3313 except RuntimeError as error:
3314 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3316 if local_netlogon_info.domain_name != local_netlogon_info.forest_name:
3317 raise CommandError("The local domain [%s] is not the forest root [%s]" % (
3318 local_netlogon_info.domain_name,
3319 local_netlogon_info.forest_name))
3322 # get all information about our own forest
3323 own_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3325 except RuntimeError as error:
3326 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
3327 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3330 if self.check_runtime_error(error, werror.WERR_INVALID_FUNCTION):
3331 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3334 if self.check_runtime_error(error, werror.WERR_NERR_ACFNOTLOADED):
3335 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3338 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3340 self.outf.write("Own forest trust information...\n")
3341 self.write_forest_trust_info(own_forest_info,
3342 tln=local_lsa_info.dns_domain.string)
3345 local_samdb = self.new_local_ldap_connection()
3346 except RuntimeError as error:
3347 raise self.LocalRuntimeError(self, error, "failed to connect to SamDB")
3349 local_partitions_dn = "CN=Partitions,%s" % str(local_samdb.get_config_basedn())
3350 attrs = ['uPNSuffixes', 'msDS-SPNSuffixes']
3352 msgs = local_samdb.search(base=local_partitions_dn,
3353 scope=ldb.SCOPE_BASE,
3354 expression="(objectClass=crossRefContainer)",
3356 stored_msg = msgs[0]
3357 except ldb.LdbError as error:
3358 raise self.LocalLdbError(self, error, "failed to search partition dn")
3360 stored_upn_vals = []
3361 if 'uPNSuffixes' in stored_msg:
3362 stored_upn_vals.extend(stored_msg['uPNSuffixes'])
3364 stored_spn_vals = []
3365 if 'msDS-SPNSuffixes' in stored_msg:
3366 stored_spn_vals.extend(stored_msg['msDS-SPNSuffixes'])
3368 self.outf.write("Stored uPNSuffixes attributes[%d]:\n" % len(stored_upn_vals))
3369 for v in stored_upn_vals:
3370 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3371 self.outf.write("Stored msDS-SPNSuffixes attributes[%d]:\n" % len(stored_spn_vals))
3372 for v in stored_spn_vals:
3373 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3375 if not require_update:
3379 update_upn_vals = []
3380 update_upn_vals.extend(stored_upn_vals)
3383 update_spn_vals = []
3384 update_spn_vals.extend(stored_spn_vals)
3388 for i in xrange(0, len(update_upn_vals)):
3389 v = update_upn_vals[i]
3390 if v.lower() != upn.lower():
3395 raise CommandError("Entry already present for value[%s] specified for --add-upn-suffix" % upn)
3396 update_upn_vals.append(upn)
3399 for upn in delete_upn:
3401 for i in xrange(0, len(update_upn_vals)):
3402 v = update_upn_vals[i]
3403 if v.lower() != upn.lower():
3408 raise CommandError("Entry not found for value[%s] specified for --delete-upn-suffix" % upn)
3410 update_upn_vals.pop(idx)
3415 for i in xrange(0, len(update_spn_vals)):
3416 v = update_spn_vals[i]
3417 if v.lower() != spn.lower():
3422 raise CommandError("Entry already present for value[%s] specified for --add-spn-suffix" % spn)
3423 update_spn_vals.append(spn)
3426 for spn in delete_spn:
3428 for i in xrange(0, len(update_spn_vals)):
3429 v = update_spn_vals[i]
3430 if v.lower() != spn.lower():
3435 raise CommandError("Entry not found for value[%s] specified for --delete-spn-suffix" % spn)
3437 update_spn_vals.pop(idx)
3440 self.outf.write("Update uPNSuffixes attributes[%d]:\n" % len(update_upn_vals))
3441 for v in update_upn_vals:
3442 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3443 self.outf.write("Update msDS-SPNSuffixes attributes[%d]:\n" % len(update_spn_vals))
3444 for v in update_spn_vals:
3445 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3447 update_msg = ldb.Message()
3448 update_msg.dn = stored_msg.dn
3451 update_msg['uPNSuffixes'] = ldb.MessageElement(update_upn_vals,
3452 ldb.FLAG_MOD_REPLACE,
3455 update_msg['msDS-SPNSuffixes'] = ldb.MessageElement(update_spn_vals,
3456 ldb.FLAG_MOD_REPLACE,
3459 local_samdb.modify(update_msg)
3460 except ldb.LdbError as error:
3461 raise self.LocalLdbError(self, error, "failed to update partition dn")
3464 stored_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3466 except RuntimeError as error:
3467 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3469 self.outf.write("Stored forest trust information...\n")
3470 self.write_forest_trust_info(stored_forest_info,
3471 tln=local_lsa_info.dns_domain.string)
3475 lsaString = lsa.String()
3476 lsaString.string = domain
3477 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
3478 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
3479 except NTSTATUSError as error:
3480 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
3481 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
3483 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
3485 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
3486 local_tdo_info.netbios_name.string,
3487 local_tdo_info.domain_name.string,
3488 local_tdo_info.sid))
3490 if not local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
3491 raise CommandError("trusted domain object for domain [%s] is not marked as FOREST_TRANSITIVE." % domain)
3493 if refresh is not None:
3495 local_netlogon = self.new_local_netlogon_connection()
3496 except RuntimeError as error:
3497 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3500 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3501 except RuntimeError as error:
3502 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3504 lsa_update_check = 1
3505 if refresh == "store":
3506 netlogon_update_tdo = netlogon.DS_GFTI_UPDATE_TDO
3508 lsa_update_check = 0
3510 netlogon_update_tdo = 0
3513 # get all information about the remote trust
3514 # this triggers netr_GetForestTrustInformation to the remote domain
3515 # and lsaRSetForestTrustInformation() locally, but new top level
3516 # names are disabled by default.
3517 fresh_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3518 local_tdo_info.domain_name.string,
3519 netlogon_update_tdo)
3520 except RuntimeError as error:
3521 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3524 fresh_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3525 local_tdo_info.domain_name,
3526 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3529 except RuntimeError as error:
3530 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3532 self.outf.write("Fresh forest trust information...\n")
3533 self.write_forest_trust_info(fresh_forest_info,
3534 tln=local_tdo_info.domain_name.string,
3535 collisions=fresh_forest_collision)
3537 if refresh == "store":
3539 lsaString = lsa.String()
3540 lsaString.string = local_tdo_info.domain_name.string
3541 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3543 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3544 except RuntimeError as error:
3545 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3547 self.outf.write("Stored forest trust information...\n")
3548 self.write_forest_trust_info(stored_forest_info,
3549 tln=local_tdo_info.domain_name.string)
3554 # The none --refresh path
3558 lsaString = lsa.String()
3559 lsaString.string = local_tdo_info.domain_name.string
3560 local_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3562 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3563 except RuntimeError as error:
3564 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3566 self.outf.write("Local forest trust information...\n")
3567 self.write_forest_trust_info(local_forest_info,
3568 tln=local_tdo_info.domain_name.string)
3570 if not require_update:
3574 entries.extend(local_forest_info.entries)
3575 update_forest_info = lsa.ForestTrustInformation()
3576 update_forest_info.count = len(entries)
3577 update_forest_info.entries = entries
3580 for i in xrange(0, len(update_forest_info.entries)):
3581 r = update_forest_info.entries[i]
3582 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3584 if update_forest_info.entries[i].flags == 0:
3586 update_forest_info.entries[i].time = 0
3587 update_forest_info.entries[i].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3588 for i in xrange(0, len(update_forest_info.entries)):
3589 r = update_forest_info.entries[i]
3590 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3592 if update_forest_info.entries[i].flags == 0:
3594 update_forest_info.entries[i].time = 0
3595 update_forest_info.entries[i].flags &= ~lsa.LSA_NB_DISABLED_MASK
3596 update_forest_info.entries[i].flags &= ~lsa.LSA_SID_DISABLED_MASK
3598 for tln in enable_tln:
3600 for i in xrange(0, len(update_forest_info.entries)):
3601 r = update_forest_info.entries[i]
3602 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3604 if r.forest_trust_data.string.lower() != tln.lower():
3609 raise CommandError("Entry not found for value[%s] specified for --enable-tln" % tln)
3610 if not update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_MASK:
3611 raise CommandError("Entry found for value[%s] specified for --enable-tln is already enabled" % tln)
3612 update_forest_info.entries[idx].time = 0
3613 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3615 for tln in disable_tln:
3617 for i in xrange(0, len(update_forest_info.entries)):
3618 r = update_forest_info.entries[i]
3619 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3621 if r.forest_trust_data.string.lower() != tln.lower():
3626 raise CommandError("Entry not found for value[%s] specified for --disable-tln" % tln)
3627 if update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_ADMIN:
3628 raise CommandError("Entry found for value[%s] specified for --disable-tln is already disabled" % tln)
3629 update_forest_info.entries[idx].time = 0
3630 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3631 update_forest_info.entries[idx].flags |= lsa.LSA_TLN_DISABLED_ADMIN
3633 for tln_ex in add_tln_ex:
3635 for i in xrange(0, len(update_forest_info.entries)):
3636 r = update_forest_info.entries[i]
3637 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3639 if r.forest_trust_data.string.lower() != tln_ex.lower():
3644 raise CommandError("Entry already present for value[%s] specified for --add-tln-ex" % tln_ex)
3646 tln_dot = ".%s" % tln_ex.lower()
3648 for i in xrange(0, len(update_forest_info.entries)):
3649 r = update_forest_info.entries[i]
3650 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3652 r_dot = ".%s" % r.forest_trust_data.string.lower()
3653 if tln_dot == r_dot:
3654 raise CommandError("TLN entry present for value[%s] specified for --add-tln-ex" % tln_ex)
3655 if not tln_dot.endswith(r_dot):
3661 raise CommandError("No TLN parent present for value[%s] specified for --add-tln-ex" % tln_ex)
3663 r = lsa.ForestTrustRecord()
3664 r.type = lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX
3667 r.forest_trust_data.string = tln_ex
3670 entries.extend(update_forest_info.entries)
3671 entries.insert(idx + 1, r)
3672 update_forest_info.count = len(entries)
3673 update_forest_info.entries = entries
3675 for tln_ex in delete_tln_ex:
3677 for i in xrange(0, len(update_forest_info.entries)):
3678 r = update_forest_info.entries[i]
3679 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3681 if r.forest_trust_data.string.lower() != tln_ex.lower():
3686 raise CommandError("Entry not found for value[%s] specified for --delete-tln-ex" % tln_ex)
3689 entries.extend(update_forest_info.entries)
3691 update_forest_info.count = len(entries)
3692 update_forest_info.entries = entries
3694 for nb in enable_nb:
3696 for i in xrange(0, len(update_forest_info.entries)):
3697 r = update_forest_info.entries[i]
3698 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3700 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3705 raise CommandError("Entry not found for value[%s] specified for --enable-nb" % nb)
3706 if not update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_MASK:
3707 raise CommandError("Entry found for value[%s] specified for --enable-nb is already enabled" % nb)
3708 update_forest_info.entries[idx].time = 0
3709 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3711 for nb in disable_nb:
3713 for i in xrange(0, len(update_forest_info.entries)):
3714 r = update_forest_info.entries[i]
3715 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3717 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3722 raise CommandError("Entry not found for value[%s] specified for --delete-nb" % nb)
3723 if update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_ADMIN:
3724 raise CommandError("Entry found for value[%s] specified for --disable-nb is already disabled" % nb)
3725 update_forest_info.entries[idx].time = 0
3726 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3727 update_forest_info.entries[idx].flags |= lsa.LSA_NB_DISABLED_ADMIN
3729 for sid in enable_sid:
3731 for i in xrange(0, len(update_forest_info.entries)):
3732 r = update_forest_info.entries[i]
3733 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3735 if r.forest_trust_data.domain_sid != sid:
3740 raise CommandError("Entry not found for value[%s] specified for --enable-sid" % sid)
3741 if not update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_MASK:
3742 raise CommandError("Entry found for value[%s] specified for --enable-sid is already enabled" % nb)
3743 update_forest_info.entries[idx].time = 0
3744 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3746 for sid in disable_sid:
3748 for i in xrange(0, len(update_forest_info.entries)):
3749 r = update_forest_info.entries[i]
3750 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3752 if r.forest_trust_data.domain_sid != sid:
3757 raise CommandError("Entry not found for value[%s] specified for --delete-sid" % sid)
3758 if update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_ADMIN:
3759 raise CommandError("Entry found for value[%s] specified for --disable-sid is already disabled" % nb)
3760 update_forest_info.entries[idx].time = 0
3761 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3762 update_forest_info.entries[idx].flags |= lsa.LSA_SID_DISABLED_ADMIN
3765 update_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3766 local_tdo_info.domain_name,
3767 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3768 update_forest_info, 0)
3769 except RuntimeError as error:
3770 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3772 self.outf.write("Updated forest trust information...\n")
3773 self.write_forest_trust_info(update_forest_info,
3774 tln=local_tdo_info.domain_name.string,
3775 collisions=update_forest_collision)
3778 lsaString = lsa.String()
3779 lsaString.string = local_tdo_info.domain_name.string
3780 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3782 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3783 except RuntimeError as error:
3784 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3786 self.outf.write("Stored forest trust information...\n")
3787 self.write_forest_trust_info(stored_forest_info,
3788 tln=local_tdo_info.domain_name.string)
3791 class cmd_domain_tombstones_expunge(Command):
3792 """Expunge tombstones from the database.
3794 This command expunges tombstones from the database."""
3795 synopsis = "%prog NC [NC [...]] [options]"
3798 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3799 metavar="URL", dest="H"),
3800 Option("--current-time",
3801 help="The current time to evaluate the tombstone lifetime from, expressed as YYYY-MM-DD",
3803 Option("--tombstone-lifetime", help="Number of days a tombstone should be preserved for", type=int),
3806 takes_args = ["nc*"]
3808 takes_optiongroups = {
3809 "sambaopts": options.SambaOptions,
3810 "credopts": options.CredentialsOptions,
3811 "versionopts": options.VersionOptions,
3814 def run(self, *ncs, **kwargs):
3815 sambaopts = kwargs.get("sambaopts")
3816 credopts = kwargs.get("credopts")
3817 versionpts = kwargs.get("versionopts")
3819 current_time_string = kwargs.get("current_time")
3820 tombstone_lifetime = kwargs.get("tombstone_lifetime")
3821 lp = sambaopts.get_loadparm()
3822 creds = credopts.get_credentials(lp)
3823 samdb = SamDB(url=H, session_info=system_session(),
3824 credentials=creds, lp=lp)
3826 if current_time_string is not None:
3827 current_time_obj = time.strptime(current_time_string, "%Y-%m-%d")
3828 current_time = long(time.mktime(current_time_obj))
3831 current_time = long(time.time())
3834 res = samdb.search(expression="", base="", scope=ldb.SCOPE_BASE,
3835 attrs=["namingContexts"])
3838 for nc in res[0]["namingContexts"]:
3843 started_transaction = False
3845 samdb.transaction_start()
3846 started_transaction = True
3848 removed_links) = samdb.garbage_collect_tombstones(ncs,
3849 current_time=current_time,
3850 tombstone_lifetime=tombstone_lifetime)
3852 except Exception, err:
3853 if started_transaction:
3854 samdb.transaction_cancel()
3855 raise CommandError("Failed to expunge / garbage collect tombstones", err)
3857 samdb.transaction_commit()
3859 self.outf.write("Removed %d objects and %d links successfully\n"
3860 % (removed_objects, removed_links))
3864 class cmd_domain_trust(SuperCommand):
3865 """Domain and forest trust management."""
3868 subcommands["list"] = cmd_domain_trust_list()
3869 subcommands["show"] = cmd_domain_trust_show()
3870 subcommands["create"] = cmd_domain_trust_create()
3871 subcommands["delete"] = cmd_domain_trust_delete()
3872 subcommands["validate"] = cmd_domain_trust_validate()
3873 subcommands["namespaces"] = cmd_domain_trust_namespaces()
3875 class cmd_domain_tombstones(SuperCommand):
3876 """Domain tombstone and recycled object management."""
3879 subcommands["expunge"] = cmd_domain_tombstones_expunge()
3881 class ldif_schema_update:
3882 """Helper class for applying LDIF schema updates"""
3885 self.is_defunct = False
3886 self.unknown_oid = None
3890 def _ldap_schemaUpdateNow(self, samdb):
3894 add: schemaUpdateNow
3897 samdb.modify_ldif(ldif)
3899 def can_ignore_failure(self, error):
3900 """Checks if we can safely ignore failure to apply an LDIF update"""
3901 (num, errstr) = error.args
3903 # Microsoft has marked objects as defunct that Samba doesn't know about
3904 if num == ldb.ERR_NO_SUCH_OBJECT and self.is_defunct:
3905 print("Defunct object %s doesn't exist, skipping" % self.dn)
3907 elif self.unknown_oid is not None:
3908 print("Skipping unknown OID %s for object %s" %(self.unknown_oid, self.dn))
3913 def apply(self, samdb):
3914 """Applies a single LDIF update to the schema"""
3917 samdb.modify_ldif(self.ldif, controls=['relax:0'])
3918 except ldb.LdbError as e:
3919 if self.can_ignore_failure(e):
3922 print("Exception: %s" % e)
3923 print("Encountered while trying to apply the following LDIF")
3924 print("----------------------------------------------------")
3925 print("%s" % self.ldif)
3929 # REFRESH AFTER EVERY CHANGE
3930 # Otherwise the OID-to-attribute mapping in _apply_updates_in_file()
3931 # won't work, because it can't lookup the new OID in the schema
3932 self._ldap_schemaUpdateNow(samdb)
3936 class cmd_domain_schema_upgrade(Command):
3937 """Domain schema upgrading"""
3939 synopsis = "%prog [options]"
3941 takes_optiongroups = {
3942 "sambaopts": options.SambaOptions,
3943 "versionopts": options.VersionOptions,
3944 "credopts": options.CredentialsOptions,
3948 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3949 metavar="URL", dest="H"),
3950 Option("--quiet", help="Be quiet", action="store_true"),
3951 Option("--verbose", help="Be verbose", action="store_true"),
3952 Option("--schema", type="choice", metavar="SCHEMA",
3953 choices=["2012", "2012_R2"],
3954 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
3956 Option("--ldf-file", type=str, default=None,
3957 help="Just apply the schema updates in the adprep/.LDF file(s) specified"),
3958 Option("--base-dir", type=str, default=None,
3959 help="Location of ldf files Default is ${SETUPDIR}/adprep.")
3962 def _apply_updates_in_file(self, samdb, ldif_file):
3964 Applies a series of updates specified in an .LDIF file. The .LDIF file
3965 is based on the adprep Schema updates provided by Microsoft.
3968 ldif_op = ldif_schema_update()
3970 # parse the file line by line and work out each update operation to apply
3971 for line in ldif_file:
3973 line = line.rstrip()
3975 # the operations in the .LDIF file are separated by blank lines. If
3976 # we hit a blank line, try to apply the update we've parsed so far
3979 # keep going if we haven't parsed anything yet
3980 if ldif_op.ldif == '':
3983 # Apply the individual change
3984 count += ldif_op.apply(samdb)
3986 # start storing the next operation from scratch again
3987 ldif_op = ldif_schema_update()
3990 # replace the placeholder domain name in the .ldif file with the real domain
3991 if line.upper().endswith('DC=X'):
3992 line = line[:-len('DC=X')] + str(samdb.get_default_basedn())
3993 elif line.upper().endswith('CN=X'):
3994 line = line[:-len('CN=X')] + str(samdb.get_default_basedn())
3996 values = line.split(':')
3998 if values[0].lower() == 'dn':
3999 ldif_op.dn = values[1].strip()
4001 # replace the Windows-specific operation with the Samba one
4002 if values[0].lower() == 'changetype':
4003 line = line.lower().replace(': ntdsschemaadd',
4005 line = line.lower().replace(': ntdsschemamodify',
4008 if values[0].lower() in ['rdnattid', 'subclassof',
4009 'systemposssuperiors',
4011 'systemauxiliaryclass']:
4014 # The Microsoft updates contain some OIDs we don't recognize.
4015 # Query the DB to see if we can work out the OID this update is
4016 # referring to. If we find a match, then replace the OID with
4017 # the ldapDisplayname
4019 res = samdb.search(base=samdb.get_schema_basedn(),
4020 expression="(|(attributeId=%s)(governsId=%s))" %
4022 attrs=['ldapDisplayName'])
4025 ldif_op.unknown_oid = value
4027 display_name = res[0]['ldapDisplayName'][0]
4028 line = line.replace(value, ' ' + display_name)
4030 # Microsoft has marked objects as defunct that Samba doesn't know about
4031 if values[0].lower() == 'isdefunct' and values[1].strip().lower() == 'true':
4032 ldif_op.is_defunct = True
4034 # Samba has added the showInAdvancedViewOnly attribute to all objects,
4035 # so rather than doing an add, we need to do a replace
4036 if values[0].lower() == 'add' and values[1].strip().lower() == 'showinadvancedviewonly':
4037 line = 'replace: showInAdvancedViewOnly'
4039 # Add the line to the current LDIF operation (including the newline
4040 # we stripped off at the start of the loop)
4041 ldif_op.ldif += line + '\n'
4046 def _apply_update(self, samdb, update_file, base_dir):
4047 """Wrapper function for parsing an LDIF file and applying the updates"""
4049 print("Applying %s updates..." % update_file)
4053 ldif_file = open(os.path.join(base_dir, update_file))
4055 count = self._apply_updates_in_file(samdb, ldif_file)
4061 print("%u changes applied" % count)
4065 def run(self, **kwargs):
4066 from samba.ms_schema_markdown import read_ms_markdown
4067 from samba.schema import Schema
4069 updates_allowed_overriden = False
4070 sambaopts = kwargs.get("sambaopts")
4071 credopts = kwargs.get("credopts")
4072 versionpts = kwargs.get("versionopts")
4073 lp = sambaopts.get_loadparm()
4074 creds = credopts.get_credentials(lp)
4076 target_schema = kwargs.get("schema")
4077 ldf_files = kwargs.get("ldf_file")
4078 base_dir = kwargs.get("base_dir")
4082 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4084 # we're not going to get far if the config doesn't allow schema updates
4085 if lp.get("dsdb:schema update allowed") is None:
4086 lp.set("dsdb:schema update allowed", "yes")
4087 print("Temporarily overriding 'dsdb:schema update allowed' setting")
4088 updates_allowed_overriden = True
4090 own_dn = ldb.Dn(samdb, samdb.get_dsServiceName())
4091 master = get_fsmo_roleowner(samdb, str(samdb.get_schema_basedn()),
4093 if own_dn != master:
4094 raise CommandError("This server is not the schema master.")
4096 # if specific LDIF files were specified, just apply them
4098 schema_updates = ldf_files.split(",")
4102 # work out the version of the target schema we're upgrading to
4103 end = Schema.get_version(target_schema)
4105 # work out the version of the schema we're currently using
4106 res = samdb.search(base=samdb.get_schema_basedn(),
4107 scope=ldb.SCOPE_BASE, attrs=['objectVersion'])
4110 raise CommandError('Could not determine current schema version')
4111 start = int(res[0]['objectVersion'][0]) + 1
4113 diff_dir = setup_path("adprep/WindowsServerDocs")
4114 if base_dir is None:
4115 # Read from the Schema-Updates.md file
4116 temp_folder = tempfile.mkdtemp()
4118 update_file = setup_path("adprep/WindowsServerDocs/Schema-Updates.md")
4121 read_ms_markdown(update_file, temp_folder)
4122 except Exception as e:
4123 print("Exception in markdown parsing: %s" % e)
4124 shutil.rmtree(temp_folder)
4125 raise CommandError('Failed to upgrade schema')
4127 base_dir = temp_folder
4129 for version in range(start, end + 1):
4130 update = 'Sch%d.ldf' % version
4131 schema_updates.append(update)
4133 # Apply patches if we parsed the Schema-Updates.md file
4134 diff = os.path.abspath(os.path.join(diff_dir, update + '.diff'))
4135 if temp_folder and os.path.exists(diff):
4136 p = subprocess.Popen(['patch', update, '-i', diff],
4137 stdout=subprocess.PIPE,
4138 stderr=subprocess.PIPE, cwd=temp_folder)
4139 stdout, stderr = p.communicate()
4142 print("Exception in patch: %s\n%s" % (stdout, stderr))
4143 shutil.rmtree(temp_folder)
4144 raise CommandError('Failed to upgrade schema')
4146 print("Patched %s using %s" % (update, diff))
4148 if base_dir is None:
4149 base_dir = setup_path("adprep")
4151 samdb.transaction_start()
4153 error_encountered = False
4156 # Apply the schema updates needed to move to the new schema version
4157 for ldif_file in schema_updates:
4158 count += self._apply_update(samdb, ldif_file, base_dir)
4161 samdb.transaction_commit()
4162 print("Schema successfully updated")
4164 print("No changes applied to schema")
4165 samdb.transaction_cancel()
4166 except Exception as e:
4167 print("Exception: %s" % e)
4168 print("Error encountered, aborting schema upgrade")
4169 samdb.transaction_cancel()
4170 error_encountered = True
4172 if updates_allowed_overriden:
4173 lp.set("dsdb:schema update allowed", "no")
4176 shutil.rmtree(temp_folder)
4178 if error_encountered:
4179 raise CommandError('Failed to upgrade schema')
4181 class cmd_domain_functional_prep(Command):
4182 """Domain functional level preparation"""
4184 synopsis = "%prog [options]"
4186 takes_optiongroups = {
4187 "sambaopts": options.SambaOptions,
4188 "versionopts": options.VersionOptions,
4189 "credopts": options.CredentialsOptions,
4193 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
4194 metavar="URL", dest="H"),
4195 Option("--quiet", help="Be quiet", action="store_true"),
4196 Option("--verbose", help="Be verbose", action="store_true"),
4197 Option("--function-level", type="choice", metavar="FUNCTION_LEVEL",
4198 choices=["2012", "2012_R2"],
4199 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
4201 Option("--forest-prep", action="store_true",
4202 help="Run the forest prep (by default, both the domain and forest prep are run)."),
4203 Option("--domain-prep", action="store_true",
4204 help="Run the domain prep (by default, both the domain and forest prep are run).")
4207 def run(self, **kwargs):
4208 updates_allowed_overriden = False
4209 sambaopts = kwargs.get("sambaopts")
4210 credopts = kwargs.get("credopts")
4211 versionpts = kwargs.get("versionopts")
4212 lp = sambaopts.get_loadparm()
4213 creds = credopts.get_credentials(lp)
4215 target_level = string_version_to_constant[kwargs.get("function_level")]
4216 forest_prep = kwargs.get("forest_prep")
4217 domain_prep = kwargs.get("domain_prep")
4219 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4221 # we're not going to get far if the config doesn't allow schema updates
4222 if lp.get("dsdb:schema update allowed") is None:
4223 lp.set("dsdb:schema update allowed", "yes")
4224 print("Temporarily overriding 'dsdb:schema update allowed' setting")
4225 updates_allowed_overriden = True
4227 if forest_prep is None and domain_prep is None:
4231 own_dn = ldb.Dn(samdb, samdb.get_dsServiceName())
4233 master = get_fsmo_roleowner(samdb, str(samdb.get_schema_basedn()),
4235 if own_dn != master:
4236 raise CommandError("This server is not the schema master.")
4239 domain_dn = samdb.domain_dn()
4240 infrastructure_dn = "CN=Infrastructure," + domain_dn
4241 master = get_fsmo_roleowner(samdb, infrastructure_dn,
4243 if own_dn != master:
4244 raise CommandError("This server is not the infrastructure master.")
4247 samdb.transaction_start()
4248 error_encountered = False
4250 from samba.forest_update import ForestUpdate
4251 forest = ForestUpdate(samdb, fix=True)
4253 forest.check_updates_iterator([53, 79, 80, 81, 82, 83])
4254 forest.check_updates_functional_level(target_level,
4255 DS_DOMAIN_FUNCTION_2008_R2,
4256 update_revision=True)
4258 samdb.transaction_commit()
4259 except Exception as e:
4260 print("Exception: %s" % e)
4261 samdb.transaction_cancel()
4262 error_encountered = True
4265 samdb.transaction_start()
4266 error_encountered = False
4268 from samba.domain_update import DomainUpdate
4270 domain = DomainUpdate(samdb, fix=True)
4271 domain.check_updates_functional_level(target_level,
4272 DS_DOMAIN_FUNCTION_2008,
4273 update_revision=True)
4275 samdb.transaction_commit()
4276 except Exception as e:
4277 print("Exception: %s" % e)
4278 samdb.transaction_cancel()
4279 error_encountered = True
4281 if updates_allowed_overriden:
4282 lp.set("dsdb:schema update allowed", "no")
4284 if error_encountered:
4285 raise CommandError('Failed to perform functional prep')
4287 class cmd_domain(SuperCommand):
4288 """Domain management."""
4291 subcommands["demote"] = cmd_domain_demote()
4292 if cmd_domain_export_keytab is not None:
4293 subcommands["exportkeytab"] = cmd_domain_export_keytab()
4294 subcommands["info"] = cmd_domain_info()
4295 subcommands["provision"] = cmd_domain_provision()
4296 subcommands["join"] = cmd_domain_join()
4297 subcommands["dcpromo"] = cmd_domain_dcpromo()
4298 subcommands["level"] = cmd_domain_level()
4299 subcommands["passwordsettings"] = cmd_domain_passwordsettings()
4300 subcommands["classicupgrade"] = cmd_domain_classicupgrade()
4301 subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()
4302 subcommands["trust"] = cmd_domain_trust()
4303 subcommands["tombstones"] = cmd_domain_tombstones()
4304 subcommands["schemaupgrade"] = cmd_domain_schema_upgrade()
4305 subcommands["functionalprep"] = cmd_domain_functional_prep()