3 # Copyright Matthias Dieter Wallnoefer 2009
4 # Copyright Andrew Kroeger 2009
5 # Copyright Jelmer Vernooij 2007-2012
6 # Copyright Giampaolo Lauria 2011
7 # Copyright Matthieu Patou <mat@matws.net> 2011
8 # Copyright Andrew Bartlett 2008-2015
9 # Copyright Stefan Metzmacher 2012
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import samba.getopt as options
37 from samba import ntstatus
38 from samba import NTSTATUSError
39 from samba import werror
40 from getpass import getpass
41 from samba.net import Net, LIBNET_JOIN_AUTOMATIC
43 from samba.join import join_RODC, join_DC, join_subdomain
44 from samba.auth import system_session
45 from samba.samdb import SamDB
46 from samba.ndr import ndr_unpack, ndr_pack, ndr_print
47 from samba.dcerpc import drsuapi
48 from samba.dcerpc import drsblobs
49 from samba.dcerpc import lsa
50 from samba.dcerpc import netlogon
51 from samba.dcerpc import security
52 from samba.dcerpc import nbt
53 from samba.dcerpc import misc
54 from samba.dcerpc.samr import DOMAIN_PASSWORD_COMPLEX, DOMAIN_PASSWORD_STORE_CLEARTEXT
55 from samba.netcmd import (
61 from samba.netcmd.common import netcmd_get_domain_infos_via_cldap
62 from samba.samba3 import Samba3
63 from samba.samba3 import param as s3param
64 from samba.upgrade import upgrade_from_samba3
65 from samba.drs_utils import (
66 sendDsReplicaSync, drsuapi_connect, drsException,
68 from samba import remove_dc, arcfour_encrypt, string_to_byte_array
69 from samba.ms_markdown import read_ms_markdown
71 from samba.dsdb import (
72 DS_DOMAIN_FUNCTION_2000,
73 DS_DOMAIN_FUNCTION_2003,
74 DS_DOMAIN_FUNCTION_2003_MIXED,
75 DS_DOMAIN_FUNCTION_2008,
76 DS_DOMAIN_FUNCTION_2008_R2,
77 DS_DOMAIN_FUNCTION_2012,
78 DS_DOMAIN_FUNCTION_2012_R2,
79 DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL,
80 DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL,
81 UF_WORKSTATION_TRUST_ACCOUNT,
82 UF_SERVER_TRUST_ACCOUNT,
83 UF_TRUSTED_FOR_DELEGATION,
84 UF_PARTIAL_SECRETS_ACCOUNT
87 from samba.provision import (
90 DEFAULT_MIN_PWD_LENGTH,
94 from samba.provision.common import (
100 def get_testparm_var(testparm, smbconf, varname):
101 errfile = open(os.devnull, 'w')
102 p = subprocess.Popen([testparm, '-s', '-l',
103 '--parameter-name=%s' % varname, smbconf],
104 stdout=subprocess.PIPE, stderr=errfile)
105 (out,err) = p.communicate()
107 lines = out.split('\n')
109 return lines[0].strip()
113 import samba.dckeytab
115 cmd_domain_export_keytab = None
117 class cmd_domain_export_keytab(Command):
118 """Dump Kerberos keys of the domain into a keytab."""
120 synopsis = "%prog <keytab> [options]"
122 takes_optiongroups = {
123 "sambaopts": options.SambaOptions,
124 "credopts": options.CredentialsOptions,
125 "versionopts": options.VersionOptions,
129 Option("--principal", help="extract only this principal", type=str),
132 takes_args = ["keytab"]
134 def run(self, keytab, credopts=None, sambaopts=None, versionopts=None, principal=None):
135 lp = sambaopts.get_loadparm()
137 net.export_keytab(keytab=keytab, principal=principal)
140 class cmd_domain_info(Command):
141 """Print basic info about a domain and the DC passed as parameter."""
143 synopsis = "%prog <ip_address> [options]"
148 takes_optiongroups = {
149 "sambaopts": options.SambaOptions,
150 "credopts": options.CredentialsOptions,
151 "versionopts": options.VersionOptions,
154 takes_args = ["address"]
156 def run(self, address, credopts=None, sambaopts=None, versionopts=None):
157 lp = sambaopts.get_loadparm()
159 res = netcmd_get_domain_infos_via_cldap(lp, None, address)
161 raise CommandError("Invalid IP address '" + address + "'!")
162 self.outf.write("Forest : %s\n" % res.forest)
163 self.outf.write("Domain : %s\n" % res.dns_domain)
164 self.outf.write("Netbios domain : %s\n" % res.domain_name)
165 self.outf.write("DC name : %s\n" % res.pdc_dns_name)
166 self.outf.write("DC netbios name : %s\n" % res.pdc_name)
167 self.outf.write("Server site : %s\n" % res.server_site)
168 self.outf.write("Client site : %s\n" % res.client_site)
171 class cmd_domain_provision(Command):
172 """Provision a domain."""
174 synopsis = "%prog [options]"
176 takes_optiongroups = {
177 "sambaopts": options.SambaOptions,
178 "versionopts": options.VersionOptions,
182 Option("--interactive", help="Ask for names", action="store_true"),
183 Option("--domain", type="string", metavar="DOMAIN",
184 help="NetBIOS domain name to use"),
185 Option("--domain-guid", type="string", metavar="GUID",
186 help="set domainguid (otherwise random)"),
187 Option("--domain-sid", type="string", metavar="SID",
188 help="set domainsid (otherwise random)"),
189 Option("--ntds-guid", type="string", metavar="GUID",
190 help="set NTDS object GUID (otherwise random)"),
191 Option("--invocationid", type="string", metavar="GUID",
192 help="set invocationid (otherwise random)"),
193 Option("--host-name", type="string", metavar="HOSTNAME",
194 help="set hostname"),
195 Option("--host-ip", type="string", metavar="IPADDRESS",
196 help="set IPv4 ipaddress"),
197 Option("--host-ip6", type="string", metavar="IP6ADDRESS",
198 help="set IPv6 ipaddress"),
199 Option("--site", type="string", metavar="SITENAME",
200 help="set site name"),
201 Option("--adminpass", type="string", metavar="PASSWORD",
202 help="choose admin password (otherwise random)"),
203 Option("--krbtgtpass", type="string", metavar="PASSWORD",
204 help="choose krbtgt password (otherwise random)"),
205 Option("--machinepass", type="string", metavar="PASSWORD",
206 help="choose machine password (otherwise random)"),
207 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
208 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
209 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
210 "BIND9_FLATFILE uses bind9 text database to store zone information, "
211 "BIND9_DLZ uses samba4 AD to store zone information, "
212 "NONE skips the DNS setup entirely (not recommended)",
213 default="SAMBA_INTERNAL"),
214 Option("--dnspass", type="string", metavar="PASSWORD",
215 help="choose dns password (otherwise random)"),
216 Option("--ldapadminpass", type="string", metavar="PASSWORD",
217 help="choose password to set between Samba and its LDAP backend (otherwise random)"),
218 Option("--root", type="string", metavar="USERNAME",
219 help="choose 'root' unix username"),
220 Option("--nobody", type="string", metavar="USERNAME",
221 help="choose 'nobody' user"),
222 Option("--users", type="string", metavar="GROUPNAME",
223 help="choose 'users' group"),
224 Option("--quiet", help="Be quiet", action="store_true"),
225 Option("--blank", action="store_true",
226 help="do not add users or groups, just the structure"),
227 Option("--ldap-backend-type", type="choice", metavar="LDAP-BACKEND-TYPE",
228 help="Test initialisation support for unsupported LDAP backend type (fedora-ds or openldap) DO NOT USE",
229 choices=["fedora-ds", "openldap"]),
230 Option("--server-role", type="choice", metavar="ROLE",
231 choices=["domain controller", "dc", "member server", "member", "standalone"],
232 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
233 default="domain controller"),
234 Option("--function-level", type="choice", metavar="FOR-FUN-LEVEL",
235 choices=["2000", "2003", "2008", "2008_R2"],
236 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2008_R2 Native.",
238 Option("--base-schema", type="choice", metavar="BASE-SCHEMA",
239 choices=["2008_R2", "2008_R2_old", "2012", "2012_R2"],
240 help="The base schema files to use. Default is (Windows) 2008_R2.",
242 Option("--next-rid", type="int", metavar="NEXTRID", default=1000,
243 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
244 Option("--partitions-only",
245 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"),
246 Option("--targetdir", type="string", metavar="DIR",
247 help="Set target directory"),
248 Option("--ol-mmr-urls", type="string", metavar="LDAPSERVER",
249 help="List of LDAP-URLS [ ldap://<FQHN>:<PORT>/ (where <PORT> has to be different than 389!) ] separated with comma (\",\") for use with OpenLDAP-MMR (Multi-Master-Replication), e.g.: \"ldap://s4dc1:9000,ldap://s4dc2:9000\""),
250 Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
254 Option("--ldap-dryrun-mode", help="Configure LDAP backend, but do not run any binaries and exit early. Used only for the test environment. DO NOT USE",
255 action="store_true"),
256 Option("--slapd-path", type="string", metavar="SLAPD-PATH",
257 help="Path to slapd for LDAP backend [e.g.:'/usr/local/libexec/slapd']. Required for Setup with LDAP-Backend. OpenLDAP Version >= 2.4.17 should be used."),
258 Option("--ldap-backend-extra-port", type="int", metavar="LDAP-BACKEND-EXTRA-PORT", help="Additional TCP port for LDAP backend server (to use for replication)"),
259 Option("--ldap-backend-forced-uri", type="string", metavar="LDAP-BACKEND-FORCED-URI",
260 help="Force the LDAP backend connection to be to a particular URI. Use this ONLY for 'existing' backends, or when debugging the interaction with the LDAP backend and you need to intercept the LDA"),
261 Option("--ldap-backend-nosync", help="Configure LDAP backend not to call fsync() (for performance in test environments)", action="store_true"),
265 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
266 Option("--use-xattrs", type="choice", choices=["yes","no","auto"],
267 metavar="[yes|no|auto]",
268 help="Define if we should use the native fs capabilities or a tdb file for "
269 "storing attributes likes ntacl when --use-ntvfs is set. "
270 "auto tries to make an inteligent guess based on the user rights and system capabilities",
274 if os.getenv('TEST_LDAP', "no") == "yes":
275 takes_options.extend(openldap_options)
277 if samba.is_ntvfs_fileserver_built():
278 takes_options.extend(ntvfs_options)
282 def run(self, sambaopts=None, versionopts=None,
305 ldap_backend_type=None,
309 partitions_only=None,
316 ldap_backend_nosync=None,
317 ldap_backend_extra_port=None,
318 ldap_backend_forced_uri=None,
319 ldap_dryrun_mode=None,
322 self.logger = self.get_logger("provision")
324 self.logger.setLevel(logging.WARNING)
326 self.logger.setLevel(logging.INFO)
328 lp = sambaopts.get_loadparm()
329 smbconf = lp.configfile
331 if dns_forwarder is not None:
332 suggested_forwarder = dns_forwarder
334 suggested_forwarder = self._get_nameserver_ip()
335 if suggested_forwarder is None:
336 suggested_forwarder = "none"
338 if len(self.raw_argv) == 1:
342 from getpass import getpass
345 def ask(prompt, default=None):
346 if default is not None:
347 print "%s [%s]: " % (prompt, default),
349 print "%s: " % (prompt,),
350 return sys.stdin.readline().rstrip("\n") or default
353 default = socket.getfqdn().split(".", 1)[1].upper()
356 realm = ask("Realm", default)
357 if realm in (None, ""):
358 raise CommandError("No realm set!")
361 default = realm.split(".")[0]
364 domain = ask("Domain", default)
366 raise CommandError("No domain set!")
368 server_role = ask("Server Role (dc, member, standalone)", "dc")
370 dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
371 if dns_backend in (None, ''):
372 raise CommandError("No DNS backend set!")
374 if dns_backend == "SAMBA_INTERNAL":
375 dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
376 if dns_forwarder.lower() in (None, 'none'):
377 suggested_forwarder = None
381 adminpassplain = getpass("Administrator password: ")
382 issue = self._adminpass_issue(adminpassplain)
384 self.errf.write("%s.\n" % issue)
386 adminpassverify = getpass("Retype password: ")
387 if not adminpassplain == adminpassverify:
388 self.errf.write("Sorry, passwords do not match.\n")
390 adminpass = adminpassplain
394 realm = sambaopts._lp.get('realm')
396 raise CommandError("No realm set!")
398 raise CommandError("No domain set!")
401 issue = self._adminpass_issue(adminpass)
403 raise CommandError(issue)
405 self.logger.info("Administrator password will be set randomly!")
407 if function_level == "2000":
408 dom_for_fun_level = DS_DOMAIN_FUNCTION_2000
409 elif function_level == "2003":
410 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
411 elif function_level == "2008":
412 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
413 elif function_level == "2008_R2":
414 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
416 if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
417 dns_forwarder = suggested_forwarder
419 samdb_fill = FILL_FULL
421 samdb_fill = FILL_NT4SYNC
422 elif partitions_only:
423 samdb_fill = FILL_DRS
425 if targetdir is not None:
426 if not os.path.isdir(targetdir):
431 if use_xattrs == "yes":
433 elif use_xattrs == "auto" and use_ntvfs == False:
435 elif use_ntvfs == False:
436 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
437 "Please re-run with --use-xattrs omitted.")
438 elif use_xattrs == "auto" and not lp.get("posix:eadb"):
440 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
442 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
445 samba.ntacls.setntacl(lp, file.name,
446 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
449 self.logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. ")
454 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.")
455 if ldap_backend_type == "existing":
456 if ldap_backend_forced_uri is not None:
457 self.logger.warn("You have specified to use an existing LDAP server as the backend, please make sure an LDAP server is running at %s" % ldap_backend_forced_uri)
459 self.logger.info("You have specified to use an existing LDAP server as the backend, please make sure an LDAP server is running at the default location")
461 if ldap_backend_forced_uri is not None:
462 self.logger.warn("You have specified to use an fixed URI %s for connecting to your LDAP server backend. This is NOT RECOMMENDED, as our default communiation over ldapi:// is more secure and much less")
464 if domain_sid is not None:
465 domain_sid = security.dom_sid(domain_sid)
467 session = system_session()
469 result = provision(self.logger,
470 session, smbconf=smbconf, targetdir=targetdir,
471 samdb_fill=samdb_fill, realm=realm, domain=domain,
472 domainguid=domain_guid, domainsid=domain_sid,
474 hostip=host_ip, hostip6=host_ip6,
475 sitename=site, ntdsguid=ntds_guid,
476 invocationid=invocationid, adminpass=adminpass,
477 krbtgtpass=krbtgtpass, machinepass=machinepass,
478 dns_backend=dns_backend, dns_forwarder=dns_forwarder,
479 dnspass=dnspass, root=root, nobody=nobody,
481 serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
482 backend_type=ldap_backend_type,
483 ldapadminpass=ldapadminpass, ol_mmr_urls=ol_mmr_urls, slapd_path=slapd_path,
484 useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
485 use_rfc2307=use_rfc2307, skip_sysvolacl=False,
486 ldap_backend_extra_port=ldap_backend_extra_port,
487 ldap_backend_forced_uri=ldap_backend_forced_uri,
488 nosync=ldap_backend_nosync, ldap_dryrun_mode=ldap_dryrun_mode,
489 base_schema=base_schema)
491 except ProvisioningError, e:
492 raise CommandError("Provision failed", e)
494 result.report_logger(self.logger)
496 def _get_nameserver_ip(self):
497 """Grab the nameserver IP address from /etc/resolv.conf."""
499 RESOLV_CONF="/etc/resolv.conf"
501 if not path.isfile(RESOLV_CONF):
502 self.logger.warning("Failed to locate %s" % RESOLV_CONF)
507 handle = open(RESOLV_CONF, 'r')
509 if not line.startswith('nameserver'):
511 # we want the last non-space continuous string of the line
512 return line.strip().split()[-1]
514 if handle is not None:
517 self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
519 def _adminpass_issue(self, adminpass):
520 """Returns error string for a bad administrator password,
521 or None if acceptable"""
523 if len(adminpass.decode('utf-8')) < DEFAULT_MIN_PWD_LENGTH:
524 return "Administrator password does not meet the default minimum" \
525 " password length requirement (%d characters)" \
526 % DEFAULT_MIN_PWD_LENGTH
527 elif not samba.check_password_quality(adminpass):
528 return "Administrator password does not meet the default" \
534 class cmd_domain_dcpromo(Command):
535 """Promote an existing domain member or NT4 PDC to an AD DC."""
537 synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
539 takes_optiongroups = {
540 "sambaopts": options.SambaOptions,
541 "versionopts": options.VersionOptions,
542 "credopts": options.CredentialsOptions,
546 Option("--server", help="DC to join", type=str),
547 Option("--site", help="site to join", type=str),
548 Option("--targetdir", help="where to store provision", type=str),
549 Option("--domain-critical-only",
550 help="only replicate critical domain objects",
551 action="store_true"),
552 Option("--machinepass", type=str, metavar="PASSWORD",
553 help="choose machine password (otherwise random)"),
554 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
555 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
556 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
557 "BIND9_DLZ uses samba4 AD to store zone information, "
558 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
559 default="SAMBA_INTERNAL"),
560 Option("--quiet", help="Be quiet", action="store_true"),
561 Option("--verbose", help="Be verbose", action="store_true")
565 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
568 if samba.is_ntvfs_fileserver_built():
569 takes_options.extend(ntvfs_options)
572 takes_args = ["domain", "role?"]
574 def run(self, domain, role=None, sambaopts=None, credopts=None,
575 versionopts=None, server=None, site=None, targetdir=None,
576 domain_critical_only=False, parent_domain=None, machinepass=None,
577 use_ntvfs=False, dns_backend=None,
578 quiet=False, verbose=False):
579 lp = sambaopts.get_loadparm()
580 creds = credopts.get_credentials(lp)
581 net = Net(creds, lp, server=credopts.ipaddress)
583 logger = self.get_logger()
585 logger.setLevel(logging.DEBUG)
587 logger.setLevel(logging.WARNING)
589 logger.setLevel(logging.INFO)
591 netbios_name = lp.get("netbios name")
597 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
598 site=site, netbios_name=netbios_name, targetdir=targetdir,
599 domain_critical_only=domain_critical_only,
600 machinepass=machinepass, use_ntvfs=use_ntvfs,
601 dns_backend=dns_backend,
602 promote_existing=True)
604 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
605 site=site, netbios_name=netbios_name, targetdir=targetdir,
606 domain_critical_only=domain_critical_only,
607 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
608 promote_existing=True)
610 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
613 class cmd_domain_join(Command):
614 """Join domain as either member or backup domain controller."""
616 synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
618 takes_optiongroups = {
619 "sambaopts": options.SambaOptions,
620 "versionopts": options.VersionOptions,
621 "credopts": options.CredentialsOptions,
625 Option("--server", help="DC to join", type=str),
626 Option("--site", help="site to join", type=str),
627 Option("--targetdir", help="where to store provision", type=str),
628 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
629 Option("--domain-critical-only",
630 help="only replicate critical domain objects",
631 action="store_true"),
632 Option("--machinepass", type=str, metavar="PASSWORD",
633 help="choose machine password (otherwise random)"),
634 Option("--adminpass", type="string", metavar="PASSWORD",
635 help="choose adminstrator password when joining as a subdomain (otherwise random)"),
636 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
637 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
638 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
639 "BIND9_DLZ uses samba4 AD to store zone information, "
640 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
641 default="SAMBA_INTERNAL"),
642 Option("--quiet", help="Be quiet", action="store_true"),
643 Option("--verbose", help="Be verbose", action="store_true")
647 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
650 if samba.is_ntvfs_fileserver_built():
651 takes_options.extend(ntvfs_options)
653 takes_args = ["domain", "role?"]
655 def run(self, domain, role=None, sambaopts=None, credopts=None,
656 versionopts=None, server=None, site=None, targetdir=None,
657 domain_critical_only=False, parent_domain=None, machinepass=None,
658 use_ntvfs=False, dns_backend=None, adminpass=None,
659 quiet=False, verbose=False):
660 lp = sambaopts.get_loadparm()
661 creds = credopts.get_credentials(lp)
662 net = Net(creds, lp, server=credopts.ipaddress)
665 site = "Default-First-Site-Name"
667 logger = self.get_logger()
669 logger.setLevel(logging.DEBUG)
671 logger.setLevel(logging.WARNING)
673 logger.setLevel(logging.INFO)
675 netbios_name = lp.get("netbios name")
680 if role is None or role == "MEMBER":
681 (join_password, sid, domain_name) = net.join_member(
682 domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
683 machinepass=machinepass)
685 self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
687 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
688 site=site, netbios_name=netbios_name, targetdir=targetdir,
689 domain_critical_only=domain_critical_only,
690 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend)
692 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
693 site=site, netbios_name=netbios_name, targetdir=targetdir,
694 domain_critical_only=domain_critical_only,
695 machinepass=machinepass, use_ntvfs=use_ntvfs,
696 dns_backend=dns_backend)
697 elif role == "SUBDOMAIN":
699 logger.info("Administrator password will be set randomly!")
701 netbios_domain = lp.get("workgroup")
702 if parent_domain is None:
703 parent_domain = ".".join(domain.split(".")[1:])
704 join_subdomain(logger=logger, server=server, creds=creds, lp=lp, dnsdomain=domain,
705 parent_domain=parent_domain, site=site,
706 netbios_name=netbios_name, netbios_domain=netbios_domain,
707 targetdir=targetdir, machinepass=machinepass,
708 use_ntvfs=use_ntvfs, dns_backend=dns_backend,
711 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role)
714 class cmd_domain_demote(Command):
715 """Demote ourselves from the role of Domain Controller."""
717 synopsis = "%prog [options]"
720 Option("--server", help="writable DC to write demotion changes on", type=str),
721 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
722 metavar="URL", dest="H"),
723 Option("--remove-other-dead-server", help="Dead DC (name or NTDS GUID) "
724 "to remove ALL references to (rather than this DC)", type=str),
725 Option("--quiet", help="Be quiet", action="store_true"),
726 Option("--verbose", help="Be verbose", action="store_true"),
729 takes_optiongroups = {
730 "sambaopts": options.SambaOptions,
731 "credopts": options.CredentialsOptions,
732 "versionopts": options.VersionOptions,
735 def run(self, sambaopts=None, credopts=None,
736 versionopts=None, server=None,
737 remove_other_dead_server=None, H=None,
738 verbose=False, quiet=False):
739 lp = sambaopts.get_loadparm()
740 creds = credopts.get_credentials(lp)
741 net = Net(creds, lp, server=credopts.ipaddress)
743 logger = self.get_logger()
745 logger.setLevel(logging.DEBUG)
747 logger.setLevel(logging.WARNING)
749 logger.setLevel(logging.INFO)
751 if remove_other_dead_server is not None:
752 if server is not None:
753 samdb = SamDB(url="ldap://%s" % server,
754 session_info=system_session(),
755 credentials=creds, lp=lp)
757 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
759 remove_dc.remove_dc(samdb, logger, remove_other_dead_server)
760 except remove_dc.DemoteException as err:
761 raise CommandError("Demote failed: %s" % err)
764 netbios_name = lp.get("netbios name")
765 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
767 res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
769 raise CommandError("Unable to search for servers")
772 raise CommandError("You are the latest server in the domain")
776 if str(e["name"]).lower() != netbios_name.lower():
777 server = e["dnsHostName"]
780 ntds_guid = samdb.get_ntds_GUID()
781 msg = samdb.search(base=str(samdb.get_config_basedn()),
782 scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
784 if len(msg) == 0 or "options" not in msg[0]:
785 raise CommandError("Failed to find options on %s" % ntds_guid)
788 dsa_options = int(str(msg[0]['options']))
790 res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
791 controls=["search_options:1:2"])
794 raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res))
796 self.errf.write("Using %s as partner server for the demotion\n" %
798 (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
800 self.errf.write("Deactivating inbound replication\n")
805 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
806 dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
807 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
811 self.errf.write("Asking partner server %s to synchronize from us\n"
813 for part in (samdb.get_schema_basedn(),
814 samdb.get_config_basedn(),
815 samdb.get_root_basedn()):
816 nc = drsuapi.DsReplicaObjectIdentifier()
819 req1 = drsuapi.DsReplicaSyncRequest1()
820 req1.naming_context = nc;
821 req1.options = drsuapi.DRSUAPI_DRS_WRIT_REP
822 req1.source_dsa_guid = misc.GUID(ntds_guid)
825 drsuapiBind.DsReplicaSync(drsuapi_handle, 1, req1)
826 except RuntimeError as (werr, string):
827 if werr == werror.WERR_DS_DRA_NO_REPLICA:
831 "Error while replicating out last local changes from '%s' for demotion, "
832 "re-enabling inbound replication\n" % part)
833 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
834 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
836 raise CommandError("Error while sending a DsReplicaSync for partition '%s'" % str(part), string)
838 remote_samdb = SamDB(url="ldap://%s" % server,
839 session_info=system_session(),
840 credentials=creds, lp=lp)
842 self.errf.write("Changing userControl and container\n")
843 res = remote_samdb.search(base=str(remote_samdb.domain_dn()),
844 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
845 netbios_name.upper(),
846 attrs=["userAccountControl"])
848 uac = int(str(res[0]["userAccountControl"]))
851 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
853 "Error while demoting, re-enabling inbound replication\n")
854 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
855 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
857 raise CommandError("Error while changing account control", e)
860 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
862 "Error while demoting, re-enabling inbound replication")
863 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
864 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
866 raise CommandError("Unable to find object with samaccountName = %s$"
867 " in the remote dc" % netbios_name.upper())
871 uac &= ~(UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION|UF_PARTIAL_SECRETS_ACCOUNT)
872 uac |= UF_WORKSTATION_TRUST_ACCOUNT
877 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
878 ldb.FLAG_MOD_REPLACE,
879 "userAccountControl")
881 remote_samdb.modify(msg)
883 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
885 "Error while demoting, re-enabling inbound replication")
886 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
887 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
890 raise CommandError("Error while changing account control", e)
892 parent = msg.dn.parent()
893 dc_name = res[0].dn.get_rdn_value()
894 rdn = "CN=%s" % dc_name
896 # Let's move to the Computer container
900 computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.domain_dn()))
901 res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
904 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
905 scope=ldb.SCOPE_ONELEVEL)
906 while(len(res) != 0 and i < 100):
908 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
909 scope=ldb.SCOPE_ONELEVEL)
912 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
914 "Error while demoting, re-enabling inbound replication\n")
915 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
916 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
922 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
923 ldb.FLAG_MOD_REPLACE,
924 "userAccountControl")
926 remote_samdb.modify(msg)
928 raise CommandError("Unable to find a slot for renaming %s,"
929 " all names from %s-1 to %s-%d seemed used" %
930 (str(dc_dn), rdn, rdn, i - 9))
932 newrdn = "%s-%d" % (rdn, i)
935 newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
936 remote_samdb.rename(dc_dn, newdn)
938 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
940 "Error while demoting, re-enabling inbound replication\n")
941 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
942 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
948 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
949 ldb.FLAG_MOD_REPLACE,
950 "userAccountControl")
952 remote_samdb.modify(msg)
953 raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
956 server_dsa_dn = samdb.get_serverName()
957 domain = remote_samdb.get_root_basedn()
960 req1 = drsuapi.DsRemoveDSServerRequest1()
961 req1.server_dn = str(server_dsa_dn)
962 req1.domain_dn = str(domain)
965 drsuapiBind.DsRemoveDSServer(drsuapi_handle, 1, req1)
966 except RuntimeError as (werr, string):
967 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
969 "Error while demoting, re-enabling inbound replication\n")
970 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
971 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
977 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
978 ldb.FLAG_MOD_REPLACE,
979 "userAccountControl")
980 remote_samdb.modify(msg)
981 remote_samdb.rename(newdn, dc_dn)
982 if werr == werror.WERR_DS_DRA_NO_REPLICA:
983 raise CommandError("The DC %s is not present on (already removed from) the remote server: " % server_dsa_dn, e)
985 raise CommandError("Error while sending a removeDsServer of %s: " % server_dsa_dn, e)
987 remove_dc.remove_sysvol_references(remote_samdb, logger, dc_name)
989 # These are objects under the computer account that should be deleted
990 for s in ("CN=Enterprise,CN=NTFRS Subscriptions",
991 "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
992 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
993 "CN=NTFRS Subscriptions"):
995 remote_samdb.delete(ldb.Dn(remote_samdb,
996 "%s,%s" % (s, str(newdn))))
997 except ldb.LdbError, l:
1000 self.errf.write("Demote successful\n")
1003 class cmd_domain_level(Command):
1004 """Raise domain and forest function levels."""
1006 synopsis = "%prog (show|raise <options>) [options]"
1008 takes_optiongroups = {
1009 "sambaopts": options.SambaOptions,
1010 "credopts": options.CredentialsOptions,
1011 "versionopts": options.VersionOptions,
1015 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1016 metavar="URL", dest="H"),
1017 Option("--quiet", help="Be quiet", action="store_true"),
1018 Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1019 help="The forest function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)"),
1020 Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1021 help="The domain function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)")
1024 takes_args = ["subcommand"]
1026 def run(self, subcommand, H=None, forest_level=None, domain_level=None,
1027 quiet=False, credopts=None, sambaopts=None, versionopts=None):
1028 lp = sambaopts.get_loadparm()
1029 creds = credopts.get_credentials(lp, fallback_machine=True)
1031 samdb = SamDB(url=H, session_info=system_session(),
1032 credentials=creds, lp=lp)
1034 domain_dn = samdb.domain_dn()
1036 res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
1037 scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
1038 assert len(res_forest) == 1
1040 res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1041 attrs=["msDS-Behavior-Version", "nTMixedDomain"])
1042 assert len(res_domain) == 1
1044 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
1045 scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
1046 attrs=["msDS-Behavior-Version"])
1047 assert len(res_dc_s) >= 1
1049 # default values, since "msDS-Behavior-Version" does not exist on Windows 2000 AD
1050 level_forest = DS_DOMAIN_FUNCTION_2000
1051 level_domain = DS_DOMAIN_FUNCTION_2000
1053 if "msDS-Behavior-Version" in res_forest[0]:
1054 level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
1055 if "msDS-Behavior-Version" in res_domain[0]:
1056 level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
1057 level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
1060 for msg in res_dc_s:
1061 if "msDS-Behavior-Version" in msg:
1062 if min_level_dc is None or int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
1063 min_level_dc = int(msg["msDS-Behavior-Version"][0])
1065 min_level_dc = DS_DOMAIN_FUNCTION_2000
1066 # well, this is the least
1069 if level_forest < DS_DOMAIN_FUNCTION_2000 or level_domain < DS_DOMAIN_FUNCTION_2000:
1070 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
1071 if min_level_dc < DS_DOMAIN_FUNCTION_2000:
1072 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
1073 if level_forest > level_domain:
1074 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
1075 if level_domain > min_level_dc:
1076 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
1078 if subcommand == "show":
1079 self.message("Domain and forest function level for domain '%s'" % domain_dn)
1080 if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1081 self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1082 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1083 self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1084 if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1085 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)!")
1089 if level_forest == DS_DOMAIN_FUNCTION_2000:
1091 elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
1092 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1093 elif level_forest == DS_DOMAIN_FUNCTION_2003:
1095 elif level_forest == DS_DOMAIN_FUNCTION_2008:
1097 elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
1099 elif level_forest == DS_DOMAIN_FUNCTION_2012:
1101 elif level_forest == DS_DOMAIN_FUNCTION_2012_R2:
1104 outstr = "higher than 2012 R2"
1105 self.message("Forest function level: (Windows) " + outstr)
1107 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1108 outstr = "2000 mixed (NT4 DC support)"
1109 elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
1111 elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
1112 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1113 elif level_domain == DS_DOMAIN_FUNCTION_2003:
1115 elif level_domain == DS_DOMAIN_FUNCTION_2008:
1117 elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
1119 elif level_domain == DS_DOMAIN_FUNCTION_2012:
1121 elif level_domain == DS_DOMAIN_FUNCTION_2012_R2:
1124 outstr = "higher than 2012 R2"
1125 self.message("Domain function level: (Windows) " + outstr)
1127 if min_level_dc == DS_DOMAIN_FUNCTION_2000:
1129 elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
1131 elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
1133 elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
1135 elif min_level_dc == DS_DOMAIN_FUNCTION_2012:
1137 elif min_level_dc == DS_DOMAIN_FUNCTION_2012_R2:
1140 outstr = "higher than 2012 R2"
1141 self.message("Lowest function level of a DC: (Windows) " + outstr)
1143 elif subcommand == "raise":
1146 if domain_level is not None:
1147 if domain_level == "2003":
1148 new_level_domain = DS_DOMAIN_FUNCTION_2003
1149 elif domain_level == "2008":
1150 new_level_domain = DS_DOMAIN_FUNCTION_2008
1151 elif domain_level == "2008_R2":
1152 new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
1153 elif domain_level == "2012":
1154 new_level_domain = DS_DOMAIN_FUNCTION_2012
1155 elif domain_level == "2012_R2":
1156 new_level_domain = DS_DOMAIN_FUNCTION_2012_R2
1158 if new_level_domain <= level_domain and level_domain_mixed == 0:
1159 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
1160 if new_level_domain > min_level_dc:
1161 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
1163 # Deactivate mixed/interim domain support
1164 if level_domain_mixed != 0:
1165 # Directly on the base DN
1167 m.dn = ldb.Dn(samdb, domain_dn)
1168 m["nTMixedDomain"] = ldb.MessageElement("0",
1169 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1173 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
1174 m["nTMixedDomain"] = ldb.MessageElement("0",
1175 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1178 except ldb.LdbError, (enum, emsg):
1179 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1182 # Directly on the base DN
1184 m.dn = ldb.Dn(samdb, domain_dn)
1185 m["msDS-Behavior-Version"]= ldb.MessageElement(
1186 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1187 "msDS-Behavior-Version")
1191 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
1192 + ",CN=Partitions,%s" % samdb.get_config_basedn())
1193 m["msDS-Behavior-Version"]= ldb.MessageElement(
1194 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1195 "msDS-Behavior-Version")
1198 except ldb.LdbError, (enum, emsg):
1199 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1202 level_domain = new_level_domain
1203 msgs.append("Domain function level changed!")
1205 if forest_level is not None:
1206 if forest_level == "2003":
1207 new_level_forest = DS_DOMAIN_FUNCTION_2003
1208 elif forest_level == "2008":
1209 new_level_forest = DS_DOMAIN_FUNCTION_2008
1210 elif forest_level == "2008_R2":
1211 new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
1212 elif forest_level == "2012":
1213 new_level_forest = DS_DOMAIN_FUNCTION_2012
1214 elif forest_level == "2012_R2":
1215 new_level_forest = DS_DOMAIN_FUNCTION_2012_R2
1217 if new_level_forest <= level_forest:
1218 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1219 if new_level_forest > level_domain:
1220 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1223 m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
1224 m["msDS-Behavior-Version"]= ldb.MessageElement(
1225 str(new_level_forest), ldb.FLAG_MOD_REPLACE,
1226 "msDS-Behavior-Version")
1228 msgs.append("Forest function level changed!")
1229 msgs.append("All changes applied successfully!")
1230 self.message("\n".join(msgs))
1232 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
1235 class cmd_domain_passwordsettings(Command):
1236 """Set password settings.
1238 Password complexity, password lockout policy, history length,
1239 minimum password length, the minimum and maximum password age) on
1240 a Samba AD DC server.
1242 Use against a Windows DC is possible, but group policy will override it.
1245 synopsis = "%prog (show|set <options>) [options]"
1247 takes_optiongroups = {
1248 "sambaopts": options.SambaOptions,
1249 "versionopts": options.VersionOptions,
1250 "credopts": options.CredentialsOptions,
1254 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1255 metavar="URL", dest="H"),
1256 Option("--quiet", help="Be quiet", action="store_true"),
1257 Option("--complexity", type="choice", choices=["on","off","default"],
1258 help="The password complexity (on | off | default). Default is 'on'"),
1259 Option("--store-plaintext", type="choice", choices=["on","off","default"],
1260 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1261 Option("--history-length",
1262 help="The password history length (<integer> | default). Default is 24.", type=str),
1263 Option("--min-pwd-length",
1264 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1265 Option("--min-pwd-age",
1266 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1267 Option("--max-pwd-age",
1268 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1269 Option("--account-lockout-duration",
1270 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),
1271 Option("--account-lockout-threshold",
1272 help="The number of bad password attempts allowed before locking out the account (<integer> | default). Default is 0 (never lock out).", type=str),
1273 Option("--reset-account-lockout-after",
1274 help="After this time is elapsed, the recorded number of attempts restarts from zero (<integer> | default). Default is 30.", type=str),
1277 takes_args = ["subcommand"]
1279 def run(self, subcommand, H=None, min_pwd_age=None, max_pwd_age=None,
1280 quiet=False, complexity=None, store_plaintext=None, history_length=None,
1281 min_pwd_length=None, account_lockout_duration=None, account_lockout_threshold=None,
1282 reset_account_lockout_after=None, credopts=None, sambaopts=None,
1284 lp = sambaopts.get_loadparm()
1285 creds = credopts.get_credentials(lp)
1287 samdb = SamDB(url=H, session_info=system_session(),
1288 credentials=creds, lp=lp)
1290 domain_dn = samdb.domain_dn()
1291 res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1292 attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1293 "minPwdAge", "maxPwdAge", "lockoutDuration", "lockoutThreshold",
1294 "lockOutObservationWindow"])
1295 assert(len(res) == 1)
1297 pwd_props = int(res[0]["pwdProperties"][0])
1298 pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
1299 cur_min_pwd_len = int(res[0]["minPwdLength"][0])
1301 cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1302 if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000:
1305 cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1306 cur_account_lockout_threshold = int(res[0]["lockoutThreshold"][0])
1308 if int(res[0]["lockoutDuration"][0]) == -0x8000000000000000:
1309 cur_account_lockout_duration = 0
1311 cur_account_lockout_duration = abs(int(res[0]["lockoutDuration"][0])) / (1e7 * 60)
1312 cur_reset_account_lockout_after = abs(int(res[0]["lockOutObservationWindow"][0])) / (1e7 * 60)
1313 except Exception, e:
1314 raise CommandError("Could not retrieve password properties!", e)
1316 if subcommand == "show":
1317 self.message("Password informations for domain '%s'" % domain_dn)
1319 if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
1320 self.message("Password complexity: on")
1322 self.message("Password complexity: off")
1323 if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
1324 self.message("Store plaintext passwords: on")
1326 self.message("Store plaintext passwords: off")
1327 self.message("Password history length: %d" % pwd_hist_len)
1328 self.message("Minimum password length: %d" % cur_min_pwd_len)
1329 self.message("Minimum password age (days): %d" % cur_min_pwd_age)
1330 self.message("Maximum password age (days): %d" % cur_max_pwd_age)
1331 self.message("Account lockout duration (mins): %d" % cur_account_lockout_duration)
1332 self.message("Account lockout threshold (attempts): %d" % cur_account_lockout_threshold)
1333 self.message("Reset account lockout after (mins): %d" % cur_reset_account_lockout_after)
1334 elif subcommand == "set":
1337 m.dn = ldb.Dn(samdb, domain_dn)
1339 if complexity is not None:
1340 if complexity == "on" or complexity == "default":
1341 pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
1342 msgs.append("Password complexity activated!")
1343 elif complexity == "off":
1344 pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
1345 msgs.append("Password complexity deactivated!")
1347 if store_plaintext is not None:
1348 if store_plaintext == "on" or store_plaintext == "default":
1349 pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1350 msgs.append("Plaintext password storage for changed passwords activated!")
1351 elif store_plaintext == "off":
1352 pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
1353 msgs.append("Plaintext password storage for changed passwords deactivated!")
1355 if complexity is not None or store_plaintext is not None:
1356 m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
1357 ldb.FLAG_MOD_REPLACE, "pwdProperties")
1359 if history_length is not None:
1360 if history_length == "default":
1363 pwd_hist_len = int(history_length)
1365 if pwd_hist_len < 0 or pwd_hist_len > 24:
1366 raise CommandError("Password history length must be in the range of 0 to 24!")
1368 m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
1369 ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
1370 msgs.append("Password history length changed!")
1372 if min_pwd_length is not None:
1373 if min_pwd_length == "default":
1376 min_pwd_len = int(min_pwd_length)
1378 if min_pwd_len < 0 or min_pwd_len > 14:
1379 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1381 m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
1382 ldb.FLAG_MOD_REPLACE, "minPwdLength")
1383 msgs.append("Minimum password length changed!")
1385 if min_pwd_age is not None:
1386 if min_pwd_age == "default":
1389 min_pwd_age = int(min_pwd_age)
1391 if min_pwd_age < 0 or min_pwd_age > 998:
1392 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1395 min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
1397 m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
1398 ldb.FLAG_MOD_REPLACE, "minPwdAge")
1399 msgs.append("Minimum password age changed!")
1401 if max_pwd_age is not None:
1402 if max_pwd_age == "default":
1405 max_pwd_age = int(max_pwd_age)
1407 if max_pwd_age < 0 or max_pwd_age > 999:
1408 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1411 if max_pwd_age == 0:
1412 max_pwd_age_ticks = -0x8000000000000000
1414 max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
1416 m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
1417 ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1418 msgs.append("Maximum password age changed!")
1420 if account_lockout_duration is not None:
1421 if account_lockout_duration == "default":
1422 account_lockout_duration = 30
1424 account_lockout_duration = int(account_lockout_duration)
1426 if account_lockout_duration < 0 or account_lockout_duration > 99999:
1427 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1430 if account_lockout_duration == 0:
1431 account_lockout_duration_ticks = -0x8000000000000000
1433 account_lockout_duration_ticks = -int(account_lockout_duration * (60 * 1e7))
1435 m["lockoutDuration"] = ldb.MessageElement(str(account_lockout_duration_ticks),
1436 ldb.FLAG_MOD_REPLACE, "lockoutDuration")
1437 msgs.append("Account lockout duration changed!")
1439 if account_lockout_threshold is not None:
1440 if account_lockout_threshold == "default":
1441 account_lockout_threshold = 0
1443 account_lockout_threshold = int(account_lockout_threshold)
1445 m["lockoutThreshold"] = ldb.MessageElement(str(account_lockout_threshold),
1446 ldb.FLAG_MOD_REPLACE, "lockoutThreshold")
1447 msgs.append("Account lockout threshold changed!")
1449 if reset_account_lockout_after is not None:
1450 if reset_account_lockout_after == "default":
1451 reset_account_lockout_after = 30
1453 reset_account_lockout_after = int(reset_account_lockout_after)
1455 if reset_account_lockout_after < 0 or reset_account_lockout_after > 99999:
1456 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1459 if reset_account_lockout_after == 0:
1460 reset_account_lockout_after_ticks = -0x8000000000000000
1462 reset_account_lockout_after_ticks = -int(reset_account_lockout_after * (60 * 1e7))
1464 m["lockOutObservationWindow"] = ldb.MessageElement(str(reset_account_lockout_after_ticks),
1465 ldb.FLAG_MOD_REPLACE, "lockOutObservationWindow")
1466 msgs.append("Duration to reset account lockout after changed!")
1468 if max_pwd_age > 0 and min_pwd_age >= max_pwd_age:
1469 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
1472 raise CommandError("You must specify at least one option to set. Try --help")
1474 msgs.append("All changes applied successfully!")
1475 self.message("\n".join(msgs))
1477 raise CommandError("Wrong argument '%s'!" % subcommand)
1480 class cmd_domain_classicupgrade(Command):
1481 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1483 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1484 the testparm utility from your classic installation (with --testparm).
1487 synopsis = "%prog [options] <classic_smb_conf>"
1489 takes_optiongroups = {
1490 "sambaopts": options.SambaOptions,
1491 "versionopts": options.VersionOptions
1495 Option("--dbdir", type="string", metavar="DIR",
1496 help="Path to samba classic DC database directory"),
1497 Option("--testparm", type="string", metavar="PATH",
1498 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1499 Option("--targetdir", type="string", metavar="DIR",
1500 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1501 Option("--quiet", help="Be quiet", action="store_true"),
1502 Option("--verbose", help="Be verbose", action="store_true"),
1503 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
1504 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1505 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1506 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1507 "BIND9_DLZ uses samba4 AD to store zone information, "
1508 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1509 default="SAMBA_INTERNAL")
1513 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
1514 action="store_true"),
1515 Option("--use-xattrs", type="choice", choices=["yes","no","auto"],
1516 metavar="[yes|no|auto]",
1517 help="Define if we should use the native fs capabilities or a tdb file for "
1518 "storing attributes likes ntacl when --use-ntvfs is set. "
1519 "auto tries to make an inteligent guess based on the user rights and system capabilities",
1522 if samba.is_ntvfs_fileserver_built():
1523 takes_options.extend(ntvfs_options)
1525 takes_args = ["smbconf"]
1527 def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
1528 quiet=False, verbose=False, use_xattrs="auto", sambaopts=None, versionopts=None,
1529 dns_backend=None, use_ntvfs=False):
1531 if not os.path.exists(smbconf):
1532 raise CommandError("File %s does not exist" % smbconf)
1534 if testparm and not os.path.exists(testparm):
1535 raise CommandError("Testparm utility %s does not exist" % testparm)
1537 if dbdir and not os.path.exists(dbdir):
1538 raise CommandError("Directory %s does not exist" % dbdir)
1540 if not dbdir and not testparm:
1541 raise CommandError("Please specify either dbdir or testparm")
1543 logger = self.get_logger()
1545 logger.setLevel(logging.DEBUG)
1547 logger.setLevel(logging.WARNING)
1549 logger.setLevel(logging.INFO)
1551 if dbdir and testparm:
1552 logger.warning("both dbdir and testparm specified, ignoring dbdir.")
1555 lp = sambaopts.get_loadparm()
1557 s3conf = s3param.get_context()
1560 s3conf.set("realm", sambaopts.realm)
1562 if targetdir is not None:
1563 if not os.path.isdir(targetdir):
1567 if use_xattrs == "yes":
1569 elif use_xattrs == "auto" and use_ntvfs == False:
1571 elif use_ntvfs == False:
1572 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
1573 "Please re-run with --use-xattrs omitted.")
1574 elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
1576 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
1578 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
1581 samba.ntacls.setntacl(lp, tmpfile.name,
1582 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1585 # FIXME: Don't catch all exceptions here
1586 logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. "
1587 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1591 # Set correct default values from dbdir or testparm
1594 paths["state directory"] = dbdir
1595 paths["private dir"] = dbdir
1596 paths["lock directory"] = dbdir
1597 paths["smb passwd file"] = dbdir + "/smbpasswd"
1599 paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
1600 paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
1601 paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
1602 paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
1603 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1604 # "state directory", instead make use of "lock directory"
1605 if len(paths["state directory"]) == 0:
1606 paths["state directory"] = paths["lock directory"]
1609 s3conf.set(p, paths[p])
1611 # load smb.conf parameters
1612 logger.info("Reading smb.conf")
1613 s3conf.load(smbconf)
1614 samba3 = Samba3(smbconf, s3conf)
1616 logger.info("Provisioning")
1617 upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
1618 useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
1621 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
1622 __doc__ = cmd_domain_classicupgrade.__doc__
1624 # This command is present for backwards compatibility only,
1625 # and should not be shown.
1629 class LocalDCCredentialsOptions(options.CredentialsOptions):
1630 def __init__(self, parser):
1631 options.CredentialsOptions.__init__(self, parser, special_name="local-dc")
1633 class DomainTrustCommand(Command):
1634 """List domain trusts."""
1637 Command.__init__(self)
1638 self.local_lp = None
1640 self.local_server = None
1641 self.local_binding_string = None
1642 self.local_creds = None
1644 self.remote_server = None
1645 self.remote_binding_string = None
1646 self.remote_creds = None
1648 def _uint32(self, v):
1649 return ctypes.c_uint32(v).value
1651 def check_runtime_error(self, runtime, val):
1655 err32 = self._uint32(runtime[0])
1661 class LocalRuntimeError(CommandError):
1662 def __init__(exception_self, self, runtime, message):
1663 err32 = self._uint32(runtime[0])
1665 msg = "LOCAL_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1666 self.local_server, message, err32, errstr)
1667 CommandError.__init__(exception_self, msg)
1669 class RemoteRuntimeError(CommandError):
1670 def __init__(exception_self, self, runtime, message):
1671 err32 = self._uint32(runtime[0])
1673 msg = "REMOTE_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1674 self.remote_server, message, err32, errstr)
1675 CommandError.__init__(exception_self, msg)
1677 class LocalLdbError(CommandError):
1678 def __init__(exception_self, self, ldb_error, message):
1679 errval = ldb_error[0]
1680 errstr = ldb_error[1]
1681 msg = "LOCAL_DC[%s]: %s - ERROR(%d) - %s" % (
1682 self.local_server, message, errval, errstr)
1683 CommandError.__init__(exception_self, msg)
1685 def setup_local_server(self, sambaopts, localdcopts):
1686 if self.local_server is not None:
1687 return self.local_server
1689 lp = sambaopts.get_loadparm()
1691 local_server = localdcopts.ipaddress
1692 if local_server is None:
1693 server_role = lp.server_role()
1694 if server_role != "ROLE_ACTIVE_DIRECTORY_DC":
1695 raise CommandError("Invalid server_role %s" % (server_role))
1696 local_server = lp.get('netbios name')
1697 local_transport = "ncalrpc"
1698 local_binding_options = ""
1699 local_binding_options += ",auth_type=ncalrpc_as_system"
1700 local_ldap_url = None
1703 local_transport = "ncacn_np"
1704 local_binding_options = ""
1705 local_ldap_url = "ldap://%s" % local_server
1706 local_creds = localdcopts.get_credentials(lp)
1710 self.local_server = local_server
1711 self.local_binding_string = "%s:%s[%s]" % (local_transport, local_server, local_binding_options)
1712 self.local_ldap_url = local_ldap_url
1713 self.local_creds = local_creds
1714 return self.local_server
1716 def new_local_lsa_connection(self):
1717 return lsa.lsarpc(self.local_binding_string, self.local_lp, self.local_creds)
1719 def new_local_netlogon_connection(self):
1720 return netlogon.netlogon(self.local_binding_string, self.local_lp, self.local_creds)
1722 def new_local_ldap_connection(self):
1723 return SamDB(url=self.local_ldap_url,
1724 session_info=system_session(),
1725 credentials=self.local_creds,
1728 def setup_remote_server(self, credopts, domain,
1730 require_writable=True):
1733 assert require_writable
1735 if self.remote_server is not None:
1736 return self.remote_server
1738 self.remote_server = "__unknown__remote_server__.%s" % domain
1739 assert self.local_server is not None
1741 remote_creds = credopts.get_credentials(self.local_lp)
1742 remote_server = credopts.ipaddress
1743 remote_binding_options = ""
1745 # TODO: we should also support NT4 domains
1746 # we could use local_netlogon.netr_DsRGetDCNameEx2() with the remote domain name
1747 # and delegate NBT or CLDAP to the local netlogon server
1749 remote_net = Net(remote_creds, self.local_lp, server=remote_server)
1750 remote_flags = nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS
1751 if require_writable:
1752 remote_flags |= nbt.NBT_SERVER_WRITABLE
1754 remote_flags |= nbt.NBT_SERVER_PDC
1755 remote_info = remote_net.finddc(flags=remote_flags, domain=domain, address=remote_server)
1757 raise CommandError("Failed to find a writeable DC for domain '%s'" % domain)
1759 nbt.NBT_SERVER_PDC: "PDC",
1760 nbt.NBT_SERVER_GC: "GC",
1761 nbt.NBT_SERVER_LDAP: "LDAP",
1762 nbt.NBT_SERVER_DS: "DS",
1763 nbt.NBT_SERVER_KDC: "KDC",
1764 nbt.NBT_SERVER_TIMESERV: "TIMESERV",
1765 nbt.NBT_SERVER_CLOSEST: "CLOSEST",
1766 nbt.NBT_SERVER_WRITABLE: "WRITABLE",
1767 nbt.NBT_SERVER_GOOD_TIMESERV: "GOOD_TIMESERV",
1768 nbt.NBT_SERVER_NDNC: "NDNC",
1769 nbt.NBT_SERVER_SELECT_SECRET_DOMAIN_6: "SELECT_SECRET_DOMAIN_6",
1770 nbt.NBT_SERVER_FULL_SECRET_DOMAIN_6: "FULL_SECRET_DOMAIN_6",
1771 nbt.NBT_SERVER_ADS_WEB_SERVICE: "ADS_WEB_SERVICE",
1772 nbt.NBT_SERVER_DS_8: "DS_8",
1773 nbt.NBT_SERVER_HAS_DNS_NAME: "HAS_DNS_NAME",
1774 nbt.NBT_SERVER_IS_DEFAULT_NC: "IS_DEFAULT_NC",
1775 nbt.NBT_SERVER_FOREST_ROOT: "FOREST_ROOT",
1777 server_type_string = self.generic_bitmap_to_string(flag_map,
1778 remote_info.server_type, names_only=True)
1779 self.outf.write("RemoteDC Netbios[%s] DNS[%s] ServerType[%s]\n" % (
1780 remote_info.pdc_name,
1781 remote_info.pdc_dns_name,
1782 server_type_string))
1784 self.remote_server = remote_info.pdc_dns_name
1785 self.remote_binding_string="ncacn_np:%s[%s]" % (self.remote_server, remote_binding_options)
1786 self.remote_creds = remote_creds
1787 return self.remote_server
1789 def new_remote_lsa_connection(self):
1790 return lsa.lsarpc(self.remote_binding_string, self.local_lp, self.remote_creds)
1792 def new_remote_netlogon_connection(self):
1793 return netlogon.netlogon(self.remote_binding_string, self.local_lp, self.remote_creds)
1795 def get_lsa_info(self, conn, policy_access):
1796 objectAttr = lsa.ObjectAttribute()
1797 objectAttr.sec_qos = lsa.QosInfo()
1799 policy = conn.OpenPolicy2(''.decode('utf-8'),
1800 objectAttr, policy_access)
1802 info = conn.QueryInfoPolicy2(policy, lsa.LSA_POLICY_INFO_DNS)
1804 return (policy, info)
1806 def get_netlogon_dc_info(self, conn, server):
1807 info = conn.netr_DsRGetDCNameEx2(server,
1808 None, 0, None, None, None,
1809 netlogon.DS_RETURN_DNS_NAME)
1812 def netr_DomainTrust_to_name(self, t):
1813 if t.trust_type == lsa.LSA_TRUST_TYPE_DOWNLEVEL:
1814 return t.netbios_name
1818 def netr_DomainTrust_to_type(self, a, t):
1820 primary_parent = None
1822 if _t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
1824 if not _t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1825 primary_parent = a[_t.parent_index]
1828 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1829 if t is primary_parent:
1832 if t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1835 parent = a[t.parent_index]
1836 if parent is primary:
1841 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1846 def netr_DomainTrust_to_transitive(self, t):
1847 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1850 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE:
1853 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1858 def netr_DomainTrust_to_direction(self, t):
1859 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND and \
1860 t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1863 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND:
1866 if t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1871 def generic_enum_to_string(self, e_dict, v, names_only=False):
1875 v32 = self._uint32(v)
1876 w = "__unknown__%08X__" % v32
1878 r = "0x%x (%s)" % (v, w)
1881 def generic_bitmap_to_string(self, b_dict, v, names_only=False):
1886 for b in sorted(b_dict.keys()):
1893 c32 = self._uint32(c)
1894 s += ["__unknown_%08X__" % c32]
1899 r = "0x%x (%s)" % (v, w)
1902 def trustType_string(self, v):
1904 lsa.LSA_TRUST_TYPE_DOWNLEVEL : "DOWNLEVEL",
1905 lsa.LSA_TRUST_TYPE_UPLEVEL : "UPLEVEL",
1906 lsa.LSA_TRUST_TYPE_MIT : "MIT",
1907 lsa.LSA_TRUST_TYPE_DCE : "DCE",
1909 return self.generic_enum_to_string(types, v)
1911 def trustDirection_string(self, v):
1913 lsa.LSA_TRUST_DIRECTION_INBOUND |
1914 lsa.LSA_TRUST_DIRECTION_OUTBOUND : "BOTH",
1915 lsa.LSA_TRUST_DIRECTION_INBOUND : "INBOUND",
1916 lsa.LSA_TRUST_DIRECTION_OUTBOUND : "OUTBOUND",
1918 return self.generic_enum_to_string(directions, v)
1920 def trustAttributes_string(self, v):
1922 lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE : "NON_TRANSITIVE",
1923 lsa.LSA_TRUST_ATTRIBUTE_UPLEVEL_ONLY : "UPLEVEL_ONLY",
1924 lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN : "QUARANTINED_DOMAIN",
1925 lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE : "FOREST_TRANSITIVE",
1926 lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION : "CROSS_ORGANIZATION",
1927 lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST : "WITHIN_FOREST",
1928 lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL : "TREAT_AS_EXTERNAL",
1929 lsa.LSA_TRUST_ATTRIBUTE_USES_RC4_ENCRYPTION : "USES_RC4_ENCRYPTION",
1931 return self.generic_bitmap_to_string(attributes, v)
1933 def kerb_EncTypes_string(self, v):
1935 security.KERB_ENCTYPE_DES_CBC_CRC : "DES_CBC_CRC",
1936 security.KERB_ENCTYPE_DES_CBC_MD5 : "DES_CBC_MD5",
1937 security.KERB_ENCTYPE_RC4_HMAC_MD5 : "RC4_HMAC_MD5",
1938 security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96 : "AES128_CTS_HMAC_SHA1_96",
1939 security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96 : "AES256_CTS_HMAC_SHA1_96",
1940 security.KERB_ENCTYPE_FAST_SUPPORTED : "FAST_SUPPORTED",
1941 security.KERB_ENCTYPE_COMPOUND_IDENTITY_SUPPORTED : "COMPOUND_IDENTITY_SUPPORTED",
1942 security.KERB_ENCTYPE_CLAIMS_SUPPORTED : "CLAIMS_SUPPORTED",
1943 security.KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED : "RESOURCE_SID_COMPRESSION_DISABLED",
1945 return self.generic_bitmap_to_string(enctypes, v)
1947 def entry_tln_status(self, e_flags, ):
1949 return "Status[Enabled]"
1952 lsa.LSA_TLN_DISABLED_NEW : "Disabled-New",
1953 lsa.LSA_TLN_DISABLED_ADMIN : "Disabled",
1954 lsa.LSA_TLN_DISABLED_CONFLICT : "Disabled-Conflicting",
1956 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
1958 def entry_dom_status(self, e_flags):
1960 return "Status[Enabled]"
1963 lsa.LSA_SID_DISABLED_ADMIN : "Disabled-SID",
1964 lsa.LSA_SID_DISABLED_CONFLICT : "Disabled-SID-Conflicting",
1965 lsa.LSA_NB_DISABLED_ADMIN : "Disabled-NB",
1966 lsa.LSA_NB_DISABLED_CONFLICT : "Disabled-NB-Conflicting",
1968 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
1970 def write_forest_trust_info(self, fti, tln=None, collisions=None):
1972 tln_string = " TDO[%s]" % tln
1976 self.outf.write("Namespaces[%d]%s:\n" % (
1977 len(fti.entries), tln_string))
1979 for i in xrange(0, len(fti.entries)):
1983 collision_string = ""
1985 if collisions is not None:
1986 for c in collisions.entries:
1990 collision_string = " Collision[%s]" % (c.name.string)
1992 d = e.forest_trust_data
1993 if e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
1994 self.outf.write("TLN: %-32s DNS[*.%s]%s\n" % (
1995 self.entry_tln_status(flags),
1996 d.string, collision_string))
1997 elif e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
1998 self.outf.write("TLN_EX: %-29s DNS[*.%s]\n" % (
2000 elif e.type == lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
2001 self.outf.write("DOM: %-32s DNS[%s] Netbios[%s] SID[%s]%s\n" % (
2002 self.entry_dom_status(flags),
2003 d.dns_domain_name.string,
2004 d.netbios_domain_name.string,
2005 d.domain_sid, collision_string))
2008 class cmd_domain_trust_list(DomainTrustCommand):
2009 """List domain trusts."""
2011 synopsis = "%prog [options]"
2013 takes_optiongroups = {
2014 "sambaopts": options.SambaOptions,
2015 "versionopts": options.VersionOptions,
2016 "localdcopts": LocalDCCredentialsOptions,
2022 def run(self, sambaopts=None, versionopts=None, localdcopts=None):
2024 local_server = self.setup_local_server(sambaopts, localdcopts)
2026 local_netlogon = self.new_local_netlogon_connection()
2027 except RuntimeError as error:
2028 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2031 local_netlogon_trusts = local_netlogon.netr_DsrEnumerateDomainTrusts(local_server,
2032 netlogon.NETR_TRUST_FLAG_IN_FOREST |
2033 netlogon.NETR_TRUST_FLAG_OUTBOUND |
2034 netlogon.NETR_TRUST_FLAG_INBOUND)
2035 except RuntimeError as error:
2036 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
2037 # TODO: we could implement a fallback to lsa.EnumTrustDom()
2038 raise CommandError("LOCAL_DC[%s]: netr_DsrEnumerateDomainTrusts not supported." % (
2040 raise self.LocalRuntimeError(self, error, "netr_DsrEnumerateDomainTrusts failed")
2042 a = local_netlogon_trusts.array
2044 if t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
2046 self.outf.write("%-14s %-15s %-19s %s\n" % (
2047 "Type[%s]" % self.netr_DomainTrust_to_type(a, t),
2048 "Transitive[%s]" % self.netr_DomainTrust_to_transitive(t),
2049 "Direction[%s]" % self.netr_DomainTrust_to_direction(t),
2050 "Name[%s]" % self.netr_DomainTrust_to_name(t)))
2053 class cmd_domain_trust_show(DomainTrustCommand):
2054 """Show trusted domain details."""
2056 synopsis = "%prog NAME [options]"
2058 takes_optiongroups = {
2059 "sambaopts": options.SambaOptions,
2060 "versionopts": options.VersionOptions,
2061 "localdcopts": LocalDCCredentialsOptions,
2067 takes_args = ["domain"]
2069 def run(self, domain, sambaopts=None, versionopts=None, localdcopts=None):
2071 local_server = self.setup_local_server(sambaopts, localdcopts)
2073 local_lsa = self.new_local_lsa_connection()
2074 except RuntimeError as error:
2075 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2078 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2079 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2080 except RuntimeError as error:
2081 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2083 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2084 local_lsa_info.name.string,
2085 local_lsa_info.dns_domain.string,
2086 local_lsa_info.sid))
2088 lsaString = lsa.String()
2089 lsaString.string = domain
2091 local_tdo_full = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2092 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2093 local_tdo_info = local_tdo_full.info_ex
2094 local_tdo_posix = local_tdo_full.posix_offset
2095 except NTSTATUSError as error:
2096 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2097 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2099 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(FULL_INFO) failed")
2102 local_tdo_enctypes = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2103 lsaString, lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES)
2104 except NTSTATUSError as error:
2105 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_PARAMETER):
2107 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_INFO_CLASS):
2110 if error is not None:
2111 raise self.LocalRuntimeError(self, error,
2112 "QueryTrustedDomainInfoByName(SUPPORTED_ENCRYPTION_TYPES) failed")
2114 local_tdo_enctypes = lsa.TrustDomainInfoSupportedEncTypes()
2115 local_tdo_enctypes.enc_types = 0
2118 local_tdo_forest = None
2119 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2120 local_tdo_forest = local_lsa.lsaRQueryForestTrustInformation(local_policy,
2121 lsaString, lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
2122 except RuntimeError as error:
2123 if self.check_runtime_error(error, ntstatus.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE):
2125 if self.check_runtime_error(error, ntstatus.NT_STATUS_NOT_FOUND):
2127 if error is not None:
2128 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation failed")
2130 local_tdo_forest = lsa.ForestTrustInformation()
2131 local_tdo_forest.count = 0
2132 local_tdo_forest.entries = []
2134 self.outf.write("TrusteDomain:\n\n");
2135 self.outf.write("NetbiosName: %s\n" % local_tdo_info.netbios_name.string)
2136 if local_tdo_info.netbios_name.string != local_tdo_info.domain_name.string:
2137 self.outf.write("DnsName: %s\n" % local_tdo_info.domain_name.string)
2138 self.outf.write("SID: %s\n" % local_tdo_info.sid)
2139 self.outf.write("Type: %s\n" % self.trustType_string(local_tdo_info.trust_type))
2140 self.outf.write("Direction: %s\n" % self.trustDirection_string(local_tdo_info.trust_direction))
2141 self.outf.write("Attributes: %s\n" % self.trustAttributes_string(local_tdo_info.trust_attributes))
2142 posix_offset_u32 = ctypes.c_uint32(local_tdo_posix.posix_offset).value
2143 posix_offset_i32 = ctypes.c_int32(local_tdo_posix.posix_offset).value
2144 self.outf.write("PosixOffset: 0x%08X (%d)\n" % (posix_offset_u32, posix_offset_i32))
2145 self.outf.write("kerb_EncTypes: %s\n" % self.kerb_EncTypes_string(local_tdo_enctypes.enc_types))
2147 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2148 self.write_forest_trust_info(local_tdo_forest,
2149 tln=local_tdo_info.domain_name.string)
2153 class cmd_domain_trust_create(DomainTrustCommand):
2154 """Create a domain or forest trust."""
2156 synopsis = "%prog DOMAIN [options]"
2158 takes_optiongroups = {
2159 "sambaopts": options.SambaOptions,
2160 "versionopts": options.VersionOptions,
2161 "credopts": options.CredentialsOptions,
2162 "localdcopts": LocalDCCredentialsOptions,
2166 Option("--type", type="choice", metavar="TYPE",
2167 choices=["external", "forest"],
2168 help="The type of the trust: 'external' or 'forest'.",
2170 default="external"),
2171 Option("--direction", type="choice", metavar="DIRECTION",
2172 choices=["incoming", "outgoing", "both"],
2173 help="The trust direction: 'incoming', 'outgoing' or 'both'.",
2174 dest='trust_direction',
2176 Option("--create-location", type="choice", metavar="LOCATION",
2177 choices=["local", "both"],
2178 help="Where to create the trusted domain object: 'local' or 'both'.",
2179 dest='create_location',
2181 Option("--cross-organisation", action="store_true",
2182 help="The related domains does not belong to the same organisation.",
2183 dest='cross_organisation',
2185 Option("--quarantined", type="choice", metavar="yes|no",
2186 choices=["yes", "no", None],
2187 help="Special SID filtering rules are applied to the trust. "
2188 "With --type=external the default is yes. "
2189 "With --type=forest the default is no.",
2190 dest='quarantined_arg',
2192 Option("--not-transitive", action="store_true",
2193 help="The forest trust is not transitive.",
2194 dest='not_transitive',
2196 Option("--treat-as-external", action="store_true",
2197 help="The treat the forest trust as external.",
2198 dest='treat_as_external',
2200 Option("--no-aes-keys", action="store_false",
2201 help="The trust uses aes kerberos keys.",
2202 dest='use_aes_keys',
2204 Option("--skip-validation", action="store_false",
2205 help="Skip validation of the trust.",
2210 takes_args = ["domain"]
2212 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2213 trust_type=None, trust_direction=None, create_location=None,
2214 cross_organisation=False, quarantined_arg=None,
2215 not_transitive=False, treat_as_external=False,
2216 use_aes_keys=False, validate=True):
2218 lsaString = lsa.String()
2221 if quarantined_arg is None:
2222 if trust_type == 'external':
2224 elif quarantined_arg == 'yes':
2227 if trust_type != 'forest':
2229 raise CommandError("--not-transitive requires --type=forest")
2230 if treat_as_external:
2231 raise CommandError("--treat-as-external requires --type=forest")
2235 enc_types = lsa.TrustDomainInfoSupportedEncTypes()
2236 enc_types.enc_types = security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96
2237 enc_types.enc_types |= security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96
2239 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2240 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2241 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2243 local_trust_info = lsa.TrustDomainInfoInfoEx()
2244 local_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2245 local_trust_info.trust_direction = 0
2246 if trust_direction == "both":
2247 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2248 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2249 elif trust_direction == "incoming":
2250 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2251 elif trust_direction == "outgoing":
2252 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2253 local_trust_info.trust_attributes = 0
2254 if cross_organisation:
2255 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2257 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2258 if trust_type == "forest":
2259 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2261 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2262 if treat_as_external:
2263 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2265 def get_password(name):
2268 if password is not None and password is not '':
2270 password = getpass("New %s Password: " % name)
2271 passwordverify = getpass("Retype %s Password: " % name)
2272 if not password == passwordverify:
2274 self.outf.write("Sorry, passwords do not match.\n")
2276 incoming_secret = None
2277 outgoing_secret = None
2278 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2279 if create_location == "local":
2280 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2281 incoming_password = get_password("Incoming Trust")
2282 incoming_secret = string_to_byte_array(incoming_password.encode('utf-16-le'))
2283 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2284 outgoing_password = get_password("Outgoing Trust")
2285 outgoing_secret = string_to_byte_array(outgoing_password.encode('utf-16-le'))
2287 remote_trust_info = None
2289 # We use 240 random bytes.
2290 # Windows uses 28 or 240 random bytes. I guess it's
2291 # based on the trust type external vs. forest.
2293 # The initial trust password can be up to 512 bytes
2294 # while the versioned passwords used for periodic updates
2295 # can only be up to 498 bytes, as netr_ServerPasswordSet2()
2296 # needs to pass the NL_PASSWORD_VERSION structure within the
2297 # 512 bytes and a 2 bytes confounder is required.
2299 def random_trust_secret(length):
2300 pw = samba.generate_random_machine_password(length/2, length/2)
2301 return string_to_byte_array(pw.encode('utf-16-le'))
2303 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2304 incoming_secret = random_trust_secret(240)
2305 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2306 outgoing_secret = random_trust_secret(240)
2308 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2309 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2311 remote_trust_info = lsa.TrustDomainInfoInfoEx()
2312 remote_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2313 remote_trust_info.trust_direction = 0
2314 if trust_direction == "both":
2315 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2316 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2317 elif trust_direction == "incoming":
2318 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2319 elif trust_direction == "outgoing":
2320 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2321 remote_trust_info.trust_attributes = 0
2322 if cross_organisation:
2323 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2325 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2326 if trust_type == "forest":
2327 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2329 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2330 if treat_as_external:
2331 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2333 local_server = self.setup_local_server(sambaopts, localdcopts)
2335 local_lsa = self.new_local_lsa_connection()
2336 except RuntimeError as error:
2337 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2340 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2341 except RuntimeError as error:
2342 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2344 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2345 local_lsa_info.name.string,
2346 local_lsa_info.dns_domain.string,
2347 local_lsa_info.sid))
2350 remote_server = self.setup_remote_server(credopts, domain)
2351 except RuntimeError as error:
2352 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2355 remote_lsa = self.new_remote_lsa_connection()
2356 except RuntimeError as error:
2357 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2360 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2361 except RuntimeError as error:
2362 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2364 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2365 remote_lsa_info.name.string,
2366 remote_lsa_info.dns_domain.string,
2367 remote_lsa_info.sid))
2369 local_trust_info.domain_name.string = remote_lsa_info.dns_domain.string
2370 local_trust_info.netbios_name.string = remote_lsa_info.name.string
2371 local_trust_info.sid = remote_lsa_info.sid
2373 if remote_trust_info:
2374 remote_trust_info.domain_name.string = local_lsa_info.dns_domain.string
2375 remote_trust_info.netbios_name.string = local_lsa_info.name.string
2376 remote_trust_info.sid = local_lsa_info.sid
2379 lsaString.string = local_trust_info.domain_name.string
2380 local_old_netbios = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2381 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2382 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2383 except NTSTATUSError as error:
2384 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2385 raise self.LocalRuntimeError(self, error,
2386 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2390 lsaString.string = local_trust_info.netbios_name.string
2391 local_old_dns = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2392 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2393 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2394 except NTSTATUSError as error:
2395 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2396 raise self.LocalRuntimeError(self, error,
2397 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2400 if remote_trust_info:
2402 lsaString.string = remote_trust_info.domain_name.string
2403 remote_old_netbios = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2404 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2405 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2406 except NTSTATUSError as error:
2407 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2408 raise self.RemoteRuntimeError(self, error,
2409 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2413 lsaString.string = remote_trust_info.netbios_name.string
2414 remote_old_dns = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2415 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2416 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2417 except NTSTATUSError as error:
2418 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2419 raise self.RemoteRuntimeError(self, error,
2420 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2424 local_netlogon = self.new_local_netlogon_connection()
2425 except RuntimeError as error:
2426 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2429 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
2430 except RuntimeError as error:
2431 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
2433 if remote_trust_info:
2435 remote_netlogon = self.new_remote_netlogon_connection()
2436 except RuntimeError as error:
2437 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2440 remote_netlogon_info = self.get_netlogon_dc_info(remote_netlogon, remote_server)
2441 except RuntimeError as error:
2442 raise self.RemoteRuntimeError(self, error, "failed to get netlogon dc info")
2444 def generate_AuthInOutBlob(secret, update_time):
2446 blob = drsblobs.trustAuthInOutBlob()
2451 clear = drsblobs.AuthInfoClear()
2452 clear.size = len(secret)
2453 clear.password = secret
2455 info = drsblobs.AuthenticationInformation()
2456 info.LastUpdateTime = samba.unix2nttime(update_time)
2457 info.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
2458 info.AuthInfo = clear
2460 array = drsblobs.AuthenticationInformationArray()
2462 array.array = [info]
2464 blob = drsblobs.trustAuthInOutBlob()
2466 blob.current = array
2470 def generate_AuthInfoInternal(session_key, incoming=None, outgoing=None):
2471 confounder = [0] * 512
2472 for i in range(len(confounder)):
2473 confounder[i] = random.randint(0, 255)
2475 trustpass = drsblobs.trustDomainPasswords()
2477 trustpass.confounder = confounder
2478 trustpass.outgoing = outgoing
2479 trustpass.incoming = incoming
2481 trustpass_blob = ndr_pack(trustpass)
2483 encrypted_trustpass = arcfour_encrypt(session_key, trustpass_blob)
2485 auth_blob = lsa.DATA_BUF2()
2486 auth_blob.size = len(encrypted_trustpass)
2487 auth_blob.data = string_to_byte_array(encrypted_trustpass)
2489 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
2490 auth_info.auth_blob = auth_blob
2494 update_time = samba.current_unix_time()
2495 incoming_blob = generate_AuthInOutBlob(incoming_secret, update_time)
2496 outgoing_blob = generate_AuthInOutBlob(outgoing_secret, update_time)
2498 local_tdo_handle = None
2499 remote_tdo_handle = None
2501 local_auth_info = generate_AuthInfoInternal(local_lsa.session_key,
2502 incoming=incoming_blob,
2503 outgoing=outgoing_blob)
2504 if remote_trust_info:
2505 remote_auth_info = generate_AuthInfoInternal(remote_lsa.session_key,
2506 incoming=outgoing_blob,
2507 outgoing=incoming_blob)
2510 if remote_trust_info:
2511 self.outf.write("Creating remote TDO.\n")
2512 current_request = { "location": "remote", "name": "CreateTrustedDomainEx2"}
2513 remote_tdo_handle = remote_lsa.CreateTrustedDomainEx2(remote_policy,
2516 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2517 self.outf.write("Remote TDO created.\n")
2519 self.outf.write("Setting supported encryption types on remote TDO.\n")
2520 current_request = { "location": "remote", "name": "SetInformationTrustedDomain"}
2521 remote_lsa.SetInformationTrustedDomain(remote_tdo_handle,
2522 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2525 self.outf.write("Creating local TDO.\n")
2526 current_request = { "location": "local", "name": "CreateTrustedDomainEx2"}
2527 local_tdo_handle = local_lsa.CreateTrustedDomainEx2(local_policy,
2530 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2531 self.outf.write("Local TDO created\n")
2533 self.outf.write("Setting supported encryption types on local TDO.\n")
2534 current_request = { "location": "local", "name": "SetInformationTrustedDomain"}
2535 local_lsa.SetInformationTrustedDomain(local_tdo_handle,
2536 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2538 except RuntimeError as error:
2539 self.outf.write("Error: %s failed %sly - cleaning up\n" % (
2540 current_request['name'], current_request['location']))
2541 if remote_tdo_handle:
2542 self.outf.write("Deleting remote TDO.\n")
2543 remote_lsa.DeleteObject(remote_tdo_handle)
2544 remote_tdo_handle = None
2545 if local_tdo_handle:
2546 self.outf.write("Deleting local TDO.\n")
2547 local_lsa.DeleteObject(local_tdo_handle)
2548 local_tdo_handle = None
2549 if current_request['location'] is "remote":
2550 raise self.RemoteRuntimeError(self, error, "%s" % (
2551 current_request['name']))
2552 raise self.LocalRuntimeError(self, error, "%s" % (
2553 current_request['name']))
2556 if local_trust_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2557 self.outf.write("Setup local forest trust information...\n")
2559 # get all information about the remote trust
2560 # this triggers netr_GetForestTrustInformation to the remote domain
2561 # and lsaRSetForestTrustInformation() locally, but new top level
2562 # names are disabled by default.
2563 local_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
2564 remote_lsa_info.dns_domain.string,
2565 netlogon.DS_GFTI_UPDATE_TDO)
2566 except RuntimeError as error:
2567 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2570 # here we try to enable all top level names
2571 local_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
2572 remote_lsa_info.dns_domain,
2573 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2576 except RuntimeError as error:
2577 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2579 self.write_forest_trust_info(local_forest_info,
2580 tln=remote_lsa_info.dns_domain.string,
2581 collisions=local_forest_collision)
2583 if remote_trust_info:
2584 self.outf.write("Setup remote forest trust information...\n")
2586 # get all information about the local trust (from the perspective of the remote domain)
2587 # this triggers netr_GetForestTrustInformation to our domain.
2588 # and lsaRSetForestTrustInformation() remotely, but new top level
2589 # names are disabled by default.
2590 remote_forest_info = remote_netlogon.netr_DsRGetForestTrustInformation(remote_netlogon_info.dc_unc,
2591 local_lsa_info.dns_domain.string,
2592 netlogon.DS_GFTI_UPDATE_TDO)
2593 except RuntimeError as error:
2594 raise self.RemoteRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2597 # here we try to enable all top level names
2598 remote_forest_collision = remote_lsa.lsaRSetForestTrustInformation(remote_policy,
2599 local_lsa_info.dns_domain,
2600 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2603 except RuntimeError as error:
2604 raise self.RemoteRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2606 self.write_forest_trust_info(remote_forest_info,
2607 tln=local_lsa_info.dns_domain.string,
2608 collisions=remote_forest_collision)
2610 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2611 self.outf.write("Validating outgoing trust...\n")
2613 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_netlogon_info.dc_unc,
2614 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2616 remote_lsa_info.dns_domain.string)
2617 except RuntimeError as error:
2618 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2620 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2621 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2623 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2624 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2625 local_trust_verify.trusted_dc_name,
2626 local_trust_verify.tc_connection_status[1],
2627 local_trust_verify.pdc_connection_status[1])
2629 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2630 local_trust_verify.trusted_dc_name,
2631 local_trust_verify.tc_connection_status[1],
2632 local_trust_verify.pdc_connection_status[1])
2634 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2635 raise CommandError(local_validation)
2637 self.outf.write("OK: %s\n" % local_validation)
2639 if remote_trust_info:
2640 if remote_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2641 self.outf.write("Validating incoming trust...\n")
2643 remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_netlogon_info.dc_unc,
2644 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2646 local_lsa_info.dns_domain.string)
2647 except RuntimeError as error:
2648 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2650 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2651 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2653 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2654 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2655 remote_trust_verify.trusted_dc_name,
2656 remote_trust_verify.tc_connection_status[1],
2657 remote_trust_verify.pdc_connection_status[1])
2659 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2660 remote_trust_verify.trusted_dc_name,
2661 remote_trust_verify.tc_connection_status[1],
2662 remote_trust_verify.pdc_connection_status[1])
2664 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
2665 raise CommandError(remote_validation)
2667 self.outf.write("OK: %s\n" % remote_validation)
2669 if remote_tdo_handle is not None:
2671 remote_lsa.Close(remote_tdo_handle)
2672 except RuntimeError as error:
2674 remote_tdo_handle = None
2675 if local_tdo_handle is not None:
2677 local_lsa.Close(local_tdo_handle)
2678 except RuntimeError as error:
2680 local_tdo_handle = None
2682 self.outf.write("Success.\n")
2685 class cmd_domain_trust_delete(DomainTrustCommand):
2686 """Delete a domain trust."""
2688 synopsis = "%prog DOMAIN [options]"
2690 takes_optiongroups = {
2691 "sambaopts": options.SambaOptions,
2692 "versionopts": options.VersionOptions,
2693 "credopts": options.CredentialsOptions,
2694 "localdcopts": LocalDCCredentialsOptions,
2698 Option("--delete-location", type="choice", metavar="LOCATION",
2699 choices=["local", "both"],
2700 help="Where to delete the trusted domain object: 'local' or 'both'.",
2701 dest='delete_location',
2705 takes_args = ["domain"]
2707 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2708 delete_location=None):
2710 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2711 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2712 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2714 if delete_location == "local":
2715 remote_policy_access = None
2717 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2718 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2719 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2721 local_server = self.setup_local_server(sambaopts, localdcopts)
2723 local_lsa = self.new_local_lsa_connection()
2724 except RuntimeError as error:
2725 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2728 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2729 except RuntimeError as error:
2730 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2732 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2733 local_lsa_info.name.string,
2734 local_lsa_info.dns_domain.string,
2735 local_lsa_info.sid))
2737 local_tdo_info = None
2738 local_tdo_handle = None
2739 remote_tdo_info = None
2740 remote_tdo_handle = None
2742 lsaString = lsa.String()
2744 lsaString.string = domain
2745 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2746 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2747 except NTSTATUSError as error:
2748 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2749 raise CommandError("Failed to find trust for domain '%s'" % domain)
2750 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2753 if remote_policy_access is not None:
2755 remote_server = self.setup_remote_server(credopts, domain)
2756 except RuntimeError as error:
2757 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2760 remote_lsa = self.new_remote_lsa_connection()
2761 except RuntimeError as error:
2762 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2765 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2766 except RuntimeError as error:
2767 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2769 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2770 remote_lsa_info.name.string,
2771 remote_lsa_info.dns_domain.string,
2772 remote_lsa_info.sid))
2774 if remote_lsa_info.sid != local_tdo_info.sid or \
2775 remote_lsa_info.name.string != local_tdo_info.netbios_name.string or \
2776 remote_lsa_info.dns_domain.string != local_tdo_info.domain_name.string:
2777 raise CommandError("LocalTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2778 local_tdo_info.netbios_name.string,
2779 local_tdo_info.domain_name.string,
2780 local_tdo_info.sid))
2783 lsaString.string = local_lsa_info.dns_domain.string
2784 remote_tdo_info = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2785 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2786 except NTSTATUSError as error:
2787 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2788 raise self.RemoteRuntimeError(self, error, "QueryTrustedDomainInfoByName(%s)" % (
2792 if remote_tdo_info is not None:
2793 if local_lsa_info.sid != remote_tdo_info.sid or \
2794 local_lsa_info.name.string != remote_tdo_info.netbios_name.string or \
2795 local_lsa_info.dns_domain.string != remote_tdo_info.domain_name.string:
2796 raise CommandError("RemoteTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2797 remote_tdo_info.netbios_name.string,
2798 remote_tdo_info.domain_name.string,
2799 remote_tdo_info.sid))
2801 if local_tdo_info is not None:
2803 lsaString.string = local_tdo_info.domain_name.string
2804 local_tdo_handle = local_lsa.OpenTrustedDomainByName(local_policy,
2806 security.SEC_STD_DELETE)
2807 except RuntimeError as error:
2808 raise self.LocalRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2811 local_lsa.DeleteObject(local_tdo_handle)
2812 local_tdo_handle = None
2814 if remote_tdo_info is not None:
2816 lsaString.string = remote_tdo_info.domain_name.string
2817 remote_tdo_handle = remote_lsa.OpenTrustedDomainByName(remote_policy,
2819 security.SEC_STD_DELETE)
2820 except RuntimeError as error:
2821 raise self.RemoteRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2824 if remote_tdo_handle is not None:
2826 remote_lsa.DeleteObject(remote_tdo_handle)
2827 remote_tdo_handle = None
2828 self.outf.write("RemoteTDO deleted.\n")
2829 except RuntimeError as error:
2830 self.outf.write("%s\n" % self.RemoteRuntimeError(self, error, "DeleteObject() failed"))
2832 if local_tdo_handle is not None:
2834 local_lsa.DeleteObject(local_tdo_handle)
2835 local_tdo_handle = None
2836 self.outf.write("LocalTDO deleted.\n")
2837 except RuntimeError as error:
2838 self.outf.write("%s\n" % self.LocalRuntimeError(self, error, "DeleteObject() failed"))
2842 class cmd_domain_trust_validate(DomainTrustCommand):
2843 """Validate a domain trust."""
2845 synopsis = "%prog DOMAIN [options]"
2847 takes_optiongroups = {
2848 "sambaopts": options.SambaOptions,
2849 "versionopts": options.VersionOptions,
2850 "credopts": options.CredentialsOptions,
2851 "localdcopts": LocalDCCredentialsOptions,
2855 Option("--validate-location", type="choice", metavar="LOCATION",
2856 choices=["local", "both"],
2857 help="Where to validate the trusted domain object: 'local' or 'both'.",
2858 dest='validate_location',
2862 takes_args = ["domain"]
2864 def run(self, domain, sambaopts=None, versionopts=None, credopts=None, localdcopts=None,
2865 validate_location=None):
2867 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2869 local_server = self.setup_local_server(sambaopts, localdcopts)
2871 local_lsa = self.new_local_lsa_connection()
2872 except RuntimeError as error:
2873 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2876 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2877 except RuntimeError as error:
2878 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2880 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2881 local_lsa_info.name.string,
2882 local_lsa_info.dns_domain.string,
2883 local_lsa_info.sid))
2886 lsaString = lsa.String()
2887 lsaString.string = domain
2888 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2889 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2890 except NTSTATUSError as error:
2891 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2892 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2894 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
2896 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
2897 local_tdo_info.netbios_name.string,
2898 local_tdo_info.domain_name.string,
2899 local_tdo_info.sid))
2902 local_netlogon = self.new_local_netlogon_connection()
2903 except RuntimeError as error:
2904 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2907 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_server,
2908 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2910 local_tdo_info.domain_name.string)
2911 except RuntimeError as error:
2912 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2914 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2915 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2917 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2918 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2919 local_trust_verify.trusted_dc_name,
2920 local_trust_verify.tc_connection_status[1],
2921 local_trust_verify.pdc_connection_status[1])
2923 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2924 local_trust_verify.trusted_dc_name,
2925 local_trust_verify.tc_connection_status[1],
2926 local_trust_verify.pdc_connection_status[1])
2928 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2929 raise CommandError(local_validation)
2931 self.outf.write("OK: %s\n" % local_validation)
2934 server = local_trust_verify.trusted_dc_name.replace('\\', '')
2935 domain_and_server = "%s\\%s" % (local_tdo_info.domain_name.string, server)
2936 local_trust_rediscover = local_netlogon.netr_LogonControl2Ex(local_server,
2937 netlogon.NETLOGON_CONTROL_REDISCOVER,
2940 except RuntimeError as error:
2941 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
2943 local_conn_status = self._uint32(local_trust_rediscover.tc_connection_status[0])
2944 local_rediscover = "LocalRediscover: DC[%s] CONNECTION[%s]" % (
2945 local_trust_rediscover.trusted_dc_name,
2946 local_trust_rediscover.tc_connection_status[1])
2948 if local_conn_status != werror.WERR_SUCCESS:
2949 raise CommandError(local_rediscover)
2951 self.outf.write("OK: %s\n" % local_rediscover)
2953 if validate_location != "local":
2955 remote_server = self.setup_remote_server(credopts, domain, require_pdc=False)
2956 except RuntimeError as error:
2957 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2960 remote_netlogon = self.new_remote_netlogon_connection()
2961 except RuntimeError as error:
2962 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2965 remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_server,
2966 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2968 local_lsa_info.dns_domain.string)
2969 except RuntimeError as error:
2970 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2972 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2973 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2975 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2976 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2977 remote_trust_verify.trusted_dc_name,
2978 remote_trust_verify.tc_connection_status[1],
2979 remote_trust_verify.pdc_connection_status[1])
2981 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2982 remote_trust_verify.trusted_dc_name,
2983 remote_trust_verify.tc_connection_status[1],
2984 remote_trust_verify.pdc_connection_status[1])
2986 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
2987 raise CommandError(remote_validation)
2989 self.outf.write("OK: %s\n" % remote_validation)
2992 server = remote_trust_verify.trusted_dc_name.replace('\\', '')
2993 domain_and_server = "%s\\%s" % (local_lsa_info.dns_domain.string, server)
2994 remote_trust_rediscover = remote_netlogon.netr_LogonControl2Ex(remote_server,
2995 netlogon.NETLOGON_CONTROL_REDISCOVER,
2998 except RuntimeError as error:
2999 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
3001 remote_conn_status = self._uint32(remote_trust_rediscover.tc_connection_status[0])
3003 remote_rediscover = "RemoteRediscover: DC[%s] CONNECTION[%s]" % (
3004 remote_trust_rediscover.trusted_dc_name,
3005 remote_trust_rediscover.tc_connection_status[1])
3007 if remote_conn_status != werror.WERR_SUCCESS:
3008 raise CommandError(remote_rediscover)
3010 self.outf.write("OK: %s\n" % remote_rediscover)
3014 class cmd_domain_trust_namespaces(DomainTrustCommand):
3015 """Manage forest trust namespaces."""
3017 synopsis = "%prog [DOMAIN] [options]"
3019 takes_optiongroups = {
3020 "sambaopts": options.SambaOptions,
3021 "versionopts": options.VersionOptions,
3022 "localdcopts": LocalDCCredentialsOptions,
3026 Option("--refresh", type="choice", metavar="check|store",
3027 choices=["check", "store", None],
3028 help="List and maybe store refreshed forest trust information: 'check' or 'store'.",
3031 Option("--enable-all", action="store_true",
3032 help="Try to update disabled entries, not allowed with --refresh=check.",
3035 Option("--enable-tln", action="append", metavar='DNSDOMAIN',
3036 help="Enable a top level name entry. Can be specified multiple times.",
3039 Option("--disable-tln", action="append", metavar='DNSDOMAIN',
3040 help="Disable a top level name entry. Can be specified multiple times.",
3043 Option("--add-tln-ex", action="append", metavar='DNSDOMAIN',
3044 help="Add a top level exclusion entry. Can be specified multiple times.",
3047 Option("--delete-tln-ex", action="append", metavar='DNSDOMAIN',
3048 help="Delete a top level exclusion entry. Can be specified multiple times.",
3049 dest='delete_tln_ex',
3051 Option("--enable-nb", action="append", metavar='NETBIOSDOMAIN',
3052 help="Enable a netbios name in a domain entry. Can be specified multiple times.",
3055 Option("--disable-nb", action="append", metavar='NETBIOSDOMAIN',
3056 help="Disable a netbios name in a domain entry. Can be specified multiple times.",
3059 Option("--enable-sid", action="append", metavar='DOMAINSID',
3060 help="Enable a SID in a domain entry. Can be specified multiple times.",
3061 dest='enable_sid_str',
3063 Option("--disable-sid", action="append", metavar='DOMAINSID',
3064 help="Disable a SID in a domain entry. Can be specified multiple times.",
3065 dest='disable_sid_str',
3067 Option("--add-upn-suffix", action="append", metavar='DNSDOMAIN',
3068 help="Add a new uPNSuffixes attribute for the local forest. Can be specified multiple times.",
3071 Option("--delete-upn-suffix", action="append", metavar='DNSDOMAIN',
3072 help="Delete an existing uPNSuffixes attribute of the local forest. Can be specified multiple times.",
3075 Option("--add-spn-suffix", action="append", metavar='DNSDOMAIN',
3076 help="Add a new msDS-SPNSuffixes attribute for the local forest. Can be specified multiple times.",
3079 Option("--delete-spn-suffix", action="append", metavar='DNSDOMAIN',
3080 help="Delete an existing msDS-SPNSuffixes attribute of the local forest. Can be specified multiple times.",
3085 takes_args = ["domain?"]
3087 def run(self, domain=None, sambaopts=None, localdcopts=None, versionopts=None,
3088 refresh=None, enable_all=False,
3089 enable_tln=[], disable_tln=[], add_tln_ex=[], delete_tln_ex=[],
3090 enable_sid_str=[], disable_sid_str=[], enable_nb=[], disable_nb=[],
3091 add_upn=[], delete_upn=[], add_spn=[], delete_spn=[]):
3093 require_update = False
3096 if refresh == "store":
3097 raise CommandError("--refresh=%s not allowed without DOMAIN" % refresh)
3100 raise CommandError("--enable-all not allowed without DOMAIN")
3102 if len(enable_tln) > 0:
3103 raise CommandError("--enable-tln not allowed without DOMAIN")
3104 if len(disable_tln) > 0:
3105 raise CommandError("--disable-tln not allowed without DOMAIN")
3107 if len(add_tln_ex) > 0:
3108 raise CommandError("--add-tln-ex not allowed without DOMAIN")
3109 if len(delete_tln_ex) > 0:
3110 raise CommandError("--delete-tln-ex not allowed without DOMAIN")
3112 if len(enable_nb) > 0:
3113 raise CommandError("--enable-nb not allowed without DOMAIN")
3114 if len(disable_nb) > 0:
3115 raise CommandError("--disable-nb not allowed without DOMAIN")
3117 if len(enable_sid_str) > 0:
3118 raise CommandError("--enable-sid not allowed without DOMAIN")
3119 if len(disable_sid_str) > 0:
3120 raise CommandError("--disable-sid not allowed without DOMAIN")
3122 if len(add_upn) > 0:
3124 if not n.startswith("*."):
3126 raise CommandError("value[%s] specified for --add-upn-suffix should not include with '*.'" % n)
3127 require_update = True
3128 if len(delete_upn) > 0:
3129 for n in delete_upn:
3130 if not n.startswith("*."):
3132 raise CommandError("value[%s] specified for --delete-upn-suffix should not include with '*.'" % n)
3133 require_update = True
3135 for d in delete_upn:
3136 if a.lower() != d.lower():
3138 raise CommandError("value[%s] specified for --add-upn-suffix and --delete-upn-suffix" % a)
3140 if len(add_spn) > 0:
3142 if not n.startswith("*."):
3144 raise CommandError("value[%s] specified for --add-spn-suffix should not include with '*.'" % n)
3145 require_update = True
3146 if len(delete_spn) > 0:
3147 for n in delete_spn:
3148 if not n.startswith("*."):
3150 raise CommandError("value[%s] specified for --delete-spn-suffix should not include with '*.'" % n)
3151 require_update = True
3153 for d in delete_spn:
3154 if a.lower() != d.lower():
3156 raise CommandError("value[%s] specified for --add-spn-suffix and --delete-spn-suffix" % a)
3158 if len(add_upn) > 0:
3159 raise CommandError("--add-upn-suffix not allowed together with DOMAIN")
3160 if len(delete_upn) > 0:
3161 raise CommandError("--delete-upn-suffix not allowed together with DOMAIN")
3162 if len(add_spn) > 0:
3163 raise CommandError("--add-spn-suffix not allowed together with DOMAIN")
3164 if len(delete_spn) > 0:
3165 raise CommandError("--delete-spn-suffix not allowed together with DOMAIN")
3167 if refresh is not None:
3168 if refresh == "store":
3169 require_update = True
3171 if enable_all and refresh != "store":
3172 raise CommandError("--enable-all not allowed together with --refresh=%s" % refresh)
3174 if len(enable_tln) > 0:
3175 raise CommandError("--enable-tln not allowed together with --refresh")
3176 if len(disable_tln) > 0:
3177 raise CommandError("--disable-tln not allowed together with --refresh")
3179 if len(add_tln_ex) > 0:
3180 raise CommandError("--add-tln-ex not allowed together with --refresh")
3181 if len(delete_tln_ex) > 0:
3182 raise CommandError("--delete-tln-ex not allowed together with --refresh")
3184 if len(enable_nb) > 0:
3185 raise CommandError("--enable-nb not allowed together with --refresh")
3186 if len(disable_nb) > 0:
3187 raise CommandError("--disable-nb not allowed together with --refresh")
3189 if len(enable_sid_str) > 0:
3190 raise CommandError("--enable-sid not allowed together with --refresh")
3191 if len(disable_sid_str) > 0:
3192 raise CommandError("--disable-sid not allowed together with --refresh")
3195 require_update = True
3197 if len(enable_tln) > 0:
3198 raise CommandError("--enable-tln not allowed together with --enable-all")
3200 if len(enable_nb) > 0:
3201 raise CommandError("--enable-nb not allowed together with --enable-all")
3203 if len(enable_sid_str) > 0:
3204 raise CommandError("--enable-sid not allowed together with --enable-all")
3206 if len(enable_tln) > 0:
3207 require_update = True
3208 if len(disable_tln) > 0:
3209 require_update = True
3210 for e in enable_tln:
3211 for d in disable_tln:
3212 if e.lower() != d.lower():
3214 raise CommandError("value[%s] specified for --enable-tln and --disable-tln" % e)
3216 if len(add_tln_ex) > 0:
3217 for n in add_tln_ex:
3218 if not n.startswith("*."):
3220 raise CommandError("value[%s] specified for --add-tln-ex should not include with '*.'" % n)
3221 require_update = True
3222 if len(delete_tln_ex) > 0:
3223 for n in delete_tln_ex:
3224 if not n.startswith("*."):
3226 raise CommandError("value[%s] specified for --delete-tln-ex should not include with '*.'" % n)
3227 require_update = True
3228 for a in add_tln_ex:
3229 for d in delete_tln_ex:
3230 if a.lower() != d.lower():
3232 raise CommandError("value[%s] specified for --add-tln-ex and --delete-tln-ex" % a)
3234 if len(enable_nb) > 0:
3235 require_update = True
3236 if len(disable_nb) > 0:
3237 require_update = True
3239 for d in disable_nb:
3240 if e.upper() != d.upper():
3242 raise CommandError("value[%s] specified for --enable-nb and --disable-nb" % e)
3245 for s in enable_sid_str:
3247 sid = security.dom_sid(s)
3248 except TypeError as error:
3249 raise CommandError("value[%s] specified for --enable-sid is not a valid SID" % s)
3250 enable_sid.append(sid)
3252 for s in disable_sid_str:
3254 sid = security.dom_sid(s)
3255 except TypeError as error:
3256 raise CommandError("value[%s] specified for --disable-sid is not a valid SID" % s)
3257 disable_sid.append(sid)
3258 if len(enable_sid) > 0:
3259 require_update = True
3260 if len(disable_sid) > 0:
3261 require_update = True
3262 for e in enable_sid:
3263 for d in disable_sid:
3266 raise CommandError("value[%s] specified for --enable-sid and --disable-sid" % e)
3268 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
3270 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
3272 local_server = self.setup_local_server(sambaopts, localdcopts)
3274 local_lsa = self.new_local_lsa_connection()
3275 except RuntimeError as error:
3276 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
3279 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
3280 except RuntimeError as error:
3281 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
3283 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
3284 local_lsa_info.name.string,
3285 local_lsa_info.dns_domain.string,
3286 local_lsa_info.sid))
3290 local_netlogon = self.new_local_netlogon_connection()
3291 except RuntimeError as error:
3292 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3295 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3296 except RuntimeError as error:
3297 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3299 if local_netlogon_info.domain_name != local_netlogon_info.forest_name:
3300 raise CommandError("The local domain [%s] is not the forest root [%s]" % (
3301 local_netlogon_info.domain_name,
3302 local_netlogon_info.forest_name))
3305 # get all information about our own forest
3306 own_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3308 except RuntimeError as error:
3309 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
3310 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3313 if self.check_runtime_error(error, werror.WERR_INVALID_FUNCTION):
3314 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3317 if self.check_runtime_error(error, werror.WERR_NERR_ACFNOTLOADED):
3318 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3321 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3323 self.outf.write("Own forest trust information...\n")
3324 self.write_forest_trust_info(own_forest_info,
3325 tln=local_lsa_info.dns_domain.string)
3328 local_samdb = self.new_local_ldap_connection()
3329 except RuntimeError as error:
3330 raise self.LocalRuntimeError(self, error, "failed to connect to SamDB")
3332 local_partitions_dn = "CN=Partitions,%s" % str(local_samdb.get_config_basedn())
3333 attrs = ['uPNSuffixes', 'msDS-SPNSuffixes']
3335 msgs = local_samdb.search(base=local_partitions_dn,
3336 scope=ldb.SCOPE_BASE,
3337 expression="(objectClass=crossRefContainer)",
3339 stored_msg = msgs[0]
3340 except ldb.LdbError as error:
3341 raise self.LocalLdbError(self, error, "failed to search partition dn")
3343 stored_upn_vals = []
3344 if 'uPNSuffixes' in stored_msg:
3345 stored_upn_vals.extend(stored_msg['uPNSuffixes'])
3347 stored_spn_vals = []
3348 if 'msDS-SPNSuffixes' in stored_msg:
3349 stored_spn_vals.extend(stored_msg['msDS-SPNSuffixes'])
3351 self.outf.write("Stored uPNSuffixes attributes[%d]:\n" % len(stored_upn_vals))
3352 for v in stored_upn_vals:
3353 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3354 self.outf.write("Stored msDS-SPNSuffixes attributes[%d]:\n" % len(stored_spn_vals))
3355 for v in stored_spn_vals:
3356 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3358 if not require_update:
3362 update_upn_vals = []
3363 update_upn_vals.extend(stored_upn_vals)
3366 update_spn_vals = []
3367 update_spn_vals.extend(stored_spn_vals)
3371 for i in xrange(0, len(update_upn_vals)):
3372 v = update_upn_vals[i]
3373 if v.lower() != upn.lower():
3378 raise CommandError("Entry already present for value[%s] specified for --add-upn-suffix" % upn)
3379 update_upn_vals.append(upn)
3382 for upn in delete_upn:
3384 for i in xrange(0, len(update_upn_vals)):
3385 v = update_upn_vals[i]
3386 if v.lower() != upn.lower():
3391 raise CommandError("Entry not found for value[%s] specified for --delete-upn-suffix" % upn)
3393 update_upn_vals.pop(idx)
3398 for i in xrange(0, len(update_spn_vals)):
3399 v = update_spn_vals[i]
3400 if v.lower() != spn.lower():
3405 raise CommandError("Entry already present for value[%s] specified for --add-spn-suffix" % spn)
3406 update_spn_vals.append(spn)
3409 for spn in delete_spn:
3411 for i in xrange(0, len(update_spn_vals)):
3412 v = update_spn_vals[i]
3413 if v.lower() != spn.lower():
3418 raise CommandError("Entry not found for value[%s] specified for --delete-spn-suffix" % spn)
3420 update_spn_vals.pop(idx)
3423 self.outf.write("Update uPNSuffixes attributes[%d]:\n" % len(update_upn_vals))
3424 for v in update_upn_vals:
3425 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3426 self.outf.write("Update msDS-SPNSuffixes attributes[%d]:\n" % len(update_spn_vals))
3427 for v in update_spn_vals:
3428 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3430 update_msg = ldb.Message()
3431 update_msg.dn = stored_msg.dn
3434 update_msg['uPNSuffixes'] = ldb.MessageElement(update_upn_vals,
3435 ldb.FLAG_MOD_REPLACE,
3438 update_msg['msDS-SPNSuffixes'] = ldb.MessageElement(update_spn_vals,
3439 ldb.FLAG_MOD_REPLACE,
3442 local_samdb.modify(update_msg)
3443 except ldb.LdbError as error:
3444 raise self.LocalLdbError(self, error, "failed to update partition dn")
3447 stored_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3449 except RuntimeError as error:
3450 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3452 self.outf.write("Stored forest trust information...\n")
3453 self.write_forest_trust_info(stored_forest_info,
3454 tln=local_lsa_info.dns_domain.string)
3458 lsaString = lsa.String()
3459 lsaString.string = domain
3460 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
3461 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
3462 except NTSTATUSError as error:
3463 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
3464 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
3466 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
3468 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
3469 local_tdo_info.netbios_name.string,
3470 local_tdo_info.domain_name.string,
3471 local_tdo_info.sid))
3473 if not local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
3474 raise CommandError("trusted domain object for domain [%s] is not marked as FOREST_TRANSITIVE." % domain)
3476 if refresh is not None:
3478 local_netlogon = self.new_local_netlogon_connection()
3479 except RuntimeError as error:
3480 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3483 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3484 except RuntimeError as error:
3485 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3487 lsa_update_check = 1
3488 if refresh == "store":
3489 netlogon_update_tdo = netlogon.DS_GFTI_UPDATE_TDO
3491 lsa_update_check = 0
3493 netlogon_update_tdo = 0
3496 # get all information about the remote trust
3497 # this triggers netr_GetForestTrustInformation to the remote domain
3498 # and lsaRSetForestTrustInformation() locally, but new top level
3499 # names are disabled by default.
3500 fresh_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3501 local_tdo_info.domain_name.string,
3502 netlogon_update_tdo)
3503 except RuntimeError as error:
3504 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3507 fresh_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3508 local_tdo_info.domain_name,
3509 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3512 except RuntimeError as error:
3513 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3515 self.outf.write("Fresh forest trust information...\n")
3516 self.write_forest_trust_info(fresh_forest_info,
3517 tln=local_tdo_info.domain_name.string,
3518 collisions=fresh_forest_collision)
3520 if refresh == "store":
3522 lsaString = lsa.String()
3523 lsaString.string = local_tdo_info.domain_name.string
3524 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3526 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3527 except RuntimeError as error:
3528 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3530 self.outf.write("Stored forest trust information...\n")
3531 self.write_forest_trust_info(stored_forest_info,
3532 tln=local_tdo_info.domain_name.string)
3537 # The none --refresh path
3541 lsaString = lsa.String()
3542 lsaString.string = local_tdo_info.domain_name.string
3543 local_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3545 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3546 except RuntimeError as error:
3547 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3549 self.outf.write("Local forest trust information...\n")
3550 self.write_forest_trust_info(local_forest_info,
3551 tln=local_tdo_info.domain_name.string)
3553 if not require_update:
3557 entries.extend(local_forest_info.entries)
3558 update_forest_info = lsa.ForestTrustInformation()
3559 update_forest_info.count = len(entries)
3560 update_forest_info.entries = entries
3563 for i in xrange(0, len(update_forest_info.entries)):
3564 r = update_forest_info.entries[i]
3565 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3567 if update_forest_info.entries[i].flags == 0:
3569 update_forest_info.entries[i].time = 0
3570 update_forest_info.entries[i].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3571 for i in xrange(0, len(update_forest_info.entries)):
3572 r = update_forest_info.entries[i]
3573 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3575 if update_forest_info.entries[i].flags == 0:
3577 update_forest_info.entries[i].time = 0
3578 update_forest_info.entries[i].flags &= ~lsa.LSA_NB_DISABLED_MASK
3579 update_forest_info.entries[i].flags &= ~lsa.LSA_SID_DISABLED_MASK
3581 for tln in enable_tln:
3583 for i in xrange(0, len(update_forest_info.entries)):
3584 r = update_forest_info.entries[i]
3585 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3587 if r.forest_trust_data.string.lower() != tln.lower():
3592 raise CommandError("Entry not found for value[%s] specified for --enable-tln" % tln)
3593 if not update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_MASK:
3594 raise CommandError("Entry found for value[%s] specified for --enable-tln is already enabled" % tln)
3595 update_forest_info.entries[idx].time = 0
3596 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3598 for tln in disable_tln:
3600 for i in xrange(0, len(update_forest_info.entries)):
3601 r = update_forest_info.entries[i]
3602 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3604 if r.forest_trust_data.string.lower() != tln.lower():
3609 raise CommandError("Entry not found for value[%s] specified for --disable-tln" % tln)
3610 if update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_ADMIN:
3611 raise CommandError("Entry found for value[%s] specified for --disable-tln is already disabled" % tln)
3612 update_forest_info.entries[idx].time = 0
3613 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3614 update_forest_info.entries[idx].flags |= lsa.LSA_TLN_DISABLED_ADMIN
3616 for tln_ex in add_tln_ex:
3618 for i in xrange(0, len(update_forest_info.entries)):
3619 r = update_forest_info.entries[i]
3620 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3622 if r.forest_trust_data.string.lower() != tln_ex.lower():
3627 raise CommandError("Entry already present for value[%s] specified for --add-tln-ex" % tln_ex)
3629 tln_dot = ".%s" % tln_ex.lower()
3631 for i in xrange(0, len(update_forest_info.entries)):
3632 r = update_forest_info.entries[i]
3633 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3635 r_dot = ".%s" % r.forest_trust_data.string.lower()
3636 if tln_dot == r_dot:
3637 raise CommandError("TLN entry present for value[%s] specified for --add-tln-ex" % tln_ex)
3638 if not tln_dot.endswith(r_dot):
3644 raise CommandError("No TLN parent present for value[%s] specified for --add-tln-ex" % tln_ex)
3646 r = lsa.ForestTrustRecord()
3647 r.type = lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX
3650 r.forest_trust_data.string = tln_ex
3653 entries.extend(update_forest_info.entries)
3654 entries.insert(idx + 1, r)
3655 update_forest_info.count = len(entries)
3656 update_forest_info.entries = entries
3658 for tln_ex in delete_tln_ex:
3660 for i in xrange(0, len(update_forest_info.entries)):
3661 r = update_forest_info.entries[i]
3662 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3664 if r.forest_trust_data.string.lower() != tln_ex.lower():
3669 raise CommandError("Entry not found for value[%s] specified for --delete-tln-ex" % tln_ex)
3672 entries.extend(update_forest_info.entries)
3674 update_forest_info.count = len(entries)
3675 update_forest_info.entries = entries
3677 for nb in enable_nb:
3679 for i in xrange(0, len(update_forest_info.entries)):
3680 r = update_forest_info.entries[i]
3681 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3683 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3688 raise CommandError("Entry not found for value[%s] specified for --enable-nb" % nb)
3689 if not update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_MASK:
3690 raise CommandError("Entry found for value[%s] specified for --enable-nb is already enabled" % nb)
3691 update_forest_info.entries[idx].time = 0
3692 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3694 for nb in disable_nb:
3696 for i in xrange(0, len(update_forest_info.entries)):
3697 r = update_forest_info.entries[i]
3698 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3700 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3705 raise CommandError("Entry not found for value[%s] specified for --delete-nb" % nb)
3706 if update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_ADMIN:
3707 raise CommandError("Entry found for value[%s] specified for --disable-nb is already disabled" % nb)
3708 update_forest_info.entries[idx].time = 0
3709 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3710 update_forest_info.entries[idx].flags |= lsa.LSA_NB_DISABLED_ADMIN
3712 for sid in enable_sid:
3714 for i in xrange(0, len(update_forest_info.entries)):
3715 r = update_forest_info.entries[i]
3716 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3718 if r.forest_trust_data.domain_sid != sid:
3723 raise CommandError("Entry not found for value[%s] specified for --enable-sid" % sid)
3724 if not update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_MASK:
3725 raise CommandError("Entry found for value[%s] specified for --enable-sid is already enabled" % nb)
3726 update_forest_info.entries[idx].time = 0
3727 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3729 for sid in disable_sid:
3731 for i in xrange(0, len(update_forest_info.entries)):
3732 r = update_forest_info.entries[i]
3733 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3735 if r.forest_trust_data.domain_sid != sid:
3740 raise CommandError("Entry not found for value[%s] specified for --delete-sid" % sid)
3741 if update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_ADMIN:
3742 raise CommandError("Entry found for value[%s] specified for --disable-sid is already disabled" % nb)
3743 update_forest_info.entries[idx].time = 0
3744 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3745 update_forest_info.entries[idx].flags |= lsa.LSA_SID_DISABLED_ADMIN
3748 update_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3749 local_tdo_info.domain_name,
3750 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3751 update_forest_info, 0)
3752 except RuntimeError as error:
3753 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3755 self.outf.write("Updated forest trust information...\n")
3756 self.write_forest_trust_info(update_forest_info,
3757 tln=local_tdo_info.domain_name.string,
3758 collisions=update_forest_collision)
3761 lsaString = lsa.String()
3762 lsaString.string = local_tdo_info.domain_name.string
3763 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3765 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3766 except RuntimeError as error:
3767 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3769 self.outf.write("Stored forest trust information...\n")
3770 self.write_forest_trust_info(stored_forest_info,
3771 tln=local_tdo_info.domain_name.string)
3774 class cmd_domain_tombstones_expunge(Command):
3775 """Expunge tombstones from the database.
3777 This command expunges tombstones from the database."""
3778 synopsis = "%prog NC [NC [...]] [options]"
3781 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3782 metavar="URL", dest="H"),
3783 Option("--current-time",
3784 help="The current time to evaluate the tombstone lifetime from, expressed as YYYY-MM-DD",
3786 Option("--tombstone-lifetime", help="Number of days a tombstone should be preserved for", type=int),
3789 takes_args = ["nc*"]
3791 takes_optiongroups = {
3792 "sambaopts": options.SambaOptions,
3793 "credopts": options.CredentialsOptions,
3794 "versionopts": options.VersionOptions,
3797 def run(self, *ncs, **kwargs):
3798 sambaopts = kwargs.get("sambaopts")
3799 credopts = kwargs.get("credopts")
3800 versionpts = kwargs.get("versionopts")
3802 current_time_string = kwargs.get("current_time")
3803 tombstone_lifetime = kwargs.get("tombstone_lifetime")
3804 lp = sambaopts.get_loadparm()
3805 creds = credopts.get_credentials(lp)
3806 samdb = SamDB(url=H, session_info=system_session(),
3807 credentials=creds, lp=lp)
3809 if current_time_string is not None:
3810 current_time_obj = time.strptime(current_time_string, "%Y-%m-%d")
3811 current_time = long(time.mktime(current_time_obj))
3814 current_time = long(time.time())
3817 res = samdb.search(expression="", base="", scope=ldb.SCOPE_BASE,
3818 attrs=["namingContexts"])
3821 for nc in res[0]["namingContexts"]:
3826 started_transaction = False
3828 samdb.transaction_start()
3829 started_transaction = True
3831 removed_links) = samdb.garbage_collect_tombstones(ncs,
3832 current_time=current_time,
3833 tombstone_lifetime=tombstone_lifetime)
3835 except Exception, err:
3836 if started_transaction:
3837 samdb.transaction_cancel()
3838 raise CommandError("Failed to expunge / garbage collect tombstones", err)
3840 samdb.transaction_commit()
3842 self.outf.write("Removed %d objects and %d links successfully\n"
3843 % (removed_objects, removed_links))
3847 class cmd_domain_trust(SuperCommand):
3848 """Domain and forest trust management."""
3851 subcommands["list"] = cmd_domain_trust_list()
3852 subcommands["show"] = cmd_domain_trust_show()
3853 subcommands["create"] = cmd_domain_trust_create()
3854 subcommands["delete"] = cmd_domain_trust_delete()
3855 subcommands["validate"] = cmd_domain_trust_validate()
3856 subcommands["namespaces"] = cmd_domain_trust_namespaces()
3858 class cmd_domain_tombstones(SuperCommand):
3859 """Domain tombstone and recycled object management."""
3862 subcommands["expunge"] = cmd_domain_tombstones_expunge()
3864 class ldif_schema_update:
3865 """Helper class for applying LDIF schema updates"""
3868 self.is_defunct = False
3869 self.unknown_oid = None
3873 def _ldap_schemaUpdateNow(self, samdb):
3877 add: schemaUpdateNow
3880 samdb.modify_ldif(ldif)
3882 def can_ignore_failure(self, error):
3883 """Checks if we can safely ignore failure to apply an LDIF update"""
3884 (num, errstr) = error.args
3886 # Microsoft has marked objects as defunct that Samba doesn't know about
3887 if num == ldb.ERR_NO_SUCH_OBJECT and self.is_defunct:
3888 print("Defunct object %s doesn't exist, skipping" % self.dn)
3890 elif self.unknown_oid is not None:
3891 print("Skipping unknown OID %s for object %s" %(self.unknown_oid, self.dn))
3896 def apply(self, samdb):
3897 """Applies a single LDIF update to the schema"""
3900 samdb.modify_ldif(self.ldif, controls=['relax:0'])
3901 except ldb.LdbError as e:
3902 if self.can_ignore_failure(e):
3905 print("Exception: %s" % e)
3906 print("Encountered while trying to apply the following LDIF")
3907 print("----------------------------------------------------")
3908 print("%s" % self.ldif)
3912 # REFRESH AFTER EVERY CHANGE
3913 # Otherwise the OID-to-attribute mapping in _apply_updates_in_file()
3914 # won't work, because it can't lookup the new OID in the schema
3915 self._ldap_schemaUpdateNow(samdb)
3919 class cmd_domain_schema_upgrade(Command):
3920 """Domain schema upgrading"""
3922 synopsis = "%prog [options]"
3924 takes_optiongroups = {
3925 "sambaopts": options.SambaOptions,
3926 "versionopts": options.VersionOptions,
3927 "credopts": options.CredentialsOptions,
3931 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3932 metavar="URL", dest="H"),
3933 Option("--quiet", help="Be quiet", action="store_true"),
3934 Option("--verbose", help="Be verbose", action="store_true"),
3935 Option("--schema", type="choice", metavar="SCHEMA",
3936 choices=["2012", "2012_R2"],
3937 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
3939 Option("--ldf-file", type=str, default=None,
3940 help="Just apply the schema updates in the adprep/.LDF file(s) specified"),
3941 Option("--base-dir", type=str, default=None,
3942 help="Location of ldf files Default is ${SETUPDIR}/adprep.")
3945 def _apply_updates_in_file(self, samdb, ldif_file):
3947 Applies a series of updates specified in an .LDIF file. The .LDIF file
3948 is based on the adprep Schema updates provided by Microsoft.
3951 ldif_op = ldif_schema_update()
3953 # parse the file line by line and work out each update operation to apply
3954 for line in ldif_file:
3956 line = line.rstrip()
3958 # the operations in the .LDIF file are separated by blank lines. If
3959 # we hit a blank line, try to apply the update we've parsed so far
3962 # keep going if we haven't parsed anything yet
3963 if ldif_op.ldif == '':
3966 # Apply the individual change
3967 count += ldif_op.apply(samdb)
3969 # start storing the next operation from scratch again
3970 ldif_op = ldif_schema_update()
3973 # replace the placeholder domain name in the .ldif file with the real domain
3974 if line.upper().endswith('DC=X'):
3975 line = line[:-len('DC=X')] + str(samdb.get_default_basedn())
3976 elif line.upper().endswith('CN=X'):
3977 line = line[:-len('CN=X')] + str(samdb.get_default_basedn())
3979 values = line.split(':')
3981 if values[0].lower() == 'dn':
3982 ldif_op.dn = values[1].strip()
3984 # replace the Windows-specific operation with the Samba one
3985 if values[0].lower() == 'changetype':
3986 line = line.lower().replace(': ntdsschemaadd',
3988 line = line.lower().replace(': ntdsschemamodify',
3991 if values[0].lower() in ['rdnattid', 'subclassof',
3992 'systemposssuperiors',
3994 'systemauxiliaryclass']:
3997 # The Microsoft updates contain some OIDs we don't recognize.
3998 # Query the DB to see if we can work out the OID this update is
3999 # referring to. If we find a match, then replace the OID with
4000 # the ldapDisplayname
4002 res = samdb.search(base=samdb.get_schema_basedn(),
4003 expression="(|(attributeId=%s)(governsId=%s))" %
4005 attrs=['ldapDisplayName'])
4008 ldif_op.unknown_oid = value
4010 display_name = res[0]['ldapDisplayName'][0]
4011 line = line.replace(value, ' ' + display_name)
4013 # Microsoft has marked objects as defunct that Samba doesn't know about
4014 if values[0].lower() == 'isdefunct' and values[1].strip().lower() == 'true':
4015 ldif_op.is_defunct = True
4017 # Samba has added the showInAdvancedViewOnly attribute to all objects,
4018 # so rather than doing an add, we need to do a replace
4019 if values[0].lower() == 'add' and values[1].strip().lower() == 'showinadvancedviewonly':
4020 line = 'replace: showInAdvancedViewOnly'
4022 # Add the line to the current LDIF operation (including the newline
4023 # we stripped off at the start of the loop)
4024 ldif_op.ldif += line + '\n'
4029 def _apply_update(self, samdb, update_file, base_dir):
4030 """Wrapper function for parsing an LDIF file and applying the updates"""
4032 print("Applying %s updates..." % update_file)
4036 ldif_file = open(os.path.join(base_dir, update_file))
4038 count = self._apply_updates_in_file(samdb, ldif_file)
4044 print("%u changes applied" % count)
4048 def run(self, **kwargs):
4049 from samba.schema import Schema
4051 updates_allowed_overriden = False
4052 sambaopts = kwargs.get("sambaopts")
4053 credopts = kwargs.get("credopts")
4054 versionpts = kwargs.get("versionopts")
4055 lp = sambaopts.get_loadparm()
4056 creds = credopts.get_credentials(lp)
4058 target_schema = kwargs.get("schema")
4059 ldf_files = kwargs.get("ldf_file")
4060 base_dir = kwargs.get("base_dir")
4064 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4066 # we're not going to get far if the config doesn't allow schema updates
4067 if lp.get("dsdb:schema update allowed") is None:
4068 lp.set("dsdb:schema update allowed", "yes")
4069 print("Temporarily overriding 'dsdb:schema update allowed' setting")
4070 updates_allowed_overriden = True
4072 # if specific LDIF files were specified, just apply them
4074 schema_updates = ldf_files.split(",")
4078 # work out the version of the target schema we're upgrading to
4079 end = Schema.get_version(target_schema)
4081 # work out the version of the schema we're currently using
4082 res = samdb.search(base=samdb.get_schema_basedn(),
4083 scope=ldb.SCOPE_BASE, attrs=['objectVersion'])
4086 raise CommandError('Could not determine current schema version')
4087 start = int(res[0]['objectVersion'][0]) + 1
4089 diff_dir = setup_path("adprep/WindowsServerDocs")
4090 if base_dir is None:
4091 # Read from the Schema-Updates.md file
4092 temp_folder = tempfile.mkdtemp()
4094 update_file = setup_path("adprep/WindowsServerDocs/Schema-Updates.md")
4097 read_ms_markdown(update_file, temp_folder)
4098 except Exception as e:
4099 print("Exception in markdown parsing: %s" % e)
4100 shutil.rmtree(temp_folder)
4101 raise CommandError('Failed to upgrade schema')
4103 base_dir = temp_folder
4105 for version in range(start, end + 1):
4106 update = 'Sch%d.ldf' % version
4107 schema_updates.append(update)
4109 # Apply patches if we parsed the Schema-Updates.md file
4110 diff = os.path.abspath(os.path.join(diff_dir, update + '.diff'))
4111 if temp_folder and os.path.exists(diff):
4112 p = subprocess.Popen(['patch', update, '-i', diff],
4113 stdout=subprocess.PIPE,
4114 stderr=subprocess.PIPE, cwd=temp_folder)
4115 stdout, stderr = p.communicate()
4118 print("Exception in patch: %s\n%s" % (stdout, stderr))
4119 shutil.rmtree(temp_folder)
4120 raise CommandError('Failed to upgrade schema')
4122 print("Patched %s using %s" % (update, diff))
4124 if base_dir is None:
4125 base_dir = setup_path("adprep")
4127 samdb.transaction_start()
4129 error_encountered = False
4132 # Apply the schema updates needed to move to the new schema version
4133 for ldif_file in schema_updates:
4134 count += self._apply_update(samdb, ldif_file, base_dir)
4137 samdb.transaction_commit()
4138 print("Schema successfully updated")
4140 print("No changes applied to schema")
4141 samdb.transaction_cancel()
4142 except Exception as e:
4143 print("Exception: %s" % e)
4144 print("Error encountered, aborting schema upgrade")
4145 samdb.transaction_cancel()
4146 error_encountered = True
4148 if updates_allowed_overriden:
4149 lp.set("dsdb:schema update allowed", "no")
4152 shutil.rmtree(temp_folder)
4154 if error_encountered:
4155 raise CommandError('Failed to upgrade schema')
4157 class cmd_domain(SuperCommand):
4158 """Domain management."""
4161 subcommands["demote"] = cmd_domain_demote()
4162 if cmd_domain_export_keytab is not None:
4163 subcommands["exportkeytab"] = cmd_domain_export_keytab()
4164 subcommands["info"] = cmd_domain_info()
4165 subcommands["provision"] = cmd_domain_provision()
4166 subcommands["join"] = cmd_domain_join()
4167 subcommands["dcpromo"] = cmd_domain_dcpromo()
4168 subcommands["level"] = cmd_domain_level()
4169 subcommands["passwordsettings"] = cmd_domain_passwordsettings()
4170 subcommands["classicupgrade"] = cmd_domain_classicupgrade()
4171 subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()
4172 subcommands["trust"] = cmd_domain_trust()
4173 subcommands["tombstones"] = cmd_domain_tombstones()
4174 subcommands["schemaupgrade"] = cmd_domain_schema_upgrade()