3 # Copyright Matthias Dieter Wallnoefer 2009
4 # Copyright Andrew Kroeger 2009
5 # Copyright Jelmer Vernooij 2007-2012
6 # Copyright Giampaolo Lauria 2011
7 # Copyright Matthieu Patou <mat@matws.net> 2011
8 # Copyright Andrew Bartlett 2008-2015
9 # Copyright Stefan Metzmacher 2012
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 from __future__ import print_function
26 from __future__ import division
27 import samba.getopt as options
39 from samba import ntstatus
40 from samba import NTSTATUSError
41 from samba import werror
42 from getpass import getpass
43 from samba.net import Net, LIBNET_JOIN_AUTOMATIC
45 from samba.join import join_RODC, join_DC, join_subdomain
46 from samba.auth import system_session
47 from samba.samdb import SamDB, get_default_backend_store
48 from samba.ndr import ndr_unpack, ndr_pack, ndr_print
49 from samba.dcerpc import drsuapi
50 from samba.dcerpc import drsblobs
51 from samba.dcerpc import lsa
52 from samba.dcerpc import netlogon
53 from samba.dcerpc import security
54 from samba.dcerpc import nbt
55 from samba.dcerpc import misc
56 from samba.dcerpc.samr import DOMAIN_PASSWORD_COMPLEX, DOMAIN_PASSWORD_STORE_CLEARTEXT
57 from samba.netcmd import (
63 from samba.netcmd.fsmo import get_fsmo_roleowner
64 from samba.netcmd.common import netcmd_get_domain_infos_via_cldap
65 from samba.samba3 import Samba3
66 from samba.samba3 import param as s3param
67 from samba.upgrade import upgrade_from_samba3
68 from samba.drs_utils import (
69 sendDsReplicaSync, drsuapi_connect, drsException,
71 from samba import remove_dc, arcfour_encrypt, string_to_byte_array
73 from samba.dsdb import (
74 DS_DOMAIN_FUNCTION_2000,
75 DS_DOMAIN_FUNCTION_2003,
76 DS_DOMAIN_FUNCTION_2003_MIXED,
77 DS_DOMAIN_FUNCTION_2008,
78 DS_DOMAIN_FUNCTION_2008_R2,
79 DS_DOMAIN_FUNCTION_2012,
80 DS_DOMAIN_FUNCTION_2012_R2,
81 DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL,
82 DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL,
83 UF_WORKSTATION_TRUST_ACCOUNT,
84 UF_SERVER_TRUST_ACCOUNT,
85 UF_TRUSTED_FOR_DELEGATION,
86 UF_PARTIAL_SECRETS_ACCOUNT
89 from samba.provision import (
92 DEFAULT_MIN_PWD_LENGTH,
96 from samba.provision.common import (
102 from samba.netcmd.pso import cmd_domain_passwordsettings_pso
103 from samba.netcmd.domain_backup import cmd_domain_backup
105 from samba.compat import binary_type
107 string_version_to_constant = {
108 "2008_R2": DS_DOMAIN_FUNCTION_2008_R2,
109 "2012": DS_DOMAIN_FUNCTION_2012,
110 "2012_R2": DS_DOMAIN_FUNCTION_2012_R2,
113 common_provision_join_options = [
114 Option("--machinepass", type="string", metavar="PASSWORD",
115 help="choose machine password (otherwise random)"),
116 Option("--plaintext-secrets", action="store_true",
117 help="Store secret/sensitive values as plain text on disk" +
118 "(default is to encrypt secret/ensitive values)"),
119 Option("--backend-store", type="choice", metavar="BACKENDSTORE",
120 choices=["tdb", "mdb"],
121 help="Specify the database backend to be used "
122 "(default is %s)" % get_default_backend_store()),
123 Option("--targetdir", metavar="DIR",
124 help="Set target directory (where to store provision)", type=str),
125 Option("-q", "--quiet", help="Be quiet", action="store_true"),
128 common_join_options = [
129 Option("--server", help="DC to join", type=str),
130 Option("--site", help="site to join", type=str),
131 Option("--domain-critical-only",
132 help="only replicate critical domain objects",
133 action="store_true"),
134 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
135 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
136 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
137 "BIND9_DLZ uses samba4 AD to store zone information, "
138 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
139 default="SAMBA_INTERNAL"),
140 Option("-v", "--verbose", help="Be verbose", action="store_true")
143 common_ntvfs_options = [
144 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
149 def get_testparm_var(testparm, smbconf, varname):
150 errfile = open(os.devnull, 'w')
151 p = subprocess.Popen([testparm, '-s', '-l',
152 '--parameter-name=%s' % varname, smbconf],
153 stdout=subprocess.PIPE, stderr=errfile)
154 (out, err) = p.communicate()
156 lines = out.split('\n')
158 return lines[0].strip()
163 import samba.dckeytab
165 cmd_domain_export_keytab = None
167 class cmd_domain_export_keytab(Command):
168 """Dump Kerberos keys of the domain into a keytab."""
170 synopsis = "%prog <keytab> [options]"
172 takes_optiongroups = {
173 "sambaopts": options.SambaOptions,
174 "credopts": options.CredentialsOptions,
175 "versionopts": options.VersionOptions,
179 Option("--principal", help="extract only this principal", type=str),
182 takes_args = ["keytab"]
184 def run(self, keytab, credopts=None, sambaopts=None, versionopts=None, principal=None):
185 lp = sambaopts.get_loadparm()
187 net.export_keytab(keytab=keytab, principal=principal)
190 class cmd_domain_info(Command):
191 """Print basic info about a domain and the DC passed as parameter."""
193 synopsis = "%prog <ip_address> [options]"
198 takes_optiongroups = {
199 "sambaopts": options.SambaOptions,
200 "credopts": options.CredentialsOptions,
201 "versionopts": options.VersionOptions,
204 takes_args = ["address"]
206 def run(self, address, credopts=None, sambaopts=None, versionopts=None):
207 lp = sambaopts.get_loadparm()
209 res = netcmd_get_domain_infos_via_cldap(lp, None, address)
211 raise CommandError("Invalid IP address '" + address + "'!")
212 self.outf.write("Forest : %s\n" % res.forest)
213 self.outf.write("Domain : %s\n" % res.dns_domain)
214 self.outf.write("Netbios domain : %s\n" % res.domain_name)
215 self.outf.write("DC name : %s\n" % res.pdc_dns_name)
216 self.outf.write("DC netbios name : %s\n" % res.pdc_name)
217 self.outf.write("Server site : %s\n" % res.server_site)
218 self.outf.write("Client site : %s\n" % res.client_site)
221 class cmd_domain_provision(Command):
222 """Provision a domain."""
224 synopsis = "%prog [options]"
226 takes_optiongroups = {
227 "sambaopts": options.SambaOptions,
228 "versionopts": options.VersionOptions,
232 Option("--interactive", help="Ask for names", action="store_true"),
233 Option("--domain", type="string", metavar="DOMAIN",
234 help="NetBIOS domain name to use"),
235 Option("--domain-guid", type="string", metavar="GUID",
236 help="set domainguid (otherwise random)"),
237 Option("--domain-sid", type="string", metavar="SID",
238 help="set domainsid (otherwise random)"),
239 Option("--ntds-guid", type="string", metavar="GUID",
240 help="set NTDS object GUID (otherwise random)"),
241 Option("--invocationid", type="string", metavar="GUID",
242 help="set invocationid (otherwise random)"),
243 Option("--host-name", type="string", metavar="HOSTNAME",
244 help="set hostname"),
245 Option("--host-ip", type="string", metavar="IPADDRESS",
246 help="set IPv4 ipaddress"),
247 Option("--host-ip6", type="string", metavar="IP6ADDRESS",
248 help="set IPv6 ipaddress"),
249 Option("--site", type="string", metavar="SITENAME",
250 help="set site name"),
251 Option("--adminpass", type="string", metavar="PASSWORD",
252 help="choose admin password (otherwise random)"),
253 Option("--krbtgtpass", type="string", metavar="PASSWORD",
254 help="choose krbtgt password (otherwise random)"),
255 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
256 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
257 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
258 "BIND9_FLATFILE uses bind9 text database to store zone information, "
259 "BIND9_DLZ uses samba4 AD to store zone information, "
260 "NONE skips the DNS setup entirely (not recommended)",
261 default="SAMBA_INTERNAL"),
262 Option("--dnspass", type="string", metavar="PASSWORD",
263 help="choose dns password (otherwise random)"),
264 Option("--root", type="string", metavar="USERNAME",
265 help="choose 'root' unix username"),
266 Option("--nobody", type="string", metavar="USERNAME",
267 help="choose 'nobody' user"),
268 Option("--users", type="string", metavar="GROUPNAME",
269 help="choose 'users' group"),
270 Option("--blank", action="store_true",
271 help="do not add users or groups, just the structure"),
272 Option("--server-role", type="choice", metavar="ROLE",
273 choices=["domain controller", "dc", "member server", "member", "standalone"],
274 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
275 default="domain controller"),
276 Option("--function-level", type="choice", metavar="FOR-FUN-LEVEL",
277 choices=["2000", "2003", "2008", "2008_R2"],
278 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2008_R2 Native.",
280 Option("--base-schema", type="choice", metavar="BASE-SCHEMA",
281 choices=["2008_R2", "2008_R2_old", "2012", "2012_R2"],
282 help="The base schema files to use. Default is (Windows) 2008_R2.",
284 Option("--next-rid", type="int", metavar="NEXTRID", default=1000,
285 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
286 Option("--partitions-only",
287 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"),
288 Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
292 Option("--ldapadminpass", type="string", metavar="PASSWORD",
293 help="choose password to set between Samba and its LDAP backend (otherwise random)"),
294 Option("--ldap-backend-type", type="choice", metavar="LDAP-BACKEND-TYPE",
295 help="Test initialisation support for unsupported LDAP backend type (fedora-ds or openldap) DO NOT USE",
296 choices=["fedora-ds", "openldap"]),
297 Option("--ol-mmr-urls", type="string", metavar="LDAPSERVER",
298 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\""),
299 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",
300 action="store_true"),
301 Option("--slapd-path", type="string", metavar="SLAPD-PATH",
302 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."),
303 Option("--ldap-backend-extra-port", type="int", metavar="LDAP-BACKEND-EXTRA-PORT", help="Additional TCP port for LDAP backend server (to use for replication)"),
304 Option("--ldap-backend-forced-uri", type="string", metavar="LDAP-BACKEND-FORCED-URI",
305 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"),
306 Option("--ldap-backend-nosync", help="Configure LDAP backend not to call fsync() (for performance in test environments)", action="store_true"),
310 Option("--use-xattrs", type="choice", choices=["yes", "no", "auto"],
311 metavar="[yes|no|auto]",
312 help="Define if we should use the native fs capabilities or a tdb file for "
313 "storing attributes likes ntacl when --use-ntvfs is set. "
314 "auto tries to make an inteligent guess based on the user rights and system capabilities",
318 takes_options.extend(common_provision_join_options)
320 if os.getenv('TEST_LDAP', "no") == "yes":
321 takes_options.extend(openldap_options)
323 if samba.is_ntvfs_fileserver_built():
324 takes_options.extend(common_ntvfs_options)
325 takes_options.extend(ntvfs_options)
329 def run(self, sambaopts=None, versionopts=None,
352 ldap_backend_type=None,
356 partitions_only=None,
363 ldap_backend_nosync=None,
364 ldap_backend_extra_port=None,
365 ldap_backend_forced_uri=None,
366 ldap_dryrun_mode=None,
368 plaintext_secrets=False,
371 self.logger = self.get_logger("provision")
373 self.logger.setLevel(logging.WARNING)
375 self.logger.setLevel(logging.INFO)
377 lp = sambaopts.get_loadparm()
378 smbconf = lp.configfile
380 if dns_forwarder is not None:
381 suggested_forwarder = dns_forwarder
383 suggested_forwarder = self._get_nameserver_ip()
384 if suggested_forwarder is None:
385 suggested_forwarder = "none"
387 if len(self.raw_argv) == 1:
391 from getpass import getpass
394 def ask(prompt, default=None):
395 if default is not None:
396 print("%s [%s]: " % (prompt, default), end=' ')
398 print("%s: " % (prompt,), end=' ')
399 return sys.stdin.readline().rstrip("\n") or default
402 default = socket.getfqdn().split(".", 1)[1].upper()
405 realm = ask("Realm", default)
406 if realm in (None, ""):
407 raise CommandError("No realm set!")
410 default = realm.split(".")[0]
413 domain = ask("Domain", default)
415 raise CommandError("No domain set!")
417 server_role = ask("Server Role (dc, member, standalone)", "dc")
419 dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
420 if dns_backend in (None, ''):
421 raise CommandError("No DNS backend set!")
423 if dns_backend == "SAMBA_INTERNAL":
424 dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
425 if dns_forwarder.lower() in (None, 'none'):
426 suggested_forwarder = None
430 adminpassplain = getpass("Administrator password: ")
431 issue = self._adminpass_issue(adminpassplain)
433 self.errf.write("%s.\n" % issue)
435 adminpassverify = getpass("Retype password: ")
436 if not adminpassplain == adminpassverify:
437 self.errf.write("Sorry, passwords do not match.\n")
439 adminpass = adminpassplain
443 realm = sambaopts._lp.get('realm')
445 raise CommandError("No realm set!")
447 raise CommandError("No domain set!")
450 issue = self._adminpass_issue(adminpass)
452 raise CommandError(issue)
454 self.logger.info("Administrator password will be set randomly!")
456 if function_level == "2000":
457 dom_for_fun_level = DS_DOMAIN_FUNCTION_2000
458 elif function_level == "2003":
459 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
460 elif function_level == "2008":
461 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
462 elif function_level == "2008_R2":
463 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
465 if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
466 dns_forwarder = suggested_forwarder
468 samdb_fill = FILL_FULL
470 samdb_fill = FILL_NT4SYNC
471 elif partitions_only:
472 samdb_fill = FILL_DRS
474 if targetdir is not None:
475 if not os.path.isdir(targetdir):
480 if use_xattrs == "yes":
482 elif use_xattrs == "auto" and use_ntvfs == False:
484 elif use_ntvfs == False:
485 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
486 "Please re-run with --use-xattrs omitted.")
487 elif use_xattrs == "auto" and not lp.get("posix:eadb"):
489 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
491 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
494 samba.ntacls.setntacl(lp, file.name,
495 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
498 self.logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. ")
503 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.")
504 if ldap_backend_type == "existing":
505 if ldap_backend_forced_uri is not None:
506 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)
508 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")
510 if ldap_backend_forced_uri is not None:
511 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")
513 if domain_sid is not None:
514 domain_sid = security.dom_sid(domain_sid)
516 session = system_session()
517 if backend_store is None:
518 backend_store = get_default_backend_store()
520 result = provision(self.logger,
521 session, smbconf=smbconf, targetdir=targetdir,
522 samdb_fill=samdb_fill, realm=realm, domain=domain,
523 domainguid=domain_guid, domainsid=domain_sid,
525 hostip=host_ip, hostip6=host_ip6,
526 sitename=site, ntdsguid=ntds_guid,
527 invocationid=invocationid, adminpass=adminpass,
528 krbtgtpass=krbtgtpass, machinepass=machinepass,
529 dns_backend=dns_backend, dns_forwarder=dns_forwarder,
530 dnspass=dnspass, root=root, nobody=nobody,
532 serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
533 backend_type=ldap_backend_type,
534 ldapadminpass=ldapadminpass, ol_mmr_urls=ol_mmr_urls, slapd_path=slapd_path,
535 useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
536 use_rfc2307=use_rfc2307, skip_sysvolacl=False,
537 ldap_backend_extra_port=ldap_backend_extra_port,
538 ldap_backend_forced_uri=ldap_backend_forced_uri,
539 nosync=ldap_backend_nosync, ldap_dryrun_mode=ldap_dryrun_mode,
540 base_schema=base_schema,
541 plaintext_secrets=plaintext_secrets,
542 backend_store=backend_store)
544 except ProvisioningError as e:
545 raise CommandError("Provision failed", e)
547 result.report_logger(self.logger)
549 def _get_nameserver_ip(self):
550 """Grab the nameserver IP address from /etc/resolv.conf."""
552 RESOLV_CONF = "/etc/resolv.conf"
554 if not path.isfile(RESOLV_CONF):
555 self.logger.warning("Failed to locate %s" % RESOLV_CONF)
560 handle = open(RESOLV_CONF, 'r')
562 if not line.startswith('nameserver'):
564 # we want the last non-space continuous string of the line
565 return line.strip().split()[-1]
567 if handle is not None:
570 self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
572 def _adminpass_issue(self, adminpass):
573 """Returns error string for a bad administrator password,
574 or None if acceptable"""
575 if isinstance(adminpass, binary_type):
576 adminpass = adminpass.decode('utf8')
577 if len(adminpass) < DEFAULT_MIN_PWD_LENGTH:
578 return "Administrator password does not meet the default minimum" \
579 " password length requirement (%d characters)" \
580 % DEFAULT_MIN_PWD_LENGTH
581 elif not samba.check_password_quality(adminpass):
582 return "Administrator password does not meet the default" \
588 class cmd_domain_dcpromo(Command):
589 """Promote an existing domain member or NT4 PDC to an AD DC."""
591 synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
593 takes_optiongroups = {
594 "sambaopts": options.SambaOptions,
595 "versionopts": options.VersionOptions,
596 "credopts": options.CredentialsOptions,
600 takes_options.extend(common_join_options)
602 takes_options.extend(common_provision_join_options)
604 if samba.is_ntvfs_fileserver_built():
605 takes_options.extend(common_ntvfs_options)
607 takes_args = ["domain", "role?"]
609 def run(self, domain, role=None, sambaopts=None, credopts=None,
610 versionopts=None, server=None, site=None, targetdir=None,
611 domain_critical_only=False, parent_domain=None, machinepass=None,
612 use_ntvfs=False, dns_backend=None,
613 quiet=False, verbose=False, plaintext_secrets=False,
615 lp = sambaopts.get_loadparm()
616 creds = credopts.get_credentials(lp)
617 net = Net(creds, lp, server=credopts.ipaddress)
619 logger = self.get_logger()
621 logger.setLevel(logging.DEBUG)
623 logger.setLevel(logging.WARNING)
625 logger.setLevel(logging.INFO)
627 netbios_name = lp.get("netbios name")
633 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
634 site=site, netbios_name=netbios_name, targetdir=targetdir,
635 domain_critical_only=domain_critical_only,
636 machinepass=machinepass, use_ntvfs=use_ntvfs,
637 dns_backend=dns_backend,
638 promote_existing=True, plaintext_secrets=plaintext_secrets,
639 backend_store=backend_store)
641 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
642 site=site, netbios_name=netbios_name, targetdir=targetdir,
643 domain_critical_only=domain_critical_only,
644 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
645 promote_existing=True, plaintext_secrets=plaintext_secrets,
646 backend_store=backend_store)
648 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
651 class cmd_domain_join(Command):
652 """Join domain as either member or backup domain controller."""
654 synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
656 takes_optiongroups = {
657 "sambaopts": options.SambaOptions,
658 "versionopts": options.VersionOptions,
659 "credopts": options.CredentialsOptions,
663 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
664 Option("--adminpass", type="string", metavar="PASSWORD",
665 help="choose adminstrator password when joining as a subdomain (otherwise random)"),
669 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
672 takes_options.extend(common_join_options)
673 takes_options.extend(common_provision_join_options)
675 if samba.is_ntvfs_fileserver_built():
676 takes_options.extend(ntvfs_options)
678 takes_args = ["domain", "role?"]
680 def run(self, domain, role=None, sambaopts=None, credopts=None,
681 versionopts=None, server=None, site=None, targetdir=None,
682 domain_critical_only=False, parent_domain=None, machinepass=None,
683 use_ntvfs=False, dns_backend=None, adminpass=None,
684 quiet=False, verbose=False,
685 plaintext_secrets=False,
687 lp = sambaopts.get_loadparm()
688 creds = credopts.get_credentials(lp)
689 net = Net(creds, lp, server=credopts.ipaddress)
692 site = "Default-First-Site-Name"
694 logger = self.get_logger()
696 logger.setLevel(logging.DEBUG)
698 logger.setLevel(logging.WARNING)
700 logger.setLevel(logging.INFO)
702 netbios_name = lp.get("netbios name")
707 if role is None or role == "MEMBER":
708 (join_password, sid, domain_name) = net.join_member(
709 domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
710 machinepass=machinepass)
712 self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
714 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
715 site=site, netbios_name=netbios_name, targetdir=targetdir,
716 domain_critical_only=domain_critical_only,
717 machinepass=machinepass, use_ntvfs=use_ntvfs,
718 dns_backend=dns_backend,
719 plaintext_secrets=plaintext_secrets,
720 backend_store=backend_store)
722 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
723 site=site, netbios_name=netbios_name, targetdir=targetdir,
724 domain_critical_only=domain_critical_only,
725 machinepass=machinepass, use_ntvfs=use_ntvfs,
726 dns_backend=dns_backend,
727 plaintext_secrets=plaintext_secrets,
728 backend_store=backend_store)
729 elif role == "SUBDOMAIN":
731 logger.info("Administrator password will be set randomly!")
733 netbios_domain = lp.get("workgroup")
734 if parent_domain is None:
735 parent_domain = ".".join(domain.split(".")[1:])
736 join_subdomain(logger=logger, server=server, creds=creds, lp=lp, dnsdomain=domain,
737 parent_domain=parent_domain, site=site,
738 netbios_name=netbios_name, netbios_domain=netbios_domain,
739 targetdir=targetdir, machinepass=machinepass,
740 use_ntvfs=use_ntvfs, dns_backend=dns_backend,
742 plaintext_secrets=plaintext_secrets,
743 backend_store=backend_store)
745 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role)
748 class cmd_domain_demote(Command):
749 """Demote ourselves from the role of Domain Controller."""
751 synopsis = "%prog [options]"
754 Option("--server", help="writable DC to write demotion changes on", type=str),
755 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
756 metavar="URL", dest="H"),
757 Option("--remove-other-dead-server", help="Dead DC (name or NTDS GUID) "
758 "to remove ALL references to (rather than this DC)", type=str),
759 Option("-q", "--quiet", help="Be quiet", action="store_true"),
760 Option("-v", "--verbose", help="Be verbose", action="store_true"),
763 takes_optiongroups = {
764 "sambaopts": options.SambaOptions,
765 "credopts": options.CredentialsOptions,
766 "versionopts": options.VersionOptions,
769 def run(self, sambaopts=None, credopts=None,
770 versionopts=None, server=None,
771 remove_other_dead_server=None, H=None,
772 verbose=False, quiet=False):
773 lp = sambaopts.get_loadparm()
774 creds = credopts.get_credentials(lp)
775 net = Net(creds, lp, server=credopts.ipaddress)
777 logger = self.get_logger()
779 logger.setLevel(logging.DEBUG)
781 logger.setLevel(logging.WARNING)
783 logger.setLevel(logging.INFO)
785 if remove_other_dead_server is not None:
786 if server is not None:
787 samdb = SamDB(url="ldap://%s" % server,
788 session_info=system_session(),
789 credentials=creds, lp=lp)
791 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
793 remove_dc.remove_dc(samdb, logger, remove_other_dead_server)
794 except remove_dc.DemoteException as err:
795 raise CommandError("Demote failed: %s" % err)
798 netbios_name = lp.get("netbios name")
799 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
801 res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
803 raise CommandError("Unable to search for servers")
806 raise CommandError("You are the last server in the domain")
810 if str(e["name"]).lower() != netbios_name.lower():
811 server = e["dnsHostName"]
814 ntds_guid = samdb.get_ntds_GUID()
815 msg = samdb.search(base=str(samdb.get_config_basedn()),
816 scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
818 if len(msg) == 0 or "options" not in msg[0]:
819 raise CommandError("Failed to find options on %s" % ntds_guid)
822 dsa_options = int(str(msg[0]['options']))
824 res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
825 controls=["search_options:1:2"])
828 raise CommandError("Current DC is still the owner of %d role(s), "
829 "use the role command to transfer roles to "
833 self.errf.write("Using %s as partner server for the demotion\n" %
835 (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
837 self.errf.write("Deactivating inbound replication\n")
842 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
843 dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
844 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
847 self.errf.write("Asking partner server %s to synchronize from us\n"
849 for part in (samdb.get_schema_basedn(),
850 samdb.get_config_basedn(),
851 samdb.get_root_basedn()):
852 nc = drsuapi.DsReplicaObjectIdentifier()
855 req1 = drsuapi.DsReplicaSyncRequest1()
856 req1.naming_context = nc
857 req1.options = drsuapi.DRSUAPI_DRS_WRIT_REP
858 req1.source_dsa_guid = misc.GUID(ntds_guid)
861 drsuapiBind.DsReplicaSync(drsuapi_handle, 1, req1)
862 except RuntimeError as e1:
863 (werr, string) = e1.args
864 if werr == werror.WERR_DS_DRA_NO_REPLICA:
868 "Error while replicating out last local changes from '%s' for demotion, "
869 "re-enabling inbound replication\n" % part)
870 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
871 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
873 raise CommandError("Error while sending a DsReplicaSync for partition '%s'" % str(part), string)
875 remote_samdb = SamDB(url="ldap://%s" % server,
876 session_info=system_session(),
877 credentials=creds, lp=lp)
879 self.errf.write("Changing userControl and container\n")
880 res = remote_samdb.search(base=str(remote_samdb.domain_dn()),
881 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
882 netbios_name.upper(),
883 attrs=["userAccountControl"])
885 uac = int(str(res[0]["userAccountControl"]))
887 except Exception as e:
888 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
890 "Error while demoting, re-enabling inbound replication\n")
891 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
892 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
894 raise CommandError("Error while changing account control", e)
897 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
899 "Error while demoting, re-enabling inbound replication")
900 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
901 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
903 raise CommandError("Unable to find object with samaccountName = %s$"
904 " in the remote dc" % netbios_name.upper())
908 uac &= ~(UF_SERVER_TRUST_ACCOUNT |
909 UF_TRUSTED_FOR_DELEGATION |
910 UF_PARTIAL_SECRETS_ACCOUNT)
911 uac |= UF_WORKSTATION_TRUST_ACCOUNT
916 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
917 ldb.FLAG_MOD_REPLACE,
918 "userAccountControl")
920 remote_samdb.modify(msg)
921 except Exception as e:
922 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
924 "Error while demoting, re-enabling inbound replication")
925 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
926 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
929 raise CommandError("Error while changing account control", e)
931 parent = msg.dn.parent()
932 dc_name = res[0].dn.get_rdn_value()
933 rdn = "CN=%s" % dc_name
935 # Let's move to the Computer container
939 computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.domain_dn()))
940 res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
943 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
944 scope=ldb.SCOPE_ONELEVEL)
945 while(len(res) != 0 and i < 100):
947 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
948 scope=ldb.SCOPE_ONELEVEL)
951 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
953 "Error while demoting, re-enabling inbound replication\n")
954 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
955 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
961 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
962 ldb.FLAG_MOD_REPLACE,
963 "userAccountControl")
965 remote_samdb.modify(msg)
967 raise CommandError("Unable to find a slot for renaming %s,"
968 " all names from %s-1 to %s-%d seemed used" %
969 (str(dc_dn), rdn, rdn, i - 9))
971 newrdn = "%s-%d" % (rdn, i)
974 newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
975 remote_samdb.rename(dc_dn, newdn)
976 except Exception as e:
977 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
979 "Error while demoting, re-enabling inbound replication\n")
980 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
981 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
987 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
988 ldb.FLAG_MOD_REPLACE,
989 "userAccountControl")
991 remote_samdb.modify(msg)
992 raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
994 server_dsa_dn = samdb.get_serverName()
995 domain = remote_samdb.get_root_basedn()
998 req1 = drsuapi.DsRemoveDSServerRequest1()
999 req1.server_dn = str(server_dsa_dn)
1000 req1.domain_dn = str(domain)
1003 drsuapiBind.DsRemoveDSServer(drsuapi_handle, 1, req1)
1004 except RuntimeError as e3:
1005 (werr, string) = e3.args
1006 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
1008 "Error while demoting, re-enabling inbound replication\n")
1009 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
1010 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
1016 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
1017 ldb.FLAG_MOD_REPLACE,
1018 "userAccountControl")
1019 remote_samdb.modify(msg)
1020 remote_samdb.rename(newdn, dc_dn)
1021 if werr == werror.WERR_DS_DRA_NO_REPLICA:
1022 raise CommandError("The DC %s is not present on (already "
1023 "removed from) the remote server: %s" %
1024 (server_dsa_dn, e3))
1026 raise CommandError("Error while sending a removeDsServer "
1028 (server_dsa_dn, e3))
1030 remove_dc.remove_sysvol_references(remote_samdb, logger, dc_name)
1032 # These are objects under the computer account that should be deleted
1033 for s in ("CN=Enterprise,CN=NTFRS Subscriptions",
1034 "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
1035 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
1036 "CN=NTFRS Subscriptions"):
1038 remote_samdb.delete(ldb.Dn(remote_samdb,
1039 "%s,%s" % (s, str(newdn))))
1040 except ldb.LdbError as l:
1043 # get dns host name for target server to demote, remove dns references
1044 remove_dc.remove_dns_references(remote_samdb, logger, samdb.host_dns_name(),
1045 ignore_no_name=True)
1047 self.errf.write("Demote successful\n")
1050 class cmd_domain_level(Command):
1051 """Raise domain and forest function levels."""
1053 synopsis = "%prog (show|raise <options>) [options]"
1055 takes_optiongroups = {
1056 "sambaopts": options.SambaOptions,
1057 "credopts": options.CredentialsOptions,
1058 "versionopts": options.VersionOptions,
1062 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1063 metavar="URL", dest="H"),
1064 Option("-q", "--quiet", help="Be quiet", action="store_true"), # unused
1065 Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1066 help="The forest function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)"),
1067 Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1068 help="The domain function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)")
1071 takes_args = ["subcommand"]
1073 def run(self, subcommand, H=None, forest_level=None, domain_level=None,
1074 quiet=False, credopts=None, sambaopts=None, versionopts=None):
1075 lp = sambaopts.get_loadparm()
1076 creds = credopts.get_credentials(lp, fallback_machine=True)
1078 samdb = SamDB(url=H, session_info=system_session(),
1079 credentials=creds, lp=lp)
1081 domain_dn = samdb.domain_dn()
1083 res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
1084 scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
1085 assert len(res_forest) == 1
1087 res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1088 attrs=["msDS-Behavior-Version", "nTMixedDomain"])
1089 assert len(res_domain) == 1
1091 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
1092 scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
1093 attrs=["msDS-Behavior-Version"])
1094 assert len(res_dc_s) >= 1
1096 # default values, since "msDS-Behavior-Version" does not exist on Windows 2000 AD
1097 level_forest = DS_DOMAIN_FUNCTION_2000
1098 level_domain = DS_DOMAIN_FUNCTION_2000
1100 if "msDS-Behavior-Version" in res_forest[0]:
1101 level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
1102 if "msDS-Behavior-Version" in res_domain[0]:
1103 level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
1104 level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
1107 for msg in res_dc_s:
1108 if "msDS-Behavior-Version" in msg:
1109 if min_level_dc is None or int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
1110 min_level_dc = int(msg["msDS-Behavior-Version"][0])
1112 min_level_dc = DS_DOMAIN_FUNCTION_2000
1113 # well, this is the least
1116 if level_forest < DS_DOMAIN_FUNCTION_2000 or level_domain < DS_DOMAIN_FUNCTION_2000:
1117 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
1118 if min_level_dc < DS_DOMAIN_FUNCTION_2000:
1119 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
1120 if level_forest > level_domain:
1121 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
1122 if level_domain > min_level_dc:
1123 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
1125 if subcommand == "show":
1126 self.message("Domain and forest function level for domain '%s'" % domain_dn)
1127 if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1128 self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1129 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1130 self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1131 if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1132 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)!")
1136 if level_forest == DS_DOMAIN_FUNCTION_2000:
1138 elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
1139 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1140 elif level_forest == DS_DOMAIN_FUNCTION_2003:
1142 elif level_forest == DS_DOMAIN_FUNCTION_2008:
1144 elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
1146 elif level_forest == DS_DOMAIN_FUNCTION_2012:
1148 elif level_forest == DS_DOMAIN_FUNCTION_2012_R2:
1151 outstr = "higher than 2012 R2"
1152 self.message("Forest function level: (Windows) " + outstr)
1154 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1155 outstr = "2000 mixed (NT4 DC support)"
1156 elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
1158 elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
1159 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1160 elif level_domain == DS_DOMAIN_FUNCTION_2003:
1162 elif level_domain == DS_DOMAIN_FUNCTION_2008:
1164 elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
1166 elif level_domain == DS_DOMAIN_FUNCTION_2012:
1168 elif level_domain == DS_DOMAIN_FUNCTION_2012_R2:
1171 outstr = "higher than 2012 R2"
1172 self.message("Domain function level: (Windows) " + outstr)
1174 if min_level_dc == DS_DOMAIN_FUNCTION_2000:
1176 elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
1178 elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
1180 elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
1182 elif min_level_dc == DS_DOMAIN_FUNCTION_2012:
1184 elif min_level_dc == DS_DOMAIN_FUNCTION_2012_R2:
1187 outstr = "higher than 2012 R2"
1188 self.message("Lowest function level of a DC: (Windows) " + outstr)
1190 elif subcommand == "raise":
1193 if domain_level is not None:
1194 if domain_level == "2003":
1195 new_level_domain = DS_DOMAIN_FUNCTION_2003
1196 elif domain_level == "2008":
1197 new_level_domain = DS_DOMAIN_FUNCTION_2008
1198 elif domain_level == "2008_R2":
1199 new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
1200 elif domain_level == "2012":
1201 new_level_domain = DS_DOMAIN_FUNCTION_2012
1202 elif domain_level == "2012_R2":
1203 new_level_domain = DS_DOMAIN_FUNCTION_2012_R2
1205 if new_level_domain <= level_domain and level_domain_mixed == 0:
1206 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
1207 if new_level_domain > min_level_dc:
1208 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
1210 # Deactivate mixed/interim domain support
1211 if level_domain_mixed != 0:
1212 # Directly on the base DN
1214 m.dn = ldb.Dn(samdb, domain_dn)
1215 m["nTMixedDomain"] = ldb.MessageElement("0",
1216 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1220 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
1221 m["nTMixedDomain"] = ldb.MessageElement("0",
1222 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1225 except ldb.LdbError as e:
1226 (enum, emsg) = e.args
1227 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1230 # Directly on the base DN
1232 m.dn = ldb.Dn(samdb, domain_dn)
1233 m["msDS-Behavior-Version"] = ldb.MessageElement(
1234 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1235 "msDS-Behavior-Version")
1239 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
1240 + ",CN=Partitions,%s" % samdb.get_config_basedn())
1241 m["msDS-Behavior-Version"] = ldb.MessageElement(
1242 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1243 "msDS-Behavior-Version")
1246 except ldb.LdbError as e2:
1247 (enum, emsg) = e2.args
1248 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1251 level_domain = new_level_domain
1252 msgs.append("Domain function level changed!")
1254 if forest_level is not None:
1255 if forest_level == "2003":
1256 new_level_forest = DS_DOMAIN_FUNCTION_2003
1257 elif forest_level == "2008":
1258 new_level_forest = DS_DOMAIN_FUNCTION_2008
1259 elif forest_level == "2008_R2":
1260 new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
1261 elif forest_level == "2012":
1262 new_level_forest = DS_DOMAIN_FUNCTION_2012
1263 elif forest_level == "2012_R2":
1264 new_level_forest = DS_DOMAIN_FUNCTION_2012_R2
1266 if new_level_forest <= level_forest:
1267 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1268 if new_level_forest > level_domain:
1269 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1272 m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
1273 m["msDS-Behavior-Version"] = ldb.MessageElement(
1274 str(new_level_forest), ldb.FLAG_MOD_REPLACE,
1275 "msDS-Behavior-Version")
1277 msgs.append("Forest function level changed!")
1278 msgs.append("All changes applied successfully!")
1279 self.message("\n".join(msgs))
1281 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
1284 class cmd_domain_passwordsettings_show(Command):
1285 """Display current password settings for the domain."""
1287 synopsis = "%prog [options]"
1289 takes_optiongroups = {
1290 "sambaopts": options.SambaOptions,
1291 "versionopts": options.VersionOptions,
1292 "credopts": options.CredentialsOptions,
1296 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1297 metavar="URL", dest="H"),
1300 def run(self, H=None, credopts=None, sambaopts=None, versionopts=None):
1301 lp = sambaopts.get_loadparm()
1302 creds = credopts.get_credentials(lp)
1304 samdb = SamDB(url=H, session_info=system_session(),
1305 credentials=creds, lp=lp)
1307 domain_dn = samdb.domain_dn()
1308 res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1309 attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1310 "minPwdAge", "maxPwdAge", "lockoutDuration", "lockoutThreshold",
1311 "lockOutObservationWindow"])
1312 assert(len(res) == 1)
1314 pwd_props = int(res[0]["pwdProperties"][0])
1315 pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
1316 cur_min_pwd_len = int(res[0]["minPwdLength"][0])
1318 cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1319 if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000:
1322 cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1323 cur_account_lockout_threshold = int(res[0]["lockoutThreshold"][0])
1325 if int(res[0]["lockoutDuration"][0]) == -0x8000000000000000:
1326 cur_account_lockout_duration = 0
1328 cur_account_lockout_duration = abs(int(res[0]["lockoutDuration"][0])) / (1e7 * 60)
1329 cur_reset_account_lockout_after = abs(int(res[0]["lockOutObservationWindow"][0])) / (1e7 * 60)
1330 except Exception as e:
1331 raise CommandError("Could not retrieve password properties!", e)
1333 self.message("Password informations for domain '%s'" % domain_dn)
1335 if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
1336 self.message("Password complexity: on")
1338 self.message("Password complexity: off")
1339 if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
1340 self.message("Store plaintext passwords: on")
1342 self.message("Store plaintext passwords: off")
1343 self.message("Password history length: %d" % pwd_hist_len)
1344 self.message("Minimum password length: %d" % cur_min_pwd_len)
1345 self.message("Minimum password age (days): %d" % cur_min_pwd_age)
1346 self.message("Maximum password age (days): %d" % cur_max_pwd_age)
1347 self.message("Account lockout duration (mins): %d" % cur_account_lockout_duration)
1348 self.message("Account lockout threshold (attempts): %d" % cur_account_lockout_threshold)
1349 self.message("Reset account lockout after (mins): %d" % cur_reset_account_lockout_after)
1352 class cmd_domain_passwordsettings_set(Command):
1353 """Set password settings.
1355 Password complexity, password lockout policy, history length,
1356 minimum password length, the minimum and maximum password age) on
1357 a Samba AD DC server.
1359 Use against a Windows DC is possible, but group policy will override it.
1362 synopsis = "%prog <options> [options]"
1364 takes_optiongroups = {
1365 "sambaopts": options.SambaOptions,
1366 "versionopts": options.VersionOptions,
1367 "credopts": options.CredentialsOptions,
1371 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1372 metavar="URL", dest="H"),
1373 Option("-q", "--quiet", help="Be quiet", action="store_true"), # unused
1374 Option("--complexity", type="choice", choices=["on", "off", "default"],
1375 help="The password complexity (on | off | default). Default is 'on'"),
1376 Option("--store-plaintext", type="choice", choices=["on", "off", "default"],
1377 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1378 Option("--history-length",
1379 help="The password history length (<integer> | default). Default is 24.", type=str),
1380 Option("--min-pwd-length",
1381 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1382 Option("--min-pwd-age",
1383 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1384 Option("--max-pwd-age",
1385 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1386 Option("--account-lockout-duration",
1387 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),
1388 Option("--account-lockout-threshold",
1389 help="The number of bad password attempts allowed before locking out the account (<integer> | default). Default is 0 (never lock out).", type=str),
1390 Option("--reset-account-lockout-after",
1391 help="After this time is elapsed, the recorded number of attempts restarts from zero (<integer> | default). Default is 30.", type=str),
1394 def run(self, H=None, min_pwd_age=None, max_pwd_age=None,
1395 quiet=False, complexity=None, store_plaintext=None, history_length=None,
1396 min_pwd_length=None, account_lockout_duration=None, account_lockout_threshold=None,
1397 reset_account_lockout_after=None, credopts=None, sambaopts=None,
1399 lp = sambaopts.get_loadparm()
1400 creds = credopts.get_credentials(lp)
1402 samdb = SamDB(url=H, session_info=system_session(),
1403 credentials=creds, lp=lp)
1405 domain_dn = samdb.domain_dn()
1408 m.dn = ldb.Dn(samdb, domain_dn)
1409 pwd_props = int(samdb.get_pwdProperties())
1411 if complexity is not None:
1412 if complexity == "on" or complexity == "default":
1413 pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
1414 msgs.append("Password complexity activated!")
1415 elif complexity == "off":
1416 pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
1417 msgs.append("Password complexity deactivated!")
1419 if store_plaintext is not None:
1420 if store_plaintext == "on" or store_plaintext == "default":
1421 pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1422 msgs.append("Plaintext password storage for changed passwords activated!")
1423 elif store_plaintext == "off":
1424 pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
1425 msgs.append("Plaintext password storage for changed passwords deactivated!")
1427 if complexity is not None or store_plaintext is not None:
1428 m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
1429 ldb.FLAG_MOD_REPLACE, "pwdProperties")
1431 if history_length is not None:
1432 if history_length == "default":
1435 pwd_hist_len = int(history_length)
1437 if pwd_hist_len < 0 or pwd_hist_len > 24:
1438 raise CommandError("Password history length must be in the range of 0 to 24!")
1440 m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
1441 ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
1442 msgs.append("Password history length changed!")
1444 if min_pwd_length is not None:
1445 if min_pwd_length == "default":
1448 min_pwd_len = int(min_pwd_length)
1450 if min_pwd_len < 0 or min_pwd_len > 14:
1451 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1453 m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
1454 ldb.FLAG_MOD_REPLACE, "minPwdLength")
1455 msgs.append("Minimum password length changed!")
1457 if min_pwd_age is not None:
1458 if min_pwd_age == "default":
1461 min_pwd_age = int(min_pwd_age)
1463 if min_pwd_age < 0 or min_pwd_age > 998:
1464 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1467 min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
1469 m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
1470 ldb.FLAG_MOD_REPLACE, "minPwdAge")
1471 msgs.append("Minimum password age changed!")
1473 if max_pwd_age is not None:
1474 if max_pwd_age == "default":
1477 max_pwd_age = int(max_pwd_age)
1479 if max_pwd_age < 0 or max_pwd_age > 999:
1480 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1483 if max_pwd_age == 0:
1484 max_pwd_age_ticks = -0x8000000000000000
1486 max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
1488 m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
1489 ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1490 msgs.append("Maximum password age changed!")
1492 if account_lockout_duration is not None:
1493 if account_lockout_duration == "default":
1494 account_lockout_duration = 30
1496 account_lockout_duration = int(account_lockout_duration)
1498 if account_lockout_duration < 0 or account_lockout_duration > 99999:
1499 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1502 if account_lockout_duration == 0:
1503 account_lockout_duration_ticks = -0x8000000000000000
1505 account_lockout_duration_ticks = -int(account_lockout_duration * (60 * 1e7))
1507 m["lockoutDuration"] = ldb.MessageElement(str(account_lockout_duration_ticks),
1508 ldb.FLAG_MOD_REPLACE, "lockoutDuration")
1509 msgs.append("Account lockout duration changed!")
1511 if account_lockout_threshold is not None:
1512 if account_lockout_threshold == "default":
1513 account_lockout_threshold = 0
1515 account_lockout_threshold = int(account_lockout_threshold)
1517 m["lockoutThreshold"] = ldb.MessageElement(str(account_lockout_threshold),
1518 ldb.FLAG_MOD_REPLACE, "lockoutThreshold")
1519 msgs.append("Account lockout threshold changed!")
1521 if reset_account_lockout_after is not None:
1522 if reset_account_lockout_after == "default":
1523 reset_account_lockout_after = 30
1525 reset_account_lockout_after = int(reset_account_lockout_after)
1527 if reset_account_lockout_after < 0 or reset_account_lockout_after > 99999:
1528 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1531 if reset_account_lockout_after == 0:
1532 reset_account_lockout_after_ticks = -0x8000000000000000
1534 reset_account_lockout_after_ticks = -int(reset_account_lockout_after * (60 * 1e7))
1536 m["lockOutObservationWindow"] = ldb.MessageElement(str(reset_account_lockout_after_ticks),
1537 ldb.FLAG_MOD_REPLACE, "lockOutObservationWindow")
1538 msgs.append("Duration to reset account lockout after changed!")
1540 if max_pwd_age and max_pwd_age > 0 and min_pwd_age >= max_pwd_age:
1541 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
1544 raise CommandError("You must specify at least one option to set. Try --help")
1546 msgs.append("All changes applied successfully!")
1547 self.message("\n".join(msgs))
1550 class cmd_domain_passwordsettings(SuperCommand):
1551 """Manage password policy settings."""
1554 subcommands["pso"] = cmd_domain_passwordsettings_pso()
1555 subcommands["show"] = cmd_domain_passwordsettings_show()
1556 subcommands["set"] = cmd_domain_passwordsettings_set()
1559 class cmd_domain_classicupgrade(Command):
1560 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1562 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1563 the testparm utility from your classic installation (with --testparm).
1566 synopsis = "%prog [options] <classic_smb_conf>"
1568 takes_optiongroups = {
1569 "sambaopts": options.SambaOptions,
1570 "versionopts": options.VersionOptions
1574 Option("--dbdir", type="string", metavar="DIR",
1575 help="Path to samba classic DC database directory"),
1576 Option("--testparm", type="string", metavar="PATH",
1577 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1578 Option("--targetdir", type="string", metavar="DIR",
1579 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1580 Option("-q", "--quiet", help="Be quiet", action="store_true"),
1581 Option("-v", "--verbose", help="Be verbose", action="store_true"),
1582 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
1583 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1584 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1585 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1586 "BIND9_DLZ uses samba4 AD to store zone information, "
1587 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1588 default="SAMBA_INTERNAL")
1592 Option("--use-xattrs", type="choice", choices=["yes", "no", "auto"],
1593 metavar="[yes|no|auto]",
1594 help="Define if we should use the native fs capabilities or a tdb file for "
1595 "storing attributes likes ntacl when --use-ntvfs is set. "
1596 "auto tries to make an inteligent guess based on the user rights and system capabilities",
1599 if samba.is_ntvfs_fileserver_built():
1600 takes_options.extend(common_ntvfs_options)
1601 takes_options.extend(ntvfs_options)
1603 takes_args = ["smbconf"]
1605 def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
1606 quiet=False, verbose=False, use_xattrs="auto", sambaopts=None, versionopts=None,
1607 dns_backend=None, use_ntvfs=False):
1609 if not os.path.exists(smbconf):
1610 raise CommandError("File %s does not exist" % smbconf)
1612 if testparm and not os.path.exists(testparm):
1613 raise CommandError("Testparm utility %s does not exist" % testparm)
1615 if dbdir and not os.path.exists(dbdir):
1616 raise CommandError("Directory %s does not exist" % dbdir)
1618 if not dbdir and not testparm:
1619 raise CommandError("Please specify either dbdir or testparm")
1621 logger = self.get_logger()
1623 logger.setLevel(logging.DEBUG)
1625 logger.setLevel(logging.WARNING)
1627 logger.setLevel(logging.INFO)
1629 if dbdir and testparm:
1630 logger.warning("both dbdir and testparm specified, ignoring dbdir.")
1633 lp = sambaopts.get_loadparm()
1635 s3conf = s3param.get_context()
1638 s3conf.set("realm", sambaopts.realm)
1640 if targetdir is not None:
1641 if not os.path.isdir(targetdir):
1645 if use_xattrs == "yes":
1647 elif use_xattrs == "auto" and use_ntvfs == False:
1649 elif use_ntvfs == False:
1650 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
1651 "Please re-run with --use-xattrs omitted.")
1652 elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
1654 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
1656 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
1659 samba.ntacls.setntacl(lp, tmpfile.name,
1660 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1663 # FIXME: Don't catch all exceptions here
1664 logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. "
1665 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1669 # Set correct default values from dbdir or testparm
1672 paths["state directory"] = dbdir
1673 paths["private dir"] = dbdir
1674 paths["lock directory"] = dbdir
1675 paths["smb passwd file"] = dbdir + "/smbpasswd"
1677 paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
1678 paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
1679 paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
1680 paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
1681 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1682 # "state directory", instead make use of "lock directory"
1683 if len(paths["state directory"]) == 0:
1684 paths["state directory"] = paths["lock directory"]
1687 s3conf.set(p, paths[p])
1689 # load smb.conf parameters
1690 logger.info("Reading smb.conf")
1691 s3conf.load(smbconf)
1692 samba3 = Samba3(smbconf, s3conf)
1694 logger.info("Provisioning")
1695 upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
1696 useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
1699 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
1700 __doc__ = cmd_domain_classicupgrade.__doc__
1702 # This command is present for backwards compatibility only,
1703 # and should not be shown.
1708 class LocalDCCredentialsOptions(options.CredentialsOptions):
1709 def __init__(self, parser):
1710 options.CredentialsOptions.__init__(self, parser, special_name="local-dc")
1713 class DomainTrustCommand(Command):
1714 """List domain trusts."""
1717 Command.__init__(self)
1718 self.local_lp = None
1720 self.local_server = None
1721 self.local_binding_string = None
1722 self.local_creds = None
1724 self.remote_server = None
1725 self.remote_binding_string = None
1726 self.remote_creds = None
1728 def _uint32(self, v):
1729 return ctypes.c_uint32(v).value
1731 def check_runtime_error(self, runtime, val):
1735 err32 = self._uint32(runtime.args[0])
1741 class LocalRuntimeError(CommandError):
1742 def __init__(exception_self, self, runtime, message):
1743 err32 = self._uint32(runtime.args[0])
1744 errstr = runtime.args[1]
1745 msg = "LOCAL_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1746 self.local_server, message, err32, errstr)
1747 CommandError.__init__(exception_self, msg)
1749 class RemoteRuntimeError(CommandError):
1750 def __init__(exception_self, self, runtime, message):
1751 err32 = self._uint32(runtime.args[0])
1752 errstr = runtime.args[1]
1753 msg = "REMOTE_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1754 self.remote_server, message, err32, errstr)
1755 CommandError.__init__(exception_self, msg)
1757 class LocalLdbError(CommandError):
1758 def __init__(exception_self, self, ldb_error, message):
1759 errval = ldb_error.args[0]
1760 errstr = ldb_error.args[1]
1761 msg = "LOCAL_DC[%s]: %s - ERROR(%d) - %s" % (
1762 self.local_server, message, errval, errstr)
1763 CommandError.__init__(exception_self, msg)
1765 def setup_local_server(self, sambaopts, localdcopts):
1766 if self.local_server is not None:
1767 return self.local_server
1769 lp = sambaopts.get_loadparm()
1771 local_server = localdcopts.ipaddress
1772 if local_server is None:
1773 server_role = lp.server_role()
1774 if server_role != "ROLE_ACTIVE_DIRECTORY_DC":
1775 raise CommandError("Invalid server_role %s" % (server_role))
1776 local_server = lp.get('netbios name')
1777 local_transport = "ncalrpc"
1778 local_binding_options = ""
1779 local_binding_options += ",auth_type=ncalrpc_as_system"
1780 local_ldap_url = None
1783 local_transport = "ncacn_np"
1784 local_binding_options = ""
1785 local_ldap_url = "ldap://%s" % local_server
1786 local_creds = localdcopts.get_credentials(lp)
1790 self.local_server = local_server
1791 self.local_binding_string = "%s:%s[%s]" % (local_transport, local_server, local_binding_options)
1792 self.local_ldap_url = local_ldap_url
1793 self.local_creds = local_creds
1794 return self.local_server
1796 def new_local_lsa_connection(self):
1797 return lsa.lsarpc(self.local_binding_string, self.local_lp, self.local_creds)
1799 def new_local_netlogon_connection(self):
1800 return netlogon.netlogon(self.local_binding_string, self.local_lp, self.local_creds)
1802 def new_local_ldap_connection(self):
1803 return SamDB(url=self.local_ldap_url,
1804 session_info=system_session(),
1805 credentials=self.local_creds,
1808 def setup_remote_server(self, credopts, domain,
1810 require_writable=True):
1813 assert require_writable
1815 if self.remote_server is not None:
1816 return self.remote_server
1818 self.remote_server = "__unknown__remote_server__.%s" % domain
1819 assert self.local_server is not None
1821 remote_creds = credopts.get_credentials(self.local_lp)
1822 remote_server = credopts.ipaddress
1823 remote_binding_options = ""
1825 # TODO: we should also support NT4 domains
1826 # we could use local_netlogon.netr_DsRGetDCNameEx2() with the remote domain name
1827 # and delegate NBT or CLDAP to the local netlogon server
1829 remote_net = Net(remote_creds, self.local_lp, server=remote_server)
1830 remote_flags = nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS
1831 if require_writable:
1832 remote_flags |= nbt.NBT_SERVER_WRITABLE
1834 remote_flags |= nbt.NBT_SERVER_PDC
1835 remote_info = remote_net.finddc(flags=remote_flags, domain=domain, address=remote_server)
1836 except NTSTATUSError as error:
1837 raise CommandError("Failed to find a writeable DC for domain '%s': %s" %
1840 raise CommandError("Failed to find a writeable DC for domain '%s'" % domain)
1842 nbt.NBT_SERVER_PDC: "PDC",
1843 nbt.NBT_SERVER_GC: "GC",
1844 nbt.NBT_SERVER_LDAP: "LDAP",
1845 nbt.NBT_SERVER_DS: "DS",
1846 nbt.NBT_SERVER_KDC: "KDC",
1847 nbt.NBT_SERVER_TIMESERV: "TIMESERV",
1848 nbt.NBT_SERVER_CLOSEST: "CLOSEST",
1849 nbt.NBT_SERVER_WRITABLE: "WRITABLE",
1850 nbt.NBT_SERVER_GOOD_TIMESERV: "GOOD_TIMESERV",
1851 nbt.NBT_SERVER_NDNC: "NDNC",
1852 nbt.NBT_SERVER_SELECT_SECRET_DOMAIN_6: "SELECT_SECRET_DOMAIN_6",
1853 nbt.NBT_SERVER_FULL_SECRET_DOMAIN_6: "FULL_SECRET_DOMAIN_6",
1854 nbt.NBT_SERVER_ADS_WEB_SERVICE: "ADS_WEB_SERVICE",
1855 nbt.NBT_SERVER_DS_8: "DS_8",
1856 nbt.NBT_SERVER_HAS_DNS_NAME: "HAS_DNS_NAME",
1857 nbt.NBT_SERVER_IS_DEFAULT_NC: "IS_DEFAULT_NC",
1858 nbt.NBT_SERVER_FOREST_ROOT: "FOREST_ROOT",
1860 server_type_string = self.generic_bitmap_to_string(flag_map,
1861 remote_info.server_type, names_only=True)
1862 self.outf.write("RemoteDC Netbios[%s] DNS[%s] ServerType[%s]\n" % (
1863 remote_info.pdc_name,
1864 remote_info.pdc_dns_name,
1865 server_type_string))
1867 self.remote_server = remote_info.pdc_dns_name
1868 self.remote_binding_string = "ncacn_np:%s[%s]" % (self.remote_server, remote_binding_options)
1869 self.remote_creds = remote_creds
1870 return self.remote_server
1872 def new_remote_lsa_connection(self):
1873 return lsa.lsarpc(self.remote_binding_string, self.local_lp, self.remote_creds)
1875 def new_remote_netlogon_connection(self):
1876 return netlogon.netlogon(self.remote_binding_string, self.local_lp, self.remote_creds)
1878 def get_lsa_info(self, conn, policy_access):
1879 objectAttr = lsa.ObjectAttribute()
1880 objectAttr.sec_qos = lsa.QosInfo()
1882 policy = conn.OpenPolicy2(''.decode('utf-8'),
1883 objectAttr, policy_access)
1885 info = conn.QueryInfoPolicy2(policy, lsa.LSA_POLICY_INFO_DNS)
1887 return (policy, info)
1889 def get_netlogon_dc_unc(self, conn, server, domain):
1891 info = conn.netr_DsRGetDCNameEx2(server,
1892 None, 0, None, None, None,
1893 netlogon.DS_RETURN_DNS_NAME)
1895 except RuntimeError:
1896 return conn.netr_GetDcName(server, domain)
1898 def get_netlogon_dc_info(self, conn, server):
1899 info = conn.netr_DsRGetDCNameEx2(server,
1900 None, 0, None, None, None,
1901 netlogon.DS_RETURN_DNS_NAME)
1904 def netr_DomainTrust_to_name(self, t):
1905 if t.trust_type == lsa.LSA_TRUST_TYPE_DOWNLEVEL:
1906 return t.netbios_name
1910 def netr_DomainTrust_to_type(self, a, t):
1912 primary_parent = None
1914 if _t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
1916 if not _t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1917 primary_parent = a[_t.parent_index]
1920 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1921 if t is primary_parent:
1924 if t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1927 parent = a[t.parent_index]
1928 if parent is primary:
1933 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1938 def netr_DomainTrust_to_transitive(self, t):
1939 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1942 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE:
1945 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1950 def netr_DomainTrust_to_direction(self, t):
1951 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND and \
1952 t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1955 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND:
1958 if t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1963 def generic_enum_to_string(self, e_dict, v, names_only=False):
1967 v32 = self._uint32(v)
1968 w = "__unknown__%08X__" % v32
1970 r = "0x%x (%s)" % (v, w)
1973 def generic_bitmap_to_string(self, b_dict, v, names_only=False):
1978 for b in sorted(b_dict.keys()):
1985 c32 = self._uint32(c)
1986 s += ["__unknown_%08X__" % c32]
1991 r = "0x%x (%s)" % (v, w)
1994 def trustType_string(self, v):
1996 lsa.LSA_TRUST_TYPE_DOWNLEVEL: "DOWNLEVEL",
1997 lsa.LSA_TRUST_TYPE_UPLEVEL: "UPLEVEL",
1998 lsa.LSA_TRUST_TYPE_MIT: "MIT",
1999 lsa.LSA_TRUST_TYPE_DCE: "DCE",
2001 return self.generic_enum_to_string(types, v)
2003 def trustDirection_string(self, v):
2005 lsa.LSA_TRUST_DIRECTION_INBOUND |
2006 lsa.LSA_TRUST_DIRECTION_OUTBOUND: "BOTH",
2007 lsa.LSA_TRUST_DIRECTION_INBOUND: "INBOUND",
2008 lsa.LSA_TRUST_DIRECTION_OUTBOUND: "OUTBOUND",
2010 return self.generic_enum_to_string(directions, v)
2012 def trustAttributes_string(self, v):
2014 lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE: "NON_TRANSITIVE",
2015 lsa.LSA_TRUST_ATTRIBUTE_UPLEVEL_ONLY: "UPLEVEL_ONLY",
2016 lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN: "QUARANTINED_DOMAIN",
2017 lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE: "FOREST_TRANSITIVE",
2018 lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION: "CROSS_ORGANIZATION",
2019 lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST: "WITHIN_FOREST",
2020 lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL: "TREAT_AS_EXTERNAL",
2021 lsa.LSA_TRUST_ATTRIBUTE_USES_RC4_ENCRYPTION: "USES_RC4_ENCRYPTION",
2023 return self.generic_bitmap_to_string(attributes, v)
2025 def kerb_EncTypes_string(self, v):
2027 security.KERB_ENCTYPE_DES_CBC_CRC: "DES_CBC_CRC",
2028 security.KERB_ENCTYPE_DES_CBC_MD5: "DES_CBC_MD5",
2029 security.KERB_ENCTYPE_RC4_HMAC_MD5: "RC4_HMAC_MD5",
2030 security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96: "AES128_CTS_HMAC_SHA1_96",
2031 security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96: "AES256_CTS_HMAC_SHA1_96",
2032 security.KERB_ENCTYPE_FAST_SUPPORTED: "FAST_SUPPORTED",
2033 security.KERB_ENCTYPE_COMPOUND_IDENTITY_SUPPORTED: "COMPOUND_IDENTITY_SUPPORTED",
2034 security.KERB_ENCTYPE_CLAIMS_SUPPORTED: "CLAIMS_SUPPORTED",
2035 security.KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED: "RESOURCE_SID_COMPRESSION_DISABLED",
2037 return self.generic_bitmap_to_string(enctypes, v)
2039 def entry_tln_status(self, e_flags, ):
2041 return "Status[Enabled]"
2044 lsa.LSA_TLN_DISABLED_NEW: "Disabled-New",
2045 lsa.LSA_TLN_DISABLED_ADMIN: "Disabled",
2046 lsa.LSA_TLN_DISABLED_CONFLICT: "Disabled-Conflicting",
2048 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
2050 def entry_dom_status(self, e_flags):
2052 return "Status[Enabled]"
2055 lsa.LSA_SID_DISABLED_ADMIN: "Disabled-SID",
2056 lsa.LSA_SID_DISABLED_CONFLICT: "Disabled-SID-Conflicting",
2057 lsa.LSA_NB_DISABLED_ADMIN: "Disabled-NB",
2058 lsa.LSA_NB_DISABLED_CONFLICT: "Disabled-NB-Conflicting",
2060 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
2062 def write_forest_trust_info(self, fti, tln=None, collisions=None):
2064 tln_string = " TDO[%s]" % tln
2068 self.outf.write("Namespaces[%d]%s:\n" % (
2069 len(fti.entries), tln_string))
2071 for i, e in enumerate(fti.entries):
2074 collision_string = ""
2076 if collisions is not None:
2077 for c in collisions.entries:
2081 collision_string = " Collision[%s]" % (c.name.string)
2083 d = e.forest_trust_data
2084 if e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
2085 self.outf.write("TLN: %-32s DNS[*.%s]%s\n" % (
2086 self.entry_tln_status(flags),
2087 d.string, collision_string))
2088 elif e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
2089 self.outf.write("TLN_EX: %-29s DNS[*.%s]\n" % (
2091 elif e.type == lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
2092 self.outf.write("DOM: %-32s DNS[%s] Netbios[%s] SID[%s]%s\n" % (
2093 self.entry_dom_status(flags),
2094 d.dns_domain_name.string,
2095 d.netbios_domain_name.string,
2096 d.domain_sid, collision_string))
2100 class cmd_domain_trust_list(DomainTrustCommand):
2101 """List domain trusts."""
2103 synopsis = "%prog [options]"
2105 takes_optiongroups = {
2106 "sambaopts": options.SambaOptions,
2107 "versionopts": options.VersionOptions,
2108 "localdcopts": LocalDCCredentialsOptions,
2114 def run(self, sambaopts=None, versionopts=None, localdcopts=None):
2116 local_server = self.setup_local_server(sambaopts, localdcopts)
2118 local_netlogon = self.new_local_netlogon_connection()
2119 except RuntimeError as error:
2120 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2123 local_netlogon_trusts = \
2124 local_netlogon.netr_DsrEnumerateDomainTrusts(local_server,
2125 netlogon.NETR_TRUST_FLAG_IN_FOREST |
2126 netlogon.NETR_TRUST_FLAG_OUTBOUND |
2127 netlogon.NETR_TRUST_FLAG_INBOUND)
2128 except RuntimeError as error:
2129 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
2130 # TODO: we could implement a fallback to lsa.EnumTrustDom()
2131 raise CommandError("LOCAL_DC[%s]: netr_DsrEnumerateDomainTrusts not supported." % (
2133 raise self.LocalRuntimeError(self, error, "netr_DsrEnumerateDomainTrusts failed")
2135 a = local_netlogon_trusts.array
2137 if t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
2139 self.outf.write("%-14s %-15s %-19s %s\n" % (
2140 "Type[%s]" % self.netr_DomainTrust_to_type(a, t),
2141 "Transitive[%s]" % self.netr_DomainTrust_to_transitive(t),
2142 "Direction[%s]" % self.netr_DomainTrust_to_direction(t),
2143 "Name[%s]" % self.netr_DomainTrust_to_name(t)))
2147 class cmd_domain_trust_show(DomainTrustCommand):
2148 """Show trusted domain details."""
2150 synopsis = "%prog NAME [options]"
2152 takes_optiongroups = {
2153 "sambaopts": options.SambaOptions,
2154 "versionopts": options.VersionOptions,
2155 "localdcopts": LocalDCCredentialsOptions,
2161 takes_args = ["domain"]
2163 def run(self, domain, sambaopts=None, versionopts=None, localdcopts=None):
2165 local_server = self.setup_local_server(sambaopts, localdcopts)
2167 local_lsa = self.new_local_lsa_connection()
2168 except RuntimeError as error:
2169 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2172 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2173 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2174 except RuntimeError as error:
2175 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2177 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2178 local_lsa_info.name.string,
2179 local_lsa_info.dns_domain.string,
2180 local_lsa_info.sid))
2182 lsaString = lsa.String()
2183 lsaString.string = domain
2186 local_lsa.QueryTrustedDomainInfoByName(local_policy,
2188 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2189 local_tdo_info = local_tdo_full.info_ex
2190 local_tdo_posix = local_tdo_full.posix_offset
2191 except NTSTATUSError as error:
2192 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2193 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2195 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(FULL_INFO) failed")
2198 local_tdo_enctypes = \
2199 local_lsa.QueryTrustedDomainInfoByName(local_policy,
2201 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES)
2202 except NTSTATUSError as error:
2203 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_PARAMETER):
2205 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_INFO_CLASS):
2208 if error is not None:
2209 raise self.LocalRuntimeError(self, error,
2210 "QueryTrustedDomainInfoByName(SUPPORTED_ENCRYPTION_TYPES) failed")
2212 local_tdo_enctypes = lsa.TrustDomainInfoSupportedEncTypes()
2213 local_tdo_enctypes.enc_types = 0
2216 local_tdo_forest = None
2217 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2218 local_tdo_forest = \
2219 local_lsa.lsaRQueryForestTrustInformation(local_policy,
2221 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
2222 except RuntimeError as error:
2223 if self.check_runtime_error(error, ntstatus.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE):
2225 if self.check_runtime_error(error, ntstatus.NT_STATUS_NOT_FOUND):
2227 if error is not None:
2228 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation failed")
2230 local_tdo_forest = lsa.ForestTrustInformation()
2231 local_tdo_forest.count = 0
2232 local_tdo_forest.entries = []
2234 self.outf.write("TrustedDomain:\n\n")
2235 self.outf.write("NetbiosName: %s\n" % local_tdo_info.netbios_name.string)
2236 if local_tdo_info.netbios_name.string != local_tdo_info.domain_name.string:
2237 self.outf.write("DnsName: %s\n" % local_tdo_info.domain_name.string)
2238 self.outf.write("SID: %s\n" % local_tdo_info.sid)
2239 self.outf.write("Type: %s\n" % self.trustType_string(local_tdo_info.trust_type))
2240 self.outf.write("Direction: %s\n" % self.trustDirection_string(local_tdo_info.trust_direction))
2241 self.outf.write("Attributes: %s\n" % self.trustAttributes_string(local_tdo_info.trust_attributes))
2242 posix_offset_u32 = ctypes.c_uint32(local_tdo_posix.posix_offset).value
2243 posix_offset_i32 = ctypes.c_int32(local_tdo_posix.posix_offset).value
2244 self.outf.write("PosixOffset: 0x%08X (%d)\n" % (posix_offset_u32, posix_offset_i32))
2245 self.outf.write("kerb_EncTypes: %s\n" % self.kerb_EncTypes_string(local_tdo_enctypes.enc_types))
2247 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2248 self.write_forest_trust_info(local_tdo_forest,
2249 tln=local_tdo_info.domain_name.string)
2254 class cmd_domain_trust_create(DomainTrustCommand):
2255 """Create a domain or forest trust."""
2257 synopsis = "%prog DOMAIN [options]"
2259 takes_optiongroups = {
2260 "sambaopts": options.SambaOptions,
2261 "versionopts": options.VersionOptions,
2262 "credopts": options.CredentialsOptions,
2263 "localdcopts": LocalDCCredentialsOptions,
2267 Option("--type", type="choice", metavar="TYPE",
2268 choices=["external", "forest"],
2269 help="The type of the trust: 'external' or 'forest'.",
2271 default="external"),
2272 Option("--direction", type="choice", metavar="DIRECTION",
2273 choices=["incoming", "outgoing", "both"],
2274 help="The trust direction: 'incoming', 'outgoing' or 'both'.",
2275 dest='trust_direction',
2277 Option("--create-location", type="choice", metavar="LOCATION",
2278 choices=["local", "both"],
2279 help="Where to create the trusted domain object: 'local' or 'both'.",
2280 dest='create_location',
2282 Option("--cross-organisation", action="store_true",
2283 help="The related domains does not belong to the same organisation.",
2284 dest='cross_organisation',
2286 Option("--quarantined", type="choice", metavar="yes|no",
2287 choices=["yes", "no", None],
2288 help="Special SID filtering rules are applied to the trust. "
2289 "With --type=external the default is yes. "
2290 "With --type=forest the default is no.",
2291 dest='quarantined_arg',
2293 Option("--not-transitive", action="store_true",
2294 help="The forest trust is not transitive.",
2295 dest='not_transitive',
2297 Option("--treat-as-external", action="store_true",
2298 help="The treat the forest trust as external.",
2299 dest='treat_as_external',
2301 Option("--no-aes-keys", action="store_false",
2302 help="The trust uses aes kerberos keys.",
2303 dest='use_aes_keys',
2305 Option("--skip-validation", action="store_false",
2306 help="Skip validation of the trust.",
2311 takes_args = ["domain"]
2313 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2314 trust_type=None, trust_direction=None, create_location=None,
2315 cross_organisation=False, quarantined_arg=None,
2316 not_transitive=False, treat_as_external=False,
2317 use_aes_keys=False, validate=True):
2319 lsaString = lsa.String()
2322 if quarantined_arg is None:
2323 if trust_type == 'external':
2325 elif quarantined_arg == 'yes':
2328 if trust_type != 'forest':
2330 raise CommandError("--not-transitive requires --type=forest")
2331 if treat_as_external:
2332 raise CommandError("--treat-as-external requires --type=forest")
2336 enc_types = lsa.TrustDomainInfoSupportedEncTypes()
2337 enc_types.enc_types = security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96
2338 enc_types.enc_types |= security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96
2340 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2341 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2342 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2344 local_trust_info = lsa.TrustDomainInfoInfoEx()
2345 local_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2346 local_trust_info.trust_direction = 0
2347 if trust_direction == "both":
2348 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2349 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2350 elif trust_direction == "incoming":
2351 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2352 elif trust_direction == "outgoing":
2353 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2354 local_trust_info.trust_attributes = 0
2355 if cross_organisation:
2356 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2358 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2359 if trust_type == "forest":
2360 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2362 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2363 if treat_as_external:
2364 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2366 def get_password(name):
2369 if password is not None and password is not '':
2371 password = getpass("New %s Password: " % name)
2372 passwordverify = getpass("Retype %s Password: " % name)
2373 if not password == passwordverify:
2375 self.outf.write("Sorry, passwords do not match.\n")
2377 incoming_secret = None
2378 outgoing_secret = None
2379 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2380 if create_location == "local":
2381 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2382 incoming_password = get_password("Incoming Trust")
2383 incoming_secret = string_to_byte_array(incoming_password.encode('utf-16-le'))
2384 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2385 outgoing_password = get_password("Outgoing Trust")
2386 outgoing_secret = string_to_byte_array(outgoing_password.encode('utf-16-le'))
2388 remote_trust_info = None
2390 # We use 240 random bytes.
2391 # Windows uses 28 or 240 random bytes. I guess it's
2392 # based on the trust type external vs. forest.
2394 # The initial trust password can be up to 512 bytes
2395 # while the versioned passwords used for periodic updates
2396 # can only be up to 498 bytes, as netr_ServerPasswordSet2()
2397 # needs to pass the NL_PASSWORD_VERSION structure within the
2398 # 512 bytes and a 2 bytes confounder is required.
2400 def random_trust_secret(length):
2401 pw = samba.generate_random_machine_password(length // 2, length // 2)
2402 return string_to_byte_array(pw.encode('utf-16-le'))
2404 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2405 incoming_secret = random_trust_secret(240)
2406 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2407 outgoing_secret = random_trust_secret(240)
2409 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2410 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2412 remote_trust_info = lsa.TrustDomainInfoInfoEx()
2413 remote_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2414 remote_trust_info.trust_direction = 0
2415 if trust_direction == "both":
2416 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2417 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2418 elif trust_direction == "incoming":
2419 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2420 elif trust_direction == "outgoing":
2421 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2422 remote_trust_info.trust_attributes = 0
2423 if cross_organisation:
2424 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2426 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2427 if trust_type == "forest":
2428 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2430 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2431 if treat_as_external:
2432 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2434 local_server = self.setup_local_server(sambaopts, localdcopts)
2436 local_lsa = self.new_local_lsa_connection()
2437 except RuntimeError as error:
2438 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2441 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2442 except RuntimeError as error:
2443 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2445 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2446 local_lsa_info.name.string,
2447 local_lsa_info.dns_domain.string,
2448 local_lsa_info.sid))
2451 remote_server = self.setup_remote_server(credopts, domain)
2452 except RuntimeError as error:
2453 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2456 remote_lsa = self.new_remote_lsa_connection()
2457 except RuntimeError as error:
2458 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2461 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2462 except RuntimeError as error:
2463 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2465 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2466 remote_lsa_info.name.string,
2467 remote_lsa_info.dns_domain.string,
2468 remote_lsa_info.sid))
2470 local_trust_info.domain_name.string = remote_lsa_info.dns_domain.string
2471 local_trust_info.netbios_name.string = remote_lsa_info.name.string
2472 local_trust_info.sid = remote_lsa_info.sid
2474 if remote_trust_info:
2475 remote_trust_info.domain_name.string = local_lsa_info.dns_domain.string
2476 remote_trust_info.netbios_name.string = local_lsa_info.name.string
2477 remote_trust_info.sid = local_lsa_info.sid
2480 lsaString.string = local_trust_info.domain_name.string
2481 local_old_netbios = \
2482 local_lsa.QueryTrustedDomainInfoByName(local_policy,
2484 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2485 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2486 except NTSTATUSError as error:
2487 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2488 raise self.LocalRuntimeError(self, error,
2489 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2493 lsaString.string = local_trust_info.netbios_name.string
2495 local_lsa.QueryTrustedDomainInfoByName(local_policy,
2497 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2498 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2499 except NTSTATUSError as error:
2500 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2501 raise self.LocalRuntimeError(self, error,
2502 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2505 if remote_trust_info:
2507 lsaString.string = remote_trust_info.domain_name.string
2508 remote_old_netbios = \
2509 remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2511 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2512 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2513 except NTSTATUSError as error:
2514 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2515 raise self.RemoteRuntimeError(self, error,
2516 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2520 lsaString.string = remote_trust_info.netbios_name.string
2522 remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2524 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2525 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2526 except NTSTATUSError as error:
2527 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2528 raise self.RemoteRuntimeError(self, error,
2529 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2533 local_netlogon = self.new_local_netlogon_connection()
2534 except RuntimeError as error:
2535 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2538 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
2539 except RuntimeError as error:
2540 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
2542 if remote_trust_info:
2544 remote_netlogon = self.new_remote_netlogon_connection()
2545 except RuntimeError as error:
2546 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2549 remote_netlogon_dc_unc = self.get_netlogon_dc_unc(remote_netlogon,
2550 remote_server, domain)
2551 except RuntimeError as error:
2552 raise self.RemoteRuntimeError(self, error, "failed to get netlogon dc info")
2554 def generate_AuthInOutBlob(secret, update_time):
2556 blob = drsblobs.trustAuthInOutBlob()
2561 clear = drsblobs.AuthInfoClear()
2562 clear.size = len(secret)
2563 clear.password = secret
2565 info = drsblobs.AuthenticationInformation()
2566 info.LastUpdateTime = samba.unix2nttime(update_time)
2567 info.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
2568 info.AuthInfo = clear
2570 array = drsblobs.AuthenticationInformationArray()
2572 array.array = [info]
2574 blob = drsblobs.trustAuthInOutBlob()
2576 blob.current = array
2580 def generate_AuthInfoInternal(session_key, incoming=None, outgoing=None):
2581 confounder = [0] * 512
2582 for i in range(len(confounder)):
2583 confounder[i] = random.randint(0, 255)
2585 trustpass = drsblobs.trustDomainPasswords()
2587 trustpass.confounder = confounder
2588 trustpass.outgoing = outgoing
2589 trustpass.incoming = incoming
2591 trustpass_blob = ndr_pack(trustpass)
2593 encrypted_trustpass = arcfour_encrypt(session_key, trustpass_blob)
2595 auth_blob = lsa.DATA_BUF2()
2596 auth_blob.size = len(encrypted_trustpass)
2597 auth_blob.data = string_to_byte_array(encrypted_trustpass)
2599 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
2600 auth_info.auth_blob = auth_blob
2604 update_time = samba.current_unix_time()
2605 incoming_blob = generate_AuthInOutBlob(incoming_secret, update_time)
2606 outgoing_blob = generate_AuthInOutBlob(outgoing_secret, update_time)
2608 local_tdo_handle = None
2609 remote_tdo_handle = None
2611 local_auth_info = generate_AuthInfoInternal(local_lsa.session_key,
2612 incoming=incoming_blob,
2613 outgoing=outgoing_blob)
2614 if remote_trust_info:
2615 remote_auth_info = generate_AuthInfoInternal(remote_lsa.session_key,
2616 incoming=outgoing_blob,
2617 outgoing=incoming_blob)
2620 if remote_trust_info:
2621 self.outf.write("Creating remote TDO.\n")
2622 current_request = {"location": "remote", "name": "CreateTrustedDomainEx2"}
2623 remote_tdo_handle = \
2624 remote_lsa.CreateTrustedDomainEx2(remote_policy,
2627 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2628 self.outf.write("Remote TDO created.\n")
2630 self.outf.write("Setting supported encryption types on remote TDO.\n")
2631 current_request = {"location": "remote", "name": "SetInformationTrustedDomain"}
2632 remote_lsa.SetInformationTrustedDomain(remote_tdo_handle,
2633 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2636 self.outf.write("Creating local TDO.\n")
2637 current_request = {"location": "local", "name": "CreateTrustedDomainEx2"}
2638 local_tdo_handle = local_lsa.CreateTrustedDomainEx2(local_policy,
2641 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2642 self.outf.write("Local TDO created\n")
2644 self.outf.write("Setting supported encryption types on local TDO.\n")
2645 current_request = {"location": "local", "name": "SetInformationTrustedDomain"}
2646 local_lsa.SetInformationTrustedDomain(local_tdo_handle,
2647 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2649 except RuntimeError as error:
2650 self.outf.write("Error: %s failed %sly - cleaning up\n" % (
2651 current_request['name'], current_request['location']))
2652 if remote_tdo_handle:
2653 self.outf.write("Deleting remote TDO.\n")
2654 remote_lsa.DeleteObject(remote_tdo_handle)
2655 remote_tdo_handle = None
2656 if local_tdo_handle:
2657 self.outf.write("Deleting local TDO.\n")
2658 local_lsa.DeleteObject(local_tdo_handle)
2659 local_tdo_handle = None
2660 if current_request['location'] is "remote":
2661 raise self.RemoteRuntimeError(self, error, "%s" % (
2662 current_request['name']))
2663 raise self.LocalRuntimeError(self, error, "%s" % (
2664 current_request['name']))
2667 if local_trust_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2668 self.outf.write("Setup local forest trust information...\n")
2670 # get all information about the remote trust
2671 # this triggers netr_GetForestTrustInformation to the remote domain
2672 # and lsaRSetForestTrustInformation() locally, but new top level
2673 # names are disabled by default.
2674 local_forest_info = \
2675 local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
2676 remote_lsa_info.dns_domain.string,
2677 netlogon.DS_GFTI_UPDATE_TDO)
2678 except RuntimeError as error:
2679 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2682 # here we try to enable all top level names
2683 local_forest_collision = \
2684 local_lsa.lsaRSetForestTrustInformation(local_policy,
2685 remote_lsa_info.dns_domain,
2686 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2689 except RuntimeError as error:
2690 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2692 self.write_forest_trust_info(local_forest_info,
2693 tln=remote_lsa_info.dns_domain.string,
2694 collisions=local_forest_collision)
2696 if remote_trust_info:
2697 self.outf.write("Setup remote forest trust information...\n")
2699 # get all information about the local trust (from the perspective of the remote domain)
2700 # this triggers netr_GetForestTrustInformation to our domain.
2701 # and lsaRSetForestTrustInformation() remotely, but new top level
2702 # names are disabled by default.
2703 remote_forest_info = \
2704 remote_netlogon.netr_DsRGetForestTrustInformation(remote_netlogon_dc_unc,
2705 local_lsa_info.dns_domain.string,
2706 netlogon.DS_GFTI_UPDATE_TDO)
2707 except RuntimeError as error:
2708 raise self.RemoteRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2711 # here we try to enable all top level names
2712 remote_forest_collision = \
2713 remote_lsa.lsaRSetForestTrustInformation(remote_policy,
2714 local_lsa_info.dns_domain,
2715 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2718 except RuntimeError as error:
2719 raise self.RemoteRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2721 self.write_forest_trust_info(remote_forest_info,
2722 tln=local_lsa_info.dns_domain.string,
2723 collisions=remote_forest_collision)
2725 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2726 self.outf.write("Validating outgoing trust...\n")
2728 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_netlogon_info.dc_unc,
2729 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2731 remote_lsa_info.dns_domain.string)
2732 except RuntimeError as error:
2733 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2735 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2736 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2738 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2739 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2740 local_trust_verify.trusted_dc_name,
2741 local_trust_verify.tc_connection_status[1],
2742 local_trust_verify.pdc_connection_status[1])
2744 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2745 local_trust_verify.trusted_dc_name,
2746 local_trust_verify.tc_connection_status[1],
2747 local_trust_verify.pdc_connection_status[1])
2749 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2750 raise CommandError(local_validation)
2752 self.outf.write("OK: %s\n" % local_validation)
2754 if remote_trust_info:
2755 if remote_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2756 self.outf.write("Validating incoming trust...\n")
2758 remote_trust_verify = \
2759 remote_netlogon.netr_LogonControl2Ex(remote_netlogon_dc_unc,
2760 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2762 local_lsa_info.dns_domain.string)
2763 except RuntimeError as error:
2764 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2766 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2767 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2769 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2770 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2771 remote_trust_verify.trusted_dc_name,
2772 remote_trust_verify.tc_connection_status[1],
2773 remote_trust_verify.pdc_connection_status[1])
2775 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2776 remote_trust_verify.trusted_dc_name,
2777 remote_trust_verify.tc_connection_status[1],
2778 remote_trust_verify.pdc_connection_status[1])
2780 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
2781 raise CommandError(remote_validation)
2783 self.outf.write("OK: %s\n" % remote_validation)
2785 if remote_tdo_handle is not None:
2787 remote_lsa.Close(remote_tdo_handle)
2788 except RuntimeError as error:
2790 remote_tdo_handle = None
2791 if local_tdo_handle is not None:
2793 local_lsa.Close(local_tdo_handle)
2794 except RuntimeError as error:
2796 local_tdo_handle = None
2798 self.outf.write("Success.\n")
2802 class cmd_domain_trust_delete(DomainTrustCommand):
2803 """Delete a domain trust."""
2805 synopsis = "%prog DOMAIN [options]"
2807 takes_optiongroups = {
2808 "sambaopts": options.SambaOptions,
2809 "versionopts": options.VersionOptions,
2810 "credopts": options.CredentialsOptions,
2811 "localdcopts": LocalDCCredentialsOptions,
2815 Option("--delete-location", type="choice", metavar="LOCATION",
2816 choices=["local", "both"],
2817 help="Where to delete the trusted domain object: 'local' or 'both'.",
2818 dest='delete_location',
2822 takes_args = ["domain"]
2824 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2825 delete_location=None):
2827 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2828 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2829 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2831 if delete_location == "local":
2832 remote_policy_access = None
2834 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2835 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2836 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2838 local_server = self.setup_local_server(sambaopts, localdcopts)
2840 local_lsa = self.new_local_lsa_connection()
2841 except RuntimeError as error:
2842 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2845 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2846 except RuntimeError as error:
2847 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2849 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2850 local_lsa_info.name.string,
2851 local_lsa_info.dns_domain.string,
2852 local_lsa_info.sid))
2854 local_tdo_info = None
2855 local_tdo_handle = None
2856 remote_tdo_info = None
2857 remote_tdo_handle = None
2859 lsaString = lsa.String()
2861 lsaString.string = domain
2862 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2863 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2864 except NTSTATUSError as error:
2865 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2866 raise CommandError("Failed to find trust for domain '%s'" % domain)
2867 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2869 if remote_policy_access is not None:
2871 remote_server = self.setup_remote_server(credopts, domain)
2872 except RuntimeError as error:
2873 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2876 remote_lsa = self.new_remote_lsa_connection()
2877 except RuntimeError as error:
2878 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2881 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2882 except RuntimeError as error:
2883 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2885 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2886 remote_lsa_info.name.string,
2887 remote_lsa_info.dns_domain.string,
2888 remote_lsa_info.sid))
2890 if remote_lsa_info.sid != local_tdo_info.sid or \
2891 remote_lsa_info.name.string != local_tdo_info.netbios_name.string or \
2892 remote_lsa_info.dns_domain.string != local_tdo_info.domain_name.string:
2893 raise CommandError("LocalTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2894 local_tdo_info.netbios_name.string,
2895 local_tdo_info.domain_name.string,
2896 local_tdo_info.sid))
2899 lsaString.string = local_lsa_info.dns_domain.string
2901 remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2903 lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2904 except NTSTATUSError as error:
2905 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2906 raise self.RemoteRuntimeError(self, error, "QueryTrustedDomainInfoByName(%s)" % (
2910 if remote_tdo_info is not None:
2911 if local_lsa_info.sid != remote_tdo_info.sid or \
2912 local_lsa_info.name.string != remote_tdo_info.netbios_name.string or \
2913 local_lsa_info.dns_domain.string != remote_tdo_info.domain_name.string:
2914 raise CommandError("RemoteTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2915 remote_tdo_info.netbios_name.string,
2916 remote_tdo_info.domain_name.string,
2917 remote_tdo_info.sid))
2919 if local_tdo_info is not None:
2921 lsaString.string = local_tdo_info.domain_name.string
2922 local_tdo_handle = \
2923 local_lsa.OpenTrustedDomainByName(local_policy,
2925 security.SEC_STD_DELETE)
2926 except RuntimeError as error:
2927 raise self.LocalRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2930 local_lsa.DeleteObject(local_tdo_handle)
2931 local_tdo_handle = None
2933 if remote_tdo_info is not None:
2935 lsaString.string = remote_tdo_info.domain_name.string
2936 remote_tdo_handle = \
2937 remote_lsa.OpenTrustedDomainByName(remote_policy,
2939 security.SEC_STD_DELETE)
2940 except RuntimeError as error:
2941 raise self.RemoteRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2944 if remote_tdo_handle is not None:
2946 remote_lsa.DeleteObject(remote_tdo_handle)
2947 remote_tdo_handle = None
2948 self.outf.write("RemoteTDO deleted.\n")
2949 except RuntimeError as error:
2950 self.outf.write("%s\n" % self.RemoteRuntimeError(self, error, "DeleteObject() failed"))
2952 if local_tdo_handle is not None:
2954 local_lsa.DeleteObject(local_tdo_handle)
2955 local_tdo_handle = None
2956 self.outf.write("LocalTDO deleted.\n")
2957 except RuntimeError as error:
2958 self.outf.write("%s\n" % self.LocalRuntimeError(self, error, "DeleteObject() failed"))
2963 class cmd_domain_trust_validate(DomainTrustCommand):
2964 """Validate a domain trust."""
2966 synopsis = "%prog DOMAIN [options]"
2968 takes_optiongroups = {
2969 "sambaopts": options.SambaOptions,
2970 "versionopts": options.VersionOptions,
2971 "credopts": options.CredentialsOptions,
2972 "localdcopts": LocalDCCredentialsOptions,
2976 Option("--validate-location", type="choice", metavar="LOCATION",
2977 choices=["local", "both"],
2978 help="Where to validate the trusted domain object: 'local' or 'both'.",
2979 dest='validate_location',
2983 takes_args = ["domain"]
2985 def run(self, domain, sambaopts=None, versionopts=None, credopts=None, localdcopts=None,
2986 validate_location=None):
2988 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2990 local_server = self.setup_local_server(sambaopts, localdcopts)
2992 local_lsa = self.new_local_lsa_connection()
2993 except RuntimeError as error:
2994 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2997 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2998 except RuntimeError as error:
2999 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
3001 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
3002 local_lsa_info.name.string,
3003 local_lsa_info.dns_domain.string,
3004 local_lsa_info.sid))
3007 lsaString = lsa.String()
3008 lsaString.string = domain
3010 local_lsa.QueryTrustedDomainInfoByName(local_policy,
3012 lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
3013 except NTSTATUSError as error:
3014 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
3015 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
3017 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
3019 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
3020 local_tdo_info.netbios_name.string,
3021 local_tdo_info.domain_name.string,
3022 local_tdo_info.sid))
3025 local_netlogon = self.new_local_netlogon_connection()
3026 except RuntimeError as error:
3027 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3030 local_trust_verify = \
3031 local_netlogon.netr_LogonControl2Ex(local_server,
3032 netlogon.NETLOGON_CONTROL_TC_VERIFY,
3034 local_tdo_info.domain_name.string)
3035 except RuntimeError as error:
3036 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
3038 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
3039 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
3041 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
3042 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
3043 local_trust_verify.trusted_dc_name,
3044 local_trust_verify.tc_connection_status[1],
3045 local_trust_verify.pdc_connection_status[1])
3047 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
3048 local_trust_verify.trusted_dc_name,
3049 local_trust_verify.tc_connection_status[1],
3050 local_trust_verify.pdc_connection_status[1])
3052 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
3053 raise CommandError(local_validation)
3055 self.outf.write("OK: %s\n" % local_validation)
3058 server = local_trust_verify.trusted_dc_name.replace('\\', '')
3059 domain_and_server = "%s\\%s" % (local_tdo_info.domain_name.string, server)
3060 local_trust_rediscover = \
3061 local_netlogon.netr_LogonControl2Ex(local_server,
3062 netlogon.NETLOGON_CONTROL_REDISCOVER,
3065 except RuntimeError as error:
3066 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
3068 local_conn_status = self._uint32(local_trust_rediscover.tc_connection_status[0])
3069 local_rediscover = "LocalRediscover: DC[%s] CONNECTION[%s]" % (
3070 local_trust_rediscover.trusted_dc_name,
3071 local_trust_rediscover.tc_connection_status[1])
3073 if local_conn_status != werror.WERR_SUCCESS:
3074 raise CommandError(local_rediscover)
3076 self.outf.write("OK: %s\n" % local_rediscover)
3078 if validate_location != "local":
3080 remote_server = self.setup_remote_server(credopts, domain, require_pdc=False)
3081 except RuntimeError as error:
3082 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
3085 remote_netlogon = self.new_remote_netlogon_connection()
3086 except RuntimeError as error:
3087 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
3090 remote_trust_verify = \
3091 remote_netlogon.netr_LogonControl2Ex(remote_server,
3092 netlogon.NETLOGON_CONTROL_TC_VERIFY,
3094 local_lsa_info.dns_domain.string)
3095 except RuntimeError as error:
3096 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
3098 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
3099 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
3101 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
3102 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
3103 remote_trust_verify.trusted_dc_name,
3104 remote_trust_verify.tc_connection_status[1],
3105 remote_trust_verify.pdc_connection_status[1])
3107 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
3108 remote_trust_verify.trusted_dc_name,
3109 remote_trust_verify.tc_connection_status[1],
3110 remote_trust_verify.pdc_connection_status[1])
3112 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
3113 raise CommandError(remote_validation)
3115 self.outf.write("OK: %s\n" % remote_validation)
3118 server = remote_trust_verify.trusted_dc_name.replace('\\', '')
3119 domain_and_server = "%s\\%s" % (local_lsa_info.dns_domain.string, server)
3120 remote_trust_rediscover = \
3121 remote_netlogon.netr_LogonControl2Ex(remote_server,
3122 netlogon.NETLOGON_CONTROL_REDISCOVER,
3125 except RuntimeError as error:
3126 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
3128 remote_conn_status = self._uint32(remote_trust_rediscover.tc_connection_status[0])
3130 remote_rediscover = "RemoteRediscover: DC[%s] CONNECTION[%s]" % (
3131 remote_trust_rediscover.trusted_dc_name,
3132 remote_trust_rediscover.tc_connection_status[1])
3134 if remote_conn_status != werror.WERR_SUCCESS:
3135 raise CommandError(remote_rediscover)
3137 self.outf.write("OK: %s\n" % remote_rediscover)
3142 class cmd_domain_trust_namespaces(DomainTrustCommand):
3143 """Manage forest trust namespaces."""
3145 synopsis = "%prog [DOMAIN] [options]"
3147 takes_optiongroups = {
3148 "sambaopts": options.SambaOptions,
3149 "versionopts": options.VersionOptions,
3150 "localdcopts": LocalDCCredentialsOptions,
3154 Option("--refresh", type="choice", metavar="check|store",
3155 choices=["check", "store", None],
3156 help="List and maybe store refreshed forest trust information: 'check' or 'store'.",
3159 Option("--enable-all", action="store_true",
3160 help="Try to update disabled entries, not allowed with --refresh=check.",
3163 Option("--enable-tln", action="append", metavar='DNSDOMAIN',
3164 help="Enable a top level name entry. Can be specified multiple times.",
3167 Option("--disable-tln", action="append", metavar='DNSDOMAIN',
3168 help="Disable a top level name entry. Can be specified multiple times.",
3171 Option("--add-tln-ex", action="append", metavar='DNSDOMAIN',
3172 help="Add a top level exclusion entry. Can be specified multiple times.",
3175 Option("--delete-tln-ex", action="append", metavar='DNSDOMAIN',
3176 help="Delete a top level exclusion entry. Can be specified multiple times.",
3177 dest='delete_tln_ex',
3179 Option("--enable-nb", action="append", metavar='NETBIOSDOMAIN',
3180 help="Enable a netbios name in a domain entry. Can be specified multiple times.",
3183 Option("--disable-nb", action="append", metavar='NETBIOSDOMAIN',
3184 help="Disable a netbios name in a domain entry. Can be specified multiple times.",
3187 Option("--enable-sid", action="append", metavar='DOMAINSID',
3188 help="Enable a SID in a domain entry. Can be specified multiple times.",
3189 dest='enable_sid_str',
3191 Option("--disable-sid", action="append", metavar='DOMAINSID',
3192 help="Disable a SID in a domain entry. Can be specified multiple times.",
3193 dest='disable_sid_str',
3195 Option("--add-upn-suffix", action="append", metavar='DNSDOMAIN',
3196 help="Add a new uPNSuffixes attribute for the local forest. Can be specified multiple times.",
3199 Option("--delete-upn-suffix", action="append", metavar='DNSDOMAIN',
3200 help="Delete an existing uPNSuffixes attribute of the local forest. Can be specified multiple times.",
3203 Option("--add-spn-suffix", action="append", metavar='DNSDOMAIN',
3204 help="Add a new msDS-SPNSuffixes attribute for the local forest. Can be specified multiple times.",
3207 Option("--delete-spn-suffix", action="append", metavar='DNSDOMAIN',
3208 help="Delete an existing msDS-SPNSuffixes attribute of the local forest. Can be specified multiple times.",
3213 takes_args = ["domain?"]
3215 def run(self, domain=None, sambaopts=None, localdcopts=None, versionopts=None,
3216 refresh=None, enable_all=False,
3217 enable_tln=[], disable_tln=[], add_tln_ex=[], delete_tln_ex=[],
3218 enable_sid_str=[], disable_sid_str=[], enable_nb=[], disable_nb=[],
3219 add_upn=[], delete_upn=[], add_spn=[], delete_spn=[]):
3221 require_update = False
3224 if refresh == "store":
3225 raise CommandError("--refresh=%s not allowed without DOMAIN" % refresh)
3228 raise CommandError("--enable-all not allowed without DOMAIN")
3230 if len(enable_tln) > 0:
3231 raise CommandError("--enable-tln not allowed without DOMAIN")
3232 if len(disable_tln) > 0:
3233 raise CommandError("--disable-tln not allowed without DOMAIN")
3235 if len(add_tln_ex) > 0:
3236 raise CommandError("--add-tln-ex not allowed without DOMAIN")
3237 if len(delete_tln_ex) > 0:
3238 raise CommandError("--delete-tln-ex not allowed without DOMAIN")
3240 if len(enable_nb) > 0:
3241 raise CommandError("--enable-nb not allowed without DOMAIN")
3242 if len(disable_nb) > 0:
3243 raise CommandError("--disable-nb not allowed without DOMAIN")
3245 if len(enable_sid_str) > 0:
3246 raise CommandError("--enable-sid not allowed without DOMAIN")
3247 if len(disable_sid_str) > 0:
3248 raise CommandError("--disable-sid not allowed without DOMAIN")
3250 if len(add_upn) > 0:
3252 if not n.startswith("*."):
3254 raise CommandError("value[%s] specified for --add-upn-suffix should not include with '*.'" % n)
3255 require_update = True
3256 if len(delete_upn) > 0:
3257 for n in delete_upn:
3258 if not n.startswith("*."):
3260 raise CommandError("value[%s] specified for --delete-upn-suffix should not include with '*.'" % n)
3261 require_update = True
3263 for d in delete_upn:
3264 if a.lower() != d.lower():
3266 raise CommandError("value[%s] specified for --add-upn-suffix and --delete-upn-suffix" % a)
3268 if len(add_spn) > 0:
3270 if not n.startswith("*."):
3272 raise CommandError("value[%s] specified for --add-spn-suffix should not include with '*.'" % n)
3273 require_update = True
3274 if len(delete_spn) > 0:
3275 for n in delete_spn:
3276 if not n.startswith("*."):
3278 raise CommandError("value[%s] specified for --delete-spn-suffix should not include with '*.'" % n)
3279 require_update = True
3281 for d in delete_spn:
3282 if a.lower() != d.lower():
3284 raise CommandError("value[%s] specified for --add-spn-suffix and --delete-spn-suffix" % a)
3286 if len(add_upn) > 0:
3287 raise CommandError("--add-upn-suffix not allowed together with DOMAIN")
3288 if len(delete_upn) > 0:
3289 raise CommandError("--delete-upn-suffix not allowed together with DOMAIN")
3290 if len(add_spn) > 0:
3291 raise CommandError("--add-spn-suffix not allowed together with DOMAIN")
3292 if len(delete_spn) > 0:
3293 raise CommandError("--delete-spn-suffix not allowed together with DOMAIN")
3295 if refresh is not None:
3296 if refresh == "store":
3297 require_update = True
3299 if enable_all and refresh != "store":
3300 raise CommandError("--enable-all not allowed together with --refresh=%s" % refresh)
3302 if len(enable_tln) > 0:
3303 raise CommandError("--enable-tln not allowed together with --refresh")
3304 if len(disable_tln) > 0:
3305 raise CommandError("--disable-tln not allowed together with --refresh")
3307 if len(add_tln_ex) > 0:
3308 raise CommandError("--add-tln-ex not allowed together with --refresh")
3309 if len(delete_tln_ex) > 0:
3310 raise CommandError("--delete-tln-ex not allowed together with --refresh")
3312 if len(enable_nb) > 0:
3313 raise CommandError("--enable-nb not allowed together with --refresh")
3314 if len(disable_nb) > 0:
3315 raise CommandError("--disable-nb not allowed together with --refresh")
3317 if len(enable_sid_str) > 0:
3318 raise CommandError("--enable-sid not allowed together with --refresh")
3319 if len(disable_sid_str) > 0:
3320 raise CommandError("--disable-sid not allowed together with --refresh")
3323 require_update = True
3325 if len(enable_tln) > 0:
3326 raise CommandError("--enable-tln not allowed together with --enable-all")
3328 if len(enable_nb) > 0:
3329 raise CommandError("--enable-nb not allowed together with --enable-all")
3331 if len(enable_sid_str) > 0:
3332 raise CommandError("--enable-sid not allowed together with --enable-all")
3334 if len(enable_tln) > 0:
3335 require_update = True
3336 if len(disable_tln) > 0:
3337 require_update = True
3338 for e in enable_tln:
3339 for d in disable_tln:
3340 if e.lower() != d.lower():
3342 raise CommandError("value[%s] specified for --enable-tln and --disable-tln" % e)
3344 if len(add_tln_ex) > 0:
3345 for n in add_tln_ex:
3346 if not n.startswith("*."):
3348 raise CommandError("value[%s] specified for --add-tln-ex should not include with '*.'" % n)
3349 require_update = True
3350 if len(delete_tln_ex) > 0:
3351 for n in delete_tln_ex:
3352 if not n.startswith("*."):
3354 raise CommandError("value[%s] specified for --delete-tln-ex should not include with '*.'" % n)
3355 require_update = True
3356 for a in add_tln_ex:
3357 for d in delete_tln_ex:
3358 if a.lower() != d.lower():
3360 raise CommandError("value[%s] specified for --add-tln-ex and --delete-tln-ex" % a)
3362 if len(enable_nb) > 0:
3363 require_update = True
3364 if len(disable_nb) > 0:
3365 require_update = True
3367 for d in disable_nb:
3368 if e.upper() != d.upper():
3370 raise CommandError("value[%s] specified for --enable-nb and --disable-nb" % e)
3373 for s in enable_sid_str:
3375 sid = security.dom_sid(s)
3376 except TypeError as error:
3377 raise CommandError("value[%s] specified for --enable-sid is not a valid SID" % s)
3378 enable_sid.append(sid)
3380 for s in disable_sid_str:
3382 sid = security.dom_sid(s)
3383 except TypeError as error:
3384 raise CommandError("value[%s] specified for --disable-sid is not a valid SID" % s)
3385 disable_sid.append(sid)
3386 if len(enable_sid) > 0:
3387 require_update = True
3388 if len(disable_sid) > 0:
3389 require_update = True
3390 for e in enable_sid:
3391 for d in disable_sid:
3394 raise CommandError("value[%s] specified for --enable-sid and --disable-sid" % e)
3396 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
3398 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
3400 local_server = self.setup_local_server(sambaopts, localdcopts)
3402 local_lsa = self.new_local_lsa_connection()
3403 except RuntimeError as error:
3404 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
3407 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
3408 except RuntimeError as error:
3409 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
3411 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
3412 local_lsa_info.name.string,
3413 local_lsa_info.dns_domain.string,
3414 local_lsa_info.sid))
3418 local_netlogon = self.new_local_netlogon_connection()
3419 except RuntimeError as error:
3420 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3423 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3424 except RuntimeError as error:
3425 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3427 if local_netlogon_info.domain_name != local_netlogon_info.forest_name:
3428 raise CommandError("The local domain [%s] is not the forest root [%s]" % (
3429 local_netlogon_info.domain_name,
3430 local_netlogon_info.forest_name))
3433 # get all information about our own forest
3434 own_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3436 except RuntimeError as error:
3437 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
3438 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3441 if self.check_runtime_error(error, werror.WERR_INVALID_FUNCTION):
3442 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3445 if self.check_runtime_error(error, werror.WERR_NERR_ACFNOTLOADED):
3446 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3449 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3451 self.outf.write("Own forest trust information...\n")
3452 self.write_forest_trust_info(own_forest_info,
3453 tln=local_lsa_info.dns_domain.string)
3456 local_samdb = self.new_local_ldap_connection()
3457 except RuntimeError as error:
3458 raise self.LocalRuntimeError(self, error, "failed to connect to SamDB")
3460 local_partitions_dn = "CN=Partitions,%s" % str(local_samdb.get_config_basedn())
3461 attrs = ['uPNSuffixes', 'msDS-SPNSuffixes']
3463 msgs = local_samdb.search(base=local_partitions_dn,
3464 scope=ldb.SCOPE_BASE,
3465 expression="(objectClass=crossRefContainer)",
3467 stored_msg = msgs[0]
3468 except ldb.LdbError as error:
3469 raise self.LocalLdbError(self, error, "failed to search partition dn")
3471 stored_upn_vals = []
3472 if 'uPNSuffixes' in stored_msg:
3473 stored_upn_vals.extend(stored_msg['uPNSuffixes'])
3475 stored_spn_vals = []
3476 if 'msDS-SPNSuffixes' in stored_msg:
3477 stored_spn_vals.extend(stored_msg['msDS-SPNSuffixes'])
3479 self.outf.write("Stored uPNSuffixes attributes[%d]:\n" % len(stored_upn_vals))
3480 for v in stored_upn_vals:
3481 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3482 self.outf.write("Stored msDS-SPNSuffixes attributes[%d]:\n" % len(stored_spn_vals))
3483 for v in stored_spn_vals:
3484 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3486 if not require_update:
3490 update_upn_vals = []
3491 update_upn_vals.extend(stored_upn_vals)
3494 update_spn_vals = []
3495 update_spn_vals.extend(stored_spn_vals)
3498 for i, v in enumerate(update_upn_vals):
3499 if v.lower() == upn.lower():
3500 raise CommandError("Entry already present for "
3501 "value[%s] specified for "
3502 "--add-upn-suffix" % upn)
3503 update_upn_vals.append(upn)
3506 for upn in delete_upn:
3508 for i, v in enumerate(update_upn_vals):
3509 if v.lower() != upn.lower():
3514 raise CommandError("Entry not found for value[%s] specified for --delete-upn-suffix" % upn)
3516 update_upn_vals.pop(idx)
3520 for i, v in enumerate(update_spn_vals):
3521 if v.lower() == spn.lower():
3522 raise CommandError("Entry already present for "
3523 "value[%s] specified for "
3524 "--add-spn-suffix" % spn)
3525 update_spn_vals.append(spn)
3528 for spn in delete_spn:
3530 for i, v in enumerate(update_spn_vals):
3531 if v.lower() != spn.lower():
3536 raise CommandError("Entry not found for value[%s] specified for --delete-spn-suffix" % spn)
3538 update_spn_vals.pop(idx)
3541 self.outf.write("Update uPNSuffixes attributes[%d]:\n" % len(update_upn_vals))
3542 for v in update_upn_vals:
3543 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3544 self.outf.write("Update msDS-SPNSuffixes attributes[%d]:\n" % len(update_spn_vals))
3545 for v in update_spn_vals:
3546 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3548 update_msg = ldb.Message()
3549 update_msg.dn = stored_msg.dn
3552 update_msg['uPNSuffixes'] = ldb.MessageElement(update_upn_vals,
3553 ldb.FLAG_MOD_REPLACE,
3556 update_msg['msDS-SPNSuffixes'] = ldb.MessageElement(update_spn_vals,
3557 ldb.FLAG_MOD_REPLACE,
3560 local_samdb.modify(update_msg)
3561 except ldb.LdbError as error:
3562 raise self.LocalLdbError(self, error, "failed to update partition dn")
3565 stored_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3567 except RuntimeError as error:
3568 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3570 self.outf.write("Stored forest trust information...\n")
3571 self.write_forest_trust_info(stored_forest_info,
3572 tln=local_lsa_info.dns_domain.string)
3576 lsaString = lsa.String()
3577 lsaString.string = domain
3579 local_lsa.QueryTrustedDomainInfoByName(local_policy,
3581 lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
3582 except NTSTATUSError as error:
3583 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
3584 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
3586 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
3588 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
3589 local_tdo_info.netbios_name.string,
3590 local_tdo_info.domain_name.string,
3591 local_tdo_info.sid))
3593 if not local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
3594 raise CommandError("trusted domain object for domain [%s] is not marked as FOREST_TRANSITIVE." % domain)
3596 if refresh is not None:
3598 local_netlogon = self.new_local_netlogon_connection()
3599 except RuntimeError as error:
3600 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3603 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3604 except RuntimeError as error:
3605 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3607 lsa_update_check = 1
3608 if refresh == "store":
3609 netlogon_update_tdo = netlogon.DS_GFTI_UPDATE_TDO
3611 lsa_update_check = 0
3613 netlogon_update_tdo = 0
3616 # get all information about the remote trust
3617 # this triggers netr_GetForestTrustInformation to the remote domain
3618 # and lsaRSetForestTrustInformation() locally, but new top level
3619 # names are disabled by default.
3620 fresh_forest_info = \
3621 local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3622 local_tdo_info.domain_name.string,
3623 netlogon_update_tdo)
3624 except RuntimeError as error:
3625 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3628 fresh_forest_collision = \
3629 local_lsa.lsaRSetForestTrustInformation(local_policy,
3630 local_tdo_info.domain_name,
3631 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3634 except RuntimeError as error:
3635 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3637 self.outf.write("Fresh forest trust information...\n")
3638 self.write_forest_trust_info(fresh_forest_info,
3639 tln=local_tdo_info.domain_name.string,
3640 collisions=fresh_forest_collision)
3642 if refresh == "store":
3644 lsaString = lsa.String()
3645 lsaString.string = local_tdo_info.domain_name.string
3646 stored_forest_info = \
3647 local_lsa.lsaRQueryForestTrustInformation(local_policy,
3649 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3650 except RuntimeError as error:
3651 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3653 self.outf.write("Stored forest trust information...\n")
3654 self.write_forest_trust_info(stored_forest_info,
3655 tln=local_tdo_info.domain_name.string)
3660 # The none --refresh path
3664 lsaString = lsa.String()
3665 lsaString.string = local_tdo_info.domain_name.string
3666 local_forest_info = \
3667 local_lsa.lsaRQueryForestTrustInformation(local_policy,
3669 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3670 except RuntimeError as error:
3671 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3673 self.outf.write("Local forest trust information...\n")
3674 self.write_forest_trust_info(local_forest_info,
3675 tln=local_tdo_info.domain_name.string)
3677 if not require_update:
3681 entries.extend(local_forest_info.entries)
3682 update_forest_info = lsa.ForestTrustInformation()
3683 update_forest_info.count = len(entries)
3684 update_forest_info.entries = entries
3687 for i, r in enumerate(update_forest_info.entries):
3688 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3690 if update_forest_info.entries[i].flags == 0:
3692 update_forest_info.entries[i].time = 0
3693 update_forest_info.entries[i].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3694 for i, r in enumerate(update_forest_info.entries):
3695 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3697 if update_forest_info.entries[i].flags == 0:
3699 update_forest_info.entries[i].time = 0
3700 update_forest_info.entries[i].flags &= ~lsa.LSA_NB_DISABLED_MASK
3701 update_forest_info.entries[i].flags &= ~lsa.LSA_SID_DISABLED_MASK
3703 for tln in enable_tln:
3705 for i, r in enumerate(update_forest_info.entries):
3706 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3708 if r.forest_trust_data.string.lower() != tln.lower():
3713 raise CommandError("Entry not found for value[%s] specified for --enable-tln" % tln)
3714 if not update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_MASK:
3715 raise CommandError("Entry found for value[%s] specified for --enable-tln is already enabled" % tln)
3716 update_forest_info.entries[idx].time = 0
3717 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3719 for tln in disable_tln:
3721 for i, r in enumerate(update_forest_info.entries):
3722 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3724 if r.forest_trust_data.string.lower() != tln.lower():
3729 raise CommandError("Entry not found for value[%s] specified for --disable-tln" % tln)
3730 if update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_ADMIN:
3731 raise CommandError("Entry found for value[%s] specified for --disable-tln is already disabled" % tln)
3732 update_forest_info.entries[idx].time = 0
3733 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3734 update_forest_info.entries[idx].flags |= lsa.LSA_TLN_DISABLED_ADMIN
3736 for tln_ex in add_tln_ex:
3738 for i, r in enumerate(update_forest_info.entries):
3739 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3741 if r.forest_trust_data.string.lower() != tln_ex.lower():
3746 raise CommandError("Entry already present for value[%s] specified for --add-tln-ex" % tln_ex)
3748 tln_dot = ".%s" % tln_ex.lower()
3750 for i, r in enumerate(update_forest_info.entries):
3751 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3753 r_dot = ".%s" % r.forest_trust_data.string.lower()
3754 if tln_dot == r_dot:
3755 raise CommandError("TLN entry present for value[%s] specified for --add-tln-ex" % tln_ex)
3756 if not tln_dot.endswith(r_dot):
3762 raise CommandError("No TLN parent present for value[%s] specified for --add-tln-ex" % tln_ex)
3764 r = lsa.ForestTrustRecord()
3765 r.type = lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX
3768 r.forest_trust_data.string = tln_ex
3771 entries.extend(update_forest_info.entries)
3772 entries.insert(idx + 1, r)
3773 update_forest_info.count = len(entries)
3774 update_forest_info.entries = entries
3776 for tln_ex in delete_tln_ex:
3778 for i, r in enumerate(update_forest_info.entries):
3779 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3781 if r.forest_trust_data.string.lower() != tln_ex.lower():
3786 raise CommandError("Entry not found for value[%s] specified for --delete-tln-ex" % tln_ex)
3789 entries.extend(update_forest_info.entries)
3791 update_forest_info.count = len(entries)
3792 update_forest_info.entries = entries
3794 for nb in enable_nb:
3796 for i, r in enumerate(update_forest_info.entries):
3797 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3799 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3804 raise CommandError("Entry not found for value[%s] specified for --enable-nb" % nb)
3805 if not update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_MASK:
3806 raise CommandError("Entry found for value[%s] specified for --enable-nb is already enabled" % nb)
3807 update_forest_info.entries[idx].time = 0
3808 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3810 for nb in disable_nb:
3812 for i, r in enumerate(update_forest_info.entries):
3813 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3815 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3820 raise CommandError("Entry not found for value[%s] specified for --delete-nb" % nb)
3821 if update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_ADMIN:
3822 raise CommandError("Entry found for value[%s] specified for --disable-nb is already disabled" % nb)
3823 update_forest_info.entries[idx].time = 0
3824 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3825 update_forest_info.entries[idx].flags |= lsa.LSA_NB_DISABLED_ADMIN
3827 for sid in enable_sid:
3829 for i, r in enumerate(update_forest_info.entries):
3830 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3832 if r.forest_trust_data.domain_sid != sid:
3837 raise CommandError("Entry not found for value[%s] specified for --enable-sid" % sid)
3838 if not update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_MASK:
3839 raise CommandError("Entry found for value[%s] specified for --enable-sid is already enabled" % nb)
3840 update_forest_info.entries[idx].time = 0
3841 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3843 for sid in disable_sid:
3845 for i, r in enumerate(update_forest_info.entries):
3846 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3848 if r.forest_trust_data.domain_sid != sid:
3853 raise CommandError("Entry not found for value[%s] specified for --delete-sid" % sid)
3854 if update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_ADMIN:
3855 raise CommandError("Entry found for value[%s] specified for --disable-sid is already disabled" % nb)
3856 update_forest_info.entries[idx].time = 0
3857 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3858 update_forest_info.entries[idx].flags |= lsa.LSA_SID_DISABLED_ADMIN
3861 update_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3862 local_tdo_info.domain_name,
3863 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3864 update_forest_info, 0)
3865 except RuntimeError as error:
3866 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3868 self.outf.write("Updated forest trust information...\n")
3869 self.write_forest_trust_info(update_forest_info,
3870 tln=local_tdo_info.domain_name.string,
3871 collisions=update_forest_collision)
3874 lsaString = lsa.String()
3875 lsaString.string = local_tdo_info.domain_name.string
3876 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3878 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3879 except RuntimeError as error:
3880 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3882 self.outf.write("Stored forest trust information...\n")
3883 self.write_forest_trust_info(stored_forest_info,
3884 tln=local_tdo_info.domain_name.string)
3888 class cmd_domain_tombstones_expunge(Command):
3889 """Expunge tombstones from the database.
3891 This command expunges tombstones from the database."""
3892 synopsis = "%prog NC [NC [...]] [options]"
3895 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3896 metavar="URL", dest="H"),
3897 Option("--current-time",
3898 help="The current time to evaluate the tombstone lifetime from, expressed as YYYY-MM-DD",
3900 Option("--tombstone-lifetime", help="Number of days a tombstone should be preserved for", type=int),
3903 takes_args = ["nc*"]
3905 takes_optiongroups = {
3906 "sambaopts": options.SambaOptions,
3907 "credopts": options.CredentialsOptions,
3908 "versionopts": options.VersionOptions,
3911 def run(self, *ncs, **kwargs):
3912 sambaopts = kwargs.get("sambaopts")
3913 credopts = kwargs.get("credopts")
3914 versionpts = kwargs.get("versionopts")
3916 current_time_string = kwargs.get("current_time")
3917 tombstone_lifetime = kwargs.get("tombstone_lifetime")
3918 lp = sambaopts.get_loadparm()
3919 creds = credopts.get_credentials(lp)
3920 samdb = SamDB(url=H, session_info=system_session(),
3921 credentials=creds, lp=lp)
3923 if current_time_string is not None:
3924 current_time_obj = time.strptime(current_time_string, "%Y-%m-%d")
3925 current_time = long(time.mktime(current_time_obj))
3928 current_time = long(time.time())
3931 res = samdb.search(expression="", base="", scope=ldb.SCOPE_BASE,
3932 attrs=["namingContexts"])
3935 for nc in res[0]["namingContexts"]:
3940 started_transaction = False
3942 samdb.transaction_start()
3943 started_transaction = True
3945 removed_links) = samdb.garbage_collect_tombstones(ncs,
3946 current_time=current_time,
3947 tombstone_lifetime=tombstone_lifetime)
3949 except Exception as err:
3950 if started_transaction:
3951 samdb.transaction_cancel()
3952 raise CommandError("Failed to expunge / garbage collect tombstones", err)
3954 samdb.transaction_commit()
3956 self.outf.write("Removed %d objects and %d links successfully\n"
3957 % (removed_objects, removed_links))
3960 class cmd_domain_trust(SuperCommand):
3961 """Domain and forest trust management."""
3964 subcommands["list"] = cmd_domain_trust_list()
3965 subcommands["show"] = cmd_domain_trust_show()
3966 subcommands["create"] = cmd_domain_trust_create()
3967 subcommands["delete"] = cmd_domain_trust_delete()
3968 subcommands["validate"] = cmd_domain_trust_validate()
3969 subcommands["namespaces"] = cmd_domain_trust_namespaces()
3972 class cmd_domain_tombstones(SuperCommand):
3973 """Domain tombstone and recycled object management."""
3976 subcommands["expunge"] = cmd_domain_tombstones_expunge()
3979 class ldif_schema_update:
3980 """Helper class for applying LDIF schema updates"""
3983 self.is_defunct = False
3984 self.unknown_oid = None
3988 def can_ignore_failure(self, error):
3989 """Checks if we can safely ignore failure to apply an LDIF update"""
3990 (num, errstr) = error.args
3992 # Microsoft has marked objects as defunct that Samba doesn't know about
3993 if num == ldb.ERR_NO_SUCH_OBJECT and self.is_defunct:
3994 print("Defunct object %s doesn't exist, skipping" % self.dn)
3996 elif self.unknown_oid is not None:
3997 print("Skipping unknown OID %s for object %s" % (self.unknown_oid, self.dn))
4002 def apply(self, samdb):
4003 """Applies a single LDIF update to the schema"""
4007 samdb.modify_ldif(self.ldif, controls=['relax:0'])
4008 except ldb.LdbError as e:
4009 if e.args[0] == ldb.ERR_INVALID_ATTRIBUTE_SYNTAX:
4011 # REFRESH after a failed change
4013 # Otherwise the OID-to-attribute mapping in
4014 # _apply_updates_in_file() won't work, because it
4015 # can't lookup the new OID in the schema
4016 samdb.set_schema_update_now()
4018 samdb.modify_ldif(self.ldif, controls=['relax:0'])
4021 except ldb.LdbError as e:
4022 if self.can_ignore_failure(e):
4025 print("Exception: %s" % e)
4026 print("Encountered while trying to apply the following LDIF")
4027 print("----------------------------------------------------")
4028 print("%s" % self.ldif)
4035 class cmd_domain_schema_upgrade(Command):
4036 """Domain schema upgrading"""
4038 synopsis = "%prog [options]"
4040 takes_optiongroups = {
4041 "sambaopts": options.SambaOptions,
4042 "versionopts": options.VersionOptions,
4043 "credopts": options.CredentialsOptions,
4047 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
4048 metavar="URL", dest="H"),
4049 Option("-q", "--quiet", help="Be quiet", action="store_true"), # unused
4050 Option("-v", "--verbose", help="Be verbose", action="store_true"),
4051 Option("--schema", type="choice", metavar="SCHEMA",
4052 choices=["2012", "2012_R2"],
4053 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
4055 Option("--ldf-file", type=str, default=None,
4056 help="Just apply the schema updates in the adprep/.LDF file(s) specified"),
4057 Option("--base-dir", type=str, default=None,
4058 help="Location of ldf files Default is ${SETUPDIR}/adprep.")
4061 def _apply_updates_in_file(self, samdb, ldif_file):
4063 Applies a series of updates specified in an .LDIF file. The .LDIF file
4064 is based on the adprep Schema updates provided by Microsoft.
4067 ldif_op = ldif_schema_update()
4069 # parse the file line by line and work out each update operation to apply
4070 for line in ldif_file:
4072 line = line.rstrip()
4074 # the operations in the .LDIF file are separated by blank lines. If
4075 # we hit a blank line, try to apply the update we've parsed so far
4078 # keep going if we haven't parsed anything yet
4079 if ldif_op.ldif == '':
4082 # Apply the individual change
4083 count += ldif_op.apply(samdb)
4085 # start storing the next operation from scratch again
4086 ldif_op = ldif_schema_update()
4089 # replace the placeholder domain name in the .ldif file with the real domain
4090 if line.upper().endswith('DC=X'):
4091 line = line[:-len('DC=X')] + str(samdb.get_default_basedn())
4092 elif line.upper().endswith('CN=X'):
4093 line = line[:-len('CN=X')] + str(samdb.get_default_basedn())
4095 values = line.split(':')
4097 if values[0].lower() == 'dn':
4098 ldif_op.dn = values[1].strip()
4100 # replace the Windows-specific operation with the Samba one
4101 if values[0].lower() == 'changetype':
4102 line = line.lower().replace(': ntdsschemaadd',
4104 line = line.lower().replace(': ntdsschemamodify',
4107 if values[0].lower() in ['rdnattid', 'subclassof',
4108 'systemposssuperiors',
4110 'systemauxiliaryclass']:
4113 # The Microsoft updates contain some OIDs we don't recognize.
4114 # Query the DB to see if we can work out the OID this update is
4115 # referring to. If we find a match, then replace the OID with
4116 # the ldapDisplayname
4118 res = samdb.search(base=samdb.get_schema_basedn(),
4119 expression="(|(attributeId=%s)(governsId=%s))" %
4121 attrs=['ldapDisplayName'])
4124 ldif_op.unknown_oid = value
4126 display_name = res[0]['ldapDisplayName'][0]
4127 line = line.replace(value, ' ' + display_name)
4129 # Microsoft has marked objects as defunct that Samba doesn't know about
4130 if values[0].lower() == 'isdefunct' and values[1].strip().lower() == 'true':
4131 ldif_op.is_defunct = True
4133 # Samba has added the showInAdvancedViewOnly attribute to all objects,
4134 # so rather than doing an add, we need to do a replace
4135 if values[0].lower() == 'add' and values[1].strip().lower() == 'showinadvancedviewonly':
4136 line = 'replace: showInAdvancedViewOnly'
4138 # Add the line to the current LDIF operation (including the newline
4139 # we stripped off at the start of the loop)
4140 ldif_op.ldif += line + '\n'
4144 def _apply_update(self, samdb, update_file, base_dir):
4145 """Wrapper function for parsing an LDIF file and applying the updates"""
4147 print("Applying %s updates..." % update_file)
4151 ldif_file = open(os.path.join(base_dir, update_file))
4153 count = self._apply_updates_in_file(samdb, ldif_file)
4159 print("%u changes applied" % count)
4163 def run(self, **kwargs):
4164 from samba.ms_schema_markdown import read_ms_markdown
4165 from samba.schema import Schema
4167 updates_allowed_overriden = False
4168 sambaopts = kwargs.get("sambaopts")
4169 credopts = kwargs.get("credopts")
4170 versionpts = kwargs.get("versionopts")
4171 lp = sambaopts.get_loadparm()
4172 creds = credopts.get_credentials(lp)
4174 target_schema = kwargs.get("schema")
4175 ldf_files = kwargs.get("ldf_file")
4176 base_dir = kwargs.get("base_dir")
4180 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4182 # we're not going to get far if the config doesn't allow schema updates
4183 if lp.get("dsdb:schema update allowed") is None:
4184 lp.set("dsdb:schema update allowed", "yes")
4185 print("Temporarily overriding 'dsdb:schema update allowed' setting")
4186 updates_allowed_overriden = True
4188 own_dn = ldb.Dn(samdb, samdb.get_dsServiceName())
4189 master = get_fsmo_roleowner(samdb, str(samdb.get_schema_basedn()),
4191 if own_dn != master:
4192 raise CommandError("This server is not the schema master.")
4194 # if specific LDIF files were specified, just apply them
4196 schema_updates = ldf_files.split(",")
4200 # work out the version of the target schema we're upgrading to
4201 end = Schema.get_version(target_schema)
4203 # work out the version of the schema we're currently using
4204 res = samdb.search(base=samdb.get_schema_basedn(),
4205 scope=ldb.SCOPE_BASE, attrs=['objectVersion'])
4208 raise CommandError('Could not determine current schema version')
4209 start = int(res[0]['objectVersion'][0]) + 1
4211 diff_dir = setup_path("adprep/WindowsServerDocs")
4212 if base_dir is None:
4213 # Read from the Schema-Updates.md file
4214 temp_folder = tempfile.mkdtemp()
4216 update_file = setup_path("adprep/WindowsServerDocs/Schema-Updates.md")
4219 read_ms_markdown(update_file, temp_folder)
4220 except Exception as e:
4221 print("Exception in markdown parsing: %s" % e)
4222 shutil.rmtree(temp_folder)
4223 raise CommandError('Failed to upgrade schema')
4225 base_dir = temp_folder
4227 for version in range(start, end + 1):
4228 update = 'Sch%d.ldf' % version
4229 schema_updates.append(update)
4231 # Apply patches if we parsed the Schema-Updates.md file
4232 diff = os.path.abspath(os.path.join(diff_dir, update + '.diff'))
4233 if temp_folder and os.path.exists(diff):
4235 p = subprocess.Popen(['patch', update, '-i', diff],
4236 stdout=subprocess.PIPE,
4237 stderr=subprocess.PIPE, cwd=temp_folder)
4238 except (OSError, IOError):
4239 shutil.rmtree(temp_folder)
4240 raise CommandError("Failed to upgrade schema. Check if 'patch' is installed.")
4242 stdout, stderr = p.communicate()
4245 print("Exception in patch: %s\n%s" % (stdout, stderr))
4246 shutil.rmtree(temp_folder)
4247 raise CommandError('Failed to upgrade schema')
4249 print("Patched %s using %s" % (update, diff))
4251 if base_dir is None:
4252 base_dir = setup_path("adprep")
4254 samdb.transaction_start()
4256 error_encountered = False
4259 # Apply the schema updates needed to move to the new schema version
4260 for ldif_file in schema_updates:
4261 count += self._apply_update(samdb, ldif_file, base_dir)
4264 samdb.transaction_commit()
4265 print("Schema successfully updated")
4267 print("No changes applied to schema")
4268 samdb.transaction_cancel()
4269 except Exception as e:
4270 print("Exception: %s" % e)
4271 print("Error encountered, aborting schema upgrade")
4272 samdb.transaction_cancel()
4273 error_encountered = True
4275 if updates_allowed_overriden:
4276 lp.set("dsdb:schema update allowed", "no")
4279 shutil.rmtree(temp_folder)
4281 if error_encountered:
4282 raise CommandError('Failed to upgrade schema')
4285 class cmd_domain_functional_prep(Command):
4286 """Domain functional level preparation"""
4288 synopsis = "%prog [options]"
4290 takes_optiongroups = {
4291 "sambaopts": options.SambaOptions,
4292 "versionopts": options.VersionOptions,
4293 "credopts": options.CredentialsOptions,
4297 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
4298 metavar="URL", dest="H"),
4299 Option("-q", "--quiet", help="Be quiet", action="store_true"),
4300 Option("-v", "--verbose", help="Be verbose", action="store_true"),
4301 Option("--function-level", type="choice", metavar="FUNCTION_LEVEL",
4302 choices=["2008_R2", "2012", "2012_R2"],
4303 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
4305 Option("--forest-prep", action="store_true",
4306 help="Run the forest prep (by default, both the domain and forest prep are run)."),
4307 Option("--domain-prep", action="store_true",
4308 help="Run the domain prep (by default, both the domain and forest prep are run).")
4311 def run(self, **kwargs):
4312 updates_allowed_overriden = False
4313 sambaopts = kwargs.get("sambaopts")
4314 credopts = kwargs.get("credopts")
4315 versionpts = kwargs.get("versionopts")
4316 lp = sambaopts.get_loadparm()
4317 creds = credopts.get_credentials(lp)
4319 target_level = string_version_to_constant[kwargs.get("function_level")]
4320 forest_prep = kwargs.get("forest_prep")
4321 domain_prep = kwargs.get("domain_prep")
4323 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4325 # we're not going to get far if the config doesn't allow schema updates
4326 if lp.get("dsdb:schema update allowed") is None:
4327 lp.set("dsdb:schema update allowed", "yes")
4328 print("Temporarily overriding 'dsdb:schema update allowed' setting")
4329 updates_allowed_overriden = True
4331 if forest_prep is None and domain_prep is None:
4335 own_dn = ldb.Dn(samdb, samdb.get_dsServiceName())
4337 master = get_fsmo_roleowner(samdb, str(samdb.get_schema_basedn()),
4339 if own_dn != master:
4340 raise CommandError("This server is not the schema master.")
4343 domain_dn = samdb.domain_dn()
4344 infrastructure_dn = "CN=Infrastructure," + domain_dn
4345 master = get_fsmo_roleowner(samdb, infrastructure_dn,
4347 if own_dn != master:
4348 raise CommandError("This server is not the infrastructure master.")
4351 samdb.transaction_start()
4352 error_encountered = False
4354 from samba.forest_update import ForestUpdate
4355 forest = ForestUpdate(samdb, fix=True)
4357 forest.check_updates_iterator([53, 79, 80, 81, 82, 83])
4358 forest.check_updates_functional_level(target_level,
4359 DS_DOMAIN_FUNCTION_2008_R2,
4360 update_revision=True)
4362 samdb.transaction_commit()
4363 except Exception as e:
4364 print("Exception: %s" % e)
4365 samdb.transaction_cancel()
4366 error_encountered = True
4369 samdb.transaction_start()
4370 error_encountered = False
4372 from samba.domain_update import DomainUpdate
4374 domain = DomainUpdate(samdb, fix=True)
4375 domain.check_updates_functional_level(target_level,
4376 DS_DOMAIN_FUNCTION_2008,
4377 update_revision=True)
4379 samdb.transaction_commit()
4380 except Exception as e:
4381 print("Exception: %s" % e)
4382 samdb.transaction_cancel()
4383 error_encountered = True
4385 if updates_allowed_overriden:
4386 lp.set("dsdb:schema update allowed", "no")
4388 if error_encountered:
4389 raise CommandError('Failed to perform functional prep')
4392 class cmd_domain(SuperCommand):
4393 """Domain management."""
4396 subcommands["demote"] = cmd_domain_demote()
4397 if cmd_domain_export_keytab is not None:
4398 subcommands["exportkeytab"] = cmd_domain_export_keytab()
4399 subcommands["info"] = cmd_domain_info()
4400 subcommands["provision"] = cmd_domain_provision()
4401 subcommands["join"] = cmd_domain_join()
4402 subcommands["dcpromo"] = cmd_domain_dcpromo()
4403 subcommands["level"] = cmd_domain_level()
4404 subcommands["passwordsettings"] = cmd_domain_passwordsettings()
4405 subcommands["classicupgrade"] = cmd_domain_classicupgrade()
4406 subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()
4407 subcommands["trust"] = cmd_domain_trust()
4408 subcommands["tombstones"] = cmd_domain_tombstones()
4409 subcommands["schemaupgrade"] = cmd_domain_schema_upgrade()
4410 subcommands["functionalprep"] = cmd_domain_functional_prep()
4411 subcommands["backup"] = cmd_domain_backup()