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 from __future__ import print_function
26 from __future__ import division
27 import samba.getopt as options
38 from samba import ntstatus
39 from samba import NTSTATUSError
40 from samba import werror
41 from getpass import getpass
42 from samba.net import Net, LIBNET_JOIN_AUTOMATIC
44 from samba.join import join_RODC, join_DC, join_subdomain
45 from samba.auth import system_session
46 from samba.samdb import SamDB, get_default_backend_store
47 from samba.ndr import ndr_pack, ndr_print
48 from samba.dcerpc import drsuapi
49 from samba.dcerpc import drsblobs
50 from samba.dcerpc import lsa
51 from samba.dcerpc import netlogon
52 from samba.dcerpc import security
53 from samba.dcerpc import nbt
54 from samba.dcerpc import misc
55 from samba.dcerpc.samr import DOMAIN_PASSWORD_COMPLEX, DOMAIN_PASSWORD_STORE_CLEARTEXT
56 from samba.netcmd import (
62 from samba.netcmd.fsmo import get_fsmo_roleowner
63 from samba.netcmd.common import netcmd_get_domain_infos_via_cldap
64 from samba.samba3 import Samba3
65 from samba.samba3 import param as s3param
66 from samba.upgrade import upgrade_from_samba3
67 from samba.drs_utils import drsuapi_connect
68 from samba import remove_dc, arcfour_encrypt, string_to_byte_array
70 from samba.dsdb import (
71 DS_DOMAIN_FUNCTION_2000,
72 DS_DOMAIN_FUNCTION_2003,
73 DS_DOMAIN_FUNCTION_2003_MIXED,
74 DS_DOMAIN_FUNCTION_2008,
75 DS_DOMAIN_FUNCTION_2008_R2,
76 DS_DOMAIN_FUNCTION_2012,
77 DS_DOMAIN_FUNCTION_2012_R2,
78 DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL,
79 DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL,
80 UF_WORKSTATION_TRUST_ACCOUNT,
81 UF_SERVER_TRUST_ACCOUNT,
82 UF_TRUSTED_FOR_DELEGATION,
83 UF_PARTIAL_SECRETS_ACCOUNT
86 from samba.provision import (
89 DEFAULT_MIN_PWD_LENGTH,
93 from samba.provision.common import (
99 from samba.netcmd.pso import cmd_domain_passwordsettings_pso
100 from samba.netcmd.domain_backup import cmd_domain_backup
102 from samba.compat import binary_type
103 from samba.compat import get_string
105 string_version_to_constant = {
106 "2008_R2": DS_DOMAIN_FUNCTION_2008_R2,
107 "2012": DS_DOMAIN_FUNCTION_2012,
108 "2012_R2": DS_DOMAIN_FUNCTION_2012_R2,
111 common_provision_join_options = [
112 Option("--machinepass", type="string", metavar="PASSWORD",
113 help="choose machine password (otherwise random)"),
114 Option("--plaintext-secrets", action="store_true",
115 help="Store secret/sensitive values as plain text on disk" +
116 "(default is to encrypt secret/ensitive values)"),
117 Option("--backend-store", type="choice", metavar="BACKENDSTORE",
118 choices=["tdb", "mdb"],
119 help="Specify the database backend to be used "
120 "(default is %s)" % get_default_backend_store()),
121 Option("--targetdir", metavar="DIR",
122 help="Set target directory (where to store provision)", type=str),
123 Option("-q", "--quiet", help="Be quiet", action="store_true"),
126 common_join_options = [
127 Option("--server", help="DC to join", type=str),
128 Option("--site", help="site to join", type=str),
129 Option("--domain-critical-only",
130 help="only replicate critical domain objects",
131 action="store_true"),
132 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
133 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
134 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
135 "BIND9_DLZ uses samba4 AD to store zone information, "
136 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
137 default="SAMBA_INTERNAL"),
138 Option("-v", "--verbose", help="Be verbose", action="store_true")
141 common_ntvfs_options = [
142 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
147 def get_testparm_var(testparm, smbconf, varname):
148 errfile = open(os.devnull, 'w')
149 p = subprocess.Popen([testparm, '-s', '-l',
150 '--parameter-name=%s' % varname, smbconf],
151 stdout=subprocess.PIPE, stderr=errfile)
152 (out, err) = p.communicate()
154 lines = out.split(b'\n')
156 return get_string(lines[0]).strip()
161 import samba.dckeytab
163 cmd_domain_export_keytab = None
165 class cmd_domain_export_keytab(Command):
166 """Dump Kerberos keys of the domain into a keytab."""
168 synopsis = "%prog <keytab> [options]"
170 takes_optiongroups = {
171 "sambaopts": options.SambaOptions,
172 "credopts": options.CredentialsOptions,
173 "versionopts": options.VersionOptions,
177 Option("--principal", help="extract only this principal", type=str),
180 takes_args = ["keytab"]
182 def run(self, keytab, credopts=None, sambaopts=None, versionopts=None, principal=None):
183 lp = sambaopts.get_loadparm()
185 net.export_keytab(keytab=keytab, principal=principal)
188 class cmd_domain_info(Command):
189 """Print basic info about a domain and the DC passed as parameter."""
191 synopsis = "%prog <ip_address> [options]"
196 takes_optiongroups = {
197 "sambaopts": options.SambaOptions,
198 "credopts": options.CredentialsOptions,
199 "versionopts": options.VersionOptions,
202 takes_args = ["address"]
204 def run(self, address, credopts=None, sambaopts=None, versionopts=None):
205 lp = sambaopts.get_loadparm()
207 res = netcmd_get_domain_infos_via_cldap(lp, None, address)
209 raise CommandError("Invalid IP address '" + address + "'!")
210 self.outf.write("Forest : %s\n" % res.forest)
211 self.outf.write("Domain : %s\n" % res.dns_domain)
212 self.outf.write("Netbios domain : %s\n" % res.domain_name)
213 self.outf.write("DC name : %s\n" % res.pdc_dns_name)
214 self.outf.write("DC netbios name : %s\n" % res.pdc_name)
215 self.outf.write("Server site : %s\n" % res.server_site)
216 self.outf.write("Client site : %s\n" % res.client_site)
219 class cmd_domain_provision(Command):
220 """Provision a domain."""
222 synopsis = "%prog [options]"
224 takes_optiongroups = {
225 "sambaopts": options.SambaOptions,
226 "versionopts": options.VersionOptions,
230 Option("--interactive", help="Ask for names", action="store_true"),
231 Option("--domain", type="string", metavar="DOMAIN",
232 help="NetBIOS domain name to use"),
233 Option("--domain-guid", type="string", metavar="GUID",
234 help="set domainguid (otherwise random)"),
235 Option("--domain-sid", type="string", metavar="SID",
236 help="set domainsid (otherwise random)"),
237 Option("--ntds-guid", type="string", metavar="GUID",
238 help="set NTDS object GUID (otherwise random)"),
239 Option("--invocationid", type="string", metavar="GUID",
240 help="set invocationid (otherwise random)"),
241 Option("--host-name", type="string", metavar="HOSTNAME",
242 help="set hostname"),
243 Option("--host-ip", type="string", metavar="IPADDRESS",
244 help="set IPv4 ipaddress"),
245 Option("--host-ip6", type="string", metavar="IP6ADDRESS",
246 help="set IPv6 ipaddress"),
247 Option("--site", type="string", metavar="SITENAME",
248 help="set site name"),
249 Option("--adminpass", type="string", metavar="PASSWORD",
250 help="choose admin password (otherwise random)"),
251 Option("--krbtgtpass", type="string", metavar="PASSWORD",
252 help="choose krbtgt password (otherwise random)"),
253 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
254 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
255 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
256 "BIND9_FLATFILE uses bind9 text database to store zone information, "
257 "BIND9_DLZ uses samba4 AD to store zone information, "
258 "NONE skips the DNS setup entirely (not recommended)",
259 default="SAMBA_INTERNAL"),
260 Option("--dnspass", type="string", metavar="PASSWORD",
261 help="choose dns password (otherwise random)"),
262 Option("--root", type="string", metavar="USERNAME",
263 help="choose 'root' unix username"),
264 Option("--nobody", type="string", metavar="USERNAME",
265 help="choose 'nobody' user"),
266 Option("--users", type="string", metavar="GROUPNAME",
267 help="choose 'users' group"),
268 Option("--blank", action="store_true",
269 help="do not add users or groups, just the structure"),
270 Option("--server-role", type="choice", metavar="ROLE",
271 choices=["domain controller", "dc", "member server", "member", "standalone"],
272 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
273 default="domain controller"),
274 Option("--function-level", type="choice", metavar="FOR-FUN-LEVEL",
275 choices=["2000", "2003", "2008", "2008_R2"],
276 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2008_R2 Native.",
278 Option("--base-schema", type="choice", metavar="BASE-SCHEMA",
279 choices=["2008_R2", "2008_R2_old", "2012", "2012_R2"],
280 help="The base schema files to use. Default is (Windows) 2008_R2.",
282 Option("--next-rid", type="int", metavar="NEXTRID", default=1000,
283 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
284 Option("--partitions-only",
285 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"),
286 Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
290 Option("--ldapadminpass", type="string", metavar="PASSWORD",
291 help="choose password to set between Samba and its LDAP backend (otherwise random)"),
292 Option("--ldap-backend-type", type="choice", metavar="LDAP-BACKEND-TYPE",
293 help="Test initialisation support for unsupported LDAP backend type (fedora-ds or openldap) DO NOT USE",
294 choices=["fedora-ds", "openldap"]),
295 Option("--ol-mmr-urls", type="string", metavar="LDAPSERVER",
296 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\""),
297 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",
298 action="store_true"),
299 Option("--slapd-path", type="string", metavar="SLAPD-PATH",
300 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."),
301 Option("--ldap-backend-extra-port", type="int", metavar="LDAP-BACKEND-EXTRA-PORT", help="Additional TCP port for LDAP backend server (to use for replication)"),
302 Option("--ldap-backend-forced-uri", type="string", metavar="LDAP-BACKEND-FORCED-URI",
303 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"),
304 Option("--ldap-backend-nosync", help="Configure LDAP backend not to call fsync() (for performance in test environments)", action="store_true"),
308 Option("--use-xattrs", type="choice", choices=["yes", "no", "auto"],
309 metavar="[yes|no|auto]",
310 help="Define if we should use the native fs capabilities or a tdb file for "
311 "storing attributes likes ntacl when --use-ntvfs is set. "
312 "auto tries to make an inteligent guess based on the user rights and system capabilities",
316 takes_options.extend(common_provision_join_options)
318 if os.getenv('TEST_LDAP', "no") == "yes":
319 takes_options.extend(openldap_options)
321 if samba.is_ntvfs_fileserver_built():
322 takes_options.extend(common_ntvfs_options)
323 takes_options.extend(ntvfs_options)
327 def run(self, sambaopts=None, versionopts=None,
350 ldap_backend_type=None,
354 partitions_only=None,
361 ldap_backend_nosync=None,
362 ldap_backend_extra_port=None,
363 ldap_backend_forced_uri=None,
364 ldap_dryrun_mode=None,
366 plaintext_secrets=False,
369 self.logger = self.get_logger(name="provision", quiet=quiet)
371 lp = sambaopts.get_loadparm()
372 smbconf = lp.configfile
374 if dns_forwarder is not None:
375 suggested_forwarder = dns_forwarder
377 suggested_forwarder = self._get_nameserver_ip()
378 if suggested_forwarder is None:
379 suggested_forwarder = "none"
381 if len(self.raw_argv) == 1:
385 from getpass import getpass
388 def ask(prompt, default=None):
389 if default is not None:
390 print("%s [%s]: " % (prompt, default), end=' ')
392 print("%s: " % (prompt,), end=' ')
393 return sys.stdin.readline().rstrip("\n") or default
396 default = socket.getfqdn().split(".", 1)[1].upper()
399 realm = ask("Realm", default)
400 if realm in (None, ""):
401 raise CommandError("No realm set!")
404 default = realm.split(".")[0]
407 domain = ask("Domain", default)
409 raise CommandError("No domain set!")
411 server_role = ask("Server Role (dc, member, standalone)", "dc")
413 dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
414 if dns_backend in (None, ''):
415 raise CommandError("No DNS backend set!")
417 if dns_backend == "SAMBA_INTERNAL":
418 dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
419 if dns_forwarder.lower() in (None, 'none'):
420 suggested_forwarder = None
424 adminpassplain = getpass("Administrator password: ")
425 issue = self._adminpass_issue(adminpassplain)
427 self.errf.write("%s.\n" % issue)
429 adminpassverify = getpass("Retype password: ")
430 if not adminpassplain == adminpassverify:
431 self.errf.write("Sorry, passwords do not match.\n")
433 adminpass = adminpassplain
437 realm = sambaopts._lp.get('realm')
439 raise CommandError("No realm set!")
441 raise CommandError("No domain set!")
444 issue = self._adminpass_issue(adminpass)
446 raise CommandError(issue)
448 self.logger.info("Administrator password will be set randomly!")
450 if function_level == "2000":
451 dom_for_fun_level = DS_DOMAIN_FUNCTION_2000
452 elif function_level == "2003":
453 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
454 elif function_level == "2008":
455 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
456 elif function_level == "2008_R2":
457 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
459 if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
460 dns_forwarder = suggested_forwarder
462 samdb_fill = FILL_FULL
464 samdb_fill = FILL_NT4SYNC
465 elif partitions_only:
466 samdb_fill = FILL_DRS
468 if targetdir is not None:
469 if not os.path.isdir(targetdir):
474 if use_xattrs == "yes":
476 elif use_xattrs == "auto" and use_ntvfs == False:
478 elif use_ntvfs == False:
479 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
480 "Please re-run with --use-xattrs omitted.")
481 elif use_xattrs == "auto" and not lp.get("posix:eadb"):
483 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
485 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
488 samba.ntacls.setntacl(lp, file.name,
489 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
492 self.logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. ")
497 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.")
498 if ldap_backend_type == "existing":
499 if ldap_backend_forced_uri is not None:
500 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)
502 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")
504 if ldap_backend_forced_uri is not None:
505 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")
507 if domain_sid is not None:
508 domain_sid = security.dom_sid(domain_sid)
510 session = system_session()
511 if backend_store is None:
512 backend_store = get_default_backend_store()
514 result = provision(self.logger,
515 session, smbconf=smbconf, targetdir=targetdir,
516 samdb_fill=samdb_fill, realm=realm, domain=domain,
517 domainguid=domain_guid, domainsid=domain_sid,
519 hostip=host_ip, hostip6=host_ip6,
520 sitename=site, ntdsguid=ntds_guid,
521 invocationid=invocationid, adminpass=adminpass,
522 krbtgtpass=krbtgtpass, machinepass=machinepass,
523 dns_backend=dns_backend, dns_forwarder=dns_forwarder,
524 dnspass=dnspass, root=root, nobody=nobody,
526 serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
527 backend_type=ldap_backend_type,
528 ldapadminpass=ldapadminpass, ol_mmr_urls=ol_mmr_urls, slapd_path=slapd_path,
529 useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
530 use_rfc2307=use_rfc2307, skip_sysvolacl=False,
531 ldap_backend_extra_port=ldap_backend_extra_port,
532 ldap_backend_forced_uri=ldap_backend_forced_uri,
533 nosync=ldap_backend_nosync, ldap_dryrun_mode=ldap_dryrun_mode,
534 base_schema=base_schema,
535 plaintext_secrets=plaintext_secrets,
536 backend_store=backend_store)
538 except ProvisioningError as e:
539 raise CommandError("Provision failed", e)
541 result.report_logger(self.logger)
543 def _get_nameserver_ip(self):
544 """Grab the nameserver IP address from /etc/resolv.conf."""
546 RESOLV_CONF = "/etc/resolv.conf"
548 if not path.isfile(RESOLV_CONF):
549 self.logger.warning("Failed to locate %s" % RESOLV_CONF)
554 handle = open(RESOLV_CONF, 'r')
556 if not line.startswith('nameserver'):
558 # we want the last non-space continuous string of the line
559 return line.strip().split()[-1]
561 if handle is not None:
564 self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
566 def _adminpass_issue(self, adminpass):
567 """Returns error string for a bad administrator password,
568 or None if acceptable"""
569 if isinstance(adminpass, binary_type):
570 adminpass = adminpass.decode('utf8')
571 if len(adminpass) < DEFAULT_MIN_PWD_LENGTH:
572 return "Administrator password does not meet the default minimum" \
573 " password length requirement (%d characters)" \
574 % DEFAULT_MIN_PWD_LENGTH
575 elif not samba.check_password_quality(adminpass):
576 return "Administrator password does not meet the default" \
582 class cmd_domain_dcpromo(Command):
583 """Promote an existing domain member or NT4 PDC to an AD DC."""
585 synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
587 takes_optiongroups = {
588 "sambaopts": options.SambaOptions,
589 "versionopts": options.VersionOptions,
590 "credopts": options.CredentialsOptions,
594 takes_options.extend(common_join_options)
596 takes_options.extend(common_provision_join_options)
598 if samba.is_ntvfs_fileserver_built():
599 takes_options.extend(common_ntvfs_options)
601 takes_args = ["domain", "role?"]
603 def run(self, domain, role=None, sambaopts=None, credopts=None,
604 versionopts=None, server=None, site=None, targetdir=None,
605 domain_critical_only=False, parent_domain=None, machinepass=None,
606 use_ntvfs=False, dns_backend=None,
607 quiet=False, verbose=False, plaintext_secrets=False,
609 lp = sambaopts.get_loadparm()
610 creds = credopts.get_credentials(lp)
611 net = Net(creds, lp, server=credopts.ipaddress)
613 logger = self.get_logger(verbose=verbose, quiet=quiet)
615 netbios_name = lp.get("netbios name")
621 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
622 site=site, netbios_name=netbios_name, targetdir=targetdir,
623 domain_critical_only=domain_critical_only,
624 machinepass=machinepass, use_ntvfs=use_ntvfs,
625 dns_backend=dns_backend,
626 promote_existing=True, plaintext_secrets=plaintext_secrets,
627 backend_store=backend_store)
629 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
630 site=site, netbios_name=netbios_name, targetdir=targetdir,
631 domain_critical_only=domain_critical_only,
632 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
633 promote_existing=True, plaintext_secrets=plaintext_secrets,
634 backend_store=backend_store)
636 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
639 class cmd_domain_join(Command):
640 """Join domain as either member or backup domain controller."""
642 synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
644 takes_optiongroups = {
645 "sambaopts": options.SambaOptions,
646 "versionopts": options.VersionOptions,
647 "credopts": options.CredentialsOptions,
651 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
652 Option("--adminpass", type="string", metavar="PASSWORD",
653 help="choose adminstrator password when joining as a subdomain (otherwise random)"),
657 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
660 takes_options.extend(common_join_options)
661 takes_options.extend(common_provision_join_options)
663 if samba.is_ntvfs_fileserver_built():
664 takes_options.extend(ntvfs_options)
666 takes_args = ["domain", "role?"]
668 def run(self, domain, role=None, sambaopts=None, credopts=None,
669 versionopts=None, server=None, site=None, targetdir=None,
670 domain_critical_only=False, parent_domain=None, machinepass=None,
671 use_ntvfs=False, dns_backend=None, adminpass=None,
672 quiet=False, verbose=False,
673 plaintext_secrets=False,
675 lp = sambaopts.get_loadparm()
676 creds = credopts.get_credentials(lp)
677 net = Net(creds, lp, server=credopts.ipaddress)
679 logger = self.get_logger(verbose=verbose, quiet=quiet)
681 netbios_name = lp.get("netbios name")
686 if role is None or role == "MEMBER":
687 (join_password, sid, domain_name) = net.join_member(
688 domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
689 machinepass=machinepass)
691 self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
693 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
694 site=site, netbios_name=netbios_name, targetdir=targetdir,
695 domain_critical_only=domain_critical_only,
696 machinepass=machinepass, use_ntvfs=use_ntvfs,
697 dns_backend=dns_backend,
698 plaintext_secrets=plaintext_secrets,
699 backend_store=backend_store)
701 join_RODC(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,
707 backend_store=backend_store)
708 elif role == "SUBDOMAIN":
710 logger.info("Administrator password will be set randomly!")
712 netbios_domain = lp.get("workgroup")
713 if parent_domain is None:
714 parent_domain = ".".join(domain.split(".")[1:])
715 join_subdomain(logger=logger, server=server, creds=creds, lp=lp, dnsdomain=domain,
716 parent_domain=parent_domain, site=site,
717 netbios_name=netbios_name, netbios_domain=netbios_domain,
718 targetdir=targetdir, machinepass=machinepass,
719 use_ntvfs=use_ntvfs, dns_backend=dns_backend,
721 plaintext_secrets=plaintext_secrets,
722 backend_store=backend_store)
724 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role)
727 class cmd_domain_demote(Command):
728 """Demote ourselves from the role of Domain Controller."""
730 synopsis = "%prog [options]"
733 Option("--server", help="writable DC to write demotion changes on", type=str),
734 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
735 metavar="URL", dest="H"),
736 Option("--remove-other-dead-server", help="Dead DC (name or NTDS GUID) "
737 "to remove ALL references to (rather than this DC)", type=str),
738 Option("-q", "--quiet", help="Be quiet", action="store_true"),
739 Option("-v", "--verbose", help="Be verbose", action="store_true"),
742 takes_optiongroups = {
743 "sambaopts": options.SambaOptions,
744 "credopts": options.CredentialsOptions,
745 "versionopts": options.VersionOptions,
748 def run(self, sambaopts=None, credopts=None,
749 versionopts=None, server=None,
750 remove_other_dead_server=None, H=None,
751 verbose=False, quiet=False):
752 lp = sambaopts.get_loadparm()
753 creds = credopts.get_credentials(lp)
754 net = Net(creds, lp, server=credopts.ipaddress)
756 logger = self.get_logger(verbose=verbose, quiet=quiet)
758 if remove_other_dead_server is not None:
759 if server is not None:
760 samdb = SamDB(url="ldap://%s" % server,
761 session_info=system_session(),
762 credentials=creds, lp=lp)
764 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
766 remove_dc.remove_dc(samdb, logger, remove_other_dead_server)
767 except remove_dc.DemoteException as err:
768 raise CommandError("Demote failed: %s" % err)
771 netbios_name = lp.get("netbios name")
772 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
774 res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
776 raise CommandError("Unable to search for servers")
779 raise CommandError("You are the last server in the domain")
783 if str(e["name"]).lower() != netbios_name.lower():
784 server = e["dnsHostName"]
787 ntds_guid = samdb.get_ntds_GUID()
788 msg = samdb.search(base=str(samdb.get_config_basedn()),
789 scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
791 if len(msg) == 0 or "options" not in msg[0]:
792 raise CommandError("Failed to find options on %s" % ntds_guid)
795 dsa_options = int(str(msg[0]['options']))
797 res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
798 controls=["search_options:1:2"])
801 raise CommandError("Current DC is still the owner of %d role(s), "
802 "use the role command to transfer roles to "
806 self.errf.write("Using %s as partner server for the demotion\n" %
808 (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
810 self.errf.write("Deactivating inbound replication\n")
815 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
816 dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
817 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
820 self.errf.write("Asking partner server %s to synchronize from us\n"
822 for part in (samdb.get_schema_basedn(),
823 samdb.get_config_basedn(),
824 samdb.get_root_basedn()):
825 nc = drsuapi.DsReplicaObjectIdentifier()
828 req1 = drsuapi.DsReplicaSyncRequest1()
829 req1.naming_context = nc
830 req1.options = drsuapi.DRSUAPI_DRS_WRIT_REP
831 req1.source_dsa_guid = misc.GUID(ntds_guid)
834 drsuapiBind.DsReplicaSync(drsuapi_handle, 1, req1)
835 except RuntimeError as e1:
836 (werr, string) = e1.args
837 if werr == werror.WERR_DS_DRA_NO_REPLICA:
841 "Error while replicating out last local changes from '%s' for demotion, "
842 "re-enabling inbound replication\n" % part)
843 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
844 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
846 raise CommandError("Error while sending a DsReplicaSync for partition '%s'" % str(part), string)
848 remote_samdb = SamDB(url="ldap://%s" % server,
849 session_info=system_session(),
850 credentials=creds, lp=lp)
852 self.errf.write("Changing userControl and container\n")
853 res = remote_samdb.search(base=str(remote_samdb.domain_dn()),
854 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
855 netbios_name.upper(),
856 attrs=["userAccountControl"])
858 uac = int(str(res[0]["userAccountControl"]))
860 except Exception as e:
861 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
863 "Error while demoting, re-enabling inbound replication\n")
864 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
865 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
867 raise CommandError("Error while changing account control", e)
870 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
872 "Error while demoting, re-enabling inbound replication")
873 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
874 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
876 raise CommandError("Unable to find object with samaccountName = %s$"
877 " in the remote dc" % netbios_name.upper())
881 uac &= ~(UF_SERVER_TRUST_ACCOUNT |
882 UF_TRUSTED_FOR_DELEGATION |
883 UF_PARTIAL_SECRETS_ACCOUNT)
884 uac |= UF_WORKSTATION_TRUST_ACCOUNT
889 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
890 ldb.FLAG_MOD_REPLACE,
891 "userAccountControl")
893 remote_samdb.modify(msg)
894 except Exception as e:
895 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
897 "Error while demoting, re-enabling inbound replication")
898 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
899 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
902 raise CommandError("Error while changing account control", e)
904 parent = msg.dn.parent()
905 dc_name = res[0].dn.get_rdn_value()
906 rdn = "CN=%s" % dc_name
908 # Let's move to the Computer container
912 computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.domain_dn()))
913 res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
916 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
917 scope=ldb.SCOPE_ONELEVEL)
918 while(len(res) != 0 and i < 100):
920 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
921 scope=ldb.SCOPE_ONELEVEL)
924 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
926 "Error while demoting, re-enabling inbound replication\n")
927 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
928 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
934 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
935 ldb.FLAG_MOD_REPLACE,
936 "userAccountControl")
938 remote_samdb.modify(msg)
940 raise CommandError("Unable to find a slot for renaming %s,"
941 " all names from %s-1 to %s-%d seemed used" %
942 (str(dc_dn), rdn, rdn, i - 9))
944 newrdn = "%s-%d" % (rdn, i)
947 newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
948 remote_samdb.rename(dc_dn, newdn)
949 except Exception as e:
950 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
952 "Error while demoting, re-enabling inbound replication\n")
953 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
954 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
960 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
961 ldb.FLAG_MOD_REPLACE,
962 "userAccountControl")
964 remote_samdb.modify(msg)
965 raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
967 server_dsa_dn = samdb.get_serverName()
968 domain = remote_samdb.get_root_basedn()
971 req1 = drsuapi.DsRemoveDSServerRequest1()
972 req1.server_dn = str(server_dsa_dn)
973 req1.domain_dn = str(domain)
976 drsuapiBind.DsRemoveDSServer(drsuapi_handle, 1, req1)
977 except RuntimeError as e3:
978 (werr, string) = e3.args
979 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
981 "Error while demoting, re-enabling inbound replication\n")
982 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
983 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
989 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
990 ldb.FLAG_MOD_REPLACE,
991 "userAccountControl")
992 remote_samdb.modify(msg)
993 remote_samdb.rename(newdn, dc_dn)
994 if werr == werror.WERR_DS_DRA_NO_REPLICA:
995 raise CommandError("The DC %s is not present on (already "
996 "removed from) the remote server: %s" %
999 raise CommandError("Error while sending a removeDsServer "
1001 (server_dsa_dn, e3))
1003 remove_dc.remove_sysvol_references(remote_samdb, logger, dc_name)
1005 # These are objects under the computer account that should be deleted
1006 for s in ("CN=Enterprise,CN=NTFRS Subscriptions",
1007 "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
1008 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
1009 "CN=NTFRS Subscriptions"):
1011 remote_samdb.delete(ldb.Dn(remote_samdb,
1012 "%s,%s" % (s, str(newdn))))
1013 except ldb.LdbError as l:
1016 # get dns host name for target server to demote, remove dns references
1017 remove_dc.remove_dns_references(remote_samdb, logger, samdb.host_dns_name(),
1018 ignore_no_name=True)
1020 self.errf.write("Demote successful\n")
1023 class cmd_domain_level(Command):
1024 """Raise domain and forest function levels."""
1026 synopsis = "%prog (show|raise <options>) [options]"
1028 takes_optiongroups = {
1029 "sambaopts": options.SambaOptions,
1030 "credopts": options.CredentialsOptions,
1031 "versionopts": options.VersionOptions,
1035 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1036 metavar="URL", dest="H"),
1037 Option("-q", "--quiet", help="Be quiet", action="store_true"), # unused
1038 Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1039 help="The forest function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)"),
1040 Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1041 help="The domain function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)")
1044 takes_args = ["subcommand"]
1046 def run(self, subcommand, H=None, forest_level=None, domain_level=None,
1047 quiet=False, credopts=None, sambaopts=None, versionopts=None):
1048 lp = sambaopts.get_loadparm()
1049 creds = credopts.get_credentials(lp, fallback_machine=True)
1051 samdb = SamDB(url=H, session_info=system_session(),
1052 credentials=creds, lp=lp)
1054 domain_dn = samdb.domain_dn()
1056 res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
1057 scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
1058 assert len(res_forest) == 1
1060 res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1061 attrs=["msDS-Behavior-Version", "nTMixedDomain"])
1062 assert len(res_domain) == 1
1064 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
1065 scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
1066 attrs=["msDS-Behavior-Version"])
1067 assert len(res_dc_s) >= 1
1069 # default values, since "msDS-Behavior-Version" does not exist on Windows 2000 AD
1070 level_forest = DS_DOMAIN_FUNCTION_2000
1071 level_domain = DS_DOMAIN_FUNCTION_2000
1073 if "msDS-Behavior-Version" in res_forest[0]:
1074 level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
1075 if "msDS-Behavior-Version" in res_domain[0]:
1076 level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
1077 level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
1080 for msg in res_dc_s:
1081 if "msDS-Behavior-Version" in msg:
1082 if min_level_dc is None or int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
1083 min_level_dc = int(msg["msDS-Behavior-Version"][0])
1085 min_level_dc = DS_DOMAIN_FUNCTION_2000
1086 # well, this is the least
1089 if level_forest < DS_DOMAIN_FUNCTION_2000 or level_domain < DS_DOMAIN_FUNCTION_2000:
1090 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
1091 if min_level_dc < DS_DOMAIN_FUNCTION_2000:
1092 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
1093 if level_forest > level_domain:
1094 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
1095 if level_domain > min_level_dc:
1096 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
1098 if subcommand == "show":
1099 self.message("Domain and forest function level for domain '%s'" % domain_dn)
1100 if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1101 self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1102 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1103 self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1104 if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1105 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)!")
1109 if level_forest == DS_DOMAIN_FUNCTION_2000:
1111 elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
1112 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1113 elif level_forest == DS_DOMAIN_FUNCTION_2003:
1115 elif level_forest == DS_DOMAIN_FUNCTION_2008:
1117 elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
1119 elif level_forest == DS_DOMAIN_FUNCTION_2012:
1121 elif level_forest == DS_DOMAIN_FUNCTION_2012_R2:
1124 outstr = "higher than 2012 R2"
1125 self.message("Forest function level: (Windows) " + outstr)
1127 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1128 outstr = "2000 mixed (NT4 DC support)"
1129 elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
1131 elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
1132 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1133 elif level_domain == DS_DOMAIN_FUNCTION_2003:
1135 elif level_domain == DS_DOMAIN_FUNCTION_2008:
1137 elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
1139 elif level_domain == DS_DOMAIN_FUNCTION_2012:
1141 elif level_domain == DS_DOMAIN_FUNCTION_2012_R2:
1144 outstr = "higher than 2012 R2"
1145 self.message("Domain function level: (Windows) " + outstr)
1147 if min_level_dc == DS_DOMAIN_FUNCTION_2000:
1149 elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
1151 elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
1153 elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
1155 elif min_level_dc == DS_DOMAIN_FUNCTION_2012:
1157 elif min_level_dc == DS_DOMAIN_FUNCTION_2012_R2:
1160 outstr = "higher than 2012 R2"
1161 self.message("Lowest function level of a DC: (Windows) " + outstr)
1163 elif subcommand == "raise":
1166 if domain_level is not None:
1167 if domain_level == "2003":
1168 new_level_domain = DS_DOMAIN_FUNCTION_2003
1169 elif domain_level == "2008":
1170 new_level_domain = DS_DOMAIN_FUNCTION_2008
1171 elif domain_level == "2008_R2":
1172 new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
1173 elif domain_level == "2012":
1174 new_level_domain = DS_DOMAIN_FUNCTION_2012
1175 elif domain_level == "2012_R2":
1176 new_level_domain = DS_DOMAIN_FUNCTION_2012_R2
1178 if new_level_domain <= level_domain and level_domain_mixed == 0:
1179 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
1180 if new_level_domain > min_level_dc:
1181 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
1183 # Deactivate mixed/interim domain support
1184 if level_domain_mixed != 0:
1185 # Directly on the base DN
1187 m.dn = ldb.Dn(samdb, domain_dn)
1188 m["nTMixedDomain"] = ldb.MessageElement("0",
1189 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1193 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
1194 m["nTMixedDomain"] = ldb.MessageElement("0",
1195 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1198 except ldb.LdbError as e:
1199 (enum, emsg) = e.args
1200 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1203 # Directly on the base DN
1205 m.dn = ldb.Dn(samdb, domain_dn)
1206 m["msDS-Behavior-Version"] = ldb.MessageElement(
1207 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1208 "msDS-Behavior-Version")
1212 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
1213 + ",CN=Partitions,%s" % samdb.get_config_basedn())
1214 m["msDS-Behavior-Version"] = ldb.MessageElement(
1215 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1216 "msDS-Behavior-Version")
1219 except ldb.LdbError as e2:
1220 (enum, emsg) = e2.args
1221 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1224 level_domain = new_level_domain
1225 msgs.append("Domain function level changed!")
1227 if forest_level is not None:
1228 if forest_level == "2003":
1229 new_level_forest = DS_DOMAIN_FUNCTION_2003
1230 elif forest_level == "2008":
1231 new_level_forest = DS_DOMAIN_FUNCTION_2008
1232 elif forest_level == "2008_R2":
1233 new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
1234 elif forest_level == "2012":
1235 new_level_forest = DS_DOMAIN_FUNCTION_2012
1236 elif forest_level == "2012_R2":
1237 new_level_forest = DS_DOMAIN_FUNCTION_2012_R2
1239 if new_level_forest <= level_forest:
1240 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1241 if new_level_forest > level_domain:
1242 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1245 m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
1246 m["msDS-Behavior-Version"] = ldb.MessageElement(
1247 str(new_level_forest), ldb.FLAG_MOD_REPLACE,
1248 "msDS-Behavior-Version")
1250 msgs.append("Forest function level changed!")
1251 msgs.append("All changes applied successfully!")
1252 self.message("\n".join(msgs))
1254 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
1257 class cmd_domain_passwordsettings_show(Command):
1258 """Display current password settings for the domain."""
1260 synopsis = "%prog [options]"
1262 takes_optiongroups = {
1263 "sambaopts": options.SambaOptions,
1264 "versionopts": options.VersionOptions,
1265 "credopts": options.CredentialsOptions,
1269 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1270 metavar="URL", dest="H"),
1273 def run(self, H=None, credopts=None, sambaopts=None, versionopts=None):
1274 lp = sambaopts.get_loadparm()
1275 creds = credopts.get_credentials(lp)
1277 samdb = SamDB(url=H, session_info=system_session(),
1278 credentials=creds, lp=lp)
1280 domain_dn = samdb.domain_dn()
1281 res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1282 attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1283 "minPwdAge", "maxPwdAge", "lockoutDuration", "lockoutThreshold",
1284 "lockOutObservationWindow"])
1285 assert(len(res) == 1)
1287 pwd_props = int(res[0]["pwdProperties"][0])
1288 pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
1289 cur_min_pwd_len = int(res[0]["minPwdLength"][0])
1291 cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1292 if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000:
1295 cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1296 cur_account_lockout_threshold = int(res[0]["lockoutThreshold"][0])
1298 if int(res[0]["lockoutDuration"][0]) == -0x8000000000000000:
1299 cur_account_lockout_duration = 0
1301 cur_account_lockout_duration = abs(int(res[0]["lockoutDuration"][0])) / (1e7 * 60)
1302 cur_reset_account_lockout_after = abs(int(res[0]["lockOutObservationWindow"][0])) / (1e7 * 60)
1303 except Exception as e:
1304 raise CommandError("Could not retrieve password properties!", e)
1306 self.message("Password informations for domain '%s'" % domain_dn)
1308 if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
1309 self.message("Password complexity: on")
1311 self.message("Password complexity: off")
1312 if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
1313 self.message("Store plaintext passwords: on")
1315 self.message("Store plaintext passwords: off")
1316 self.message("Password history length: %d" % pwd_hist_len)
1317 self.message("Minimum password length: %d" % cur_min_pwd_len)
1318 self.message("Minimum password age (days): %d" % cur_min_pwd_age)
1319 self.message("Maximum password age (days): %d" % cur_max_pwd_age)
1320 self.message("Account lockout duration (mins): %d" % cur_account_lockout_duration)
1321 self.message("Account lockout threshold (attempts): %d" % cur_account_lockout_threshold)
1322 self.message("Reset account lockout after (mins): %d" % cur_reset_account_lockout_after)
1325 class cmd_domain_passwordsettings_set(Command):
1326 """Set password settings.
1328 Password complexity, password lockout policy, history length,
1329 minimum password length, the minimum and maximum password age) on
1330 a Samba AD DC server.
1332 Use against a Windows DC is possible, but group policy will override it.
1335 synopsis = "%prog <options> [options]"
1337 takes_optiongroups = {
1338 "sambaopts": options.SambaOptions,
1339 "versionopts": options.VersionOptions,
1340 "credopts": options.CredentialsOptions,
1344 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1345 metavar="URL", dest="H"),
1346 Option("-q", "--quiet", help="Be quiet", action="store_true"), # unused
1347 Option("--complexity", type="choice", choices=["on", "off", "default"],
1348 help="The password complexity (on | off | default). Default is 'on'"),
1349 Option("--store-plaintext", type="choice", choices=["on", "off", "default"],
1350 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1351 Option("--history-length",
1352 help="The password history length (<integer> | default). Default is 24.", type=str),
1353 Option("--min-pwd-length",
1354 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1355 Option("--min-pwd-age",
1356 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1357 Option("--max-pwd-age",
1358 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1359 Option("--account-lockout-duration",
1360 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),
1361 Option("--account-lockout-threshold",
1362 help="The number of bad password attempts allowed before locking out the account (<integer> | default). Default is 0 (never lock out).", type=str),
1363 Option("--reset-account-lockout-after",
1364 help="After this time is elapsed, the recorded number of attempts restarts from zero (<integer> | default). Default is 30.", type=str),
1367 def run(self, H=None, min_pwd_age=None, max_pwd_age=None,
1368 quiet=False, complexity=None, store_plaintext=None, history_length=None,
1369 min_pwd_length=None, account_lockout_duration=None, account_lockout_threshold=None,
1370 reset_account_lockout_after=None, credopts=None, sambaopts=None,
1372 lp = sambaopts.get_loadparm()
1373 creds = credopts.get_credentials(lp)
1375 samdb = SamDB(url=H, session_info=system_session(),
1376 credentials=creds, lp=lp)
1378 domain_dn = samdb.domain_dn()
1381 m.dn = ldb.Dn(samdb, domain_dn)
1382 pwd_props = int(samdb.get_pwdProperties())
1384 if complexity is not None:
1385 if complexity == "on" or complexity == "default":
1386 pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
1387 msgs.append("Password complexity activated!")
1388 elif complexity == "off":
1389 pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
1390 msgs.append("Password complexity deactivated!")
1392 if store_plaintext is not None:
1393 if store_plaintext == "on" or store_plaintext == "default":
1394 pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1395 msgs.append("Plaintext password storage for changed passwords activated!")
1396 elif store_plaintext == "off":
1397 pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
1398 msgs.append("Plaintext password storage for changed passwords deactivated!")
1400 if complexity is not None or store_plaintext is not None:
1401 m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
1402 ldb.FLAG_MOD_REPLACE, "pwdProperties")
1404 if history_length is not None:
1405 if history_length == "default":
1408 pwd_hist_len = int(history_length)
1410 if pwd_hist_len < 0 or pwd_hist_len > 24:
1411 raise CommandError("Password history length must be in the range of 0 to 24!")
1413 m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
1414 ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
1415 msgs.append("Password history length changed!")
1417 if min_pwd_length is not None:
1418 if min_pwd_length == "default":
1421 min_pwd_len = int(min_pwd_length)
1423 if min_pwd_len < 0 or min_pwd_len > 14:
1424 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1426 m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
1427 ldb.FLAG_MOD_REPLACE, "minPwdLength")
1428 msgs.append("Minimum password length changed!")
1430 if min_pwd_age is not None:
1431 if min_pwd_age == "default":
1434 min_pwd_age = int(min_pwd_age)
1436 if min_pwd_age < 0 or min_pwd_age > 998:
1437 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1440 min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
1442 m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
1443 ldb.FLAG_MOD_REPLACE, "minPwdAge")
1444 msgs.append("Minimum password age changed!")
1446 if max_pwd_age is not None:
1447 if max_pwd_age == "default":
1450 max_pwd_age = int(max_pwd_age)
1452 if max_pwd_age < 0 or max_pwd_age > 999:
1453 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1456 if max_pwd_age == 0:
1457 max_pwd_age_ticks = -0x8000000000000000
1459 max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
1461 m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
1462 ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1463 msgs.append("Maximum password age changed!")
1465 if account_lockout_duration is not None:
1466 if account_lockout_duration == "default":
1467 account_lockout_duration = 30
1469 account_lockout_duration = int(account_lockout_duration)
1471 if account_lockout_duration < 0 or account_lockout_duration > 99999:
1472 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1475 if account_lockout_duration == 0:
1476 account_lockout_duration_ticks = -0x8000000000000000
1478 account_lockout_duration_ticks = -int(account_lockout_duration * (60 * 1e7))
1480 m["lockoutDuration"] = ldb.MessageElement(str(account_lockout_duration_ticks),
1481 ldb.FLAG_MOD_REPLACE, "lockoutDuration")
1482 msgs.append("Account lockout duration changed!")
1484 if account_lockout_threshold is not None:
1485 if account_lockout_threshold == "default":
1486 account_lockout_threshold = 0
1488 account_lockout_threshold = int(account_lockout_threshold)
1490 m["lockoutThreshold"] = ldb.MessageElement(str(account_lockout_threshold),
1491 ldb.FLAG_MOD_REPLACE, "lockoutThreshold")
1492 msgs.append("Account lockout threshold changed!")
1494 if reset_account_lockout_after is not None:
1495 if reset_account_lockout_after == "default":
1496 reset_account_lockout_after = 30
1498 reset_account_lockout_after = int(reset_account_lockout_after)
1500 if reset_account_lockout_after < 0 or reset_account_lockout_after > 99999:
1501 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1504 if reset_account_lockout_after == 0:
1505 reset_account_lockout_after_ticks = -0x8000000000000000
1507 reset_account_lockout_after_ticks = -int(reset_account_lockout_after * (60 * 1e7))
1509 m["lockOutObservationWindow"] = ldb.MessageElement(str(reset_account_lockout_after_ticks),
1510 ldb.FLAG_MOD_REPLACE, "lockOutObservationWindow")
1511 msgs.append("Duration to reset account lockout after changed!")
1513 if max_pwd_age and max_pwd_age > 0 and min_pwd_age >= max_pwd_age:
1514 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
1517 raise CommandError("You must specify at least one option to set. Try --help")
1519 msgs.append("All changes applied successfully!")
1520 self.message("\n".join(msgs))
1523 class cmd_domain_passwordsettings(SuperCommand):
1524 """Manage password policy settings."""
1527 subcommands["pso"] = cmd_domain_passwordsettings_pso()
1528 subcommands["show"] = cmd_domain_passwordsettings_show()
1529 subcommands["set"] = cmd_domain_passwordsettings_set()
1532 class cmd_domain_classicupgrade(Command):
1533 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1535 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1536 the testparm utility from your classic installation (with --testparm).
1539 synopsis = "%prog [options] <classic_smb_conf>"
1541 takes_optiongroups = {
1542 "sambaopts": options.SambaOptions,
1543 "versionopts": options.VersionOptions
1547 Option("--dbdir", type="string", metavar="DIR",
1548 help="Path to samba classic DC database directory"),
1549 Option("--testparm", type="string", metavar="PATH",
1550 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1551 Option("--targetdir", type="string", metavar="DIR",
1552 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1553 Option("-q", "--quiet", help="Be quiet", action="store_true"),
1554 Option("-v", "--verbose", help="Be verbose", action="store_true"),
1555 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
1556 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1557 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1558 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1559 "BIND9_DLZ uses samba4 AD to store zone information, "
1560 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1561 default="SAMBA_INTERNAL")
1565 Option("--use-xattrs", type="choice", choices=["yes", "no", "auto"],
1566 metavar="[yes|no|auto]",
1567 help="Define if we should use the native fs capabilities or a tdb file for "
1568 "storing attributes likes ntacl when --use-ntvfs is set. "
1569 "auto tries to make an inteligent guess based on the user rights and system capabilities",
1572 if samba.is_ntvfs_fileserver_built():
1573 takes_options.extend(common_ntvfs_options)
1574 takes_options.extend(ntvfs_options)
1576 takes_args = ["smbconf"]
1578 def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
1579 quiet=False, verbose=False, use_xattrs="auto", sambaopts=None, versionopts=None,
1580 dns_backend=None, use_ntvfs=False):
1582 if not os.path.exists(smbconf):
1583 raise CommandError("File %s does not exist" % smbconf)
1585 if testparm and not os.path.exists(testparm):
1586 raise CommandError("Testparm utility %s does not exist" % testparm)
1588 if dbdir and not os.path.exists(dbdir):
1589 raise CommandError("Directory %s does not exist" % dbdir)
1591 if not dbdir and not testparm:
1592 raise CommandError("Please specify either dbdir or testparm")
1594 logger = self.get_logger(verbose=verbose, quiet=quiet)
1596 if dbdir and testparm:
1597 logger.warning("both dbdir and testparm specified, ignoring dbdir.")
1600 lp = sambaopts.get_loadparm()
1602 s3conf = s3param.get_context()
1605 s3conf.set("realm", sambaopts.realm)
1607 if targetdir is not None:
1608 if not os.path.isdir(targetdir):
1612 if use_xattrs == "yes":
1614 elif use_xattrs == "auto" and use_ntvfs == False:
1616 elif use_ntvfs == False:
1617 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
1618 "Please re-run with --use-xattrs omitted.")
1619 elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
1621 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
1623 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
1626 samba.ntacls.setntacl(lp, tmpfile.name,
1627 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1630 # FIXME: Don't catch all exceptions here
1631 logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. "
1632 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1636 # Set correct default values from dbdir or testparm
1639 paths["state directory"] = dbdir
1640 paths["private dir"] = dbdir
1641 paths["lock directory"] = dbdir
1642 paths["smb passwd file"] = dbdir + "/smbpasswd"
1644 paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
1645 paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
1646 paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
1647 paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
1648 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1649 # "state directory", instead make use of "lock directory"
1650 if len(paths["state directory"]) == 0:
1651 paths["state directory"] = paths["lock directory"]
1654 s3conf.set(p, paths[p])
1656 # load smb.conf parameters
1657 logger.info("Reading smb.conf")
1658 s3conf.load(smbconf)
1659 samba3 = Samba3(smbconf, s3conf)
1661 logger.info("Provisioning")
1662 upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
1663 useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
1666 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
1667 __doc__ = cmd_domain_classicupgrade.__doc__
1669 # This command is present for backwards compatibility only,
1670 # and should not be shown.
1675 class LocalDCCredentialsOptions(options.CredentialsOptions):
1676 def __init__(self, parser):
1677 options.CredentialsOptions.__init__(self, parser, special_name="local-dc")
1680 class DomainTrustCommand(Command):
1681 """List domain trusts."""
1684 Command.__init__(self)
1685 self.local_lp = None
1687 self.local_server = None
1688 self.local_binding_string = None
1689 self.local_creds = None
1691 self.remote_server = None
1692 self.remote_binding_string = None
1693 self.remote_creds = None
1695 def _uint32(self, v):
1696 return ctypes.c_uint32(v).value
1698 def check_runtime_error(self, runtime, val):
1702 err32 = self._uint32(runtime.args[0])
1708 class LocalRuntimeError(CommandError):
1709 def __init__(exception_self, self, runtime, message):
1710 err32 = self._uint32(runtime.args[0])
1711 errstr = runtime.args[1]
1712 msg = "LOCAL_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1713 self.local_server, message, err32, errstr)
1714 CommandError.__init__(exception_self, msg)
1716 class RemoteRuntimeError(CommandError):
1717 def __init__(exception_self, self, runtime, message):
1718 err32 = self._uint32(runtime.args[0])
1719 errstr = runtime.args[1]
1720 msg = "REMOTE_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1721 self.remote_server, message, err32, errstr)
1722 CommandError.__init__(exception_self, msg)
1724 class LocalLdbError(CommandError):
1725 def __init__(exception_self, self, ldb_error, message):
1726 errval = ldb_error.args[0]
1727 errstr = ldb_error.args[1]
1728 msg = "LOCAL_DC[%s]: %s - ERROR(%d) - %s" % (
1729 self.local_server, message, errval, errstr)
1730 CommandError.__init__(exception_self, msg)
1732 def setup_local_server(self, sambaopts, localdcopts):
1733 if self.local_server is not None:
1734 return self.local_server
1736 lp = sambaopts.get_loadparm()
1738 local_server = localdcopts.ipaddress
1739 if local_server is None:
1740 server_role = lp.server_role()
1741 if server_role != "ROLE_ACTIVE_DIRECTORY_DC":
1742 raise CommandError("Invalid server_role %s" % (server_role))
1743 local_server = lp.get('netbios name')
1744 local_transport = "ncalrpc"
1745 local_binding_options = ""
1746 local_binding_options += ",auth_type=ncalrpc_as_system"
1747 local_ldap_url = None
1750 local_transport = "ncacn_np"
1751 local_binding_options = ""
1752 local_ldap_url = "ldap://%s" % local_server
1753 local_creds = localdcopts.get_credentials(lp)
1757 self.local_server = local_server
1758 self.local_binding_string = "%s:%s[%s]" % (local_transport, local_server, local_binding_options)
1759 self.local_ldap_url = local_ldap_url
1760 self.local_creds = local_creds
1761 return self.local_server
1763 def new_local_lsa_connection(self):
1764 return lsa.lsarpc(self.local_binding_string, self.local_lp, self.local_creds)
1766 def new_local_netlogon_connection(self):
1767 return netlogon.netlogon(self.local_binding_string, self.local_lp, self.local_creds)
1769 def new_local_ldap_connection(self):
1770 return SamDB(url=self.local_ldap_url,
1771 session_info=system_session(),
1772 credentials=self.local_creds,
1775 def setup_remote_server(self, credopts, domain,
1777 require_writable=True):
1780 assert require_writable
1782 if self.remote_server is not None:
1783 return self.remote_server
1785 self.remote_server = "__unknown__remote_server__.%s" % domain
1786 assert self.local_server is not None
1788 remote_creds = credopts.get_credentials(self.local_lp)
1789 remote_server = credopts.ipaddress
1790 remote_binding_options = ""
1792 # TODO: we should also support NT4 domains
1793 # we could use local_netlogon.netr_DsRGetDCNameEx2() with the remote domain name
1794 # and delegate NBT or CLDAP to the local netlogon server
1796 remote_net = Net(remote_creds, self.local_lp, server=remote_server)
1797 remote_flags = nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS
1798 if require_writable:
1799 remote_flags |= nbt.NBT_SERVER_WRITABLE
1801 remote_flags |= nbt.NBT_SERVER_PDC
1802 remote_info = remote_net.finddc(flags=remote_flags, domain=domain, address=remote_server)
1803 except NTSTATUSError as error:
1804 raise CommandError("Failed to find a writeable DC for domain '%s': %s" %
1807 raise CommandError("Failed to find a writeable DC for domain '%s'" % domain)
1809 nbt.NBT_SERVER_PDC: "PDC",
1810 nbt.NBT_SERVER_GC: "GC",
1811 nbt.NBT_SERVER_LDAP: "LDAP",
1812 nbt.NBT_SERVER_DS: "DS",
1813 nbt.NBT_SERVER_KDC: "KDC",
1814 nbt.NBT_SERVER_TIMESERV: "TIMESERV",
1815 nbt.NBT_SERVER_CLOSEST: "CLOSEST",
1816 nbt.NBT_SERVER_WRITABLE: "WRITABLE",
1817 nbt.NBT_SERVER_GOOD_TIMESERV: "GOOD_TIMESERV",
1818 nbt.NBT_SERVER_NDNC: "NDNC",
1819 nbt.NBT_SERVER_SELECT_SECRET_DOMAIN_6: "SELECT_SECRET_DOMAIN_6",
1820 nbt.NBT_SERVER_FULL_SECRET_DOMAIN_6: "FULL_SECRET_DOMAIN_6",
1821 nbt.NBT_SERVER_ADS_WEB_SERVICE: "ADS_WEB_SERVICE",
1822 nbt.NBT_SERVER_DS_8: "DS_8",
1823 nbt.NBT_SERVER_HAS_DNS_NAME: "HAS_DNS_NAME",
1824 nbt.NBT_SERVER_IS_DEFAULT_NC: "IS_DEFAULT_NC",
1825 nbt.NBT_SERVER_FOREST_ROOT: "FOREST_ROOT",
1827 server_type_string = self.generic_bitmap_to_string(flag_map,
1828 remote_info.server_type, names_only=True)
1829 self.outf.write("RemoteDC Netbios[%s] DNS[%s] ServerType[%s]\n" % (
1830 remote_info.pdc_name,
1831 remote_info.pdc_dns_name,
1832 server_type_string))
1834 self.remote_server = remote_info.pdc_dns_name
1835 self.remote_binding_string = "ncacn_np:%s[%s]" % (self.remote_server, remote_binding_options)
1836 self.remote_creds = remote_creds
1837 return self.remote_server
1839 def new_remote_lsa_connection(self):
1840 return lsa.lsarpc(self.remote_binding_string, self.local_lp, self.remote_creds)
1842 def new_remote_netlogon_connection(self):
1843 return netlogon.netlogon(self.remote_binding_string, self.local_lp, self.remote_creds)
1845 def get_lsa_info(self, conn, policy_access):
1846 objectAttr = lsa.ObjectAttribute()
1847 objectAttr.sec_qos = lsa.QosInfo()
1849 policy = conn.OpenPolicy2(b''.decode('utf-8'),
1850 objectAttr, policy_access)
1852 info = conn.QueryInfoPolicy2(policy, lsa.LSA_POLICY_INFO_DNS)
1854 return (policy, info)
1856 def get_netlogon_dc_unc(self, conn, server, domain):
1858 info = conn.netr_DsRGetDCNameEx2(server,
1859 None, 0, None, None, None,
1860 netlogon.DS_RETURN_DNS_NAME)
1862 except RuntimeError:
1863 return conn.netr_GetDcName(server, domain)
1865 def get_netlogon_dc_info(self, conn, server):
1866 info = conn.netr_DsRGetDCNameEx2(server,
1867 None, 0, None, None, None,
1868 netlogon.DS_RETURN_DNS_NAME)
1871 def netr_DomainTrust_to_name(self, t):
1872 if t.trust_type == lsa.LSA_TRUST_TYPE_DOWNLEVEL:
1873 return t.netbios_name
1877 def netr_DomainTrust_to_type(self, a, t):
1879 primary_parent = None
1881 if _t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
1883 if not _t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1884 primary_parent = a[_t.parent_index]
1887 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1888 if t is primary_parent:
1891 if t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1894 parent = a[t.parent_index]
1895 if parent is primary:
1900 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1905 def netr_DomainTrust_to_transitive(self, t):
1906 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1909 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE:
1912 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1917 def netr_DomainTrust_to_direction(self, t):
1918 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND and \
1919 t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1922 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND:
1925 if t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1930 def generic_enum_to_string(self, e_dict, v, names_only=False):
1934 v32 = self._uint32(v)
1935 w = "__unknown__%08X__" % v32
1937 r = "0x%x (%s)" % (v, w)
1940 def generic_bitmap_to_string(self, b_dict, v, names_only=False):
1945 for b in sorted(b_dict.keys()):
1952 c32 = self._uint32(c)
1953 s += ["__unknown_%08X__" % c32]
1958 r = "0x%x (%s)" % (v, w)
1961 def trustType_string(self, v):
1963 lsa.LSA_TRUST_TYPE_DOWNLEVEL: "DOWNLEVEL",
1964 lsa.LSA_TRUST_TYPE_UPLEVEL: "UPLEVEL",
1965 lsa.LSA_TRUST_TYPE_MIT: "MIT",
1966 lsa.LSA_TRUST_TYPE_DCE: "DCE",
1968 return self.generic_enum_to_string(types, v)
1970 def trustDirection_string(self, v):
1972 lsa.LSA_TRUST_DIRECTION_INBOUND |
1973 lsa.LSA_TRUST_DIRECTION_OUTBOUND: "BOTH",
1974 lsa.LSA_TRUST_DIRECTION_INBOUND: "INBOUND",
1975 lsa.LSA_TRUST_DIRECTION_OUTBOUND: "OUTBOUND",
1977 return self.generic_enum_to_string(directions, v)
1979 def trustAttributes_string(self, v):
1981 lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE: "NON_TRANSITIVE",
1982 lsa.LSA_TRUST_ATTRIBUTE_UPLEVEL_ONLY: "UPLEVEL_ONLY",
1983 lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN: "QUARANTINED_DOMAIN",
1984 lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE: "FOREST_TRANSITIVE",
1985 lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION: "CROSS_ORGANIZATION",
1986 lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST: "WITHIN_FOREST",
1987 lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL: "TREAT_AS_EXTERNAL",
1988 lsa.LSA_TRUST_ATTRIBUTE_USES_RC4_ENCRYPTION: "USES_RC4_ENCRYPTION",
1990 return self.generic_bitmap_to_string(attributes, v)
1992 def kerb_EncTypes_string(self, v):
1994 security.KERB_ENCTYPE_DES_CBC_CRC: "DES_CBC_CRC",
1995 security.KERB_ENCTYPE_DES_CBC_MD5: "DES_CBC_MD5",
1996 security.KERB_ENCTYPE_RC4_HMAC_MD5: "RC4_HMAC_MD5",
1997 security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96: "AES128_CTS_HMAC_SHA1_96",
1998 security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96: "AES256_CTS_HMAC_SHA1_96",
1999 security.KERB_ENCTYPE_FAST_SUPPORTED: "FAST_SUPPORTED",
2000 security.KERB_ENCTYPE_COMPOUND_IDENTITY_SUPPORTED: "COMPOUND_IDENTITY_SUPPORTED",
2001 security.KERB_ENCTYPE_CLAIMS_SUPPORTED: "CLAIMS_SUPPORTED",
2002 security.KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED: "RESOURCE_SID_COMPRESSION_DISABLED",
2004 return self.generic_bitmap_to_string(enctypes, v)
2006 def entry_tln_status(self, e_flags, ):
2008 return "Status[Enabled]"
2011 lsa.LSA_TLN_DISABLED_NEW: "Disabled-New",
2012 lsa.LSA_TLN_DISABLED_ADMIN: "Disabled",
2013 lsa.LSA_TLN_DISABLED_CONFLICT: "Disabled-Conflicting",
2015 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
2017 def entry_dom_status(self, e_flags):
2019 return "Status[Enabled]"
2022 lsa.LSA_SID_DISABLED_ADMIN: "Disabled-SID",
2023 lsa.LSA_SID_DISABLED_CONFLICT: "Disabled-SID-Conflicting",
2024 lsa.LSA_NB_DISABLED_ADMIN: "Disabled-NB",
2025 lsa.LSA_NB_DISABLED_CONFLICT: "Disabled-NB-Conflicting",
2027 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
2029 def write_forest_trust_info(self, fti, tln=None, collisions=None):
2031 tln_string = " TDO[%s]" % tln
2035 self.outf.write("Namespaces[%d]%s:\n" % (
2036 len(fti.entries), tln_string))
2038 for i, e in enumerate(fti.entries):
2041 collision_string = ""
2043 if collisions is not None:
2044 for c in collisions.entries:
2048 collision_string = " Collision[%s]" % (c.name.string)
2050 d = e.forest_trust_data
2051 if e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
2052 self.outf.write("TLN: %-32s DNS[*.%s]%s\n" % (
2053 self.entry_tln_status(flags),
2054 d.string, collision_string))
2055 elif e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
2056 self.outf.write("TLN_EX: %-29s DNS[*.%s]\n" % (
2058 elif e.type == lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
2059 self.outf.write("DOM: %-32s DNS[%s] Netbios[%s] SID[%s]%s\n" % (
2060 self.entry_dom_status(flags),
2061 d.dns_domain_name.string,
2062 d.netbios_domain_name.string,
2063 d.domain_sid, collision_string))
2067 class cmd_domain_trust_list(DomainTrustCommand):
2068 """List domain trusts."""
2070 synopsis = "%prog [options]"
2072 takes_optiongroups = {
2073 "sambaopts": options.SambaOptions,
2074 "versionopts": options.VersionOptions,
2075 "localdcopts": LocalDCCredentialsOptions,
2081 def run(self, sambaopts=None, versionopts=None, localdcopts=None):
2083 local_server = self.setup_local_server(sambaopts, localdcopts)
2085 local_netlogon = self.new_local_netlogon_connection()
2086 except RuntimeError as error:
2087 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2090 local_netlogon_trusts = \
2091 local_netlogon.netr_DsrEnumerateDomainTrusts(local_server,
2092 netlogon.NETR_TRUST_FLAG_IN_FOREST |
2093 netlogon.NETR_TRUST_FLAG_OUTBOUND |
2094 netlogon.NETR_TRUST_FLAG_INBOUND)
2095 except RuntimeError as error:
2096 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
2097 # TODO: we could implement a fallback to lsa.EnumTrustDom()
2098 raise CommandError("LOCAL_DC[%s]: netr_DsrEnumerateDomainTrusts not supported." % (
2100 raise self.LocalRuntimeError(self, error, "netr_DsrEnumerateDomainTrusts failed")
2102 a = local_netlogon_trusts.array
2104 if t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
2106 self.outf.write("%-14s %-15s %-19s %s\n" % (
2107 "Type[%s]" % self.netr_DomainTrust_to_type(a, t),
2108 "Transitive[%s]" % self.netr_DomainTrust_to_transitive(t),
2109 "Direction[%s]" % self.netr_DomainTrust_to_direction(t),
2110 "Name[%s]" % self.netr_DomainTrust_to_name(t)))
2114 class cmd_domain_trust_show(DomainTrustCommand):
2115 """Show trusted domain details."""
2117 synopsis = "%prog NAME [options]"
2119 takes_optiongroups = {
2120 "sambaopts": options.SambaOptions,
2121 "versionopts": options.VersionOptions,
2122 "localdcopts": LocalDCCredentialsOptions,
2128 takes_args = ["domain"]
2130 def run(self, domain, sambaopts=None, versionopts=None, localdcopts=None):
2132 local_server = self.setup_local_server(sambaopts, localdcopts)
2134 local_lsa = self.new_local_lsa_connection()
2135 except RuntimeError as error:
2136 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2139 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2140 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2141 except RuntimeError as error:
2142 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2144 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2145 local_lsa_info.name.string,
2146 local_lsa_info.dns_domain.string,
2147 local_lsa_info.sid))
2149 lsaString = lsa.String()
2150 lsaString.string = domain
2153 local_lsa.QueryTrustedDomainInfoByName(local_policy,
2155 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2156 local_tdo_info = local_tdo_full.info_ex
2157 local_tdo_posix = local_tdo_full.posix_offset
2158 except NTSTATUSError as error:
2159 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2160 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2162 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(FULL_INFO) failed")
2165 local_tdo_enctypes = \
2166 local_lsa.QueryTrustedDomainInfoByName(local_policy,
2168 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES)
2169 except NTSTATUSError as error:
2170 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_PARAMETER):
2172 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_INFO_CLASS):
2175 if error is not None:
2176 raise self.LocalRuntimeError(self, error,
2177 "QueryTrustedDomainInfoByName(SUPPORTED_ENCRYPTION_TYPES) failed")
2179 local_tdo_enctypes = lsa.TrustDomainInfoSupportedEncTypes()
2180 local_tdo_enctypes.enc_types = 0
2183 local_tdo_forest = None
2184 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2185 local_tdo_forest = \
2186 local_lsa.lsaRQueryForestTrustInformation(local_policy,
2188 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
2189 except RuntimeError as error:
2190 if self.check_runtime_error(error, ntstatus.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE):
2192 if self.check_runtime_error(error, ntstatus.NT_STATUS_NOT_FOUND):
2194 if error is not None:
2195 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation failed")
2197 local_tdo_forest = lsa.ForestTrustInformation()
2198 local_tdo_forest.count = 0
2199 local_tdo_forest.entries = []
2201 self.outf.write("TrustedDomain:\n\n")
2202 self.outf.write("NetbiosName: %s\n" % local_tdo_info.netbios_name.string)
2203 if local_tdo_info.netbios_name.string != local_tdo_info.domain_name.string:
2204 self.outf.write("DnsName: %s\n" % local_tdo_info.domain_name.string)
2205 self.outf.write("SID: %s\n" % local_tdo_info.sid)
2206 self.outf.write("Type: %s\n" % self.trustType_string(local_tdo_info.trust_type))
2207 self.outf.write("Direction: %s\n" % self.trustDirection_string(local_tdo_info.trust_direction))
2208 self.outf.write("Attributes: %s\n" % self.trustAttributes_string(local_tdo_info.trust_attributes))
2209 posix_offset_u32 = ctypes.c_uint32(local_tdo_posix.posix_offset).value
2210 posix_offset_i32 = ctypes.c_int32(local_tdo_posix.posix_offset).value
2211 self.outf.write("PosixOffset: 0x%08X (%d)\n" % (posix_offset_u32, posix_offset_i32))
2212 self.outf.write("kerb_EncTypes: %s\n" % self.kerb_EncTypes_string(local_tdo_enctypes.enc_types))
2214 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2215 self.write_forest_trust_info(local_tdo_forest,
2216 tln=local_tdo_info.domain_name.string)
2221 class cmd_domain_trust_create(DomainTrustCommand):
2222 """Create a domain or forest trust."""
2224 synopsis = "%prog DOMAIN [options]"
2226 takes_optiongroups = {
2227 "sambaopts": options.SambaOptions,
2228 "versionopts": options.VersionOptions,
2229 "credopts": options.CredentialsOptions,
2230 "localdcopts": LocalDCCredentialsOptions,
2234 Option("--type", type="choice", metavar="TYPE",
2235 choices=["external", "forest"],
2236 help="The type of the trust: 'external' or 'forest'.",
2238 default="external"),
2239 Option("--direction", type="choice", metavar="DIRECTION",
2240 choices=["incoming", "outgoing", "both"],
2241 help="The trust direction: 'incoming', 'outgoing' or 'both'.",
2242 dest='trust_direction',
2244 Option("--create-location", type="choice", metavar="LOCATION",
2245 choices=["local", "both"],
2246 help="Where to create the trusted domain object: 'local' or 'both'.",
2247 dest='create_location',
2249 Option("--cross-organisation", action="store_true",
2250 help="The related domains does not belong to the same organisation.",
2251 dest='cross_organisation',
2253 Option("--quarantined", type="choice", metavar="yes|no",
2254 choices=["yes", "no", None],
2255 help="Special SID filtering rules are applied to the trust. "
2256 "With --type=external the default is yes. "
2257 "With --type=forest the default is no.",
2258 dest='quarantined_arg',
2260 Option("--not-transitive", action="store_true",
2261 help="The forest trust is not transitive.",
2262 dest='not_transitive',
2264 Option("--treat-as-external", action="store_true",
2265 help="The treat the forest trust as external.",
2266 dest='treat_as_external',
2268 Option("--no-aes-keys", action="store_false",
2269 help="The trust uses aes kerberos keys.",
2270 dest='use_aes_keys',
2272 Option("--skip-validation", action="store_false",
2273 help="Skip validation of the trust.",
2278 takes_args = ["domain"]
2280 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2281 trust_type=None, trust_direction=None, create_location=None,
2282 cross_organisation=False, quarantined_arg=None,
2283 not_transitive=False, treat_as_external=False,
2284 use_aes_keys=False, validate=True):
2286 lsaString = lsa.String()
2289 if quarantined_arg is None:
2290 if trust_type == 'external':
2292 elif quarantined_arg == 'yes':
2295 if trust_type != 'forest':
2297 raise CommandError("--not-transitive requires --type=forest")
2298 if treat_as_external:
2299 raise CommandError("--treat-as-external requires --type=forest")
2303 enc_types = lsa.TrustDomainInfoSupportedEncTypes()
2304 enc_types.enc_types = security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96
2305 enc_types.enc_types |= security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96
2307 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2308 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2309 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2311 local_trust_info = lsa.TrustDomainInfoInfoEx()
2312 local_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2313 local_trust_info.trust_direction = 0
2314 if trust_direction == "both":
2315 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2316 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2317 elif trust_direction == "incoming":
2318 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2319 elif trust_direction == "outgoing":
2320 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2321 local_trust_info.trust_attributes = 0
2322 if cross_organisation:
2323 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2325 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2326 if trust_type == "forest":
2327 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2329 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2330 if treat_as_external:
2331 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2333 def get_password(name):
2336 if password is not None and password != '':
2338 password = getpass("New %s Password: " % name)
2339 passwordverify = getpass("Retype %s Password: " % name)
2340 if not password == passwordverify:
2342 self.outf.write("Sorry, passwords do not match.\n")
2344 incoming_secret = None
2345 outgoing_secret = None
2346 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2347 if create_location == "local":
2348 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2349 incoming_password = get_password("Incoming Trust")
2350 incoming_secret = string_to_byte_array(incoming_password.encode('utf-16-le'))
2351 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2352 outgoing_password = get_password("Outgoing Trust")
2353 outgoing_secret = string_to_byte_array(outgoing_password.encode('utf-16-le'))
2355 remote_trust_info = None
2357 # We use 240 random bytes.
2358 # Windows uses 28 or 240 random bytes. I guess it's
2359 # based on the trust type external vs. forest.
2361 # The initial trust password can be up to 512 bytes
2362 # while the versioned passwords used for periodic updates
2363 # can only be up to 498 bytes, as netr_ServerPasswordSet2()
2364 # needs to pass the NL_PASSWORD_VERSION structure within the
2365 # 512 bytes and a 2 bytes confounder is required.
2367 def random_trust_secret(length):
2368 pw = samba.generate_random_machine_password(length // 2, length // 2)
2369 return string_to_byte_array(pw.encode('utf-16-le'))
2371 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2372 incoming_secret = random_trust_secret(240)
2373 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2374 outgoing_secret = random_trust_secret(240)
2376 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2377 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2379 remote_trust_info = lsa.TrustDomainInfoInfoEx()
2380 remote_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2381 remote_trust_info.trust_direction = 0
2382 if trust_direction == "both":
2383 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2384 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2385 elif trust_direction == "incoming":
2386 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2387 elif trust_direction == "outgoing":
2388 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2389 remote_trust_info.trust_attributes = 0
2390 if cross_organisation:
2391 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2393 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2394 if trust_type == "forest":
2395 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2397 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2398 if treat_as_external:
2399 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2401 local_server = self.setup_local_server(sambaopts, localdcopts)
2403 local_lsa = self.new_local_lsa_connection()
2404 except RuntimeError as error:
2405 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2408 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2409 except RuntimeError as error:
2410 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2412 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2413 local_lsa_info.name.string,
2414 local_lsa_info.dns_domain.string,
2415 local_lsa_info.sid))
2418 remote_server = self.setup_remote_server(credopts, domain)
2419 except RuntimeError as error:
2420 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2423 remote_lsa = self.new_remote_lsa_connection()
2424 except RuntimeError as error:
2425 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2428 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2429 except RuntimeError as error:
2430 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2432 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2433 remote_lsa_info.name.string,
2434 remote_lsa_info.dns_domain.string,
2435 remote_lsa_info.sid))
2437 local_trust_info.domain_name.string = remote_lsa_info.dns_domain.string
2438 local_trust_info.netbios_name.string = remote_lsa_info.name.string
2439 local_trust_info.sid = remote_lsa_info.sid
2441 if remote_trust_info:
2442 remote_trust_info.domain_name.string = local_lsa_info.dns_domain.string
2443 remote_trust_info.netbios_name.string = local_lsa_info.name.string
2444 remote_trust_info.sid = local_lsa_info.sid
2447 lsaString.string = local_trust_info.domain_name.string
2448 local_old_netbios = \
2449 local_lsa.QueryTrustedDomainInfoByName(local_policy,
2451 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2452 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2453 except NTSTATUSError as error:
2454 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2455 raise self.LocalRuntimeError(self, error,
2456 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2460 lsaString.string = local_trust_info.netbios_name.string
2462 local_lsa.QueryTrustedDomainInfoByName(local_policy,
2464 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2465 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2466 except NTSTATUSError as error:
2467 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2468 raise self.LocalRuntimeError(self, error,
2469 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2472 if remote_trust_info:
2474 lsaString.string = remote_trust_info.domain_name.string
2475 remote_old_netbios = \
2476 remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2478 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2479 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2480 except NTSTATUSError as error:
2481 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2482 raise self.RemoteRuntimeError(self, error,
2483 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2487 lsaString.string = remote_trust_info.netbios_name.string
2489 remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2491 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2492 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2493 except NTSTATUSError as error:
2494 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2495 raise self.RemoteRuntimeError(self, error,
2496 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2500 local_netlogon = self.new_local_netlogon_connection()
2501 except RuntimeError as error:
2502 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2505 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
2506 except RuntimeError as error:
2507 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
2509 if remote_trust_info:
2511 remote_netlogon = self.new_remote_netlogon_connection()
2512 except RuntimeError as error:
2513 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2516 remote_netlogon_dc_unc = self.get_netlogon_dc_unc(remote_netlogon,
2517 remote_server, domain)
2518 except RuntimeError as error:
2519 raise self.RemoteRuntimeError(self, error, "failed to get netlogon dc info")
2521 def generate_AuthInOutBlob(secret, update_time):
2523 blob = drsblobs.trustAuthInOutBlob()
2528 clear = drsblobs.AuthInfoClear()
2529 clear.size = len(secret)
2530 clear.password = secret
2532 info = drsblobs.AuthenticationInformation()
2533 info.LastUpdateTime = samba.unix2nttime(update_time)
2534 info.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
2535 info.AuthInfo = clear
2537 array = drsblobs.AuthenticationInformationArray()
2539 array.array = [info]
2541 blob = drsblobs.trustAuthInOutBlob()
2543 blob.current = array
2547 def generate_AuthInfoInternal(session_key, incoming=None, outgoing=None):
2548 confounder = [0] * 512
2549 for i in range(len(confounder)):
2550 confounder[i] = random.randint(0, 255)
2552 trustpass = drsblobs.trustDomainPasswords()
2554 trustpass.confounder = confounder
2555 trustpass.outgoing = outgoing
2556 trustpass.incoming = incoming
2558 trustpass_blob = ndr_pack(trustpass)
2560 encrypted_trustpass = arcfour_encrypt(session_key, trustpass_blob)
2562 auth_blob = lsa.DATA_BUF2()
2563 auth_blob.size = len(encrypted_trustpass)
2564 auth_blob.data = string_to_byte_array(encrypted_trustpass)
2566 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
2567 auth_info.auth_blob = auth_blob
2571 update_time = samba.current_unix_time()
2572 incoming_blob = generate_AuthInOutBlob(incoming_secret, update_time)
2573 outgoing_blob = generate_AuthInOutBlob(outgoing_secret, update_time)
2575 local_tdo_handle = None
2576 remote_tdo_handle = None
2578 local_auth_info = generate_AuthInfoInternal(local_lsa.session_key,
2579 incoming=incoming_blob,
2580 outgoing=outgoing_blob)
2581 if remote_trust_info:
2582 remote_auth_info = generate_AuthInfoInternal(remote_lsa.session_key,
2583 incoming=outgoing_blob,
2584 outgoing=incoming_blob)
2587 if remote_trust_info:
2588 self.outf.write("Creating remote TDO.\n")
2589 current_request = {"location": "remote", "name": "CreateTrustedDomainEx2"}
2590 remote_tdo_handle = \
2591 remote_lsa.CreateTrustedDomainEx2(remote_policy,
2594 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2595 self.outf.write("Remote TDO created.\n")
2597 self.outf.write("Setting supported encryption types on remote TDO.\n")
2598 current_request = {"location": "remote", "name": "SetInformationTrustedDomain"}
2599 remote_lsa.SetInformationTrustedDomain(remote_tdo_handle,
2600 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2603 self.outf.write("Creating local TDO.\n")
2604 current_request = {"location": "local", "name": "CreateTrustedDomainEx2"}
2605 local_tdo_handle = local_lsa.CreateTrustedDomainEx2(local_policy,
2608 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2609 self.outf.write("Local TDO created\n")
2611 self.outf.write("Setting supported encryption types on local TDO.\n")
2612 current_request = {"location": "local", "name": "SetInformationTrustedDomain"}
2613 local_lsa.SetInformationTrustedDomain(local_tdo_handle,
2614 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2616 except RuntimeError as error:
2617 self.outf.write("Error: %s failed %sly - cleaning up\n" % (
2618 current_request['name'], current_request['location']))
2619 if remote_tdo_handle:
2620 self.outf.write("Deleting remote TDO.\n")
2621 remote_lsa.DeleteObject(remote_tdo_handle)
2622 remote_tdo_handle = None
2623 if local_tdo_handle:
2624 self.outf.write("Deleting local TDO.\n")
2625 local_lsa.DeleteObject(local_tdo_handle)
2626 local_tdo_handle = None
2627 if current_request['location'] == "remote":
2628 raise self.RemoteRuntimeError(self, error, "%s" % (
2629 current_request['name']))
2630 raise self.LocalRuntimeError(self, error, "%s" % (
2631 current_request['name']))
2634 if local_trust_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2635 self.outf.write("Setup local forest trust information...\n")
2637 # get all information about the remote trust
2638 # this triggers netr_GetForestTrustInformation to the remote domain
2639 # and lsaRSetForestTrustInformation() locally, but new top level
2640 # names are disabled by default.
2641 local_forest_info = \
2642 local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
2643 remote_lsa_info.dns_domain.string,
2644 netlogon.DS_GFTI_UPDATE_TDO)
2645 except RuntimeError as error:
2646 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2649 # here we try to enable all top level names
2650 local_forest_collision = \
2651 local_lsa.lsaRSetForestTrustInformation(local_policy,
2652 remote_lsa_info.dns_domain,
2653 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2656 except RuntimeError as error:
2657 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2659 self.write_forest_trust_info(local_forest_info,
2660 tln=remote_lsa_info.dns_domain.string,
2661 collisions=local_forest_collision)
2663 if remote_trust_info:
2664 self.outf.write("Setup remote forest trust information...\n")
2666 # get all information about the local trust (from the perspective of the remote domain)
2667 # this triggers netr_GetForestTrustInformation to our domain.
2668 # and lsaRSetForestTrustInformation() remotely, but new top level
2669 # names are disabled by default.
2670 remote_forest_info = \
2671 remote_netlogon.netr_DsRGetForestTrustInformation(remote_netlogon_dc_unc,
2672 local_lsa_info.dns_domain.string,
2673 netlogon.DS_GFTI_UPDATE_TDO)
2674 except RuntimeError as error:
2675 raise self.RemoteRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2678 # here we try to enable all top level names
2679 remote_forest_collision = \
2680 remote_lsa.lsaRSetForestTrustInformation(remote_policy,
2681 local_lsa_info.dns_domain,
2682 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2685 except RuntimeError as error:
2686 raise self.RemoteRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2688 self.write_forest_trust_info(remote_forest_info,
2689 tln=local_lsa_info.dns_domain.string,
2690 collisions=remote_forest_collision)
2692 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2693 self.outf.write("Validating outgoing trust...\n")
2695 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_netlogon_info.dc_unc,
2696 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2698 remote_lsa_info.dns_domain.string)
2699 except RuntimeError as error:
2700 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2702 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2703 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2705 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2706 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2707 local_trust_verify.trusted_dc_name,
2708 local_trust_verify.tc_connection_status[1],
2709 local_trust_verify.pdc_connection_status[1])
2711 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2712 local_trust_verify.trusted_dc_name,
2713 local_trust_verify.tc_connection_status[1],
2714 local_trust_verify.pdc_connection_status[1])
2716 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2717 raise CommandError(local_validation)
2719 self.outf.write("OK: %s\n" % local_validation)
2721 if remote_trust_info:
2722 if remote_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2723 self.outf.write("Validating incoming trust...\n")
2725 remote_trust_verify = \
2726 remote_netlogon.netr_LogonControl2Ex(remote_netlogon_dc_unc,
2727 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2729 local_lsa_info.dns_domain.string)
2730 except RuntimeError as error:
2731 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2733 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2734 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2736 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2737 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2738 remote_trust_verify.trusted_dc_name,
2739 remote_trust_verify.tc_connection_status[1],
2740 remote_trust_verify.pdc_connection_status[1])
2742 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2743 remote_trust_verify.trusted_dc_name,
2744 remote_trust_verify.tc_connection_status[1],
2745 remote_trust_verify.pdc_connection_status[1])
2747 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
2748 raise CommandError(remote_validation)
2750 self.outf.write("OK: %s\n" % remote_validation)
2752 if remote_tdo_handle is not None:
2754 remote_lsa.Close(remote_tdo_handle)
2755 except RuntimeError as error:
2757 remote_tdo_handle = None
2758 if local_tdo_handle is not None:
2760 local_lsa.Close(local_tdo_handle)
2761 except RuntimeError as error:
2763 local_tdo_handle = None
2765 self.outf.write("Success.\n")
2769 class cmd_domain_trust_delete(DomainTrustCommand):
2770 """Delete a domain trust."""
2772 synopsis = "%prog DOMAIN [options]"
2774 takes_optiongroups = {
2775 "sambaopts": options.SambaOptions,
2776 "versionopts": options.VersionOptions,
2777 "credopts": options.CredentialsOptions,
2778 "localdcopts": LocalDCCredentialsOptions,
2782 Option("--delete-location", type="choice", metavar="LOCATION",
2783 choices=["local", "both"],
2784 help="Where to delete the trusted domain object: 'local' or 'both'.",
2785 dest='delete_location',
2789 takes_args = ["domain"]
2791 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2792 delete_location=None):
2794 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2795 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2796 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2798 if delete_location == "local":
2799 remote_policy_access = None
2801 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2802 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2803 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2805 local_server = self.setup_local_server(sambaopts, localdcopts)
2807 local_lsa = self.new_local_lsa_connection()
2808 except RuntimeError as error:
2809 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2812 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2813 except RuntimeError as error:
2814 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2816 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2817 local_lsa_info.name.string,
2818 local_lsa_info.dns_domain.string,
2819 local_lsa_info.sid))
2821 local_tdo_info = None
2822 local_tdo_handle = None
2823 remote_tdo_info = None
2824 remote_tdo_handle = None
2826 lsaString = lsa.String()
2828 lsaString.string = domain
2829 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2830 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2831 except NTSTATUSError as error:
2832 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2833 raise CommandError("Failed to find trust for domain '%s'" % domain)
2834 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2836 if remote_policy_access is not None:
2838 remote_server = self.setup_remote_server(credopts, domain)
2839 except RuntimeError as error:
2840 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2843 remote_lsa = self.new_remote_lsa_connection()
2844 except RuntimeError as error:
2845 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2848 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2849 except RuntimeError as error:
2850 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2852 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2853 remote_lsa_info.name.string,
2854 remote_lsa_info.dns_domain.string,
2855 remote_lsa_info.sid))
2857 if remote_lsa_info.sid != local_tdo_info.sid or \
2858 remote_lsa_info.name.string != local_tdo_info.netbios_name.string or \
2859 remote_lsa_info.dns_domain.string != local_tdo_info.domain_name.string:
2860 raise CommandError("LocalTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2861 local_tdo_info.netbios_name.string,
2862 local_tdo_info.domain_name.string,
2863 local_tdo_info.sid))
2866 lsaString.string = local_lsa_info.dns_domain.string
2868 remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2870 lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2871 except NTSTATUSError as error:
2872 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2873 raise self.RemoteRuntimeError(self, error, "QueryTrustedDomainInfoByName(%s)" % (
2877 if remote_tdo_info is not None:
2878 if local_lsa_info.sid != remote_tdo_info.sid or \
2879 local_lsa_info.name.string != remote_tdo_info.netbios_name.string or \
2880 local_lsa_info.dns_domain.string != remote_tdo_info.domain_name.string:
2881 raise CommandError("RemoteTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2882 remote_tdo_info.netbios_name.string,
2883 remote_tdo_info.domain_name.string,
2884 remote_tdo_info.sid))
2886 if local_tdo_info is not None:
2888 lsaString.string = local_tdo_info.domain_name.string
2889 local_tdo_handle = \
2890 local_lsa.OpenTrustedDomainByName(local_policy,
2892 security.SEC_STD_DELETE)
2893 except RuntimeError as error:
2894 raise self.LocalRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2897 local_lsa.DeleteObject(local_tdo_handle)
2898 local_tdo_handle = None
2900 if remote_tdo_info is not None:
2902 lsaString.string = remote_tdo_info.domain_name.string
2903 remote_tdo_handle = \
2904 remote_lsa.OpenTrustedDomainByName(remote_policy,
2906 security.SEC_STD_DELETE)
2907 except RuntimeError as error:
2908 raise self.RemoteRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2911 if remote_tdo_handle is not None:
2913 remote_lsa.DeleteObject(remote_tdo_handle)
2914 remote_tdo_handle = None
2915 self.outf.write("RemoteTDO deleted.\n")
2916 except RuntimeError as error:
2917 self.outf.write("%s\n" % self.RemoteRuntimeError(self, error, "DeleteObject() failed"))
2919 if local_tdo_handle is not None:
2921 local_lsa.DeleteObject(local_tdo_handle)
2922 local_tdo_handle = None
2923 self.outf.write("LocalTDO deleted.\n")
2924 except RuntimeError as error:
2925 self.outf.write("%s\n" % self.LocalRuntimeError(self, error, "DeleteObject() failed"))
2930 class cmd_domain_trust_validate(DomainTrustCommand):
2931 """Validate a domain trust."""
2933 synopsis = "%prog DOMAIN [options]"
2935 takes_optiongroups = {
2936 "sambaopts": options.SambaOptions,
2937 "versionopts": options.VersionOptions,
2938 "credopts": options.CredentialsOptions,
2939 "localdcopts": LocalDCCredentialsOptions,
2943 Option("--validate-location", type="choice", metavar="LOCATION",
2944 choices=["local", "both"],
2945 help="Where to validate the trusted domain object: 'local' or 'both'.",
2946 dest='validate_location',
2950 takes_args = ["domain"]
2952 def run(self, domain, sambaopts=None, versionopts=None, credopts=None, localdcopts=None,
2953 validate_location=None):
2955 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2957 local_server = self.setup_local_server(sambaopts, localdcopts)
2959 local_lsa = self.new_local_lsa_connection()
2960 except RuntimeError as error:
2961 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2964 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2965 except RuntimeError as error:
2966 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2968 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2969 local_lsa_info.name.string,
2970 local_lsa_info.dns_domain.string,
2971 local_lsa_info.sid))
2974 lsaString = lsa.String()
2975 lsaString.string = domain
2977 local_lsa.QueryTrustedDomainInfoByName(local_policy,
2979 lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2980 except NTSTATUSError as error:
2981 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2982 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2984 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
2986 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
2987 local_tdo_info.netbios_name.string,
2988 local_tdo_info.domain_name.string,
2989 local_tdo_info.sid))
2992 local_netlogon = self.new_local_netlogon_connection()
2993 except RuntimeError as error:
2994 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2997 local_trust_verify = \
2998 local_netlogon.netr_LogonControl2Ex(local_server,
2999 netlogon.NETLOGON_CONTROL_TC_VERIFY,
3001 local_tdo_info.domain_name.string)
3002 except RuntimeError as error:
3003 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
3005 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
3006 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
3008 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
3009 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
3010 local_trust_verify.trusted_dc_name,
3011 local_trust_verify.tc_connection_status[1],
3012 local_trust_verify.pdc_connection_status[1])
3014 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
3015 local_trust_verify.trusted_dc_name,
3016 local_trust_verify.tc_connection_status[1],
3017 local_trust_verify.pdc_connection_status[1])
3019 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
3020 raise CommandError(local_validation)
3022 self.outf.write("OK: %s\n" % local_validation)
3025 server = local_trust_verify.trusted_dc_name.replace('\\', '')
3026 domain_and_server = "%s\\%s" % (local_tdo_info.domain_name.string, server)
3027 local_trust_rediscover = \
3028 local_netlogon.netr_LogonControl2Ex(local_server,
3029 netlogon.NETLOGON_CONTROL_REDISCOVER,
3032 except RuntimeError as error:
3033 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
3035 local_conn_status = self._uint32(local_trust_rediscover.tc_connection_status[0])
3036 local_rediscover = "LocalRediscover: DC[%s] CONNECTION[%s]" % (
3037 local_trust_rediscover.trusted_dc_name,
3038 local_trust_rediscover.tc_connection_status[1])
3040 if local_conn_status != werror.WERR_SUCCESS:
3041 raise CommandError(local_rediscover)
3043 self.outf.write("OK: %s\n" % local_rediscover)
3045 if validate_location != "local":
3047 remote_server = self.setup_remote_server(credopts, domain, require_pdc=False)
3048 except RuntimeError as error:
3049 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
3052 remote_netlogon = self.new_remote_netlogon_connection()
3053 except RuntimeError as error:
3054 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
3057 remote_trust_verify = \
3058 remote_netlogon.netr_LogonControl2Ex(remote_server,
3059 netlogon.NETLOGON_CONTROL_TC_VERIFY,
3061 local_lsa_info.dns_domain.string)
3062 except RuntimeError as error:
3063 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
3065 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
3066 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
3068 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
3069 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
3070 remote_trust_verify.trusted_dc_name,
3071 remote_trust_verify.tc_connection_status[1],
3072 remote_trust_verify.pdc_connection_status[1])
3074 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
3075 remote_trust_verify.trusted_dc_name,
3076 remote_trust_verify.tc_connection_status[1],
3077 remote_trust_verify.pdc_connection_status[1])
3079 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
3080 raise CommandError(remote_validation)
3082 self.outf.write("OK: %s\n" % remote_validation)
3085 server = remote_trust_verify.trusted_dc_name.replace('\\', '')
3086 domain_and_server = "%s\\%s" % (local_lsa_info.dns_domain.string, server)
3087 remote_trust_rediscover = \
3088 remote_netlogon.netr_LogonControl2Ex(remote_server,
3089 netlogon.NETLOGON_CONTROL_REDISCOVER,
3092 except RuntimeError as error:
3093 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
3095 remote_conn_status = self._uint32(remote_trust_rediscover.tc_connection_status[0])
3097 remote_rediscover = "RemoteRediscover: DC[%s] CONNECTION[%s]" % (
3098 remote_trust_rediscover.trusted_dc_name,
3099 remote_trust_rediscover.tc_connection_status[1])
3101 if remote_conn_status != werror.WERR_SUCCESS:
3102 raise CommandError(remote_rediscover)
3104 self.outf.write("OK: %s\n" % remote_rediscover)
3109 class cmd_domain_trust_namespaces(DomainTrustCommand):
3110 """Manage forest trust namespaces."""
3112 synopsis = "%prog [DOMAIN] [options]"
3114 takes_optiongroups = {
3115 "sambaopts": options.SambaOptions,
3116 "versionopts": options.VersionOptions,
3117 "localdcopts": LocalDCCredentialsOptions,
3121 Option("--refresh", type="choice", metavar="check|store",
3122 choices=["check", "store", None],
3123 help="List and maybe store refreshed forest trust information: 'check' or 'store'.",
3126 Option("--enable-all", action="store_true",
3127 help="Try to update disabled entries, not allowed with --refresh=check.",
3130 Option("--enable-tln", action="append", metavar='DNSDOMAIN',
3131 help="Enable a top level name entry. Can be specified multiple times.",
3134 Option("--disable-tln", action="append", metavar='DNSDOMAIN',
3135 help="Disable a top level name entry. Can be specified multiple times.",
3138 Option("--add-tln-ex", action="append", metavar='DNSDOMAIN',
3139 help="Add a top level exclusion entry. Can be specified multiple times.",
3142 Option("--delete-tln-ex", action="append", metavar='DNSDOMAIN',
3143 help="Delete a top level exclusion entry. Can be specified multiple times.",
3144 dest='delete_tln_ex',
3146 Option("--enable-nb", action="append", metavar='NETBIOSDOMAIN',
3147 help="Enable a netbios name in a domain entry. Can be specified multiple times.",
3150 Option("--disable-nb", action="append", metavar='NETBIOSDOMAIN',
3151 help="Disable a netbios name in a domain entry. Can be specified multiple times.",
3154 Option("--enable-sid", action="append", metavar='DOMAINSID',
3155 help="Enable a SID in a domain entry. Can be specified multiple times.",
3156 dest='enable_sid_str',
3158 Option("--disable-sid", action="append", metavar='DOMAINSID',
3159 help="Disable a SID in a domain entry. Can be specified multiple times.",
3160 dest='disable_sid_str',
3162 Option("--add-upn-suffix", action="append", metavar='DNSDOMAIN',
3163 help="Add a new uPNSuffixes attribute for the local forest. Can be specified multiple times.",
3166 Option("--delete-upn-suffix", action="append", metavar='DNSDOMAIN',
3167 help="Delete an existing uPNSuffixes attribute of the local forest. Can be specified multiple times.",
3170 Option("--add-spn-suffix", action="append", metavar='DNSDOMAIN',
3171 help="Add a new msDS-SPNSuffixes attribute for the local forest. Can be specified multiple times.",
3174 Option("--delete-spn-suffix", action="append", metavar='DNSDOMAIN',
3175 help="Delete an existing msDS-SPNSuffixes attribute of the local forest. Can be specified multiple times.",
3180 takes_args = ["domain?"]
3182 def run(self, domain=None, sambaopts=None, localdcopts=None, versionopts=None,
3183 refresh=None, enable_all=False,
3184 enable_tln=[], disable_tln=[], add_tln_ex=[], delete_tln_ex=[],
3185 enable_sid_str=[], disable_sid_str=[], enable_nb=[], disable_nb=[],
3186 add_upn=[], delete_upn=[], add_spn=[], delete_spn=[]):
3188 require_update = False
3191 if refresh == "store":
3192 raise CommandError("--refresh=%s not allowed without DOMAIN" % refresh)
3195 raise CommandError("--enable-all not allowed without DOMAIN")
3197 if len(enable_tln) > 0:
3198 raise CommandError("--enable-tln not allowed without DOMAIN")
3199 if len(disable_tln) > 0:
3200 raise CommandError("--disable-tln not allowed without DOMAIN")
3202 if len(add_tln_ex) > 0:
3203 raise CommandError("--add-tln-ex not allowed without DOMAIN")
3204 if len(delete_tln_ex) > 0:
3205 raise CommandError("--delete-tln-ex not allowed without DOMAIN")
3207 if len(enable_nb) > 0:
3208 raise CommandError("--enable-nb not allowed without DOMAIN")
3209 if len(disable_nb) > 0:
3210 raise CommandError("--disable-nb not allowed without DOMAIN")
3212 if len(enable_sid_str) > 0:
3213 raise CommandError("--enable-sid not allowed without DOMAIN")
3214 if len(disable_sid_str) > 0:
3215 raise CommandError("--disable-sid not allowed without DOMAIN")
3217 if len(add_upn) > 0:
3219 if not n.startswith("*."):
3221 raise CommandError("value[%s] specified for --add-upn-suffix should not include with '*.'" % n)
3222 require_update = True
3223 if len(delete_upn) > 0:
3224 for n in delete_upn:
3225 if not n.startswith("*."):
3227 raise CommandError("value[%s] specified for --delete-upn-suffix should not include with '*.'" % n)
3228 require_update = True
3230 for d in delete_upn:
3231 if a.lower() != d.lower():
3233 raise CommandError("value[%s] specified for --add-upn-suffix and --delete-upn-suffix" % a)
3235 if len(add_spn) > 0:
3237 if not n.startswith("*."):
3239 raise CommandError("value[%s] specified for --add-spn-suffix should not include with '*.'" % n)
3240 require_update = True
3241 if len(delete_spn) > 0:
3242 for n in delete_spn:
3243 if not n.startswith("*."):
3245 raise CommandError("value[%s] specified for --delete-spn-suffix should not include with '*.'" % n)
3246 require_update = True
3248 for d in delete_spn:
3249 if a.lower() != d.lower():
3251 raise CommandError("value[%s] specified for --add-spn-suffix and --delete-spn-suffix" % a)
3253 if len(add_upn) > 0:
3254 raise CommandError("--add-upn-suffix not allowed together with DOMAIN")
3255 if len(delete_upn) > 0:
3256 raise CommandError("--delete-upn-suffix not allowed together with DOMAIN")
3257 if len(add_spn) > 0:
3258 raise CommandError("--add-spn-suffix not allowed together with DOMAIN")
3259 if len(delete_spn) > 0:
3260 raise CommandError("--delete-spn-suffix not allowed together with DOMAIN")
3262 if refresh is not None:
3263 if refresh == "store":
3264 require_update = True
3266 if enable_all and refresh != "store":
3267 raise CommandError("--enable-all not allowed together with --refresh=%s" % refresh)
3269 if len(enable_tln) > 0:
3270 raise CommandError("--enable-tln not allowed together with --refresh")
3271 if len(disable_tln) > 0:
3272 raise CommandError("--disable-tln not allowed together with --refresh")
3274 if len(add_tln_ex) > 0:
3275 raise CommandError("--add-tln-ex not allowed together with --refresh")
3276 if len(delete_tln_ex) > 0:
3277 raise CommandError("--delete-tln-ex not allowed together with --refresh")
3279 if len(enable_nb) > 0:
3280 raise CommandError("--enable-nb not allowed together with --refresh")
3281 if len(disable_nb) > 0:
3282 raise CommandError("--disable-nb not allowed together with --refresh")
3284 if len(enable_sid_str) > 0:
3285 raise CommandError("--enable-sid not allowed together with --refresh")
3286 if len(disable_sid_str) > 0:
3287 raise CommandError("--disable-sid not allowed together with --refresh")
3290 require_update = True
3292 if len(enable_tln) > 0:
3293 raise CommandError("--enable-tln not allowed together with --enable-all")
3295 if len(enable_nb) > 0:
3296 raise CommandError("--enable-nb not allowed together with --enable-all")
3298 if len(enable_sid_str) > 0:
3299 raise CommandError("--enable-sid not allowed together with --enable-all")
3301 if len(enable_tln) > 0:
3302 require_update = True
3303 if len(disable_tln) > 0:
3304 require_update = True
3305 for e in enable_tln:
3306 for d in disable_tln:
3307 if e.lower() != d.lower():
3309 raise CommandError("value[%s] specified for --enable-tln and --disable-tln" % e)
3311 if len(add_tln_ex) > 0:
3312 for n in add_tln_ex:
3313 if not n.startswith("*."):
3315 raise CommandError("value[%s] specified for --add-tln-ex should not include with '*.'" % n)
3316 require_update = True
3317 if len(delete_tln_ex) > 0:
3318 for n in delete_tln_ex:
3319 if not n.startswith("*."):
3321 raise CommandError("value[%s] specified for --delete-tln-ex should not include with '*.'" % n)
3322 require_update = True
3323 for a in add_tln_ex:
3324 for d in delete_tln_ex:
3325 if a.lower() != d.lower():
3327 raise CommandError("value[%s] specified for --add-tln-ex and --delete-tln-ex" % a)
3329 if len(enable_nb) > 0:
3330 require_update = True
3331 if len(disable_nb) > 0:
3332 require_update = True
3334 for d in disable_nb:
3335 if e.upper() != d.upper():
3337 raise CommandError("value[%s] specified for --enable-nb and --disable-nb" % e)
3340 for s in enable_sid_str:
3342 sid = security.dom_sid(s)
3343 except TypeError as error:
3344 raise CommandError("value[%s] specified for --enable-sid is not a valid SID" % s)
3345 enable_sid.append(sid)
3347 for s in disable_sid_str:
3349 sid = security.dom_sid(s)
3350 except TypeError as error:
3351 raise CommandError("value[%s] specified for --disable-sid is not a valid SID" % s)
3352 disable_sid.append(sid)
3353 if len(enable_sid) > 0:
3354 require_update = True
3355 if len(disable_sid) > 0:
3356 require_update = True
3357 for e in enable_sid:
3358 for d in disable_sid:
3361 raise CommandError("value[%s] specified for --enable-sid and --disable-sid" % e)
3363 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
3365 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
3367 local_server = self.setup_local_server(sambaopts, localdcopts)
3369 local_lsa = self.new_local_lsa_connection()
3370 except RuntimeError as error:
3371 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
3374 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
3375 except RuntimeError as error:
3376 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
3378 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
3379 local_lsa_info.name.string,
3380 local_lsa_info.dns_domain.string,
3381 local_lsa_info.sid))
3385 local_netlogon = self.new_local_netlogon_connection()
3386 except RuntimeError as error:
3387 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3390 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3391 except RuntimeError as error:
3392 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3394 if local_netlogon_info.domain_name != local_netlogon_info.forest_name:
3395 raise CommandError("The local domain [%s] is not the forest root [%s]" % (
3396 local_netlogon_info.domain_name,
3397 local_netlogon_info.forest_name))
3400 # get all information about our own forest
3401 own_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3403 except RuntimeError as error:
3404 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
3405 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3408 if self.check_runtime_error(error, werror.WERR_INVALID_FUNCTION):
3409 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3412 if self.check_runtime_error(error, werror.WERR_NERR_ACFNOTLOADED):
3413 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3416 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3418 self.outf.write("Own forest trust information...\n")
3419 self.write_forest_trust_info(own_forest_info,
3420 tln=local_lsa_info.dns_domain.string)
3423 local_samdb = self.new_local_ldap_connection()
3424 except RuntimeError as error:
3425 raise self.LocalRuntimeError(self, error, "failed to connect to SamDB")
3427 local_partitions_dn = "CN=Partitions,%s" % str(local_samdb.get_config_basedn())
3428 attrs = ['uPNSuffixes', 'msDS-SPNSuffixes']
3430 msgs = local_samdb.search(base=local_partitions_dn,
3431 scope=ldb.SCOPE_BASE,
3432 expression="(objectClass=crossRefContainer)",
3434 stored_msg = msgs[0]
3435 except ldb.LdbError as error:
3436 raise self.LocalLdbError(self, error, "failed to search partition dn")
3438 stored_upn_vals = []
3439 if 'uPNSuffixes' in stored_msg:
3440 stored_upn_vals.extend(stored_msg['uPNSuffixes'])
3442 stored_spn_vals = []
3443 if 'msDS-SPNSuffixes' in stored_msg:
3444 stored_spn_vals.extend(stored_msg['msDS-SPNSuffixes'])
3446 self.outf.write("Stored uPNSuffixes attributes[%d]:\n" % len(stored_upn_vals))
3447 for v in stored_upn_vals:
3448 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3449 self.outf.write("Stored msDS-SPNSuffixes attributes[%d]:\n" % len(stored_spn_vals))
3450 for v in stored_spn_vals:
3451 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3453 if not require_update:
3457 update_upn_vals = []
3458 update_upn_vals.extend(stored_upn_vals)
3461 update_spn_vals = []
3462 update_spn_vals.extend(stored_spn_vals)
3465 for i, v in enumerate(update_upn_vals):
3466 if str(v).lower() == upn.lower():
3467 raise CommandError("Entry already present for "
3468 "value[%s] specified for "
3469 "--add-upn-suffix" % upn)
3470 update_upn_vals.append(upn)
3473 for upn in delete_upn:
3475 for i, v in enumerate(update_upn_vals):
3476 if str(v).lower() != upn.lower():
3481 raise CommandError("Entry not found for value[%s] specified for --delete-upn-suffix" % upn)
3483 update_upn_vals.pop(idx)
3487 for i, v in enumerate(update_spn_vals):
3488 if str(v).lower() == spn.lower():
3489 raise CommandError("Entry already present for "
3490 "value[%s] specified for "
3491 "--add-spn-suffix" % spn)
3492 update_spn_vals.append(spn)
3495 for spn in delete_spn:
3497 for i, v in enumerate(update_spn_vals):
3498 if str(v).lower() != spn.lower():
3503 raise CommandError("Entry not found for value[%s] specified for --delete-spn-suffix" % spn)
3505 update_spn_vals.pop(idx)
3508 self.outf.write("Update uPNSuffixes attributes[%d]:\n" % len(update_upn_vals))
3509 for v in update_upn_vals:
3510 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3511 self.outf.write("Update msDS-SPNSuffixes attributes[%d]:\n" % len(update_spn_vals))
3512 for v in update_spn_vals:
3513 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3515 update_msg = ldb.Message()
3516 update_msg.dn = stored_msg.dn
3519 update_msg['uPNSuffixes'] = ldb.MessageElement(update_upn_vals,
3520 ldb.FLAG_MOD_REPLACE,
3523 update_msg['msDS-SPNSuffixes'] = ldb.MessageElement(update_spn_vals,
3524 ldb.FLAG_MOD_REPLACE,
3527 local_samdb.modify(update_msg)
3528 except ldb.LdbError as error:
3529 raise self.LocalLdbError(self, error, "failed to update partition dn")
3532 stored_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3534 except RuntimeError as error:
3535 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3537 self.outf.write("Stored forest trust information...\n")
3538 self.write_forest_trust_info(stored_forest_info,
3539 tln=local_lsa_info.dns_domain.string)
3543 lsaString = lsa.String()
3544 lsaString.string = domain
3546 local_lsa.QueryTrustedDomainInfoByName(local_policy,
3548 lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
3549 except NTSTATUSError as error:
3550 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
3551 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
3553 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
3555 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
3556 local_tdo_info.netbios_name.string,
3557 local_tdo_info.domain_name.string,
3558 local_tdo_info.sid))
3560 if not local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
3561 raise CommandError("trusted domain object for domain [%s] is not marked as FOREST_TRANSITIVE." % domain)
3563 if refresh is not None:
3565 local_netlogon = self.new_local_netlogon_connection()
3566 except RuntimeError as error:
3567 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3570 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3571 except RuntimeError as error:
3572 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3574 lsa_update_check = 1
3575 if refresh == "store":
3576 netlogon_update_tdo = netlogon.DS_GFTI_UPDATE_TDO
3578 lsa_update_check = 0
3580 netlogon_update_tdo = 0
3583 # get all information about the remote trust
3584 # this triggers netr_GetForestTrustInformation to the remote domain
3585 # and lsaRSetForestTrustInformation() locally, but new top level
3586 # names are disabled by default.
3587 fresh_forest_info = \
3588 local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3589 local_tdo_info.domain_name.string,
3590 netlogon_update_tdo)
3591 except RuntimeError as error:
3592 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3595 fresh_forest_collision = \
3596 local_lsa.lsaRSetForestTrustInformation(local_policy,
3597 local_tdo_info.domain_name,
3598 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3601 except RuntimeError as error:
3602 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3604 self.outf.write("Fresh forest trust information...\n")
3605 self.write_forest_trust_info(fresh_forest_info,
3606 tln=local_tdo_info.domain_name.string,
3607 collisions=fresh_forest_collision)
3609 if refresh == "store":
3611 lsaString = lsa.String()
3612 lsaString.string = local_tdo_info.domain_name.string
3613 stored_forest_info = \
3614 local_lsa.lsaRQueryForestTrustInformation(local_policy,
3616 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3617 except RuntimeError as error:
3618 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3620 self.outf.write("Stored forest trust information...\n")
3621 self.write_forest_trust_info(stored_forest_info,
3622 tln=local_tdo_info.domain_name.string)
3627 # The none --refresh path
3631 lsaString = lsa.String()
3632 lsaString.string = local_tdo_info.domain_name.string
3633 local_forest_info = \
3634 local_lsa.lsaRQueryForestTrustInformation(local_policy,
3636 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3637 except RuntimeError as error:
3638 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3640 self.outf.write("Local forest trust information...\n")
3641 self.write_forest_trust_info(local_forest_info,
3642 tln=local_tdo_info.domain_name.string)
3644 if not require_update:
3648 entries.extend(local_forest_info.entries)
3649 update_forest_info = lsa.ForestTrustInformation()
3650 update_forest_info.count = len(entries)
3651 update_forest_info.entries = entries
3654 for i, r in enumerate(update_forest_info.entries):
3655 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3657 if update_forest_info.entries[i].flags == 0:
3659 update_forest_info.entries[i].time = 0
3660 update_forest_info.entries[i].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3661 for i, r in enumerate(update_forest_info.entries):
3662 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3664 if update_forest_info.entries[i].flags == 0:
3666 update_forest_info.entries[i].time = 0
3667 update_forest_info.entries[i].flags &= ~lsa.LSA_NB_DISABLED_MASK
3668 update_forest_info.entries[i].flags &= ~lsa.LSA_SID_DISABLED_MASK
3670 for tln in enable_tln:
3672 for i, r in enumerate(update_forest_info.entries):
3673 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3675 if r.forest_trust_data.string.lower() != tln.lower():
3680 raise CommandError("Entry not found for value[%s] specified for --enable-tln" % tln)
3681 if not update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_MASK:
3682 raise CommandError("Entry found for value[%s] specified for --enable-tln is already enabled" % tln)
3683 update_forest_info.entries[idx].time = 0
3684 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3686 for tln in disable_tln:
3688 for i, r in enumerate(update_forest_info.entries):
3689 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3691 if r.forest_trust_data.string.lower() != tln.lower():
3696 raise CommandError("Entry not found for value[%s] specified for --disable-tln" % tln)
3697 if update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_ADMIN:
3698 raise CommandError("Entry found for value[%s] specified for --disable-tln is already disabled" % tln)
3699 update_forest_info.entries[idx].time = 0
3700 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3701 update_forest_info.entries[idx].flags |= lsa.LSA_TLN_DISABLED_ADMIN
3703 for tln_ex in add_tln_ex:
3705 for i, r in enumerate(update_forest_info.entries):
3706 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3708 if r.forest_trust_data.string.lower() != tln_ex.lower():
3713 raise CommandError("Entry already present for value[%s] specified for --add-tln-ex" % tln_ex)
3715 tln_dot = ".%s" % tln_ex.lower()
3717 for i, r in enumerate(update_forest_info.entries):
3718 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3720 r_dot = ".%s" % r.forest_trust_data.string.lower()
3721 if tln_dot == r_dot:
3722 raise CommandError("TLN entry present for value[%s] specified for --add-tln-ex" % tln_ex)
3723 if not tln_dot.endswith(r_dot):
3729 raise CommandError("No TLN parent present for value[%s] specified for --add-tln-ex" % tln_ex)
3731 r = lsa.ForestTrustRecord()
3732 r.type = lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX
3735 r.forest_trust_data.string = tln_ex
3738 entries.extend(update_forest_info.entries)
3739 entries.insert(idx + 1, r)
3740 update_forest_info.count = len(entries)
3741 update_forest_info.entries = entries
3743 for tln_ex in delete_tln_ex:
3745 for i, r in enumerate(update_forest_info.entries):
3746 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3748 if r.forest_trust_data.string.lower() != tln_ex.lower():
3753 raise CommandError("Entry not found for value[%s] specified for --delete-tln-ex" % tln_ex)
3756 entries.extend(update_forest_info.entries)
3758 update_forest_info.count = len(entries)
3759 update_forest_info.entries = entries
3761 for nb in enable_nb:
3763 for i, r in enumerate(update_forest_info.entries):
3764 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3766 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3771 raise CommandError("Entry not found for value[%s] specified for --enable-nb" % nb)
3772 if not update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_MASK:
3773 raise CommandError("Entry found for value[%s] specified for --enable-nb is already enabled" % nb)
3774 update_forest_info.entries[idx].time = 0
3775 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3777 for nb in disable_nb:
3779 for i, r in enumerate(update_forest_info.entries):
3780 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3782 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3787 raise CommandError("Entry not found for value[%s] specified for --delete-nb" % nb)
3788 if update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_ADMIN:
3789 raise CommandError("Entry found for value[%s] specified for --disable-nb is already disabled" % nb)
3790 update_forest_info.entries[idx].time = 0
3791 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3792 update_forest_info.entries[idx].flags |= lsa.LSA_NB_DISABLED_ADMIN
3794 for sid in enable_sid:
3796 for i, r in enumerate(update_forest_info.entries):
3797 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3799 if r.forest_trust_data.domain_sid != sid:
3804 raise CommandError("Entry not found for value[%s] specified for --enable-sid" % sid)
3805 if not update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_MASK:
3806 raise CommandError("Entry found for value[%s] specified for --enable-sid is already enabled" % nb)
3807 update_forest_info.entries[idx].time = 0
3808 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3810 for sid in disable_sid:
3812 for i, r in enumerate(update_forest_info.entries):
3813 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3815 if r.forest_trust_data.domain_sid != sid:
3820 raise CommandError("Entry not found for value[%s] specified for --delete-sid" % sid)
3821 if update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_ADMIN:
3822 raise CommandError("Entry found for value[%s] specified for --disable-sid is already disabled" % nb)
3823 update_forest_info.entries[idx].time = 0
3824 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3825 update_forest_info.entries[idx].flags |= lsa.LSA_SID_DISABLED_ADMIN
3828 update_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3829 local_tdo_info.domain_name,
3830 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3831 update_forest_info, 0)
3832 except RuntimeError as error:
3833 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3835 self.outf.write("Updated forest trust information...\n")
3836 self.write_forest_trust_info(update_forest_info,
3837 tln=local_tdo_info.domain_name.string,
3838 collisions=update_forest_collision)
3841 lsaString = lsa.String()
3842 lsaString.string = local_tdo_info.domain_name.string
3843 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3845 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3846 except RuntimeError as error:
3847 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3849 self.outf.write("Stored forest trust information...\n")
3850 self.write_forest_trust_info(stored_forest_info,
3851 tln=local_tdo_info.domain_name.string)
3855 class cmd_domain_tombstones_expunge(Command):
3856 """Expunge tombstones from the database.
3858 This command expunges tombstones from the database."""
3859 synopsis = "%prog NC [NC [...]] [options]"
3862 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3863 metavar="URL", dest="H"),
3864 Option("--current-time",
3865 help="The current time to evaluate the tombstone lifetime from, expressed as YYYY-MM-DD",
3867 Option("--tombstone-lifetime", help="Number of days a tombstone should be preserved for", type=int),
3870 takes_args = ["nc*"]
3872 takes_optiongroups = {
3873 "sambaopts": options.SambaOptions,
3874 "credopts": options.CredentialsOptions,
3875 "versionopts": options.VersionOptions,
3878 def run(self, *ncs, **kwargs):
3879 sambaopts = kwargs.get("sambaopts")
3880 credopts = kwargs.get("credopts")
3882 current_time_string = kwargs.get("current_time")
3883 tombstone_lifetime = kwargs.get("tombstone_lifetime")
3884 lp = sambaopts.get_loadparm()
3885 creds = credopts.get_credentials(lp)
3886 samdb = SamDB(url=H, session_info=system_session(),
3887 credentials=creds, lp=lp)
3889 if current_time_string is not None:
3890 current_time_obj = time.strptime(current_time_string, "%Y-%m-%d")
3891 current_time = int(time.mktime(current_time_obj))
3894 current_time = int(time.time())
3897 res = samdb.search(expression="", base="", scope=ldb.SCOPE_BASE,
3898 attrs=["namingContexts"])
3901 for nc in res[0]["namingContexts"]:
3906 started_transaction = False
3908 samdb.transaction_start()
3909 started_transaction = True
3911 removed_links) = samdb.garbage_collect_tombstones(ncs,
3912 current_time=current_time,
3913 tombstone_lifetime=tombstone_lifetime)
3915 except Exception as err:
3916 if started_transaction:
3917 samdb.transaction_cancel()
3918 raise CommandError("Failed to expunge / garbage collect tombstones", err)
3920 samdb.transaction_commit()
3922 self.outf.write("Removed %d objects and %d links successfully\n"
3923 % (removed_objects, removed_links))
3926 class cmd_domain_trust(SuperCommand):
3927 """Domain and forest trust management."""
3930 subcommands["list"] = cmd_domain_trust_list()
3931 subcommands["show"] = cmd_domain_trust_show()
3932 subcommands["create"] = cmd_domain_trust_create()
3933 subcommands["delete"] = cmd_domain_trust_delete()
3934 subcommands["validate"] = cmd_domain_trust_validate()
3935 subcommands["namespaces"] = cmd_domain_trust_namespaces()
3938 class cmd_domain_tombstones(SuperCommand):
3939 """Domain tombstone and recycled object management."""
3942 subcommands["expunge"] = cmd_domain_tombstones_expunge()
3945 class ldif_schema_update:
3946 """Helper class for applying LDIF schema updates"""
3949 self.is_defunct = False
3950 self.unknown_oid = None
3954 def can_ignore_failure(self, error):
3955 """Checks if we can safely ignore failure to apply an LDIF update"""
3956 (num, errstr) = error.args
3958 # Microsoft has marked objects as defunct that Samba doesn't know about
3959 if num == ldb.ERR_NO_SUCH_OBJECT and self.is_defunct:
3960 print("Defunct object %s doesn't exist, skipping" % self.dn)
3962 elif self.unknown_oid is not None:
3963 print("Skipping unknown OID %s for object %s" % (self.unknown_oid, self.dn))
3968 def apply(self, samdb):
3969 """Applies a single LDIF update to the schema"""
3973 samdb.modify_ldif(self.ldif, controls=['relax:0'])
3974 except ldb.LdbError as e:
3975 if e.args[0] == ldb.ERR_INVALID_ATTRIBUTE_SYNTAX:
3977 # REFRESH after a failed change
3979 # Otherwise the OID-to-attribute mapping in
3980 # _apply_updates_in_file() won't work, because it
3981 # can't lookup the new OID in the schema
3982 samdb.set_schema_update_now()
3984 samdb.modify_ldif(self.ldif, controls=['relax:0'])
3987 except ldb.LdbError as e:
3988 if self.can_ignore_failure(e):
3991 print("Exception: %s" % e)
3992 print("Encountered while trying to apply the following LDIF")
3993 print("----------------------------------------------------")
3994 print("%s" % self.ldif)
4001 class cmd_domain_schema_upgrade(Command):
4002 """Domain schema upgrading"""
4004 synopsis = "%prog [options]"
4006 takes_optiongroups = {
4007 "sambaopts": options.SambaOptions,
4008 "versionopts": options.VersionOptions,
4009 "credopts": options.CredentialsOptions,
4013 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
4014 metavar="URL", dest="H"),
4015 Option("-q", "--quiet", help="Be quiet", action="store_true"), # unused
4016 Option("-v", "--verbose", help="Be verbose", action="store_true"),
4017 Option("--schema", type="choice", metavar="SCHEMA",
4018 choices=["2012", "2012_R2"],
4019 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
4021 Option("--ldf-file", type=str, default=None,
4022 help="Just apply the schema updates in the adprep/.LDF file(s) specified"),
4023 Option("--base-dir", type=str, default=None,
4024 help="Location of ldf files Default is ${SETUPDIR}/adprep.")
4027 def _apply_updates_in_file(self, samdb, ldif_file):
4029 Applies a series of updates specified in an .LDIF file. The .LDIF file
4030 is based on the adprep Schema updates provided by Microsoft.
4033 ldif_op = ldif_schema_update()
4035 # parse the file line by line and work out each update operation to apply
4036 for line in ldif_file:
4038 line = line.rstrip()
4040 # the operations in the .LDIF file are separated by blank lines. If
4041 # we hit a blank line, try to apply the update we've parsed so far
4044 # keep going if we haven't parsed anything yet
4045 if ldif_op.ldif == '':
4048 # Apply the individual change
4049 count += ldif_op.apply(samdb)
4051 # start storing the next operation from scratch again
4052 ldif_op = ldif_schema_update()
4055 # replace the placeholder domain name in the .ldif file with the real domain
4056 if line.upper().endswith('DC=X'):
4057 line = line[:-len('DC=X')] + str(samdb.get_default_basedn())
4058 elif line.upper().endswith('CN=X'):
4059 line = line[:-len('CN=X')] + str(samdb.get_default_basedn())
4061 values = line.split(':')
4063 if values[0].lower() == 'dn':
4064 ldif_op.dn = values[1].strip()
4066 # replace the Windows-specific operation with the Samba one
4067 if values[0].lower() == 'changetype':
4068 line = line.lower().replace(': ntdsschemaadd',
4070 line = line.lower().replace(': ntdsschemamodify',
4073 if values[0].lower() in ['rdnattid', 'subclassof',
4074 'systemposssuperiors',
4076 'systemauxiliaryclass']:
4079 # The Microsoft updates contain some OIDs we don't recognize.
4080 # Query the DB to see if we can work out the OID this update is
4081 # referring to. If we find a match, then replace the OID with
4082 # the ldapDisplayname
4084 res = samdb.search(base=samdb.get_schema_basedn(),
4085 expression="(|(attributeId=%s)(governsId=%s))" %
4087 attrs=['ldapDisplayName'])
4090 ldif_op.unknown_oid = value
4092 display_name = str(res[0]['ldapDisplayName'][0])
4093 line = line.replace(value, ' ' + display_name)
4095 # Microsoft has marked objects as defunct that Samba doesn't know about
4096 if values[0].lower() == 'isdefunct' and values[1].strip().lower() == 'true':
4097 ldif_op.is_defunct = True
4099 # Samba has added the showInAdvancedViewOnly attribute to all objects,
4100 # so rather than doing an add, we need to do a replace
4101 if values[0].lower() == 'add' and values[1].strip().lower() == 'showinadvancedviewonly':
4102 line = 'replace: showInAdvancedViewOnly'
4104 # Add the line to the current LDIF operation (including the newline
4105 # we stripped off at the start of the loop)
4106 ldif_op.ldif += line + '\n'
4110 def _apply_update(self, samdb, update_file, base_dir):
4111 """Wrapper function for parsing an LDIF file and applying the updates"""
4113 print("Applying %s updates..." % update_file)
4117 ldif_file = open(os.path.join(base_dir, update_file))
4119 count = self._apply_updates_in_file(samdb, ldif_file)
4125 print("%u changes applied" % count)
4129 def run(self, **kwargs):
4130 from samba.ms_schema_markdown import read_ms_markdown
4131 from samba.schema import Schema
4133 updates_allowed_overriden = False
4134 sambaopts = kwargs.get("sambaopts")
4135 credopts = kwargs.get("credopts")
4136 lp = sambaopts.get_loadparm()
4137 creds = credopts.get_credentials(lp)
4139 target_schema = kwargs.get("schema")
4140 ldf_files = kwargs.get("ldf_file")
4141 base_dir = kwargs.get("base_dir")
4145 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4147 # we're not going to get far if the config doesn't allow schema updates
4148 if lp.get("dsdb:schema update allowed") is None:
4149 lp.set("dsdb:schema update allowed", "yes")
4150 print("Temporarily overriding 'dsdb:schema update allowed' setting")
4151 updates_allowed_overriden = True
4153 own_dn = ldb.Dn(samdb, samdb.get_dsServiceName())
4154 master = get_fsmo_roleowner(samdb, str(samdb.get_schema_basedn()),
4156 if own_dn != master:
4157 raise CommandError("This server is not the schema master.")
4159 # if specific LDIF files were specified, just apply them
4161 schema_updates = ldf_files.split(",")
4165 # work out the version of the target schema we're upgrading to
4166 end = Schema.get_version(target_schema)
4168 # work out the version of the schema we're currently using
4169 res = samdb.search(base=samdb.get_schema_basedn(),
4170 scope=ldb.SCOPE_BASE, attrs=['objectVersion'])
4173 raise CommandError('Could not determine current schema version')
4174 start = int(res[0]['objectVersion'][0]) + 1
4176 diff_dir = setup_path("adprep/WindowsServerDocs")
4177 if base_dir is None:
4178 # Read from the Schema-Updates.md file
4179 temp_folder = tempfile.mkdtemp()
4181 update_file = setup_path("adprep/WindowsServerDocs/Schema-Updates.md")
4184 read_ms_markdown(update_file, temp_folder)
4185 except Exception as e:
4186 print("Exception in markdown parsing: %s" % e)
4187 shutil.rmtree(temp_folder)
4188 raise CommandError('Failed to upgrade schema')
4190 base_dir = temp_folder
4192 for version in range(start, end + 1):
4193 update = 'Sch%d.ldf' % version
4194 schema_updates.append(update)
4196 # Apply patches if we parsed the Schema-Updates.md file
4197 diff = os.path.abspath(os.path.join(diff_dir, update + '.diff'))
4198 if temp_folder and os.path.exists(diff):
4200 p = subprocess.Popen(['patch', update, '-i', diff],
4201 stdout=subprocess.PIPE,
4202 stderr=subprocess.PIPE, cwd=temp_folder)
4203 except (OSError, IOError):
4204 shutil.rmtree(temp_folder)
4205 raise CommandError("Failed to upgrade schema. Check if 'patch' is installed.")
4207 stdout, stderr = p.communicate()
4210 print("Exception in patch: %s\n%s" % (stdout, stderr))
4211 shutil.rmtree(temp_folder)
4212 raise CommandError('Failed to upgrade schema')
4214 print("Patched %s using %s" % (update, diff))
4216 if base_dir is None:
4217 base_dir = setup_path("adprep")
4219 samdb.transaction_start()
4221 error_encountered = False
4224 # Apply the schema updates needed to move to the new schema version
4225 for ldif_file in schema_updates:
4226 count += self._apply_update(samdb, ldif_file, base_dir)
4229 samdb.transaction_commit()
4230 print("Schema successfully updated")
4232 print("No changes applied to schema")
4233 samdb.transaction_cancel()
4234 except Exception as e:
4235 print("Exception: %s" % e)
4236 print("Error encountered, aborting schema upgrade")
4237 samdb.transaction_cancel()
4238 error_encountered = True
4240 if updates_allowed_overriden:
4241 lp.set("dsdb:schema update allowed", "no")
4244 shutil.rmtree(temp_folder)
4246 if error_encountered:
4247 raise CommandError('Failed to upgrade schema')
4250 class cmd_domain_functional_prep(Command):
4251 """Domain functional level preparation"""
4253 synopsis = "%prog [options]"
4255 takes_optiongroups = {
4256 "sambaopts": options.SambaOptions,
4257 "versionopts": options.VersionOptions,
4258 "credopts": options.CredentialsOptions,
4262 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
4263 metavar="URL", dest="H"),
4264 Option("-q", "--quiet", help="Be quiet", action="store_true"),
4265 Option("-v", "--verbose", help="Be verbose", action="store_true"),
4266 Option("--function-level", type="choice", metavar="FUNCTION_LEVEL",
4267 choices=["2008_R2", "2012", "2012_R2"],
4268 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
4270 Option("--forest-prep", action="store_true",
4271 help="Run the forest prep (by default, both the domain and forest prep are run)."),
4272 Option("--domain-prep", action="store_true",
4273 help="Run the domain prep (by default, both the domain and forest prep are run).")
4276 def run(self, **kwargs):
4277 updates_allowed_overriden = False
4278 sambaopts = kwargs.get("sambaopts")
4279 credopts = kwargs.get("credopts")
4280 lp = sambaopts.get_loadparm()
4281 creds = credopts.get_credentials(lp)
4283 target_level = string_version_to_constant[kwargs.get("function_level")]
4284 forest_prep = kwargs.get("forest_prep")
4285 domain_prep = kwargs.get("domain_prep")
4287 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4289 # we're not going to get far if the config doesn't allow schema updates
4290 if lp.get("dsdb:schema update allowed") is None:
4291 lp.set("dsdb:schema update allowed", "yes")
4292 print("Temporarily overriding 'dsdb:schema update allowed' setting")
4293 updates_allowed_overriden = True
4295 if forest_prep is None and domain_prep is None:
4299 own_dn = ldb.Dn(samdb, samdb.get_dsServiceName())
4301 master = get_fsmo_roleowner(samdb, str(samdb.get_schema_basedn()),
4303 if own_dn != master:
4304 raise CommandError("This server is not the schema master.")
4307 domain_dn = samdb.domain_dn()
4308 infrastructure_dn = "CN=Infrastructure," + domain_dn
4309 master = get_fsmo_roleowner(samdb, infrastructure_dn,
4311 if own_dn != master:
4312 raise CommandError("This server is not the infrastructure master.")
4315 samdb.transaction_start()
4316 error_encountered = False
4318 from samba.forest_update import ForestUpdate
4319 forest = ForestUpdate(samdb, fix=True)
4321 forest.check_updates_iterator([53, 79, 80, 81, 82, 83])
4322 forest.check_updates_functional_level(target_level,
4323 DS_DOMAIN_FUNCTION_2008_R2,
4324 update_revision=True)
4326 samdb.transaction_commit()
4327 except Exception as e:
4328 print("Exception: %s" % e)
4329 samdb.transaction_cancel()
4330 error_encountered = True
4333 samdb.transaction_start()
4334 error_encountered = False
4336 from samba.domain_update import DomainUpdate
4338 domain = DomainUpdate(samdb, fix=True)
4339 domain.check_updates_functional_level(target_level,
4340 DS_DOMAIN_FUNCTION_2008,
4341 update_revision=True)
4343 samdb.transaction_commit()
4344 except Exception as e:
4345 print("Exception: %s" % e)
4346 samdb.transaction_cancel()
4347 error_encountered = True
4349 if updates_allowed_overriden:
4350 lp.set("dsdb:schema update allowed", "no")
4352 if error_encountered:
4353 raise CommandError('Failed to perform functional prep')
4356 class cmd_domain(SuperCommand):
4357 """Domain management."""
4360 subcommands["demote"] = cmd_domain_demote()
4361 if cmd_domain_export_keytab is not None:
4362 subcommands["exportkeytab"] = cmd_domain_export_keytab()
4363 subcommands["info"] = cmd_domain_info()
4364 subcommands["provision"] = cmd_domain_provision()
4365 subcommands["join"] = cmd_domain_join()
4366 subcommands["dcpromo"] = cmd_domain_dcpromo()
4367 subcommands["level"] = cmd_domain_level()
4368 subcommands["passwordsettings"] = cmd_domain_passwordsettings()
4369 subcommands["classicupgrade"] = cmd_domain_classicupgrade()
4370 subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()
4371 subcommands["trust"] = cmd_domain_trust()
4372 subcommands["tombstones"] = cmd_domain_tombstones()
4373 subcommands["schemaupgrade"] = cmd_domain_schema_upgrade()
4374 subcommands["functionalprep"] = cmd_domain_functional_prep()
4375 subcommands["backup"] = cmd_domain_backup()