3 # Copyright Matthias Dieter Wallnoefer 2009
4 # Copyright Andrew Kroeger 2009
5 # Copyright Jelmer Vernooij 2007-2012
6 # Copyright Giampaolo Lauria 2011
7 # Copyright Matthieu Patou <mat@matws.net> 2011
8 # Copyright Andrew Bartlett 2008-2015
9 # Copyright Stefan Metzmacher 2012
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 from __future__ import print_function
26 from __future__ import division
27 import samba.getopt as options
38 from samba import ntstatus
39 from samba import NTSTATUSError
40 from samba import werror
41 from getpass import getpass
42 from samba.net import Net, LIBNET_JOIN_AUTOMATIC
44 from samba.join import join_RODC, join_DC, join_subdomain
45 from samba.auth import system_session
46 from samba.samdb import SamDB, get_default_backend_store
47 from samba.ndr import ndr_pack, ndr_print
48 from samba.dcerpc import drsuapi
49 from samba.dcerpc import drsblobs
50 from samba.dcerpc import lsa
51 from samba.dcerpc import netlogon
52 from samba.dcerpc import security
53 from samba.dcerpc import nbt
54 from samba.dcerpc import misc
55 from samba.dcerpc.samr import DOMAIN_PASSWORD_COMPLEX, DOMAIN_PASSWORD_STORE_CLEARTEXT
56 from samba.netcmd import (
62 from samba.netcmd.fsmo import get_fsmo_roleowner
63 from samba.netcmd.common import netcmd_get_domain_infos_via_cldap
64 from samba.samba3 import Samba3
65 from samba.samba3 import param as s3param
66 from samba.upgrade import upgrade_from_samba3
67 from samba.drs_utils import drsuapi_connect
68 from samba import remove_dc, arcfour_encrypt, string_to_byte_array
70 from samba.dsdb import (
71 DS_DOMAIN_FUNCTION_2000,
72 DS_DOMAIN_FUNCTION_2003,
73 DS_DOMAIN_FUNCTION_2003_MIXED,
74 DS_DOMAIN_FUNCTION_2008,
75 DS_DOMAIN_FUNCTION_2008_R2,
76 DS_DOMAIN_FUNCTION_2012,
77 DS_DOMAIN_FUNCTION_2012_R2,
78 DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL,
79 DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL,
80 UF_WORKSTATION_TRUST_ACCOUNT,
81 UF_SERVER_TRUST_ACCOUNT,
82 UF_TRUSTED_FOR_DELEGATION,
83 UF_PARTIAL_SECRETS_ACCOUNT
86 from samba.provision import (
89 DEFAULT_MIN_PWD_LENGTH,
93 from samba.provision.common import (
99 from samba.netcmd.pso import cmd_domain_passwordsettings_pso
100 from samba.netcmd.domain_backup import cmd_domain_backup
102 from samba.compat import binary_type
103 from samba.compat import get_string
105 string_version_to_constant = {
106 "2008_R2": DS_DOMAIN_FUNCTION_2008_R2,
107 "2012": DS_DOMAIN_FUNCTION_2012,
108 "2012_R2": DS_DOMAIN_FUNCTION_2012_R2,
111 common_provision_join_options = [
112 Option("--machinepass", type="string", metavar="PASSWORD",
113 help="choose machine password (otherwise random)"),
114 Option("--plaintext-secrets", action="store_true",
115 help="Store secret/sensitive values as plain text on disk" +
116 "(default is to encrypt secret/ensitive values)"),
117 Option("--backend-store", type="choice", metavar="BACKENDSTORE",
118 choices=["tdb", "mdb"],
119 help="Specify the database backend to be used "
120 "(default is %s)" % get_default_backend_store()),
121 Option("--backend-store-size", type="bytes", metavar="SIZE",
122 help="Specify the size of the backend database, currently only " +
123 "supported by lmdb backends (default is 8 Gb)."),
124 Option("--targetdir", metavar="DIR",
125 help="Set target directory (where to store provision)", type=str),
126 Option("-q", "--quiet", help="Be quiet", action="store_true"),
129 common_join_options = [
130 Option("--server", help="DC to join", type=str),
131 Option("--site", help="site to join", type=str),
132 Option("--domain-critical-only",
133 help="only replicate critical domain objects",
134 action="store_true"),
135 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
136 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
137 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
138 "BIND9_DLZ uses samba4 AD to store zone information, "
139 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
140 default="SAMBA_INTERNAL"),
141 Option("-v", "--verbose", help="Be verbose", action="store_true")
144 common_ntvfs_options = [
145 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
150 def get_testparm_var(testparm, smbconf, varname):
151 errfile = open(os.devnull, 'w')
152 p = subprocess.Popen([testparm, '-s', '-l',
153 '--parameter-name=%s' % varname, smbconf],
154 stdout=subprocess.PIPE, stderr=errfile)
155 (out, err) = p.communicate()
157 lines = out.split(b'\n')
159 return get_string(lines[0]).strip()
164 import samba.dckeytab
166 cmd_domain_export_keytab = None
168 class cmd_domain_export_keytab(Command):
169 """Dump Kerberos keys of the domain into a keytab."""
171 synopsis = "%prog <keytab> [options]"
173 takes_optiongroups = {
174 "sambaopts": options.SambaOptions,
175 "credopts": options.CredentialsOptions,
176 "versionopts": options.VersionOptions,
180 Option("--principal", help="extract only this principal", type=str),
183 takes_args = ["keytab"]
185 def run(self, keytab, credopts=None, sambaopts=None, versionopts=None, principal=None):
186 lp = sambaopts.get_loadparm()
188 net.export_keytab(keytab=keytab, principal=principal)
191 class cmd_domain_info(Command):
192 """Print basic info about a domain and the DC passed as parameter."""
194 synopsis = "%prog <ip_address> [options]"
199 takes_optiongroups = {
200 "sambaopts": options.SambaOptions,
201 "credopts": options.CredentialsOptions,
202 "versionopts": options.VersionOptions,
205 takes_args = ["address"]
207 def run(self, address, credopts=None, sambaopts=None, versionopts=None):
208 lp = sambaopts.get_loadparm()
210 res = netcmd_get_domain_infos_via_cldap(lp, None, address)
212 raise CommandError("Invalid IP address '" + address + "'!")
213 self.outf.write("Forest : %s\n" % res.forest)
214 self.outf.write("Domain : %s\n" % res.dns_domain)
215 self.outf.write("Netbios domain : %s\n" % res.domain_name)
216 self.outf.write("DC name : %s\n" % res.pdc_dns_name)
217 self.outf.write("DC netbios name : %s\n" % res.pdc_name)
218 self.outf.write("Server site : %s\n" % res.server_site)
219 self.outf.write("Client site : %s\n" % res.client_site)
222 class cmd_domain_provision(Command):
223 """Provision a domain."""
225 synopsis = "%prog [options]"
227 takes_optiongroups = {
228 "sambaopts": options.SambaOptions,
229 "versionopts": options.VersionOptions,
233 Option("--interactive", help="Ask for names", action="store_true"),
234 Option("--domain", type="string", metavar="DOMAIN",
235 help="NetBIOS domain name to use"),
236 Option("--domain-guid", type="string", metavar="GUID",
237 help="set domainguid (otherwise random)"),
238 Option("--domain-sid", type="string", metavar="SID",
239 help="set domainsid (otherwise random)"),
240 Option("--ntds-guid", type="string", metavar="GUID",
241 help="set NTDS object GUID (otherwise random)"),
242 Option("--invocationid", type="string", metavar="GUID",
243 help="set invocationid (otherwise random)"),
244 Option("--host-name", type="string", metavar="HOSTNAME",
245 help="set hostname"),
246 Option("--host-ip", type="string", metavar="IPADDRESS",
247 help="set IPv4 ipaddress"),
248 Option("--host-ip6", type="string", metavar="IP6ADDRESS",
249 help="set IPv6 ipaddress"),
250 Option("--site", type="string", metavar="SITENAME",
251 help="set site name"),
252 Option("--adminpass", type="string", metavar="PASSWORD",
253 help="choose admin password (otherwise random)"),
254 Option("--krbtgtpass", type="string", metavar="PASSWORD",
255 help="choose krbtgt password (otherwise random)"),
256 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
257 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
258 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
259 "BIND9_FLATFILE uses bind9 text database to store zone information, "
260 "BIND9_DLZ uses samba4 AD to store zone information, "
261 "NONE skips the DNS setup entirely (not recommended)",
262 default="SAMBA_INTERNAL"),
263 Option("--dnspass", type="string", metavar="PASSWORD",
264 help="choose dns password (otherwise random)"),
265 Option("--root", type="string", metavar="USERNAME",
266 help="choose 'root' unix username"),
267 Option("--nobody", type="string", metavar="USERNAME",
268 help="choose 'nobody' user"),
269 Option("--users", type="string", metavar="GROUPNAME",
270 help="choose 'users' group"),
271 Option("--blank", action="store_true",
272 help="do not add users or groups, just the structure"),
273 Option("--server-role", type="choice", metavar="ROLE",
274 choices=["domain controller", "dc", "member server", "member", "standalone"],
275 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
276 default="domain controller"),
277 Option("--function-level", type="choice", metavar="FOR-FUN-LEVEL",
278 choices=["2000", "2003", "2008", "2008_R2"],
279 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2008_R2 Native.",
281 Option("--base-schema", type="choice", metavar="BASE-SCHEMA",
282 choices=["2008_R2", "2008_R2_old", "2012", "2012_R2"],
283 help="The base schema files to use. Default is (Windows) 2012_R2.",
285 Option("--next-rid", type="int", metavar="NEXTRID", default=1000,
286 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
287 Option("--partitions-only",
288 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"),
289 Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
293 Option("--use-xattrs", type="choice", choices=["yes", "no", "auto"],
294 metavar="[yes|no|auto]",
295 help="Define if we should use the native fs capabilities or a tdb file for "
296 "storing attributes likes ntacl when --use-ntvfs is set. "
297 "auto tries to make an inteligent guess based on the user rights and system capabilities",
301 takes_options.extend(common_provision_join_options)
303 if samba.is_ntvfs_fileserver_built():
304 takes_options.extend(common_ntvfs_options)
305 takes_options.extend(ntvfs_options)
309 def run(self, sambaopts=None, versionopts=None,
335 partitions_only=None,
341 plaintext_secrets=False,
343 backend_store_size=None):
345 self.logger = self.get_logger(name="provision", quiet=quiet)
347 lp = sambaopts.get_loadparm()
348 smbconf = lp.configfile
350 if dns_forwarder is not None:
351 suggested_forwarder = dns_forwarder
353 suggested_forwarder = self._get_nameserver_ip()
354 if suggested_forwarder is None:
355 suggested_forwarder = "none"
357 if len(self.raw_argv) == 1:
361 from getpass import getpass
364 def ask(prompt, default=None):
365 if default is not None:
366 print("%s [%s]: " % (prompt, default), end=' ')
368 print("%s: " % (prompt,), end=' ')
370 return sys.stdin.readline().rstrip("\n") or default
373 default = socket.getfqdn().split(".", 1)[1].upper()
376 realm = ask("Realm", default)
377 if realm in (None, ""):
378 raise CommandError("No realm set!")
381 default = realm.split(".")[0]
384 domain = ask("Domain", default)
386 raise CommandError("No domain set!")
388 server_role = ask("Server Role (dc, member, standalone)", "dc")
390 dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
391 if dns_backend in (None, ''):
392 raise CommandError("No DNS backend set!")
394 if dns_backend == "SAMBA_INTERNAL":
395 dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
396 if dns_forwarder.lower() in (None, 'none'):
397 suggested_forwarder = None
401 adminpassplain = getpass("Administrator password: ")
402 issue = self._adminpass_issue(adminpassplain)
404 self.errf.write("%s.\n" % issue)
406 adminpassverify = getpass("Retype password: ")
407 if not adminpassplain == adminpassverify:
408 self.errf.write("Sorry, passwords do not match.\n")
410 adminpass = adminpassplain
414 realm = sambaopts._lp.get('realm')
416 raise CommandError("No realm set!")
418 raise CommandError("No domain set!")
421 issue = self._adminpass_issue(adminpass)
423 raise CommandError(issue)
425 self.logger.info("Administrator password will be set randomly!")
427 if function_level == "2000":
428 dom_for_fun_level = DS_DOMAIN_FUNCTION_2000
429 elif function_level == "2003":
430 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
431 elif function_level == "2008":
432 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
433 elif function_level == "2008_R2":
434 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
436 if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
437 dns_forwarder = suggested_forwarder
439 samdb_fill = FILL_FULL
441 samdb_fill = FILL_NT4SYNC
442 elif partitions_only:
443 samdb_fill = FILL_DRS
445 if targetdir is not None:
446 if not os.path.isdir(targetdir):
451 if use_xattrs == "yes":
453 elif use_xattrs == "auto" and use_ntvfs == False:
455 elif use_ntvfs == False:
456 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
457 "Please re-run with --use-xattrs omitted.")
458 elif use_xattrs == "auto" and not lp.get("posix:eadb"):
460 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
462 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
465 samba.ntacls.setntacl(lp, file.name,
466 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
469 self.logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. ")
474 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.")
476 if domain_sid is not None:
477 domain_sid = security.dom_sid(domain_sid)
479 session = system_session()
480 if backend_store is None:
481 backend_store = get_default_backend_store()
483 result = provision(self.logger,
484 session, smbconf=smbconf, targetdir=targetdir,
485 samdb_fill=samdb_fill, realm=realm, domain=domain,
486 domainguid=domain_guid, domainsid=domain_sid,
488 hostip=host_ip, hostip6=host_ip6,
489 sitename=site, ntdsguid=ntds_guid,
490 invocationid=invocationid, adminpass=adminpass,
491 krbtgtpass=krbtgtpass, machinepass=machinepass,
492 dns_backend=dns_backend, dns_forwarder=dns_forwarder,
493 dnspass=dnspass, root=root, nobody=nobody,
495 serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
496 useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
497 use_rfc2307=use_rfc2307, skip_sysvolacl=False,
498 base_schema=base_schema,
499 plaintext_secrets=plaintext_secrets,
500 backend_store=backend_store,
501 backend_store_size=backend_store_size)
503 except ProvisioningError as e:
504 raise CommandError("Provision failed", e)
506 result.report_logger(self.logger)
508 def _get_nameserver_ip(self):
509 """Grab the nameserver IP address from /etc/resolv.conf."""
511 RESOLV_CONF = "/etc/resolv.conf"
513 if not path.isfile(RESOLV_CONF):
514 self.logger.warning("Failed to locate %s" % RESOLV_CONF)
519 handle = open(RESOLV_CONF, 'r')
521 if not line.startswith('nameserver'):
523 # we want the last non-space continuous string of the line
524 return line.strip().split()[-1]
526 if handle is not None:
529 self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
531 def _adminpass_issue(self, adminpass):
532 """Returns error string for a bad administrator password,
533 or None if acceptable"""
534 if isinstance(adminpass, binary_type):
535 adminpass = adminpass.decode('utf8')
536 if len(adminpass) < DEFAULT_MIN_PWD_LENGTH:
537 return "Administrator password does not meet the default minimum" \
538 " password length requirement (%d characters)" \
539 % DEFAULT_MIN_PWD_LENGTH
540 elif not samba.check_password_quality(adminpass):
541 return "Administrator password does not meet the default" \
547 class cmd_domain_dcpromo(Command):
548 """Promote an existing domain member or NT4 PDC to an AD DC."""
550 synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
552 takes_optiongroups = {
553 "sambaopts": options.SambaOptions,
554 "versionopts": options.VersionOptions,
555 "credopts": options.CredentialsOptions,
559 takes_options.extend(common_join_options)
561 takes_options.extend(common_provision_join_options)
563 if samba.is_ntvfs_fileserver_built():
564 takes_options.extend(common_ntvfs_options)
566 takes_args = ["domain", "role?"]
568 def run(self, domain, role=None, sambaopts=None, credopts=None,
569 versionopts=None, server=None, site=None, targetdir=None,
570 domain_critical_only=False, parent_domain=None, machinepass=None,
571 use_ntvfs=False, dns_backend=None,
572 quiet=False, verbose=False, plaintext_secrets=False,
573 backend_store=None, backend_store_size=None):
574 lp = sambaopts.get_loadparm()
575 creds = credopts.get_credentials(lp)
576 net = Net(creds, lp, server=credopts.ipaddress)
578 logger = self.get_logger(verbose=verbose, quiet=quiet)
580 netbios_name = lp.get("netbios name")
586 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
587 site=site, netbios_name=netbios_name, targetdir=targetdir,
588 domain_critical_only=domain_critical_only,
589 machinepass=machinepass, use_ntvfs=use_ntvfs,
590 dns_backend=dns_backend,
591 promote_existing=True, plaintext_secrets=plaintext_secrets,
592 backend_store=backend_store,
593 backend_store_size=backend_store_size)
595 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
596 site=site, netbios_name=netbios_name, targetdir=targetdir,
597 domain_critical_only=domain_critical_only,
598 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
599 promote_existing=True, plaintext_secrets=plaintext_secrets,
600 backend_store=backend_store,
601 backend_store_size=backend_store_size)
603 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
606 class cmd_domain_join(Command):
607 """Join domain as either member or backup domain controller."""
609 synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER] [options]"
611 takes_optiongroups = {
612 "sambaopts": options.SambaOptions,
613 "versionopts": options.VersionOptions,
614 "credopts": options.CredentialsOptions,
618 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
619 Option("--adminpass", type="string", metavar="PASSWORD",
620 help="choose adminstrator password when joining as a subdomain (otherwise random)"),
624 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
627 takes_options.extend(common_join_options)
628 takes_options.extend(common_provision_join_options)
630 if samba.is_ntvfs_fileserver_built():
631 takes_options.extend(ntvfs_options)
633 takes_args = ["domain", "role?"]
635 def run(self, domain, role=None, sambaopts=None, credopts=None,
636 versionopts=None, server=None, site=None, targetdir=None,
637 domain_critical_only=False, parent_domain=None, machinepass=None,
638 use_ntvfs=False, dns_backend=None, adminpass=None,
639 quiet=False, verbose=False,
640 plaintext_secrets=False,
641 backend_store=None,backend_store_size=None):
642 lp = sambaopts.get_loadparm()
643 creds = credopts.get_credentials(lp)
644 net = Net(creds, lp, server=credopts.ipaddress)
646 logger = self.get_logger(verbose=verbose, quiet=quiet)
648 netbios_name = lp.get("netbios name")
653 if role is None or role == "MEMBER":
654 (join_password, sid, domain_name) = net.join_member(
655 domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
656 machinepass=machinepass)
658 self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
660 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
661 site=site, netbios_name=netbios_name, targetdir=targetdir,
662 domain_critical_only=domain_critical_only,
663 machinepass=machinepass, use_ntvfs=use_ntvfs,
664 dns_backend=dns_backend,
665 plaintext_secrets=plaintext_secrets,
666 backend_store=backend_store,
667 backend_store_size=backend_store_size)
669 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
670 site=site, netbios_name=netbios_name, targetdir=targetdir,
671 domain_critical_only=domain_critical_only,
672 machinepass=machinepass, use_ntvfs=use_ntvfs,
673 dns_backend=dns_backend,
674 plaintext_secrets=plaintext_secrets,
675 backend_store=backend_store,
676 backend_store_size=backend_store_size)
677 # elif role == "SUBDOMAIN":
678 # subdomain command removed by Gary Lockyer <gary@catalyst.net.nz>
679 # on the 28th June 2019.
681 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC)" % role)
684 class cmd_domain_demote(Command):
685 """Demote ourselves from the role of Domain Controller."""
687 synopsis = "%prog [options]"
690 Option("--server", help="writable DC to write demotion changes on", type=str),
691 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
692 metavar="URL", dest="H"),
693 Option("--remove-other-dead-server", help="Dead DC (name or NTDS GUID) "
694 "to remove ALL references to (rather than this DC)", type=str),
695 Option("-q", "--quiet", help="Be quiet", action="store_true"),
696 Option("-v", "--verbose", help="Be verbose", action="store_true"),
699 takes_optiongroups = {
700 "sambaopts": options.SambaOptions,
701 "credopts": options.CredentialsOptions,
702 "versionopts": options.VersionOptions,
705 def run(self, sambaopts=None, credopts=None,
706 versionopts=None, server=None,
707 remove_other_dead_server=None, H=None,
708 verbose=False, quiet=False):
709 lp = sambaopts.get_loadparm()
710 creds = credopts.get_credentials(lp)
711 net = Net(creds, lp, server=credopts.ipaddress)
713 logger = self.get_logger(verbose=verbose, quiet=quiet)
715 if remove_other_dead_server is not None:
716 if server is not None:
717 samdb = SamDB(url="ldap://%s" % server,
718 session_info=system_session(),
719 credentials=creds, lp=lp)
721 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
723 remove_dc.remove_dc(samdb, logger, remove_other_dead_server)
724 except remove_dc.DemoteException as err:
725 raise CommandError("Demote failed: %s" % err)
728 netbios_name = lp.get("netbios name")
729 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
731 res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
733 raise CommandError("Unable to search for servers")
736 raise CommandError("You are the last server in the domain")
740 if str(e["name"]).lower() != netbios_name.lower():
741 server = e["dnsHostName"]
744 ntds_guid = samdb.get_ntds_GUID()
745 msg = samdb.search(base=str(samdb.get_config_basedn()),
746 scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
748 if len(msg) == 0 or "options" not in msg[0]:
749 raise CommandError("Failed to find options on %s" % ntds_guid)
752 dsa_options = int(str(msg[0]['options']))
754 res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
755 controls=["search_options:1:2"])
758 raise CommandError("Current DC is still the owner of %d role(s), "
759 "use the role command to transfer roles to "
763 self.errf.write("Using %s as partner server for the demotion\n" %
765 (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
767 self.errf.write("Deactivating inbound replication\n")
772 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
773 dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
774 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
777 self.errf.write("Asking partner server %s to synchronize from us\n"
779 for part in (samdb.get_schema_basedn(),
780 samdb.get_config_basedn(),
781 samdb.get_root_basedn()):
782 nc = drsuapi.DsReplicaObjectIdentifier()
785 req1 = drsuapi.DsReplicaSyncRequest1()
786 req1.naming_context = nc
787 req1.options = drsuapi.DRSUAPI_DRS_WRIT_REP
788 req1.source_dsa_guid = misc.GUID(ntds_guid)
791 drsuapiBind.DsReplicaSync(drsuapi_handle, 1, req1)
792 except RuntimeError as e1:
793 (werr, string) = e1.args
794 if werr == werror.WERR_DS_DRA_NO_REPLICA:
798 "Error while replicating out last local changes from '%s' for demotion, "
799 "re-enabling inbound replication\n" % part)
800 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
801 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
803 raise CommandError("Error while sending a DsReplicaSync for partition '%s'" % str(part), string)
805 remote_samdb = SamDB(url="ldap://%s" % server,
806 session_info=system_session(),
807 credentials=creds, lp=lp)
809 self.errf.write("Changing userControl and container\n")
810 res = remote_samdb.search(base=str(remote_samdb.domain_dn()),
811 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
812 netbios_name.upper(),
813 attrs=["userAccountControl"])
815 uac = int(str(res[0]["userAccountControl"]))
817 except Exception as e:
818 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
820 "Error while demoting, re-enabling inbound replication\n")
821 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
822 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
824 raise CommandError("Error while changing account control", e)
827 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
829 "Error while demoting, re-enabling inbound replication")
830 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
831 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
833 raise CommandError("Unable to find object with samaccountName = %s$"
834 " in the remote dc" % netbios_name.upper())
838 uac &= ~(UF_SERVER_TRUST_ACCOUNT |
839 UF_TRUSTED_FOR_DELEGATION |
840 UF_PARTIAL_SECRETS_ACCOUNT)
841 uac |= UF_WORKSTATION_TRUST_ACCOUNT
846 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
847 ldb.FLAG_MOD_REPLACE,
848 "userAccountControl")
850 remote_samdb.modify(msg)
851 except Exception as e:
852 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
854 "Error while demoting, re-enabling inbound replication")
855 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
856 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
859 raise CommandError("Error while changing account control", e)
861 parent = msg.dn.parent()
862 dc_name = res[0].dn.get_rdn_value()
863 rdn = "CN=%s" % dc_name
865 # Let's move to the Computer container
869 computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.domain_dn()))
870 res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
873 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
874 scope=ldb.SCOPE_ONELEVEL)
875 while(len(res) != 0 and i < 100):
877 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
878 scope=ldb.SCOPE_ONELEVEL)
881 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
883 "Error while demoting, re-enabling inbound replication\n")
884 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
885 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
891 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
892 ldb.FLAG_MOD_REPLACE,
893 "userAccountControl")
895 remote_samdb.modify(msg)
897 raise CommandError("Unable to find a slot for renaming %s,"
898 " all names from %s-1 to %s-%d seemed used" %
899 (str(dc_dn), rdn, rdn, i - 9))
901 newrdn = "%s-%d" % (rdn, i)
904 newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
905 remote_samdb.rename(dc_dn, newdn)
906 except Exception as e:
907 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
909 "Error while demoting, re-enabling inbound replication\n")
910 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
911 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
917 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
918 ldb.FLAG_MOD_REPLACE,
919 "userAccountControl")
921 remote_samdb.modify(msg)
922 raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
924 server_dsa_dn = samdb.get_serverName()
925 domain = remote_samdb.get_root_basedn()
928 req1 = drsuapi.DsRemoveDSServerRequest1()
929 req1.server_dn = str(server_dsa_dn)
930 req1.domain_dn = str(domain)
933 drsuapiBind.DsRemoveDSServer(drsuapi_handle, 1, req1)
934 except RuntimeError as e3:
935 (werr, string) = e3.args
936 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
938 "Error while demoting, re-enabling inbound replication\n")
939 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
940 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
946 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
947 ldb.FLAG_MOD_REPLACE,
948 "userAccountControl")
949 remote_samdb.modify(msg)
950 remote_samdb.rename(newdn, dc_dn)
951 if werr == werror.WERR_DS_DRA_NO_REPLICA:
952 raise CommandError("The DC %s is not present on (already "
953 "removed from) the remote server: %s" %
956 raise CommandError("Error while sending a removeDsServer "
960 remove_dc.remove_sysvol_references(remote_samdb, logger, dc_name)
962 # These are objects under the computer account that should be deleted
963 for s in ("CN=Enterprise,CN=NTFRS Subscriptions",
964 "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
965 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
966 "CN=NTFRS Subscriptions"):
968 remote_samdb.delete(ldb.Dn(remote_samdb,
969 "%s,%s" % (s, str(newdn))))
970 except ldb.LdbError as l:
973 # get dns host name for target server to demote, remove dns references
974 remove_dc.remove_dns_references(remote_samdb, logger, samdb.host_dns_name(),
977 self.errf.write("Demote successful\n")
980 class cmd_domain_level(Command):
981 """Raise domain and forest function levels."""
983 synopsis = "%prog (show|raise <options>) [options]"
985 takes_optiongroups = {
986 "sambaopts": options.SambaOptions,
987 "credopts": options.CredentialsOptions,
988 "versionopts": options.VersionOptions,
992 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
993 metavar="URL", dest="H"),
994 Option("-q", "--quiet", help="Be quiet", action="store_true"), # unused
995 Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
996 help="The forest function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)"),
997 Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
998 help="The domain function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)")
1001 takes_args = ["subcommand"]
1003 def run(self, subcommand, H=None, forest_level=None, domain_level=None,
1004 quiet=False, credopts=None, sambaopts=None, versionopts=None):
1005 lp = sambaopts.get_loadparm()
1006 creds = credopts.get_credentials(lp, fallback_machine=True)
1008 samdb = SamDB(url=H, session_info=system_session(),
1009 credentials=creds, lp=lp)
1011 domain_dn = samdb.domain_dn()
1013 res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
1014 scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
1015 assert len(res_forest) == 1
1017 res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1018 attrs=["msDS-Behavior-Version", "nTMixedDomain"])
1019 assert len(res_domain) == 1
1021 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
1022 scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
1023 attrs=["msDS-Behavior-Version"])
1024 assert len(res_dc_s) >= 1
1026 # default values, since "msDS-Behavior-Version" does not exist on Windows 2000 AD
1027 level_forest = DS_DOMAIN_FUNCTION_2000
1028 level_domain = DS_DOMAIN_FUNCTION_2000
1030 if "msDS-Behavior-Version" in res_forest[0]:
1031 level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
1032 if "msDS-Behavior-Version" in res_domain[0]:
1033 level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
1034 level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
1037 for msg in res_dc_s:
1038 if "msDS-Behavior-Version" in msg:
1039 if min_level_dc is None or int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
1040 min_level_dc = int(msg["msDS-Behavior-Version"][0])
1042 min_level_dc = DS_DOMAIN_FUNCTION_2000
1043 # well, this is the least
1046 if level_forest < DS_DOMAIN_FUNCTION_2000 or level_domain < DS_DOMAIN_FUNCTION_2000:
1047 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
1048 if min_level_dc < DS_DOMAIN_FUNCTION_2000:
1049 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
1050 if level_forest > level_domain:
1051 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
1052 if level_domain > min_level_dc:
1053 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
1055 if subcommand == "show":
1056 self.message("Domain and forest function level for domain '%s'" % domain_dn)
1057 if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1058 self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1059 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1060 self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1061 if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1062 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)!")
1066 if level_forest == DS_DOMAIN_FUNCTION_2000:
1068 elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
1069 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1070 elif level_forest == DS_DOMAIN_FUNCTION_2003:
1072 elif level_forest == DS_DOMAIN_FUNCTION_2008:
1074 elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
1076 elif level_forest == DS_DOMAIN_FUNCTION_2012:
1078 elif level_forest == DS_DOMAIN_FUNCTION_2012_R2:
1081 outstr = "higher than 2012 R2"
1082 self.message("Forest function level: (Windows) " + outstr)
1084 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1085 outstr = "2000 mixed (NT4 DC support)"
1086 elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
1088 elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
1089 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1090 elif level_domain == DS_DOMAIN_FUNCTION_2003:
1092 elif level_domain == DS_DOMAIN_FUNCTION_2008:
1094 elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
1096 elif level_domain == DS_DOMAIN_FUNCTION_2012:
1098 elif level_domain == DS_DOMAIN_FUNCTION_2012_R2:
1101 outstr = "higher than 2012 R2"
1102 self.message("Domain function level: (Windows) " + outstr)
1104 if min_level_dc == DS_DOMAIN_FUNCTION_2000:
1106 elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
1108 elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
1110 elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
1112 elif min_level_dc == DS_DOMAIN_FUNCTION_2012:
1114 elif min_level_dc == DS_DOMAIN_FUNCTION_2012_R2:
1117 outstr = "higher than 2012 R2"
1118 self.message("Lowest function level of a DC: (Windows) " + outstr)
1120 elif subcommand == "raise":
1123 if domain_level is not None:
1124 if domain_level == "2003":
1125 new_level_domain = DS_DOMAIN_FUNCTION_2003
1126 elif domain_level == "2008":
1127 new_level_domain = DS_DOMAIN_FUNCTION_2008
1128 elif domain_level == "2008_R2":
1129 new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
1130 elif domain_level == "2012":
1131 new_level_domain = DS_DOMAIN_FUNCTION_2012
1132 elif domain_level == "2012_R2":
1133 new_level_domain = DS_DOMAIN_FUNCTION_2012_R2
1135 if new_level_domain <= level_domain and level_domain_mixed == 0:
1136 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
1137 if new_level_domain > min_level_dc:
1138 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
1140 # Deactivate mixed/interim domain support
1141 if level_domain_mixed != 0:
1142 # Directly on the base DN
1144 m.dn = ldb.Dn(samdb, domain_dn)
1145 m["nTMixedDomain"] = ldb.MessageElement("0",
1146 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1150 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
1151 m["nTMixedDomain"] = ldb.MessageElement("0",
1152 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1155 except ldb.LdbError as e:
1156 (enum, emsg) = e.args
1157 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1160 # Directly on the base DN
1162 m.dn = ldb.Dn(samdb, domain_dn)
1163 m["msDS-Behavior-Version"] = ldb.MessageElement(
1164 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1165 "msDS-Behavior-Version")
1169 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
1170 + ",CN=Partitions,%s" % samdb.get_config_basedn())
1171 m["msDS-Behavior-Version"] = ldb.MessageElement(
1172 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1173 "msDS-Behavior-Version")
1176 except ldb.LdbError as e2:
1177 (enum, emsg) = e2.args
1178 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1181 level_domain = new_level_domain
1182 msgs.append("Domain function level changed!")
1184 if forest_level is not None:
1185 if forest_level == "2003":
1186 new_level_forest = DS_DOMAIN_FUNCTION_2003
1187 elif forest_level == "2008":
1188 new_level_forest = DS_DOMAIN_FUNCTION_2008
1189 elif forest_level == "2008_R2":
1190 new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
1191 elif forest_level == "2012":
1192 new_level_forest = DS_DOMAIN_FUNCTION_2012
1193 elif forest_level == "2012_R2":
1194 new_level_forest = DS_DOMAIN_FUNCTION_2012_R2
1196 if new_level_forest <= level_forest:
1197 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1198 if new_level_forest > level_domain:
1199 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1202 m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
1203 m["msDS-Behavior-Version"] = ldb.MessageElement(
1204 str(new_level_forest), ldb.FLAG_MOD_REPLACE,
1205 "msDS-Behavior-Version")
1207 msgs.append("Forest function level changed!")
1208 msgs.append("All changes applied successfully!")
1209 self.message("\n".join(msgs))
1211 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
1214 # In MS AD, setting a timeout to '(never)' corresponds to this value
1215 NEVER_TIMESTAMP = int(-0x8000000000000000)
1218 def timestamp_to_mins(timestamp_str):
1219 """Converts a timestamp in -100 nanosecond units to minutes"""
1220 # treat a timestamp of 'never' the same as zero (this should work OK for
1221 # most settings, and it displays better than trying to convert
1222 # -0x8000000000000000 to minutes)
1223 if int(timestamp_str) == NEVER_TIMESTAMP:
1226 return abs(int(timestamp_str)) / (1e7 * 60)
1229 def timestamp_to_days(timestamp_str):
1230 """Converts a timestamp in -100 nanosecond units to days"""
1231 return timestamp_to_mins(timestamp_str) / (60 * 24)
1234 class cmd_domain_passwordsettings_show(Command):
1235 """Display current password settings for the domain."""
1237 synopsis = "%prog [options]"
1239 takes_optiongroups = {
1240 "sambaopts": options.SambaOptions,
1241 "versionopts": options.VersionOptions,
1242 "credopts": options.CredentialsOptions,
1246 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1247 metavar="URL", dest="H"),
1250 def run(self, H=None, credopts=None, sambaopts=None, versionopts=None):
1251 lp = sambaopts.get_loadparm()
1252 creds = credopts.get_credentials(lp)
1254 samdb = SamDB(url=H, session_info=system_session(),
1255 credentials=creds, lp=lp)
1257 domain_dn = samdb.domain_dn()
1258 res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1259 attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1260 "minPwdAge", "maxPwdAge", "lockoutDuration", "lockoutThreshold",
1261 "lockOutObservationWindow"])
1262 assert(len(res) == 1)
1264 pwd_props = int(res[0]["pwdProperties"][0])
1265 pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
1266 cur_min_pwd_len = int(res[0]["minPwdLength"][0])
1268 cur_min_pwd_age = timestamp_to_days(res[0]["minPwdAge"][0])
1269 cur_max_pwd_age = timestamp_to_days(res[0]["maxPwdAge"][0])
1271 cur_account_lockout_threshold = int(res[0]["lockoutThreshold"][0])
1274 cur_account_lockout_duration = timestamp_to_mins(res[0]["lockoutDuration"][0])
1275 cur_reset_account_lockout_after = timestamp_to_mins(res[0]["lockOutObservationWindow"][0])
1276 except Exception as e:
1277 raise CommandError("Could not retrieve password properties!", e)
1279 self.message("Password information for domain '%s'" % domain_dn)
1281 if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
1282 self.message("Password complexity: on")
1284 self.message("Password complexity: off")
1285 if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
1286 self.message("Store plaintext passwords: on")
1288 self.message("Store plaintext passwords: off")
1289 self.message("Password history length: %d" % pwd_hist_len)
1290 self.message("Minimum password length: %d" % cur_min_pwd_len)
1291 self.message("Minimum password age (days): %d" % cur_min_pwd_age)
1292 self.message("Maximum password age (days): %d" % cur_max_pwd_age)
1293 self.message("Account lockout duration (mins): %d" % cur_account_lockout_duration)
1294 self.message("Account lockout threshold (attempts): %d" % cur_account_lockout_threshold)
1295 self.message("Reset account lockout after (mins): %d" % cur_reset_account_lockout_after)
1298 class cmd_domain_passwordsettings_set(Command):
1299 """Set password settings.
1301 Password complexity, password lockout policy, history length,
1302 minimum password length, the minimum and maximum password age) on
1303 a Samba AD DC server.
1305 Use against a Windows DC is possible, but group policy will override it.
1308 synopsis = "%prog <options> [options]"
1310 takes_optiongroups = {
1311 "sambaopts": options.SambaOptions,
1312 "versionopts": options.VersionOptions,
1313 "credopts": options.CredentialsOptions,
1317 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1318 metavar="URL", dest="H"),
1319 Option("-q", "--quiet", help="Be quiet", action="store_true"), # unused
1320 Option("--complexity", type="choice", choices=["on", "off", "default"],
1321 help="The password complexity (on | off | default). Default is 'on'"),
1322 Option("--store-plaintext", type="choice", choices=["on", "off", "default"],
1323 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1324 Option("--history-length",
1325 help="The password history length (<integer> | default). Default is 24.", type=str),
1326 Option("--min-pwd-length",
1327 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1328 Option("--min-pwd-age",
1329 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1330 Option("--max-pwd-age",
1331 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1332 Option("--account-lockout-duration",
1333 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),
1334 Option("--account-lockout-threshold",
1335 help="The number of bad password attempts allowed before locking out the account (<integer> | default). Default is 0 (never lock out).", type=str),
1336 Option("--reset-account-lockout-after",
1337 help="After this time is elapsed, the recorded number of attempts restarts from zero (<integer> | default). Default is 30.", type=str),
1340 def run(self, H=None, min_pwd_age=None, max_pwd_age=None,
1341 quiet=False, complexity=None, store_plaintext=None, history_length=None,
1342 min_pwd_length=None, account_lockout_duration=None, account_lockout_threshold=None,
1343 reset_account_lockout_after=None, credopts=None, sambaopts=None,
1345 lp = sambaopts.get_loadparm()
1346 creds = credopts.get_credentials(lp)
1348 samdb = SamDB(url=H, session_info=system_session(),
1349 credentials=creds, lp=lp)
1351 domain_dn = samdb.domain_dn()
1354 m.dn = ldb.Dn(samdb, domain_dn)
1355 pwd_props = int(samdb.get_pwdProperties())
1357 # get the current password age settings
1358 max_pwd_age_ticks = samdb.get_maxPwdAge()
1359 min_pwd_age_ticks = samdb.get_minPwdAge()
1361 if complexity is not None:
1362 if complexity == "on" or complexity == "default":
1363 pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
1364 msgs.append("Password complexity activated!")
1365 elif complexity == "off":
1366 pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
1367 msgs.append("Password complexity deactivated!")
1369 if store_plaintext is not None:
1370 if store_plaintext == "on" or store_plaintext == "default":
1371 pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1372 msgs.append("Plaintext password storage for changed passwords activated!")
1373 elif store_plaintext == "off":
1374 pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
1375 msgs.append("Plaintext password storage for changed passwords deactivated!")
1377 if complexity is not None or store_plaintext is not None:
1378 m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
1379 ldb.FLAG_MOD_REPLACE, "pwdProperties")
1381 if history_length is not None:
1382 if history_length == "default":
1385 pwd_hist_len = int(history_length)
1387 if pwd_hist_len < 0 or pwd_hist_len > 24:
1388 raise CommandError("Password history length must be in the range of 0 to 24!")
1390 m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
1391 ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
1392 msgs.append("Password history length changed!")
1394 if min_pwd_length is not None:
1395 if min_pwd_length == "default":
1398 min_pwd_len = int(min_pwd_length)
1400 if min_pwd_len < 0 or min_pwd_len > 14:
1401 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1403 m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
1404 ldb.FLAG_MOD_REPLACE, "minPwdLength")
1405 msgs.append("Minimum password length changed!")
1407 if min_pwd_age is not None:
1408 if min_pwd_age == "default":
1411 min_pwd_age = int(min_pwd_age)
1413 if min_pwd_age < 0 or min_pwd_age > 998:
1414 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1417 min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
1419 m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
1420 ldb.FLAG_MOD_REPLACE, "minPwdAge")
1421 msgs.append("Minimum password age changed!")
1423 if max_pwd_age is not None:
1424 if max_pwd_age == "default":
1427 max_pwd_age = int(max_pwd_age)
1429 if max_pwd_age < 0 or max_pwd_age > 999:
1430 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1433 if max_pwd_age == 0:
1434 max_pwd_age_ticks = NEVER_TIMESTAMP
1436 max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
1438 m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
1439 ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1440 msgs.append("Maximum password age changed!")
1442 if account_lockout_duration is not None:
1443 if account_lockout_duration == "default":
1444 account_lockout_duration = 30
1446 account_lockout_duration = int(account_lockout_duration)
1448 if account_lockout_duration < 0 or account_lockout_duration > 99999:
1449 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1452 if account_lockout_duration == 0:
1453 account_lockout_duration_ticks = NEVER_TIMESTAMP
1455 account_lockout_duration_ticks = -int(account_lockout_duration * (60 * 1e7))
1457 m["lockoutDuration"] = ldb.MessageElement(str(account_lockout_duration_ticks),
1458 ldb.FLAG_MOD_REPLACE, "lockoutDuration")
1459 msgs.append("Account lockout duration changed!")
1461 if account_lockout_threshold is not None:
1462 if account_lockout_threshold == "default":
1463 account_lockout_threshold = 0
1465 account_lockout_threshold = int(account_lockout_threshold)
1467 m["lockoutThreshold"] = ldb.MessageElement(str(account_lockout_threshold),
1468 ldb.FLAG_MOD_REPLACE, "lockoutThreshold")
1469 msgs.append("Account lockout threshold changed!")
1471 if reset_account_lockout_after is not None:
1472 if reset_account_lockout_after == "default":
1473 reset_account_lockout_after = 30
1475 reset_account_lockout_after = int(reset_account_lockout_after)
1477 if reset_account_lockout_after < 0 or reset_account_lockout_after > 99999:
1478 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1481 if reset_account_lockout_after == 0:
1482 reset_account_lockout_after_ticks = NEVER_TIMESTAMP
1484 reset_account_lockout_after_ticks = -int(reset_account_lockout_after * (60 * 1e7))
1486 m["lockOutObservationWindow"] = ldb.MessageElement(str(reset_account_lockout_after_ticks),
1487 ldb.FLAG_MOD_REPLACE, "lockOutObservationWindow")
1488 msgs.append("Duration to reset account lockout after changed!")
1490 if max_pwd_age or min_pwd_age:
1491 # If we're setting either min or max password, make sure the max is
1492 # still greater overall. As either setting could be None, we use the
1493 # ticks here (which are always set) and work backwards.
1494 max_pwd_age = timestamp_to_days(max_pwd_age_ticks)
1495 min_pwd_age = timestamp_to_days(min_pwd_age_ticks)
1496 if max_pwd_age != 0 and min_pwd_age >= max_pwd_age:
1497 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
1500 raise CommandError("You must specify at least one option to set. Try --help")
1502 msgs.append("All changes applied successfully!")
1503 self.message("\n".join(msgs))
1506 class cmd_domain_passwordsettings(SuperCommand):
1507 """Manage password policy settings."""
1510 subcommands["pso"] = cmd_domain_passwordsettings_pso()
1511 subcommands["show"] = cmd_domain_passwordsettings_show()
1512 subcommands["set"] = cmd_domain_passwordsettings_set()
1515 class cmd_domain_classicupgrade(Command):
1516 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1518 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1519 the testparm utility from your classic installation (with --testparm).
1522 synopsis = "%prog [options] <classic_smb_conf>"
1524 takes_optiongroups = {
1525 "sambaopts": options.SambaOptions,
1526 "versionopts": options.VersionOptions
1530 Option("--dbdir", type="string", metavar="DIR",
1531 help="Path to samba classic DC database directory"),
1532 Option("--testparm", type="string", metavar="PATH",
1533 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1534 Option("--targetdir", type="string", metavar="DIR",
1535 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1536 Option("-q", "--quiet", help="Be quiet", action="store_true"),
1537 Option("-v", "--verbose", help="Be verbose", action="store_true"),
1538 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
1539 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1540 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1541 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1542 "BIND9_DLZ uses samba4 AD to store zone information, "
1543 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1544 default="SAMBA_INTERNAL")
1548 Option("--use-xattrs", type="choice", choices=["yes", "no", "auto"],
1549 metavar="[yes|no|auto]",
1550 help="Define if we should use the native fs capabilities or a tdb file for "
1551 "storing attributes likes ntacl when --use-ntvfs is set. "
1552 "auto tries to make an inteligent guess based on the user rights and system capabilities",
1555 if samba.is_ntvfs_fileserver_built():
1556 takes_options.extend(common_ntvfs_options)
1557 takes_options.extend(ntvfs_options)
1559 takes_args = ["smbconf"]
1561 def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
1562 quiet=False, verbose=False, use_xattrs="auto", sambaopts=None, versionopts=None,
1563 dns_backend=None, use_ntvfs=False):
1565 if not os.path.exists(smbconf):
1566 raise CommandError("File %s does not exist" % smbconf)
1568 if testparm and not os.path.exists(testparm):
1569 raise CommandError("Testparm utility %s does not exist" % testparm)
1571 if dbdir and not os.path.exists(dbdir):
1572 raise CommandError("Directory %s does not exist" % dbdir)
1574 if not dbdir and not testparm:
1575 raise CommandError("Please specify either dbdir or testparm")
1577 logger = self.get_logger(verbose=verbose, quiet=quiet)
1579 if dbdir and testparm:
1580 logger.warning("both dbdir and testparm specified, ignoring dbdir.")
1583 lp = sambaopts.get_loadparm()
1585 s3conf = s3param.get_context()
1588 s3conf.set("realm", sambaopts.realm)
1590 if targetdir is not None:
1591 if not os.path.isdir(targetdir):
1595 if use_xattrs == "yes":
1597 elif use_xattrs == "auto" and use_ntvfs == False:
1599 elif use_ntvfs == False:
1600 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
1601 "Please re-run with --use-xattrs omitted.")
1602 elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
1604 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
1606 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
1609 samba.ntacls.setntacl(lp, tmpfile.name,
1610 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1613 # FIXME: Don't catch all exceptions here
1614 logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. "
1615 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1619 # Set correct default values from dbdir or testparm
1622 paths["state directory"] = dbdir
1623 paths["private dir"] = dbdir
1624 paths["lock directory"] = dbdir
1625 paths["smb passwd file"] = dbdir + "/smbpasswd"
1627 paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
1628 paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
1629 paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
1630 paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
1631 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1632 # "state directory", instead make use of "lock directory"
1633 if len(paths["state directory"]) == 0:
1634 paths["state directory"] = paths["lock directory"]
1637 s3conf.set(p, paths[p])
1639 # load smb.conf parameters
1640 logger.info("Reading smb.conf")
1641 s3conf.load(smbconf)
1642 samba3 = Samba3(smbconf, s3conf)
1644 logger.info("Provisioning")
1645 upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
1646 useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
1649 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
1650 __doc__ = cmd_domain_classicupgrade.__doc__
1652 # This command is present for backwards compatibility only,
1653 # and should not be shown.
1658 class LocalDCCredentialsOptions(options.CredentialsOptions):
1659 def __init__(self, parser):
1660 options.CredentialsOptions.__init__(self, parser, special_name="local-dc")
1663 class DomainTrustCommand(Command):
1664 """List domain trusts."""
1667 Command.__init__(self)
1668 self.local_lp = None
1670 self.local_server = None
1671 self.local_binding_string = None
1672 self.local_creds = None
1674 self.remote_server = None
1675 self.remote_binding_string = None
1676 self.remote_creds = None
1678 def _uint32(self, v):
1679 return ctypes.c_uint32(v).value
1681 def check_runtime_error(self, runtime, val):
1685 err32 = self._uint32(runtime.args[0])
1691 class LocalRuntimeError(CommandError):
1692 def __init__(exception_self, self, runtime, message):
1693 err32 = self._uint32(runtime.args[0])
1694 errstr = runtime.args[1]
1695 msg = "LOCAL_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1696 self.local_server, message, err32, errstr)
1697 CommandError.__init__(exception_self, msg)
1699 class RemoteRuntimeError(CommandError):
1700 def __init__(exception_self, self, runtime, message):
1701 err32 = self._uint32(runtime.args[0])
1702 errstr = runtime.args[1]
1703 msg = "REMOTE_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1704 self.remote_server, message, err32, errstr)
1705 CommandError.__init__(exception_self, msg)
1707 class LocalLdbError(CommandError):
1708 def __init__(exception_self, self, ldb_error, message):
1709 errval = ldb_error.args[0]
1710 errstr = ldb_error.args[1]
1711 msg = "LOCAL_DC[%s]: %s - ERROR(%d) - %s" % (
1712 self.local_server, message, errval, errstr)
1713 CommandError.__init__(exception_self, msg)
1715 def setup_local_server(self, sambaopts, localdcopts):
1716 if self.local_server is not None:
1717 return self.local_server
1719 lp = sambaopts.get_loadparm()
1721 local_server = localdcopts.ipaddress
1722 if local_server is None:
1723 server_role = lp.server_role()
1724 if server_role != "ROLE_ACTIVE_DIRECTORY_DC":
1725 raise CommandError("Invalid server_role %s" % (server_role))
1726 local_server = lp.get('netbios name')
1727 local_transport = "ncalrpc"
1728 local_binding_options = ""
1729 local_binding_options += ",auth_type=ncalrpc_as_system"
1730 local_ldap_url = None
1733 local_transport = "ncacn_np"
1734 local_binding_options = ""
1735 local_ldap_url = "ldap://%s" % local_server
1736 local_creds = localdcopts.get_credentials(lp)
1740 self.local_server = local_server
1741 self.local_binding_string = "%s:%s[%s]" % (local_transport, local_server, local_binding_options)
1742 self.local_ldap_url = local_ldap_url
1743 self.local_creds = local_creds
1744 return self.local_server
1746 def new_local_lsa_connection(self):
1747 return lsa.lsarpc(self.local_binding_string, self.local_lp, self.local_creds)
1749 def new_local_netlogon_connection(self):
1750 return netlogon.netlogon(self.local_binding_string, self.local_lp, self.local_creds)
1752 def new_local_ldap_connection(self):
1753 return SamDB(url=self.local_ldap_url,
1754 session_info=system_session(),
1755 credentials=self.local_creds,
1758 def setup_remote_server(self, credopts, domain,
1760 require_writable=True):
1763 assert require_writable
1765 if self.remote_server is not None:
1766 return self.remote_server
1768 self.remote_server = "__unknown__remote_server__.%s" % domain
1769 assert self.local_server is not None
1771 remote_creds = credopts.get_credentials(self.local_lp)
1772 remote_server = credopts.ipaddress
1773 remote_binding_options = ""
1775 # TODO: we should also support NT4 domains
1776 # we could use local_netlogon.netr_DsRGetDCNameEx2() with the remote domain name
1777 # and delegate NBT or CLDAP to the local netlogon server
1779 remote_net = Net(remote_creds, self.local_lp, server=remote_server)
1780 remote_flags = nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS
1781 if require_writable:
1782 remote_flags |= nbt.NBT_SERVER_WRITABLE
1784 remote_flags |= nbt.NBT_SERVER_PDC
1785 remote_info = remote_net.finddc(flags=remote_flags, domain=domain, address=remote_server)
1786 except NTSTATUSError as error:
1787 raise CommandError("Failed to find a writeable DC for domain '%s': %s" %
1788 (domain, error.args[1]))
1790 raise CommandError("Failed to find a writeable DC for domain '%s'" % domain)
1792 nbt.NBT_SERVER_PDC: "PDC",
1793 nbt.NBT_SERVER_GC: "GC",
1794 nbt.NBT_SERVER_LDAP: "LDAP",
1795 nbt.NBT_SERVER_DS: "DS",
1796 nbt.NBT_SERVER_KDC: "KDC",
1797 nbt.NBT_SERVER_TIMESERV: "TIMESERV",
1798 nbt.NBT_SERVER_CLOSEST: "CLOSEST",
1799 nbt.NBT_SERVER_WRITABLE: "WRITABLE",
1800 nbt.NBT_SERVER_GOOD_TIMESERV: "GOOD_TIMESERV",
1801 nbt.NBT_SERVER_NDNC: "NDNC",
1802 nbt.NBT_SERVER_SELECT_SECRET_DOMAIN_6: "SELECT_SECRET_DOMAIN_6",
1803 nbt.NBT_SERVER_FULL_SECRET_DOMAIN_6: "FULL_SECRET_DOMAIN_6",
1804 nbt.NBT_SERVER_ADS_WEB_SERVICE: "ADS_WEB_SERVICE",
1805 nbt.NBT_SERVER_DS_8: "DS_8",
1806 nbt.NBT_SERVER_HAS_DNS_NAME: "HAS_DNS_NAME",
1807 nbt.NBT_SERVER_IS_DEFAULT_NC: "IS_DEFAULT_NC",
1808 nbt.NBT_SERVER_FOREST_ROOT: "FOREST_ROOT",
1810 server_type_string = self.generic_bitmap_to_string(flag_map,
1811 remote_info.server_type, names_only=True)
1812 self.outf.write("RemoteDC Netbios[%s] DNS[%s] ServerType[%s]\n" % (
1813 remote_info.pdc_name,
1814 remote_info.pdc_dns_name,
1815 server_type_string))
1817 self.remote_server = remote_info.pdc_dns_name
1818 self.remote_binding_string = "ncacn_np:%s[%s]" % (self.remote_server, remote_binding_options)
1819 self.remote_creds = remote_creds
1820 return self.remote_server
1822 def new_remote_lsa_connection(self):
1823 return lsa.lsarpc(self.remote_binding_string, self.local_lp, self.remote_creds)
1825 def new_remote_netlogon_connection(self):
1826 return netlogon.netlogon(self.remote_binding_string, self.local_lp, self.remote_creds)
1828 def get_lsa_info(self, conn, policy_access):
1829 objectAttr = lsa.ObjectAttribute()
1830 objectAttr.sec_qos = lsa.QosInfo()
1832 policy = conn.OpenPolicy2(b''.decode('utf-8'),
1833 objectAttr, policy_access)
1835 info = conn.QueryInfoPolicy2(policy, lsa.LSA_POLICY_INFO_DNS)
1837 return (policy, info)
1839 def get_netlogon_dc_unc(self, conn, server, domain):
1841 info = conn.netr_DsRGetDCNameEx2(server,
1842 None, 0, None, None, None,
1843 netlogon.DS_RETURN_DNS_NAME)
1845 except RuntimeError:
1846 return conn.netr_GetDcName(server, domain)
1848 def get_netlogon_dc_info(self, conn, server):
1849 info = conn.netr_DsRGetDCNameEx2(server,
1850 None, 0, None, None, None,
1851 netlogon.DS_RETURN_DNS_NAME)
1854 def netr_DomainTrust_to_name(self, t):
1855 if t.trust_type == lsa.LSA_TRUST_TYPE_DOWNLEVEL:
1856 return t.netbios_name
1860 def netr_DomainTrust_to_type(self, a, t):
1862 primary_parent = None
1864 if _t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
1866 if not _t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1867 primary_parent = a[_t.parent_index]
1870 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1871 if t is primary_parent:
1874 if t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1877 parent = a[t.parent_index]
1878 if parent is primary:
1883 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1888 def netr_DomainTrust_to_transitive(self, t):
1889 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1892 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE:
1895 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1900 def netr_DomainTrust_to_direction(self, t):
1901 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND and \
1902 t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1905 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND:
1908 if t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1913 def generic_enum_to_string(self, e_dict, v, names_only=False):
1917 v32 = self._uint32(v)
1918 w = "__unknown__%08X__" % v32
1920 r = "0x%x (%s)" % (v, w)
1923 def generic_bitmap_to_string(self, b_dict, v, names_only=False):
1928 for b in sorted(b_dict.keys()):
1935 c32 = self._uint32(c)
1936 s += ["__unknown_%08X__" % c32]
1941 r = "0x%x (%s)" % (v, w)
1944 def trustType_string(self, v):
1946 lsa.LSA_TRUST_TYPE_DOWNLEVEL: "DOWNLEVEL",
1947 lsa.LSA_TRUST_TYPE_UPLEVEL: "UPLEVEL",
1948 lsa.LSA_TRUST_TYPE_MIT: "MIT",
1949 lsa.LSA_TRUST_TYPE_DCE: "DCE",
1951 return self.generic_enum_to_string(types, v)
1953 def trustDirection_string(self, v):
1955 lsa.LSA_TRUST_DIRECTION_INBOUND |
1956 lsa.LSA_TRUST_DIRECTION_OUTBOUND: "BOTH",
1957 lsa.LSA_TRUST_DIRECTION_INBOUND: "INBOUND",
1958 lsa.LSA_TRUST_DIRECTION_OUTBOUND: "OUTBOUND",
1960 return self.generic_enum_to_string(directions, v)
1962 def trustAttributes_string(self, v):
1964 lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE: "NON_TRANSITIVE",
1965 lsa.LSA_TRUST_ATTRIBUTE_UPLEVEL_ONLY: "UPLEVEL_ONLY",
1966 lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN: "QUARANTINED_DOMAIN",
1967 lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE: "FOREST_TRANSITIVE",
1968 lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION: "CROSS_ORGANIZATION",
1969 lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST: "WITHIN_FOREST",
1970 lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL: "TREAT_AS_EXTERNAL",
1971 lsa.LSA_TRUST_ATTRIBUTE_USES_RC4_ENCRYPTION: "USES_RC4_ENCRYPTION",
1973 return self.generic_bitmap_to_string(attributes, v)
1975 def kerb_EncTypes_string(self, v):
1977 security.KERB_ENCTYPE_DES_CBC_CRC: "DES_CBC_CRC",
1978 security.KERB_ENCTYPE_DES_CBC_MD5: "DES_CBC_MD5",
1979 security.KERB_ENCTYPE_RC4_HMAC_MD5: "RC4_HMAC_MD5",
1980 security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96: "AES128_CTS_HMAC_SHA1_96",
1981 security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96: "AES256_CTS_HMAC_SHA1_96",
1982 security.KERB_ENCTYPE_FAST_SUPPORTED: "FAST_SUPPORTED",
1983 security.KERB_ENCTYPE_COMPOUND_IDENTITY_SUPPORTED: "COMPOUND_IDENTITY_SUPPORTED",
1984 security.KERB_ENCTYPE_CLAIMS_SUPPORTED: "CLAIMS_SUPPORTED",
1985 security.KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED: "RESOURCE_SID_COMPRESSION_DISABLED",
1987 return self.generic_bitmap_to_string(enctypes, v)
1989 def entry_tln_status(self, e_flags, ):
1991 return "Status[Enabled]"
1994 lsa.LSA_TLN_DISABLED_NEW: "Disabled-New",
1995 lsa.LSA_TLN_DISABLED_ADMIN: "Disabled",
1996 lsa.LSA_TLN_DISABLED_CONFLICT: "Disabled-Conflicting",
1998 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
2000 def entry_dom_status(self, e_flags):
2002 return "Status[Enabled]"
2005 lsa.LSA_SID_DISABLED_ADMIN: "Disabled-SID",
2006 lsa.LSA_SID_DISABLED_CONFLICT: "Disabled-SID-Conflicting",
2007 lsa.LSA_NB_DISABLED_ADMIN: "Disabled-NB",
2008 lsa.LSA_NB_DISABLED_CONFLICT: "Disabled-NB-Conflicting",
2010 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
2012 def write_forest_trust_info(self, fti, tln=None, collisions=None):
2014 tln_string = " TDO[%s]" % tln
2018 self.outf.write("Namespaces[%d]%s:\n" % (
2019 len(fti.entries), tln_string))
2021 for i, e in enumerate(fti.entries):
2024 collision_string = ""
2026 if collisions is not None:
2027 for c in collisions.entries:
2031 collision_string = " Collision[%s]" % (c.name.string)
2033 d = e.forest_trust_data
2034 if e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
2035 self.outf.write("TLN: %-32s DNS[*.%s]%s\n" % (
2036 self.entry_tln_status(flags),
2037 d.string, collision_string))
2038 elif e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
2039 self.outf.write("TLN_EX: %-29s DNS[*.%s]\n" % (
2041 elif e.type == lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
2042 self.outf.write("DOM: %-32s DNS[%s] Netbios[%s] SID[%s]%s\n" % (
2043 self.entry_dom_status(flags),
2044 d.dns_domain_name.string,
2045 d.netbios_domain_name.string,
2046 d.domain_sid, collision_string))
2050 class cmd_domain_trust_list(DomainTrustCommand):
2051 """List domain trusts."""
2053 synopsis = "%prog [options]"
2055 takes_optiongroups = {
2056 "sambaopts": options.SambaOptions,
2057 "versionopts": options.VersionOptions,
2058 "localdcopts": LocalDCCredentialsOptions,
2064 def run(self, sambaopts=None, versionopts=None, localdcopts=None):
2066 local_server = self.setup_local_server(sambaopts, localdcopts)
2068 local_netlogon = self.new_local_netlogon_connection()
2069 except RuntimeError as error:
2070 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2073 local_netlogon_trusts = \
2074 local_netlogon.netr_DsrEnumerateDomainTrusts(local_server,
2075 netlogon.NETR_TRUST_FLAG_IN_FOREST |
2076 netlogon.NETR_TRUST_FLAG_OUTBOUND |
2077 netlogon.NETR_TRUST_FLAG_INBOUND)
2078 except RuntimeError as error:
2079 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
2080 # TODO: we could implement a fallback to lsa.EnumTrustDom()
2081 raise CommandError("LOCAL_DC[%s]: netr_DsrEnumerateDomainTrusts not supported." % (
2083 raise self.LocalRuntimeError(self, error, "netr_DsrEnumerateDomainTrusts failed")
2085 a = local_netlogon_trusts.array
2087 if t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
2089 self.outf.write("%-14s %-15s %-19s %s\n" % (
2090 "Type[%s]" % self.netr_DomainTrust_to_type(a, t),
2091 "Transitive[%s]" % self.netr_DomainTrust_to_transitive(t),
2092 "Direction[%s]" % self.netr_DomainTrust_to_direction(t),
2093 "Name[%s]" % self.netr_DomainTrust_to_name(t)))
2097 class cmd_domain_trust_show(DomainTrustCommand):
2098 """Show trusted domain details."""
2100 synopsis = "%prog NAME [options]"
2102 takes_optiongroups = {
2103 "sambaopts": options.SambaOptions,
2104 "versionopts": options.VersionOptions,
2105 "localdcopts": LocalDCCredentialsOptions,
2111 takes_args = ["domain"]
2113 def run(self, domain, sambaopts=None, versionopts=None, localdcopts=None):
2115 local_server = self.setup_local_server(sambaopts, localdcopts)
2117 local_lsa = self.new_local_lsa_connection()
2118 except RuntimeError as error:
2119 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2122 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2123 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2124 except RuntimeError as error:
2125 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2127 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2128 local_lsa_info.name.string,
2129 local_lsa_info.dns_domain.string,
2130 local_lsa_info.sid))
2132 lsaString = lsa.String()
2133 lsaString.string = domain
2136 local_lsa.QueryTrustedDomainInfoByName(local_policy,
2138 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2139 local_tdo_info = local_tdo_full.info_ex
2140 local_tdo_posix = local_tdo_full.posix_offset
2141 except NTSTATUSError as error:
2142 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2143 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2145 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(FULL_INFO) failed")
2148 local_tdo_enctypes = \
2149 local_lsa.QueryTrustedDomainInfoByName(local_policy,
2151 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES)
2152 except NTSTATUSError as error:
2153 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_PARAMETER):
2155 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_INFO_CLASS):
2158 if error is not None:
2159 raise self.LocalRuntimeError(self, error,
2160 "QueryTrustedDomainInfoByName(SUPPORTED_ENCRYPTION_TYPES) failed")
2162 local_tdo_enctypes = lsa.TrustDomainInfoSupportedEncTypes()
2163 local_tdo_enctypes.enc_types = 0
2166 local_tdo_forest = None
2167 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2168 local_tdo_forest = \
2169 local_lsa.lsaRQueryForestTrustInformation(local_policy,
2171 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
2172 except RuntimeError as error:
2173 if self.check_runtime_error(error, ntstatus.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE):
2175 if self.check_runtime_error(error, ntstatus.NT_STATUS_NOT_FOUND):
2177 if error is not None:
2178 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation failed")
2180 local_tdo_forest = lsa.ForestTrustInformation()
2181 local_tdo_forest.count = 0
2182 local_tdo_forest.entries = []
2184 self.outf.write("TrustedDomain:\n\n")
2185 self.outf.write("NetbiosName: %s\n" % local_tdo_info.netbios_name.string)
2186 if local_tdo_info.netbios_name.string != local_tdo_info.domain_name.string:
2187 self.outf.write("DnsName: %s\n" % local_tdo_info.domain_name.string)
2188 self.outf.write("SID: %s\n" % local_tdo_info.sid)
2189 self.outf.write("Type: %s\n" % self.trustType_string(local_tdo_info.trust_type))
2190 self.outf.write("Direction: %s\n" % self.trustDirection_string(local_tdo_info.trust_direction))
2191 self.outf.write("Attributes: %s\n" % self.trustAttributes_string(local_tdo_info.trust_attributes))
2192 posix_offset_u32 = ctypes.c_uint32(local_tdo_posix.posix_offset).value
2193 posix_offset_i32 = ctypes.c_int32(local_tdo_posix.posix_offset).value
2194 self.outf.write("PosixOffset: 0x%08X (%d)\n" % (posix_offset_u32, posix_offset_i32))
2195 self.outf.write("kerb_EncTypes: %s\n" % self.kerb_EncTypes_string(local_tdo_enctypes.enc_types))
2197 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2198 self.write_forest_trust_info(local_tdo_forest,
2199 tln=local_tdo_info.domain_name.string)
2204 class cmd_domain_trust_create(DomainTrustCommand):
2205 """Create a domain or forest trust."""
2207 synopsis = "%prog DOMAIN [options]"
2209 takes_optiongroups = {
2210 "sambaopts": options.SambaOptions,
2211 "versionopts": options.VersionOptions,
2212 "credopts": options.CredentialsOptions,
2213 "localdcopts": LocalDCCredentialsOptions,
2217 Option("--type", type="choice", metavar="TYPE",
2218 choices=["external", "forest"],
2219 help="The type of the trust: 'external' or 'forest'.",
2221 default="external"),
2222 Option("--direction", type="choice", metavar="DIRECTION",
2223 choices=["incoming", "outgoing", "both"],
2224 help="The trust direction: 'incoming', 'outgoing' or 'both'.",
2225 dest='trust_direction',
2227 Option("--create-location", type="choice", metavar="LOCATION",
2228 choices=["local", "both"],
2229 help="Where to create the trusted domain object: 'local' or 'both'.",
2230 dest='create_location',
2232 Option("--cross-organisation", action="store_true",
2233 help="The related domains does not belong to the same organisation.",
2234 dest='cross_organisation',
2236 Option("--quarantined", type="choice", metavar="yes|no",
2237 choices=["yes", "no", None],
2238 help="Special SID filtering rules are applied to the trust. "
2239 "With --type=external the default is yes. "
2240 "With --type=forest the default is no.",
2241 dest='quarantined_arg',
2243 Option("--not-transitive", action="store_true",
2244 help="The forest trust is not transitive.",
2245 dest='not_transitive',
2247 Option("--treat-as-external", action="store_true",
2248 help="The treat the forest trust as external.",
2249 dest='treat_as_external',
2251 Option("--no-aes-keys", action="store_false",
2252 help="The trust uses aes kerberos keys.",
2253 dest='use_aes_keys',
2255 Option("--skip-validation", action="store_false",
2256 help="Skip validation of the trust.",
2261 takes_args = ["domain"]
2263 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2264 trust_type=None, trust_direction=None, create_location=None,
2265 cross_organisation=False, quarantined_arg=None,
2266 not_transitive=False, treat_as_external=False,
2267 use_aes_keys=False, validate=True):
2269 lsaString = lsa.String()
2272 if quarantined_arg is None:
2273 if trust_type == 'external':
2275 elif quarantined_arg == 'yes':
2278 if trust_type != 'forest':
2280 raise CommandError("--not-transitive requires --type=forest")
2281 if treat_as_external:
2282 raise CommandError("--treat-as-external requires --type=forest")
2286 enc_types = lsa.TrustDomainInfoSupportedEncTypes()
2287 enc_types.enc_types = security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96
2288 enc_types.enc_types |= security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96
2290 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2291 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2292 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2294 local_trust_info = lsa.TrustDomainInfoInfoEx()
2295 local_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2296 local_trust_info.trust_direction = 0
2297 if trust_direction == "both":
2298 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2299 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2300 elif trust_direction == "incoming":
2301 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2302 elif trust_direction == "outgoing":
2303 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2304 local_trust_info.trust_attributes = 0
2305 if cross_organisation:
2306 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2308 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2309 if trust_type == "forest":
2310 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2312 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2313 if treat_as_external:
2314 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2316 def get_password(name):
2319 if password is not None and password != '':
2321 password = getpass("New %s Password: " % name)
2322 passwordverify = getpass("Retype %s Password: " % name)
2323 if not password == passwordverify:
2325 self.outf.write("Sorry, passwords do not match.\n")
2327 incoming_secret = None
2328 outgoing_secret = None
2329 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2330 if create_location == "local":
2331 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2332 incoming_password = get_password("Incoming Trust")
2333 incoming_secret = string_to_byte_array(incoming_password.encode('utf-16-le'))
2334 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2335 outgoing_password = get_password("Outgoing Trust")
2336 outgoing_secret = string_to_byte_array(outgoing_password.encode('utf-16-le'))
2338 remote_trust_info = None
2340 # We use 240 random bytes.
2341 # Windows uses 28 or 240 random bytes. I guess it's
2342 # based on the trust type external vs. forest.
2344 # The initial trust password can be up to 512 bytes
2345 # while the versioned passwords used for periodic updates
2346 # can only be up to 498 bytes, as netr_ServerPasswordSet2()
2347 # needs to pass the NL_PASSWORD_VERSION structure within the
2348 # 512 bytes and a 2 bytes confounder is required.
2350 def random_trust_secret(length):
2351 pw = samba.generate_random_machine_password(length // 2, length // 2)
2352 return string_to_byte_array(pw.encode('utf-16-le'))
2354 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2355 incoming_secret = random_trust_secret(240)
2356 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2357 outgoing_secret = random_trust_secret(240)
2359 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2360 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2362 remote_trust_info = lsa.TrustDomainInfoInfoEx()
2363 remote_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2364 remote_trust_info.trust_direction = 0
2365 if trust_direction == "both":
2366 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2367 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2368 elif trust_direction == "incoming":
2369 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2370 elif trust_direction == "outgoing":
2371 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2372 remote_trust_info.trust_attributes = 0
2373 if cross_organisation:
2374 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2376 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2377 if trust_type == "forest":
2378 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2380 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2381 if treat_as_external:
2382 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2384 local_server = self.setup_local_server(sambaopts, localdcopts)
2386 local_lsa = self.new_local_lsa_connection()
2387 except RuntimeError as error:
2388 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2391 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2392 except RuntimeError as error:
2393 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2395 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2396 local_lsa_info.name.string,
2397 local_lsa_info.dns_domain.string,
2398 local_lsa_info.sid))
2401 remote_server = self.setup_remote_server(credopts, domain)
2402 except RuntimeError as error:
2403 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2406 remote_lsa = self.new_remote_lsa_connection()
2407 except RuntimeError as error:
2408 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2411 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2412 except RuntimeError as error:
2413 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2415 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2416 remote_lsa_info.name.string,
2417 remote_lsa_info.dns_domain.string,
2418 remote_lsa_info.sid))
2420 local_trust_info.domain_name.string = remote_lsa_info.dns_domain.string
2421 local_trust_info.netbios_name.string = remote_lsa_info.name.string
2422 local_trust_info.sid = remote_lsa_info.sid
2424 if remote_trust_info:
2425 remote_trust_info.domain_name.string = local_lsa_info.dns_domain.string
2426 remote_trust_info.netbios_name.string = local_lsa_info.name.string
2427 remote_trust_info.sid = local_lsa_info.sid
2430 lsaString.string = local_trust_info.domain_name.string
2431 local_old_netbios = \
2432 local_lsa.QueryTrustedDomainInfoByName(local_policy,
2434 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2435 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2436 except NTSTATUSError as error:
2437 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2438 raise self.LocalRuntimeError(self, error,
2439 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2443 lsaString.string = local_trust_info.netbios_name.string
2445 local_lsa.QueryTrustedDomainInfoByName(local_policy,
2447 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2448 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2449 except NTSTATUSError as error:
2450 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2451 raise self.LocalRuntimeError(self, error,
2452 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2455 if remote_trust_info:
2457 lsaString.string = remote_trust_info.domain_name.string
2458 remote_old_netbios = \
2459 remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2461 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2462 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2463 except NTSTATUSError as error:
2464 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2465 raise self.RemoteRuntimeError(self, error,
2466 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2470 lsaString.string = remote_trust_info.netbios_name.string
2472 remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2474 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2475 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2476 except NTSTATUSError as error:
2477 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2478 raise self.RemoteRuntimeError(self, error,
2479 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2483 local_netlogon = self.new_local_netlogon_connection()
2484 except RuntimeError as error:
2485 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2488 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
2489 except RuntimeError as error:
2490 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
2492 if remote_trust_info:
2494 remote_netlogon = self.new_remote_netlogon_connection()
2495 except RuntimeError as error:
2496 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2499 remote_netlogon_dc_unc = self.get_netlogon_dc_unc(remote_netlogon,
2500 remote_server, domain)
2501 except RuntimeError as error:
2502 raise self.RemoteRuntimeError(self, error, "failed to get netlogon dc info")
2504 def generate_AuthInOutBlob(secret, update_time):
2506 blob = drsblobs.trustAuthInOutBlob()
2511 clear = drsblobs.AuthInfoClear()
2512 clear.size = len(secret)
2513 clear.password = secret
2515 info = drsblobs.AuthenticationInformation()
2516 info.LastUpdateTime = samba.unix2nttime(update_time)
2517 info.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
2518 info.AuthInfo = clear
2520 array = drsblobs.AuthenticationInformationArray()
2522 array.array = [info]
2524 blob = drsblobs.trustAuthInOutBlob()
2526 blob.current = array
2530 def generate_AuthInfoInternal(session_key, incoming=None, outgoing=None):
2531 confounder = [0] * 512
2532 for i in range(len(confounder)):
2533 confounder[i] = random.randint(0, 255)
2535 trustpass = drsblobs.trustDomainPasswords()
2537 trustpass.confounder = confounder
2538 trustpass.outgoing = outgoing
2539 trustpass.incoming = incoming
2541 trustpass_blob = ndr_pack(trustpass)
2543 encrypted_trustpass = arcfour_encrypt(session_key, trustpass_blob)
2545 auth_blob = lsa.DATA_BUF2()
2546 auth_blob.size = len(encrypted_trustpass)
2547 auth_blob.data = string_to_byte_array(encrypted_trustpass)
2549 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
2550 auth_info.auth_blob = auth_blob
2554 update_time = samba.current_unix_time()
2555 incoming_blob = generate_AuthInOutBlob(incoming_secret, update_time)
2556 outgoing_blob = generate_AuthInOutBlob(outgoing_secret, update_time)
2558 local_tdo_handle = None
2559 remote_tdo_handle = None
2561 local_auth_info = generate_AuthInfoInternal(local_lsa.session_key,
2562 incoming=incoming_blob,
2563 outgoing=outgoing_blob)
2564 if remote_trust_info:
2565 remote_auth_info = generate_AuthInfoInternal(remote_lsa.session_key,
2566 incoming=outgoing_blob,
2567 outgoing=incoming_blob)
2570 if remote_trust_info:
2571 self.outf.write("Creating remote TDO.\n")
2572 current_request = {"location": "remote", "name": "CreateTrustedDomainEx2"}
2573 remote_tdo_handle = \
2574 remote_lsa.CreateTrustedDomainEx2(remote_policy,
2577 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2578 self.outf.write("Remote TDO created.\n")
2580 self.outf.write("Setting supported encryption types on remote TDO.\n")
2581 current_request = {"location": "remote", "name": "SetInformationTrustedDomain"}
2582 remote_lsa.SetInformationTrustedDomain(remote_tdo_handle,
2583 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2586 self.outf.write("Creating local TDO.\n")
2587 current_request = {"location": "local", "name": "CreateTrustedDomainEx2"}
2588 local_tdo_handle = local_lsa.CreateTrustedDomainEx2(local_policy,
2591 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2592 self.outf.write("Local TDO created\n")
2594 self.outf.write("Setting supported encryption types on local TDO.\n")
2595 current_request = {"location": "local", "name": "SetInformationTrustedDomain"}
2596 local_lsa.SetInformationTrustedDomain(local_tdo_handle,
2597 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2599 except RuntimeError as error:
2600 self.outf.write("Error: %s failed %sly - cleaning up\n" % (
2601 current_request['name'], current_request['location']))
2602 if remote_tdo_handle:
2603 self.outf.write("Deleting remote TDO.\n")
2604 remote_lsa.DeleteObject(remote_tdo_handle)
2605 remote_tdo_handle = None
2606 if local_tdo_handle:
2607 self.outf.write("Deleting local TDO.\n")
2608 local_lsa.DeleteObject(local_tdo_handle)
2609 local_tdo_handle = None
2610 if current_request['location'] == "remote":
2611 raise self.RemoteRuntimeError(self, error, "%s" % (
2612 current_request['name']))
2613 raise self.LocalRuntimeError(self, error, "%s" % (
2614 current_request['name']))
2617 if local_trust_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2618 self.outf.write("Setup local forest trust information...\n")
2620 # get all information about the remote trust
2621 # this triggers netr_GetForestTrustInformation to the remote domain
2622 # and lsaRSetForestTrustInformation() locally, but new top level
2623 # names are disabled by default.
2624 local_forest_info = \
2625 local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
2626 remote_lsa_info.dns_domain.string,
2627 netlogon.DS_GFTI_UPDATE_TDO)
2628 except RuntimeError as error:
2629 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2632 # here we try to enable all top level names
2633 local_forest_collision = \
2634 local_lsa.lsaRSetForestTrustInformation(local_policy,
2635 remote_lsa_info.dns_domain,
2636 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2639 except RuntimeError as error:
2640 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2642 self.write_forest_trust_info(local_forest_info,
2643 tln=remote_lsa_info.dns_domain.string,
2644 collisions=local_forest_collision)
2646 if remote_trust_info:
2647 self.outf.write("Setup remote forest trust information...\n")
2649 # get all information about the local trust (from the perspective of the remote domain)
2650 # this triggers netr_GetForestTrustInformation to our domain.
2651 # and lsaRSetForestTrustInformation() remotely, but new top level
2652 # names are disabled by default.
2653 remote_forest_info = \
2654 remote_netlogon.netr_DsRGetForestTrustInformation(remote_netlogon_dc_unc,
2655 local_lsa_info.dns_domain.string,
2656 netlogon.DS_GFTI_UPDATE_TDO)
2657 except RuntimeError as error:
2658 raise self.RemoteRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2661 # here we try to enable all top level names
2662 remote_forest_collision = \
2663 remote_lsa.lsaRSetForestTrustInformation(remote_policy,
2664 local_lsa_info.dns_domain,
2665 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2668 except RuntimeError as error:
2669 raise self.RemoteRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2671 self.write_forest_trust_info(remote_forest_info,
2672 tln=local_lsa_info.dns_domain.string,
2673 collisions=remote_forest_collision)
2675 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2676 self.outf.write("Validating outgoing trust...\n")
2678 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_netlogon_info.dc_unc,
2679 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2681 remote_lsa_info.dns_domain.string)
2682 except RuntimeError as error:
2683 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2685 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2686 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2688 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2689 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2690 local_trust_verify.trusted_dc_name,
2691 local_trust_verify.tc_connection_status[1],
2692 local_trust_verify.pdc_connection_status[1])
2694 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2695 local_trust_verify.trusted_dc_name,
2696 local_trust_verify.tc_connection_status[1],
2697 local_trust_verify.pdc_connection_status[1])
2699 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2700 raise CommandError(local_validation)
2702 self.outf.write("OK: %s\n" % local_validation)
2704 if remote_trust_info:
2705 if remote_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2706 self.outf.write("Validating incoming trust...\n")
2708 remote_trust_verify = \
2709 remote_netlogon.netr_LogonControl2Ex(remote_netlogon_dc_unc,
2710 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2712 local_lsa_info.dns_domain.string)
2713 except RuntimeError as error:
2714 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2716 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2717 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2719 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2720 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2721 remote_trust_verify.trusted_dc_name,
2722 remote_trust_verify.tc_connection_status[1],
2723 remote_trust_verify.pdc_connection_status[1])
2725 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2726 remote_trust_verify.trusted_dc_name,
2727 remote_trust_verify.tc_connection_status[1],
2728 remote_trust_verify.pdc_connection_status[1])
2730 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
2731 raise CommandError(remote_validation)
2733 self.outf.write("OK: %s\n" % remote_validation)
2735 if remote_tdo_handle is not None:
2737 remote_lsa.Close(remote_tdo_handle)
2738 except RuntimeError as error:
2740 remote_tdo_handle = None
2741 if local_tdo_handle is not None:
2743 local_lsa.Close(local_tdo_handle)
2744 except RuntimeError as error:
2746 local_tdo_handle = None
2748 self.outf.write("Success.\n")
2752 class cmd_domain_trust_delete(DomainTrustCommand):
2753 """Delete a domain trust."""
2755 synopsis = "%prog DOMAIN [options]"
2757 takes_optiongroups = {
2758 "sambaopts": options.SambaOptions,
2759 "versionopts": options.VersionOptions,
2760 "credopts": options.CredentialsOptions,
2761 "localdcopts": LocalDCCredentialsOptions,
2765 Option("--delete-location", type="choice", metavar="LOCATION",
2766 choices=["local", "both"],
2767 help="Where to delete the trusted domain object: 'local' or 'both'.",
2768 dest='delete_location',
2772 takes_args = ["domain"]
2774 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2775 delete_location=None):
2777 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2778 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2779 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2781 if delete_location == "local":
2782 remote_policy_access = None
2784 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2785 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2786 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2788 local_server = self.setup_local_server(sambaopts, localdcopts)
2790 local_lsa = self.new_local_lsa_connection()
2791 except RuntimeError as error:
2792 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2795 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2796 except RuntimeError as error:
2797 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2799 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2800 local_lsa_info.name.string,
2801 local_lsa_info.dns_domain.string,
2802 local_lsa_info.sid))
2804 local_tdo_info = None
2805 local_tdo_handle = None
2806 remote_tdo_info = None
2807 remote_tdo_handle = None
2809 lsaString = lsa.String()
2811 lsaString.string = domain
2812 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2813 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2814 except NTSTATUSError as error:
2815 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2816 raise CommandError("Failed to find trust for domain '%s'" % domain)
2817 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2819 if remote_policy_access is not None:
2821 remote_server = self.setup_remote_server(credopts, domain)
2822 except RuntimeError as error:
2823 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2826 remote_lsa = self.new_remote_lsa_connection()
2827 except RuntimeError as error:
2828 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2831 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2832 except RuntimeError as error:
2833 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2835 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2836 remote_lsa_info.name.string,
2837 remote_lsa_info.dns_domain.string,
2838 remote_lsa_info.sid))
2840 if remote_lsa_info.sid != local_tdo_info.sid or \
2841 remote_lsa_info.name.string != local_tdo_info.netbios_name.string or \
2842 remote_lsa_info.dns_domain.string != local_tdo_info.domain_name.string:
2843 raise CommandError("LocalTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2844 local_tdo_info.netbios_name.string,
2845 local_tdo_info.domain_name.string,
2846 local_tdo_info.sid))
2849 lsaString.string = local_lsa_info.dns_domain.string
2851 remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2853 lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2854 except NTSTATUSError as error:
2855 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2856 raise self.RemoteRuntimeError(self, error, "QueryTrustedDomainInfoByName(%s)" % (
2860 if remote_tdo_info is not None:
2861 if local_lsa_info.sid != remote_tdo_info.sid or \
2862 local_lsa_info.name.string != remote_tdo_info.netbios_name.string or \
2863 local_lsa_info.dns_domain.string != remote_tdo_info.domain_name.string:
2864 raise CommandError("RemoteTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2865 remote_tdo_info.netbios_name.string,
2866 remote_tdo_info.domain_name.string,
2867 remote_tdo_info.sid))
2869 if local_tdo_info is not None:
2871 lsaString.string = local_tdo_info.domain_name.string
2872 local_tdo_handle = \
2873 local_lsa.OpenTrustedDomainByName(local_policy,
2875 security.SEC_STD_DELETE)
2876 except RuntimeError as error:
2877 raise self.LocalRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2880 local_lsa.DeleteObject(local_tdo_handle)
2881 local_tdo_handle = None
2883 if remote_tdo_info is not None:
2885 lsaString.string = remote_tdo_info.domain_name.string
2886 remote_tdo_handle = \
2887 remote_lsa.OpenTrustedDomainByName(remote_policy,
2889 security.SEC_STD_DELETE)
2890 except RuntimeError as error:
2891 raise self.RemoteRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2894 if remote_tdo_handle is not None:
2896 remote_lsa.DeleteObject(remote_tdo_handle)
2897 remote_tdo_handle = None
2898 self.outf.write("RemoteTDO deleted.\n")
2899 except RuntimeError as error:
2900 self.outf.write("%s\n" % self.RemoteRuntimeError(self, error, "DeleteObject() failed"))
2902 if local_tdo_handle is not None:
2904 local_lsa.DeleteObject(local_tdo_handle)
2905 local_tdo_handle = None
2906 self.outf.write("LocalTDO deleted.\n")
2907 except RuntimeError as error:
2908 self.outf.write("%s\n" % self.LocalRuntimeError(self, error, "DeleteObject() failed"))
2913 class cmd_domain_trust_validate(DomainTrustCommand):
2914 """Validate a domain trust."""
2916 synopsis = "%prog DOMAIN [options]"
2918 takes_optiongroups = {
2919 "sambaopts": options.SambaOptions,
2920 "versionopts": options.VersionOptions,
2921 "credopts": options.CredentialsOptions,
2922 "localdcopts": LocalDCCredentialsOptions,
2926 Option("--validate-location", type="choice", metavar="LOCATION",
2927 choices=["local", "both"],
2928 help="Where to validate the trusted domain object: 'local' or 'both'.",
2929 dest='validate_location',
2933 takes_args = ["domain"]
2935 def run(self, domain, sambaopts=None, versionopts=None, credopts=None, localdcopts=None,
2936 validate_location=None):
2938 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2940 local_server = self.setup_local_server(sambaopts, localdcopts)
2942 local_lsa = self.new_local_lsa_connection()
2943 except RuntimeError as error:
2944 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2947 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2948 except RuntimeError as error:
2949 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2951 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2952 local_lsa_info.name.string,
2953 local_lsa_info.dns_domain.string,
2954 local_lsa_info.sid))
2957 lsaString = lsa.String()
2958 lsaString.string = domain
2960 local_lsa.QueryTrustedDomainInfoByName(local_policy,
2962 lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2963 except NTSTATUSError as error:
2964 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2965 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2967 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
2969 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
2970 local_tdo_info.netbios_name.string,
2971 local_tdo_info.domain_name.string,
2972 local_tdo_info.sid))
2975 local_netlogon = self.new_local_netlogon_connection()
2976 except RuntimeError as error:
2977 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2980 local_trust_verify = \
2981 local_netlogon.netr_LogonControl2Ex(local_server,
2982 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2984 local_tdo_info.domain_name.string)
2985 except RuntimeError as error:
2986 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2988 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2989 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2991 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2992 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2993 local_trust_verify.trusted_dc_name,
2994 local_trust_verify.tc_connection_status[1],
2995 local_trust_verify.pdc_connection_status[1])
2997 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2998 local_trust_verify.trusted_dc_name,
2999 local_trust_verify.tc_connection_status[1],
3000 local_trust_verify.pdc_connection_status[1])
3002 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
3003 raise CommandError(local_validation)
3005 self.outf.write("OK: %s\n" % local_validation)
3008 server = local_trust_verify.trusted_dc_name.replace('\\', '')
3009 domain_and_server = "%s\\%s" % (local_tdo_info.domain_name.string, server)
3010 local_trust_rediscover = \
3011 local_netlogon.netr_LogonControl2Ex(local_server,
3012 netlogon.NETLOGON_CONTROL_REDISCOVER,
3015 except RuntimeError as error:
3016 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
3018 local_conn_status = self._uint32(local_trust_rediscover.tc_connection_status[0])
3019 local_rediscover = "LocalRediscover: DC[%s] CONNECTION[%s]" % (
3020 local_trust_rediscover.trusted_dc_name,
3021 local_trust_rediscover.tc_connection_status[1])
3023 if local_conn_status != werror.WERR_SUCCESS:
3024 raise CommandError(local_rediscover)
3026 self.outf.write("OK: %s\n" % local_rediscover)
3028 if validate_location != "local":
3030 remote_server = self.setup_remote_server(credopts, domain, require_pdc=False)
3031 except RuntimeError as error:
3032 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
3035 remote_netlogon = self.new_remote_netlogon_connection()
3036 except RuntimeError as error:
3037 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
3040 remote_trust_verify = \
3041 remote_netlogon.netr_LogonControl2Ex(remote_server,
3042 netlogon.NETLOGON_CONTROL_TC_VERIFY,
3044 local_lsa_info.dns_domain.string)
3045 except RuntimeError as error:
3046 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
3048 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
3049 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
3051 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
3052 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
3053 remote_trust_verify.trusted_dc_name,
3054 remote_trust_verify.tc_connection_status[1],
3055 remote_trust_verify.pdc_connection_status[1])
3057 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
3058 remote_trust_verify.trusted_dc_name,
3059 remote_trust_verify.tc_connection_status[1],
3060 remote_trust_verify.pdc_connection_status[1])
3062 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
3063 raise CommandError(remote_validation)
3065 self.outf.write("OK: %s\n" % remote_validation)
3068 server = remote_trust_verify.trusted_dc_name.replace('\\', '')
3069 domain_and_server = "%s\\%s" % (local_lsa_info.dns_domain.string, server)
3070 remote_trust_rediscover = \
3071 remote_netlogon.netr_LogonControl2Ex(remote_server,
3072 netlogon.NETLOGON_CONTROL_REDISCOVER,
3075 except RuntimeError as error:
3076 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
3078 remote_conn_status = self._uint32(remote_trust_rediscover.tc_connection_status[0])
3080 remote_rediscover = "RemoteRediscover: DC[%s] CONNECTION[%s]" % (
3081 remote_trust_rediscover.trusted_dc_name,
3082 remote_trust_rediscover.tc_connection_status[1])
3084 if remote_conn_status != werror.WERR_SUCCESS:
3085 raise CommandError(remote_rediscover)
3087 self.outf.write("OK: %s\n" % remote_rediscover)
3092 class cmd_domain_trust_namespaces(DomainTrustCommand):
3093 """Manage forest trust namespaces."""
3095 synopsis = "%prog [DOMAIN] [options]"
3097 takes_optiongroups = {
3098 "sambaopts": options.SambaOptions,
3099 "versionopts": options.VersionOptions,
3100 "localdcopts": LocalDCCredentialsOptions,
3104 Option("--refresh", type="choice", metavar="check|store",
3105 choices=["check", "store", None],
3106 help="List and maybe store refreshed forest trust information: 'check' or 'store'.",
3109 Option("--enable-all", action="store_true",
3110 help="Try to update disabled entries, not allowed with --refresh=check.",
3113 Option("--enable-tln", action="append", metavar='DNSDOMAIN',
3114 help="Enable a top level name entry. Can be specified multiple times.",
3117 Option("--disable-tln", action="append", metavar='DNSDOMAIN',
3118 help="Disable a top level name entry. Can be specified multiple times.",
3121 Option("--add-tln-ex", action="append", metavar='DNSDOMAIN',
3122 help="Add a top level exclusion entry. Can be specified multiple times.",
3125 Option("--delete-tln-ex", action="append", metavar='DNSDOMAIN',
3126 help="Delete a top level exclusion entry. Can be specified multiple times.",
3127 dest='delete_tln_ex',
3129 Option("--enable-nb", action="append", metavar='NETBIOSDOMAIN',
3130 help="Enable a netbios name in a domain entry. Can be specified multiple times.",
3133 Option("--disable-nb", action="append", metavar='NETBIOSDOMAIN',
3134 help="Disable a netbios name in a domain entry. Can be specified multiple times.",
3137 Option("--enable-sid", action="append", metavar='DOMAINSID',
3138 help="Enable a SID in a domain entry. Can be specified multiple times.",
3139 dest='enable_sid_str',
3141 Option("--disable-sid", action="append", metavar='DOMAINSID',
3142 help="Disable a SID in a domain entry. Can be specified multiple times.",
3143 dest='disable_sid_str',
3145 Option("--add-upn-suffix", action="append", metavar='DNSDOMAIN',
3146 help="Add a new uPNSuffixes attribute for the local forest. Can be specified multiple times.",
3149 Option("--delete-upn-suffix", action="append", metavar='DNSDOMAIN',
3150 help="Delete an existing uPNSuffixes attribute of the local forest. Can be specified multiple times.",
3153 Option("--add-spn-suffix", action="append", metavar='DNSDOMAIN',
3154 help="Add a new msDS-SPNSuffixes attribute for the local forest. Can be specified multiple times.",
3157 Option("--delete-spn-suffix", action="append", metavar='DNSDOMAIN',
3158 help="Delete an existing msDS-SPNSuffixes attribute of the local forest. Can be specified multiple times.",
3163 takes_args = ["domain?"]
3165 def run(self, domain=None, sambaopts=None, localdcopts=None, versionopts=None,
3166 refresh=None, enable_all=False,
3167 enable_tln=[], disable_tln=[], add_tln_ex=[], delete_tln_ex=[],
3168 enable_sid_str=[], disable_sid_str=[], enable_nb=[], disable_nb=[],
3169 add_upn=[], delete_upn=[], add_spn=[], delete_spn=[]):
3171 require_update = False
3174 if refresh == "store":
3175 raise CommandError("--refresh=%s not allowed without DOMAIN" % refresh)
3178 raise CommandError("--enable-all not allowed without DOMAIN")
3180 if len(enable_tln) > 0:
3181 raise CommandError("--enable-tln not allowed without DOMAIN")
3182 if len(disable_tln) > 0:
3183 raise CommandError("--disable-tln not allowed without DOMAIN")
3185 if len(add_tln_ex) > 0:
3186 raise CommandError("--add-tln-ex not allowed without DOMAIN")
3187 if len(delete_tln_ex) > 0:
3188 raise CommandError("--delete-tln-ex not allowed without DOMAIN")
3190 if len(enable_nb) > 0:
3191 raise CommandError("--enable-nb not allowed without DOMAIN")
3192 if len(disable_nb) > 0:
3193 raise CommandError("--disable-nb not allowed without DOMAIN")
3195 if len(enable_sid_str) > 0:
3196 raise CommandError("--enable-sid not allowed without DOMAIN")
3197 if len(disable_sid_str) > 0:
3198 raise CommandError("--disable-sid not allowed without DOMAIN")
3200 if len(add_upn) > 0:
3202 if not n.startswith("*."):
3204 raise CommandError("value[%s] specified for --add-upn-suffix should not include with '*.'" % n)
3205 require_update = True
3206 if len(delete_upn) > 0:
3207 for n in delete_upn:
3208 if not n.startswith("*."):
3210 raise CommandError("value[%s] specified for --delete-upn-suffix should not include with '*.'" % n)
3211 require_update = True
3213 for d in delete_upn:
3214 if a.lower() != d.lower():
3216 raise CommandError("value[%s] specified for --add-upn-suffix and --delete-upn-suffix" % a)
3218 if len(add_spn) > 0:
3220 if not n.startswith("*."):
3222 raise CommandError("value[%s] specified for --add-spn-suffix should not include with '*.'" % n)
3223 require_update = True
3224 if len(delete_spn) > 0:
3225 for n in delete_spn:
3226 if not n.startswith("*."):
3228 raise CommandError("value[%s] specified for --delete-spn-suffix should not include with '*.'" % n)
3229 require_update = True
3231 for d in delete_spn:
3232 if a.lower() != d.lower():
3234 raise CommandError("value[%s] specified for --add-spn-suffix and --delete-spn-suffix" % a)
3236 if len(add_upn) > 0:
3237 raise CommandError("--add-upn-suffix not allowed together with DOMAIN")
3238 if len(delete_upn) > 0:
3239 raise CommandError("--delete-upn-suffix not allowed together with DOMAIN")
3240 if len(add_spn) > 0:
3241 raise CommandError("--add-spn-suffix not allowed together with DOMAIN")
3242 if len(delete_spn) > 0:
3243 raise CommandError("--delete-spn-suffix not allowed together with DOMAIN")
3245 if refresh is not None:
3246 if refresh == "store":
3247 require_update = True
3249 if enable_all and refresh != "store":
3250 raise CommandError("--enable-all not allowed together with --refresh=%s" % refresh)
3252 if len(enable_tln) > 0:
3253 raise CommandError("--enable-tln not allowed together with --refresh")
3254 if len(disable_tln) > 0:
3255 raise CommandError("--disable-tln not allowed together with --refresh")
3257 if len(add_tln_ex) > 0:
3258 raise CommandError("--add-tln-ex not allowed together with --refresh")
3259 if len(delete_tln_ex) > 0:
3260 raise CommandError("--delete-tln-ex not allowed together with --refresh")
3262 if len(enable_nb) > 0:
3263 raise CommandError("--enable-nb not allowed together with --refresh")
3264 if len(disable_nb) > 0:
3265 raise CommandError("--disable-nb not allowed together with --refresh")
3267 if len(enable_sid_str) > 0:
3268 raise CommandError("--enable-sid not allowed together with --refresh")
3269 if len(disable_sid_str) > 0:
3270 raise CommandError("--disable-sid not allowed together with --refresh")
3273 require_update = True
3275 if len(enable_tln) > 0:
3276 raise CommandError("--enable-tln not allowed together with --enable-all")
3278 if len(enable_nb) > 0:
3279 raise CommandError("--enable-nb not allowed together with --enable-all")
3281 if len(enable_sid_str) > 0:
3282 raise CommandError("--enable-sid not allowed together with --enable-all")
3284 if len(enable_tln) > 0:
3285 require_update = True
3286 if len(disable_tln) > 0:
3287 require_update = True
3288 for e in enable_tln:
3289 for d in disable_tln:
3290 if e.lower() != d.lower():
3292 raise CommandError("value[%s] specified for --enable-tln and --disable-tln" % e)
3294 if len(add_tln_ex) > 0:
3295 for n in add_tln_ex:
3296 if not n.startswith("*."):
3298 raise CommandError("value[%s] specified for --add-tln-ex should not include with '*.'" % n)
3299 require_update = True
3300 if len(delete_tln_ex) > 0:
3301 for n in delete_tln_ex:
3302 if not n.startswith("*."):
3304 raise CommandError("value[%s] specified for --delete-tln-ex should not include with '*.'" % n)
3305 require_update = True
3306 for a in add_tln_ex:
3307 for d in delete_tln_ex:
3308 if a.lower() != d.lower():
3310 raise CommandError("value[%s] specified for --add-tln-ex and --delete-tln-ex" % a)
3312 if len(enable_nb) > 0:
3313 require_update = True
3314 if len(disable_nb) > 0:
3315 require_update = True
3317 for d in disable_nb:
3318 if e.upper() != d.upper():
3320 raise CommandError("value[%s] specified for --enable-nb and --disable-nb" % e)
3323 for s in enable_sid_str:
3325 sid = security.dom_sid(s)
3326 except TypeError as error:
3327 raise CommandError("value[%s] specified for --enable-sid is not a valid SID" % s)
3328 enable_sid.append(sid)
3330 for s in disable_sid_str:
3332 sid = security.dom_sid(s)
3333 except TypeError as error:
3334 raise CommandError("value[%s] specified for --disable-sid is not a valid SID" % s)
3335 disable_sid.append(sid)
3336 if len(enable_sid) > 0:
3337 require_update = True
3338 if len(disable_sid) > 0:
3339 require_update = True
3340 for e in enable_sid:
3341 for d in disable_sid:
3344 raise CommandError("value[%s] specified for --enable-sid and --disable-sid" % e)
3346 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
3348 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
3350 local_server = self.setup_local_server(sambaopts, localdcopts)
3352 local_lsa = self.new_local_lsa_connection()
3353 except RuntimeError as error:
3354 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
3357 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
3358 except RuntimeError as error:
3359 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
3361 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
3362 local_lsa_info.name.string,
3363 local_lsa_info.dns_domain.string,
3364 local_lsa_info.sid))
3368 local_netlogon = self.new_local_netlogon_connection()
3369 except RuntimeError as error:
3370 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3373 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3374 except RuntimeError as error:
3375 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3377 if local_netlogon_info.domain_name != local_netlogon_info.forest_name:
3378 raise CommandError("The local domain [%s] is not the forest root [%s]" % (
3379 local_netlogon_info.domain_name,
3380 local_netlogon_info.forest_name))
3383 # get all information about our own forest
3384 own_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3386 except RuntimeError as error:
3387 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
3388 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3391 if self.check_runtime_error(error, werror.WERR_INVALID_FUNCTION):
3392 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3395 if self.check_runtime_error(error, werror.WERR_NERR_ACFNOTLOADED):
3396 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3399 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3401 self.outf.write("Own forest trust information...\n")
3402 self.write_forest_trust_info(own_forest_info,
3403 tln=local_lsa_info.dns_domain.string)
3406 local_samdb = self.new_local_ldap_connection()
3407 except RuntimeError as error:
3408 raise self.LocalRuntimeError(self, error, "failed to connect to SamDB")
3410 local_partitions_dn = "CN=Partitions,%s" % str(local_samdb.get_config_basedn())
3411 attrs = ['uPNSuffixes', 'msDS-SPNSuffixes']
3413 msgs = local_samdb.search(base=local_partitions_dn,
3414 scope=ldb.SCOPE_BASE,
3415 expression="(objectClass=crossRefContainer)",
3417 stored_msg = msgs[0]
3418 except ldb.LdbError as error:
3419 raise self.LocalLdbError(self, error, "failed to search partition dn")
3421 stored_upn_vals = []
3422 if 'uPNSuffixes' in stored_msg:
3423 stored_upn_vals.extend(stored_msg['uPNSuffixes'])
3425 stored_spn_vals = []
3426 if 'msDS-SPNSuffixes' in stored_msg:
3427 stored_spn_vals.extend(stored_msg['msDS-SPNSuffixes'])
3429 self.outf.write("Stored uPNSuffixes attributes[%d]:\n" % len(stored_upn_vals))
3430 for v in stored_upn_vals:
3431 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3432 self.outf.write("Stored msDS-SPNSuffixes attributes[%d]:\n" % len(stored_spn_vals))
3433 for v in stored_spn_vals:
3434 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3436 if not require_update:
3440 update_upn_vals = []
3441 update_upn_vals.extend(stored_upn_vals)
3444 update_spn_vals = []
3445 update_spn_vals.extend(stored_spn_vals)
3448 for i, v in enumerate(update_upn_vals):
3449 if str(v).lower() == upn.lower():
3450 raise CommandError("Entry already present for "
3451 "value[%s] specified for "
3452 "--add-upn-suffix" % upn)
3453 update_upn_vals.append(upn)
3456 for upn in delete_upn:
3458 for i, v in enumerate(update_upn_vals):
3459 if str(v).lower() != upn.lower():
3464 raise CommandError("Entry not found for value[%s] specified for --delete-upn-suffix" % upn)
3466 update_upn_vals.pop(idx)
3470 for i, v in enumerate(update_spn_vals):
3471 if str(v).lower() == spn.lower():
3472 raise CommandError("Entry already present for "
3473 "value[%s] specified for "
3474 "--add-spn-suffix" % spn)
3475 update_spn_vals.append(spn)
3478 for spn in delete_spn:
3480 for i, v in enumerate(update_spn_vals):
3481 if str(v).lower() != spn.lower():
3486 raise CommandError("Entry not found for value[%s] specified for --delete-spn-suffix" % spn)
3488 update_spn_vals.pop(idx)
3491 self.outf.write("Update uPNSuffixes attributes[%d]:\n" % len(update_upn_vals))
3492 for v in update_upn_vals:
3493 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3494 self.outf.write("Update msDS-SPNSuffixes attributes[%d]:\n" % len(update_spn_vals))
3495 for v in update_spn_vals:
3496 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3498 update_msg = ldb.Message()
3499 update_msg.dn = stored_msg.dn
3502 update_msg['uPNSuffixes'] = ldb.MessageElement(update_upn_vals,
3503 ldb.FLAG_MOD_REPLACE,
3506 update_msg['msDS-SPNSuffixes'] = ldb.MessageElement(update_spn_vals,
3507 ldb.FLAG_MOD_REPLACE,
3510 local_samdb.modify(update_msg)
3511 except ldb.LdbError as error:
3512 raise self.LocalLdbError(self, error, "failed to update partition dn")
3515 stored_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3517 except RuntimeError as error:
3518 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3520 self.outf.write("Stored forest trust information...\n")
3521 self.write_forest_trust_info(stored_forest_info,
3522 tln=local_lsa_info.dns_domain.string)
3526 lsaString = lsa.String()
3527 lsaString.string = domain
3529 local_lsa.QueryTrustedDomainInfoByName(local_policy,
3531 lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
3532 except NTSTATUSError as error:
3533 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
3534 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
3536 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
3538 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
3539 local_tdo_info.netbios_name.string,
3540 local_tdo_info.domain_name.string,
3541 local_tdo_info.sid))
3543 if not local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
3544 raise CommandError("trusted domain object for domain [%s] is not marked as FOREST_TRANSITIVE." % domain)
3546 if refresh is not None:
3548 local_netlogon = self.new_local_netlogon_connection()
3549 except RuntimeError as error:
3550 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3553 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3554 except RuntimeError as error:
3555 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3557 lsa_update_check = 1
3558 if refresh == "store":
3559 netlogon_update_tdo = netlogon.DS_GFTI_UPDATE_TDO
3561 lsa_update_check = 0
3563 netlogon_update_tdo = 0
3566 # get all information about the remote trust
3567 # this triggers netr_GetForestTrustInformation to the remote domain
3568 # and lsaRSetForestTrustInformation() locally, but new top level
3569 # names are disabled by default.
3570 fresh_forest_info = \
3571 local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3572 local_tdo_info.domain_name.string,
3573 netlogon_update_tdo)
3574 except RuntimeError as error:
3575 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3578 fresh_forest_collision = \
3579 local_lsa.lsaRSetForestTrustInformation(local_policy,
3580 local_tdo_info.domain_name,
3581 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3584 except RuntimeError as error:
3585 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3587 self.outf.write("Fresh forest trust information...\n")
3588 self.write_forest_trust_info(fresh_forest_info,
3589 tln=local_tdo_info.domain_name.string,
3590 collisions=fresh_forest_collision)
3592 if refresh == "store":
3594 lsaString = lsa.String()
3595 lsaString.string = local_tdo_info.domain_name.string
3596 stored_forest_info = \
3597 local_lsa.lsaRQueryForestTrustInformation(local_policy,
3599 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3600 except RuntimeError as error:
3601 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3603 self.outf.write("Stored forest trust information...\n")
3604 self.write_forest_trust_info(stored_forest_info,
3605 tln=local_tdo_info.domain_name.string)
3610 # The none --refresh path
3614 lsaString = lsa.String()
3615 lsaString.string = local_tdo_info.domain_name.string
3616 local_forest_info = \
3617 local_lsa.lsaRQueryForestTrustInformation(local_policy,
3619 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3620 except RuntimeError as error:
3621 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3623 self.outf.write("Local forest trust information...\n")
3624 self.write_forest_trust_info(local_forest_info,
3625 tln=local_tdo_info.domain_name.string)
3627 if not require_update:
3631 entries.extend(local_forest_info.entries)
3632 update_forest_info = lsa.ForestTrustInformation()
3633 update_forest_info.count = len(entries)
3634 update_forest_info.entries = entries
3637 for i, r in enumerate(update_forest_info.entries):
3638 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3640 if update_forest_info.entries[i].flags == 0:
3642 update_forest_info.entries[i].time = 0
3643 update_forest_info.entries[i].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3644 for i, r in enumerate(update_forest_info.entries):
3645 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3647 if update_forest_info.entries[i].flags == 0:
3649 update_forest_info.entries[i].time = 0
3650 update_forest_info.entries[i].flags &= ~lsa.LSA_NB_DISABLED_MASK
3651 update_forest_info.entries[i].flags &= ~lsa.LSA_SID_DISABLED_MASK
3653 for tln in enable_tln:
3655 for i, r in enumerate(update_forest_info.entries):
3656 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3658 if r.forest_trust_data.string.lower() != tln.lower():
3663 raise CommandError("Entry not found for value[%s] specified for --enable-tln" % tln)
3664 if not update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_MASK:
3665 raise CommandError("Entry found for value[%s] specified for --enable-tln is already enabled" % tln)
3666 update_forest_info.entries[idx].time = 0
3667 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3669 for tln in disable_tln:
3671 for i, r in enumerate(update_forest_info.entries):
3672 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3674 if r.forest_trust_data.string.lower() != tln.lower():
3679 raise CommandError("Entry not found for value[%s] specified for --disable-tln" % tln)
3680 if update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_ADMIN:
3681 raise CommandError("Entry found for value[%s] specified for --disable-tln is already disabled" % tln)
3682 update_forest_info.entries[idx].time = 0
3683 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3684 update_forest_info.entries[idx].flags |= lsa.LSA_TLN_DISABLED_ADMIN
3686 for tln_ex in add_tln_ex:
3688 for i, r in enumerate(update_forest_info.entries):
3689 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3691 if r.forest_trust_data.string.lower() != tln_ex.lower():
3696 raise CommandError("Entry already present for value[%s] specified for --add-tln-ex" % tln_ex)
3698 tln_dot = ".%s" % tln_ex.lower()
3700 for i, r in enumerate(update_forest_info.entries):
3701 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3703 r_dot = ".%s" % r.forest_trust_data.string.lower()
3704 if tln_dot == r_dot:
3705 raise CommandError("TLN entry present for value[%s] specified for --add-tln-ex" % tln_ex)
3706 if not tln_dot.endswith(r_dot):
3712 raise CommandError("No TLN parent present for value[%s] specified for --add-tln-ex" % tln_ex)
3714 r = lsa.ForestTrustRecord()
3715 r.type = lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX
3718 r.forest_trust_data.string = tln_ex
3721 entries.extend(update_forest_info.entries)
3722 entries.insert(idx + 1, r)
3723 update_forest_info.count = len(entries)
3724 update_forest_info.entries = entries
3726 for tln_ex in delete_tln_ex:
3728 for i, r in enumerate(update_forest_info.entries):
3729 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3731 if r.forest_trust_data.string.lower() != tln_ex.lower():
3736 raise CommandError("Entry not found for value[%s] specified for --delete-tln-ex" % tln_ex)
3739 entries.extend(update_forest_info.entries)
3741 update_forest_info.count = len(entries)
3742 update_forest_info.entries = entries
3744 for nb in enable_nb:
3746 for i, r in enumerate(update_forest_info.entries):
3747 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3749 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3754 raise CommandError("Entry not found for value[%s] specified for --enable-nb" % nb)
3755 if not update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_MASK:
3756 raise CommandError("Entry found for value[%s] specified for --enable-nb is already enabled" % nb)
3757 update_forest_info.entries[idx].time = 0
3758 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3760 for nb in disable_nb:
3762 for i, r in enumerate(update_forest_info.entries):
3763 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3765 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3770 raise CommandError("Entry not found for value[%s] specified for --delete-nb" % nb)
3771 if update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_ADMIN:
3772 raise CommandError("Entry found for value[%s] specified for --disable-nb is already disabled" % nb)
3773 update_forest_info.entries[idx].time = 0
3774 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3775 update_forest_info.entries[idx].flags |= lsa.LSA_NB_DISABLED_ADMIN
3777 for sid in enable_sid:
3779 for i, r in enumerate(update_forest_info.entries):
3780 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3782 if r.forest_trust_data.domain_sid != sid:
3787 raise CommandError("Entry not found for value[%s] specified for --enable-sid" % sid)
3788 if not update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_MASK:
3789 raise CommandError("Entry found for value[%s] specified for --enable-sid is already enabled" % nb)
3790 update_forest_info.entries[idx].time = 0
3791 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3793 for sid in disable_sid:
3795 for i, r in enumerate(update_forest_info.entries):
3796 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3798 if r.forest_trust_data.domain_sid != sid:
3803 raise CommandError("Entry not found for value[%s] specified for --delete-sid" % sid)
3804 if update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_ADMIN:
3805 raise CommandError("Entry found for value[%s] specified for --disable-sid is already disabled" % nb)
3806 update_forest_info.entries[idx].time = 0
3807 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3808 update_forest_info.entries[idx].flags |= lsa.LSA_SID_DISABLED_ADMIN
3811 update_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3812 local_tdo_info.domain_name,
3813 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3814 update_forest_info, 0)
3815 except RuntimeError as error:
3816 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3818 self.outf.write("Updated forest trust information...\n")
3819 self.write_forest_trust_info(update_forest_info,
3820 tln=local_tdo_info.domain_name.string,
3821 collisions=update_forest_collision)
3824 lsaString = lsa.String()
3825 lsaString.string = local_tdo_info.domain_name.string
3826 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3828 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3829 except RuntimeError as error:
3830 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3832 self.outf.write("Stored forest trust information...\n")
3833 self.write_forest_trust_info(stored_forest_info,
3834 tln=local_tdo_info.domain_name.string)
3838 class cmd_domain_tombstones_expunge(Command):
3839 """Expunge tombstones from the database.
3841 This command expunges tombstones from the database."""
3842 synopsis = "%prog NC [NC [...]] [options]"
3845 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3846 metavar="URL", dest="H"),
3847 Option("--current-time",
3848 help="The current time to evaluate the tombstone lifetime from, expressed as YYYY-MM-DD",
3850 Option("--tombstone-lifetime", help="Number of days a tombstone should be preserved for", type=int),
3853 takes_args = ["nc*"]
3855 takes_optiongroups = {
3856 "sambaopts": options.SambaOptions,
3857 "credopts": options.CredentialsOptions,
3858 "versionopts": options.VersionOptions,
3861 def run(self, *ncs, **kwargs):
3862 sambaopts = kwargs.get("sambaopts")
3863 credopts = kwargs.get("credopts")
3865 current_time_string = kwargs.get("current_time")
3866 tombstone_lifetime = kwargs.get("tombstone_lifetime")
3867 lp = sambaopts.get_loadparm()
3868 creds = credopts.get_credentials(lp)
3869 samdb = SamDB(url=H, session_info=system_session(),
3870 credentials=creds, lp=lp)
3872 if current_time_string is not None:
3873 current_time_obj = time.strptime(current_time_string, "%Y-%m-%d")
3874 current_time = int(time.mktime(current_time_obj))
3877 current_time = int(time.time())
3880 res = samdb.search(expression="", base="", scope=ldb.SCOPE_BASE,
3881 attrs=["namingContexts"])
3884 for nc in res[0]["namingContexts"]:
3889 started_transaction = False
3891 samdb.transaction_start()
3892 started_transaction = True
3894 removed_links) = samdb.garbage_collect_tombstones(ncs,
3895 current_time=current_time,
3896 tombstone_lifetime=tombstone_lifetime)
3898 except Exception as err:
3899 if started_transaction:
3900 samdb.transaction_cancel()
3901 raise CommandError("Failed to expunge / garbage collect tombstones", err)
3903 samdb.transaction_commit()
3905 self.outf.write("Removed %d objects and %d links successfully\n"
3906 % (removed_objects, removed_links))
3909 class cmd_domain_trust(SuperCommand):
3910 """Domain and forest trust management."""
3913 subcommands["list"] = cmd_domain_trust_list()
3914 subcommands["show"] = cmd_domain_trust_show()
3915 subcommands["create"] = cmd_domain_trust_create()
3916 subcommands["delete"] = cmd_domain_trust_delete()
3917 subcommands["validate"] = cmd_domain_trust_validate()
3918 subcommands["namespaces"] = cmd_domain_trust_namespaces()
3921 class cmd_domain_tombstones(SuperCommand):
3922 """Domain tombstone and recycled object management."""
3925 subcommands["expunge"] = cmd_domain_tombstones_expunge()
3928 class ldif_schema_update:
3929 """Helper class for applying LDIF schema updates"""
3932 self.is_defunct = False
3933 self.unknown_oid = None
3937 def can_ignore_failure(self, error):
3938 """Checks if we can safely ignore failure to apply an LDIF update"""
3939 (num, errstr) = error.args
3941 # Microsoft has marked objects as defunct that Samba doesn't know about
3942 if num == ldb.ERR_NO_SUCH_OBJECT and self.is_defunct:
3943 print("Defunct object %s doesn't exist, skipping" % self.dn)
3945 elif self.unknown_oid is not None:
3946 print("Skipping unknown OID %s for object %s" % (self.unknown_oid, self.dn))
3951 def apply(self, samdb):
3952 """Applies a single LDIF update to the schema"""
3956 samdb.modify_ldif(self.ldif, controls=['relax:0'])
3957 except ldb.LdbError as e:
3958 if e.args[0] == ldb.ERR_INVALID_ATTRIBUTE_SYNTAX:
3960 # REFRESH after a failed change
3962 # Otherwise the OID-to-attribute mapping in
3963 # _apply_updates_in_file() won't work, because it
3964 # can't lookup the new OID in the schema
3965 samdb.set_schema_update_now()
3967 samdb.modify_ldif(self.ldif, controls=['relax:0'])
3970 except ldb.LdbError as e:
3971 if self.can_ignore_failure(e):
3974 print("Exception: %s" % e)
3975 print("Encountered while trying to apply the following LDIF")
3976 print("----------------------------------------------------")
3977 print("%s" % self.ldif)
3984 class cmd_domain_schema_upgrade(Command):
3985 """Domain schema upgrading"""
3987 synopsis = "%prog [options]"
3989 takes_optiongroups = {
3990 "sambaopts": options.SambaOptions,
3991 "versionopts": options.VersionOptions,
3992 "credopts": options.CredentialsOptions,
3996 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3997 metavar="URL", dest="H"),
3998 Option("-q", "--quiet", help="Be quiet", action="store_true"), # unused
3999 Option("-v", "--verbose", help="Be verbose", action="store_true"),
4000 Option("--schema", type="choice", metavar="SCHEMA",
4001 choices=["2012", "2012_R2"],
4002 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
4004 Option("--ldf-file", type=str, default=None,
4005 help="Just apply the schema updates in the adprep/.LDF file(s) specified"),
4006 Option("--base-dir", type=str, default=None,
4007 help="Location of ldf files Default is ${SETUPDIR}/adprep.")
4010 def _apply_updates_in_file(self, samdb, ldif_file):
4012 Applies a series of updates specified in an .LDIF file. The .LDIF file
4013 is based on the adprep Schema updates provided by Microsoft.
4016 ldif_op = ldif_schema_update()
4018 # parse the file line by line and work out each update operation to apply
4019 for line in ldif_file:
4021 line = line.rstrip()
4023 # the operations in the .LDIF file are separated by blank lines. If
4024 # we hit a blank line, try to apply the update we've parsed so far
4027 # keep going if we haven't parsed anything yet
4028 if ldif_op.ldif == '':
4031 # Apply the individual change
4032 count += ldif_op.apply(samdb)
4034 # start storing the next operation from scratch again
4035 ldif_op = ldif_schema_update()
4038 # replace the placeholder domain name in the .ldif file with the real domain
4039 if line.upper().endswith('DC=X'):
4040 line = line[:-len('DC=X')] + str(samdb.get_default_basedn())
4041 elif line.upper().endswith('CN=X'):
4042 line = line[:-len('CN=X')] + str(samdb.get_default_basedn())
4044 values = line.split(':')
4046 if values[0].lower() == 'dn':
4047 ldif_op.dn = values[1].strip()
4049 # replace the Windows-specific operation with the Samba one
4050 if values[0].lower() == 'changetype':
4051 line = line.lower().replace(': ntdsschemaadd',
4053 line = line.lower().replace(': ntdsschemamodify',
4056 if values[0].lower() in ['rdnattid', 'subclassof',
4057 'systemposssuperiors',
4059 'systemauxiliaryclass']:
4062 # The Microsoft updates contain some OIDs we don't recognize.
4063 # Query the DB to see if we can work out the OID this update is
4064 # referring to. If we find a match, then replace the OID with
4065 # the ldapDisplayname
4067 res = samdb.search(base=samdb.get_schema_basedn(),
4068 expression="(|(attributeId=%s)(governsId=%s))" %
4070 attrs=['ldapDisplayName'])
4073 ldif_op.unknown_oid = value
4075 display_name = str(res[0]['ldapDisplayName'][0])
4076 line = line.replace(value, ' ' + display_name)
4078 # Microsoft has marked objects as defunct that Samba doesn't know about
4079 if values[0].lower() == 'isdefunct' and values[1].strip().lower() == 'true':
4080 ldif_op.is_defunct = True
4082 # Samba has added the showInAdvancedViewOnly attribute to all objects,
4083 # so rather than doing an add, we need to do a replace
4084 if values[0].lower() == 'add' and values[1].strip().lower() == 'showinadvancedviewonly':
4085 line = 'replace: showInAdvancedViewOnly'
4087 # Add the line to the current LDIF operation (including the newline
4088 # we stripped off at the start of the loop)
4089 ldif_op.ldif += line + '\n'
4093 def _apply_update(self, samdb, update_file, base_dir):
4094 """Wrapper function for parsing an LDIF file and applying the updates"""
4096 print("Applying %s updates..." % update_file)
4100 ldif_file = open(os.path.join(base_dir, update_file))
4102 count = self._apply_updates_in_file(samdb, ldif_file)
4108 print("%u changes applied" % count)
4112 def run(self, **kwargs):
4114 from samba.ms_schema_markdown import read_ms_markdown
4115 except ImportError as e:
4116 self.outf.write("Exception in importing markdown: %s" % e)
4117 raise CommandError('Failed to import module markdown')
4118 from samba.schema import Schema
4120 updates_allowed_overridden = False
4121 sambaopts = kwargs.get("sambaopts")
4122 credopts = kwargs.get("credopts")
4123 lp = sambaopts.get_loadparm()
4124 creds = credopts.get_credentials(lp)
4126 target_schema = kwargs.get("schema")
4127 ldf_files = kwargs.get("ldf_file")
4128 base_dir = kwargs.get("base_dir")
4132 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4134 # we're not going to get far if the config doesn't allow schema updates
4135 if lp.get("dsdb:schema update allowed") is None:
4136 lp.set("dsdb:schema update allowed", "yes")
4137 print("Temporarily overriding 'dsdb:schema update allowed' setting")
4138 updates_allowed_overridden = True
4140 own_dn = ldb.Dn(samdb, samdb.get_dsServiceName())
4141 master = get_fsmo_roleowner(samdb, str(samdb.get_schema_basedn()),
4143 if own_dn != master:
4144 raise CommandError("This server is not the schema master.")
4146 # if specific LDIF files were specified, just apply them
4148 schema_updates = ldf_files.split(",")
4152 # work out the version of the target schema we're upgrading to
4153 end = Schema.get_version(target_schema)
4155 # work out the version of the schema we're currently using
4156 res = samdb.search(base=samdb.get_schema_basedn(),
4157 scope=ldb.SCOPE_BASE, attrs=['objectVersion'])
4160 raise CommandError('Could not determine current schema version')
4161 start = int(res[0]['objectVersion'][0]) + 1
4163 diff_dir = setup_path("adprep/WindowsServerDocs")
4164 if base_dir is None:
4165 # Read from the Schema-Updates.md file
4166 temp_folder = tempfile.mkdtemp()
4168 update_file = setup_path("adprep/WindowsServerDocs/Schema-Updates.md")
4171 read_ms_markdown(update_file, temp_folder)
4172 except Exception as e:
4173 print("Exception in markdown parsing: %s" % e)
4174 shutil.rmtree(temp_folder)
4175 raise CommandError('Failed to upgrade schema')
4177 base_dir = temp_folder
4179 for version in range(start, end + 1):
4180 update = 'Sch%d.ldf' % version
4181 schema_updates.append(update)
4183 # Apply patches if we parsed the Schema-Updates.md file
4184 diff = os.path.abspath(os.path.join(diff_dir, update + '.diff'))
4185 if temp_folder and os.path.exists(diff):
4187 p = subprocess.Popen(['patch', update, '-i', diff],
4188 stdout=subprocess.PIPE,
4189 stderr=subprocess.PIPE, cwd=temp_folder)
4190 except (OSError, IOError):
4191 shutil.rmtree(temp_folder)
4192 raise CommandError("Failed to upgrade schema. Check if 'patch' is installed.")
4194 stdout, stderr = p.communicate()
4197 print("Exception in patch: %s\n%s" % (stdout, stderr))
4198 shutil.rmtree(temp_folder)
4199 raise CommandError('Failed to upgrade schema')
4201 print("Patched %s using %s" % (update, diff))
4203 if base_dir is None:
4204 base_dir = setup_path("adprep")
4206 samdb.transaction_start()
4208 error_encountered = False
4211 # Apply the schema updates needed to move to the new schema version
4212 for ldif_file in schema_updates:
4213 count += self._apply_update(samdb, ldif_file, base_dir)
4216 samdb.transaction_commit()
4217 print("Schema successfully updated")
4219 print("No changes applied to schema")
4220 samdb.transaction_cancel()
4221 except Exception as e:
4222 print("Exception: %s" % e)
4223 print("Error encountered, aborting schema upgrade")
4224 samdb.transaction_cancel()
4225 error_encountered = True
4227 if updates_allowed_overridden:
4228 lp.set("dsdb:schema update allowed", "no")
4231 shutil.rmtree(temp_folder)
4233 if error_encountered:
4234 raise CommandError('Failed to upgrade schema')
4237 class cmd_domain_functional_prep(Command):
4238 """Domain functional level preparation"""
4240 synopsis = "%prog [options]"
4242 takes_optiongroups = {
4243 "sambaopts": options.SambaOptions,
4244 "versionopts": options.VersionOptions,
4245 "credopts": options.CredentialsOptions,
4249 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
4250 metavar="URL", dest="H"),
4251 Option("-q", "--quiet", help="Be quiet", action="store_true"),
4252 Option("-v", "--verbose", help="Be verbose", action="store_true"),
4253 Option("--function-level", type="choice", metavar="FUNCTION_LEVEL",
4254 choices=["2008_R2", "2012", "2012_R2"],
4255 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
4257 Option("--forest-prep", action="store_true",
4258 help="Run the forest prep (by default, both the domain and forest prep are run)."),
4259 Option("--domain-prep", action="store_true",
4260 help="Run the domain prep (by default, both the domain and forest prep are run).")
4263 def run(self, **kwargs):
4264 updates_allowed_overridden = False
4265 sambaopts = kwargs.get("sambaopts")
4266 credopts = kwargs.get("credopts")
4267 lp = sambaopts.get_loadparm()
4268 creds = credopts.get_credentials(lp)
4270 target_level = string_version_to_constant[kwargs.get("function_level")]
4271 forest_prep = kwargs.get("forest_prep")
4272 domain_prep = kwargs.get("domain_prep")
4274 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4276 # we're not going to get far if the config doesn't allow schema updates
4277 if lp.get("dsdb:schema update allowed") is None:
4278 lp.set("dsdb:schema update allowed", "yes")
4279 print("Temporarily overriding 'dsdb:schema update allowed' setting")
4280 updates_allowed_overridden = True
4282 if forest_prep is None and domain_prep is None:
4286 own_dn = ldb.Dn(samdb, samdb.get_dsServiceName())
4288 master = get_fsmo_roleowner(samdb, str(samdb.get_schema_basedn()),
4290 if own_dn != master:
4291 raise CommandError("This server is not the schema master.")
4294 domain_dn = samdb.domain_dn()
4295 infrastructure_dn = "CN=Infrastructure," + domain_dn
4296 master = get_fsmo_roleowner(samdb, infrastructure_dn,
4298 if own_dn != master:
4299 raise CommandError("This server is not the infrastructure master.")
4302 samdb.transaction_start()
4303 error_encountered = False
4305 from samba.forest_update import ForestUpdate
4306 forest = ForestUpdate(samdb, fix=True)
4308 forest.check_updates_iterator([53, 79, 80, 81, 82, 83])
4309 forest.check_updates_functional_level(target_level,
4310 DS_DOMAIN_FUNCTION_2008_R2,
4311 update_revision=True)
4313 samdb.transaction_commit()
4314 except Exception as e:
4315 print("Exception: %s" % e)
4316 samdb.transaction_cancel()
4317 error_encountered = True
4320 samdb.transaction_start()
4321 error_encountered = False
4323 from samba.domain_update import DomainUpdate
4325 domain = DomainUpdate(samdb, fix=True)
4326 domain.check_updates_functional_level(target_level,
4327 DS_DOMAIN_FUNCTION_2008,
4328 update_revision=True)
4330 samdb.transaction_commit()
4331 except Exception as e:
4332 print("Exception: %s" % e)
4333 samdb.transaction_cancel()
4334 error_encountered = True
4336 if updates_allowed_overridden:
4337 lp.set("dsdb:schema update allowed", "no")
4339 if error_encountered:
4340 raise CommandError('Failed to perform functional prep')
4343 class cmd_domain(SuperCommand):
4344 """Domain management."""
4347 subcommands["demote"] = cmd_domain_demote()
4348 if cmd_domain_export_keytab is not None:
4349 subcommands["exportkeytab"] = cmd_domain_export_keytab()
4350 subcommands["info"] = cmd_domain_info()
4351 subcommands["provision"] = cmd_domain_provision()
4352 subcommands["join"] = cmd_domain_join()
4353 subcommands["dcpromo"] = cmd_domain_dcpromo()
4354 subcommands["level"] = cmd_domain_level()
4355 subcommands["passwordsettings"] = cmd_domain_passwordsettings()
4356 subcommands["classicupgrade"] = cmd_domain_classicupgrade()
4357 subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()
4358 subcommands["trust"] = cmd_domain_trust()
4359 subcommands["tombstones"] = cmd_domain_tombstones()
4360 subcommands["schemaupgrade"] = cmd_domain_schema_upgrade()
4361 subcommands["functionalprep"] = cmd_domain_functional_prep()
4362 subcommands["backup"] = cmd_domain_backup()