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 "2008_R2" : DS_DOMAIN_FUNCTION_2008_R2,
102 "2012": DS_DOMAIN_FUNCTION_2012,
103 "2012_R2": DS_DOMAIN_FUNCTION_2012_R2,
106 def get_testparm_var(testparm, smbconf, varname):
107 errfile = open(os.devnull, 'w')
108 p = subprocess.Popen([testparm, '-s', '-l',
109 '--parameter-name=%s' % varname, smbconf],
110 stdout=subprocess.PIPE, stderr=errfile)
111 (out,err) = p.communicate()
113 lines = out.split('\n')
115 return lines[0].strip()
119 import samba.dckeytab
121 cmd_domain_export_keytab = None
123 class cmd_domain_export_keytab(Command):
124 """Dump Kerberos keys of the domain into a keytab."""
126 synopsis = "%prog <keytab> [options]"
128 takes_optiongroups = {
129 "sambaopts": options.SambaOptions,
130 "credopts": options.CredentialsOptions,
131 "versionopts": options.VersionOptions,
135 Option("--principal", help="extract only this principal", type=str),
138 takes_args = ["keytab"]
140 def run(self, keytab, credopts=None, sambaopts=None, versionopts=None, principal=None):
141 lp = sambaopts.get_loadparm()
143 net.export_keytab(keytab=keytab, principal=principal)
146 class cmd_domain_info(Command):
147 """Print basic info about a domain and the DC passed as parameter."""
149 synopsis = "%prog <ip_address> [options]"
154 takes_optiongroups = {
155 "sambaopts": options.SambaOptions,
156 "credopts": options.CredentialsOptions,
157 "versionopts": options.VersionOptions,
160 takes_args = ["address"]
162 def run(self, address, credopts=None, sambaopts=None, versionopts=None):
163 lp = sambaopts.get_loadparm()
165 res = netcmd_get_domain_infos_via_cldap(lp, None, address)
167 raise CommandError("Invalid IP address '" + address + "'!")
168 self.outf.write("Forest : %s\n" % res.forest)
169 self.outf.write("Domain : %s\n" % res.dns_domain)
170 self.outf.write("Netbios domain : %s\n" % res.domain_name)
171 self.outf.write("DC name : %s\n" % res.pdc_dns_name)
172 self.outf.write("DC netbios name : %s\n" % res.pdc_name)
173 self.outf.write("Server site : %s\n" % res.server_site)
174 self.outf.write("Client site : %s\n" % res.client_site)
177 class cmd_domain_provision(Command):
178 """Provision a domain."""
180 synopsis = "%prog [options]"
182 takes_optiongroups = {
183 "sambaopts": options.SambaOptions,
184 "versionopts": options.VersionOptions,
188 Option("--interactive", help="Ask for names", action="store_true"),
189 Option("--domain", type="string", metavar="DOMAIN",
190 help="NetBIOS domain name to use"),
191 Option("--domain-guid", type="string", metavar="GUID",
192 help="set domainguid (otherwise random)"),
193 Option("--domain-sid", type="string", metavar="SID",
194 help="set domainsid (otherwise random)"),
195 Option("--ntds-guid", type="string", metavar="GUID",
196 help="set NTDS object GUID (otherwise random)"),
197 Option("--invocationid", type="string", metavar="GUID",
198 help="set invocationid (otherwise random)"),
199 Option("--host-name", type="string", metavar="HOSTNAME",
200 help="set hostname"),
201 Option("--host-ip", type="string", metavar="IPADDRESS",
202 help="set IPv4 ipaddress"),
203 Option("--host-ip6", type="string", metavar="IP6ADDRESS",
204 help="set IPv6 ipaddress"),
205 Option("--site", type="string", metavar="SITENAME",
206 help="set site name"),
207 Option("--adminpass", type="string", metavar="PASSWORD",
208 help="choose admin password (otherwise random)"),
209 Option("--krbtgtpass", type="string", metavar="PASSWORD",
210 help="choose krbtgt password (otherwise random)"),
211 Option("--machinepass", type="string", metavar="PASSWORD",
212 help="choose machine password (otherwise random)"),
213 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
214 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
215 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
216 "BIND9_FLATFILE uses bind9 text database to store zone information, "
217 "BIND9_DLZ uses samba4 AD to store zone information, "
218 "NONE skips the DNS setup entirely (not recommended)",
219 default="SAMBA_INTERNAL"),
220 Option("--dnspass", type="string", metavar="PASSWORD",
221 help="choose dns password (otherwise random)"),
222 Option("--ldapadminpass", type="string", metavar="PASSWORD",
223 help="choose password to set between Samba and its LDAP backend (otherwise random)"),
224 Option("--root", type="string", metavar="USERNAME",
225 help="choose 'root' unix username"),
226 Option("--nobody", type="string", metavar="USERNAME",
227 help="choose 'nobody' user"),
228 Option("--users", type="string", metavar="GROUPNAME",
229 help="choose 'users' group"),
230 Option("--quiet", help="Be quiet", action="store_true"),
231 Option("--blank", action="store_true",
232 help="do not add users or groups, just the structure"),
233 Option("--ldap-backend-type", type="choice", metavar="LDAP-BACKEND-TYPE",
234 help="Test initialisation support for unsupported LDAP backend type (fedora-ds or openldap) DO NOT USE",
235 choices=["fedora-ds", "openldap"]),
236 Option("--server-role", type="choice", metavar="ROLE",
237 choices=["domain controller", "dc", "member server", "member", "standalone"],
238 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
239 default="domain controller"),
240 Option("--function-level", type="choice", metavar="FOR-FUN-LEVEL",
241 choices=["2000", "2003", "2008", "2008_R2"],
242 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2008_R2 Native.",
244 Option("--base-schema", type="choice", metavar="BASE-SCHEMA",
245 choices=["2008_R2", "2008_R2_old", "2012", "2012_R2"],
246 help="The base schema files to use. Default is (Windows) 2008_R2.",
248 Option("--next-rid", type="int", metavar="NEXTRID", default=1000,
249 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
250 Option("--partitions-only",
251 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"),
252 Option("--targetdir", type="string", metavar="DIR",
253 help="Set target directory"),
254 Option("--ol-mmr-urls", type="string", metavar="LDAPSERVER",
255 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\""),
256 Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
257 Option("--plaintext-secrets", action="store_true",
258 help="Store secret/sensitive values as plain text on disk" +
259 "(default is to encrypt secret/ensitive values)"),
263 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",
264 action="store_true"),
265 Option("--slapd-path", type="string", metavar="SLAPD-PATH",
266 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."),
267 Option("--ldap-backend-extra-port", type="int", metavar="LDAP-BACKEND-EXTRA-PORT", help="Additional TCP port for LDAP backend server (to use for replication)"),
268 Option("--ldap-backend-forced-uri", type="string", metavar="LDAP-BACKEND-FORCED-URI",
269 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"),
270 Option("--ldap-backend-nosync", help="Configure LDAP backend not to call fsync() (for performance in test environments)", action="store_true"),
274 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
275 Option("--use-xattrs", type="choice", choices=["yes","no","auto"],
276 metavar="[yes|no|auto]",
277 help="Define if we should use the native fs capabilities or a tdb file for "
278 "storing attributes likes ntacl when --use-ntvfs is set. "
279 "auto tries to make an inteligent guess based on the user rights and system capabilities",
283 if os.getenv('TEST_LDAP', "no") == "yes":
284 takes_options.extend(openldap_options)
286 if samba.is_ntvfs_fileserver_built():
287 takes_options.extend(ntvfs_options)
291 def run(self, sambaopts=None, versionopts=None,
314 ldap_backend_type=None,
318 partitions_only=None,
325 ldap_backend_nosync=None,
326 ldap_backend_extra_port=None,
327 ldap_backend_forced_uri=None,
328 ldap_dryrun_mode=None,
330 plaintext_secrets=False):
332 self.logger = self.get_logger("provision")
334 self.logger.setLevel(logging.WARNING)
336 self.logger.setLevel(logging.INFO)
338 lp = sambaopts.get_loadparm()
339 smbconf = lp.configfile
341 if dns_forwarder is not None:
342 suggested_forwarder = dns_forwarder
344 suggested_forwarder = self._get_nameserver_ip()
345 if suggested_forwarder is None:
346 suggested_forwarder = "none"
348 if len(self.raw_argv) == 1:
352 from getpass import getpass
355 def ask(prompt, default=None):
356 if default is not None:
357 print "%s [%s]: " % (prompt, default),
359 print "%s: " % (prompt,),
360 return sys.stdin.readline().rstrip("\n") or default
363 default = socket.getfqdn().split(".", 1)[1].upper()
366 realm = ask("Realm", default)
367 if realm in (None, ""):
368 raise CommandError("No realm set!")
371 default = realm.split(".")[0]
374 domain = ask("Domain", default)
376 raise CommandError("No domain set!")
378 server_role = ask("Server Role (dc, member, standalone)", "dc")
380 dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
381 if dns_backend in (None, ''):
382 raise CommandError("No DNS backend set!")
384 if dns_backend == "SAMBA_INTERNAL":
385 dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
386 if dns_forwarder.lower() in (None, 'none'):
387 suggested_forwarder = None
391 adminpassplain = getpass("Administrator password: ")
392 issue = self._adminpass_issue(adminpassplain)
394 self.errf.write("%s.\n" % issue)
396 adminpassverify = getpass("Retype password: ")
397 if not adminpassplain == adminpassverify:
398 self.errf.write("Sorry, passwords do not match.\n")
400 adminpass = adminpassplain
404 realm = sambaopts._lp.get('realm')
406 raise CommandError("No realm set!")
408 raise CommandError("No domain set!")
411 issue = self._adminpass_issue(adminpass)
413 raise CommandError(issue)
415 self.logger.info("Administrator password will be set randomly!")
417 if function_level == "2000":
418 dom_for_fun_level = DS_DOMAIN_FUNCTION_2000
419 elif function_level == "2003":
420 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
421 elif function_level == "2008":
422 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
423 elif function_level == "2008_R2":
424 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
426 if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
427 dns_forwarder = suggested_forwarder
429 samdb_fill = FILL_FULL
431 samdb_fill = FILL_NT4SYNC
432 elif partitions_only:
433 samdb_fill = FILL_DRS
435 if targetdir is not None:
436 if not os.path.isdir(targetdir):
441 if use_xattrs == "yes":
443 elif use_xattrs == "auto" and use_ntvfs == False:
445 elif use_ntvfs == False:
446 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
447 "Please re-run with --use-xattrs omitted.")
448 elif use_xattrs == "auto" and not lp.get("posix:eadb"):
450 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
452 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
455 samba.ntacls.setntacl(lp, file.name,
456 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
459 self.logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. ")
464 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.")
465 if ldap_backend_type == "existing":
466 if ldap_backend_forced_uri is not None:
467 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)
469 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")
471 if ldap_backend_forced_uri is not None:
472 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")
474 if domain_sid is not None:
475 domain_sid = security.dom_sid(domain_sid)
477 session = system_session()
479 result = provision(self.logger,
480 session, smbconf=smbconf, targetdir=targetdir,
481 samdb_fill=samdb_fill, realm=realm, domain=domain,
482 domainguid=domain_guid, domainsid=domain_sid,
484 hostip=host_ip, hostip6=host_ip6,
485 sitename=site, ntdsguid=ntds_guid,
486 invocationid=invocationid, adminpass=adminpass,
487 krbtgtpass=krbtgtpass, machinepass=machinepass,
488 dns_backend=dns_backend, dns_forwarder=dns_forwarder,
489 dnspass=dnspass, root=root, nobody=nobody,
491 serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
492 backend_type=ldap_backend_type,
493 ldapadminpass=ldapadminpass, ol_mmr_urls=ol_mmr_urls, slapd_path=slapd_path,
494 useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
495 use_rfc2307=use_rfc2307, skip_sysvolacl=False,
496 ldap_backend_extra_port=ldap_backend_extra_port,
497 ldap_backend_forced_uri=ldap_backend_forced_uri,
498 nosync=ldap_backend_nosync, ldap_dryrun_mode=ldap_dryrun_mode,
499 base_schema=base_schema,
500 plaintext_secrets=plaintext_secrets)
502 except ProvisioningError, e:
503 raise CommandError("Provision failed", e)
505 result.report_logger(self.logger)
507 def _get_nameserver_ip(self):
508 """Grab the nameserver IP address from /etc/resolv.conf."""
510 RESOLV_CONF="/etc/resolv.conf"
512 if not path.isfile(RESOLV_CONF):
513 self.logger.warning("Failed to locate %s" % RESOLV_CONF)
518 handle = open(RESOLV_CONF, 'r')
520 if not line.startswith('nameserver'):
522 # we want the last non-space continuous string of the line
523 return line.strip().split()[-1]
525 if handle is not None:
528 self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
530 def _adminpass_issue(self, adminpass):
531 """Returns error string for a bad administrator password,
532 or None if acceptable"""
534 if len(adminpass.decode('utf-8')) < DEFAULT_MIN_PWD_LENGTH:
535 return "Administrator password does not meet the default minimum" \
536 " password length requirement (%d characters)" \
537 % DEFAULT_MIN_PWD_LENGTH
538 elif not samba.check_password_quality(adminpass):
539 return "Administrator password does not meet the default" \
545 class cmd_domain_dcpromo(Command):
546 """Promote an existing domain member or NT4 PDC to an AD DC."""
548 synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
550 takes_optiongroups = {
551 "sambaopts": options.SambaOptions,
552 "versionopts": options.VersionOptions,
553 "credopts": options.CredentialsOptions,
557 Option("--server", help="DC to join", type=str),
558 Option("--site", help="site to join", type=str),
559 Option("--targetdir", help="where to store provision", type=str),
560 Option("--domain-critical-only",
561 help="only replicate critical domain objects",
562 action="store_true"),
563 Option("--machinepass", type=str, metavar="PASSWORD",
564 help="choose machine password (otherwise random)"),
565 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
566 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
567 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
568 "BIND9_DLZ uses samba4 AD to store zone information, "
569 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
570 default="SAMBA_INTERNAL"),
571 Option("--quiet", help="Be quiet", action="store_true"),
572 Option("--verbose", help="Be verbose", action="store_true")
576 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
579 if samba.is_ntvfs_fileserver_built():
580 takes_options.extend(ntvfs_options)
583 takes_args = ["domain", "role?"]
585 def run(self, domain, role=None, sambaopts=None, credopts=None,
586 versionopts=None, server=None, site=None, targetdir=None,
587 domain_critical_only=False, parent_domain=None, machinepass=None,
588 use_ntvfs=False, dns_backend=None,
589 quiet=False, verbose=False):
590 lp = sambaopts.get_loadparm()
591 creds = credopts.get_credentials(lp)
592 net = Net(creds, lp, server=credopts.ipaddress)
594 logger = self.get_logger()
596 logger.setLevel(logging.DEBUG)
598 logger.setLevel(logging.WARNING)
600 logger.setLevel(logging.INFO)
602 netbios_name = lp.get("netbios name")
608 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
609 site=site, netbios_name=netbios_name, targetdir=targetdir,
610 domain_critical_only=domain_critical_only,
611 machinepass=machinepass, use_ntvfs=use_ntvfs,
612 dns_backend=dns_backend,
613 promote_existing=True)
615 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
616 site=site, netbios_name=netbios_name, targetdir=targetdir,
617 domain_critical_only=domain_critical_only,
618 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
619 promote_existing=True)
621 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
624 class cmd_domain_join(Command):
625 """Join domain as either member or backup domain controller."""
627 synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
629 takes_optiongroups = {
630 "sambaopts": options.SambaOptions,
631 "versionopts": options.VersionOptions,
632 "credopts": options.CredentialsOptions,
636 Option("--server", help="DC to join", type=str),
637 Option("--site", help="site to join", type=str),
638 Option("--targetdir", help="where to store provision", type=str),
639 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
640 Option("--domain-critical-only",
641 help="only replicate critical domain objects",
642 action="store_true"),
643 Option("--machinepass", type=str, metavar="PASSWORD",
644 help="choose machine password (otherwise random)"),
645 Option("--adminpass", type="string", metavar="PASSWORD",
646 help="choose adminstrator password when joining as a subdomain (otherwise random)"),
647 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
648 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
649 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
650 "BIND9_DLZ uses samba4 AD to store zone information, "
651 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
652 default="SAMBA_INTERNAL"),
653 Option("--plaintext-secrets", action="store_true",
654 help="Store secret/sensitive values as plain text on disk" +
655 "(default is to encrypt secret/ensitive values)"),
656 Option("--quiet", help="Be quiet", action="store_true"),
657 Option("--verbose", help="Be verbose", action="store_true")
661 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
664 if samba.is_ntvfs_fileserver_built():
665 takes_options.extend(ntvfs_options)
667 takes_args = ["domain", "role?"]
669 def run(self, domain, role=None, sambaopts=None, credopts=None,
670 versionopts=None, server=None, site=None, targetdir=None,
671 domain_critical_only=False, parent_domain=None, machinepass=None,
672 use_ntvfs=False, dns_backend=None, adminpass=None,
673 quiet=False, verbose=False, plaintext_secrets=False):
674 lp = sambaopts.get_loadparm()
675 creds = credopts.get_credentials(lp)
676 net = Net(creds, lp, server=credopts.ipaddress)
679 site = "Default-First-Site-Name"
681 logger = self.get_logger()
683 logger.setLevel(logging.DEBUG)
685 logger.setLevel(logging.WARNING)
687 logger.setLevel(logging.INFO)
689 netbios_name = lp.get("netbios name")
694 if role is None or role == "MEMBER":
695 (join_password, sid, domain_name) = net.join_member(
696 domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
697 machinepass=machinepass)
699 self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
701 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
702 site=site, netbios_name=netbios_name, targetdir=targetdir,
703 domain_critical_only=domain_critical_only,
704 machinepass=machinepass, use_ntvfs=use_ntvfs,
705 dns_backend=dns_backend,
706 plaintext_secrets=plaintext_secrets)
708 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
709 site=site, netbios_name=netbios_name, targetdir=targetdir,
710 domain_critical_only=domain_critical_only,
711 machinepass=machinepass, use_ntvfs=use_ntvfs,
712 dns_backend=dns_backend,
713 plaintext_secrets=plaintext_secrets)
714 elif role == "SUBDOMAIN":
716 logger.info("Administrator password will be set randomly!")
718 netbios_domain = lp.get("workgroup")
719 if parent_domain is None:
720 parent_domain = ".".join(domain.split(".")[1:])
721 join_subdomain(logger=logger, server=server, creds=creds, lp=lp, dnsdomain=domain,
722 parent_domain=parent_domain, site=site,
723 netbios_name=netbios_name, netbios_domain=netbios_domain,
724 targetdir=targetdir, machinepass=machinepass,
725 use_ntvfs=use_ntvfs, dns_backend=dns_backend,
727 plaintext_secrets=plaintext_secrets)
729 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role)
732 class cmd_domain_demote(Command):
733 """Demote ourselves from the role of Domain Controller."""
735 synopsis = "%prog [options]"
738 Option("--server", help="writable DC to write demotion changes on", type=str),
739 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
740 metavar="URL", dest="H"),
741 Option("--remove-other-dead-server", help="Dead DC (name or NTDS GUID) "
742 "to remove ALL references to (rather than this DC)", type=str),
743 Option("--quiet", help="Be quiet", action="store_true"),
744 Option("--verbose", help="Be verbose", action="store_true"),
747 takes_optiongroups = {
748 "sambaopts": options.SambaOptions,
749 "credopts": options.CredentialsOptions,
750 "versionopts": options.VersionOptions,
753 def run(self, sambaopts=None, credopts=None,
754 versionopts=None, server=None,
755 remove_other_dead_server=None, H=None,
756 verbose=False, quiet=False):
757 lp = sambaopts.get_loadparm()
758 creds = credopts.get_credentials(lp)
759 net = Net(creds, lp, server=credopts.ipaddress)
761 logger = self.get_logger()
763 logger.setLevel(logging.DEBUG)
765 logger.setLevel(logging.WARNING)
767 logger.setLevel(logging.INFO)
769 if remove_other_dead_server is not None:
770 if server is not None:
771 samdb = SamDB(url="ldap://%s" % server,
772 session_info=system_session(),
773 credentials=creds, lp=lp)
775 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
777 remove_dc.remove_dc(samdb, logger, remove_other_dead_server)
778 except remove_dc.DemoteException as err:
779 raise CommandError("Demote failed: %s" % err)
782 netbios_name = lp.get("netbios name")
783 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
785 res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
787 raise CommandError("Unable to search for servers")
790 raise CommandError("You are the latest server in the domain")
794 if str(e["name"]).lower() != netbios_name.lower():
795 server = e["dnsHostName"]
798 ntds_guid = samdb.get_ntds_GUID()
799 msg = samdb.search(base=str(samdb.get_config_basedn()),
800 scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
802 if len(msg) == 0 or "options" not in msg[0]:
803 raise CommandError("Failed to find options on %s" % ntds_guid)
806 dsa_options = int(str(msg[0]['options']))
808 res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
809 controls=["search_options:1:2"])
812 raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res))
814 self.errf.write("Using %s as partner server for the demotion\n" %
816 (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
818 self.errf.write("Deactivating inbound replication\n")
823 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
824 dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
825 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
829 self.errf.write("Asking partner server %s to synchronize from us\n"
831 for part in (samdb.get_schema_basedn(),
832 samdb.get_config_basedn(),
833 samdb.get_root_basedn()):
834 nc = drsuapi.DsReplicaObjectIdentifier()
837 req1 = drsuapi.DsReplicaSyncRequest1()
838 req1.naming_context = nc;
839 req1.options = drsuapi.DRSUAPI_DRS_WRIT_REP
840 req1.source_dsa_guid = misc.GUID(ntds_guid)
843 drsuapiBind.DsReplicaSync(drsuapi_handle, 1, req1)
844 except RuntimeError as (werr, string):
845 if werr == werror.WERR_DS_DRA_NO_REPLICA:
849 "Error while replicating out last local changes from '%s' for demotion, "
850 "re-enabling inbound replication\n" % part)
851 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
852 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
854 raise CommandError("Error while sending a DsReplicaSync for partition '%s'" % str(part), string)
856 remote_samdb = SamDB(url="ldap://%s" % server,
857 session_info=system_session(),
858 credentials=creds, lp=lp)
860 self.errf.write("Changing userControl and container\n")
861 res = remote_samdb.search(base=str(remote_samdb.domain_dn()),
862 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
863 netbios_name.upper(),
864 attrs=["userAccountControl"])
866 uac = int(str(res[0]["userAccountControl"]))
869 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
871 "Error while demoting, re-enabling inbound replication\n")
872 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
873 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
875 raise CommandError("Error while changing account control", e)
878 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
880 "Error while demoting, re-enabling inbound replication")
881 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
882 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
884 raise CommandError("Unable to find object with samaccountName = %s$"
885 " in the remote dc" % netbios_name.upper())
889 uac &= ~(UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION|UF_PARTIAL_SECRETS_ACCOUNT)
890 uac |= UF_WORKSTATION_TRUST_ACCOUNT
895 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
896 ldb.FLAG_MOD_REPLACE,
897 "userAccountControl")
899 remote_samdb.modify(msg)
901 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
903 "Error while demoting, re-enabling inbound replication")
904 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
905 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
908 raise CommandError("Error while changing account control", e)
910 parent = msg.dn.parent()
911 dc_name = res[0].dn.get_rdn_value()
912 rdn = "CN=%s" % dc_name
914 # Let's move to the Computer container
918 computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.domain_dn()))
919 res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
922 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
923 scope=ldb.SCOPE_ONELEVEL)
924 while(len(res) != 0 and i < 100):
926 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
927 scope=ldb.SCOPE_ONELEVEL)
930 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
932 "Error while demoting, re-enabling inbound replication\n")
933 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
934 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
940 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
941 ldb.FLAG_MOD_REPLACE,
942 "userAccountControl")
944 remote_samdb.modify(msg)
946 raise CommandError("Unable to find a slot for renaming %s,"
947 " all names from %s-1 to %s-%d seemed used" %
948 (str(dc_dn), rdn, rdn, i - 9))
950 newrdn = "%s-%d" % (rdn, i)
953 newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
954 remote_samdb.rename(dc_dn, newdn)
956 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
958 "Error while demoting, re-enabling inbound replication\n")
959 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
960 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
966 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
967 ldb.FLAG_MOD_REPLACE,
968 "userAccountControl")
970 remote_samdb.modify(msg)
971 raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
974 server_dsa_dn = samdb.get_serverName()
975 domain = remote_samdb.get_root_basedn()
978 req1 = drsuapi.DsRemoveDSServerRequest1()
979 req1.server_dn = str(server_dsa_dn)
980 req1.domain_dn = str(domain)
983 drsuapiBind.DsRemoveDSServer(drsuapi_handle, 1, req1)
984 except RuntimeError as (werr, string):
985 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
987 "Error while demoting, re-enabling inbound replication\n")
988 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
989 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
995 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
996 ldb.FLAG_MOD_REPLACE,
997 "userAccountControl")
998 remote_samdb.modify(msg)
999 remote_samdb.rename(newdn, dc_dn)
1000 if werr == werror.WERR_DS_DRA_NO_REPLICA:
1001 raise CommandError("The DC %s is not present on (already removed from) the remote server: " % server_dsa_dn, e)
1003 raise CommandError("Error while sending a removeDsServer of %s: " % server_dsa_dn, e)
1005 remove_dc.remove_sysvol_references(remote_samdb, logger, dc_name)
1007 # These are objects under the computer account that should be deleted
1008 for s in ("CN=Enterprise,CN=NTFRS Subscriptions",
1009 "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
1010 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
1011 "CN=NTFRS Subscriptions"):
1013 remote_samdb.delete(ldb.Dn(remote_samdb,
1014 "%s,%s" % (s, str(newdn))))
1015 except ldb.LdbError, l:
1018 self.errf.write("Demote successful\n")
1021 class cmd_domain_level(Command):
1022 """Raise domain and forest function levels."""
1024 synopsis = "%prog (show|raise <options>) [options]"
1026 takes_optiongroups = {
1027 "sambaopts": options.SambaOptions,
1028 "credopts": options.CredentialsOptions,
1029 "versionopts": options.VersionOptions,
1033 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1034 metavar="URL", dest="H"),
1035 Option("--quiet", help="Be quiet", action="store_true"),
1036 Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1037 help="The forest function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)"),
1038 Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1039 help="The domain function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)")
1042 takes_args = ["subcommand"]
1044 def run(self, subcommand, H=None, forest_level=None, domain_level=None,
1045 quiet=False, credopts=None, sambaopts=None, versionopts=None):
1046 lp = sambaopts.get_loadparm()
1047 creds = credopts.get_credentials(lp, fallback_machine=True)
1049 samdb = SamDB(url=H, session_info=system_session(),
1050 credentials=creds, lp=lp)
1052 domain_dn = samdb.domain_dn()
1054 res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
1055 scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
1056 assert len(res_forest) == 1
1058 res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1059 attrs=["msDS-Behavior-Version", "nTMixedDomain"])
1060 assert len(res_domain) == 1
1062 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
1063 scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
1064 attrs=["msDS-Behavior-Version"])
1065 assert len(res_dc_s) >= 1
1067 # default values, since "msDS-Behavior-Version" does not exist on Windows 2000 AD
1068 level_forest = DS_DOMAIN_FUNCTION_2000
1069 level_domain = DS_DOMAIN_FUNCTION_2000
1071 if "msDS-Behavior-Version" in res_forest[0]:
1072 level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
1073 if "msDS-Behavior-Version" in res_domain[0]:
1074 level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
1075 level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
1078 for msg in res_dc_s:
1079 if "msDS-Behavior-Version" in msg:
1080 if min_level_dc is None or int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
1081 min_level_dc = int(msg["msDS-Behavior-Version"][0])
1083 min_level_dc = DS_DOMAIN_FUNCTION_2000
1084 # well, this is the least
1087 if level_forest < DS_DOMAIN_FUNCTION_2000 or level_domain < DS_DOMAIN_FUNCTION_2000:
1088 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
1089 if min_level_dc < DS_DOMAIN_FUNCTION_2000:
1090 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
1091 if level_forest > level_domain:
1092 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
1093 if level_domain > min_level_dc:
1094 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
1096 if subcommand == "show":
1097 self.message("Domain and forest function level for domain '%s'" % domain_dn)
1098 if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1099 self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1100 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1101 self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1102 if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1103 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)!")
1107 if level_forest == DS_DOMAIN_FUNCTION_2000:
1109 elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
1110 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1111 elif level_forest == DS_DOMAIN_FUNCTION_2003:
1113 elif level_forest == DS_DOMAIN_FUNCTION_2008:
1115 elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
1117 elif level_forest == DS_DOMAIN_FUNCTION_2012:
1119 elif level_forest == DS_DOMAIN_FUNCTION_2012_R2:
1122 outstr = "higher than 2012 R2"
1123 self.message("Forest function level: (Windows) " + outstr)
1125 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1126 outstr = "2000 mixed (NT4 DC support)"
1127 elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
1129 elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
1130 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1131 elif level_domain == DS_DOMAIN_FUNCTION_2003:
1133 elif level_domain == DS_DOMAIN_FUNCTION_2008:
1135 elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
1137 elif level_domain == DS_DOMAIN_FUNCTION_2012:
1139 elif level_domain == DS_DOMAIN_FUNCTION_2012_R2:
1142 outstr = "higher than 2012 R2"
1143 self.message("Domain function level: (Windows) " + outstr)
1145 if min_level_dc == DS_DOMAIN_FUNCTION_2000:
1147 elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
1149 elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
1151 elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
1153 elif min_level_dc == DS_DOMAIN_FUNCTION_2012:
1155 elif min_level_dc == DS_DOMAIN_FUNCTION_2012_R2:
1158 outstr = "higher than 2012 R2"
1159 self.message("Lowest function level of a DC: (Windows) " + outstr)
1161 elif subcommand == "raise":
1164 if domain_level is not None:
1165 if domain_level == "2003":
1166 new_level_domain = DS_DOMAIN_FUNCTION_2003
1167 elif domain_level == "2008":
1168 new_level_domain = DS_DOMAIN_FUNCTION_2008
1169 elif domain_level == "2008_R2":
1170 new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
1171 elif domain_level == "2012":
1172 new_level_domain = DS_DOMAIN_FUNCTION_2012
1173 elif domain_level == "2012_R2":
1174 new_level_domain = DS_DOMAIN_FUNCTION_2012_R2
1176 if new_level_domain <= level_domain and level_domain_mixed == 0:
1177 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
1178 if new_level_domain > min_level_dc:
1179 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
1181 # Deactivate mixed/interim domain support
1182 if level_domain_mixed != 0:
1183 # Directly on the base DN
1185 m.dn = ldb.Dn(samdb, domain_dn)
1186 m["nTMixedDomain"] = ldb.MessageElement("0",
1187 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1191 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
1192 m["nTMixedDomain"] = ldb.MessageElement("0",
1193 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1196 except ldb.LdbError, (enum, emsg):
1197 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1200 # Directly on the base DN
1202 m.dn = ldb.Dn(samdb, domain_dn)
1203 m["msDS-Behavior-Version"]= ldb.MessageElement(
1204 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1205 "msDS-Behavior-Version")
1209 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
1210 + ",CN=Partitions,%s" % samdb.get_config_basedn())
1211 m["msDS-Behavior-Version"]= ldb.MessageElement(
1212 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1213 "msDS-Behavior-Version")
1216 except ldb.LdbError, (enum, emsg):
1217 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1220 level_domain = new_level_domain
1221 msgs.append("Domain function level changed!")
1223 if forest_level is not None:
1224 if forest_level == "2003":
1225 new_level_forest = DS_DOMAIN_FUNCTION_2003
1226 elif forest_level == "2008":
1227 new_level_forest = DS_DOMAIN_FUNCTION_2008
1228 elif forest_level == "2008_R2":
1229 new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
1230 elif forest_level == "2012":
1231 new_level_forest = DS_DOMAIN_FUNCTION_2012
1232 elif forest_level == "2012_R2":
1233 new_level_forest = DS_DOMAIN_FUNCTION_2012_R2
1235 if new_level_forest <= level_forest:
1236 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1237 if new_level_forest > level_domain:
1238 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1241 m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
1242 m["msDS-Behavior-Version"]= ldb.MessageElement(
1243 str(new_level_forest), ldb.FLAG_MOD_REPLACE,
1244 "msDS-Behavior-Version")
1246 msgs.append("Forest function level changed!")
1247 msgs.append("All changes applied successfully!")
1248 self.message("\n".join(msgs))
1250 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
1253 class cmd_domain_passwordsettings(Command):
1254 """Set password settings.
1256 Password complexity, password lockout policy, history length,
1257 minimum password length, the minimum and maximum password age) on
1258 a Samba AD DC server.
1260 Use against a Windows DC is possible, but group policy will override it.
1263 synopsis = "%prog (show|set <options>) [options]"
1265 takes_optiongroups = {
1266 "sambaopts": options.SambaOptions,
1267 "versionopts": options.VersionOptions,
1268 "credopts": options.CredentialsOptions,
1272 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1273 metavar="URL", dest="H"),
1274 Option("--quiet", help="Be quiet", action="store_true"),
1275 Option("--complexity", type="choice", choices=["on","off","default"],
1276 help="The password complexity (on | off | default). Default is 'on'"),
1277 Option("--store-plaintext", type="choice", choices=["on","off","default"],
1278 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1279 Option("--history-length",
1280 help="The password history length (<integer> | default). Default is 24.", type=str),
1281 Option("--min-pwd-length",
1282 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1283 Option("--min-pwd-age",
1284 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1285 Option("--max-pwd-age",
1286 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1287 Option("--account-lockout-duration",
1288 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),
1289 Option("--account-lockout-threshold",
1290 help="The number of bad password attempts allowed before locking out the account (<integer> | default). Default is 0 (never lock out).", type=str),
1291 Option("--reset-account-lockout-after",
1292 help="After this time is elapsed, the recorded number of attempts restarts from zero (<integer> | default). Default is 30.", type=str),
1295 takes_args = ["subcommand"]
1297 def run(self, subcommand, H=None, min_pwd_age=None, max_pwd_age=None,
1298 quiet=False, complexity=None, store_plaintext=None, history_length=None,
1299 min_pwd_length=None, account_lockout_duration=None, account_lockout_threshold=None,
1300 reset_account_lockout_after=None, credopts=None, sambaopts=None,
1302 lp = sambaopts.get_loadparm()
1303 creds = credopts.get_credentials(lp)
1305 samdb = SamDB(url=H, session_info=system_session(),
1306 credentials=creds, lp=lp)
1308 domain_dn = samdb.domain_dn()
1309 res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1310 attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1311 "minPwdAge", "maxPwdAge", "lockoutDuration", "lockoutThreshold",
1312 "lockOutObservationWindow"])
1313 assert(len(res) == 1)
1315 pwd_props = int(res[0]["pwdProperties"][0])
1316 pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
1317 cur_min_pwd_len = int(res[0]["minPwdLength"][0])
1319 cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1320 if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000:
1323 cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1324 cur_account_lockout_threshold = int(res[0]["lockoutThreshold"][0])
1326 if int(res[0]["lockoutDuration"][0]) == -0x8000000000000000:
1327 cur_account_lockout_duration = 0
1329 cur_account_lockout_duration = abs(int(res[0]["lockoutDuration"][0])) / (1e7 * 60)
1330 cur_reset_account_lockout_after = abs(int(res[0]["lockOutObservationWindow"][0])) / (1e7 * 60)
1331 except Exception, e:
1332 raise CommandError("Could not retrieve password properties!", e)
1334 if subcommand == "show":
1335 self.message("Password informations for domain '%s'" % domain_dn)
1337 if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
1338 self.message("Password complexity: on")
1340 self.message("Password complexity: off")
1341 if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
1342 self.message("Store plaintext passwords: on")
1344 self.message("Store plaintext passwords: off")
1345 self.message("Password history length: %d" % pwd_hist_len)
1346 self.message("Minimum password length: %d" % cur_min_pwd_len)
1347 self.message("Minimum password age (days): %d" % cur_min_pwd_age)
1348 self.message("Maximum password age (days): %d" % cur_max_pwd_age)
1349 self.message("Account lockout duration (mins): %d" % cur_account_lockout_duration)
1350 self.message("Account lockout threshold (attempts): %d" % cur_account_lockout_threshold)
1351 self.message("Reset account lockout after (mins): %d" % cur_reset_account_lockout_after)
1352 elif subcommand == "set":
1355 m.dn = ldb.Dn(samdb, domain_dn)
1357 if complexity is not None:
1358 if complexity == "on" or complexity == "default":
1359 pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
1360 msgs.append("Password complexity activated!")
1361 elif complexity == "off":
1362 pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
1363 msgs.append("Password complexity deactivated!")
1365 if store_plaintext is not None:
1366 if store_plaintext == "on" or store_plaintext == "default":
1367 pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1368 msgs.append("Plaintext password storage for changed passwords activated!")
1369 elif store_plaintext == "off":
1370 pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
1371 msgs.append("Plaintext password storage for changed passwords deactivated!")
1373 if complexity is not None or store_plaintext is not None:
1374 m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
1375 ldb.FLAG_MOD_REPLACE, "pwdProperties")
1377 if history_length is not None:
1378 if history_length == "default":
1381 pwd_hist_len = int(history_length)
1383 if pwd_hist_len < 0 or pwd_hist_len > 24:
1384 raise CommandError("Password history length must be in the range of 0 to 24!")
1386 m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
1387 ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
1388 msgs.append("Password history length changed!")
1390 if min_pwd_length is not None:
1391 if min_pwd_length == "default":
1394 min_pwd_len = int(min_pwd_length)
1396 if min_pwd_len < 0 or min_pwd_len > 14:
1397 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1399 m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
1400 ldb.FLAG_MOD_REPLACE, "minPwdLength")
1401 msgs.append("Minimum password length changed!")
1403 if min_pwd_age is not None:
1404 if min_pwd_age == "default":
1407 min_pwd_age = int(min_pwd_age)
1409 if min_pwd_age < 0 or min_pwd_age > 998:
1410 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1413 min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
1415 m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
1416 ldb.FLAG_MOD_REPLACE, "minPwdAge")
1417 msgs.append("Minimum password age changed!")
1419 if max_pwd_age is not None:
1420 if max_pwd_age == "default":
1423 max_pwd_age = int(max_pwd_age)
1425 if max_pwd_age < 0 or max_pwd_age > 999:
1426 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1429 if max_pwd_age == 0:
1430 max_pwd_age_ticks = -0x8000000000000000
1432 max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
1434 m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
1435 ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1436 msgs.append("Maximum password age changed!")
1438 if account_lockout_duration is not None:
1439 if account_lockout_duration == "default":
1440 account_lockout_duration = 30
1442 account_lockout_duration = int(account_lockout_duration)
1444 if account_lockout_duration < 0 or account_lockout_duration > 99999:
1445 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1448 if account_lockout_duration == 0:
1449 account_lockout_duration_ticks = -0x8000000000000000
1451 account_lockout_duration_ticks = -int(account_lockout_duration * (60 * 1e7))
1453 m["lockoutDuration"] = ldb.MessageElement(str(account_lockout_duration_ticks),
1454 ldb.FLAG_MOD_REPLACE, "lockoutDuration")
1455 msgs.append("Account lockout duration changed!")
1457 if account_lockout_threshold is not None:
1458 if account_lockout_threshold == "default":
1459 account_lockout_threshold = 0
1461 account_lockout_threshold = int(account_lockout_threshold)
1463 m["lockoutThreshold"] = ldb.MessageElement(str(account_lockout_threshold),
1464 ldb.FLAG_MOD_REPLACE, "lockoutThreshold")
1465 msgs.append("Account lockout threshold changed!")
1467 if reset_account_lockout_after is not None:
1468 if reset_account_lockout_after == "default":
1469 reset_account_lockout_after = 30
1471 reset_account_lockout_after = int(reset_account_lockout_after)
1473 if reset_account_lockout_after < 0 or reset_account_lockout_after > 99999:
1474 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1477 if reset_account_lockout_after == 0:
1478 reset_account_lockout_after_ticks = -0x8000000000000000
1480 reset_account_lockout_after_ticks = -int(reset_account_lockout_after * (60 * 1e7))
1482 m["lockOutObservationWindow"] = ldb.MessageElement(str(reset_account_lockout_after_ticks),
1483 ldb.FLAG_MOD_REPLACE, "lockOutObservationWindow")
1484 msgs.append("Duration to reset account lockout after changed!")
1486 if max_pwd_age > 0 and min_pwd_age >= max_pwd_age:
1487 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
1490 raise CommandError("You must specify at least one option to set. Try --help")
1492 msgs.append("All changes applied successfully!")
1493 self.message("\n".join(msgs))
1495 raise CommandError("Wrong argument '%s'!" % subcommand)
1498 class cmd_domain_classicupgrade(Command):
1499 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1501 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1502 the testparm utility from your classic installation (with --testparm).
1505 synopsis = "%prog [options] <classic_smb_conf>"
1507 takes_optiongroups = {
1508 "sambaopts": options.SambaOptions,
1509 "versionopts": options.VersionOptions
1513 Option("--dbdir", type="string", metavar="DIR",
1514 help="Path to samba classic DC database directory"),
1515 Option("--testparm", type="string", metavar="PATH",
1516 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1517 Option("--targetdir", type="string", metavar="DIR",
1518 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1519 Option("--quiet", help="Be quiet", action="store_true"),
1520 Option("--verbose", help="Be verbose", action="store_true"),
1521 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
1522 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1523 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1524 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1525 "BIND9_DLZ uses samba4 AD to store zone information, "
1526 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1527 default="SAMBA_INTERNAL")
1531 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
1532 action="store_true"),
1533 Option("--use-xattrs", type="choice", choices=["yes","no","auto"],
1534 metavar="[yes|no|auto]",
1535 help="Define if we should use the native fs capabilities or a tdb file for "
1536 "storing attributes likes ntacl when --use-ntvfs is set. "
1537 "auto tries to make an inteligent guess based on the user rights and system capabilities",
1540 if samba.is_ntvfs_fileserver_built():
1541 takes_options.extend(ntvfs_options)
1543 takes_args = ["smbconf"]
1545 def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
1546 quiet=False, verbose=False, use_xattrs="auto", sambaopts=None, versionopts=None,
1547 dns_backend=None, use_ntvfs=False):
1549 if not os.path.exists(smbconf):
1550 raise CommandError("File %s does not exist" % smbconf)
1552 if testparm and not os.path.exists(testparm):
1553 raise CommandError("Testparm utility %s does not exist" % testparm)
1555 if dbdir and not os.path.exists(dbdir):
1556 raise CommandError("Directory %s does not exist" % dbdir)
1558 if not dbdir and not testparm:
1559 raise CommandError("Please specify either dbdir or testparm")
1561 logger = self.get_logger()
1563 logger.setLevel(logging.DEBUG)
1565 logger.setLevel(logging.WARNING)
1567 logger.setLevel(logging.INFO)
1569 if dbdir and testparm:
1570 logger.warning("both dbdir and testparm specified, ignoring dbdir.")
1573 lp = sambaopts.get_loadparm()
1575 s3conf = s3param.get_context()
1578 s3conf.set("realm", sambaopts.realm)
1580 if targetdir is not None:
1581 if not os.path.isdir(targetdir):
1585 if use_xattrs == "yes":
1587 elif use_xattrs == "auto" and use_ntvfs == False:
1589 elif use_ntvfs == False:
1590 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
1591 "Please re-run with --use-xattrs omitted.")
1592 elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
1594 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
1596 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
1599 samba.ntacls.setntacl(lp, tmpfile.name,
1600 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1603 # FIXME: Don't catch all exceptions here
1604 logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. "
1605 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1609 # Set correct default values from dbdir or testparm
1612 paths["state directory"] = dbdir
1613 paths["private dir"] = dbdir
1614 paths["lock directory"] = dbdir
1615 paths["smb passwd file"] = dbdir + "/smbpasswd"
1617 paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
1618 paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
1619 paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
1620 paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
1621 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1622 # "state directory", instead make use of "lock directory"
1623 if len(paths["state directory"]) == 0:
1624 paths["state directory"] = paths["lock directory"]
1627 s3conf.set(p, paths[p])
1629 # load smb.conf parameters
1630 logger.info("Reading smb.conf")
1631 s3conf.load(smbconf)
1632 samba3 = Samba3(smbconf, s3conf)
1634 logger.info("Provisioning")
1635 upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
1636 useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
1639 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
1640 __doc__ = cmd_domain_classicupgrade.__doc__
1642 # This command is present for backwards compatibility only,
1643 # and should not be shown.
1647 class LocalDCCredentialsOptions(options.CredentialsOptions):
1648 def __init__(self, parser):
1649 options.CredentialsOptions.__init__(self, parser, special_name="local-dc")
1651 class DomainTrustCommand(Command):
1652 """List domain trusts."""
1655 Command.__init__(self)
1656 self.local_lp = None
1658 self.local_server = None
1659 self.local_binding_string = None
1660 self.local_creds = None
1662 self.remote_server = None
1663 self.remote_binding_string = None
1664 self.remote_creds = None
1666 def _uint32(self, v):
1667 return ctypes.c_uint32(v).value
1669 def check_runtime_error(self, runtime, val):
1673 err32 = self._uint32(runtime[0])
1679 class LocalRuntimeError(CommandError):
1680 def __init__(exception_self, self, runtime, message):
1681 err32 = self._uint32(runtime[0])
1683 msg = "LOCAL_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1684 self.local_server, message, err32, errstr)
1685 CommandError.__init__(exception_self, msg)
1687 class RemoteRuntimeError(CommandError):
1688 def __init__(exception_self, self, runtime, message):
1689 err32 = self._uint32(runtime[0])
1691 msg = "REMOTE_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1692 self.remote_server, message, err32, errstr)
1693 CommandError.__init__(exception_self, msg)
1695 class LocalLdbError(CommandError):
1696 def __init__(exception_self, self, ldb_error, message):
1697 errval = ldb_error[0]
1698 errstr = ldb_error[1]
1699 msg = "LOCAL_DC[%s]: %s - ERROR(%d) - %s" % (
1700 self.local_server, message, errval, errstr)
1701 CommandError.__init__(exception_self, msg)
1703 def setup_local_server(self, sambaopts, localdcopts):
1704 if self.local_server is not None:
1705 return self.local_server
1707 lp = sambaopts.get_loadparm()
1709 local_server = localdcopts.ipaddress
1710 if local_server is None:
1711 server_role = lp.server_role()
1712 if server_role != "ROLE_ACTIVE_DIRECTORY_DC":
1713 raise CommandError("Invalid server_role %s" % (server_role))
1714 local_server = lp.get('netbios name')
1715 local_transport = "ncalrpc"
1716 local_binding_options = ""
1717 local_binding_options += ",auth_type=ncalrpc_as_system"
1718 local_ldap_url = None
1721 local_transport = "ncacn_np"
1722 local_binding_options = ""
1723 local_ldap_url = "ldap://%s" % local_server
1724 local_creds = localdcopts.get_credentials(lp)
1728 self.local_server = local_server
1729 self.local_binding_string = "%s:%s[%s]" % (local_transport, local_server, local_binding_options)
1730 self.local_ldap_url = local_ldap_url
1731 self.local_creds = local_creds
1732 return self.local_server
1734 def new_local_lsa_connection(self):
1735 return lsa.lsarpc(self.local_binding_string, self.local_lp, self.local_creds)
1737 def new_local_netlogon_connection(self):
1738 return netlogon.netlogon(self.local_binding_string, self.local_lp, self.local_creds)
1740 def new_local_ldap_connection(self):
1741 return SamDB(url=self.local_ldap_url,
1742 session_info=system_session(),
1743 credentials=self.local_creds,
1746 def setup_remote_server(self, credopts, domain,
1748 require_writable=True):
1751 assert require_writable
1753 if self.remote_server is not None:
1754 return self.remote_server
1756 self.remote_server = "__unknown__remote_server__.%s" % domain
1757 assert self.local_server is not None
1759 remote_creds = credopts.get_credentials(self.local_lp)
1760 remote_server = credopts.ipaddress
1761 remote_binding_options = ""
1763 # TODO: we should also support NT4 domains
1764 # we could use local_netlogon.netr_DsRGetDCNameEx2() with the remote domain name
1765 # and delegate NBT or CLDAP to the local netlogon server
1767 remote_net = Net(remote_creds, self.local_lp, server=remote_server)
1768 remote_flags = nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS
1769 if require_writable:
1770 remote_flags |= nbt.NBT_SERVER_WRITABLE
1772 remote_flags |= nbt.NBT_SERVER_PDC
1773 remote_info = remote_net.finddc(flags=remote_flags, domain=domain, address=remote_server)
1775 raise CommandError("Failed to find a writeable DC for domain '%s'" % domain)
1777 nbt.NBT_SERVER_PDC: "PDC",
1778 nbt.NBT_SERVER_GC: "GC",
1779 nbt.NBT_SERVER_LDAP: "LDAP",
1780 nbt.NBT_SERVER_DS: "DS",
1781 nbt.NBT_SERVER_KDC: "KDC",
1782 nbt.NBT_SERVER_TIMESERV: "TIMESERV",
1783 nbt.NBT_SERVER_CLOSEST: "CLOSEST",
1784 nbt.NBT_SERVER_WRITABLE: "WRITABLE",
1785 nbt.NBT_SERVER_GOOD_TIMESERV: "GOOD_TIMESERV",
1786 nbt.NBT_SERVER_NDNC: "NDNC",
1787 nbt.NBT_SERVER_SELECT_SECRET_DOMAIN_6: "SELECT_SECRET_DOMAIN_6",
1788 nbt.NBT_SERVER_FULL_SECRET_DOMAIN_6: "FULL_SECRET_DOMAIN_6",
1789 nbt.NBT_SERVER_ADS_WEB_SERVICE: "ADS_WEB_SERVICE",
1790 nbt.NBT_SERVER_DS_8: "DS_8",
1791 nbt.NBT_SERVER_HAS_DNS_NAME: "HAS_DNS_NAME",
1792 nbt.NBT_SERVER_IS_DEFAULT_NC: "IS_DEFAULT_NC",
1793 nbt.NBT_SERVER_FOREST_ROOT: "FOREST_ROOT",
1795 server_type_string = self.generic_bitmap_to_string(flag_map,
1796 remote_info.server_type, names_only=True)
1797 self.outf.write("RemoteDC Netbios[%s] DNS[%s] ServerType[%s]\n" % (
1798 remote_info.pdc_name,
1799 remote_info.pdc_dns_name,
1800 server_type_string))
1802 self.remote_server = remote_info.pdc_dns_name
1803 self.remote_binding_string="ncacn_np:%s[%s]" % (self.remote_server, remote_binding_options)
1804 self.remote_creds = remote_creds
1805 return self.remote_server
1807 def new_remote_lsa_connection(self):
1808 return lsa.lsarpc(self.remote_binding_string, self.local_lp, self.remote_creds)
1810 def new_remote_netlogon_connection(self):
1811 return netlogon.netlogon(self.remote_binding_string, self.local_lp, self.remote_creds)
1813 def get_lsa_info(self, conn, policy_access):
1814 objectAttr = lsa.ObjectAttribute()
1815 objectAttr.sec_qos = lsa.QosInfo()
1817 policy = conn.OpenPolicy2(''.decode('utf-8'),
1818 objectAttr, policy_access)
1820 info = conn.QueryInfoPolicy2(policy, lsa.LSA_POLICY_INFO_DNS)
1822 return (policy, info)
1824 def get_netlogon_dc_info(self, conn, server):
1825 info = conn.netr_DsRGetDCNameEx2(server,
1826 None, 0, None, None, None,
1827 netlogon.DS_RETURN_DNS_NAME)
1830 def netr_DomainTrust_to_name(self, t):
1831 if t.trust_type == lsa.LSA_TRUST_TYPE_DOWNLEVEL:
1832 return t.netbios_name
1836 def netr_DomainTrust_to_type(self, a, t):
1838 primary_parent = None
1840 if _t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
1842 if not _t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1843 primary_parent = a[_t.parent_index]
1846 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1847 if t is primary_parent:
1850 if t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1853 parent = a[t.parent_index]
1854 if parent is primary:
1859 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1864 def netr_DomainTrust_to_transitive(self, t):
1865 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1868 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE:
1871 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1876 def netr_DomainTrust_to_direction(self, t):
1877 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND and \
1878 t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1881 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND:
1884 if t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1889 def generic_enum_to_string(self, e_dict, v, names_only=False):
1893 v32 = self._uint32(v)
1894 w = "__unknown__%08X__" % v32
1896 r = "0x%x (%s)" % (v, w)
1899 def generic_bitmap_to_string(self, b_dict, v, names_only=False):
1904 for b in sorted(b_dict.keys()):
1911 c32 = self._uint32(c)
1912 s += ["__unknown_%08X__" % c32]
1917 r = "0x%x (%s)" % (v, w)
1920 def trustType_string(self, v):
1922 lsa.LSA_TRUST_TYPE_DOWNLEVEL : "DOWNLEVEL",
1923 lsa.LSA_TRUST_TYPE_UPLEVEL : "UPLEVEL",
1924 lsa.LSA_TRUST_TYPE_MIT : "MIT",
1925 lsa.LSA_TRUST_TYPE_DCE : "DCE",
1927 return self.generic_enum_to_string(types, v)
1929 def trustDirection_string(self, v):
1931 lsa.LSA_TRUST_DIRECTION_INBOUND |
1932 lsa.LSA_TRUST_DIRECTION_OUTBOUND : "BOTH",
1933 lsa.LSA_TRUST_DIRECTION_INBOUND : "INBOUND",
1934 lsa.LSA_TRUST_DIRECTION_OUTBOUND : "OUTBOUND",
1936 return self.generic_enum_to_string(directions, v)
1938 def trustAttributes_string(self, v):
1940 lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE : "NON_TRANSITIVE",
1941 lsa.LSA_TRUST_ATTRIBUTE_UPLEVEL_ONLY : "UPLEVEL_ONLY",
1942 lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN : "QUARANTINED_DOMAIN",
1943 lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE : "FOREST_TRANSITIVE",
1944 lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION : "CROSS_ORGANIZATION",
1945 lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST : "WITHIN_FOREST",
1946 lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL : "TREAT_AS_EXTERNAL",
1947 lsa.LSA_TRUST_ATTRIBUTE_USES_RC4_ENCRYPTION : "USES_RC4_ENCRYPTION",
1949 return self.generic_bitmap_to_string(attributes, v)
1951 def kerb_EncTypes_string(self, v):
1953 security.KERB_ENCTYPE_DES_CBC_CRC : "DES_CBC_CRC",
1954 security.KERB_ENCTYPE_DES_CBC_MD5 : "DES_CBC_MD5",
1955 security.KERB_ENCTYPE_RC4_HMAC_MD5 : "RC4_HMAC_MD5",
1956 security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96 : "AES128_CTS_HMAC_SHA1_96",
1957 security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96 : "AES256_CTS_HMAC_SHA1_96",
1958 security.KERB_ENCTYPE_FAST_SUPPORTED : "FAST_SUPPORTED",
1959 security.KERB_ENCTYPE_COMPOUND_IDENTITY_SUPPORTED : "COMPOUND_IDENTITY_SUPPORTED",
1960 security.KERB_ENCTYPE_CLAIMS_SUPPORTED : "CLAIMS_SUPPORTED",
1961 security.KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED : "RESOURCE_SID_COMPRESSION_DISABLED",
1963 return self.generic_bitmap_to_string(enctypes, v)
1965 def entry_tln_status(self, e_flags, ):
1967 return "Status[Enabled]"
1970 lsa.LSA_TLN_DISABLED_NEW : "Disabled-New",
1971 lsa.LSA_TLN_DISABLED_ADMIN : "Disabled",
1972 lsa.LSA_TLN_DISABLED_CONFLICT : "Disabled-Conflicting",
1974 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
1976 def entry_dom_status(self, e_flags):
1978 return "Status[Enabled]"
1981 lsa.LSA_SID_DISABLED_ADMIN : "Disabled-SID",
1982 lsa.LSA_SID_DISABLED_CONFLICT : "Disabled-SID-Conflicting",
1983 lsa.LSA_NB_DISABLED_ADMIN : "Disabled-NB",
1984 lsa.LSA_NB_DISABLED_CONFLICT : "Disabled-NB-Conflicting",
1986 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
1988 def write_forest_trust_info(self, fti, tln=None, collisions=None):
1990 tln_string = " TDO[%s]" % tln
1994 self.outf.write("Namespaces[%d]%s:\n" % (
1995 len(fti.entries), tln_string))
1997 for i in xrange(0, len(fti.entries)):
2001 collision_string = ""
2003 if collisions is not None:
2004 for c in collisions.entries:
2008 collision_string = " Collision[%s]" % (c.name.string)
2010 d = e.forest_trust_data
2011 if e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
2012 self.outf.write("TLN: %-32s DNS[*.%s]%s\n" % (
2013 self.entry_tln_status(flags),
2014 d.string, collision_string))
2015 elif e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
2016 self.outf.write("TLN_EX: %-29s DNS[*.%s]\n" % (
2018 elif e.type == lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
2019 self.outf.write("DOM: %-32s DNS[%s] Netbios[%s] SID[%s]%s\n" % (
2020 self.entry_dom_status(flags),
2021 d.dns_domain_name.string,
2022 d.netbios_domain_name.string,
2023 d.domain_sid, collision_string))
2026 class cmd_domain_trust_list(DomainTrustCommand):
2027 """List domain trusts."""
2029 synopsis = "%prog [options]"
2031 takes_optiongroups = {
2032 "sambaopts": options.SambaOptions,
2033 "versionopts": options.VersionOptions,
2034 "localdcopts": LocalDCCredentialsOptions,
2040 def run(self, sambaopts=None, versionopts=None, localdcopts=None):
2042 local_server = self.setup_local_server(sambaopts, localdcopts)
2044 local_netlogon = self.new_local_netlogon_connection()
2045 except RuntimeError as error:
2046 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2049 local_netlogon_trusts = local_netlogon.netr_DsrEnumerateDomainTrusts(local_server,
2050 netlogon.NETR_TRUST_FLAG_IN_FOREST |
2051 netlogon.NETR_TRUST_FLAG_OUTBOUND |
2052 netlogon.NETR_TRUST_FLAG_INBOUND)
2053 except RuntimeError as error:
2054 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
2055 # TODO: we could implement a fallback to lsa.EnumTrustDom()
2056 raise CommandError("LOCAL_DC[%s]: netr_DsrEnumerateDomainTrusts not supported." % (
2058 raise self.LocalRuntimeError(self, error, "netr_DsrEnumerateDomainTrusts failed")
2060 a = local_netlogon_trusts.array
2062 if t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
2064 self.outf.write("%-14s %-15s %-19s %s\n" % (
2065 "Type[%s]" % self.netr_DomainTrust_to_type(a, t),
2066 "Transitive[%s]" % self.netr_DomainTrust_to_transitive(t),
2067 "Direction[%s]" % self.netr_DomainTrust_to_direction(t),
2068 "Name[%s]" % self.netr_DomainTrust_to_name(t)))
2071 class cmd_domain_trust_show(DomainTrustCommand):
2072 """Show trusted domain details."""
2074 synopsis = "%prog NAME [options]"
2076 takes_optiongroups = {
2077 "sambaopts": options.SambaOptions,
2078 "versionopts": options.VersionOptions,
2079 "localdcopts": LocalDCCredentialsOptions,
2085 takes_args = ["domain"]
2087 def run(self, domain, sambaopts=None, versionopts=None, localdcopts=None):
2089 local_server = self.setup_local_server(sambaopts, localdcopts)
2091 local_lsa = self.new_local_lsa_connection()
2092 except RuntimeError as error:
2093 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2096 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2097 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2098 except RuntimeError as error:
2099 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2101 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2102 local_lsa_info.name.string,
2103 local_lsa_info.dns_domain.string,
2104 local_lsa_info.sid))
2106 lsaString = lsa.String()
2107 lsaString.string = domain
2109 local_tdo_full = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2110 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2111 local_tdo_info = local_tdo_full.info_ex
2112 local_tdo_posix = local_tdo_full.posix_offset
2113 except NTSTATUSError as error:
2114 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2115 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2117 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(FULL_INFO) failed")
2120 local_tdo_enctypes = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2121 lsaString, lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES)
2122 except NTSTATUSError as error:
2123 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_PARAMETER):
2125 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_INFO_CLASS):
2128 if error is not None:
2129 raise self.LocalRuntimeError(self, error,
2130 "QueryTrustedDomainInfoByName(SUPPORTED_ENCRYPTION_TYPES) failed")
2132 local_tdo_enctypes = lsa.TrustDomainInfoSupportedEncTypes()
2133 local_tdo_enctypes.enc_types = 0
2136 local_tdo_forest = None
2137 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2138 local_tdo_forest = local_lsa.lsaRQueryForestTrustInformation(local_policy,
2139 lsaString, lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
2140 except RuntimeError as error:
2141 if self.check_runtime_error(error, ntstatus.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE):
2143 if self.check_runtime_error(error, ntstatus.NT_STATUS_NOT_FOUND):
2145 if error is not None:
2146 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation failed")
2148 local_tdo_forest = lsa.ForestTrustInformation()
2149 local_tdo_forest.count = 0
2150 local_tdo_forest.entries = []
2152 self.outf.write("TrusteDomain:\n\n");
2153 self.outf.write("NetbiosName: %s\n" % local_tdo_info.netbios_name.string)
2154 if local_tdo_info.netbios_name.string != local_tdo_info.domain_name.string:
2155 self.outf.write("DnsName: %s\n" % local_tdo_info.domain_name.string)
2156 self.outf.write("SID: %s\n" % local_tdo_info.sid)
2157 self.outf.write("Type: %s\n" % self.trustType_string(local_tdo_info.trust_type))
2158 self.outf.write("Direction: %s\n" % self.trustDirection_string(local_tdo_info.trust_direction))
2159 self.outf.write("Attributes: %s\n" % self.trustAttributes_string(local_tdo_info.trust_attributes))
2160 posix_offset_u32 = ctypes.c_uint32(local_tdo_posix.posix_offset).value
2161 posix_offset_i32 = ctypes.c_int32(local_tdo_posix.posix_offset).value
2162 self.outf.write("PosixOffset: 0x%08X (%d)\n" % (posix_offset_u32, posix_offset_i32))
2163 self.outf.write("kerb_EncTypes: %s\n" % self.kerb_EncTypes_string(local_tdo_enctypes.enc_types))
2165 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2166 self.write_forest_trust_info(local_tdo_forest,
2167 tln=local_tdo_info.domain_name.string)
2171 class cmd_domain_trust_create(DomainTrustCommand):
2172 """Create a domain or forest trust."""
2174 synopsis = "%prog DOMAIN [options]"
2176 takes_optiongroups = {
2177 "sambaopts": options.SambaOptions,
2178 "versionopts": options.VersionOptions,
2179 "credopts": options.CredentialsOptions,
2180 "localdcopts": LocalDCCredentialsOptions,
2184 Option("--type", type="choice", metavar="TYPE",
2185 choices=["external", "forest"],
2186 help="The type of the trust: 'external' or 'forest'.",
2188 default="external"),
2189 Option("--direction", type="choice", metavar="DIRECTION",
2190 choices=["incoming", "outgoing", "both"],
2191 help="The trust direction: 'incoming', 'outgoing' or 'both'.",
2192 dest='trust_direction',
2194 Option("--create-location", type="choice", metavar="LOCATION",
2195 choices=["local", "both"],
2196 help="Where to create the trusted domain object: 'local' or 'both'.",
2197 dest='create_location',
2199 Option("--cross-organisation", action="store_true",
2200 help="The related domains does not belong to the same organisation.",
2201 dest='cross_organisation',
2203 Option("--quarantined", type="choice", metavar="yes|no",
2204 choices=["yes", "no", None],
2205 help="Special SID filtering rules are applied to the trust. "
2206 "With --type=external the default is yes. "
2207 "With --type=forest the default is no.",
2208 dest='quarantined_arg',
2210 Option("--not-transitive", action="store_true",
2211 help="The forest trust is not transitive.",
2212 dest='not_transitive',
2214 Option("--treat-as-external", action="store_true",
2215 help="The treat the forest trust as external.",
2216 dest='treat_as_external',
2218 Option("--no-aes-keys", action="store_false",
2219 help="The trust uses aes kerberos keys.",
2220 dest='use_aes_keys',
2222 Option("--skip-validation", action="store_false",
2223 help="Skip validation of the trust.",
2228 takes_args = ["domain"]
2230 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2231 trust_type=None, trust_direction=None, create_location=None,
2232 cross_organisation=False, quarantined_arg=None,
2233 not_transitive=False, treat_as_external=False,
2234 use_aes_keys=False, validate=True):
2236 lsaString = lsa.String()
2239 if quarantined_arg is None:
2240 if trust_type == 'external':
2242 elif quarantined_arg == 'yes':
2245 if trust_type != 'forest':
2247 raise CommandError("--not-transitive requires --type=forest")
2248 if treat_as_external:
2249 raise CommandError("--treat-as-external requires --type=forest")
2253 enc_types = lsa.TrustDomainInfoSupportedEncTypes()
2254 enc_types.enc_types = security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96
2255 enc_types.enc_types |= security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96
2257 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2258 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2259 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2261 local_trust_info = lsa.TrustDomainInfoInfoEx()
2262 local_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2263 local_trust_info.trust_direction = 0
2264 if trust_direction == "both":
2265 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2266 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2267 elif trust_direction == "incoming":
2268 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2269 elif trust_direction == "outgoing":
2270 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2271 local_trust_info.trust_attributes = 0
2272 if cross_organisation:
2273 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2275 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2276 if trust_type == "forest":
2277 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2279 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2280 if treat_as_external:
2281 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2283 def get_password(name):
2286 if password is not None and password is not '':
2288 password = getpass("New %s Password: " % name)
2289 passwordverify = getpass("Retype %s Password: " % name)
2290 if not password == passwordverify:
2292 self.outf.write("Sorry, passwords do not match.\n")
2294 incoming_secret = None
2295 outgoing_secret = None
2296 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2297 if create_location == "local":
2298 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2299 incoming_password = get_password("Incoming Trust")
2300 incoming_secret = string_to_byte_array(incoming_password.encode('utf-16-le'))
2301 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2302 outgoing_password = get_password("Outgoing Trust")
2303 outgoing_secret = string_to_byte_array(outgoing_password.encode('utf-16-le'))
2305 remote_trust_info = None
2307 # We use 240 random bytes.
2308 # Windows uses 28 or 240 random bytes. I guess it's
2309 # based on the trust type external vs. forest.
2311 # The initial trust password can be up to 512 bytes
2312 # while the versioned passwords used for periodic updates
2313 # can only be up to 498 bytes, as netr_ServerPasswordSet2()
2314 # needs to pass the NL_PASSWORD_VERSION structure within the
2315 # 512 bytes and a 2 bytes confounder is required.
2317 def random_trust_secret(length):
2318 pw = samba.generate_random_machine_password(length/2, length/2)
2319 return string_to_byte_array(pw.encode('utf-16-le'))
2321 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2322 incoming_secret = random_trust_secret(240)
2323 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2324 outgoing_secret = random_trust_secret(240)
2326 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2327 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2329 remote_trust_info = lsa.TrustDomainInfoInfoEx()
2330 remote_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2331 remote_trust_info.trust_direction = 0
2332 if trust_direction == "both":
2333 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2334 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2335 elif trust_direction == "incoming":
2336 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2337 elif trust_direction == "outgoing":
2338 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2339 remote_trust_info.trust_attributes = 0
2340 if cross_organisation:
2341 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2343 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2344 if trust_type == "forest":
2345 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2347 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2348 if treat_as_external:
2349 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2351 local_server = self.setup_local_server(sambaopts, localdcopts)
2353 local_lsa = self.new_local_lsa_connection()
2354 except RuntimeError as error:
2355 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2358 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2359 except RuntimeError as error:
2360 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2362 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2363 local_lsa_info.name.string,
2364 local_lsa_info.dns_domain.string,
2365 local_lsa_info.sid))
2368 remote_server = self.setup_remote_server(credopts, domain)
2369 except RuntimeError as error:
2370 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2373 remote_lsa = self.new_remote_lsa_connection()
2374 except RuntimeError as error:
2375 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2378 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2379 except RuntimeError as error:
2380 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2382 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2383 remote_lsa_info.name.string,
2384 remote_lsa_info.dns_domain.string,
2385 remote_lsa_info.sid))
2387 local_trust_info.domain_name.string = remote_lsa_info.dns_domain.string
2388 local_trust_info.netbios_name.string = remote_lsa_info.name.string
2389 local_trust_info.sid = remote_lsa_info.sid
2391 if remote_trust_info:
2392 remote_trust_info.domain_name.string = local_lsa_info.dns_domain.string
2393 remote_trust_info.netbios_name.string = local_lsa_info.name.string
2394 remote_trust_info.sid = local_lsa_info.sid
2397 lsaString.string = local_trust_info.domain_name.string
2398 local_old_netbios = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2399 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2400 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2401 except NTSTATUSError as error:
2402 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2403 raise self.LocalRuntimeError(self, error,
2404 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2408 lsaString.string = local_trust_info.netbios_name.string
2409 local_old_dns = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2410 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2411 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2412 except NTSTATUSError as error:
2413 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2414 raise self.LocalRuntimeError(self, error,
2415 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2418 if remote_trust_info:
2420 lsaString.string = remote_trust_info.domain_name.string
2421 remote_old_netbios = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2422 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2423 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2424 except NTSTATUSError as error:
2425 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2426 raise self.RemoteRuntimeError(self, error,
2427 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2431 lsaString.string = remote_trust_info.netbios_name.string
2432 remote_old_dns = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2433 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2434 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2435 except NTSTATUSError as error:
2436 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2437 raise self.RemoteRuntimeError(self, error,
2438 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2442 local_netlogon = self.new_local_netlogon_connection()
2443 except RuntimeError as error:
2444 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2447 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
2448 except RuntimeError as error:
2449 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
2451 if remote_trust_info:
2453 remote_netlogon = self.new_remote_netlogon_connection()
2454 except RuntimeError as error:
2455 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2458 remote_netlogon_info = self.get_netlogon_dc_info(remote_netlogon, remote_server)
2459 except RuntimeError as error:
2460 raise self.RemoteRuntimeError(self, error, "failed to get netlogon dc info")
2462 def generate_AuthInOutBlob(secret, update_time):
2464 blob = drsblobs.trustAuthInOutBlob()
2469 clear = drsblobs.AuthInfoClear()
2470 clear.size = len(secret)
2471 clear.password = secret
2473 info = drsblobs.AuthenticationInformation()
2474 info.LastUpdateTime = samba.unix2nttime(update_time)
2475 info.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
2476 info.AuthInfo = clear
2478 array = drsblobs.AuthenticationInformationArray()
2480 array.array = [info]
2482 blob = drsblobs.trustAuthInOutBlob()
2484 blob.current = array
2488 def generate_AuthInfoInternal(session_key, incoming=None, outgoing=None):
2489 confounder = [0] * 512
2490 for i in range(len(confounder)):
2491 confounder[i] = random.randint(0, 255)
2493 trustpass = drsblobs.trustDomainPasswords()
2495 trustpass.confounder = confounder
2496 trustpass.outgoing = outgoing
2497 trustpass.incoming = incoming
2499 trustpass_blob = ndr_pack(trustpass)
2501 encrypted_trustpass = arcfour_encrypt(session_key, trustpass_blob)
2503 auth_blob = lsa.DATA_BUF2()
2504 auth_blob.size = len(encrypted_trustpass)
2505 auth_blob.data = string_to_byte_array(encrypted_trustpass)
2507 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
2508 auth_info.auth_blob = auth_blob
2512 update_time = samba.current_unix_time()
2513 incoming_blob = generate_AuthInOutBlob(incoming_secret, update_time)
2514 outgoing_blob = generate_AuthInOutBlob(outgoing_secret, update_time)
2516 local_tdo_handle = None
2517 remote_tdo_handle = None
2519 local_auth_info = generate_AuthInfoInternal(local_lsa.session_key,
2520 incoming=incoming_blob,
2521 outgoing=outgoing_blob)
2522 if remote_trust_info:
2523 remote_auth_info = generate_AuthInfoInternal(remote_lsa.session_key,
2524 incoming=outgoing_blob,
2525 outgoing=incoming_blob)
2528 if remote_trust_info:
2529 self.outf.write("Creating remote TDO.\n")
2530 current_request = { "location": "remote", "name": "CreateTrustedDomainEx2"}
2531 remote_tdo_handle = remote_lsa.CreateTrustedDomainEx2(remote_policy,
2534 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2535 self.outf.write("Remote TDO created.\n")
2537 self.outf.write("Setting supported encryption types on remote TDO.\n")
2538 current_request = { "location": "remote", "name": "SetInformationTrustedDomain"}
2539 remote_lsa.SetInformationTrustedDomain(remote_tdo_handle,
2540 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2543 self.outf.write("Creating local TDO.\n")
2544 current_request = { "location": "local", "name": "CreateTrustedDomainEx2"}
2545 local_tdo_handle = local_lsa.CreateTrustedDomainEx2(local_policy,
2548 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2549 self.outf.write("Local TDO created\n")
2551 self.outf.write("Setting supported encryption types on local TDO.\n")
2552 current_request = { "location": "local", "name": "SetInformationTrustedDomain"}
2553 local_lsa.SetInformationTrustedDomain(local_tdo_handle,
2554 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2556 except RuntimeError as error:
2557 self.outf.write("Error: %s failed %sly - cleaning up\n" % (
2558 current_request['name'], current_request['location']))
2559 if remote_tdo_handle:
2560 self.outf.write("Deleting remote TDO.\n")
2561 remote_lsa.DeleteObject(remote_tdo_handle)
2562 remote_tdo_handle = None
2563 if local_tdo_handle:
2564 self.outf.write("Deleting local TDO.\n")
2565 local_lsa.DeleteObject(local_tdo_handle)
2566 local_tdo_handle = None
2567 if current_request['location'] is "remote":
2568 raise self.RemoteRuntimeError(self, error, "%s" % (
2569 current_request['name']))
2570 raise self.LocalRuntimeError(self, error, "%s" % (
2571 current_request['name']))
2574 if local_trust_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2575 self.outf.write("Setup local forest trust information...\n")
2577 # get all information about the remote trust
2578 # this triggers netr_GetForestTrustInformation to the remote domain
2579 # and lsaRSetForestTrustInformation() locally, but new top level
2580 # names are disabled by default.
2581 local_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
2582 remote_lsa_info.dns_domain.string,
2583 netlogon.DS_GFTI_UPDATE_TDO)
2584 except RuntimeError as error:
2585 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2588 # here we try to enable all top level names
2589 local_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
2590 remote_lsa_info.dns_domain,
2591 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2594 except RuntimeError as error:
2595 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2597 self.write_forest_trust_info(local_forest_info,
2598 tln=remote_lsa_info.dns_domain.string,
2599 collisions=local_forest_collision)
2601 if remote_trust_info:
2602 self.outf.write("Setup remote forest trust information...\n")
2604 # get all information about the local trust (from the perspective of the remote domain)
2605 # this triggers netr_GetForestTrustInformation to our domain.
2606 # and lsaRSetForestTrustInformation() remotely, but new top level
2607 # names are disabled by default.
2608 remote_forest_info = remote_netlogon.netr_DsRGetForestTrustInformation(remote_netlogon_info.dc_unc,
2609 local_lsa_info.dns_domain.string,
2610 netlogon.DS_GFTI_UPDATE_TDO)
2611 except RuntimeError as error:
2612 raise self.RemoteRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2615 # here we try to enable all top level names
2616 remote_forest_collision = remote_lsa.lsaRSetForestTrustInformation(remote_policy,
2617 local_lsa_info.dns_domain,
2618 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2621 except RuntimeError as error:
2622 raise self.RemoteRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2624 self.write_forest_trust_info(remote_forest_info,
2625 tln=local_lsa_info.dns_domain.string,
2626 collisions=remote_forest_collision)
2628 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2629 self.outf.write("Validating outgoing trust...\n")
2631 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_netlogon_info.dc_unc,
2632 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2634 remote_lsa_info.dns_domain.string)
2635 except RuntimeError as error:
2636 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2638 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2639 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2641 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2642 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2643 local_trust_verify.trusted_dc_name,
2644 local_trust_verify.tc_connection_status[1],
2645 local_trust_verify.pdc_connection_status[1])
2647 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2648 local_trust_verify.trusted_dc_name,
2649 local_trust_verify.tc_connection_status[1],
2650 local_trust_verify.pdc_connection_status[1])
2652 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2653 raise CommandError(local_validation)
2655 self.outf.write("OK: %s\n" % local_validation)
2657 if remote_trust_info:
2658 if remote_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2659 self.outf.write("Validating incoming trust...\n")
2661 remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_netlogon_info.dc_unc,
2662 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2664 local_lsa_info.dns_domain.string)
2665 except RuntimeError as error:
2666 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2668 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2669 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2671 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2672 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2673 remote_trust_verify.trusted_dc_name,
2674 remote_trust_verify.tc_connection_status[1],
2675 remote_trust_verify.pdc_connection_status[1])
2677 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2678 remote_trust_verify.trusted_dc_name,
2679 remote_trust_verify.tc_connection_status[1],
2680 remote_trust_verify.pdc_connection_status[1])
2682 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
2683 raise CommandError(remote_validation)
2685 self.outf.write("OK: %s\n" % remote_validation)
2687 if remote_tdo_handle is not None:
2689 remote_lsa.Close(remote_tdo_handle)
2690 except RuntimeError as error:
2692 remote_tdo_handle = None
2693 if local_tdo_handle is not None:
2695 local_lsa.Close(local_tdo_handle)
2696 except RuntimeError as error:
2698 local_tdo_handle = None
2700 self.outf.write("Success.\n")
2703 class cmd_domain_trust_delete(DomainTrustCommand):
2704 """Delete a domain trust."""
2706 synopsis = "%prog DOMAIN [options]"
2708 takes_optiongroups = {
2709 "sambaopts": options.SambaOptions,
2710 "versionopts": options.VersionOptions,
2711 "credopts": options.CredentialsOptions,
2712 "localdcopts": LocalDCCredentialsOptions,
2716 Option("--delete-location", type="choice", metavar="LOCATION",
2717 choices=["local", "both"],
2718 help="Where to delete the trusted domain object: 'local' or 'both'.",
2719 dest='delete_location',
2723 takes_args = ["domain"]
2725 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2726 delete_location=None):
2728 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2729 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2730 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2732 if delete_location == "local":
2733 remote_policy_access = None
2735 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2736 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2737 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2739 local_server = self.setup_local_server(sambaopts, localdcopts)
2741 local_lsa = self.new_local_lsa_connection()
2742 except RuntimeError as error:
2743 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2746 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2747 except RuntimeError as error:
2748 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2750 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2751 local_lsa_info.name.string,
2752 local_lsa_info.dns_domain.string,
2753 local_lsa_info.sid))
2755 local_tdo_info = None
2756 local_tdo_handle = None
2757 remote_tdo_info = None
2758 remote_tdo_handle = None
2760 lsaString = lsa.String()
2762 lsaString.string = domain
2763 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2764 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2765 except NTSTATUSError as error:
2766 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2767 raise CommandError("Failed to find trust for domain '%s'" % domain)
2768 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2771 if remote_policy_access is not None:
2773 remote_server = self.setup_remote_server(credopts, domain)
2774 except RuntimeError as error:
2775 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2778 remote_lsa = self.new_remote_lsa_connection()
2779 except RuntimeError as error:
2780 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2783 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2784 except RuntimeError as error:
2785 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2787 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2788 remote_lsa_info.name.string,
2789 remote_lsa_info.dns_domain.string,
2790 remote_lsa_info.sid))
2792 if remote_lsa_info.sid != local_tdo_info.sid or \
2793 remote_lsa_info.name.string != local_tdo_info.netbios_name.string or \
2794 remote_lsa_info.dns_domain.string != local_tdo_info.domain_name.string:
2795 raise CommandError("LocalTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2796 local_tdo_info.netbios_name.string,
2797 local_tdo_info.domain_name.string,
2798 local_tdo_info.sid))
2801 lsaString.string = local_lsa_info.dns_domain.string
2802 remote_tdo_info = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2803 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2804 except NTSTATUSError as error:
2805 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2806 raise self.RemoteRuntimeError(self, error, "QueryTrustedDomainInfoByName(%s)" % (
2810 if remote_tdo_info is not None:
2811 if local_lsa_info.sid != remote_tdo_info.sid or \
2812 local_lsa_info.name.string != remote_tdo_info.netbios_name.string or \
2813 local_lsa_info.dns_domain.string != remote_tdo_info.domain_name.string:
2814 raise CommandError("RemoteTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2815 remote_tdo_info.netbios_name.string,
2816 remote_tdo_info.domain_name.string,
2817 remote_tdo_info.sid))
2819 if local_tdo_info is not None:
2821 lsaString.string = local_tdo_info.domain_name.string
2822 local_tdo_handle = local_lsa.OpenTrustedDomainByName(local_policy,
2824 security.SEC_STD_DELETE)
2825 except RuntimeError as error:
2826 raise self.LocalRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2829 local_lsa.DeleteObject(local_tdo_handle)
2830 local_tdo_handle = None
2832 if remote_tdo_info is not None:
2834 lsaString.string = remote_tdo_info.domain_name.string
2835 remote_tdo_handle = remote_lsa.OpenTrustedDomainByName(remote_policy,
2837 security.SEC_STD_DELETE)
2838 except RuntimeError as error:
2839 raise self.RemoteRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2842 if remote_tdo_handle is not None:
2844 remote_lsa.DeleteObject(remote_tdo_handle)
2845 remote_tdo_handle = None
2846 self.outf.write("RemoteTDO deleted.\n")
2847 except RuntimeError as error:
2848 self.outf.write("%s\n" % self.RemoteRuntimeError(self, error, "DeleteObject() failed"))
2850 if local_tdo_handle is not None:
2852 local_lsa.DeleteObject(local_tdo_handle)
2853 local_tdo_handle = None
2854 self.outf.write("LocalTDO deleted.\n")
2855 except RuntimeError as error:
2856 self.outf.write("%s\n" % self.LocalRuntimeError(self, error, "DeleteObject() failed"))
2860 class cmd_domain_trust_validate(DomainTrustCommand):
2861 """Validate a domain trust."""
2863 synopsis = "%prog DOMAIN [options]"
2865 takes_optiongroups = {
2866 "sambaopts": options.SambaOptions,
2867 "versionopts": options.VersionOptions,
2868 "credopts": options.CredentialsOptions,
2869 "localdcopts": LocalDCCredentialsOptions,
2873 Option("--validate-location", type="choice", metavar="LOCATION",
2874 choices=["local", "both"],
2875 help="Where to validate the trusted domain object: 'local' or 'both'.",
2876 dest='validate_location',
2880 takes_args = ["domain"]
2882 def run(self, domain, sambaopts=None, versionopts=None, credopts=None, localdcopts=None,
2883 validate_location=None):
2885 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2887 local_server = self.setup_local_server(sambaopts, localdcopts)
2889 local_lsa = self.new_local_lsa_connection()
2890 except RuntimeError as error:
2891 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2894 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2895 except RuntimeError as error:
2896 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2898 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2899 local_lsa_info.name.string,
2900 local_lsa_info.dns_domain.string,
2901 local_lsa_info.sid))
2904 lsaString = lsa.String()
2905 lsaString.string = domain
2906 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2907 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2908 except NTSTATUSError as error:
2909 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2910 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2912 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
2914 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
2915 local_tdo_info.netbios_name.string,
2916 local_tdo_info.domain_name.string,
2917 local_tdo_info.sid))
2920 local_netlogon = self.new_local_netlogon_connection()
2921 except RuntimeError as error:
2922 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2925 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_server,
2926 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2928 local_tdo_info.domain_name.string)
2929 except RuntimeError as error:
2930 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2932 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2933 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2935 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2936 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2937 local_trust_verify.trusted_dc_name,
2938 local_trust_verify.tc_connection_status[1],
2939 local_trust_verify.pdc_connection_status[1])
2941 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2942 local_trust_verify.trusted_dc_name,
2943 local_trust_verify.tc_connection_status[1],
2944 local_trust_verify.pdc_connection_status[1])
2946 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2947 raise CommandError(local_validation)
2949 self.outf.write("OK: %s\n" % local_validation)
2952 server = local_trust_verify.trusted_dc_name.replace('\\', '')
2953 domain_and_server = "%s\\%s" % (local_tdo_info.domain_name.string, server)
2954 local_trust_rediscover = local_netlogon.netr_LogonControl2Ex(local_server,
2955 netlogon.NETLOGON_CONTROL_REDISCOVER,
2958 except RuntimeError as error:
2959 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
2961 local_conn_status = self._uint32(local_trust_rediscover.tc_connection_status[0])
2962 local_rediscover = "LocalRediscover: DC[%s] CONNECTION[%s]" % (
2963 local_trust_rediscover.trusted_dc_name,
2964 local_trust_rediscover.tc_connection_status[1])
2966 if local_conn_status != werror.WERR_SUCCESS:
2967 raise CommandError(local_rediscover)
2969 self.outf.write("OK: %s\n" % local_rediscover)
2971 if validate_location != "local":
2973 remote_server = self.setup_remote_server(credopts, domain, require_pdc=False)
2974 except RuntimeError as error:
2975 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2978 remote_netlogon = self.new_remote_netlogon_connection()
2979 except RuntimeError as error:
2980 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2983 remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_server,
2984 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2986 local_lsa_info.dns_domain.string)
2987 except RuntimeError as error:
2988 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2990 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2991 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2993 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2994 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2995 remote_trust_verify.trusted_dc_name,
2996 remote_trust_verify.tc_connection_status[1],
2997 remote_trust_verify.pdc_connection_status[1])
2999 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
3000 remote_trust_verify.trusted_dc_name,
3001 remote_trust_verify.tc_connection_status[1],
3002 remote_trust_verify.pdc_connection_status[1])
3004 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
3005 raise CommandError(remote_validation)
3007 self.outf.write("OK: %s\n" % remote_validation)
3010 server = remote_trust_verify.trusted_dc_name.replace('\\', '')
3011 domain_and_server = "%s\\%s" % (local_lsa_info.dns_domain.string, server)
3012 remote_trust_rediscover = remote_netlogon.netr_LogonControl2Ex(remote_server,
3013 netlogon.NETLOGON_CONTROL_REDISCOVER,
3016 except RuntimeError as error:
3017 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
3019 remote_conn_status = self._uint32(remote_trust_rediscover.tc_connection_status[0])
3021 remote_rediscover = "RemoteRediscover: DC[%s] CONNECTION[%s]" % (
3022 remote_trust_rediscover.trusted_dc_name,
3023 remote_trust_rediscover.tc_connection_status[1])
3025 if remote_conn_status != werror.WERR_SUCCESS:
3026 raise CommandError(remote_rediscover)
3028 self.outf.write("OK: %s\n" % remote_rediscover)
3032 class cmd_domain_trust_namespaces(DomainTrustCommand):
3033 """Manage forest trust namespaces."""
3035 synopsis = "%prog [DOMAIN] [options]"
3037 takes_optiongroups = {
3038 "sambaopts": options.SambaOptions,
3039 "versionopts": options.VersionOptions,
3040 "localdcopts": LocalDCCredentialsOptions,
3044 Option("--refresh", type="choice", metavar="check|store",
3045 choices=["check", "store", None],
3046 help="List and maybe store refreshed forest trust information: 'check' or 'store'.",
3049 Option("--enable-all", action="store_true",
3050 help="Try to update disabled entries, not allowed with --refresh=check.",
3053 Option("--enable-tln", action="append", metavar='DNSDOMAIN',
3054 help="Enable a top level name entry. Can be specified multiple times.",
3057 Option("--disable-tln", action="append", metavar='DNSDOMAIN',
3058 help="Disable a top level name entry. Can be specified multiple times.",
3061 Option("--add-tln-ex", action="append", metavar='DNSDOMAIN',
3062 help="Add a top level exclusion entry. Can be specified multiple times.",
3065 Option("--delete-tln-ex", action="append", metavar='DNSDOMAIN',
3066 help="Delete a top level exclusion entry. Can be specified multiple times.",
3067 dest='delete_tln_ex',
3069 Option("--enable-nb", action="append", metavar='NETBIOSDOMAIN',
3070 help="Enable a netbios name in a domain entry. Can be specified multiple times.",
3073 Option("--disable-nb", action="append", metavar='NETBIOSDOMAIN',
3074 help="Disable a netbios name in a domain entry. Can be specified multiple times.",
3077 Option("--enable-sid", action="append", metavar='DOMAINSID',
3078 help="Enable a SID in a domain entry. Can be specified multiple times.",
3079 dest='enable_sid_str',
3081 Option("--disable-sid", action="append", metavar='DOMAINSID',
3082 help="Disable a SID in a domain entry. Can be specified multiple times.",
3083 dest='disable_sid_str',
3085 Option("--add-upn-suffix", action="append", metavar='DNSDOMAIN',
3086 help="Add a new uPNSuffixes attribute for the local forest. Can be specified multiple times.",
3089 Option("--delete-upn-suffix", action="append", metavar='DNSDOMAIN',
3090 help="Delete an existing uPNSuffixes attribute of the local forest. Can be specified multiple times.",
3093 Option("--add-spn-suffix", action="append", metavar='DNSDOMAIN',
3094 help="Add a new msDS-SPNSuffixes attribute for the local forest. Can be specified multiple times.",
3097 Option("--delete-spn-suffix", action="append", metavar='DNSDOMAIN',
3098 help="Delete an existing msDS-SPNSuffixes attribute of the local forest. Can be specified multiple times.",
3103 takes_args = ["domain?"]
3105 def run(self, domain=None, sambaopts=None, localdcopts=None, versionopts=None,
3106 refresh=None, enable_all=False,
3107 enable_tln=[], disable_tln=[], add_tln_ex=[], delete_tln_ex=[],
3108 enable_sid_str=[], disable_sid_str=[], enable_nb=[], disable_nb=[],
3109 add_upn=[], delete_upn=[], add_spn=[], delete_spn=[]):
3111 require_update = False
3114 if refresh == "store":
3115 raise CommandError("--refresh=%s not allowed without DOMAIN" % refresh)
3118 raise CommandError("--enable-all not allowed without DOMAIN")
3120 if len(enable_tln) > 0:
3121 raise CommandError("--enable-tln not allowed without DOMAIN")
3122 if len(disable_tln) > 0:
3123 raise CommandError("--disable-tln not allowed without DOMAIN")
3125 if len(add_tln_ex) > 0:
3126 raise CommandError("--add-tln-ex not allowed without DOMAIN")
3127 if len(delete_tln_ex) > 0:
3128 raise CommandError("--delete-tln-ex not allowed without DOMAIN")
3130 if len(enable_nb) > 0:
3131 raise CommandError("--enable-nb not allowed without DOMAIN")
3132 if len(disable_nb) > 0:
3133 raise CommandError("--disable-nb not allowed without DOMAIN")
3135 if len(enable_sid_str) > 0:
3136 raise CommandError("--enable-sid not allowed without DOMAIN")
3137 if len(disable_sid_str) > 0:
3138 raise CommandError("--disable-sid not allowed without DOMAIN")
3140 if len(add_upn) > 0:
3142 if not n.startswith("*."):
3144 raise CommandError("value[%s] specified for --add-upn-suffix should not include with '*.'" % n)
3145 require_update = True
3146 if len(delete_upn) > 0:
3147 for n in delete_upn:
3148 if not n.startswith("*."):
3150 raise CommandError("value[%s] specified for --delete-upn-suffix should not include with '*.'" % n)
3151 require_update = True
3153 for d in delete_upn:
3154 if a.lower() != d.lower():
3156 raise CommandError("value[%s] specified for --add-upn-suffix and --delete-upn-suffix" % a)
3158 if len(add_spn) > 0:
3160 if not n.startswith("*."):
3162 raise CommandError("value[%s] specified for --add-spn-suffix should not include with '*.'" % n)
3163 require_update = True
3164 if len(delete_spn) > 0:
3165 for n in delete_spn:
3166 if not n.startswith("*."):
3168 raise CommandError("value[%s] specified for --delete-spn-suffix should not include with '*.'" % n)
3169 require_update = True
3171 for d in delete_spn:
3172 if a.lower() != d.lower():
3174 raise CommandError("value[%s] specified for --add-spn-suffix and --delete-spn-suffix" % a)
3176 if len(add_upn) > 0:
3177 raise CommandError("--add-upn-suffix not allowed together with DOMAIN")
3178 if len(delete_upn) > 0:
3179 raise CommandError("--delete-upn-suffix not allowed together with DOMAIN")
3180 if len(add_spn) > 0:
3181 raise CommandError("--add-spn-suffix not allowed together with DOMAIN")
3182 if len(delete_spn) > 0:
3183 raise CommandError("--delete-spn-suffix not allowed together with DOMAIN")
3185 if refresh is not None:
3186 if refresh == "store":
3187 require_update = True
3189 if enable_all and refresh != "store":
3190 raise CommandError("--enable-all not allowed together with --refresh=%s" % refresh)
3192 if len(enable_tln) > 0:
3193 raise CommandError("--enable-tln not allowed together with --refresh")
3194 if len(disable_tln) > 0:
3195 raise CommandError("--disable-tln not allowed together with --refresh")
3197 if len(add_tln_ex) > 0:
3198 raise CommandError("--add-tln-ex not allowed together with --refresh")
3199 if len(delete_tln_ex) > 0:
3200 raise CommandError("--delete-tln-ex not allowed together with --refresh")
3202 if len(enable_nb) > 0:
3203 raise CommandError("--enable-nb not allowed together with --refresh")
3204 if len(disable_nb) > 0:
3205 raise CommandError("--disable-nb not allowed together with --refresh")
3207 if len(enable_sid_str) > 0:
3208 raise CommandError("--enable-sid not allowed together with --refresh")
3209 if len(disable_sid_str) > 0:
3210 raise CommandError("--disable-sid not allowed together with --refresh")
3213 require_update = True
3215 if len(enable_tln) > 0:
3216 raise CommandError("--enable-tln not allowed together with --enable-all")
3218 if len(enable_nb) > 0:
3219 raise CommandError("--enable-nb not allowed together with --enable-all")
3221 if len(enable_sid_str) > 0:
3222 raise CommandError("--enable-sid not allowed together with --enable-all")
3224 if len(enable_tln) > 0:
3225 require_update = True
3226 if len(disable_tln) > 0:
3227 require_update = True
3228 for e in enable_tln:
3229 for d in disable_tln:
3230 if e.lower() != d.lower():
3232 raise CommandError("value[%s] specified for --enable-tln and --disable-tln" % e)
3234 if len(add_tln_ex) > 0:
3235 for n in add_tln_ex:
3236 if not n.startswith("*."):
3238 raise CommandError("value[%s] specified for --add-tln-ex should not include with '*.'" % n)
3239 require_update = True
3240 if len(delete_tln_ex) > 0:
3241 for n in delete_tln_ex:
3242 if not n.startswith("*."):
3244 raise CommandError("value[%s] specified for --delete-tln-ex should not include with '*.'" % n)
3245 require_update = True
3246 for a in add_tln_ex:
3247 for d in delete_tln_ex:
3248 if a.lower() != d.lower():
3250 raise CommandError("value[%s] specified for --add-tln-ex and --delete-tln-ex" % a)
3252 if len(enable_nb) > 0:
3253 require_update = True
3254 if len(disable_nb) > 0:
3255 require_update = True
3257 for d in disable_nb:
3258 if e.upper() != d.upper():
3260 raise CommandError("value[%s] specified for --enable-nb and --disable-nb" % e)
3263 for s in enable_sid_str:
3265 sid = security.dom_sid(s)
3266 except TypeError as error:
3267 raise CommandError("value[%s] specified for --enable-sid is not a valid SID" % s)
3268 enable_sid.append(sid)
3270 for s in disable_sid_str:
3272 sid = security.dom_sid(s)
3273 except TypeError as error:
3274 raise CommandError("value[%s] specified for --disable-sid is not a valid SID" % s)
3275 disable_sid.append(sid)
3276 if len(enable_sid) > 0:
3277 require_update = True
3278 if len(disable_sid) > 0:
3279 require_update = True
3280 for e in enable_sid:
3281 for d in disable_sid:
3284 raise CommandError("value[%s] specified for --enable-sid and --disable-sid" % e)
3286 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
3288 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
3290 local_server = self.setup_local_server(sambaopts, localdcopts)
3292 local_lsa = self.new_local_lsa_connection()
3293 except RuntimeError as error:
3294 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
3297 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
3298 except RuntimeError as error:
3299 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
3301 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
3302 local_lsa_info.name.string,
3303 local_lsa_info.dns_domain.string,
3304 local_lsa_info.sid))
3308 local_netlogon = self.new_local_netlogon_connection()
3309 except RuntimeError as error:
3310 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3313 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3314 except RuntimeError as error:
3315 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3317 if local_netlogon_info.domain_name != local_netlogon_info.forest_name:
3318 raise CommandError("The local domain [%s] is not the forest root [%s]" % (
3319 local_netlogon_info.domain_name,
3320 local_netlogon_info.forest_name))
3323 # get all information about our own forest
3324 own_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3326 except RuntimeError as error:
3327 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
3328 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3331 if self.check_runtime_error(error, werror.WERR_INVALID_FUNCTION):
3332 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3335 if self.check_runtime_error(error, werror.WERR_NERR_ACFNOTLOADED):
3336 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3339 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3341 self.outf.write("Own forest trust information...\n")
3342 self.write_forest_trust_info(own_forest_info,
3343 tln=local_lsa_info.dns_domain.string)
3346 local_samdb = self.new_local_ldap_connection()
3347 except RuntimeError as error:
3348 raise self.LocalRuntimeError(self, error, "failed to connect to SamDB")
3350 local_partitions_dn = "CN=Partitions,%s" % str(local_samdb.get_config_basedn())
3351 attrs = ['uPNSuffixes', 'msDS-SPNSuffixes']
3353 msgs = local_samdb.search(base=local_partitions_dn,
3354 scope=ldb.SCOPE_BASE,
3355 expression="(objectClass=crossRefContainer)",
3357 stored_msg = msgs[0]
3358 except ldb.LdbError as error:
3359 raise self.LocalLdbError(self, error, "failed to search partition dn")
3361 stored_upn_vals = []
3362 if 'uPNSuffixes' in stored_msg:
3363 stored_upn_vals.extend(stored_msg['uPNSuffixes'])
3365 stored_spn_vals = []
3366 if 'msDS-SPNSuffixes' in stored_msg:
3367 stored_spn_vals.extend(stored_msg['msDS-SPNSuffixes'])
3369 self.outf.write("Stored uPNSuffixes attributes[%d]:\n" % len(stored_upn_vals))
3370 for v in stored_upn_vals:
3371 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3372 self.outf.write("Stored msDS-SPNSuffixes attributes[%d]:\n" % len(stored_spn_vals))
3373 for v in stored_spn_vals:
3374 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3376 if not require_update:
3380 update_upn_vals = []
3381 update_upn_vals.extend(stored_upn_vals)
3384 update_spn_vals = []
3385 update_spn_vals.extend(stored_spn_vals)
3389 for i in xrange(0, len(update_upn_vals)):
3390 v = update_upn_vals[i]
3391 if v.lower() != upn.lower():
3396 raise CommandError("Entry already present for value[%s] specified for --add-upn-suffix" % upn)
3397 update_upn_vals.append(upn)
3400 for upn in delete_upn:
3402 for i in xrange(0, len(update_upn_vals)):
3403 v = update_upn_vals[i]
3404 if v.lower() != upn.lower():
3409 raise CommandError("Entry not found for value[%s] specified for --delete-upn-suffix" % upn)
3411 update_upn_vals.pop(idx)
3416 for i in xrange(0, len(update_spn_vals)):
3417 v = update_spn_vals[i]
3418 if v.lower() != spn.lower():
3423 raise CommandError("Entry already present for value[%s] specified for --add-spn-suffix" % spn)
3424 update_spn_vals.append(spn)
3427 for spn in delete_spn:
3429 for i in xrange(0, len(update_spn_vals)):
3430 v = update_spn_vals[i]
3431 if v.lower() != spn.lower():
3436 raise CommandError("Entry not found for value[%s] specified for --delete-spn-suffix" % spn)
3438 update_spn_vals.pop(idx)
3441 self.outf.write("Update uPNSuffixes attributes[%d]:\n" % len(update_upn_vals))
3442 for v in update_upn_vals:
3443 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3444 self.outf.write("Update msDS-SPNSuffixes attributes[%d]:\n" % len(update_spn_vals))
3445 for v in update_spn_vals:
3446 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3448 update_msg = ldb.Message()
3449 update_msg.dn = stored_msg.dn
3452 update_msg['uPNSuffixes'] = ldb.MessageElement(update_upn_vals,
3453 ldb.FLAG_MOD_REPLACE,
3456 update_msg['msDS-SPNSuffixes'] = ldb.MessageElement(update_spn_vals,
3457 ldb.FLAG_MOD_REPLACE,
3460 local_samdb.modify(update_msg)
3461 except ldb.LdbError as error:
3462 raise self.LocalLdbError(self, error, "failed to update partition dn")
3465 stored_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3467 except RuntimeError as error:
3468 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3470 self.outf.write("Stored forest trust information...\n")
3471 self.write_forest_trust_info(stored_forest_info,
3472 tln=local_lsa_info.dns_domain.string)
3476 lsaString = lsa.String()
3477 lsaString.string = domain
3478 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
3479 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
3480 except NTSTATUSError as error:
3481 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
3482 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
3484 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
3486 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
3487 local_tdo_info.netbios_name.string,
3488 local_tdo_info.domain_name.string,
3489 local_tdo_info.sid))
3491 if not local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
3492 raise CommandError("trusted domain object for domain [%s] is not marked as FOREST_TRANSITIVE." % domain)
3494 if refresh is not None:
3496 local_netlogon = self.new_local_netlogon_connection()
3497 except RuntimeError as error:
3498 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3501 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3502 except RuntimeError as error:
3503 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3505 lsa_update_check = 1
3506 if refresh == "store":
3507 netlogon_update_tdo = netlogon.DS_GFTI_UPDATE_TDO
3509 lsa_update_check = 0
3511 netlogon_update_tdo = 0
3514 # get all information about the remote trust
3515 # this triggers netr_GetForestTrustInformation to the remote domain
3516 # and lsaRSetForestTrustInformation() locally, but new top level
3517 # names are disabled by default.
3518 fresh_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3519 local_tdo_info.domain_name.string,
3520 netlogon_update_tdo)
3521 except RuntimeError as error:
3522 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3525 fresh_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3526 local_tdo_info.domain_name,
3527 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3530 except RuntimeError as error:
3531 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3533 self.outf.write("Fresh forest trust information...\n")
3534 self.write_forest_trust_info(fresh_forest_info,
3535 tln=local_tdo_info.domain_name.string,
3536 collisions=fresh_forest_collision)
3538 if refresh == "store":
3540 lsaString = lsa.String()
3541 lsaString.string = local_tdo_info.domain_name.string
3542 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3544 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3545 except RuntimeError as error:
3546 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3548 self.outf.write("Stored forest trust information...\n")
3549 self.write_forest_trust_info(stored_forest_info,
3550 tln=local_tdo_info.domain_name.string)
3555 # The none --refresh path
3559 lsaString = lsa.String()
3560 lsaString.string = local_tdo_info.domain_name.string
3561 local_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3563 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3564 except RuntimeError as error:
3565 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3567 self.outf.write("Local forest trust information...\n")
3568 self.write_forest_trust_info(local_forest_info,
3569 tln=local_tdo_info.domain_name.string)
3571 if not require_update:
3575 entries.extend(local_forest_info.entries)
3576 update_forest_info = lsa.ForestTrustInformation()
3577 update_forest_info.count = len(entries)
3578 update_forest_info.entries = entries
3581 for i in xrange(0, len(update_forest_info.entries)):
3582 r = update_forest_info.entries[i]
3583 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3585 if update_forest_info.entries[i].flags == 0:
3587 update_forest_info.entries[i].time = 0
3588 update_forest_info.entries[i].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3589 for i in xrange(0, len(update_forest_info.entries)):
3590 r = update_forest_info.entries[i]
3591 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3593 if update_forest_info.entries[i].flags == 0:
3595 update_forest_info.entries[i].time = 0
3596 update_forest_info.entries[i].flags &= ~lsa.LSA_NB_DISABLED_MASK
3597 update_forest_info.entries[i].flags &= ~lsa.LSA_SID_DISABLED_MASK
3599 for tln in enable_tln:
3601 for i in xrange(0, len(update_forest_info.entries)):
3602 r = update_forest_info.entries[i]
3603 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3605 if r.forest_trust_data.string.lower() != tln.lower():
3610 raise CommandError("Entry not found for value[%s] specified for --enable-tln" % tln)
3611 if not update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_MASK:
3612 raise CommandError("Entry found for value[%s] specified for --enable-tln is already enabled" % tln)
3613 update_forest_info.entries[idx].time = 0
3614 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3616 for tln in disable_tln:
3618 for i in xrange(0, len(update_forest_info.entries)):
3619 r = update_forest_info.entries[i]
3620 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3622 if r.forest_trust_data.string.lower() != tln.lower():
3627 raise CommandError("Entry not found for value[%s] specified for --disable-tln" % tln)
3628 if update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_ADMIN:
3629 raise CommandError("Entry found for value[%s] specified for --disable-tln is already disabled" % tln)
3630 update_forest_info.entries[idx].time = 0
3631 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3632 update_forest_info.entries[idx].flags |= lsa.LSA_TLN_DISABLED_ADMIN
3634 for tln_ex in add_tln_ex:
3636 for i in xrange(0, len(update_forest_info.entries)):
3637 r = update_forest_info.entries[i]
3638 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3640 if r.forest_trust_data.string.lower() != tln_ex.lower():
3645 raise CommandError("Entry already present for value[%s] specified for --add-tln-ex" % tln_ex)
3647 tln_dot = ".%s" % tln_ex.lower()
3649 for i in xrange(0, len(update_forest_info.entries)):
3650 r = update_forest_info.entries[i]
3651 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3653 r_dot = ".%s" % r.forest_trust_data.string.lower()
3654 if tln_dot == r_dot:
3655 raise CommandError("TLN entry present for value[%s] specified for --add-tln-ex" % tln_ex)
3656 if not tln_dot.endswith(r_dot):
3662 raise CommandError("No TLN parent present for value[%s] specified for --add-tln-ex" % tln_ex)
3664 r = lsa.ForestTrustRecord()
3665 r.type = lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX
3668 r.forest_trust_data.string = tln_ex
3671 entries.extend(update_forest_info.entries)
3672 entries.insert(idx + 1, r)
3673 update_forest_info.count = len(entries)
3674 update_forest_info.entries = entries
3676 for tln_ex in delete_tln_ex:
3678 for i in xrange(0, len(update_forest_info.entries)):
3679 r = update_forest_info.entries[i]
3680 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3682 if r.forest_trust_data.string.lower() != tln_ex.lower():
3687 raise CommandError("Entry not found for value[%s] specified for --delete-tln-ex" % tln_ex)
3690 entries.extend(update_forest_info.entries)
3692 update_forest_info.count = len(entries)
3693 update_forest_info.entries = entries
3695 for nb in enable_nb:
3697 for i in xrange(0, len(update_forest_info.entries)):
3698 r = update_forest_info.entries[i]
3699 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3701 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3706 raise CommandError("Entry not found for value[%s] specified for --enable-nb" % nb)
3707 if not update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_MASK:
3708 raise CommandError("Entry found for value[%s] specified for --enable-nb is already enabled" % nb)
3709 update_forest_info.entries[idx].time = 0
3710 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3712 for nb in disable_nb:
3714 for i in xrange(0, len(update_forest_info.entries)):
3715 r = update_forest_info.entries[i]
3716 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3718 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3723 raise CommandError("Entry not found for value[%s] specified for --delete-nb" % nb)
3724 if update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_ADMIN:
3725 raise CommandError("Entry found for value[%s] specified for --disable-nb is already disabled" % nb)
3726 update_forest_info.entries[idx].time = 0
3727 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3728 update_forest_info.entries[idx].flags |= lsa.LSA_NB_DISABLED_ADMIN
3730 for sid in enable_sid:
3732 for i in xrange(0, len(update_forest_info.entries)):
3733 r = update_forest_info.entries[i]
3734 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3736 if r.forest_trust_data.domain_sid != sid:
3741 raise CommandError("Entry not found for value[%s] specified for --enable-sid" % sid)
3742 if not update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_MASK:
3743 raise CommandError("Entry found for value[%s] specified for --enable-sid is already enabled" % nb)
3744 update_forest_info.entries[idx].time = 0
3745 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3747 for sid in disable_sid:
3749 for i in xrange(0, len(update_forest_info.entries)):
3750 r = update_forest_info.entries[i]
3751 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3753 if r.forest_trust_data.domain_sid != sid:
3758 raise CommandError("Entry not found for value[%s] specified for --delete-sid" % sid)
3759 if update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_ADMIN:
3760 raise CommandError("Entry found for value[%s] specified for --disable-sid is already disabled" % nb)
3761 update_forest_info.entries[idx].time = 0
3762 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3763 update_forest_info.entries[idx].flags |= lsa.LSA_SID_DISABLED_ADMIN
3766 update_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3767 local_tdo_info.domain_name,
3768 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3769 update_forest_info, 0)
3770 except RuntimeError as error:
3771 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3773 self.outf.write("Updated forest trust information...\n")
3774 self.write_forest_trust_info(update_forest_info,
3775 tln=local_tdo_info.domain_name.string,
3776 collisions=update_forest_collision)
3779 lsaString = lsa.String()
3780 lsaString.string = local_tdo_info.domain_name.string
3781 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3783 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3784 except RuntimeError as error:
3785 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3787 self.outf.write("Stored forest trust information...\n")
3788 self.write_forest_trust_info(stored_forest_info,
3789 tln=local_tdo_info.domain_name.string)
3792 class cmd_domain_tombstones_expunge(Command):
3793 """Expunge tombstones from the database.
3795 This command expunges tombstones from the database."""
3796 synopsis = "%prog NC [NC [...]] [options]"
3799 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3800 metavar="URL", dest="H"),
3801 Option("--current-time",
3802 help="The current time to evaluate the tombstone lifetime from, expressed as YYYY-MM-DD",
3804 Option("--tombstone-lifetime", help="Number of days a tombstone should be preserved for", type=int),
3807 takes_args = ["nc*"]
3809 takes_optiongroups = {
3810 "sambaopts": options.SambaOptions,
3811 "credopts": options.CredentialsOptions,
3812 "versionopts": options.VersionOptions,
3815 def run(self, *ncs, **kwargs):
3816 sambaopts = kwargs.get("sambaopts")
3817 credopts = kwargs.get("credopts")
3818 versionpts = kwargs.get("versionopts")
3820 current_time_string = kwargs.get("current_time")
3821 tombstone_lifetime = kwargs.get("tombstone_lifetime")
3822 lp = sambaopts.get_loadparm()
3823 creds = credopts.get_credentials(lp)
3824 samdb = SamDB(url=H, session_info=system_session(),
3825 credentials=creds, lp=lp)
3827 if current_time_string is not None:
3828 current_time_obj = time.strptime(current_time_string, "%Y-%m-%d")
3829 current_time = long(time.mktime(current_time_obj))
3832 current_time = long(time.time())
3835 res = samdb.search(expression="", base="", scope=ldb.SCOPE_BASE,
3836 attrs=["namingContexts"])
3839 for nc in res[0]["namingContexts"]:
3844 started_transaction = False
3846 samdb.transaction_start()
3847 started_transaction = True
3849 removed_links) = samdb.garbage_collect_tombstones(ncs,
3850 current_time=current_time,
3851 tombstone_lifetime=tombstone_lifetime)
3853 except Exception, err:
3854 if started_transaction:
3855 samdb.transaction_cancel()
3856 raise CommandError("Failed to expunge / garbage collect tombstones", err)
3858 samdb.transaction_commit()
3860 self.outf.write("Removed %d objects and %d links successfully\n"
3861 % (removed_objects, removed_links))
3865 class cmd_domain_trust(SuperCommand):
3866 """Domain and forest trust management."""
3869 subcommands["list"] = cmd_domain_trust_list()
3870 subcommands["show"] = cmd_domain_trust_show()
3871 subcommands["create"] = cmd_domain_trust_create()
3872 subcommands["delete"] = cmd_domain_trust_delete()
3873 subcommands["validate"] = cmd_domain_trust_validate()
3874 subcommands["namespaces"] = cmd_domain_trust_namespaces()
3876 class cmd_domain_tombstones(SuperCommand):
3877 """Domain tombstone and recycled object management."""
3880 subcommands["expunge"] = cmd_domain_tombstones_expunge()
3882 class ldif_schema_update:
3883 """Helper class for applying LDIF schema updates"""
3886 self.is_defunct = False
3887 self.unknown_oid = None
3891 def _ldap_schemaUpdateNow(self, samdb):
3895 add: schemaUpdateNow
3898 samdb.modify_ldif(ldif)
3900 def can_ignore_failure(self, error):
3901 """Checks if we can safely ignore failure to apply an LDIF update"""
3902 (num, errstr) = error.args
3904 # Microsoft has marked objects as defunct that Samba doesn't know about
3905 if num == ldb.ERR_NO_SUCH_OBJECT and self.is_defunct:
3906 print("Defunct object %s doesn't exist, skipping" % self.dn)
3908 elif self.unknown_oid is not None:
3909 print("Skipping unknown OID %s for object %s" %(self.unknown_oid, self.dn))
3914 def apply(self, samdb):
3915 """Applies a single LDIF update to the schema"""
3918 samdb.modify_ldif(self.ldif, controls=['relax:0'])
3919 except ldb.LdbError as e:
3920 if self.can_ignore_failure(e):
3923 print("Exception: %s" % e)
3924 print("Encountered while trying to apply the following LDIF")
3925 print("----------------------------------------------------")
3926 print("%s" % self.ldif)
3930 # REFRESH AFTER EVERY CHANGE
3931 # Otherwise the OID-to-attribute mapping in _apply_updates_in_file()
3932 # won't work, because it can't lookup the new OID in the schema
3933 self._ldap_schemaUpdateNow(samdb)
3937 class cmd_domain_schema_upgrade(Command):
3938 """Domain schema upgrading"""
3940 synopsis = "%prog [options]"
3942 takes_optiongroups = {
3943 "sambaopts": options.SambaOptions,
3944 "versionopts": options.VersionOptions,
3945 "credopts": options.CredentialsOptions,
3949 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3950 metavar="URL", dest="H"),
3951 Option("--quiet", help="Be quiet", action="store_true"),
3952 Option("--verbose", help="Be verbose", action="store_true"),
3953 Option("--schema", type="choice", metavar="SCHEMA",
3954 choices=["2012", "2012_R2"],
3955 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
3957 Option("--ldf-file", type=str, default=None,
3958 help="Just apply the schema updates in the adprep/.LDF file(s) specified"),
3959 Option("--base-dir", type=str, default=None,
3960 help="Location of ldf files Default is ${SETUPDIR}/adprep.")
3963 def _apply_updates_in_file(self, samdb, ldif_file):
3965 Applies a series of updates specified in an .LDIF file. The .LDIF file
3966 is based on the adprep Schema updates provided by Microsoft.
3969 ldif_op = ldif_schema_update()
3971 # parse the file line by line and work out each update operation to apply
3972 for line in ldif_file:
3974 line = line.rstrip()
3976 # the operations in the .LDIF file are separated by blank lines. If
3977 # we hit a blank line, try to apply the update we've parsed so far
3980 # keep going if we haven't parsed anything yet
3981 if ldif_op.ldif == '':
3984 # Apply the individual change
3985 count += ldif_op.apply(samdb)
3987 # start storing the next operation from scratch again
3988 ldif_op = ldif_schema_update()
3991 # replace the placeholder domain name in the .ldif file with the real domain
3992 if line.upper().endswith('DC=X'):
3993 line = line[:-len('DC=X')] + str(samdb.get_default_basedn())
3994 elif line.upper().endswith('CN=X'):
3995 line = line[:-len('CN=X')] + str(samdb.get_default_basedn())
3997 values = line.split(':')
3999 if values[0].lower() == 'dn':
4000 ldif_op.dn = values[1].strip()
4002 # replace the Windows-specific operation with the Samba one
4003 if values[0].lower() == 'changetype':
4004 line = line.lower().replace(': ntdsschemaadd',
4006 line = line.lower().replace(': ntdsschemamodify',
4009 if values[0].lower() in ['rdnattid', 'subclassof',
4010 'systemposssuperiors',
4012 'systemauxiliaryclass']:
4015 # The Microsoft updates contain some OIDs we don't recognize.
4016 # Query the DB to see if we can work out the OID this update is
4017 # referring to. If we find a match, then replace the OID with
4018 # the ldapDisplayname
4020 res = samdb.search(base=samdb.get_schema_basedn(),
4021 expression="(|(attributeId=%s)(governsId=%s))" %
4023 attrs=['ldapDisplayName'])
4026 ldif_op.unknown_oid = value
4028 display_name = res[0]['ldapDisplayName'][0]
4029 line = line.replace(value, ' ' + display_name)
4031 # Microsoft has marked objects as defunct that Samba doesn't know about
4032 if values[0].lower() == 'isdefunct' and values[1].strip().lower() == 'true':
4033 ldif_op.is_defunct = True
4035 # Samba has added the showInAdvancedViewOnly attribute to all objects,
4036 # so rather than doing an add, we need to do a replace
4037 if values[0].lower() == 'add' and values[1].strip().lower() == 'showinadvancedviewonly':
4038 line = 'replace: showInAdvancedViewOnly'
4040 # Add the line to the current LDIF operation (including the newline
4041 # we stripped off at the start of the loop)
4042 ldif_op.ldif += line + '\n'
4047 def _apply_update(self, samdb, update_file, base_dir):
4048 """Wrapper function for parsing an LDIF file and applying the updates"""
4050 print("Applying %s updates..." % update_file)
4054 ldif_file = open(os.path.join(base_dir, update_file))
4056 count = self._apply_updates_in_file(samdb, ldif_file)
4062 print("%u changes applied" % count)
4066 def run(self, **kwargs):
4067 from samba.ms_schema_markdown import read_ms_markdown
4068 from samba.schema import Schema
4070 updates_allowed_overriden = False
4071 sambaopts = kwargs.get("sambaopts")
4072 credopts = kwargs.get("credopts")
4073 versionpts = kwargs.get("versionopts")
4074 lp = sambaopts.get_loadparm()
4075 creds = credopts.get_credentials(lp)
4077 target_schema = kwargs.get("schema")
4078 ldf_files = kwargs.get("ldf_file")
4079 base_dir = kwargs.get("base_dir")
4083 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4085 # we're not going to get far if the config doesn't allow schema updates
4086 if lp.get("dsdb:schema update allowed") is None:
4087 lp.set("dsdb:schema update allowed", "yes")
4088 print("Temporarily overriding 'dsdb:schema update allowed' setting")
4089 updates_allowed_overriden = True
4091 own_dn = ldb.Dn(samdb, samdb.get_dsServiceName())
4092 master = get_fsmo_roleowner(samdb, str(samdb.get_schema_basedn()),
4094 if own_dn != master:
4095 raise CommandError("This server is not the schema master.")
4097 # if specific LDIF files were specified, just apply them
4099 schema_updates = ldf_files.split(",")
4103 # work out the version of the target schema we're upgrading to
4104 end = Schema.get_version(target_schema)
4106 # work out the version of the schema we're currently using
4107 res = samdb.search(base=samdb.get_schema_basedn(),
4108 scope=ldb.SCOPE_BASE, attrs=['objectVersion'])
4111 raise CommandError('Could not determine current schema version')
4112 start = int(res[0]['objectVersion'][0]) + 1
4114 diff_dir = setup_path("adprep/WindowsServerDocs")
4115 if base_dir is None:
4116 # Read from the Schema-Updates.md file
4117 temp_folder = tempfile.mkdtemp()
4119 update_file = setup_path("adprep/WindowsServerDocs/Schema-Updates.md")
4122 read_ms_markdown(update_file, temp_folder)
4123 except Exception as e:
4124 print("Exception in markdown parsing: %s" % e)
4125 shutil.rmtree(temp_folder)
4126 raise CommandError('Failed to upgrade schema')
4128 base_dir = temp_folder
4130 for version in range(start, end + 1):
4131 update = 'Sch%d.ldf' % version
4132 schema_updates.append(update)
4134 # Apply patches if we parsed the Schema-Updates.md file
4135 diff = os.path.abspath(os.path.join(diff_dir, update + '.diff'))
4136 if temp_folder and os.path.exists(diff):
4137 p = subprocess.Popen(['patch', update, '-i', diff],
4138 stdout=subprocess.PIPE,
4139 stderr=subprocess.PIPE, cwd=temp_folder)
4140 stdout, stderr = p.communicate()
4143 print("Exception in patch: %s\n%s" % (stdout, stderr))
4144 shutil.rmtree(temp_folder)
4145 raise CommandError('Failed to upgrade schema')
4147 print("Patched %s using %s" % (update, diff))
4149 if base_dir is None:
4150 base_dir = setup_path("adprep")
4152 samdb.transaction_start()
4154 error_encountered = False
4157 # Apply the schema updates needed to move to the new schema version
4158 for ldif_file in schema_updates:
4159 count += self._apply_update(samdb, ldif_file, base_dir)
4162 samdb.transaction_commit()
4163 print("Schema successfully updated")
4165 print("No changes applied to schema")
4166 samdb.transaction_cancel()
4167 except Exception as e:
4168 print("Exception: %s" % e)
4169 print("Error encountered, aborting schema upgrade")
4170 samdb.transaction_cancel()
4171 error_encountered = True
4173 if updates_allowed_overriden:
4174 lp.set("dsdb:schema update allowed", "no")
4177 shutil.rmtree(temp_folder)
4179 if error_encountered:
4180 raise CommandError('Failed to upgrade schema')
4182 class cmd_domain_functional_prep(Command):
4183 """Domain functional level preparation"""
4185 synopsis = "%prog [options]"
4187 takes_optiongroups = {
4188 "sambaopts": options.SambaOptions,
4189 "versionopts": options.VersionOptions,
4190 "credopts": options.CredentialsOptions,
4194 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
4195 metavar="URL", dest="H"),
4196 Option("--quiet", help="Be quiet", action="store_true"),
4197 Option("--verbose", help="Be verbose", action="store_true"),
4198 Option("--function-level", type="choice", metavar="FUNCTION_LEVEL",
4199 choices=["2008_R2", "2012", "2012_R2"],
4200 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
4202 Option("--forest-prep", action="store_true",
4203 help="Run the forest prep (by default, both the domain and forest prep are run)."),
4204 Option("--domain-prep", action="store_true",
4205 help="Run the domain prep (by default, both the domain and forest prep are run).")
4208 def run(self, **kwargs):
4209 updates_allowed_overriden = False
4210 sambaopts = kwargs.get("sambaopts")
4211 credopts = kwargs.get("credopts")
4212 versionpts = kwargs.get("versionopts")
4213 lp = sambaopts.get_loadparm()
4214 creds = credopts.get_credentials(lp)
4216 target_level = string_version_to_constant[kwargs.get("function_level")]
4217 forest_prep = kwargs.get("forest_prep")
4218 domain_prep = kwargs.get("domain_prep")
4220 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4222 # we're not going to get far if the config doesn't allow schema updates
4223 if lp.get("dsdb:schema update allowed") is None:
4224 lp.set("dsdb:schema update allowed", "yes")
4225 print("Temporarily overriding 'dsdb:schema update allowed' setting")
4226 updates_allowed_overriden = True
4228 if forest_prep is None and domain_prep is None:
4232 own_dn = ldb.Dn(samdb, samdb.get_dsServiceName())
4234 master = get_fsmo_roleowner(samdb, str(samdb.get_schema_basedn()),
4236 if own_dn != master:
4237 raise CommandError("This server is not the schema master.")
4240 domain_dn = samdb.domain_dn()
4241 infrastructure_dn = "CN=Infrastructure," + domain_dn
4242 master = get_fsmo_roleowner(samdb, infrastructure_dn,
4244 if own_dn != master:
4245 raise CommandError("This server is not the infrastructure master.")
4248 samdb.transaction_start()
4249 error_encountered = False
4251 from samba.forest_update import ForestUpdate
4252 forest = ForestUpdate(samdb, fix=True)
4254 forest.check_updates_iterator([53, 79, 80, 81, 82, 83])
4255 forest.check_updates_functional_level(target_level,
4256 DS_DOMAIN_FUNCTION_2008_R2,
4257 update_revision=True)
4259 samdb.transaction_commit()
4260 except Exception as e:
4261 print("Exception: %s" % e)
4262 samdb.transaction_cancel()
4263 error_encountered = True
4266 samdb.transaction_start()
4267 error_encountered = False
4269 from samba.domain_update import DomainUpdate
4271 domain = DomainUpdate(samdb, fix=True)
4272 domain.check_updates_functional_level(target_level,
4273 DS_DOMAIN_FUNCTION_2008,
4274 update_revision=True)
4276 samdb.transaction_commit()
4277 except Exception as e:
4278 print("Exception: %s" % e)
4279 samdb.transaction_cancel()
4280 error_encountered = True
4282 if updates_allowed_overriden:
4283 lp.set("dsdb:schema update allowed", "no")
4285 if error_encountered:
4286 raise CommandError('Failed to perform functional prep')
4288 class cmd_domain(SuperCommand):
4289 """Domain management."""
4292 subcommands["demote"] = cmd_domain_demote()
4293 if cmd_domain_export_keytab is not None:
4294 subcommands["exportkeytab"] = cmd_domain_export_keytab()
4295 subcommands["info"] = cmd_domain_info()
4296 subcommands["provision"] = cmd_domain_provision()
4297 subcommands["join"] = cmd_domain_join()
4298 subcommands["dcpromo"] = cmd_domain_dcpromo()
4299 subcommands["level"] = cmd_domain_level()
4300 subcommands["passwordsettings"] = cmd_domain_passwordsettings()
4301 subcommands["classicupgrade"] = cmd_domain_classicupgrade()
4302 subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()
4303 subcommands["trust"] = cmd_domain_trust()
4304 subcommands["tombstones"] = cmd_domain_tombstones()
4305 subcommands["schemaupgrade"] = cmd_domain_schema_upgrade()
4306 subcommands["functionalprep"] = cmd_domain_functional_prep()