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 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
47 from samba.ndr import ndr_unpack, 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 (
68 sendDsReplicaSync, drsuapi_connect, drsException,
70 from samba import remove_dc, arcfour_encrypt, string_to_byte_array
72 from samba.dsdb import (
73 DS_DOMAIN_FUNCTION_2000,
74 DS_DOMAIN_FUNCTION_2003,
75 DS_DOMAIN_FUNCTION_2003_MIXED,
76 DS_DOMAIN_FUNCTION_2008,
77 DS_DOMAIN_FUNCTION_2008_R2,
78 DS_DOMAIN_FUNCTION_2012,
79 DS_DOMAIN_FUNCTION_2012_R2,
80 DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL,
81 DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL,
82 UF_WORKSTATION_TRUST_ACCOUNT,
83 UF_SERVER_TRUST_ACCOUNT,
84 UF_TRUSTED_FOR_DELEGATION,
85 UF_PARTIAL_SECRETS_ACCOUNT
88 from samba.provision import (
91 DEFAULT_MIN_PWD_LENGTH,
95 from samba.provision.common import (
101 string_version_to_constant = {
102 "2008_R2" : DS_DOMAIN_FUNCTION_2008_R2,
103 "2012": DS_DOMAIN_FUNCTION_2012,
104 "2012_R2": DS_DOMAIN_FUNCTION_2012_R2,
107 def get_testparm_var(testparm, smbconf, varname):
108 errfile = open(os.devnull, 'w')
109 p = subprocess.Popen([testparm, '-s', '-l',
110 '--parameter-name=%s' % varname, smbconf],
111 stdout=subprocess.PIPE, stderr=errfile)
112 (out,err) = p.communicate()
114 lines = out.split('\n')
116 return lines[0].strip()
120 import samba.dckeytab
122 cmd_domain_export_keytab = None
124 class cmd_domain_export_keytab(Command):
125 """Dump Kerberos keys of the domain into a keytab."""
127 synopsis = "%prog <keytab> [options]"
129 takes_optiongroups = {
130 "sambaopts": options.SambaOptions,
131 "credopts": options.CredentialsOptions,
132 "versionopts": options.VersionOptions,
136 Option("--principal", help="extract only this principal", type=str),
139 takes_args = ["keytab"]
141 def run(self, keytab, credopts=None, sambaopts=None, versionopts=None, principal=None):
142 lp = sambaopts.get_loadparm()
144 net.export_keytab(keytab=keytab, principal=principal)
147 class cmd_domain_info(Command):
148 """Print basic info about a domain and the DC passed as parameter."""
150 synopsis = "%prog <ip_address> [options]"
155 takes_optiongroups = {
156 "sambaopts": options.SambaOptions,
157 "credopts": options.CredentialsOptions,
158 "versionopts": options.VersionOptions,
161 takes_args = ["address"]
163 def run(self, address, credopts=None, sambaopts=None, versionopts=None):
164 lp = sambaopts.get_loadparm()
166 res = netcmd_get_domain_infos_via_cldap(lp, None, address)
168 raise CommandError("Invalid IP address '" + address + "'!")
169 self.outf.write("Forest : %s\n" % res.forest)
170 self.outf.write("Domain : %s\n" % res.dns_domain)
171 self.outf.write("Netbios domain : %s\n" % res.domain_name)
172 self.outf.write("DC name : %s\n" % res.pdc_dns_name)
173 self.outf.write("DC netbios name : %s\n" % res.pdc_name)
174 self.outf.write("Server site : %s\n" % res.server_site)
175 self.outf.write("Client site : %s\n" % res.client_site)
178 class cmd_domain_provision(Command):
179 """Provision a domain."""
181 synopsis = "%prog [options]"
183 takes_optiongroups = {
184 "sambaopts": options.SambaOptions,
185 "versionopts": options.VersionOptions,
189 Option("--interactive", help="Ask for names", action="store_true"),
190 Option("--domain", type="string", metavar="DOMAIN",
191 help="NetBIOS domain name to use"),
192 Option("--domain-guid", type="string", metavar="GUID",
193 help="set domainguid (otherwise random)"),
194 Option("--domain-sid", type="string", metavar="SID",
195 help="set domainsid (otherwise random)"),
196 Option("--ntds-guid", type="string", metavar="GUID",
197 help="set NTDS object GUID (otherwise random)"),
198 Option("--invocationid", type="string", metavar="GUID",
199 help="set invocationid (otherwise random)"),
200 Option("--host-name", type="string", metavar="HOSTNAME",
201 help="set hostname"),
202 Option("--host-ip", type="string", metavar="IPADDRESS",
203 help="set IPv4 ipaddress"),
204 Option("--host-ip6", type="string", metavar="IP6ADDRESS",
205 help="set IPv6 ipaddress"),
206 Option("--site", type="string", metavar="SITENAME",
207 help="set site name"),
208 Option("--adminpass", type="string", metavar="PASSWORD",
209 help="choose admin password (otherwise random)"),
210 Option("--krbtgtpass", type="string", metavar="PASSWORD",
211 help="choose krbtgt password (otherwise random)"),
212 Option("--machinepass", type="string", metavar="PASSWORD",
213 help="choose machine password (otherwise random)"),
214 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
215 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
216 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
217 "BIND9_FLATFILE uses bind9 text database to store zone information, "
218 "BIND9_DLZ uses samba4 AD to store zone information, "
219 "NONE skips the DNS setup entirely (not recommended)",
220 default="SAMBA_INTERNAL"),
221 Option("--dnspass", type="string", metavar="PASSWORD",
222 help="choose dns password (otherwise random)"),
223 Option("--ldapadminpass", type="string", metavar="PASSWORD",
224 help="choose password to set between Samba and its LDAP backend (otherwise random)"),
225 Option("--root", type="string", metavar="USERNAME",
226 help="choose 'root' unix username"),
227 Option("--nobody", type="string", metavar="USERNAME",
228 help="choose 'nobody' user"),
229 Option("--users", type="string", metavar="GROUPNAME",
230 help="choose 'users' group"),
231 Option("--quiet", help="Be quiet", action="store_true"),
232 Option("--blank", action="store_true",
233 help="do not add users or groups, just the structure"),
234 Option("--ldap-backend-type", type="choice", metavar="LDAP-BACKEND-TYPE",
235 help="Test initialisation support for unsupported LDAP backend type (fedora-ds or openldap) DO NOT USE",
236 choices=["fedora-ds", "openldap"]),
237 Option("--server-role", type="choice", metavar="ROLE",
238 choices=["domain controller", "dc", "member server", "member", "standalone"],
239 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
240 default="domain controller"),
241 Option("--function-level", type="choice", metavar="FOR-FUN-LEVEL",
242 choices=["2000", "2003", "2008", "2008_R2"],
243 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2008_R2 Native.",
245 Option("--base-schema", type="choice", metavar="BASE-SCHEMA",
246 choices=["2008_R2", "2008_R2_old", "2012", "2012_R2"],
247 help="The base schema files to use. Default is (Windows) 2008_R2.",
249 Option("--next-rid", type="int", metavar="NEXTRID", default=1000,
250 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
251 Option("--partitions-only",
252 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"),
253 Option("--targetdir", type="string", metavar="DIR",
254 help="Set target directory"),
255 Option("--ol-mmr-urls", type="string", metavar="LDAPSERVER",
256 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\""),
257 Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
258 Option("--plaintext-secrets", action="store_true",
259 help="Store secret/sensitive values as plain text on disk" +
260 "(default is to encrypt secret/ensitive values)"),
264 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",
265 action="store_true"),
266 Option("--slapd-path", type="string", metavar="SLAPD-PATH",
267 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."),
268 Option("--ldap-backend-extra-port", type="int", metavar="LDAP-BACKEND-EXTRA-PORT", help="Additional TCP port for LDAP backend server (to use for replication)"),
269 Option("--ldap-backend-forced-uri", type="string", metavar="LDAP-BACKEND-FORCED-URI",
270 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"),
271 Option("--ldap-backend-nosync", help="Configure LDAP backend not to call fsync() (for performance in test environments)", action="store_true"),
275 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
276 Option("--use-xattrs", type="choice", choices=["yes","no","auto"],
277 metavar="[yes|no|auto]",
278 help="Define if we should use the native fs capabilities or a tdb file for "
279 "storing attributes likes ntacl when --use-ntvfs is set. "
280 "auto tries to make an inteligent guess based on the user rights and system capabilities",
284 if os.getenv('TEST_LDAP', "no") == "yes":
285 takes_options.extend(openldap_options)
287 if samba.is_ntvfs_fileserver_built():
288 takes_options.extend(ntvfs_options)
292 def run(self, sambaopts=None, versionopts=None,
315 ldap_backend_type=None,
319 partitions_only=None,
326 ldap_backend_nosync=None,
327 ldap_backend_extra_port=None,
328 ldap_backend_forced_uri=None,
329 ldap_dryrun_mode=None,
331 plaintext_secrets=False):
333 self.logger = self.get_logger("provision")
335 self.logger.setLevel(logging.WARNING)
337 self.logger.setLevel(logging.INFO)
339 lp = sambaopts.get_loadparm()
340 smbconf = lp.configfile
342 if dns_forwarder is not None:
343 suggested_forwarder = dns_forwarder
345 suggested_forwarder = self._get_nameserver_ip()
346 if suggested_forwarder is None:
347 suggested_forwarder = "none"
349 if len(self.raw_argv) == 1:
353 from getpass import getpass
356 def ask(prompt, default=None):
357 if default is not None:
358 print("%s [%s]: " % (prompt, default), end=' ')
360 print("%s: " % (prompt,), end=' ')
361 return sys.stdin.readline().rstrip("\n") or default
364 default = socket.getfqdn().split(".", 1)[1].upper()
367 realm = ask("Realm", default)
368 if realm in (None, ""):
369 raise CommandError("No realm set!")
372 default = realm.split(".")[0]
375 domain = ask("Domain", default)
377 raise CommandError("No domain set!")
379 server_role = ask("Server Role (dc, member, standalone)", "dc")
381 dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
382 if dns_backend in (None, ''):
383 raise CommandError("No DNS backend set!")
385 if dns_backend == "SAMBA_INTERNAL":
386 dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
387 if dns_forwarder.lower() in (None, 'none'):
388 suggested_forwarder = None
392 adminpassplain = getpass("Administrator password: ")
393 issue = self._adminpass_issue(adminpassplain)
395 self.errf.write("%s.\n" % issue)
397 adminpassverify = getpass("Retype password: ")
398 if not adminpassplain == adminpassverify:
399 self.errf.write("Sorry, passwords do not match.\n")
401 adminpass = adminpassplain
405 realm = sambaopts._lp.get('realm')
407 raise CommandError("No realm set!")
409 raise CommandError("No domain set!")
412 issue = self._adminpass_issue(adminpass)
414 raise CommandError(issue)
416 self.logger.info("Administrator password will be set randomly!")
418 if function_level == "2000":
419 dom_for_fun_level = DS_DOMAIN_FUNCTION_2000
420 elif function_level == "2003":
421 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
422 elif function_level == "2008":
423 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
424 elif function_level == "2008_R2":
425 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
427 if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
428 dns_forwarder = suggested_forwarder
430 samdb_fill = FILL_FULL
432 samdb_fill = FILL_NT4SYNC
433 elif partitions_only:
434 samdb_fill = FILL_DRS
436 if targetdir is not None:
437 if not os.path.isdir(targetdir):
442 if use_xattrs == "yes":
444 elif use_xattrs == "auto" and use_ntvfs == False:
446 elif use_ntvfs == False:
447 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
448 "Please re-run with --use-xattrs omitted.")
449 elif use_xattrs == "auto" and not lp.get("posix:eadb"):
451 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
453 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
456 samba.ntacls.setntacl(lp, file.name,
457 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
460 self.logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. ")
465 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.")
466 if ldap_backend_type == "existing":
467 if ldap_backend_forced_uri is not None:
468 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)
470 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")
472 if ldap_backend_forced_uri is not None:
473 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")
475 if domain_sid is not None:
476 domain_sid = security.dom_sid(domain_sid)
478 session = system_session()
480 result = provision(self.logger,
481 session, smbconf=smbconf, targetdir=targetdir,
482 samdb_fill=samdb_fill, realm=realm, domain=domain,
483 domainguid=domain_guid, domainsid=domain_sid,
485 hostip=host_ip, hostip6=host_ip6,
486 sitename=site, ntdsguid=ntds_guid,
487 invocationid=invocationid, adminpass=adminpass,
488 krbtgtpass=krbtgtpass, machinepass=machinepass,
489 dns_backend=dns_backend, dns_forwarder=dns_forwarder,
490 dnspass=dnspass, root=root, nobody=nobody,
492 serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
493 backend_type=ldap_backend_type,
494 ldapadminpass=ldapadminpass, ol_mmr_urls=ol_mmr_urls, slapd_path=slapd_path,
495 useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
496 use_rfc2307=use_rfc2307, skip_sysvolacl=False,
497 ldap_backend_extra_port=ldap_backend_extra_port,
498 ldap_backend_forced_uri=ldap_backend_forced_uri,
499 nosync=ldap_backend_nosync, ldap_dryrun_mode=ldap_dryrun_mode,
500 base_schema=base_schema,
501 plaintext_secrets=plaintext_secrets)
503 except ProvisioningError as e:
504 raise CommandError("Provision failed", e)
506 result.report_logger(self.logger)
508 def _get_nameserver_ip(self):
509 """Grab the nameserver IP address from /etc/resolv.conf."""
511 RESOLV_CONF="/etc/resolv.conf"
513 if not path.isfile(RESOLV_CONF):
514 self.logger.warning("Failed to locate %s" % RESOLV_CONF)
519 handle = open(RESOLV_CONF, 'r')
521 if not line.startswith('nameserver'):
523 # we want the last non-space continuous string of the line
524 return line.strip().split()[-1]
526 if handle is not None:
529 self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
531 def _adminpass_issue(self, adminpass):
532 """Returns error string for a bad administrator password,
533 or None if acceptable"""
535 if len(adminpass.decode('utf-8')) < DEFAULT_MIN_PWD_LENGTH:
536 return "Administrator password does not meet the default minimum" \
537 " password length requirement (%d characters)" \
538 % DEFAULT_MIN_PWD_LENGTH
539 elif not samba.check_password_quality(adminpass):
540 return "Administrator password does not meet the default" \
546 class cmd_domain_dcpromo(Command):
547 """Promote an existing domain member or NT4 PDC to an AD DC."""
549 synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
551 takes_optiongroups = {
552 "sambaopts": options.SambaOptions,
553 "versionopts": options.VersionOptions,
554 "credopts": options.CredentialsOptions,
558 Option("--server", help="DC to join", type=str),
559 Option("--site", help="site to join", type=str),
560 Option("--targetdir", help="where to store provision", type=str),
561 Option("--domain-critical-only",
562 help="only replicate critical domain objects",
563 action="store_true"),
564 Option("--machinepass", type=str, metavar="PASSWORD",
565 help="choose machine password (otherwise random)"),
566 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
567 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
568 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
569 "BIND9_DLZ uses samba4 AD to store zone information, "
570 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
571 default="SAMBA_INTERNAL"),
572 Option("--quiet", help="Be quiet", action="store_true"),
573 Option("--verbose", help="Be verbose", action="store_true")
577 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
580 if samba.is_ntvfs_fileserver_built():
581 takes_options.extend(ntvfs_options)
584 takes_args = ["domain", "role?"]
586 def run(self, domain, role=None, sambaopts=None, credopts=None,
587 versionopts=None, server=None, site=None, targetdir=None,
588 domain_critical_only=False, parent_domain=None, machinepass=None,
589 use_ntvfs=False, dns_backend=None,
590 quiet=False, verbose=False):
591 lp = sambaopts.get_loadparm()
592 creds = credopts.get_credentials(lp)
593 net = Net(creds, lp, server=credopts.ipaddress)
595 logger = self.get_logger()
597 logger.setLevel(logging.DEBUG)
599 logger.setLevel(logging.WARNING)
601 logger.setLevel(logging.INFO)
603 netbios_name = lp.get("netbios name")
609 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
610 site=site, netbios_name=netbios_name, targetdir=targetdir,
611 domain_critical_only=domain_critical_only,
612 machinepass=machinepass, use_ntvfs=use_ntvfs,
613 dns_backend=dns_backend,
614 promote_existing=True)
616 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
617 site=site, netbios_name=netbios_name, targetdir=targetdir,
618 domain_critical_only=domain_critical_only,
619 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
620 promote_existing=True)
622 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
625 class cmd_domain_join(Command):
626 """Join domain as either member or backup domain controller."""
628 synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
630 takes_optiongroups = {
631 "sambaopts": options.SambaOptions,
632 "versionopts": options.VersionOptions,
633 "credopts": options.CredentialsOptions,
637 Option("--server", help="DC to join", type=str),
638 Option("--site", help="site to join", type=str),
639 Option("--targetdir", help="where to store provision", type=str),
640 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
641 Option("--domain-critical-only",
642 help="only replicate critical domain objects",
643 action="store_true"),
644 Option("--machinepass", type=str, metavar="PASSWORD",
645 help="choose machine password (otherwise random)"),
646 Option("--adminpass", type="string", metavar="PASSWORD",
647 help="choose adminstrator password when joining as a subdomain (otherwise random)"),
648 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
649 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
650 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
651 "BIND9_DLZ uses samba4 AD to store zone information, "
652 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
653 default="SAMBA_INTERNAL"),
654 Option("--plaintext-secrets", action="store_true",
655 help="Store secret/sensitive values as plain text on disk" +
656 "(default is to encrypt secret/ensitive values)"),
657 Option("--quiet", help="Be quiet", action="store_true"),
658 Option("--verbose", help="Be verbose", action="store_true")
662 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
665 if samba.is_ntvfs_fileserver_built():
666 takes_options.extend(ntvfs_options)
668 takes_args = ["domain", "role?"]
670 def run(self, domain, role=None, sambaopts=None, credopts=None,
671 versionopts=None, server=None, site=None, targetdir=None,
672 domain_critical_only=False, parent_domain=None, machinepass=None,
673 use_ntvfs=False, dns_backend=None, adminpass=None,
674 quiet=False, verbose=False, plaintext_secrets=False):
675 lp = sambaopts.get_loadparm()
676 creds = credopts.get_credentials(lp)
677 net = Net(creds, lp, server=credopts.ipaddress)
680 site = "Default-First-Site-Name"
682 logger = self.get_logger()
684 logger.setLevel(logging.DEBUG)
686 logger.setLevel(logging.WARNING)
688 logger.setLevel(logging.INFO)
690 netbios_name = lp.get("netbios name")
695 if role is None or role == "MEMBER":
696 (join_password, sid, domain_name) = net.join_member(
697 domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
698 machinepass=machinepass)
700 self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
702 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
703 site=site, netbios_name=netbios_name, targetdir=targetdir,
704 domain_critical_only=domain_critical_only,
705 machinepass=machinepass, use_ntvfs=use_ntvfs,
706 dns_backend=dns_backend,
707 plaintext_secrets=plaintext_secrets)
709 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
710 site=site, netbios_name=netbios_name, targetdir=targetdir,
711 domain_critical_only=domain_critical_only,
712 machinepass=machinepass, use_ntvfs=use_ntvfs,
713 dns_backend=dns_backend,
714 plaintext_secrets=plaintext_secrets)
715 elif role == "SUBDOMAIN":
717 logger.info("Administrator password will be set randomly!")
719 netbios_domain = lp.get("workgroup")
720 if parent_domain is None:
721 parent_domain = ".".join(domain.split(".")[1:])
722 join_subdomain(logger=logger, server=server, creds=creds, lp=lp, dnsdomain=domain,
723 parent_domain=parent_domain, site=site,
724 netbios_name=netbios_name, netbios_domain=netbios_domain,
725 targetdir=targetdir, machinepass=machinepass,
726 use_ntvfs=use_ntvfs, dns_backend=dns_backend,
728 plaintext_secrets=plaintext_secrets)
730 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role)
733 class cmd_domain_demote(Command):
734 """Demote ourselves from the role of Domain Controller."""
736 synopsis = "%prog [options]"
739 Option("--server", help="writable DC to write demotion changes on", type=str),
740 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
741 metavar="URL", dest="H"),
742 Option("--remove-other-dead-server", help="Dead DC (name or NTDS GUID) "
743 "to remove ALL references to (rather than this DC)", type=str),
744 Option("--quiet", help="Be quiet", action="store_true"),
745 Option("--verbose", help="Be verbose", action="store_true"),
748 takes_optiongroups = {
749 "sambaopts": options.SambaOptions,
750 "credopts": options.CredentialsOptions,
751 "versionopts": options.VersionOptions,
754 def run(self, sambaopts=None, credopts=None,
755 versionopts=None, server=None,
756 remove_other_dead_server=None, H=None,
757 verbose=False, quiet=False):
758 lp = sambaopts.get_loadparm()
759 creds = credopts.get_credentials(lp)
760 net = Net(creds, lp, server=credopts.ipaddress)
762 logger = self.get_logger()
764 logger.setLevel(logging.DEBUG)
766 logger.setLevel(logging.WARNING)
768 logger.setLevel(logging.INFO)
770 if remove_other_dead_server is not None:
771 if server is not None:
772 samdb = SamDB(url="ldap://%s" % server,
773 session_info=system_session(),
774 credentials=creds, lp=lp)
776 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
778 remove_dc.remove_dc(samdb, logger, remove_other_dead_server)
779 except remove_dc.DemoteException as err:
780 raise CommandError("Demote failed: %s" % err)
783 netbios_name = lp.get("netbios name")
784 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
786 res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
788 raise CommandError("Unable to search for servers")
791 raise CommandError("You are the latest server in the domain")
795 if str(e["name"]).lower() != netbios_name.lower():
796 server = e["dnsHostName"]
799 ntds_guid = samdb.get_ntds_GUID()
800 msg = samdb.search(base=str(samdb.get_config_basedn()),
801 scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
803 if len(msg) == 0 or "options" not in msg[0]:
804 raise CommandError("Failed to find options on %s" % ntds_guid)
807 dsa_options = int(str(msg[0]['options']))
809 res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
810 controls=["search_options:1:2"])
813 raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res))
815 self.errf.write("Using %s as partner server for the demotion\n" %
817 (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
819 self.errf.write("Deactivating inbound replication\n")
824 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
825 dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
826 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
830 self.errf.write("Asking partner server %s to synchronize from us\n"
832 for part in (samdb.get_schema_basedn(),
833 samdb.get_config_basedn(),
834 samdb.get_root_basedn()):
835 nc = drsuapi.DsReplicaObjectIdentifier()
838 req1 = drsuapi.DsReplicaSyncRequest1()
839 req1.naming_context = nc;
840 req1.options = drsuapi.DRSUAPI_DRS_WRIT_REP
841 req1.source_dsa_guid = misc.GUID(ntds_guid)
844 drsuapiBind.DsReplicaSync(drsuapi_handle, 1, req1)
845 except RuntimeError as e1:
846 (werr, string) = e1.args
847 if werr == werror.WERR_DS_DRA_NO_REPLICA:
851 "Error while replicating out last local changes from '%s' for demotion, "
852 "re-enabling inbound replication\n" % part)
853 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
854 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
856 raise CommandError("Error while sending a DsReplicaSync for partition '%s'" % str(part), string)
858 remote_samdb = SamDB(url="ldap://%s" % server,
859 session_info=system_session(),
860 credentials=creds, lp=lp)
862 self.errf.write("Changing userControl and container\n")
863 res = remote_samdb.search(base=str(remote_samdb.domain_dn()),
864 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
865 netbios_name.upper(),
866 attrs=["userAccountControl"])
868 uac = int(str(res[0]["userAccountControl"]))
870 except Exception as e:
871 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
873 "Error while demoting, re-enabling inbound replication\n")
874 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
875 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
877 raise CommandError("Error while changing account control", e)
880 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
882 "Error while demoting, re-enabling inbound replication")
883 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
884 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
886 raise CommandError("Unable to find object with samaccountName = %s$"
887 " in the remote dc" % netbios_name.upper())
891 uac &= ~(UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION|UF_PARTIAL_SECRETS_ACCOUNT)
892 uac |= UF_WORKSTATION_TRUST_ACCOUNT
897 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
898 ldb.FLAG_MOD_REPLACE,
899 "userAccountControl")
901 remote_samdb.modify(msg)
902 except Exception as e:
903 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
905 "Error while demoting, re-enabling inbound replication")
906 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
907 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
910 raise CommandError("Error while changing account control", e)
912 parent = msg.dn.parent()
913 dc_name = res[0].dn.get_rdn_value()
914 rdn = "CN=%s" % dc_name
916 # Let's move to the Computer container
920 computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.domain_dn()))
921 res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
924 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
925 scope=ldb.SCOPE_ONELEVEL)
926 while(len(res) != 0 and i < 100):
928 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
929 scope=ldb.SCOPE_ONELEVEL)
932 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
934 "Error while demoting, re-enabling inbound replication\n")
935 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
936 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
942 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
943 ldb.FLAG_MOD_REPLACE,
944 "userAccountControl")
946 remote_samdb.modify(msg)
948 raise CommandError("Unable to find a slot for renaming %s,"
949 " all names from %s-1 to %s-%d seemed used" %
950 (str(dc_dn), rdn, rdn, i - 9))
952 newrdn = "%s-%d" % (rdn, i)
955 newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
956 remote_samdb.rename(dc_dn, newdn)
957 except Exception as e:
958 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
960 "Error while demoting, re-enabling inbound replication\n")
961 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
962 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
968 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
969 ldb.FLAG_MOD_REPLACE,
970 "userAccountControl")
972 remote_samdb.modify(msg)
973 raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
976 server_dsa_dn = samdb.get_serverName()
977 domain = remote_samdb.get_root_basedn()
980 req1 = drsuapi.DsRemoveDSServerRequest1()
981 req1.server_dn = str(server_dsa_dn)
982 req1.domain_dn = str(domain)
985 drsuapiBind.DsRemoveDSServer(drsuapi_handle, 1, req1)
986 except RuntimeError as e3:
987 (werr, string) = e3.args
988 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
990 "Error while demoting, re-enabling inbound replication\n")
991 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
992 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
998 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
999 ldb.FLAG_MOD_REPLACE,
1000 "userAccountControl")
1001 remote_samdb.modify(msg)
1002 remote_samdb.rename(newdn, dc_dn)
1003 if werr == werror.WERR_DS_DRA_NO_REPLICA:
1004 raise CommandError("The DC %s is not present on (already removed from) the remote server: " % server_dsa_dn, e)
1006 raise CommandError("Error while sending a removeDsServer of %s: " % server_dsa_dn, e)
1008 remove_dc.remove_sysvol_references(remote_samdb, logger, dc_name)
1010 # These are objects under the computer account that should be deleted
1011 for s in ("CN=Enterprise,CN=NTFRS Subscriptions",
1012 "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
1013 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
1014 "CN=NTFRS Subscriptions"):
1016 remote_samdb.delete(ldb.Dn(remote_samdb,
1017 "%s,%s" % (s, str(newdn))))
1018 except ldb.LdbError as l:
1021 self.errf.write("Demote successful\n")
1024 class cmd_domain_level(Command):
1025 """Raise domain and forest function levels."""
1027 synopsis = "%prog (show|raise <options>) [options]"
1029 takes_optiongroups = {
1030 "sambaopts": options.SambaOptions,
1031 "credopts": options.CredentialsOptions,
1032 "versionopts": options.VersionOptions,
1036 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1037 metavar="URL", dest="H"),
1038 Option("--quiet", help="Be quiet", action="store_true"),
1039 Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1040 help="The forest function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)"),
1041 Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1042 help="The domain function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)")
1045 takes_args = ["subcommand"]
1047 def run(self, subcommand, H=None, forest_level=None, domain_level=None,
1048 quiet=False, credopts=None, sambaopts=None, versionopts=None):
1049 lp = sambaopts.get_loadparm()
1050 creds = credopts.get_credentials(lp, fallback_machine=True)
1052 samdb = SamDB(url=H, session_info=system_session(),
1053 credentials=creds, lp=lp)
1055 domain_dn = samdb.domain_dn()
1057 res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
1058 scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
1059 assert len(res_forest) == 1
1061 res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1062 attrs=["msDS-Behavior-Version", "nTMixedDomain"])
1063 assert len(res_domain) == 1
1065 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
1066 scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
1067 attrs=["msDS-Behavior-Version"])
1068 assert len(res_dc_s) >= 1
1070 # default values, since "msDS-Behavior-Version" does not exist on Windows 2000 AD
1071 level_forest = DS_DOMAIN_FUNCTION_2000
1072 level_domain = DS_DOMAIN_FUNCTION_2000
1074 if "msDS-Behavior-Version" in res_forest[0]:
1075 level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
1076 if "msDS-Behavior-Version" in res_domain[0]:
1077 level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
1078 level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
1081 for msg in res_dc_s:
1082 if "msDS-Behavior-Version" in msg:
1083 if min_level_dc is None or int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
1084 min_level_dc = int(msg["msDS-Behavior-Version"][0])
1086 min_level_dc = DS_DOMAIN_FUNCTION_2000
1087 # well, this is the least
1090 if level_forest < DS_DOMAIN_FUNCTION_2000 or level_domain < DS_DOMAIN_FUNCTION_2000:
1091 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
1092 if min_level_dc < DS_DOMAIN_FUNCTION_2000:
1093 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
1094 if level_forest > level_domain:
1095 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
1096 if level_domain > min_level_dc:
1097 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
1099 if subcommand == "show":
1100 self.message("Domain and forest function level for domain '%s'" % domain_dn)
1101 if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1102 self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1103 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1104 self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1105 if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1106 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)!")
1110 if level_forest == DS_DOMAIN_FUNCTION_2000:
1112 elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
1113 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1114 elif level_forest == DS_DOMAIN_FUNCTION_2003:
1116 elif level_forest == DS_DOMAIN_FUNCTION_2008:
1118 elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
1120 elif level_forest == DS_DOMAIN_FUNCTION_2012:
1122 elif level_forest == DS_DOMAIN_FUNCTION_2012_R2:
1125 outstr = "higher than 2012 R2"
1126 self.message("Forest function level: (Windows) " + outstr)
1128 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1129 outstr = "2000 mixed (NT4 DC support)"
1130 elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
1132 elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
1133 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1134 elif level_domain == DS_DOMAIN_FUNCTION_2003:
1136 elif level_domain == DS_DOMAIN_FUNCTION_2008:
1138 elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
1140 elif level_domain == DS_DOMAIN_FUNCTION_2012:
1142 elif level_domain == DS_DOMAIN_FUNCTION_2012_R2:
1145 outstr = "higher than 2012 R2"
1146 self.message("Domain function level: (Windows) " + outstr)
1148 if min_level_dc == DS_DOMAIN_FUNCTION_2000:
1150 elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
1152 elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
1154 elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
1156 elif min_level_dc == DS_DOMAIN_FUNCTION_2012:
1158 elif min_level_dc == DS_DOMAIN_FUNCTION_2012_R2:
1161 outstr = "higher than 2012 R2"
1162 self.message("Lowest function level of a DC: (Windows) " + outstr)
1164 elif subcommand == "raise":
1167 if domain_level is not None:
1168 if domain_level == "2003":
1169 new_level_domain = DS_DOMAIN_FUNCTION_2003
1170 elif domain_level == "2008":
1171 new_level_domain = DS_DOMAIN_FUNCTION_2008
1172 elif domain_level == "2008_R2":
1173 new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
1174 elif domain_level == "2012":
1175 new_level_domain = DS_DOMAIN_FUNCTION_2012
1176 elif domain_level == "2012_R2":
1177 new_level_domain = DS_DOMAIN_FUNCTION_2012_R2
1179 if new_level_domain <= level_domain and level_domain_mixed == 0:
1180 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
1181 if new_level_domain > min_level_dc:
1182 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
1184 # Deactivate mixed/interim domain support
1185 if level_domain_mixed != 0:
1186 # Directly on the base DN
1188 m.dn = ldb.Dn(samdb, domain_dn)
1189 m["nTMixedDomain"] = ldb.MessageElement("0",
1190 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1194 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
1195 m["nTMixedDomain"] = ldb.MessageElement("0",
1196 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1199 except ldb.LdbError as e:
1200 (enum, emsg) = e.args
1201 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1204 # Directly on the base DN
1206 m.dn = ldb.Dn(samdb, domain_dn)
1207 m["msDS-Behavior-Version"]= ldb.MessageElement(
1208 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1209 "msDS-Behavior-Version")
1213 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
1214 + ",CN=Partitions,%s" % samdb.get_config_basedn())
1215 m["msDS-Behavior-Version"]= ldb.MessageElement(
1216 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1217 "msDS-Behavior-Version")
1220 except ldb.LdbError as e2:
1221 (enum, emsg) = e2.args
1222 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1225 level_domain = new_level_domain
1226 msgs.append("Domain function level changed!")
1228 if forest_level is not None:
1229 if forest_level == "2003":
1230 new_level_forest = DS_DOMAIN_FUNCTION_2003
1231 elif forest_level == "2008":
1232 new_level_forest = DS_DOMAIN_FUNCTION_2008
1233 elif forest_level == "2008_R2":
1234 new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
1235 elif forest_level == "2012":
1236 new_level_forest = DS_DOMAIN_FUNCTION_2012
1237 elif forest_level == "2012_R2":
1238 new_level_forest = DS_DOMAIN_FUNCTION_2012_R2
1240 if new_level_forest <= level_forest:
1241 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1242 if new_level_forest > level_domain:
1243 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1246 m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
1247 m["msDS-Behavior-Version"]= ldb.MessageElement(
1248 str(new_level_forest), ldb.FLAG_MOD_REPLACE,
1249 "msDS-Behavior-Version")
1251 msgs.append("Forest function level changed!")
1252 msgs.append("All changes applied successfully!")
1253 self.message("\n".join(msgs))
1255 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
1258 class cmd_domain_passwordsettings(Command):
1259 """Set password settings.
1261 Password complexity, password lockout policy, history length,
1262 minimum password length, the minimum and maximum password age) on
1263 a Samba AD DC server.
1265 Use against a Windows DC is possible, but group policy will override it.
1268 synopsis = "%prog (show|set <options>) [options]"
1270 takes_optiongroups = {
1271 "sambaopts": options.SambaOptions,
1272 "versionopts": options.VersionOptions,
1273 "credopts": options.CredentialsOptions,
1277 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1278 metavar="URL", dest="H"),
1279 Option("--quiet", help="Be quiet", action="store_true"),
1280 Option("--complexity", type="choice", choices=["on","off","default"],
1281 help="The password complexity (on | off | default). Default is 'on'"),
1282 Option("--store-plaintext", type="choice", choices=["on","off","default"],
1283 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1284 Option("--history-length",
1285 help="The password history length (<integer> | default). Default is 24.", type=str),
1286 Option("--min-pwd-length",
1287 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1288 Option("--min-pwd-age",
1289 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1290 Option("--max-pwd-age",
1291 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1292 Option("--account-lockout-duration",
1293 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),
1294 Option("--account-lockout-threshold",
1295 help="The number of bad password attempts allowed before locking out the account (<integer> | default). Default is 0 (never lock out).", type=str),
1296 Option("--reset-account-lockout-after",
1297 help="After this time is elapsed, the recorded number of attempts restarts from zero (<integer> | default). Default is 30.", type=str),
1300 takes_args = ["subcommand"]
1302 def run(self, subcommand, H=None, min_pwd_age=None, max_pwd_age=None,
1303 quiet=False, complexity=None, store_plaintext=None, history_length=None,
1304 min_pwd_length=None, account_lockout_duration=None, account_lockout_threshold=None,
1305 reset_account_lockout_after=None, credopts=None, sambaopts=None,
1307 lp = sambaopts.get_loadparm()
1308 creds = credopts.get_credentials(lp)
1310 samdb = SamDB(url=H, session_info=system_session(),
1311 credentials=creds, lp=lp)
1313 domain_dn = samdb.domain_dn()
1314 res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1315 attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1316 "minPwdAge", "maxPwdAge", "lockoutDuration", "lockoutThreshold",
1317 "lockOutObservationWindow"])
1318 assert(len(res) == 1)
1320 pwd_props = int(res[0]["pwdProperties"][0])
1321 pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
1322 cur_min_pwd_len = int(res[0]["minPwdLength"][0])
1324 cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1325 if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000:
1328 cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1329 cur_account_lockout_threshold = int(res[0]["lockoutThreshold"][0])
1331 if int(res[0]["lockoutDuration"][0]) == -0x8000000000000000:
1332 cur_account_lockout_duration = 0
1334 cur_account_lockout_duration = abs(int(res[0]["lockoutDuration"][0])) / (1e7 * 60)
1335 cur_reset_account_lockout_after = abs(int(res[0]["lockOutObservationWindow"][0])) / (1e7 * 60)
1336 except Exception as e:
1337 raise CommandError("Could not retrieve password properties!", e)
1339 if subcommand == "show":
1340 self.message("Password informations for domain '%s'" % domain_dn)
1342 if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
1343 self.message("Password complexity: on")
1345 self.message("Password complexity: off")
1346 if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
1347 self.message("Store plaintext passwords: on")
1349 self.message("Store plaintext passwords: off")
1350 self.message("Password history length: %d" % pwd_hist_len)
1351 self.message("Minimum password length: %d" % cur_min_pwd_len)
1352 self.message("Minimum password age (days): %d" % cur_min_pwd_age)
1353 self.message("Maximum password age (days): %d" % cur_max_pwd_age)
1354 self.message("Account lockout duration (mins): %d" % cur_account_lockout_duration)
1355 self.message("Account lockout threshold (attempts): %d" % cur_account_lockout_threshold)
1356 self.message("Reset account lockout after (mins): %d" % cur_reset_account_lockout_after)
1357 elif subcommand == "set":
1360 m.dn = ldb.Dn(samdb, domain_dn)
1362 if complexity is not None:
1363 if complexity == "on" or complexity == "default":
1364 pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
1365 msgs.append("Password complexity activated!")
1366 elif complexity == "off":
1367 pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
1368 msgs.append("Password complexity deactivated!")
1370 if store_plaintext is not None:
1371 if store_plaintext == "on" or store_plaintext == "default":
1372 pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1373 msgs.append("Plaintext password storage for changed passwords activated!")
1374 elif store_plaintext == "off":
1375 pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
1376 msgs.append("Plaintext password storage for changed passwords deactivated!")
1378 if complexity is not None or store_plaintext is not None:
1379 m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
1380 ldb.FLAG_MOD_REPLACE, "pwdProperties")
1382 if history_length is not None:
1383 if history_length == "default":
1386 pwd_hist_len = int(history_length)
1388 if pwd_hist_len < 0 or pwd_hist_len > 24:
1389 raise CommandError("Password history length must be in the range of 0 to 24!")
1391 m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
1392 ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
1393 msgs.append("Password history length changed!")
1395 if min_pwd_length is not None:
1396 if min_pwd_length == "default":
1399 min_pwd_len = int(min_pwd_length)
1401 if min_pwd_len < 0 or min_pwd_len > 14:
1402 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1404 m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
1405 ldb.FLAG_MOD_REPLACE, "minPwdLength")
1406 msgs.append("Minimum password length changed!")
1408 if min_pwd_age is not None:
1409 if min_pwd_age == "default":
1412 min_pwd_age = int(min_pwd_age)
1414 if min_pwd_age < 0 or min_pwd_age > 998:
1415 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1418 min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
1420 m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
1421 ldb.FLAG_MOD_REPLACE, "minPwdAge")
1422 msgs.append("Minimum password age changed!")
1424 if max_pwd_age is not None:
1425 if max_pwd_age == "default":
1428 max_pwd_age = int(max_pwd_age)
1430 if max_pwd_age < 0 or max_pwd_age > 999:
1431 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1434 if max_pwd_age == 0:
1435 max_pwd_age_ticks = -0x8000000000000000
1437 max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
1439 m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
1440 ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1441 msgs.append("Maximum password age changed!")
1443 if account_lockout_duration is not None:
1444 if account_lockout_duration == "default":
1445 account_lockout_duration = 30
1447 account_lockout_duration = int(account_lockout_duration)
1449 if account_lockout_duration < 0 or account_lockout_duration > 99999:
1450 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1453 if account_lockout_duration == 0:
1454 account_lockout_duration_ticks = -0x8000000000000000
1456 account_lockout_duration_ticks = -int(account_lockout_duration * (60 * 1e7))
1458 m["lockoutDuration"] = ldb.MessageElement(str(account_lockout_duration_ticks),
1459 ldb.FLAG_MOD_REPLACE, "lockoutDuration")
1460 msgs.append("Account lockout duration changed!")
1462 if account_lockout_threshold is not None:
1463 if account_lockout_threshold == "default":
1464 account_lockout_threshold = 0
1466 account_lockout_threshold = int(account_lockout_threshold)
1468 m["lockoutThreshold"] = ldb.MessageElement(str(account_lockout_threshold),
1469 ldb.FLAG_MOD_REPLACE, "lockoutThreshold")
1470 msgs.append("Account lockout threshold changed!")
1472 if reset_account_lockout_after is not None:
1473 if reset_account_lockout_after == "default":
1474 reset_account_lockout_after = 30
1476 reset_account_lockout_after = int(reset_account_lockout_after)
1478 if reset_account_lockout_after < 0 or reset_account_lockout_after > 99999:
1479 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1482 if reset_account_lockout_after == 0:
1483 reset_account_lockout_after_ticks = -0x8000000000000000
1485 reset_account_lockout_after_ticks = -int(reset_account_lockout_after * (60 * 1e7))
1487 m["lockOutObservationWindow"] = ldb.MessageElement(str(reset_account_lockout_after_ticks),
1488 ldb.FLAG_MOD_REPLACE, "lockOutObservationWindow")
1489 msgs.append("Duration to reset account lockout after changed!")
1491 if max_pwd_age > 0 and min_pwd_age >= max_pwd_age:
1492 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
1495 raise CommandError("You must specify at least one option to set. Try --help")
1497 msgs.append("All changes applied successfully!")
1498 self.message("\n".join(msgs))
1500 raise CommandError("Wrong argument '%s'!" % subcommand)
1503 class cmd_domain_classicupgrade(Command):
1504 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1506 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1507 the testparm utility from your classic installation (with --testparm).
1510 synopsis = "%prog [options] <classic_smb_conf>"
1512 takes_optiongroups = {
1513 "sambaopts": options.SambaOptions,
1514 "versionopts": options.VersionOptions
1518 Option("--dbdir", type="string", metavar="DIR",
1519 help="Path to samba classic DC database directory"),
1520 Option("--testparm", type="string", metavar="PATH",
1521 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1522 Option("--targetdir", type="string", metavar="DIR",
1523 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1524 Option("--quiet", help="Be quiet", action="store_true"),
1525 Option("--verbose", help="Be verbose", action="store_true"),
1526 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
1527 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1528 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1529 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1530 "BIND9_DLZ uses samba4 AD to store zone information, "
1531 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1532 default="SAMBA_INTERNAL")
1536 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
1537 action="store_true"),
1538 Option("--use-xattrs", type="choice", choices=["yes","no","auto"],
1539 metavar="[yes|no|auto]",
1540 help="Define if we should use the native fs capabilities or a tdb file for "
1541 "storing attributes likes ntacl when --use-ntvfs is set. "
1542 "auto tries to make an inteligent guess based on the user rights and system capabilities",
1545 if samba.is_ntvfs_fileserver_built():
1546 takes_options.extend(ntvfs_options)
1548 takes_args = ["smbconf"]
1550 def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
1551 quiet=False, verbose=False, use_xattrs="auto", sambaopts=None, versionopts=None,
1552 dns_backend=None, use_ntvfs=False):
1554 if not os.path.exists(smbconf):
1555 raise CommandError("File %s does not exist" % smbconf)
1557 if testparm and not os.path.exists(testparm):
1558 raise CommandError("Testparm utility %s does not exist" % testparm)
1560 if dbdir and not os.path.exists(dbdir):
1561 raise CommandError("Directory %s does not exist" % dbdir)
1563 if not dbdir and not testparm:
1564 raise CommandError("Please specify either dbdir or testparm")
1566 logger = self.get_logger()
1568 logger.setLevel(logging.DEBUG)
1570 logger.setLevel(logging.WARNING)
1572 logger.setLevel(logging.INFO)
1574 if dbdir and testparm:
1575 logger.warning("both dbdir and testparm specified, ignoring dbdir.")
1578 lp = sambaopts.get_loadparm()
1580 s3conf = s3param.get_context()
1583 s3conf.set("realm", sambaopts.realm)
1585 if targetdir is not None:
1586 if not os.path.isdir(targetdir):
1590 if use_xattrs == "yes":
1592 elif use_xattrs == "auto" and use_ntvfs == False:
1594 elif use_ntvfs == False:
1595 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
1596 "Please re-run with --use-xattrs omitted.")
1597 elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
1599 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
1601 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
1604 samba.ntacls.setntacl(lp, tmpfile.name,
1605 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1608 # FIXME: Don't catch all exceptions here
1609 logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. "
1610 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1614 # Set correct default values from dbdir or testparm
1617 paths["state directory"] = dbdir
1618 paths["private dir"] = dbdir
1619 paths["lock directory"] = dbdir
1620 paths["smb passwd file"] = dbdir + "/smbpasswd"
1622 paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
1623 paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
1624 paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
1625 paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
1626 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1627 # "state directory", instead make use of "lock directory"
1628 if len(paths["state directory"]) == 0:
1629 paths["state directory"] = paths["lock directory"]
1632 s3conf.set(p, paths[p])
1634 # load smb.conf parameters
1635 logger.info("Reading smb.conf")
1636 s3conf.load(smbconf)
1637 samba3 = Samba3(smbconf, s3conf)
1639 logger.info("Provisioning")
1640 upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
1641 useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
1644 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
1645 __doc__ = cmd_domain_classicupgrade.__doc__
1647 # This command is present for backwards compatibility only,
1648 # and should not be shown.
1652 class LocalDCCredentialsOptions(options.CredentialsOptions):
1653 def __init__(self, parser):
1654 options.CredentialsOptions.__init__(self, parser, special_name="local-dc")
1656 class DomainTrustCommand(Command):
1657 """List domain trusts."""
1660 Command.__init__(self)
1661 self.local_lp = None
1663 self.local_server = None
1664 self.local_binding_string = None
1665 self.local_creds = None
1667 self.remote_server = None
1668 self.remote_binding_string = None
1669 self.remote_creds = None
1671 def _uint32(self, v):
1672 return ctypes.c_uint32(v).value
1674 def check_runtime_error(self, runtime, val):
1678 err32 = self._uint32(runtime[0])
1684 class LocalRuntimeError(CommandError):
1685 def __init__(exception_self, self, runtime, message):
1686 err32 = self._uint32(runtime[0])
1688 msg = "LOCAL_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1689 self.local_server, message, err32, errstr)
1690 CommandError.__init__(exception_self, msg)
1692 class RemoteRuntimeError(CommandError):
1693 def __init__(exception_self, self, runtime, message):
1694 err32 = self._uint32(runtime[0])
1696 msg = "REMOTE_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1697 self.remote_server, message, err32, errstr)
1698 CommandError.__init__(exception_self, msg)
1700 class LocalLdbError(CommandError):
1701 def __init__(exception_self, self, ldb_error, message):
1702 errval = ldb_error[0]
1703 errstr = ldb_error[1]
1704 msg = "LOCAL_DC[%s]: %s - ERROR(%d) - %s" % (
1705 self.local_server, message, errval, errstr)
1706 CommandError.__init__(exception_self, msg)
1708 def setup_local_server(self, sambaopts, localdcopts):
1709 if self.local_server is not None:
1710 return self.local_server
1712 lp = sambaopts.get_loadparm()
1714 local_server = localdcopts.ipaddress
1715 if local_server is None:
1716 server_role = lp.server_role()
1717 if server_role != "ROLE_ACTIVE_DIRECTORY_DC":
1718 raise CommandError("Invalid server_role %s" % (server_role))
1719 local_server = lp.get('netbios name')
1720 local_transport = "ncalrpc"
1721 local_binding_options = ""
1722 local_binding_options += ",auth_type=ncalrpc_as_system"
1723 local_ldap_url = None
1726 local_transport = "ncacn_np"
1727 local_binding_options = ""
1728 local_ldap_url = "ldap://%s" % local_server
1729 local_creds = localdcopts.get_credentials(lp)
1733 self.local_server = local_server
1734 self.local_binding_string = "%s:%s[%s]" % (local_transport, local_server, local_binding_options)
1735 self.local_ldap_url = local_ldap_url
1736 self.local_creds = local_creds
1737 return self.local_server
1739 def new_local_lsa_connection(self):
1740 return lsa.lsarpc(self.local_binding_string, self.local_lp, self.local_creds)
1742 def new_local_netlogon_connection(self):
1743 return netlogon.netlogon(self.local_binding_string, self.local_lp, self.local_creds)
1745 def new_local_ldap_connection(self):
1746 return SamDB(url=self.local_ldap_url,
1747 session_info=system_session(),
1748 credentials=self.local_creds,
1751 def setup_remote_server(self, credopts, domain,
1753 require_writable=True):
1756 assert require_writable
1758 if self.remote_server is not None:
1759 return self.remote_server
1761 self.remote_server = "__unknown__remote_server__.%s" % domain
1762 assert self.local_server is not None
1764 remote_creds = credopts.get_credentials(self.local_lp)
1765 remote_server = credopts.ipaddress
1766 remote_binding_options = ""
1768 # TODO: we should also support NT4 domains
1769 # we could use local_netlogon.netr_DsRGetDCNameEx2() with the remote domain name
1770 # and delegate NBT or CLDAP to the local netlogon server
1772 remote_net = Net(remote_creds, self.local_lp, server=remote_server)
1773 remote_flags = nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS
1774 if require_writable:
1775 remote_flags |= nbt.NBT_SERVER_WRITABLE
1777 remote_flags |= nbt.NBT_SERVER_PDC
1778 remote_info = remote_net.finddc(flags=remote_flags, domain=domain, address=remote_server)
1779 except NTSTATUSError as error:
1780 raise CommandError("Failed to find a writeable DC for domain '%s': %s" %
1783 raise CommandError("Failed to find a writeable DC for domain '%s'" % domain)
1785 nbt.NBT_SERVER_PDC: "PDC",
1786 nbt.NBT_SERVER_GC: "GC",
1787 nbt.NBT_SERVER_LDAP: "LDAP",
1788 nbt.NBT_SERVER_DS: "DS",
1789 nbt.NBT_SERVER_KDC: "KDC",
1790 nbt.NBT_SERVER_TIMESERV: "TIMESERV",
1791 nbt.NBT_SERVER_CLOSEST: "CLOSEST",
1792 nbt.NBT_SERVER_WRITABLE: "WRITABLE",
1793 nbt.NBT_SERVER_GOOD_TIMESERV: "GOOD_TIMESERV",
1794 nbt.NBT_SERVER_NDNC: "NDNC",
1795 nbt.NBT_SERVER_SELECT_SECRET_DOMAIN_6: "SELECT_SECRET_DOMAIN_6",
1796 nbt.NBT_SERVER_FULL_SECRET_DOMAIN_6: "FULL_SECRET_DOMAIN_6",
1797 nbt.NBT_SERVER_ADS_WEB_SERVICE: "ADS_WEB_SERVICE",
1798 nbt.NBT_SERVER_DS_8: "DS_8",
1799 nbt.NBT_SERVER_HAS_DNS_NAME: "HAS_DNS_NAME",
1800 nbt.NBT_SERVER_IS_DEFAULT_NC: "IS_DEFAULT_NC",
1801 nbt.NBT_SERVER_FOREST_ROOT: "FOREST_ROOT",
1803 server_type_string = self.generic_bitmap_to_string(flag_map,
1804 remote_info.server_type, names_only=True)
1805 self.outf.write("RemoteDC Netbios[%s] DNS[%s] ServerType[%s]\n" % (
1806 remote_info.pdc_name,
1807 remote_info.pdc_dns_name,
1808 server_type_string))
1810 self.remote_server = remote_info.pdc_dns_name
1811 self.remote_binding_string="ncacn_np:%s[%s]" % (self.remote_server, remote_binding_options)
1812 self.remote_creds = remote_creds
1813 return self.remote_server
1815 def new_remote_lsa_connection(self):
1816 return lsa.lsarpc(self.remote_binding_string, self.local_lp, self.remote_creds)
1818 def new_remote_netlogon_connection(self):
1819 return netlogon.netlogon(self.remote_binding_string, self.local_lp, self.remote_creds)
1821 def get_lsa_info(self, conn, policy_access):
1822 objectAttr = lsa.ObjectAttribute()
1823 objectAttr.sec_qos = lsa.QosInfo()
1825 policy = conn.OpenPolicy2(''.decode('utf-8'),
1826 objectAttr, policy_access)
1828 info = conn.QueryInfoPolicy2(policy, lsa.LSA_POLICY_INFO_DNS)
1830 return (policy, info)
1832 def get_netlogon_dc_info(self, conn, server):
1833 info = conn.netr_DsRGetDCNameEx2(server,
1834 None, 0, None, None, None,
1835 netlogon.DS_RETURN_DNS_NAME)
1838 def netr_DomainTrust_to_name(self, t):
1839 if t.trust_type == lsa.LSA_TRUST_TYPE_DOWNLEVEL:
1840 return t.netbios_name
1844 def netr_DomainTrust_to_type(self, a, t):
1846 primary_parent = None
1848 if _t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
1850 if not _t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1851 primary_parent = a[_t.parent_index]
1854 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1855 if t is primary_parent:
1858 if t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1861 parent = a[t.parent_index]
1862 if parent is primary:
1867 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1872 def netr_DomainTrust_to_transitive(self, t):
1873 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1876 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE:
1879 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1884 def netr_DomainTrust_to_direction(self, t):
1885 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND and \
1886 t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1889 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND:
1892 if t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1897 def generic_enum_to_string(self, e_dict, v, names_only=False):
1901 v32 = self._uint32(v)
1902 w = "__unknown__%08X__" % v32
1904 r = "0x%x (%s)" % (v, w)
1907 def generic_bitmap_to_string(self, b_dict, v, names_only=False):
1912 for b in sorted(b_dict.keys()):
1919 c32 = self._uint32(c)
1920 s += ["__unknown_%08X__" % c32]
1925 r = "0x%x (%s)" % (v, w)
1928 def trustType_string(self, v):
1930 lsa.LSA_TRUST_TYPE_DOWNLEVEL : "DOWNLEVEL",
1931 lsa.LSA_TRUST_TYPE_UPLEVEL : "UPLEVEL",
1932 lsa.LSA_TRUST_TYPE_MIT : "MIT",
1933 lsa.LSA_TRUST_TYPE_DCE : "DCE",
1935 return self.generic_enum_to_string(types, v)
1937 def trustDirection_string(self, v):
1939 lsa.LSA_TRUST_DIRECTION_INBOUND |
1940 lsa.LSA_TRUST_DIRECTION_OUTBOUND : "BOTH",
1941 lsa.LSA_TRUST_DIRECTION_INBOUND : "INBOUND",
1942 lsa.LSA_TRUST_DIRECTION_OUTBOUND : "OUTBOUND",
1944 return self.generic_enum_to_string(directions, v)
1946 def trustAttributes_string(self, v):
1948 lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE : "NON_TRANSITIVE",
1949 lsa.LSA_TRUST_ATTRIBUTE_UPLEVEL_ONLY : "UPLEVEL_ONLY",
1950 lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN : "QUARANTINED_DOMAIN",
1951 lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE : "FOREST_TRANSITIVE",
1952 lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION : "CROSS_ORGANIZATION",
1953 lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST : "WITHIN_FOREST",
1954 lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL : "TREAT_AS_EXTERNAL",
1955 lsa.LSA_TRUST_ATTRIBUTE_USES_RC4_ENCRYPTION : "USES_RC4_ENCRYPTION",
1957 return self.generic_bitmap_to_string(attributes, v)
1959 def kerb_EncTypes_string(self, v):
1961 security.KERB_ENCTYPE_DES_CBC_CRC : "DES_CBC_CRC",
1962 security.KERB_ENCTYPE_DES_CBC_MD5 : "DES_CBC_MD5",
1963 security.KERB_ENCTYPE_RC4_HMAC_MD5 : "RC4_HMAC_MD5",
1964 security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96 : "AES128_CTS_HMAC_SHA1_96",
1965 security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96 : "AES256_CTS_HMAC_SHA1_96",
1966 security.KERB_ENCTYPE_FAST_SUPPORTED : "FAST_SUPPORTED",
1967 security.KERB_ENCTYPE_COMPOUND_IDENTITY_SUPPORTED : "COMPOUND_IDENTITY_SUPPORTED",
1968 security.KERB_ENCTYPE_CLAIMS_SUPPORTED : "CLAIMS_SUPPORTED",
1969 security.KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED : "RESOURCE_SID_COMPRESSION_DISABLED",
1971 return self.generic_bitmap_to_string(enctypes, v)
1973 def entry_tln_status(self, e_flags, ):
1975 return "Status[Enabled]"
1978 lsa.LSA_TLN_DISABLED_NEW : "Disabled-New",
1979 lsa.LSA_TLN_DISABLED_ADMIN : "Disabled",
1980 lsa.LSA_TLN_DISABLED_CONFLICT : "Disabled-Conflicting",
1982 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
1984 def entry_dom_status(self, e_flags):
1986 return "Status[Enabled]"
1989 lsa.LSA_SID_DISABLED_ADMIN : "Disabled-SID",
1990 lsa.LSA_SID_DISABLED_CONFLICT : "Disabled-SID-Conflicting",
1991 lsa.LSA_NB_DISABLED_ADMIN : "Disabled-NB",
1992 lsa.LSA_NB_DISABLED_CONFLICT : "Disabled-NB-Conflicting",
1994 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
1996 def write_forest_trust_info(self, fti, tln=None, collisions=None):
1998 tln_string = " TDO[%s]" % tln
2002 self.outf.write("Namespaces[%d]%s:\n" % (
2003 len(fti.entries), tln_string))
2005 for i in xrange(0, len(fti.entries)):
2009 collision_string = ""
2011 if collisions is not None:
2012 for c in collisions.entries:
2016 collision_string = " Collision[%s]" % (c.name.string)
2018 d = e.forest_trust_data
2019 if e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
2020 self.outf.write("TLN: %-32s DNS[*.%s]%s\n" % (
2021 self.entry_tln_status(flags),
2022 d.string, collision_string))
2023 elif e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
2024 self.outf.write("TLN_EX: %-29s DNS[*.%s]\n" % (
2026 elif e.type == lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
2027 self.outf.write("DOM: %-32s DNS[%s] Netbios[%s] SID[%s]%s\n" % (
2028 self.entry_dom_status(flags),
2029 d.dns_domain_name.string,
2030 d.netbios_domain_name.string,
2031 d.domain_sid, collision_string))
2034 class cmd_domain_trust_list(DomainTrustCommand):
2035 """List domain trusts."""
2037 synopsis = "%prog [options]"
2039 takes_optiongroups = {
2040 "sambaopts": options.SambaOptions,
2041 "versionopts": options.VersionOptions,
2042 "localdcopts": LocalDCCredentialsOptions,
2048 def run(self, sambaopts=None, versionopts=None, localdcopts=None):
2050 local_server = self.setup_local_server(sambaopts, localdcopts)
2052 local_netlogon = self.new_local_netlogon_connection()
2053 except RuntimeError as error:
2054 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2057 local_netlogon_trusts = local_netlogon.netr_DsrEnumerateDomainTrusts(local_server,
2058 netlogon.NETR_TRUST_FLAG_IN_FOREST |
2059 netlogon.NETR_TRUST_FLAG_OUTBOUND |
2060 netlogon.NETR_TRUST_FLAG_INBOUND)
2061 except RuntimeError as error:
2062 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
2063 # TODO: we could implement a fallback to lsa.EnumTrustDom()
2064 raise CommandError("LOCAL_DC[%s]: netr_DsrEnumerateDomainTrusts not supported." % (
2066 raise self.LocalRuntimeError(self, error, "netr_DsrEnumerateDomainTrusts failed")
2068 a = local_netlogon_trusts.array
2070 if t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
2072 self.outf.write("%-14s %-15s %-19s %s\n" % (
2073 "Type[%s]" % self.netr_DomainTrust_to_type(a, t),
2074 "Transitive[%s]" % self.netr_DomainTrust_to_transitive(t),
2075 "Direction[%s]" % self.netr_DomainTrust_to_direction(t),
2076 "Name[%s]" % self.netr_DomainTrust_to_name(t)))
2079 class cmd_domain_trust_show(DomainTrustCommand):
2080 """Show trusted domain details."""
2082 synopsis = "%prog NAME [options]"
2084 takes_optiongroups = {
2085 "sambaopts": options.SambaOptions,
2086 "versionopts": options.VersionOptions,
2087 "localdcopts": LocalDCCredentialsOptions,
2093 takes_args = ["domain"]
2095 def run(self, domain, sambaopts=None, versionopts=None, localdcopts=None):
2097 local_server = self.setup_local_server(sambaopts, localdcopts)
2099 local_lsa = self.new_local_lsa_connection()
2100 except RuntimeError as error:
2101 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2104 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2105 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2106 except RuntimeError as error:
2107 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2109 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2110 local_lsa_info.name.string,
2111 local_lsa_info.dns_domain.string,
2112 local_lsa_info.sid))
2114 lsaString = lsa.String()
2115 lsaString.string = domain
2117 local_tdo_full = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2118 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2119 local_tdo_info = local_tdo_full.info_ex
2120 local_tdo_posix = local_tdo_full.posix_offset
2121 except NTSTATUSError as error:
2122 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2123 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2125 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(FULL_INFO) failed")
2128 local_tdo_enctypes = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2129 lsaString, lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES)
2130 except NTSTATUSError as error:
2131 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_PARAMETER):
2133 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_INFO_CLASS):
2136 if error is not None:
2137 raise self.LocalRuntimeError(self, error,
2138 "QueryTrustedDomainInfoByName(SUPPORTED_ENCRYPTION_TYPES) failed")
2140 local_tdo_enctypes = lsa.TrustDomainInfoSupportedEncTypes()
2141 local_tdo_enctypes.enc_types = 0
2144 local_tdo_forest = None
2145 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2146 local_tdo_forest = local_lsa.lsaRQueryForestTrustInformation(local_policy,
2147 lsaString, lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
2148 except RuntimeError as error:
2149 if self.check_runtime_error(error, ntstatus.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE):
2151 if self.check_runtime_error(error, ntstatus.NT_STATUS_NOT_FOUND):
2153 if error is not None:
2154 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation failed")
2156 local_tdo_forest = lsa.ForestTrustInformation()
2157 local_tdo_forest.count = 0
2158 local_tdo_forest.entries = []
2160 self.outf.write("TrusteDomain:\n\n");
2161 self.outf.write("NetbiosName: %s\n" % local_tdo_info.netbios_name.string)
2162 if local_tdo_info.netbios_name.string != local_tdo_info.domain_name.string:
2163 self.outf.write("DnsName: %s\n" % local_tdo_info.domain_name.string)
2164 self.outf.write("SID: %s\n" % local_tdo_info.sid)
2165 self.outf.write("Type: %s\n" % self.trustType_string(local_tdo_info.trust_type))
2166 self.outf.write("Direction: %s\n" % self.trustDirection_string(local_tdo_info.trust_direction))
2167 self.outf.write("Attributes: %s\n" % self.trustAttributes_string(local_tdo_info.trust_attributes))
2168 posix_offset_u32 = ctypes.c_uint32(local_tdo_posix.posix_offset).value
2169 posix_offset_i32 = ctypes.c_int32(local_tdo_posix.posix_offset).value
2170 self.outf.write("PosixOffset: 0x%08X (%d)\n" % (posix_offset_u32, posix_offset_i32))
2171 self.outf.write("kerb_EncTypes: %s\n" % self.kerb_EncTypes_string(local_tdo_enctypes.enc_types))
2173 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2174 self.write_forest_trust_info(local_tdo_forest,
2175 tln=local_tdo_info.domain_name.string)
2179 class cmd_domain_trust_create(DomainTrustCommand):
2180 """Create a domain or forest trust."""
2182 synopsis = "%prog DOMAIN [options]"
2184 takes_optiongroups = {
2185 "sambaopts": options.SambaOptions,
2186 "versionopts": options.VersionOptions,
2187 "credopts": options.CredentialsOptions,
2188 "localdcopts": LocalDCCredentialsOptions,
2192 Option("--type", type="choice", metavar="TYPE",
2193 choices=["external", "forest"],
2194 help="The type of the trust: 'external' or 'forest'.",
2196 default="external"),
2197 Option("--direction", type="choice", metavar="DIRECTION",
2198 choices=["incoming", "outgoing", "both"],
2199 help="The trust direction: 'incoming', 'outgoing' or 'both'.",
2200 dest='trust_direction',
2202 Option("--create-location", type="choice", metavar="LOCATION",
2203 choices=["local", "both"],
2204 help="Where to create the trusted domain object: 'local' or 'both'.",
2205 dest='create_location',
2207 Option("--cross-organisation", action="store_true",
2208 help="The related domains does not belong to the same organisation.",
2209 dest='cross_organisation',
2211 Option("--quarantined", type="choice", metavar="yes|no",
2212 choices=["yes", "no", None],
2213 help="Special SID filtering rules are applied to the trust. "
2214 "With --type=external the default is yes. "
2215 "With --type=forest the default is no.",
2216 dest='quarantined_arg',
2218 Option("--not-transitive", action="store_true",
2219 help="The forest trust is not transitive.",
2220 dest='not_transitive',
2222 Option("--treat-as-external", action="store_true",
2223 help="The treat the forest trust as external.",
2224 dest='treat_as_external',
2226 Option("--no-aes-keys", action="store_false",
2227 help="The trust uses aes kerberos keys.",
2228 dest='use_aes_keys',
2230 Option("--skip-validation", action="store_false",
2231 help="Skip validation of the trust.",
2236 takes_args = ["domain"]
2238 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2239 trust_type=None, trust_direction=None, create_location=None,
2240 cross_organisation=False, quarantined_arg=None,
2241 not_transitive=False, treat_as_external=False,
2242 use_aes_keys=False, validate=True):
2244 lsaString = lsa.String()
2247 if quarantined_arg is None:
2248 if trust_type == 'external':
2250 elif quarantined_arg == 'yes':
2253 if trust_type != 'forest':
2255 raise CommandError("--not-transitive requires --type=forest")
2256 if treat_as_external:
2257 raise CommandError("--treat-as-external requires --type=forest")
2261 enc_types = lsa.TrustDomainInfoSupportedEncTypes()
2262 enc_types.enc_types = security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96
2263 enc_types.enc_types |= security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96
2265 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2266 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2267 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2269 local_trust_info = lsa.TrustDomainInfoInfoEx()
2270 local_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2271 local_trust_info.trust_direction = 0
2272 if trust_direction == "both":
2273 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2274 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2275 elif trust_direction == "incoming":
2276 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2277 elif trust_direction == "outgoing":
2278 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2279 local_trust_info.trust_attributes = 0
2280 if cross_organisation:
2281 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2283 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2284 if trust_type == "forest":
2285 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2287 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2288 if treat_as_external:
2289 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2291 def get_password(name):
2294 if password is not None and password is not '':
2296 password = getpass("New %s Password: " % name)
2297 passwordverify = getpass("Retype %s Password: " % name)
2298 if not password == passwordverify:
2300 self.outf.write("Sorry, passwords do not match.\n")
2302 incoming_secret = None
2303 outgoing_secret = None
2304 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2305 if create_location == "local":
2306 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2307 incoming_password = get_password("Incoming Trust")
2308 incoming_secret = string_to_byte_array(incoming_password.encode('utf-16-le'))
2309 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2310 outgoing_password = get_password("Outgoing Trust")
2311 outgoing_secret = string_to_byte_array(outgoing_password.encode('utf-16-le'))
2313 remote_trust_info = None
2315 # We use 240 random bytes.
2316 # Windows uses 28 or 240 random bytes. I guess it's
2317 # based on the trust type external vs. forest.
2319 # The initial trust password can be up to 512 bytes
2320 # while the versioned passwords used for periodic updates
2321 # can only be up to 498 bytes, as netr_ServerPasswordSet2()
2322 # needs to pass the NL_PASSWORD_VERSION structure within the
2323 # 512 bytes and a 2 bytes confounder is required.
2325 def random_trust_secret(length):
2326 pw = samba.generate_random_machine_password(length/2, length/2)
2327 return string_to_byte_array(pw.encode('utf-16-le'))
2329 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2330 incoming_secret = random_trust_secret(240)
2331 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2332 outgoing_secret = random_trust_secret(240)
2334 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2335 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2337 remote_trust_info = lsa.TrustDomainInfoInfoEx()
2338 remote_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2339 remote_trust_info.trust_direction = 0
2340 if trust_direction == "both":
2341 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2342 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2343 elif trust_direction == "incoming":
2344 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2345 elif trust_direction == "outgoing":
2346 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2347 remote_trust_info.trust_attributes = 0
2348 if cross_organisation:
2349 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2351 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2352 if trust_type == "forest":
2353 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2355 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2356 if treat_as_external:
2357 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2359 local_server = self.setup_local_server(sambaopts, localdcopts)
2361 local_lsa = self.new_local_lsa_connection()
2362 except RuntimeError as error:
2363 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2366 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2367 except RuntimeError as error:
2368 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2370 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2371 local_lsa_info.name.string,
2372 local_lsa_info.dns_domain.string,
2373 local_lsa_info.sid))
2376 remote_server = self.setup_remote_server(credopts, domain)
2377 except RuntimeError as error:
2378 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2381 remote_lsa = self.new_remote_lsa_connection()
2382 except RuntimeError as error:
2383 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2386 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2387 except RuntimeError as error:
2388 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2390 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2391 remote_lsa_info.name.string,
2392 remote_lsa_info.dns_domain.string,
2393 remote_lsa_info.sid))
2395 local_trust_info.domain_name.string = remote_lsa_info.dns_domain.string
2396 local_trust_info.netbios_name.string = remote_lsa_info.name.string
2397 local_trust_info.sid = remote_lsa_info.sid
2399 if remote_trust_info:
2400 remote_trust_info.domain_name.string = local_lsa_info.dns_domain.string
2401 remote_trust_info.netbios_name.string = local_lsa_info.name.string
2402 remote_trust_info.sid = local_lsa_info.sid
2405 lsaString.string = local_trust_info.domain_name.string
2406 local_old_netbios = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2407 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2408 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2409 except NTSTATUSError as error:
2410 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2411 raise self.LocalRuntimeError(self, error,
2412 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2416 lsaString.string = local_trust_info.netbios_name.string
2417 local_old_dns = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2418 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2419 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2420 except NTSTATUSError as error:
2421 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2422 raise self.LocalRuntimeError(self, error,
2423 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2426 if remote_trust_info:
2428 lsaString.string = remote_trust_info.domain_name.string
2429 remote_old_netbios = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2430 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2431 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2432 except NTSTATUSError as error:
2433 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2434 raise self.RemoteRuntimeError(self, error,
2435 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2439 lsaString.string = remote_trust_info.netbios_name.string
2440 remote_old_dns = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2441 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2442 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2443 except NTSTATUSError as error:
2444 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2445 raise self.RemoteRuntimeError(self, error,
2446 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2450 local_netlogon = self.new_local_netlogon_connection()
2451 except RuntimeError as error:
2452 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2455 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
2456 except RuntimeError as error:
2457 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
2459 if remote_trust_info:
2461 remote_netlogon = self.new_remote_netlogon_connection()
2462 except RuntimeError as error:
2463 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2466 remote_netlogon_info = self.get_netlogon_dc_info(remote_netlogon, remote_server)
2467 except RuntimeError as error:
2468 raise self.RemoteRuntimeError(self, error, "failed to get netlogon dc info")
2470 def generate_AuthInOutBlob(secret, update_time):
2472 blob = drsblobs.trustAuthInOutBlob()
2477 clear = drsblobs.AuthInfoClear()
2478 clear.size = len(secret)
2479 clear.password = secret
2481 info = drsblobs.AuthenticationInformation()
2482 info.LastUpdateTime = samba.unix2nttime(update_time)
2483 info.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
2484 info.AuthInfo = clear
2486 array = drsblobs.AuthenticationInformationArray()
2488 array.array = [info]
2490 blob = drsblobs.trustAuthInOutBlob()
2492 blob.current = array
2496 def generate_AuthInfoInternal(session_key, incoming=None, outgoing=None):
2497 confounder = [0] * 512
2498 for i in range(len(confounder)):
2499 confounder[i] = random.randint(0, 255)
2501 trustpass = drsblobs.trustDomainPasswords()
2503 trustpass.confounder = confounder
2504 trustpass.outgoing = outgoing
2505 trustpass.incoming = incoming
2507 trustpass_blob = ndr_pack(trustpass)
2509 encrypted_trustpass = arcfour_encrypt(session_key, trustpass_blob)
2511 auth_blob = lsa.DATA_BUF2()
2512 auth_blob.size = len(encrypted_trustpass)
2513 auth_blob.data = string_to_byte_array(encrypted_trustpass)
2515 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
2516 auth_info.auth_blob = auth_blob
2520 update_time = samba.current_unix_time()
2521 incoming_blob = generate_AuthInOutBlob(incoming_secret, update_time)
2522 outgoing_blob = generate_AuthInOutBlob(outgoing_secret, update_time)
2524 local_tdo_handle = None
2525 remote_tdo_handle = None
2527 local_auth_info = generate_AuthInfoInternal(local_lsa.session_key,
2528 incoming=incoming_blob,
2529 outgoing=outgoing_blob)
2530 if remote_trust_info:
2531 remote_auth_info = generate_AuthInfoInternal(remote_lsa.session_key,
2532 incoming=outgoing_blob,
2533 outgoing=incoming_blob)
2536 if remote_trust_info:
2537 self.outf.write("Creating remote TDO.\n")
2538 current_request = { "location": "remote", "name": "CreateTrustedDomainEx2"}
2539 remote_tdo_handle = remote_lsa.CreateTrustedDomainEx2(remote_policy,
2542 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2543 self.outf.write("Remote TDO created.\n")
2545 self.outf.write("Setting supported encryption types on remote TDO.\n")
2546 current_request = { "location": "remote", "name": "SetInformationTrustedDomain"}
2547 remote_lsa.SetInformationTrustedDomain(remote_tdo_handle,
2548 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2551 self.outf.write("Creating local TDO.\n")
2552 current_request = { "location": "local", "name": "CreateTrustedDomainEx2"}
2553 local_tdo_handle = local_lsa.CreateTrustedDomainEx2(local_policy,
2556 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2557 self.outf.write("Local TDO created\n")
2559 self.outf.write("Setting supported encryption types on local TDO.\n")
2560 current_request = { "location": "local", "name": "SetInformationTrustedDomain"}
2561 local_lsa.SetInformationTrustedDomain(local_tdo_handle,
2562 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2564 except RuntimeError as error:
2565 self.outf.write("Error: %s failed %sly - cleaning up\n" % (
2566 current_request['name'], current_request['location']))
2567 if remote_tdo_handle:
2568 self.outf.write("Deleting remote TDO.\n")
2569 remote_lsa.DeleteObject(remote_tdo_handle)
2570 remote_tdo_handle = None
2571 if local_tdo_handle:
2572 self.outf.write("Deleting local TDO.\n")
2573 local_lsa.DeleteObject(local_tdo_handle)
2574 local_tdo_handle = None
2575 if current_request['location'] is "remote":
2576 raise self.RemoteRuntimeError(self, error, "%s" % (
2577 current_request['name']))
2578 raise self.LocalRuntimeError(self, error, "%s" % (
2579 current_request['name']))
2582 if local_trust_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2583 self.outf.write("Setup local forest trust information...\n")
2585 # get all information about the remote trust
2586 # this triggers netr_GetForestTrustInformation to the remote domain
2587 # and lsaRSetForestTrustInformation() locally, but new top level
2588 # names are disabled by default.
2589 local_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
2590 remote_lsa_info.dns_domain.string,
2591 netlogon.DS_GFTI_UPDATE_TDO)
2592 except RuntimeError as error:
2593 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2596 # here we try to enable all top level names
2597 local_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
2598 remote_lsa_info.dns_domain,
2599 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2602 except RuntimeError as error:
2603 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2605 self.write_forest_trust_info(local_forest_info,
2606 tln=remote_lsa_info.dns_domain.string,
2607 collisions=local_forest_collision)
2609 if remote_trust_info:
2610 self.outf.write("Setup remote forest trust information...\n")
2612 # get all information about the local trust (from the perspective of the remote domain)
2613 # this triggers netr_GetForestTrustInformation to our domain.
2614 # and lsaRSetForestTrustInformation() remotely, but new top level
2615 # names are disabled by default.
2616 remote_forest_info = remote_netlogon.netr_DsRGetForestTrustInformation(remote_netlogon_info.dc_unc,
2617 local_lsa_info.dns_domain.string,
2618 netlogon.DS_GFTI_UPDATE_TDO)
2619 except RuntimeError as error:
2620 raise self.RemoteRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2623 # here we try to enable all top level names
2624 remote_forest_collision = remote_lsa.lsaRSetForestTrustInformation(remote_policy,
2625 local_lsa_info.dns_domain,
2626 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2629 except RuntimeError as error:
2630 raise self.RemoteRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2632 self.write_forest_trust_info(remote_forest_info,
2633 tln=local_lsa_info.dns_domain.string,
2634 collisions=remote_forest_collision)
2636 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2637 self.outf.write("Validating outgoing trust...\n")
2639 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_netlogon_info.dc_unc,
2640 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2642 remote_lsa_info.dns_domain.string)
2643 except RuntimeError as error:
2644 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2646 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2647 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2649 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2650 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2651 local_trust_verify.trusted_dc_name,
2652 local_trust_verify.tc_connection_status[1],
2653 local_trust_verify.pdc_connection_status[1])
2655 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2656 local_trust_verify.trusted_dc_name,
2657 local_trust_verify.tc_connection_status[1],
2658 local_trust_verify.pdc_connection_status[1])
2660 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2661 raise CommandError(local_validation)
2663 self.outf.write("OK: %s\n" % local_validation)
2665 if remote_trust_info:
2666 if remote_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2667 self.outf.write("Validating incoming trust...\n")
2669 remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_netlogon_info.dc_unc,
2670 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2672 local_lsa_info.dns_domain.string)
2673 except RuntimeError as error:
2674 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2676 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2677 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2679 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2680 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2681 remote_trust_verify.trusted_dc_name,
2682 remote_trust_verify.tc_connection_status[1],
2683 remote_trust_verify.pdc_connection_status[1])
2685 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2686 remote_trust_verify.trusted_dc_name,
2687 remote_trust_verify.tc_connection_status[1],
2688 remote_trust_verify.pdc_connection_status[1])
2690 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
2691 raise CommandError(remote_validation)
2693 self.outf.write("OK: %s\n" % remote_validation)
2695 if remote_tdo_handle is not None:
2697 remote_lsa.Close(remote_tdo_handle)
2698 except RuntimeError as error:
2700 remote_tdo_handle = None
2701 if local_tdo_handle is not None:
2703 local_lsa.Close(local_tdo_handle)
2704 except RuntimeError as error:
2706 local_tdo_handle = None
2708 self.outf.write("Success.\n")
2711 class cmd_domain_trust_delete(DomainTrustCommand):
2712 """Delete a domain trust."""
2714 synopsis = "%prog DOMAIN [options]"
2716 takes_optiongroups = {
2717 "sambaopts": options.SambaOptions,
2718 "versionopts": options.VersionOptions,
2719 "credopts": options.CredentialsOptions,
2720 "localdcopts": LocalDCCredentialsOptions,
2724 Option("--delete-location", type="choice", metavar="LOCATION",
2725 choices=["local", "both"],
2726 help="Where to delete the trusted domain object: 'local' or 'both'.",
2727 dest='delete_location',
2731 takes_args = ["domain"]
2733 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2734 delete_location=None):
2736 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2737 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2738 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2740 if delete_location == "local":
2741 remote_policy_access = None
2743 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2744 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2745 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2747 local_server = self.setup_local_server(sambaopts, localdcopts)
2749 local_lsa = self.new_local_lsa_connection()
2750 except RuntimeError as error:
2751 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2754 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2755 except RuntimeError as error:
2756 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2758 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2759 local_lsa_info.name.string,
2760 local_lsa_info.dns_domain.string,
2761 local_lsa_info.sid))
2763 local_tdo_info = None
2764 local_tdo_handle = None
2765 remote_tdo_info = None
2766 remote_tdo_handle = None
2768 lsaString = lsa.String()
2770 lsaString.string = domain
2771 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2772 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2773 except NTSTATUSError as error:
2774 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2775 raise CommandError("Failed to find trust for domain '%s'" % domain)
2776 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2779 if remote_policy_access is not None:
2781 remote_server = self.setup_remote_server(credopts, domain)
2782 except RuntimeError as error:
2783 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2786 remote_lsa = self.new_remote_lsa_connection()
2787 except RuntimeError as error:
2788 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2791 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2792 except RuntimeError as error:
2793 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2795 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2796 remote_lsa_info.name.string,
2797 remote_lsa_info.dns_domain.string,
2798 remote_lsa_info.sid))
2800 if remote_lsa_info.sid != local_tdo_info.sid or \
2801 remote_lsa_info.name.string != local_tdo_info.netbios_name.string or \
2802 remote_lsa_info.dns_domain.string != local_tdo_info.domain_name.string:
2803 raise CommandError("LocalTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2804 local_tdo_info.netbios_name.string,
2805 local_tdo_info.domain_name.string,
2806 local_tdo_info.sid))
2809 lsaString.string = local_lsa_info.dns_domain.string
2810 remote_tdo_info = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2811 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2812 except NTSTATUSError as error:
2813 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2814 raise self.RemoteRuntimeError(self, error, "QueryTrustedDomainInfoByName(%s)" % (
2818 if remote_tdo_info is not None:
2819 if local_lsa_info.sid != remote_tdo_info.sid or \
2820 local_lsa_info.name.string != remote_tdo_info.netbios_name.string or \
2821 local_lsa_info.dns_domain.string != remote_tdo_info.domain_name.string:
2822 raise CommandError("RemoteTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2823 remote_tdo_info.netbios_name.string,
2824 remote_tdo_info.domain_name.string,
2825 remote_tdo_info.sid))
2827 if local_tdo_info is not None:
2829 lsaString.string = local_tdo_info.domain_name.string
2830 local_tdo_handle = local_lsa.OpenTrustedDomainByName(local_policy,
2832 security.SEC_STD_DELETE)
2833 except RuntimeError as error:
2834 raise self.LocalRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2837 local_lsa.DeleteObject(local_tdo_handle)
2838 local_tdo_handle = None
2840 if remote_tdo_info is not None:
2842 lsaString.string = remote_tdo_info.domain_name.string
2843 remote_tdo_handle = remote_lsa.OpenTrustedDomainByName(remote_policy,
2845 security.SEC_STD_DELETE)
2846 except RuntimeError as error:
2847 raise self.RemoteRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2850 if remote_tdo_handle is not None:
2852 remote_lsa.DeleteObject(remote_tdo_handle)
2853 remote_tdo_handle = None
2854 self.outf.write("RemoteTDO deleted.\n")
2855 except RuntimeError as error:
2856 self.outf.write("%s\n" % self.RemoteRuntimeError(self, error, "DeleteObject() failed"))
2858 if local_tdo_handle is not None:
2860 local_lsa.DeleteObject(local_tdo_handle)
2861 local_tdo_handle = None
2862 self.outf.write("LocalTDO deleted.\n")
2863 except RuntimeError as error:
2864 self.outf.write("%s\n" % self.LocalRuntimeError(self, error, "DeleteObject() failed"))
2868 class cmd_domain_trust_validate(DomainTrustCommand):
2869 """Validate a domain trust."""
2871 synopsis = "%prog DOMAIN [options]"
2873 takes_optiongroups = {
2874 "sambaopts": options.SambaOptions,
2875 "versionopts": options.VersionOptions,
2876 "credopts": options.CredentialsOptions,
2877 "localdcopts": LocalDCCredentialsOptions,
2881 Option("--validate-location", type="choice", metavar="LOCATION",
2882 choices=["local", "both"],
2883 help="Where to validate the trusted domain object: 'local' or 'both'.",
2884 dest='validate_location',
2888 takes_args = ["domain"]
2890 def run(self, domain, sambaopts=None, versionopts=None, credopts=None, localdcopts=None,
2891 validate_location=None):
2893 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2895 local_server = self.setup_local_server(sambaopts, localdcopts)
2897 local_lsa = self.new_local_lsa_connection()
2898 except RuntimeError as error:
2899 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2902 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2903 except RuntimeError as error:
2904 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2906 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2907 local_lsa_info.name.string,
2908 local_lsa_info.dns_domain.string,
2909 local_lsa_info.sid))
2912 lsaString = lsa.String()
2913 lsaString.string = domain
2914 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2915 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2916 except NTSTATUSError as error:
2917 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2918 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2920 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
2922 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
2923 local_tdo_info.netbios_name.string,
2924 local_tdo_info.domain_name.string,
2925 local_tdo_info.sid))
2928 local_netlogon = self.new_local_netlogon_connection()
2929 except RuntimeError as error:
2930 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2933 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_server,
2934 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2936 local_tdo_info.domain_name.string)
2937 except RuntimeError as error:
2938 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2940 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2941 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2943 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2944 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2945 local_trust_verify.trusted_dc_name,
2946 local_trust_verify.tc_connection_status[1],
2947 local_trust_verify.pdc_connection_status[1])
2949 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2950 local_trust_verify.trusted_dc_name,
2951 local_trust_verify.tc_connection_status[1],
2952 local_trust_verify.pdc_connection_status[1])
2954 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2955 raise CommandError(local_validation)
2957 self.outf.write("OK: %s\n" % local_validation)
2960 server = local_trust_verify.trusted_dc_name.replace('\\', '')
2961 domain_and_server = "%s\\%s" % (local_tdo_info.domain_name.string, server)
2962 local_trust_rediscover = local_netlogon.netr_LogonControl2Ex(local_server,
2963 netlogon.NETLOGON_CONTROL_REDISCOVER,
2966 except RuntimeError as error:
2967 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
2969 local_conn_status = self._uint32(local_trust_rediscover.tc_connection_status[0])
2970 local_rediscover = "LocalRediscover: DC[%s] CONNECTION[%s]" % (
2971 local_trust_rediscover.trusted_dc_name,
2972 local_trust_rediscover.tc_connection_status[1])
2974 if local_conn_status != werror.WERR_SUCCESS:
2975 raise CommandError(local_rediscover)
2977 self.outf.write("OK: %s\n" % local_rediscover)
2979 if validate_location != "local":
2981 remote_server = self.setup_remote_server(credopts, domain, require_pdc=False)
2982 except RuntimeError as error:
2983 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2986 remote_netlogon = self.new_remote_netlogon_connection()
2987 except RuntimeError as error:
2988 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2991 remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_server,
2992 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2994 local_lsa_info.dns_domain.string)
2995 except RuntimeError as error:
2996 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2998 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2999 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
3001 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
3002 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
3003 remote_trust_verify.trusted_dc_name,
3004 remote_trust_verify.tc_connection_status[1],
3005 remote_trust_verify.pdc_connection_status[1])
3007 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
3008 remote_trust_verify.trusted_dc_name,
3009 remote_trust_verify.tc_connection_status[1],
3010 remote_trust_verify.pdc_connection_status[1])
3012 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
3013 raise CommandError(remote_validation)
3015 self.outf.write("OK: %s\n" % remote_validation)
3018 server = remote_trust_verify.trusted_dc_name.replace('\\', '')
3019 domain_and_server = "%s\\%s" % (local_lsa_info.dns_domain.string, server)
3020 remote_trust_rediscover = remote_netlogon.netr_LogonControl2Ex(remote_server,
3021 netlogon.NETLOGON_CONTROL_REDISCOVER,
3024 except RuntimeError as error:
3025 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
3027 remote_conn_status = self._uint32(remote_trust_rediscover.tc_connection_status[0])
3029 remote_rediscover = "RemoteRediscover: DC[%s] CONNECTION[%s]" % (
3030 remote_trust_rediscover.trusted_dc_name,
3031 remote_trust_rediscover.tc_connection_status[1])
3033 if remote_conn_status != werror.WERR_SUCCESS:
3034 raise CommandError(remote_rediscover)
3036 self.outf.write("OK: %s\n" % remote_rediscover)
3040 class cmd_domain_trust_namespaces(DomainTrustCommand):
3041 """Manage forest trust namespaces."""
3043 synopsis = "%prog [DOMAIN] [options]"
3045 takes_optiongroups = {
3046 "sambaopts": options.SambaOptions,
3047 "versionopts": options.VersionOptions,
3048 "localdcopts": LocalDCCredentialsOptions,
3052 Option("--refresh", type="choice", metavar="check|store",
3053 choices=["check", "store", None],
3054 help="List and maybe store refreshed forest trust information: 'check' or 'store'.",
3057 Option("--enable-all", action="store_true",
3058 help="Try to update disabled entries, not allowed with --refresh=check.",
3061 Option("--enable-tln", action="append", metavar='DNSDOMAIN',
3062 help="Enable a top level name entry. Can be specified multiple times.",
3065 Option("--disable-tln", action="append", metavar='DNSDOMAIN',
3066 help="Disable a top level name entry. Can be specified multiple times.",
3069 Option("--add-tln-ex", action="append", metavar='DNSDOMAIN',
3070 help="Add a top level exclusion entry. Can be specified multiple times.",
3073 Option("--delete-tln-ex", action="append", metavar='DNSDOMAIN',
3074 help="Delete a top level exclusion entry. Can be specified multiple times.",
3075 dest='delete_tln_ex',
3077 Option("--enable-nb", action="append", metavar='NETBIOSDOMAIN',
3078 help="Enable a netbios name in a domain entry. Can be specified multiple times.",
3081 Option("--disable-nb", action="append", metavar='NETBIOSDOMAIN',
3082 help="Disable a netbios name in a domain entry. Can be specified multiple times.",
3085 Option("--enable-sid", action="append", metavar='DOMAINSID',
3086 help="Enable a SID in a domain entry. Can be specified multiple times.",
3087 dest='enable_sid_str',
3089 Option("--disable-sid", action="append", metavar='DOMAINSID',
3090 help="Disable a SID in a domain entry. Can be specified multiple times.",
3091 dest='disable_sid_str',
3093 Option("--add-upn-suffix", action="append", metavar='DNSDOMAIN',
3094 help="Add a new uPNSuffixes attribute for the local forest. Can be specified multiple times.",
3097 Option("--delete-upn-suffix", action="append", metavar='DNSDOMAIN',
3098 help="Delete an existing uPNSuffixes attribute of the local forest. Can be specified multiple times.",
3101 Option("--add-spn-suffix", action="append", metavar='DNSDOMAIN',
3102 help="Add a new msDS-SPNSuffixes attribute for the local forest. Can be specified multiple times.",
3105 Option("--delete-spn-suffix", action="append", metavar='DNSDOMAIN',
3106 help="Delete an existing msDS-SPNSuffixes attribute of the local forest. Can be specified multiple times.",
3111 takes_args = ["domain?"]
3113 def run(self, domain=None, sambaopts=None, localdcopts=None, versionopts=None,
3114 refresh=None, enable_all=False,
3115 enable_tln=[], disable_tln=[], add_tln_ex=[], delete_tln_ex=[],
3116 enable_sid_str=[], disable_sid_str=[], enable_nb=[], disable_nb=[],
3117 add_upn=[], delete_upn=[], add_spn=[], delete_spn=[]):
3119 require_update = False
3122 if refresh == "store":
3123 raise CommandError("--refresh=%s not allowed without DOMAIN" % refresh)
3126 raise CommandError("--enable-all not allowed without DOMAIN")
3128 if len(enable_tln) > 0:
3129 raise CommandError("--enable-tln not allowed without DOMAIN")
3130 if len(disable_tln) > 0:
3131 raise CommandError("--disable-tln not allowed without DOMAIN")
3133 if len(add_tln_ex) > 0:
3134 raise CommandError("--add-tln-ex not allowed without DOMAIN")
3135 if len(delete_tln_ex) > 0:
3136 raise CommandError("--delete-tln-ex not allowed without DOMAIN")
3138 if len(enable_nb) > 0:
3139 raise CommandError("--enable-nb not allowed without DOMAIN")
3140 if len(disable_nb) > 0:
3141 raise CommandError("--disable-nb not allowed without DOMAIN")
3143 if len(enable_sid_str) > 0:
3144 raise CommandError("--enable-sid not allowed without DOMAIN")
3145 if len(disable_sid_str) > 0:
3146 raise CommandError("--disable-sid not allowed without DOMAIN")
3148 if len(add_upn) > 0:
3150 if not n.startswith("*."):
3152 raise CommandError("value[%s] specified for --add-upn-suffix should not include with '*.'" % n)
3153 require_update = True
3154 if len(delete_upn) > 0:
3155 for n in delete_upn:
3156 if not n.startswith("*."):
3158 raise CommandError("value[%s] specified for --delete-upn-suffix should not include with '*.'" % n)
3159 require_update = True
3161 for d in delete_upn:
3162 if a.lower() != d.lower():
3164 raise CommandError("value[%s] specified for --add-upn-suffix and --delete-upn-suffix" % a)
3166 if len(add_spn) > 0:
3168 if not n.startswith("*."):
3170 raise CommandError("value[%s] specified for --add-spn-suffix should not include with '*.'" % n)
3171 require_update = True
3172 if len(delete_spn) > 0:
3173 for n in delete_spn:
3174 if not n.startswith("*."):
3176 raise CommandError("value[%s] specified for --delete-spn-suffix should not include with '*.'" % n)
3177 require_update = True
3179 for d in delete_spn:
3180 if a.lower() != d.lower():
3182 raise CommandError("value[%s] specified for --add-spn-suffix and --delete-spn-suffix" % a)
3184 if len(add_upn) > 0:
3185 raise CommandError("--add-upn-suffix not allowed together with DOMAIN")
3186 if len(delete_upn) > 0:
3187 raise CommandError("--delete-upn-suffix not allowed together with DOMAIN")
3188 if len(add_spn) > 0:
3189 raise CommandError("--add-spn-suffix not allowed together with DOMAIN")
3190 if len(delete_spn) > 0:
3191 raise CommandError("--delete-spn-suffix not allowed together with DOMAIN")
3193 if refresh is not None:
3194 if refresh == "store":
3195 require_update = True
3197 if enable_all and refresh != "store":
3198 raise CommandError("--enable-all not allowed together with --refresh=%s" % refresh)
3200 if len(enable_tln) > 0:
3201 raise CommandError("--enable-tln not allowed together with --refresh")
3202 if len(disable_tln) > 0:
3203 raise CommandError("--disable-tln not allowed together with --refresh")
3205 if len(add_tln_ex) > 0:
3206 raise CommandError("--add-tln-ex not allowed together with --refresh")
3207 if len(delete_tln_ex) > 0:
3208 raise CommandError("--delete-tln-ex not allowed together with --refresh")
3210 if len(enable_nb) > 0:
3211 raise CommandError("--enable-nb not allowed together with --refresh")
3212 if len(disable_nb) > 0:
3213 raise CommandError("--disable-nb not allowed together with --refresh")
3215 if len(enable_sid_str) > 0:
3216 raise CommandError("--enable-sid not allowed together with --refresh")
3217 if len(disable_sid_str) > 0:
3218 raise CommandError("--disable-sid not allowed together with --refresh")
3221 require_update = True
3223 if len(enable_tln) > 0:
3224 raise CommandError("--enable-tln not allowed together with --enable-all")
3226 if len(enable_nb) > 0:
3227 raise CommandError("--enable-nb not allowed together with --enable-all")
3229 if len(enable_sid_str) > 0:
3230 raise CommandError("--enable-sid not allowed together with --enable-all")
3232 if len(enable_tln) > 0:
3233 require_update = True
3234 if len(disable_tln) > 0:
3235 require_update = True
3236 for e in enable_tln:
3237 for d in disable_tln:
3238 if e.lower() != d.lower():
3240 raise CommandError("value[%s] specified for --enable-tln and --disable-tln" % e)
3242 if len(add_tln_ex) > 0:
3243 for n in add_tln_ex:
3244 if not n.startswith("*."):
3246 raise CommandError("value[%s] specified for --add-tln-ex should not include with '*.'" % n)
3247 require_update = True
3248 if len(delete_tln_ex) > 0:
3249 for n in delete_tln_ex:
3250 if not n.startswith("*."):
3252 raise CommandError("value[%s] specified for --delete-tln-ex should not include with '*.'" % n)
3253 require_update = True
3254 for a in add_tln_ex:
3255 for d in delete_tln_ex:
3256 if a.lower() != d.lower():
3258 raise CommandError("value[%s] specified for --add-tln-ex and --delete-tln-ex" % a)
3260 if len(enable_nb) > 0:
3261 require_update = True
3262 if len(disable_nb) > 0:
3263 require_update = True
3265 for d in disable_nb:
3266 if e.upper() != d.upper():
3268 raise CommandError("value[%s] specified for --enable-nb and --disable-nb" % e)
3271 for s in enable_sid_str:
3273 sid = security.dom_sid(s)
3274 except TypeError as error:
3275 raise CommandError("value[%s] specified for --enable-sid is not a valid SID" % s)
3276 enable_sid.append(sid)
3278 for s in disable_sid_str:
3280 sid = security.dom_sid(s)
3281 except TypeError as error:
3282 raise CommandError("value[%s] specified for --disable-sid is not a valid SID" % s)
3283 disable_sid.append(sid)
3284 if len(enable_sid) > 0:
3285 require_update = True
3286 if len(disable_sid) > 0:
3287 require_update = True
3288 for e in enable_sid:
3289 for d in disable_sid:
3292 raise CommandError("value[%s] specified for --enable-sid and --disable-sid" % e)
3294 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
3296 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
3298 local_server = self.setup_local_server(sambaopts, localdcopts)
3300 local_lsa = self.new_local_lsa_connection()
3301 except RuntimeError as error:
3302 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
3305 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
3306 except RuntimeError as error:
3307 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
3309 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
3310 local_lsa_info.name.string,
3311 local_lsa_info.dns_domain.string,
3312 local_lsa_info.sid))
3316 local_netlogon = self.new_local_netlogon_connection()
3317 except RuntimeError as error:
3318 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3321 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3322 except RuntimeError as error:
3323 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3325 if local_netlogon_info.domain_name != local_netlogon_info.forest_name:
3326 raise CommandError("The local domain [%s] is not the forest root [%s]" % (
3327 local_netlogon_info.domain_name,
3328 local_netlogon_info.forest_name))
3331 # get all information about our own forest
3332 own_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3334 except RuntimeError as error:
3335 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
3336 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3339 if self.check_runtime_error(error, werror.WERR_INVALID_FUNCTION):
3340 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3343 if self.check_runtime_error(error, werror.WERR_NERR_ACFNOTLOADED):
3344 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3347 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3349 self.outf.write("Own forest trust information...\n")
3350 self.write_forest_trust_info(own_forest_info,
3351 tln=local_lsa_info.dns_domain.string)
3354 local_samdb = self.new_local_ldap_connection()
3355 except RuntimeError as error:
3356 raise self.LocalRuntimeError(self, error, "failed to connect to SamDB")
3358 local_partitions_dn = "CN=Partitions,%s" % str(local_samdb.get_config_basedn())
3359 attrs = ['uPNSuffixes', 'msDS-SPNSuffixes']
3361 msgs = local_samdb.search(base=local_partitions_dn,
3362 scope=ldb.SCOPE_BASE,
3363 expression="(objectClass=crossRefContainer)",
3365 stored_msg = msgs[0]
3366 except ldb.LdbError as error:
3367 raise self.LocalLdbError(self, error, "failed to search partition dn")
3369 stored_upn_vals = []
3370 if 'uPNSuffixes' in stored_msg:
3371 stored_upn_vals.extend(stored_msg['uPNSuffixes'])
3373 stored_spn_vals = []
3374 if 'msDS-SPNSuffixes' in stored_msg:
3375 stored_spn_vals.extend(stored_msg['msDS-SPNSuffixes'])
3377 self.outf.write("Stored uPNSuffixes attributes[%d]:\n" % len(stored_upn_vals))
3378 for v in stored_upn_vals:
3379 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3380 self.outf.write("Stored msDS-SPNSuffixes attributes[%d]:\n" % len(stored_spn_vals))
3381 for v in stored_spn_vals:
3382 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3384 if not require_update:
3388 update_upn_vals = []
3389 update_upn_vals.extend(stored_upn_vals)
3392 update_spn_vals = []
3393 update_spn_vals.extend(stored_spn_vals)
3397 for i in xrange(0, len(update_upn_vals)):
3398 v = update_upn_vals[i]
3399 if v.lower() != upn.lower():
3404 raise CommandError("Entry already present for value[%s] specified for --add-upn-suffix" % upn)
3405 update_upn_vals.append(upn)
3408 for upn in delete_upn:
3410 for i in xrange(0, len(update_upn_vals)):
3411 v = update_upn_vals[i]
3412 if v.lower() != upn.lower():
3417 raise CommandError("Entry not found for value[%s] specified for --delete-upn-suffix" % upn)
3419 update_upn_vals.pop(idx)
3424 for i in xrange(0, len(update_spn_vals)):
3425 v = update_spn_vals[i]
3426 if v.lower() != spn.lower():
3431 raise CommandError("Entry already present for value[%s] specified for --add-spn-suffix" % spn)
3432 update_spn_vals.append(spn)
3435 for spn in delete_spn:
3437 for i in xrange(0, len(update_spn_vals)):
3438 v = update_spn_vals[i]
3439 if v.lower() != spn.lower():
3444 raise CommandError("Entry not found for value[%s] specified for --delete-spn-suffix" % spn)
3446 update_spn_vals.pop(idx)
3449 self.outf.write("Update uPNSuffixes attributes[%d]:\n" % len(update_upn_vals))
3450 for v in update_upn_vals:
3451 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3452 self.outf.write("Update msDS-SPNSuffixes attributes[%d]:\n" % len(update_spn_vals))
3453 for v in update_spn_vals:
3454 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3456 update_msg = ldb.Message()
3457 update_msg.dn = stored_msg.dn
3460 update_msg['uPNSuffixes'] = ldb.MessageElement(update_upn_vals,
3461 ldb.FLAG_MOD_REPLACE,
3464 update_msg['msDS-SPNSuffixes'] = ldb.MessageElement(update_spn_vals,
3465 ldb.FLAG_MOD_REPLACE,
3468 local_samdb.modify(update_msg)
3469 except ldb.LdbError as error:
3470 raise self.LocalLdbError(self, error, "failed to update partition dn")
3473 stored_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3475 except RuntimeError as error:
3476 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3478 self.outf.write("Stored forest trust information...\n")
3479 self.write_forest_trust_info(stored_forest_info,
3480 tln=local_lsa_info.dns_domain.string)
3484 lsaString = lsa.String()
3485 lsaString.string = domain
3486 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
3487 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
3488 except NTSTATUSError as error:
3489 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
3490 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
3492 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
3494 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
3495 local_tdo_info.netbios_name.string,
3496 local_tdo_info.domain_name.string,
3497 local_tdo_info.sid))
3499 if not local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
3500 raise CommandError("trusted domain object for domain [%s] is not marked as FOREST_TRANSITIVE." % domain)
3502 if refresh is not None:
3504 local_netlogon = self.new_local_netlogon_connection()
3505 except RuntimeError as error:
3506 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3509 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3510 except RuntimeError as error:
3511 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3513 lsa_update_check = 1
3514 if refresh == "store":
3515 netlogon_update_tdo = netlogon.DS_GFTI_UPDATE_TDO
3517 lsa_update_check = 0
3519 netlogon_update_tdo = 0
3522 # get all information about the remote trust
3523 # this triggers netr_GetForestTrustInformation to the remote domain
3524 # and lsaRSetForestTrustInformation() locally, but new top level
3525 # names are disabled by default.
3526 fresh_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3527 local_tdo_info.domain_name.string,
3528 netlogon_update_tdo)
3529 except RuntimeError as error:
3530 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3533 fresh_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3534 local_tdo_info.domain_name,
3535 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3538 except RuntimeError as error:
3539 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3541 self.outf.write("Fresh forest trust information...\n")
3542 self.write_forest_trust_info(fresh_forest_info,
3543 tln=local_tdo_info.domain_name.string,
3544 collisions=fresh_forest_collision)
3546 if refresh == "store":
3548 lsaString = lsa.String()
3549 lsaString.string = local_tdo_info.domain_name.string
3550 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3552 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3553 except RuntimeError as error:
3554 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3556 self.outf.write("Stored forest trust information...\n")
3557 self.write_forest_trust_info(stored_forest_info,
3558 tln=local_tdo_info.domain_name.string)
3563 # The none --refresh path
3567 lsaString = lsa.String()
3568 lsaString.string = local_tdo_info.domain_name.string
3569 local_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3571 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3572 except RuntimeError as error:
3573 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3575 self.outf.write("Local forest trust information...\n")
3576 self.write_forest_trust_info(local_forest_info,
3577 tln=local_tdo_info.domain_name.string)
3579 if not require_update:
3583 entries.extend(local_forest_info.entries)
3584 update_forest_info = lsa.ForestTrustInformation()
3585 update_forest_info.count = len(entries)
3586 update_forest_info.entries = entries
3589 for i in xrange(0, len(update_forest_info.entries)):
3590 r = update_forest_info.entries[i]
3591 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3593 if update_forest_info.entries[i].flags == 0:
3595 update_forest_info.entries[i].time = 0
3596 update_forest_info.entries[i].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3597 for i in xrange(0, len(update_forest_info.entries)):
3598 r = update_forest_info.entries[i]
3599 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3601 if update_forest_info.entries[i].flags == 0:
3603 update_forest_info.entries[i].time = 0
3604 update_forest_info.entries[i].flags &= ~lsa.LSA_NB_DISABLED_MASK
3605 update_forest_info.entries[i].flags &= ~lsa.LSA_SID_DISABLED_MASK
3607 for tln in enable_tln:
3609 for i in xrange(0, len(update_forest_info.entries)):
3610 r = update_forest_info.entries[i]
3611 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3613 if r.forest_trust_data.string.lower() != tln.lower():
3618 raise CommandError("Entry not found for value[%s] specified for --enable-tln" % tln)
3619 if not update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_MASK:
3620 raise CommandError("Entry found for value[%s] specified for --enable-tln is already enabled" % tln)
3621 update_forest_info.entries[idx].time = 0
3622 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3624 for tln in disable_tln:
3626 for i in xrange(0, len(update_forest_info.entries)):
3627 r = update_forest_info.entries[i]
3628 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3630 if r.forest_trust_data.string.lower() != tln.lower():
3635 raise CommandError("Entry not found for value[%s] specified for --disable-tln" % tln)
3636 if update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_ADMIN:
3637 raise CommandError("Entry found for value[%s] specified for --disable-tln is already disabled" % tln)
3638 update_forest_info.entries[idx].time = 0
3639 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3640 update_forest_info.entries[idx].flags |= lsa.LSA_TLN_DISABLED_ADMIN
3642 for tln_ex in add_tln_ex:
3644 for i in xrange(0, len(update_forest_info.entries)):
3645 r = update_forest_info.entries[i]
3646 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3648 if r.forest_trust_data.string.lower() != tln_ex.lower():
3653 raise CommandError("Entry already present for value[%s] specified for --add-tln-ex" % tln_ex)
3655 tln_dot = ".%s" % tln_ex.lower()
3657 for i in xrange(0, len(update_forest_info.entries)):
3658 r = update_forest_info.entries[i]
3659 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3661 r_dot = ".%s" % r.forest_trust_data.string.lower()
3662 if tln_dot == r_dot:
3663 raise CommandError("TLN entry present for value[%s] specified for --add-tln-ex" % tln_ex)
3664 if not tln_dot.endswith(r_dot):
3670 raise CommandError("No TLN parent present for value[%s] specified for --add-tln-ex" % tln_ex)
3672 r = lsa.ForestTrustRecord()
3673 r.type = lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX
3676 r.forest_trust_data.string = tln_ex
3679 entries.extend(update_forest_info.entries)
3680 entries.insert(idx + 1, r)
3681 update_forest_info.count = len(entries)
3682 update_forest_info.entries = entries
3684 for tln_ex in delete_tln_ex:
3686 for i in xrange(0, len(update_forest_info.entries)):
3687 r = update_forest_info.entries[i]
3688 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3690 if r.forest_trust_data.string.lower() != tln_ex.lower():
3695 raise CommandError("Entry not found for value[%s] specified for --delete-tln-ex" % tln_ex)
3698 entries.extend(update_forest_info.entries)
3700 update_forest_info.count = len(entries)
3701 update_forest_info.entries = entries
3703 for nb in enable_nb:
3705 for i in xrange(0, len(update_forest_info.entries)):
3706 r = update_forest_info.entries[i]
3707 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3709 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3714 raise CommandError("Entry not found for value[%s] specified for --enable-nb" % nb)
3715 if not update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_MASK:
3716 raise CommandError("Entry found for value[%s] specified for --enable-nb is already enabled" % nb)
3717 update_forest_info.entries[idx].time = 0
3718 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3720 for nb in disable_nb:
3722 for i in xrange(0, len(update_forest_info.entries)):
3723 r = update_forest_info.entries[i]
3724 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3726 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3731 raise CommandError("Entry not found for value[%s] specified for --delete-nb" % nb)
3732 if update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_ADMIN:
3733 raise CommandError("Entry found for value[%s] specified for --disable-nb is already disabled" % nb)
3734 update_forest_info.entries[idx].time = 0
3735 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3736 update_forest_info.entries[idx].flags |= lsa.LSA_NB_DISABLED_ADMIN
3738 for sid in enable_sid:
3740 for i in xrange(0, len(update_forest_info.entries)):
3741 r = update_forest_info.entries[i]
3742 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3744 if r.forest_trust_data.domain_sid != sid:
3749 raise CommandError("Entry not found for value[%s] specified for --enable-sid" % sid)
3750 if not update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_MASK:
3751 raise CommandError("Entry found for value[%s] specified for --enable-sid is already enabled" % nb)
3752 update_forest_info.entries[idx].time = 0
3753 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3755 for sid in disable_sid:
3757 for i in xrange(0, len(update_forest_info.entries)):
3758 r = update_forest_info.entries[i]
3759 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3761 if r.forest_trust_data.domain_sid != sid:
3766 raise CommandError("Entry not found for value[%s] specified for --delete-sid" % sid)
3767 if update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_ADMIN:
3768 raise CommandError("Entry found for value[%s] specified for --disable-sid is already disabled" % nb)
3769 update_forest_info.entries[idx].time = 0
3770 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3771 update_forest_info.entries[idx].flags |= lsa.LSA_SID_DISABLED_ADMIN
3774 update_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3775 local_tdo_info.domain_name,
3776 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3777 update_forest_info, 0)
3778 except RuntimeError as error:
3779 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3781 self.outf.write("Updated forest trust information...\n")
3782 self.write_forest_trust_info(update_forest_info,
3783 tln=local_tdo_info.domain_name.string,
3784 collisions=update_forest_collision)
3787 lsaString = lsa.String()
3788 lsaString.string = local_tdo_info.domain_name.string
3789 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3791 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3792 except RuntimeError as error:
3793 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3795 self.outf.write("Stored forest trust information...\n")
3796 self.write_forest_trust_info(stored_forest_info,
3797 tln=local_tdo_info.domain_name.string)
3800 class cmd_domain_tombstones_expunge(Command):
3801 """Expunge tombstones from the database.
3803 This command expunges tombstones from the database."""
3804 synopsis = "%prog NC [NC [...]] [options]"
3807 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3808 metavar="URL", dest="H"),
3809 Option("--current-time",
3810 help="The current time to evaluate the tombstone lifetime from, expressed as YYYY-MM-DD",
3812 Option("--tombstone-lifetime", help="Number of days a tombstone should be preserved for", type=int),
3815 takes_args = ["nc*"]
3817 takes_optiongroups = {
3818 "sambaopts": options.SambaOptions,
3819 "credopts": options.CredentialsOptions,
3820 "versionopts": options.VersionOptions,
3823 def run(self, *ncs, **kwargs):
3824 sambaopts = kwargs.get("sambaopts")
3825 credopts = kwargs.get("credopts")
3826 versionpts = kwargs.get("versionopts")
3828 current_time_string = kwargs.get("current_time")
3829 tombstone_lifetime = kwargs.get("tombstone_lifetime")
3830 lp = sambaopts.get_loadparm()
3831 creds = credopts.get_credentials(lp)
3832 samdb = SamDB(url=H, session_info=system_session(),
3833 credentials=creds, lp=lp)
3835 if current_time_string is not None:
3836 current_time_obj = time.strptime(current_time_string, "%Y-%m-%d")
3837 current_time = long(time.mktime(current_time_obj))
3840 current_time = long(time.time())
3843 res = samdb.search(expression="", base="", scope=ldb.SCOPE_BASE,
3844 attrs=["namingContexts"])
3847 for nc in res[0]["namingContexts"]:
3852 started_transaction = False
3854 samdb.transaction_start()
3855 started_transaction = True
3857 removed_links) = samdb.garbage_collect_tombstones(ncs,
3858 current_time=current_time,
3859 tombstone_lifetime=tombstone_lifetime)
3861 except Exception as err:
3862 if started_transaction:
3863 samdb.transaction_cancel()
3864 raise CommandError("Failed to expunge / garbage collect tombstones", err)
3866 samdb.transaction_commit()
3868 self.outf.write("Removed %d objects and %d links successfully\n"
3869 % (removed_objects, removed_links))
3873 class cmd_domain_trust(SuperCommand):
3874 """Domain and forest trust management."""
3877 subcommands["list"] = cmd_domain_trust_list()
3878 subcommands["show"] = cmd_domain_trust_show()
3879 subcommands["create"] = cmd_domain_trust_create()
3880 subcommands["delete"] = cmd_domain_trust_delete()
3881 subcommands["validate"] = cmd_domain_trust_validate()
3882 subcommands["namespaces"] = cmd_domain_trust_namespaces()
3884 class cmd_domain_tombstones(SuperCommand):
3885 """Domain tombstone and recycled object management."""
3888 subcommands["expunge"] = cmd_domain_tombstones_expunge()
3890 class ldif_schema_update:
3891 """Helper class for applying LDIF schema updates"""
3894 self.is_defunct = False
3895 self.unknown_oid = None
3899 def _ldap_schemaUpdateNow(self, samdb):
3903 add: schemaUpdateNow
3906 samdb.modify_ldif(ldif)
3908 def can_ignore_failure(self, error):
3909 """Checks if we can safely ignore failure to apply an LDIF update"""
3910 (num, errstr) = error.args
3912 # Microsoft has marked objects as defunct that Samba doesn't know about
3913 if num == ldb.ERR_NO_SUCH_OBJECT and self.is_defunct:
3914 print("Defunct object %s doesn't exist, skipping" % self.dn)
3916 elif self.unknown_oid is not None:
3917 print("Skipping unknown OID %s for object %s" %(self.unknown_oid, self.dn))
3922 def apply(self, samdb):
3923 """Applies a single LDIF update to the schema"""
3927 samdb.modify_ldif(self.ldif, controls=['relax:0'])
3928 except ldb.LdbError as e:
3929 if e.args[0] == ldb.ERR_INVALID_ATTRIBUTE_SYNTAX:
3931 # REFRESH after a failed change
3933 # Otherwise the OID-to-attribute mapping in
3934 # _apply_updates_in_file() won't work, because it
3935 # can't lookup the new OID in the schema
3936 self._ldap_schemaUpdateNow(samdb)
3938 samdb.modify_ldif(self.ldif, controls=['relax:0'])
3941 except ldb.LdbError as e:
3942 if self.can_ignore_failure(e):
3945 print("Exception: %s" % e)
3946 print("Encountered while trying to apply the following LDIF")
3947 print("----------------------------------------------------")
3948 print("%s" % self.ldif)
3954 class cmd_domain_schema_upgrade(Command):
3955 """Domain schema upgrading"""
3957 synopsis = "%prog [options]"
3959 takes_optiongroups = {
3960 "sambaopts": options.SambaOptions,
3961 "versionopts": options.VersionOptions,
3962 "credopts": options.CredentialsOptions,
3966 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3967 metavar="URL", dest="H"),
3968 Option("--quiet", help="Be quiet", action="store_true"),
3969 Option("--verbose", help="Be verbose", action="store_true"),
3970 Option("--schema", type="choice", metavar="SCHEMA",
3971 choices=["2012", "2012_R2"],
3972 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
3974 Option("--ldf-file", type=str, default=None,
3975 help="Just apply the schema updates in the adprep/.LDF file(s) specified"),
3976 Option("--base-dir", type=str, default=None,
3977 help="Location of ldf files Default is ${SETUPDIR}/adprep.")
3980 def _apply_updates_in_file(self, samdb, ldif_file):
3982 Applies a series of updates specified in an .LDIF file. The .LDIF file
3983 is based on the adprep Schema updates provided by Microsoft.
3986 ldif_op = ldif_schema_update()
3988 # parse the file line by line and work out each update operation to apply
3989 for line in ldif_file:
3991 line = line.rstrip()
3993 # the operations in the .LDIF file are separated by blank lines. If
3994 # we hit a blank line, try to apply the update we've parsed so far
3997 # keep going if we haven't parsed anything yet
3998 if ldif_op.ldif == '':
4001 # Apply the individual change
4002 count += ldif_op.apply(samdb)
4004 # start storing the next operation from scratch again
4005 ldif_op = ldif_schema_update()
4008 # replace the placeholder domain name in the .ldif file with the real domain
4009 if line.upper().endswith('DC=X'):
4010 line = line[:-len('DC=X')] + str(samdb.get_default_basedn())
4011 elif line.upper().endswith('CN=X'):
4012 line = line[:-len('CN=X')] + str(samdb.get_default_basedn())
4014 values = line.split(':')
4016 if values[0].lower() == 'dn':
4017 ldif_op.dn = values[1].strip()
4019 # replace the Windows-specific operation with the Samba one
4020 if values[0].lower() == 'changetype':
4021 line = line.lower().replace(': ntdsschemaadd',
4023 line = line.lower().replace(': ntdsschemamodify',
4026 if values[0].lower() in ['rdnattid', 'subclassof',
4027 'systemposssuperiors',
4029 'systemauxiliaryclass']:
4032 # The Microsoft updates contain some OIDs we don't recognize.
4033 # Query the DB to see if we can work out the OID this update is
4034 # referring to. If we find a match, then replace the OID with
4035 # the ldapDisplayname
4037 res = samdb.search(base=samdb.get_schema_basedn(),
4038 expression="(|(attributeId=%s)(governsId=%s))" %
4040 attrs=['ldapDisplayName'])
4043 ldif_op.unknown_oid = value
4045 display_name = res[0]['ldapDisplayName'][0]
4046 line = line.replace(value, ' ' + display_name)
4048 # Microsoft has marked objects as defunct that Samba doesn't know about
4049 if values[0].lower() == 'isdefunct' and values[1].strip().lower() == 'true':
4050 ldif_op.is_defunct = True
4052 # Samba has added the showInAdvancedViewOnly attribute to all objects,
4053 # so rather than doing an add, we need to do a replace
4054 if values[0].lower() == 'add' and values[1].strip().lower() == 'showinadvancedviewonly':
4055 line = 'replace: showInAdvancedViewOnly'
4057 # Add the line to the current LDIF operation (including the newline
4058 # we stripped off at the start of the loop)
4059 ldif_op.ldif += line + '\n'
4064 def _apply_update(self, samdb, update_file, base_dir):
4065 """Wrapper function for parsing an LDIF file and applying the updates"""
4067 print("Applying %s updates..." % update_file)
4071 ldif_file = open(os.path.join(base_dir, update_file))
4073 count = self._apply_updates_in_file(samdb, ldif_file)
4079 print("%u changes applied" % count)
4083 def run(self, **kwargs):
4084 from samba.ms_schema_markdown import read_ms_markdown
4085 from samba.schema import Schema
4087 updates_allowed_overriden = False
4088 sambaopts = kwargs.get("sambaopts")
4089 credopts = kwargs.get("credopts")
4090 versionpts = kwargs.get("versionopts")
4091 lp = sambaopts.get_loadparm()
4092 creds = credopts.get_credentials(lp)
4094 target_schema = kwargs.get("schema")
4095 ldf_files = kwargs.get("ldf_file")
4096 base_dir = kwargs.get("base_dir")
4100 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4102 # we're not going to get far if the config doesn't allow schema updates
4103 if lp.get("dsdb:schema update allowed") is None:
4104 lp.set("dsdb:schema update allowed", "yes")
4105 print("Temporarily overriding 'dsdb:schema update allowed' setting")
4106 updates_allowed_overriden = True
4108 own_dn = ldb.Dn(samdb, samdb.get_dsServiceName())
4109 master = get_fsmo_roleowner(samdb, str(samdb.get_schema_basedn()),
4111 if own_dn != master:
4112 raise CommandError("This server is not the schema master.")
4114 # if specific LDIF files were specified, just apply them
4116 schema_updates = ldf_files.split(",")
4120 # work out the version of the target schema we're upgrading to
4121 end = Schema.get_version(target_schema)
4123 # work out the version of the schema we're currently using
4124 res = samdb.search(base=samdb.get_schema_basedn(),
4125 scope=ldb.SCOPE_BASE, attrs=['objectVersion'])
4128 raise CommandError('Could not determine current schema version')
4129 start = int(res[0]['objectVersion'][0]) + 1
4131 diff_dir = setup_path("adprep/WindowsServerDocs")
4132 if base_dir is None:
4133 # Read from the Schema-Updates.md file
4134 temp_folder = tempfile.mkdtemp()
4136 update_file = setup_path("adprep/WindowsServerDocs/Schema-Updates.md")
4139 read_ms_markdown(update_file, temp_folder)
4140 except Exception as e:
4141 print("Exception in markdown parsing: %s" % e)
4142 shutil.rmtree(temp_folder)
4143 raise CommandError('Failed to upgrade schema')
4145 base_dir = temp_folder
4147 for version in range(start, end + 1):
4148 update = 'Sch%d.ldf' % version
4149 schema_updates.append(update)
4151 # Apply patches if we parsed the Schema-Updates.md file
4152 diff = os.path.abspath(os.path.join(diff_dir, update + '.diff'))
4153 if temp_folder and os.path.exists(diff):
4155 p = subprocess.Popen(['patch', update, '-i', diff],
4156 stdout=subprocess.PIPE,
4157 stderr=subprocess.PIPE, cwd=temp_folder)
4158 except (OSError, IOError):
4159 shutil.rmtree(temp_folder)
4160 raise CommandError("Failed to upgrade schema. Check if 'patch' is installed.")
4162 stdout, stderr = p.communicate()
4165 print("Exception in patch: %s\n%s" % (stdout, stderr))
4166 shutil.rmtree(temp_folder)
4167 raise CommandError('Failed to upgrade schema')
4169 print("Patched %s using %s" % (update, diff))
4171 if base_dir is None:
4172 base_dir = setup_path("adprep")
4174 samdb.transaction_start()
4176 error_encountered = False
4179 # Apply the schema updates needed to move to the new schema version
4180 for ldif_file in schema_updates:
4181 count += self._apply_update(samdb, ldif_file, base_dir)
4184 samdb.transaction_commit()
4185 print("Schema successfully updated")
4187 print("No changes applied to schema")
4188 samdb.transaction_cancel()
4189 except Exception as e:
4190 print("Exception: %s" % e)
4191 print("Error encountered, aborting schema upgrade")
4192 samdb.transaction_cancel()
4193 error_encountered = True
4195 if updates_allowed_overriden:
4196 lp.set("dsdb:schema update allowed", "no")
4199 shutil.rmtree(temp_folder)
4201 if error_encountered:
4202 raise CommandError('Failed to upgrade schema')
4204 class cmd_domain_functional_prep(Command):
4205 """Domain functional level preparation"""
4207 synopsis = "%prog [options]"
4209 takes_optiongroups = {
4210 "sambaopts": options.SambaOptions,
4211 "versionopts": options.VersionOptions,
4212 "credopts": options.CredentialsOptions,
4216 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
4217 metavar="URL", dest="H"),
4218 Option("--quiet", help="Be quiet", action="store_true"),
4219 Option("--verbose", help="Be verbose", action="store_true"),
4220 Option("--function-level", type="choice", metavar="FUNCTION_LEVEL",
4221 choices=["2008_R2", "2012", "2012_R2"],
4222 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
4224 Option("--forest-prep", action="store_true",
4225 help="Run the forest prep (by default, both the domain and forest prep are run)."),
4226 Option("--domain-prep", action="store_true",
4227 help="Run the domain prep (by default, both the domain and forest prep are run).")
4230 def run(self, **kwargs):
4231 updates_allowed_overriden = False
4232 sambaopts = kwargs.get("sambaopts")
4233 credopts = kwargs.get("credopts")
4234 versionpts = kwargs.get("versionopts")
4235 lp = sambaopts.get_loadparm()
4236 creds = credopts.get_credentials(lp)
4238 target_level = string_version_to_constant[kwargs.get("function_level")]
4239 forest_prep = kwargs.get("forest_prep")
4240 domain_prep = kwargs.get("domain_prep")
4242 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4244 # we're not going to get far if the config doesn't allow schema updates
4245 if lp.get("dsdb:schema update allowed") is None:
4246 lp.set("dsdb:schema update allowed", "yes")
4247 print("Temporarily overriding 'dsdb:schema update allowed' setting")
4248 updates_allowed_overriden = True
4250 if forest_prep is None and domain_prep is None:
4254 own_dn = ldb.Dn(samdb, samdb.get_dsServiceName())
4256 master = get_fsmo_roleowner(samdb, str(samdb.get_schema_basedn()),
4258 if own_dn != master:
4259 raise CommandError("This server is not the schema master.")
4262 domain_dn = samdb.domain_dn()
4263 infrastructure_dn = "CN=Infrastructure," + domain_dn
4264 master = get_fsmo_roleowner(samdb, infrastructure_dn,
4266 if own_dn != master:
4267 raise CommandError("This server is not the infrastructure master.")
4270 samdb.transaction_start()
4271 error_encountered = False
4273 from samba.forest_update import ForestUpdate
4274 forest = ForestUpdate(samdb, fix=True)
4276 forest.check_updates_iterator([53, 79, 80, 81, 82, 83])
4277 forest.check_updates_functional_level(target_level,
4278 DS_DOMAIN_FUNCTION_2008_R2,
4279 update_revision=True)
4281 samdb.transaction_commit()
4282 except Exception as e:
4283 print("Exception: %s" % e)
4284 samdb.transaction_cancel()
4285 error_encountered = True
4288 samdb.transaction_start()
4289 error_encountered = False
4291 from samba.domain_update import DomainUpdate
4293 domain = DomainUpdate(samdb, fix=True)
4294 domain.check_updates_functional_level(target_level,
4295 DS_DOMAIN_FUNCTION_2008,
4296 update_revision=True)
4298 samdb.transaction_commit()
4299 except Exception as e:
4300 print("Exception: %s" % e)
4301 samdb.transaction_cancel()
4302 error_encountered = True
4304 if updates_allowed_overriden:
4305 lp.set("dsdb:schema update allowed", "no")
4307 if error_encountered:
4308 raise CommandError('Failed to perform functional prep')
4310 class cmd_domain(SuperCommand):
4311 """Domain management."""
4314 subcommands["demote"] = cmd_domain_demote()
4315 if cmd_domain_export_keytab is not None:
4316 subcommands["exportkeytab"] = cmd_domain_export_keytab()
4317 subcommands["info"] = cmd_domain_info()
4318 subcommands["provision"] = cmd_domain_provision()
4319 subcommands["join"] = cmd_domain_join()
4320 subcommands["dcpromo"] = cmd_domain_dcpromo()
4321 subcommands["level"] = cmd_domain_level()
4322 subcommands["passwordsettings"] = cmd_domain_passwordsettings()
4323 subcommands["classicupgrade"] = cmd_domain_classicupgrade()
4324 subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()
4325 subcommands["trust"] = cmd_domain_trust()
4326 subcommands["tombstones"] = cmd_domain_tombstones()
4327 subcommands["schemaupgrade"] = cmd_domain_schema_upgrade()
4328 subcommands["functionalprep"] = cmd_domain_functional_prep()