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
36 from samba import ntstatus
37 from samba import NTSTATUSError
38 from samba import werror
39 from getpass import getpass
40 from samba.net import Net, LIBNET_JOIN_AUTOMATIC
42 from samba.join import join_RODC, join_DC, join_subdomain
43 from samba.auth import system_session
44 from samba.samdb import SamDB
45 from samba.ndr import ndr_unpack, ndr_pack, ndr_print
46 from samba.dcerpc import drsuapi
47 from samba.dcerpc import drsblobs
48 from samba.dcerpc import lsa
49 from samba.dcerpc import netlogon
50 from samba.dcerpc import security
51 from samba.dcerpc import nbt
52 from samba.dcerpc import misc
53 from samba.dcerpc.samr import DOMAIN_PASSWORD_COMPLEX, DOMAIN_PASSWORD_STORE_CLEARTEXT
54 from samba.netcmd import (
60 from samba.netcmd.common import netcmd_get_domain_infos_via_cldap
61 from samba.samba3 import Samba3
62 from samba.samba3 import param as s3param
63 from samba.upgrade import upgrade_from_samba3
64 from samba.drs_utils import (
65 sendDsReplicaSync, drsuapi_connect, drsException,
67 from samba import remove_dc, arcfour_encrypt, string_to_byte_array
69 from samba.dsdb import (
70 DS_DOMAIN_FUNCTION_2000,
71 DS_DOMAIN_FUNCTION_2003,
72 DS_DOMAIN_FUNCTION_2003_MIXED,
73 DS_DOMAIN_FUNCTION_2008,
74 DS_DOMAIN_FUNCTION_2008_R2,
75 DS_DOMAIN_FUNCTION_2012,
76 DS_DOMAIN_FUNCTION_2012_R2,
77 DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL,
78 DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL,
79 UF_WORKSTATION_TRUST_ACCOUNT,
80 UF_SERVER_TRUST_ACCOUNT,
81 UF_TRUSTED_FOR_DELEGATION,
82 UF_PARTIAL_SECRETS_ACCOUNT
85 from samba.provision import (
88 DEFAULT_MIN_PWD_LENGTH,
92 from samba.provision.common import (
98 def get_testparm_var(testparm, smbconf, varname):
99 errfile = open(os.devnull, 'w')
100 p = subprocess.Popen([testparm, '-s', '-l',
101 '--parameter-name=%s' % varname, smbconf],
102 stdout=subprocess.PIPE, stderr=errfile)
103 (out,err) = p.communicate()
105 lines = out.split('\n')
107 return lines[0].strip()
111 import samba.dckeytab
113 cmd_domain_export_keytab = None
115 class cmd_domain_export_keytab(Command):
116 """Dump Kerberos keys of the domain into a keytab."""
118 synopsis = "%prog <keytab> [options]"
120 takes_optiongroups = {
121 "sambaopts": options.SambaOptions,
122 "credopts": options.CredentialsOptions,
123 "versionopts": options.VersionOptions,
127 Option("--principal", help="extract only this principal", type=str),
130 takes_args = ["keytab"]
132 def run(self, keytab, credopts=None, sambaopts=None, versionopts=None, principal=None):
133 lp = sambaopts.get_loadparm()
135 net.export_keytab(keytab=keytab, principal=principal)
138 class cmd_domain_info(Command):
139 """Print basic info about a domain and the DC passed as parameter."""
141 synopsis = "%prog <ip_address> [options]"
146 takes_optiongroups = {
147 "sambaopts": options.SambaOptions,
148 "credopts": options.CredentialsOptions,
149 "versionopts": options.VersionOptions,
152 takes_args = ["address"]
154 def run(self, address, credopts=None, sambaopts=None, versionopts=None):
155 lp = sambaopts.get_loadparm()
157 res = netcmd_get_domain_infos_via_cldap(lp, None, address)
159 raise CommandError("Invalid IP address '" + address + "'!")
160 self.outf.write("Forest : %s\n" % res.forest)
161 self.outf.write("Domain : %s\n" % res.dns_domain)
162 self.outf.write("Netbios domain : %s\n" % res.domain_name)
163 self.outf.write("DC name : %s\n" % res.pdc_dns_name)
164 self.outf.write("DC netbios name : %s\n" % res.pdc_name)
165 self.outf.write("Server site : %s\n" % res.server_site)
166 self.outf.write("Client site : %s\n" % res.client_site)
169 class cmd_domain_provision(Command):
170 """Provision a domain."""
172 synopsis = "%prog [options]"
174 takes_optiongroups = {
175 "sambaopts": options.SambaOptions,
176 "versionopts": options.VersionOptions,
180 Option("--interactive", help="Ask for names", action="store_true"),
181 Option("--domain", type="string", metavar="DOMAIN",
182 help="NetBIOS domain name to use"),
183 Option("--domain-guid", type="string", metavar="GUID",
184 help="set domainguid (otherwise random)"),
185 Option("--domain-sid", type="string", metavar="SID",
186 help="set domainsid (otherwise random)"),
187 Option("--ntds-guid", type="string", metavar="GUID",
188 help="set NTDS object GUID (otherwise random)"),
189 Option("--invocationid", type="string", metavar="GUID",
190 help="set invocationid (otherwise random)"),
191 Option("--host-name", type="string", metavar="HOSTNAME",
192 help="set hostname"),
193 Option("--host-ip", type="string", metavar="IPADDRESS",
194 help="set IPv4 ipaddress"),
195 Option("--host-ip6", type="string", metavar="IP6ADDRESS",
196 help="set IPv6 ipaddress"),
197 Option("--site", type="string", metavar="SITENAME",
198 help="set site name"),
199 Option("--adminpass", type="string", metavar="PASSWORD",
200 help="choose admin password (otherwise random)"),
201 Option("--krbtgtpass", type="string", metavar="PASSWORD",
202 help="choose krbtgt password (otherwise random)"),
203 Option("--machinepass", type="string", metavar="PASSWORD",
204 help="choose machine password (otherwise random)"),
205 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
206 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
207 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
208 "BIND9_FLATFILE uses bind9 text database to store zone information, "
209 "BIND9_DLZ uses samba4 AD to store zone information, "
210 "NONE skips the DNS setup entirely (not recommended)",
211 default="SAMBA_INTERNAL"),
212 Option("--dnspass", type="string", metavar="PASSWORD",
213 help="choose dns password (otherwise random)"),
214 Option("--ldapadminpass", type="string", metavar="PASSWORD",
215 help="choose password to set between Samba and its LDAP backend (otherwise random)"),
216 Option("--root", type="string", metavar="USERNAME",
217 help="choose 'root' unix username"),
218 Option("--nobody", type="string", metavar="USERNAME",
219 help="choose 'nobody' user"),
220 Option("--users", type="string", metavar="GROUPNAME",
221 help="choose 'users' group"),
222 Option("--quiet", help="Be quiet", action="store_true"),
223 Option("--blank", action="store_true",
224 help="do not add users or groups, just the structure"),
225 Option("--ldap-backend-type", type="choice", metavar="LDAP-BACKEND-TYPE",
226 help="Test initialisation support for unsupported LDAP backend type (fedora-ds or openldap) DO NOT USE",
227 choices=["fedora-ds", "openldap"]),
228 Option("--server-role", type="choice", metavar="ROLE",
229 choices=["domain controller", "dc", "member server", "member", "standalone"],
230 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
231 default="domain controller"),
232 Option("--function-level", type="choice", metavar="FOR-FUN-LEVEL",
233 choices=["2000", "2003", "2008", "2008_R2"],
234 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2008_R2 Native.",
236 Option("--base-schema", type="choice", metavar="BASE-SCHEMA",
237 choices=["2008_R2", "2012", "2012_R2"],
238 help="The base schema files to use. Default is (Windows) 2008_R2.",
240 Option("--next-rid", type="int", metavar="NEXTRID", default=1000,
241 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
242 Option("--partitions-only",
243 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"),
244 Option("--targetdir", type="string", metavar="DIR",
245 help="Set target directory"),
246 Option("--ol-mmr-urls", type="string", metavar="LDAPSERVER",
247 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\""),
248 Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
252 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",
253 action="store_true"),
254 Option("--slapd-path", type="string", metavar="SLAPD-PATH",
255 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."),
256 Option("--ldap-backend-extra-port", type="int", metavar="LDAP-BACKEND-EXTRA-PORT", help="Additional TCP port for LDAP backend server (to use for replication)"),
257 Option("--ldap-backend-forced-uri", type="string", metavar="LDAP-BACKEND-FORCED-URI",
258 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"),
259 Option("--ldap-backend-nosync", help="Configure LDAP backend not to call fsync() (for performance in test environments)", action="store_true"),
263 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
264 Option("--use-xattrs", type="choice", choices=["yes","no","auto"],
265 metavar="[yes|no|auto]",
266 help="Define if we should use the native fs capabilities or a tdb file for "
267 "storing attributes likes ntacl when --use-ntvfs is set. "
268 "auto tries to make an inteligent guess based on the user rights and system capabilities",
272 if os.getenv('TEST_LDAP', "no") == "yes":
273 takes_options.extend(openldap_options)
275 if samba.is_ntvfs_fileserver_built():
276 takes_options.extend(ntvfs_options)
280 def run(self, sambaopts=None, versionopts=None,
303 ldap_backend_type=None,
307 partitions_only=None,
314 ldap_backend_nosync=None,
315 ldap_backend_extra_port=None,
316 ldap_backend_forced_uri=None,
317 ldap_dryrun_mode=None,
320 self.logger = self.get_logger("provision")
322 self.logger.setLevel(logging.WARNING)
324 self.logger.setLevel(logging.INFO)
326 lp = sambaopts.get_loadparm()
327 smbconf = lp.configfile
329 if dns_forwarder is not None:
330 suggested_forwarder = dns_forwarder
332 suggested_forwarder = self._get_nameserver_ip()
333 if suggested_forwarder is None:
334 suggested_forwarder = "none"
336 if len(self.raw_argv) == 1:
340 from getpass import getpass
343 def ask(prompt, default=None):
344 if default is not None:
345 print "%s [%s]: " % (prompt, default),
347 print "%s: " % (prompt,),
348 return sys.stdin.readline().rstrip("\n") or default
351 default = socket.getfqdn().split(".", 1)[1].upper()
354 realm = ask("Realm", default)
355 if realm in (None, ""):
356 raise CommandError("No realm set!")
359 default = realm.split(".")[0]
362 domain = ask("Domain", default)
364 raise CommandError("No domain set!")
366 server_role = ask("Server Role (dc, member, standalone)", "dc")
368 dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
369 if dns_backend in (None, ''):
370 raise CommandError("No DNS backend set!")
372 if dns_backend == "SAMBA_INTERNAL":
373 dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
374 if dns_forwarder.lower() in (None, 'none'):
375 suggested_forwarder = None
379 adminpassplain = getpass("Administrator password: ")
380 issue = self._adminpass_issue(adminpassplain)
382 self.errf.write("%s.\n" % issue)
384 adminpassverify = getpass("Retype password: ")
385 if not adminpassplain == adminpassverify:
386 self.errf.write("Sorry, passwords do not match.\n")
388 adminpass = adminpassplain
392 realm = sambaopts._lp.get('realm')
394 raise CommandError("No realm set!")
396 raise CommandError("No domain set!")
399 issue = self._adminpass_issue(adminpass)
401 raise CommandError(issue)
403 self.logger.info("Administrator password will be set randomly!")
405 if function_level == "2000":
406 dom_for_fun_level = DS_DOMAIN_FUNCTION_2000
407 elif function_level == "2003":
408 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
409 elif function_level == "2008":
410 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
411 elif function_level == "2008_R2":
412 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
414 if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
415 dns_forwarder = suggested_forwarder
417 samdb_fill = FILL_FULL
419 samdb_fill = FILL_NT4SYNC
420 elif partitions_only:
421 samdb_fill = FILL_DRS
423 if targetdir is not None:
424 if not os.path.isdir(targetdir):
429 if use_xattrs == "yes":
431 elif use_xattrs == "auto" and use_ntvfs == False:
433 elif use_ntvfs == False:
434 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
435 "Please re-run with --use-xattrs omitted.")
436 elif use_xattrs == "auto" and not lp.get("posix:eadb"):
438 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
440 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
443 samba.ntacls.setntacl(lp, file.name,
444 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
447 self.logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. ")
452 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.")
453 if ldap_backend_type == "existing":
454 if ldap_backend_forced_uri is not None:
455 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)
457 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")
459 if ldap_backend_forced_uri is not None:
460 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")
462 if domain_sid is not None:
463 domain_sid = security.dom_sid(domain_sid)
465 session = system_session()
467 result = provision(self.logger,
468 session, smbconf=smbconf, targetdir=targetdir,
469 samdb_fill=samdb_fill, realm=realm, domain=domain,
470 domainguid=domain_guid, domainsid=domain_sid,
472 hostip=host_ip, hostip6=host_ip6,
473 sitename=site, ntdsguid=ntds_guid,
474 invocationid=invocationid, adminpass=adminpass,
475 krbtgtpass=krbtgtpass, machinepass=machinepass,
476 dns_backend=dns_backend, dns_forwarder=dns_forwarder,
477 dnspass=dnspass, root=root, nobody=nobody,
479 serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
480 backend_type=ldap_backend_type,
481 ldapadminpass=ldapadminpass, ol_mmr_urls=ol_mmr_urls, slapd_path=slapd_path,
482 useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
483 use_rfc2307=use_rfc2307, skip_sysvolacl=False,
484 ldap_backend_extra_port=ldap_backend_extra_port,
485 ldap_backend_forced_uri=ldap_backend_forced_uri,
486 nosync=ldap_backend_nosync, ldap_dryrun_mode=ldap_dryrun_mode,
487 base_schema=base_schema)
489 except ProvisioningError, e:
490 raise CommandError("Provision failed", e)
492 result.report_logger(self.logger)
494 def _get_nameserver_ip(self):
495 """Grab the nameserver IP address from /etc/resolv.conf."""
497 RESOLV_CONF="/etc/resolv.conf"
499 if not path.isfile(RESOLV_CONF):
500 self.logger.warning("Failed to locate %s" % RESOLV_CONF)
505 handle = open(RESOLV_CONF, 'r')
507 if not line.startswith('nameserver'):
509 # we want the last non-space continuous string of the line
510 return line.strip().split()[-1]
512 if handle is not None:
515 self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
517 def _adminpass_issue(self, adminpass):
518 """Returns error string for a bad administrator password,
519 or None if acceptable"""
521 if len(adminpass.decode('utf-8')) < DEFAULT_MIN_PWD_LENGTH:
522 return "Administrator password does not meet the default minimum" \
523 " password length requirement (%d characters)" \
524 % DEFAULT_MIN_PWD_LENGTH
525 elif not samba.check_password_quality(adminpass):
526 return "Administrator password does not meet the default" \
532 class cmd_domain_dcpromo(Command):
533 """Promote an existing domain member or NT4 PDC to an AD DC."""
535 synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
537 takes_optiongroups = {
538 "sambaopts": options.SambaOptions,
539 "versionopts": options.VersionOptions,
540 "credopts": options.CredentialsOptions,
544 Option("--server", help="DC to join", type=str),
545 Option("--site", help="site to join", type=str),
546 Option("--targetdir", help="where to store provision", type=str),
547 Option("--domain-critical-only",
548 help="only replicate critical domain objects",
549 action="store_true"),
550 Option("--machinepass", type=str, metavar="PASSWORD",
551 help="choose machine password (otherwise random)"),
552 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
553 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
554 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
555 "BIND9_DLZ uses samba4 AD to store zone information, "
556 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
557 default="SAMBA_INTERNAL"),
558 Option("--quiet", help="Be quiet", action="store_true"),
559 Option("--verbose", help="Be verbose", action="store_true")
563 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
566 if samba.is_ntvfs_fileserver_built():
567 takes_options.extend(ntvfs_options)
570 takes_args = ["domain", "role?"]
572 def run(self, domain, role=None, sambaopts=None, credopts=None,
573 versionopts=None, server=None, site=None, targetdir=None,
574 domain_critical_only=False, parent_domain=None, machinepass=None,
575 use_ntvfs=False, dns_backend=None,
576 quiet=False, verbose=False):
577 lp = sambaopts.get_loadparm()
578 creds = credopts.get_credentials(lp)
579 net = Net(creds, lp, server=credopts.ipaddress)
581 logger = self.get_logger()
583 logger.setLevel(logging.DEBUG)
585 logger.setLevel(logging.WARNING)
587 logger.setLevel(logging.INFO)
589 netbios_name = lp.get("netbios name")
595 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
596 site=site, netbios_name=netbios_name, targetdir=targetdir,
597 domain_critical_only=domain_critical_only,
598 machinepass=machinepass, use_ntvfs=use_ntvfs,
599 dns_backend=dns_backend,
600 promote_existing=True)
602 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
603 site=site, netbios_name=netbios_name, targetdir=targetdir,
604 domain_critical_only=domain_critical_only,
605 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
606 promote_existing=True)
608 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
611 class cmd_domain_join(Command):
612 """Join domain as either member or backup domain controller."""
614 synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
616 takes_optiongroups = {
617 "sambaopts": options.SambaOptions,
618 "versionopts": options.VersionOptions,
619 "credopts": options.CredentialsOptions,
623 Option("--server", help="DC to join", type=str),
624 Option("--site", help="site to join", type=str),
625 Option("--targetdir", help="where to store provision", type=str),
626 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
627 Option("--domain-critical-only",
628 help="only replicate critical domain objects",
629 action="store_true"),
630 Option("--machinepass", type=str, metavar="PASSWORD",
631 help="choose machine password (otherwise random)"),
632 Option("--adminpass", type="string", metavar="PASSWORD",
633 help="choose adminstrator password when joining as a subdomain (otherwise random)"),
634 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
635 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
636 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
637 "BIND9_DLZ uses samba4 AD to store zone information, "
638 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
639 default="SAMBA_INTERNAL"),
640 Option("--quiet", help="Be quiet", action="store_true"),
641 Option("--verbose", help="Be verbose", action="store_true")
645 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
648 if samba.is_ntvfs_fileserver_built():
649 takes_options.extend(ntvfs_options)
651 takes_args = ["domain", "role?"]
653 def run(self, domain, role=None, sambaopts=None, credopts=None,
654 versionopts=None, server=None, site=None, targetdir=None,
655 domain_critical_only=False, parent_domain=None, machinepass=None,
656 use_ntvfs=False, dns_backend=None, adminpass=None,
657 quiet=False, verbose=False):
658 lp = sambaopts.get_loadparm()
659 creds = credopts.get_credentials(lp)
660 net = Net(creds, lp, server=credopts.ipaddress)
663 site = "Default-First-Site-Name"
665 logger = self.get_logger()
667 logger.setLevel(logging.DEBUG)
669 logger.setLevel(logging.WARNING)
671 logger.setLevel(logging.INFO)
673 netbios_name = lp.get("netbios name")
678 if role is None or role == "MEMBER":
679 (join_password, sid, domain_name) = net.join_member(
680 domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
681 machinepass=machinepass)
683 self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
685 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
686 site=site, netbios_name=netbios_name, targetdir=targetdir,
687 domain_critical_only=domain_critical_only,
688 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend)
690 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
691 site=site, netbios_name=netbios_name, targetdir=targetdir,
692 domain_critical_only=domain_critical_only,
693 machinepass=machinepass, use_ntvfs=use_ntvfs,
694 dns_backend=dns_backend)
695 elif role == "SUBDOMAIN":
697 logger.info("Administrator password will be set randomly!")
699 netbios_domain = lp.get("workgroup")
700 if parent_domain is None:
701 parent_domain = ".".join(domain.split(".")[1:])
702 join_subdomain(logger=logger, server=server, creds=creds, lp=lp, dnsdomain=domain,
703 parent_domain=parent_domain, site=site,
704 netbios_name=netbios_name, netbios_domain=netbios_domain,
705 targetdir=targetdir, machinepass=machinepass,
706 use_ntvfs=use_ntvfs, dns_backend=dns_backend,
709 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role)
712 class cmd_domain_demote(Command):
713 """Demote ourselves from the role of Domain Controller."""
715 synopsis = "%prog [options]"
718 Option("--server", help="writable DC to write demotion changes on", type=str),
719 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
720 metavar="URL", dest="H"),
721 Option("--remove-other-dead-server", help="Dead DC (name or NTDS GUID) "
722 "to remove ALL references to (rather than this DC)", type=str),
723 Option("--quiet", help="Be quiet", action="store_true"),
724 Option("--verbose", help="Be verbose", action="store_true"),
727 takes_optiongroups = {
728 "sambaopts": options.SambaOptions,
729 "credopts": options.CredentialsOptions,
730 "versionopts": options.VersionOptions,
733 def run(self, sambaopts=None, credopts=None,
734 versionopts=None, server=None,
735 remove_other_dead_server=None, H=None,
736 verbose=False, quiet=False):
737 lp = sambaopts.get_loadparm()
738 creds = credopts.get_credentials(lp)
739 net = Net(creds, lp, server=credopts.ipaddress)
741 logger = self.get_logger()
743 logger.setLevel(logging.DEBUG)
745 logger.setLevel(logging.WARNING)
747 logger.setLevel(logging.INFO)
749 if remove_other_dead_server is not None:
750 if server is not None:
751 samdb = SamDB(url="ldap://%s" % server,
752 session_info=system_session(),
753 credentials=creds, lp=lp)
755 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
757 remove_dc.remove_dc(samdb, logger, remove_other_dead_server)
758 except remove_dc.DemoteException as err:
759 raise CommandError("Demote failed: %s" % err)
762 netbios_name = lp.get("netbios name")
763 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
765 res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
767 raise CommandError("Unable to search for servers")
770 raise CommandError("You are the latest server in the domain")
774 if str(e["name"]).lower() != netbios_name.lower():
775 server = e["dnsHostName"]
778 ntds_guid = samdb.get_ntds_GUID()
779 msg = samdb.search(base=str(samdb.get_config_basedn()),
780 scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
782 if len(msg) == 0 or "options" not in msg[0]:
783 raise CommandError("Failed to find options on %s" % ntds_guid)
786 dsa_options = int(str(msg[0]['options']))
788 res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
789 controls=["search_options:1:2"])
792 raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res))
794 self.errf.write("Using %s as partner server for the demotion\n" %
796 (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
798 self.errf.write("Deactivating inbound replication\n")
803 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
804 dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
805 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
809 self.errf.write("Asking partner server %s to synchronize from us\n"
811 for part in (samdb.get_schema_basedn(),
812 samdb.get_config_basedn(),
813 samdb.get_root_basedn()):
814 nc = drsuapi.DsReplicaObjectIdentifier()
817 req1 = drsuapi.DsReplicaSyncRequest1()
818 req1.naming_context = nc;
819 req1.options = drsuapi.DRSUAPI_DRS_WRIT_REP
820 req1.source_dsa_guid = misc.GUID(ntds_guid)
823 drsuapiBind.DsReplicaSync(drsuapi_handle, 1, req1)
824 except RuntimeError as (werr, string):
825 if werr == werror.WERR_DS_DRA_NO_REPLICA:
829 "Error while replicating out last local changes from '%s' for demotion, "
830 "re-enabling inbound replication\n" % part)
831 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
832 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
834 raise CommandError("Error while sending a DsReplicaSync for partition '%s'" % str(part), string)
836 remote_samdb = SamDB(url="ldap://%s" % server,
837 session_info=system_session(),
838 credentials=creds, lp=lp)
840 self.errf.write("Changing userControl and container\n")
841 res = remote_samdb.search(base=str(remote_samdb.domain_dn()),
842 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
843 netbios_name.upper(),
844 attrs=["userAccountControl"])
846 uac = int(str(res[0]["userAccountControl"]))
849 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
851 "Error while demoting, re-enabling inbound replication\n")
852 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
853 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
855 raise CommandError("Error while changing account control", e)
858 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
860 "Error while demoting, re-enabling inbound replication")
861 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
862 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
864 raise CommandError("Unable to find object with samaccountName = %s$"
865 " in the remote dc" % netbios_name.upper())
869 uac &= ~(UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION|UF_PARTIAL_SECRETS_ACCOUNT)
870 uac |= UF_WORKSTATION_TRUST_ACCOUNT
875 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
876 ldb.FLAG_MOD_REPLACE,
877 "userAccountControl")
879 remote_samdb.modify(msg)
881 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
883 "Error while demoting, re-enabling inbound replication")
884 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
885 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
888 raise CommandError("Error while changing account control", e)
890 parent = msg.dn.parent()
891 dc_name = res[0].dn.get_rdn_value()
892 rdn = "CN=%s" % dc_name
894 # Let's move to the Computer container
898 computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.domain_dn()))
899 res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
902 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
903 scope=ldb.SCOPE_ONELEVEL)
904 while(len(res) != 0 and i < 100):
906 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
907 scope=ldb.SCOPE_ONELEVEL)
910 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
912 "Error while demoting, re-enabling inbound replication\n")
913 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
914 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
920 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
921 ldb.FLAG_MOD_REPLACE,
922 "userAccountControl")
924 remote_samdb.modify(msg)
926 raise CommandError("Unable to find a slot for renaming %s,"
927 " all names from %s-1 to %s-%d seemed used" %
928 (str(dc_dn), rdn, rdn, i - 9))
930 newrdn = "%s-%d" % (rdn, i)
933 newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
934 remote_samdb.rename(dc_dn, newdn)
936 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
938 "Error while demoting, re-enabling inbound replication\n")
939 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
940 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
946 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
947 ldb.FLAG_MOD_REPLACE,
948 "userAccountControl")
950 remote_samdb.modify(msg)
951 raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
954 server_dsa_dn = samdb.get_serverName()
955 domain = remote_samdb.get_root_basedn()
958 req1 = drsuapi.DsRemoveDSServerRequest1()
959 req1.server_dn = str(server_dsa_dn)
960 req1.domain_dn = str(domain)
963 drsuapiBind.DsRemoveDSServer(drsuapi_handle, 1, req1)
964 except RuntimeError as (werr, string):
965 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
967 "Error while demoting, re-enabling inbound replication\n")
968 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
969 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
975 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
976 ldb.FLAG_MOD_REPLACE,
977 "userAccountControl")
978 remote_samdb.modify(msg)
979 remote_samdb.rename(newdn, dc_dn)
980 if werr == werror.WERR_DS_DRA_NO_REPLICA:
981 raise CommandError("The DC %s is not present on (already removed from) the remote server: " % server_dsa_dn, e)
983 raise CommandError("Error while sending a removeDsServer of %s: " % server_dsa_dn, e)
985 remove_dc.remove_sysvol_references(remote_samdb, logger, dc_name)
987 # These are objects under the computer account that should be deleted
988 for s in ("CN=Enterprise,CN=NTFRS Subscriptions",
989 "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
990 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
991 "CN=NTFRS Subscriptions"):
993 remote_samdb.delete(ldb.Dn(remote_samdb,
994 "%s,%s" % (s, str(newdn))))
995 except ldb.LdbError, l:
998 self.errf.write("Demote successful\n")
1001 class cmd_domain_level(Command):
1002 """Raise domain and forest function levels."""
1004 synopsis = "%prog (show|raise <options>) [options]"
1006 takes_optiongroups = {
1007 "sambaopts": options.SambaOptions,
1008 "credopts": options.CredentialsOptions,
1009 "versionopts": options.VersionOptions,
1013 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1014 metavar="URL", dest="H"),
1015 Option("--quiet", help="Be quiet", action="store_true"),
1016 Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1017 help="The forest function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)"),
1018 Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1019 help="The domain function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)")
1022 takes_args = ["subcommand"]
1024 def run(self, subcommand, H=None, forest_level=None, domain_level=None,
1025 quiet=False, credopts=None, sambaopts=None, versionopts=None):
1026 lp = sambaopts.get_loadparm()
1027 creds = credopts.get_credentials(lp, fallback_machine=True)
1029 samdb = SamDB(url=H, session_info=system_session(),
1030 credentials=creds, lp=lp)
1032 domain_dn = samdb.domain_dn()
1034 res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
1035 scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
1036 assert len(res_forest) == 1
1038 res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1039 attrs=["msDS-Behavior-Version", "nTMixedDomain"])
1040 assert len(res_domain) == 1
1042 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
1043 scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
1044 attrs=["msDS-Behavior-Version"])
1045 assert len(res_dc_s) >= 1
1047 # default values, since "msDS-Behavior-Version" does not exist on Windows 2000 AD
1048 level_forest = DS_DOMAIN_FUNCTION_2000
1049 level_domain = DS_DOMAIN_FUNCTION_2000
1051 if "msDS-Behavior-Version" in res_forest[0]:
1052 level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
1053 if "msDS-Behavior-Version" in res_domain[0]:
1054 level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
1055 level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
1058 for msg in res_dc_s:
1059 if "msDS-Behavior-Version" in msg:
1060 if min_level_dc is None or int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
1061 min_level_dc = int(msg["msDS-Behavior-Version"][0])
1063 min_level_dc = DS_DOMAIN_FUNCTION_2000
1064 # well, this is the least
1067 if level_forest < DS_DOMAIN_FUNCTION_2000 or level_domain < DS_DOMAIN_FUNCTION_2000:
1068 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
1069 if min_level_dc < DS_DOMAIN_FUNCTION_2000:
1070 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
1071 if level_forest > level_domain:
1072 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
1073 if level_domain > min_level_dc:
1074 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
1076 if subcommand == "show":
1077 self.message("Domain and forest function level for domain '%s'" % domain_dn)
1078 if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1079 self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1080 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1081 self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1082 if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1083 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)!")
1087 if level_forest == DS_DOMAIN_FUNCTION_2000:
1089 elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
1090 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1091 elif level_forest == DS_DOMAIN_FUNCTION_2003:
1093 elif level_forest == DS_DOMAIN_FUNCTION_2008:
1095 elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
1097 elif level_forest == DS_DOMAIN_FUNCTION_2012:
1099 elif level_forest == DS_DOMAIN_FUNCTION_2012_R2:
1102 outstr = "higher than 2012 R2"
1103 self.message("Forest function level: (Windows) " + outstr)
1105 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1106 outstr = "2000 mixed (NT4 DC support)"
1107 elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
1109 elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
1110 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1111 elif level_domain == DS_DOMAIN_FUNCTION_2003:
1113 elif level_domain == DS_DOMAIN_FUNCTION_2008:
1115 elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
1117 elif level_domain == DS_DOMAIN_FUNCTION_2012:
1119 elif level_domain == DS_DOMAIN_FUNCTION_2012_R2:
1122 outstr = "higher than 2012 R2"
1123 self.message("Domain function level: (Windows) " + outstr)
1125 if min_level_dc == DS_DOMAIN_FUNCTION_2000:
1127 elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
1129 elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
1131 elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
1133 elif min_level_dc == DS_DOMAIN_FUNCTION_2012:
1135 elif min_level_dc == DS_DOMAIN_FUNCTION_2012_R2:
1138 outstr = "higher than 2012 R2"
1139 self.message("Lowest function level of a DC: (Windows) " + outstr)
1141 elif subcommand == "raise":
1144 if domain_level is not None:
1145 if domain_level == "2003":
1146 new_level_domain = DS_DOMAIN_FUNCTION_2003
1147 elif domain_level == "2008":
1148 new_level_domain = DS_DOMAIN_FUNCTION_2008
1149 elif domain_level == "2008_R2":
1150 new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
1151 elif domain_level == "2012":
1152 new_level_domain = DS_DOMAIN_FUNCTION_2012
1153 elif domain_level == "2012_R2":
1154 new_level_domain = DS_DOMAIN_FUNCTION_2012_R2
1156 if new_level_domain <= level_domain and level_domain_mixed == 0:
1157 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
1158 if new_level_domain > min_level_dc:
1159 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
1161 # Deactivate mixed/interim domain support
1162 if level_domain_mixed != 0:
1163 # Directly on the base DN
1165 m.dn = ldb.Dn(samdb, domain_dn)
1166 m["nTMixedDomain"] = ldb.MessageElement("0",
1167 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1171 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
1172 m["nTMixedDomain"] = ldb.MessageElement("0",
1173 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1176 except ldb.LdbError, (enum, emsg):
1177 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1180 # Directly on the base DN
1182 m.dn = ldb.Dn(samdb, domain_dn)
1183 m["msDS-Behavior-Version"]= ldb.MessageElement(
1184 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1185 "msDS-Behavior-Version")
1189 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
1190 + ",CN=Partitions,%s" % samdb.get_config_basedn())
1191 m["msDS-Behavior-Version"]= ldb.MessageElement(
1192 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1193 "msDS-Behavior-Version")
1196 except ldb.LdbError, (enum, emsg):
1197 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1200 level_domain = new_level_domain
1201 msgs.append("Domain function level changed!")
1203 if forest_level is not None:
1204 if forest_level == "2003":
1205 new_level_forest = DS_DOMAIN_FUNCTION_2003
1206 elif forest_level == "2008":
1207 new_level_forest = DS_DOMAIN_FUNCTION_2008
1208 elif forest_level == "2008_R2":
1209 new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
1210 elif forest_level == "2012":
1211 new_level_forest = DS_DOMAIN_FUNCTION_2012
1212 elif forest_level == "2012_R2":
1213 new_level_forest = DS_DOMAIN_FUNCTION_2012_R2
1215 if new_level_forest <= level_forest:
1216 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1217 if new_level_forest > level_domain:
1218 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1221 m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
1222 m["msDS-Behavior-Version"]= ldb.MessageElement(
1223 str(new_level_forest), ldb.FLAG_MOD_REPLACE,
1224 "msDS-Behavior-Version")
1226 msgs.append("Forest function level changed!")
1227 msgs.append("All changes applied successfully!")
1228 self.message("\n".join(msgs))
1230 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
1233 class cmd_domain_passwordsettings(Command):
1234 """Set password settings.
1236 Password complexity, password lockout policy, history length,
1237 minimum password length, the minimum and maximum password age) on
1238 a Samba AD DC server.
1240 Use against a Windows DC is possible, but group policy will override it.
1243 synopsis = "%prog (show|set <options>) [options]"
1245 takes_optiongroups = {
1246 "sambaopts": options.SambaOptions,
1247 "versionopts": options.VersionOptions,
1248 "credopts": options.CredentialsOptions,
1252 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1253 metavar="URL", dest="H"),
1254 Option("--quiet", help="Be quiet", action="store_true"),
1255 Option("--complexity", type="choice", choices=["on","off","default"],
1256 help="The password complexity (on | off | default). Default is 'on'"),
1257 Option("--store-plaintext", type="choice", choices=["on","off","default"],
1258 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1259 Option("--history-length",
1260 help="The password history length (<integer> | default). Default is 24.", type=str),
1261 Option("--min-pwd-length",
1262 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1263 Option("--min-pwd-age",
1264 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1265 Option("--max-pwd-age",
1266 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1267 Option("--account-lockout-duration",
1268 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),
1269 Option("--account-lockout-threshold",
1270 help="The number of bad password attempts allowed before locking out the account (<integer> | default). Default is 0 (never lock out).", type=str),
1271 Option("--reset-account-lockout-after",
1272 help="After this time is elapsed, the recorded number of attempts restarts from zero (<integer> | default). Default is 30.", type=str),
1275 takes_args = ["subcommand"]
1277 def run(self, subcommand, H=None, min_pwd_age=None, max_pwd_age=None,
1278 quiet=False, complexity=None, store_plaintext=None, history_length=None,
1279 min_pwd_length=None, account_lockout_duration=None, account_lockout_threshold=None,
1280 reset_account_lockout_after=None, credopts=None, sambaopts=None,
1282 lp = sambaopts.get_loadparm()
1283 creds = credopts.get_credentials(lp)
1285 samdb = SamDB(url=H, session_info=system_session(),
1286 credentials=creds, lp=lp)
1288 domain_dn = samdb.domain_dn()
1289 res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1290 attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1291 "minPwdAge", "maxPwdAge", "lockoutDuration", "lockoutThreshold",
1292 "lockOutObservationWindow"])
1293 assert(len(res) == 1)
1295 pwd_props = int(res[0]["pwdProperties"][0])
1296 pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
1297 cur_min_pwd_len = int(res[0]["minPwdLength"][0])
1299 cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1300 if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000:
1303 cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1304 cur_account_lockout_threshold = int(res[0]["lockoutThreshold"][0])
1306 if int(res[0]["lockoutDuration"][0]) == -0x8000000000000000:
1307 cur_account_lockout_duration = 0
1309 cur_account_lockout_duration = abs(int(res[0]["lockoutDuration"][0])) / (1e7 * 60)
1310 cur_reset_account_lockout_after = abs(int(res[0]["lockOutObservationWindow"][0])) / (1e7 * 60)
1311 except Exception, e:
1312 raise CommandError("Could not retrieve password properties!", e)
1314 if subcommand == "show":
1315 self.message("Password informations for domain '%s'" % domain_dn)
1317 if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
1318 self.message("Password complexity: on")
1320 self.message("Password complexity: off")
1321 if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
1322 self.message("Store plaintext passwords: on")
1324 self.message("Store plaintext passwords: off")
1325 self.message("Password history length: %d" % pwd_hist_len)
1326 self.message("Minimum password length: %d" % cur_min_pwd_len)
1327 self.message("Minimum password age (days): %d" % cur_min_pwd_age)
1328 self.message("Maximum password age (days): %d" % cur_max_pwd_age)
1329 self.message("Account lockout duration (mins): %d" % cur_account_lockout_duration)
1330 self.message("Account lockout threshold (attempts): %d" % cur_account_lockout_threshold)
1331 self.message("Reset account lockout after (mins): %d" % cur_reset_account_lockout_after)
1332 elif subcommand == "set":
1335 m.dn = ldb.Dn(samdb, domain_dn)
1337 if complexity is not None:
1338 if complexity == "on" or complexity == "default":
1339 pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
1340 msgs.append("Password complexity activated!")
1341 elif complexity == "off":
1342 pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
1343 msgs.append("Password complexity deactivated!")
1345 if store_plaintext is not None:
1346 if store_plaintext == "on" or store_plaintext == "default":
1347 pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1348 msgs.append("Plaintext password storage for changed passwords activated!")
1349 elif store_plaintext == "off":
1350 pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
1351 msgs.append("Plaintext password storage for changed passwords deactivated!")
1353 if complexity is not None or store_plaintext is not None:
1354 m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
1355 ldb.FLAG_MOD_REPLACE, "pwdProperties")
1357 if history_length is not None:
1358 if history_length == "default":
1361 pwd_hist_len = int(history_length)
1363 if pwd_hist_len < 0 or pwd_hist_len > 24:
1364 raise CommandError("Password history length must be in the range of 0 to 24!")
1366 m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
1367 ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
1368 msgs.append("Password history length changed!")
1370 if min_pwd_length is not None:
1371 if min_pwd_length == "default":
1374 min_pwd_len = int(min_pwd_length)
1376 if min_pwd_len < 0 or min_pwd_len > 14:
1377 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1379 m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
1380 ldb.FLAG_MOD_REPLACE, "minPwdLength")
1381 msgs.append("Minimum password length changed!")
1383 if min_pwd_age is not None:
1384 if min_pwd_age == "default":
1387 min_pwd_age = int(min_pwd_age)
1389 if min_pwd_age < 0 or min_pwd_age > 998:
1390 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1393 min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
1395 m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
1396 ldb.FLAG_MOD_REPLACE, "minPwdAge")
1397 msgs.append("Minimum password age changed!")
1399 if max_pwd_age is not None:
1400 if max_pwd_age == "default":
1403 max_pwd_age = int(max_pwd_age)
1405 if max_pwd_age < 0 or max_pwd_age > 999:
1406 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1409 if max_pwd_age == 0:
1410 max_pwd_age_ticks = -0x8000000000000000
1412 max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
1414 m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
1415 ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1416 msgs.append("Maximum password age changed!")
1418 if account_lockout_duration is not None:
1419 if account_lockout_duration == "default":
1420 account_lockout_duration = 30
1422 account_lockout_duration = int(account_lockout_duration)
1424 if account_lockout_duration < 0 or account_lockout_duration > 99999:
1425 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1428 if account_lockout_duration == 0:
1429 account_lockout_duration_ticks = -0x8000000000000000
1431 account_lockout_duration_ticks = -int(account_lockout_duration * (60 * 1e7))
1433 m["lockoutDuration"] = ldb.MessageElement(str(account_lockout_duration_ticks),
1434 ldb.FLAG_MOD_REPLACE, "lockoutDuration")
1435 msgs.append("Account lockout duration changed!")
1437 if account_lockout_threshold is not None:
1438 if account_lockout_threshold == "default":
1439 account_lockout_threshold = 0
1441 account_lockout_threshold = int(account_lockout_threshold)
1443 m["lockoutThreshold"] = ldb.MessageElement(str(account_lockout_threshold),
1444 ldb.FLAG_MOD_REPLACE, "lockoutThreshold")
1445 msgs.append("Account lockout threshold changed!")
1447 if reset_account_lockout_after is not None:
1448 if reset_account_lockout_after == "default":
1449 reset_account_lockout_after = 30
1451 reset_account_lockout_after = int(reset_account_lockout_after)
1453 if reset_account_lockout_after < 0 or reset_account_lockout_after > 99999:
1454 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1457 if reset_account_lockout_after == 0:
1458 reset_account_lockout_after_ticks = -0x8000000000000000
1460 reset_account_lockout_after_ticks = -int(reset_account_lockout_after * (60 * 1e7))
1462 m["lockOutObservationWindow"] = ldb.MessageElement(str(reset_account_lockout_after_ticks),
1463 ldb.FLAG_MOD_REPLACE, "lockOutObservationWindow")
1464 msgs.append("Duration to reset account lockout after changed!")
1466 if max_pwd_age > 0 and min_pwd_age >= max_pwd_age:
1467 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
1470 raise CommandError("You must specify at least one option to set. Try --help")
1472 msgs.append("All changes applied successfully!")
1473 self.message("\n".join(msgs))
1475 raise CommandError("Wrong argument '%s'!" % subcommand)
1478 class cmd_domain_classicupgrade(Command):
1479 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1481 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1482 the testparm utility from your classic installation (with --testparm).
1485 synopsis = "%prog [options] <classic_smb_conf>"
1487 takes_optiongroups = {
1488 "sambaopts": options.SambaOptions,
1489 "versionopts": options.VersionOptions
1493 Option("--dbdir", type="string", metavar="DIR",
1494 help="Path to samba classic DC database directory"),
1495 Option("--testparm", type="string", metavar="PATH",
1496 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1497 Option("--targetdir", type="string", metavar="DIR",
1498 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1499 Option("--quiet", help="Be quiet", action="store_true"),
1500 Option("--verbose", help="Be verbose", action="store_true"),
1501 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
1502 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1503 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1504 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1505 "BIND9_DLZ uses samba4 AD to store zone information, "
1506 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1507 default="SAMBA_INTERNAL")
1511 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
1512 action="store_true"),
1513 Option("--use-xattrs", type="choice", choices=["yes","no","auto"],
1514 metavar="[yes|no|auto]",
1515 help="Define if we should use the native fs capabilities or a tdb file for "
1516 "storing attributes likes ntacl when --use-ntvfs is set. "
1517 "auto tries to make an inteligent guess based on the user rights and system capabilities",
1520 if samba.is_ntvfs_fileserver_built():
1521 takes_options.extend(ntvfs_options)
1523 takes_args = ["smbconf"]
1525 def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
1526 quiet=False, verbose=False, use_xattrs="auto", sambaopts=None, versionopts=None,
1527 dns_backend=None, use_ntvfs=False):
1529 if not os.path.exists(smbconf):
1530 raise CommandError("File %s does not exist" % smbconf)
1532 if testparm and not os.path.exists(testparm):
1533 raise CommandError("Testparm utility %s does not exist" % testparm)
1535 if dbdir and not os.path.exists(dbdir):
1536 raise CommandError("Directory %s does not exist" % dbdir)
1538 if not dbdir and not testparm:
1539 raise CommandError("Please specify either dbdir or testparm")
1541 logger = self.get_logger()
1543 logger.setLevel(logging.DEBUG)
1545 logger.setLevel(logging.WARNING)
1547 logger.setLevel(logging.INFO)
1549 if dbdir and testparm:
1550 logger.warning("both dbdir and testparm specified, ignoring dbdir.")
1553 lp = sambaopts.get_loadparm()
1555 s3conf = s3param.get_context()
1558 s3conf.set("realm", sambaopts.realm)
1560 if targetdir is not None:
1561 if not os.path.isdir(targetdir):
1565 if use_xattrs == "yes":
1567 elif use_xattrs == "auto" and use_ntvfs == False:
1569 elif use_ntvfs == False:
1570 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
1571 "Please re-run with --use-xattrs omitted.")
1572 elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
1574 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
1576 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
1579 samba.ntacls.setntacl(lp, tmpfile.name,
1580 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1583 # FIXME: Don't catch all exceptions here
1584 logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. "
1585 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1589 # Set correct default values from dbdir or testparm
1592 paths["state directory"] = dbdir
1593 paths["private dir"] = dbdir
1594 paths["lock directory"] = dbdir
1595 paths["smb passwd file"] = dbdir + "/smbpasswd"
1597 paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
1598 paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
1599 paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
1600 paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
1601 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1602 # "state directory", instead make use of "lock directory"
1603 if len(paths["state directory"]) == 0:
1604 paths["state directory"] = paths["lock directory"]
1607 s3conf.set(p, paths[p])
1609 # load smb.conf parameters
1610 logger.info("Reading smb.conf")
1611 s3conf.load(smbconf)
1612 samba3 = Samba3(smbconf, s3conf)
1614 logger.info("Provisioning")
1615 upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
1616 useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
1619 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
1620 __doc__ = cmd_domain_classicupgrade.__doc__
1622 # This command is present for backwards compatibility only,
1623 # and should not be shown.
1627 class LocalDCCredentialsOptions(options.CredentialsOptions):
1628 def __init__(self, parser):
1629 options.CredentialsOptions.__init__(self, parser, special_name="local-dc")
1631 class DomainTrustCommand(Command):
1632 """List domain trusts."""
1635 Command.__init__(self)
1636 self.local_lp = None
1638 self.local_server = None
1639 self.local_binding_string = None
1640 self.local_creds = None
1642 self.remote_server = None
1643 self.remote_binding_string = None
1644 self.remote_creds = None
1646 def _uint32(self, v):
1647 return ctypes.c_uint32(v).value
1649 def check_runtime_error(self, runtime, val):
1653 err32 = self._uint32(runtime[0])
1659 class LocalRuntimeError(CommandError):
1660 def __init__(exception_self, self, runtime, message):
1661 err32 = self._uint32(runtime[0])
1663 msg = "LOCAL_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1664 self.local_server, message, err32, errstr)
1665 CommandError.__init__(exception_self, msg)
1667 class RemoteRuntimeError(CommandError):
1668 def __init__(exception_self, self, runtime, message):
1669 err32 = self._uint32(runtime[0])
1671 msg = "REMOTE_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1672 self.remote_server, message, err32, errstr)
1673 CommandError.__init__(exception_self, msg)
1675 class LocalLdbError(CommandError):
1676 def __init__(exception_self, self, ldb_error, message):
1677 errval = ldb_error[0]
1678 errstr = ldb_error[1]
1679 msg = "LOCAL_DC[%s]: %s - ERROR(%d) - %s" % (
1680 self.local_server, message, errval, errstr)
1681 CommandError.__init__(exception_self, msg)
1683 def setup_local_server(self, sambaopts, localdcopts):
1684 if self.local_server is not None:
1685 return self.local_server
1687 lp = sambaopts.get_loadparm()
1689 local_server = localdcopts.ipaddress
1690 if local_server is None:
1691 server_role = lp.server_role()
1692 if server_role != "ROLE_ACTIVE_DIRECTORY_DC":
1693 raise CommandError("Invalid server_role %s" % (server_role))
1694 local_server = lp.get('netbios name')
1695 local_transport = "ncalrpc"
1696 local_binding_options = ""
1697 local_binding_options += ",auth_type=ncalrpc_as_system"
1698 local_ldap_url = None
1701 local_transport = "ncacn_np"
1702 local_binding_options = ""
1703 local_ldap_url = "ldap://%s" % local_server
1704 local_creds = localdcopts.get_credentials(lp)
1708 self.local_server = local_server
1709 self.local_binding_string = "%s:%s[%s]" % (local_transport, local_server, local_binding_options)
1710 self.local_ldap_url = local_ldap_url
1711 self.local_creds = local_creds
1712 return self.local_server
1714 def new_local_lsa_connection(self):
1715 return lsa.lsarpc(self.local_binding_string, self.local_lp, self.local_creds)
1717 def new_local_netlogon_connection(self):
1718 return netlogon.netlogon(self.local_binding_string, self.local_lp, self.local_creds)
1720 def new_local_ldap_connection(self):
1721 return SamDB(url=self.local_ldap_url,
1722 session_info=system_session(),
1723 credentials=self.local_creds,
1726 def setup_remote_server(self, credopts, domain,
1728 require_writable=True):
1731 assert require_writable
1733 if self.remote_server is not None:
1734 return self.remote_server
1736 self.remote_server = "__unknown__remote_server__.%s" % domain
1737 assert self.local_server is not None
1739 remote_creds = credopts.get_credentials(self.local_lp)
1740 remote_server = credopts.ipaddress
1741 remote_binding_options = ""
1743 # TODO: we should also support NT4 domains
1744 # we could use local_netlogon.netr_DsRGetDCNameEx2() with the remote domain name
1745 # and delegate NBT or CLDAP to the local netlogon server
1747 remote_net = Net(remote_creds, self.local_lp, server=remote_server)
1748 remote_flags = nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS
1749 if require_writable:
1750 remote_flags |= nbt.NBT_SERVER_WRITABLE
1752 remote_flags |= nbt.NBT_SERVER_PDC
1753 remote_info = remote_net.finddc(flags=remote_flags, domain=domain, address=remote_server)
1755 raise CommandError("Failed to find a writeable DC for domain '%s'" % domain)
1757 nbt.NBT_SERVER_PDC: "PDC",
1758 nbt.NBT_SERVER_GC: "GC",
1759 nbt.NBT_SERVER_LDAP: "LDAP",
1760 nbt.NBT_SERVER_DS: "DS",
1761 nbt.NBT_SERVER_KDC: "KDC",
1762 nbt.NBT_SERVER_TIMESERV: "TIMESERV",
1763 nbt.NBT_SERVER_CLOSEST: "CLOSEST",
1764 nbt.NBT_SERVER_WRITABLE: "WRITABLE",
1765 nbt.NBT_SERVER_GOOD_TIMESERV: "GOOD_TIMESERV",
1766 nbt.NBT_SERVER_NDNC: "NDNC",
1767 nbt.NBT_SERVER_SELECT_SECRET_DOMAIN_6: "SELECT_SECRET_DOMAIN_6",
1768 nbt.NBT_SERVER_FULL_SECRET_DOMAIN_6: "FULL_SECRET_DOMAIN_6",
1769 nbt.NBT_SERVER_ADS_WEB_SERVICE: "ADS_WEB_SERVICE",
1770 nbt.NBT_SERVER_DS_8: "DS_8",
1771 nbt.NBT_SERVER_HAS_DNS_NAME: "HAS_DNS_NAME",
1772 nbt.NBT_SERVER_IS_DEFAULT_NC: "IS_DEFAULT_NC",
1773 nbt.NBT_SERVER_FOREST_ROOT: "FOREST_ROOT",
1775 server_type_string = self.generic_bitmap_to_string(flag_map,
1776 remote_info.server_type, names_only=True)
1777 self.outf.write("RemoteDC Netbios[%s] DNS[%s] ServerType[%s]\n" % (
1778 remote_info.pdc_name,
1779 remote_info.pdc_dns_name,
1780 server_type_string))
1782 self.remote_server = remote_info.pdc_dns_name
1783 self.remote_binding_string="ncacn_np:%s[%s]" % (self.remote_server, remote_binding_options)
1784 self.remote_creds = remote_creds
1785 return self.remote_server
1787 def new_remote_lsa_connection(self):
1788 return lsa.lsarpc(self.remote_binding_string, self.local_lp, self.remote_creds)
1790 def new_remote_netlogon_connection(self):
1791 return netlogon.netlogon(self.remote_binding_string, self.local_lp, self.remote_creds)
1793 def get_lsa_info(self, conn, policy_access):
1794 objectAttr = lsa.ObjectAttribute()
1795 objectAttr.sec_qos = lsa.QosInfo()
1797 policy = conn.OpenPolicy2(''.decode('utf-8'),
1798 objectAttr, policy_access)
1800 info = conn.QueryInfoPolicy2(policy, lsa.LSA_POLICY_INFO_DNS)
1802 return (policy, info)
1804 def get_netlogon_dc_info(self, conn, server):
1805 info = conn.netr_DsRGetDCNameEx2(server,
1806 None, 0, None, None, None,
1807 netlogon.DS_RETURN_DNS_NAME)
1810 def netr_DomainTrust_to_name(self, t):
1811 if t.trust_type == lsa.LSA_TRUST_TYPE_DOWNLEVEL:
1812 return t.netbios_name
1816 def netr_DomainTrust_to_type(self, a, t):
1818 primary_parent = None
1820 if _t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
1822 if not _t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1823 primary_parent = a[_t.parent_index]
1826 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1827 if t is primary_parent:
1830 if t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1833 parent = a[t.parent_index]
1834 if parent is primary:
1839 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1844 def netr_DomainTrust_to_transitive(self, t):
1845 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1848 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE:
1851 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1856 def netr_DomainTrust_to_direction(self, t):
1857 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND and \
1858 t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1861 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND:
1864 if t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1869 def generic_enum_to_string(self, e_dict, v, names_only=False):
1873 v32 = self._uint32(v)
1874 w = "__unknown__%08X__" % v32
1876 r = "0x%x (%s)" % (v, w)
1879 def generic_bitmap_to_string(self, b_dict, v, names_only=False):
1884 for b in sorted(b_dict.keys()):
1891 c32 = self._uint32(c)
1892 s += ["__unknown_%08X__" % c32]
1897 r = "0x%x (%s)" % (v, w)
1900 def trustType_string(self, v):
1902 lsa.LSA_TRUST_TYPE_DOWNLEVEL : "DOWNLEVEL",
1903 lsa.LSA_TRUST_TYPE_UPLEVEL : "UPLEVEL",
1904 lsa.LSA_TRUST_TYPE_MIT : "MIT",
1905 lsa.LSA_TRUST_TYPE_DCE : "DCE",
1907 return self.generic_enum_to_string(types, v)
1909 def trustDirection_string(self, v):
1911 lsa.LSA_TRUST_DIRECTION_INBOUND |
1912 lsa.LSA_TRUST_DIRECTION_OUTBOUND : "BOTH",
1913 lsa.LSA_TRUST_DIRECTION_INBOUND : "INBOUND",
1914 lsa.LSA_TRUST_DIRECTION_OUTBOUND : "OUTBOUND",
1916 return self.generic_enum_to_string(directions, v)
1918 def trustAttributes_string(self, v):
1920 lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE : "NON_TRANSITIVE",
1921 lsa.LSA_TRUST_ATTRIBUTE_UPLEVEL_ONLY : "UPLEVEL_ONLY",
1922 lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN : "QUARANTINED_DOMAIN",
1923 lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE : "FOREST_TRANSITIVE",
1924 lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION : "CROSS_ORGANIZATION",
1925 lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST : "WITHIN_FOREST",
1926 lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL : "TREAT_AS_EXTERNAL",
1927 lsa.LSA_TRUST_ATTRIBUTE_USES_RC4_ENCRYPTION : "USES_RC4_ENCRYPTION",
1929 return self.generic_bitmap_to_string(attributes, v)
1931 def kerb_EncTypes_string(self, v):
1933 security.KERB_ENCTYPE_DES_CBC_CRC : "DES_CBC_CRC",
1934 security.KERB_ENCTYPE_DES_CBC_MD5 : "DES_CBC_MD5",
1935 security.KERB_ENCTYPE_RC4_HMAC_MD5 : "RC4_HMAC_MD5",
1936 security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96 : "AES128_CTS_HMAC_SHA1_96",
1937 security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96 : "AES256_CTS_HMAC_SHA1_96",
1938 security.KERB_ENCTYPE_FAST_SUPPORTED : "FAST_SUPPORTED",
1939 security.KERB_ENCTYPE_COMPOUND_IDENTITY_SUPPORTED : "COMPOUND_IDENTITY_SUPPORTED",
1940 security.KERB_ENCTYPE_CLAIMS_SUPPORTED : "CLAIMS_SUPPORTED",
1941 security.KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED : "RESOURCE_SID_COMPRESSION_DISABLED",
1943 return self.generic_bitmap_to_string(enctypes, v)
1945 def entry_tln_status(self, e_flags, ):
1947 return "Status[Enabled]"
1950 lsa.LSA_TLN_DISABLED_NEW : "Disabled-New",
1951 lsa.LSA_TLN_DISABLED_ADMIN : "Disabled",
1952 lsa.LSA_TLN_DISABLED_CONFLICT : "Disabled-Conflicting",
1954 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
1956 def entry_dom_status(self, e_flags):
1958 return "Status[Enabled]"
1961 lsa.LSA_SID_DISABLED_ADMIN : "Disabled-SID",
1962 lsa.LSA_SID_DISABLED_CONFLICT : "Disabled-SID-Conflicting",
1963 lsa.LSA_NB_DISABLED_ADMIN : "Disabled-NB",
1964 lsa.LSA_NB_DISABLED_CONFLICT : "Disabled-NB-Conflicting",
1966 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
1968 def write_forest_trust_info(self, fti, tln=None, collisions=None):
1970 tln_string = " TDO[%s]" % tln
1974 self.outf.write("Namespaces[%d]%s:\n" % (
1975 len(fti.entries), tln_string))
1977 for i in xrange(0, len(fti.entries)):
1981 collision_string = ""
1983 if collisions is not None:
1984 for c in collisions.entries:
1988 collision_string = " Collision[%s]" % (c.name.string)
1990 d = e.forest_trust_data
1991 if e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
1992 self.outf.write("TLN: %-32s DNS[*.%s]%s\n" % (
1993 self.entry_tln_status(flags),
1994 d.string, collision_string))
1995 elif e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
1996 self.outf.write("TLN_EX: %-29s DNS[*.%s]\n" % (
1998 elif e.type == lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
1999 self.outf.write("DOM: %-32s DNS[%s] Netbios[%s] SID[%s]%s\n" % (
2000 self.entry_dom_status(flags),
2001 d.dns_domain_name.string,
2002 d.netbios_domain_name.string,
2003 d.domain_sid, collision_string))
2006 class cmd_domain_trust_list(DomainTrustCommand):
2007 """List domain trusts."""
2009 synopsis = "%prog [options]"
2011 takes_optiongroups = {
2012 "sambaopts": options.SambaOptions,
2013 "versionopts": options.VersionOptions,
2014 "localdcopts": LocalDCCredentialsOptions,
2020 def run(self, sambaopts=None, versionopts=None, localdcopts=None):
2022 local_server = self.setup_local_server(sambaopts, localdcopts)
2024 local_netlogon = self.new_local_netlogon_connection()
2025 except RuntimeError as error:
2026 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2029 local_netlogon_trusts = local_netlogon.netr_DsrEnumerateDomainTrusts(local_server,
2030 netlogon.NETR_TRUST_FLAG_IN_FOREST |
2031 netlogon.NETR_TRUST_FLAG_OUTBOUND |
2032 netlogon.NETR_TRUST_FLAG_INBOUND)
2033 except RuntimeError as error:
2034 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
2035 # TODO: we could implement a fallback to lsa.EnumTrustDom()
2036 raise CommandError("LOCAL_DC[%s]: netr_DsrEnumerateDomainTrusts not supported." % (
2038 raise self.LocalRuntimeError(self, error, "netr_DsrEnumerateDomainTrusts failed")
2040 a = local_netlogon_trusts.array
2042 if t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
2044 self.outf.write("%-14s %-15s %-19s %s\n" % (
2045 "Type[%s]" % self.netr_DomainTrust_to_type(a, t),
2046 "Transitive[%s]" % self.netr_DomainTrust_to_transitive(t),
2047 "Direction[%s]" % self.netr_DomainTrust_to_direction(t),
2048 "Name[%s]" % self.netr_DomainTrust_to_name(t)))
2051 class cmd_domain_trust_show(DomainTrustCommand):
2052 """Show trusted domain details."""
2054 synopsis = "%prog NAME [options]"
2056 takes_optiongroups = {
2057 "sambaopts": options.SambaOptions,
2058 "versionopts": options.VersionOptions,
2059 "localdcopts": LocalDCCredentialsOptions,
2065 takes_args = ["domain"]
2067 def run(self, domain, sambaopts=None, versionopts=None, localdcopts=None):
2069 local_server = self.setup_local_server(sambaopts, localdcopts)
2071 local_lsa = self.new_local_lsa_connection()
2072 except RuntimeError as error:
2073 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2076 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2077 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2078 except RuntimeError as error:
2079 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2081 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2082 local_lsa_info.name.string,
2083 local_lsa_info.dns_domain.string,
2084 local_lsa_info.sid))
2086 lsaString = lsa.String()
2087 lsaString.string = domain
2089 local_tdo_full = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2090 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2091 local_tdo_info = local_tdo_full.info_ex
2092 local_tdo_posix = local_tdo_full.posix_offset
2093 except NTSTATUSError as error:
2094 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2095 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2097 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(FULL_INFO) failed")
2100 local_tdo_enctypes = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2101 lsaString, lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES)
2102 except NTSTATUSError as error:
2103 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_PARAMETER):
2105 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_INFO_CLASS):
2108 if error is not None:
2109 raise self.LocalRuntimeError(self, error,
2110 "QueryTrustedDomainInfoByName(SUPPORTED_ENCRYPTION_TYPES) failed")
2112 local_tdo_enctypes = lsa.TrustDomainInfoSupportedEncTypes()
2113 local_tdo_enctypes.enc_types = 0
2116 local_tdo_forest = None
2117 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2118 local_tdo_forest = local_lsa.lsaRQueryForestTrustInformation(local_policy,
2119 lsaString, lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
2120 except RuntimeError as error:
2121 if self.check_runtime_error(error, ntstatus.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE):
2123 if self.check_runtime_error(error, ntstatus.NT_STATUS_NOT_FOUND):
2125 if error is not None:
2126 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation failed")
2128 local_tdo_forest = lsa.ForestTrustInformation()
2129 local_tdo_forest.count = 0
2130 local_tdo_forest.entries = []
2132 self.outf.write("TrusteDomain:\n\n");
2133 self.outf.write("NetbiosName: %s\n" % local_tdo_info.netbios_name.string)
2134 if local_tdo_info.netbios_name.string != local_tdo_info.domain_name.string:
2135 self.outf.write("DnsName: %s\n" % local_tdo_info.domain_name.string)
2136 self.outf.write("SID: %s\n" % local_tdo_info.sid)
2137 self.outf.write("Type: %s\n" % self.trustType_string(local_tdo_info.trust_type))
2138 self.outf.write("Direction: %s\n" % self.trustDirection_string(local_tdo_info.trust_direction))
2139 self.outf.write("Attributes: %s\n" % self.trustAttributes_string(local_tdo_info.trust_attributes))
2140 posix_offset_u32 = ctypes.c_uint32(local_tdo_posix.posix_offset).value
2141 posix_offset_i32 = ctypes.c_int32(local_tdo_posix.posix_offset).value
2142 self.outf.write("PosixOffset: 0x%08X (%d)\n" % (posix_offset_u32, posix_offset_i32))
2143 self.outf.write("kerb_EncTypes: %s\n" % self.kerb_EncTypes_string(local_tdo_enctypes.enc_types))
2145 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2146 self.write_forest_trust_info(local_tdo_forest,
2147 tln=local_tdo_info.domain_name.string)
2151 class cmd_domain_trust_create(DomainTrustCommand):
2152 """Create a domain or forest trust."""
2154 synopsis = "%prog DOMAIN [options]"
2156 takes_optiongroups = {
2157 "sambaopts": options.SambaOptions,
2158 "versionopts": options.VersionOptions,
2159 "credopts": options.CredentialsOptions,
2160 "localdcopts": LocalDCCredentialsOptions,
2164 Option("--type", type="choice", metavar="TYPE",
2165 choices=["external", "forest"],
2166 help="The type of the trust: 'external' or 'forest'.",
2168 default="external"),
2169 Option("--direction", type="choice", metavar="DIRECTION",
2170 choices=["incoming", "outgoing", "both"],
2171 help="The trust direction: 'incoming', 'outgoing' or 'both'.",
2172 dest='trust_direction',
2174 Option("--create-location", type="choice", metavar="LOCATION",
2175 choices=["local", "both"],
2176 help="Where to create the trusted domain object: 'local' or 'both'.",
2177 dest='create_location',
2179 Option("--cross-organisation", action="store_true",
2180 help="The related domains does not belong to the same organisation.",
2181 dest='cross_organisation',
2183 Option("--quarantined", type="choice", metavar="yes|no",
2184 choices=["yes", "no", None],
2185 help="Special SID filtering rules are applied to the trust. "
2186 "With --type=external the default is yes. "
2187 "With --type=forest the default is no.",
2188 dest='quarantined_arg',
2190 Option("--not-transitive", action="store_true",
2191 help="The forest trust is not transitive.",
2192 dest='not_transitive',
2194 Option("--treat-as-external", action="store_true",
2195 help="The treat the forest trust as external.",
2196 dest='treat_as_external',
2198 Option("--no-aes-keys", action="store_false",
2199 help="The trust uses aes kerberos keys.",
2200 dest='use_aes_keys',
2202 Option("--skip-validation", action="store_false",
2203 help="Skip validation of the trust.",
2208 takes_args = ["domain"]
2210 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2211 trust_type=None, trust_direction=None, create_location=None,
2212 cross_organisation=False, quarantined_arg=None,
2213 not_transitive=False, treat_as_external=False,
2214 use_aes_keys=False, validate=True):
2216 lsaString = lsa.String()
2219 if quarantined_arg is None:
2220 if trust_type == 'external':
2222 elif quarantined_arg == 'yes':
2225 if trust_type != 'forest':
2227 raise CommandError("--not-transitive requires --type=forest")
2228 if treat_as_external:
2229 raise CommandError("--treat-as-external requires --type=forest")
2233 enc_types = lsa.TrustDomainInfoSupportedEncTypes()
2234 enc_types.enc_types = security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96
2235 enc_types.enc_types |= security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96
2237 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2238 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2239 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2241 local_trust_info = lsa.TrustDomainInfoInfoEx()
2242 local_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2243 local_trust_info.trust_direction = 0
2244 if trust_direction == "both":
2245 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2246 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2247 elif trust_direction == "incoming":
2248 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2249 elif trust_direction == "outgoing":
2250 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2251 local_trust_info.trust_attributes = 0
2252 if cross_organisation:
2253 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2255 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2256 if trust_type == "forest":
2257 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2259 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2260 if treat_as_external:
2261 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2263 def get_password(name):
2266 if password is not None and password is not '':
2268 password = getpass("New %s Password: " % name)
2269 passwordverify = getpass("Retype %s Password: " % name)
2270 if not password == passwordverify:
2272 self.outf.write("Sorry, passwords do not match.\n")
2274 incoming_secret = None
2275 outgoing_secret = None
2276 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2277 if create_location == "local":
2278 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2279 incoming_password = get_password("Incoming Trust")
2280 incoming_secret = string_to_byte_array(incoming_password.encode('utf-16-le'))
2281 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2282 outgoing_password = get_password("Outgoing Trust")
2283 outgoing_secret = string_to_byte_array(outgoing_password.encode('utf-16-le'))
2285 remote_trust_info = None
2287 # We use 240 random bytes.
2288 # Windows uses 28 or 240 random bytes. I guess it's
2289 # based on the trust type external vs. forest.
2291 # The initial trust password can be up to 512 bytes
2292 # while the versioned passwords used for periodic updates
2293 # can only be up to 498 bytes, as netr_ServerPasswordSet2()
2294 # needs to pass the NL_PASSWORD_VERSION structure within the
2295 # 512 bytes and a 2 bytes confounder is required.
2297 def random_trust_secret(length):
2298 pw = samba.generate_random_machine_password(length/2, length/2)
2299 return string_to_byte_array(pw.encode('utf-16-le'))
2301 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2302 incoming_secret = random_trust_secret(240)
2303 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2304 outgoing_secret = random_trust_secret(240)
2306 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2307 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2309 remote_trust_info = lsa.TrustDomainInfoInfoEx()
2310 remote_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2311 remote_trust_info.trust_direction = 0
2312 if trust_direction == "both":
2313 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2314 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2315 elif trust_direction == "incoming":
2316 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2317 elif trust_direction == "outgoing":
2318 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2319 remote_trust_info.trust_attributes = 0
2320 if cross_organisation:
2321 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2323 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2324 if trust_type == "forest":
2325 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2327 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2328 if treat_as_external:
2329 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2331 local_server = self.setup_local_server(sambaopts, localdcopts)
2333 local_lsa = self.new_local_lsa_connection()
2334 except RuntimeError as error:
2335 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2338 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2339 except RuntimeError as error:
2340 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2342 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2343 local_lsa_info.name.string,
2344 local_lsa_info.dns_domain.string,
2345 local_lsa_info.sid))
2348 remote_server = self.setup_remote_server(credopts, domain)
2349 except RuntimeError as error:
2350 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2353 remote_lsa = self.new_remote_lsa_connection()
2354 except RuntimeError as error:
2355 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2358 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2359 except RuntimeError as error:
2360 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2362 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2363 remote_lsa_info.name.string,
2364 remote_lsa_info.dns_domain.string,
2365 remote_lsa_info.sid))
2367 local_trust_info.domain_name.string = remote_lsa_info.dns_domain.string
2368 local_trust_info.netbios_name.string = remote_lsa_info.name.string
2369 local_trust_info.sid = remote_lsa_info.sid
2371 if remote_trust_info:
2372 remote_trust_info.domain_name.string = local_lsa_info.dns_domain.string
2373 remote_trust_info.netbios_name.string = local_lsa_info.name.string
2374 remote_trust_info.sid = local_lsa_info.sid
2377 lsaString.string = local_trust_info.domain_name.string
2378 local_old_netbios = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2379 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2380 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2381 except NTSTATUSError as error:
2382 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2383 raise self.LocalRuntimeError(self, error,
2384 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2388 lsaString.string = local_trust_info.netbios_name.string
2389 local_old_dns = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2390 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2391 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2392 except NTSTATUSError as error:
2393 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2394 raise self.LocalRuntimeError(self, error,
2395 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2398 if remote_trust_info:
2400 lsaString.string = remote_trust_info.domain_name.string
2401 remote_old_netbios = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2402 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2403 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2404 except NTSTATUSError as error:
2405 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2406 raise self.RemoteRuntimeError(self, error,
2407 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2411 lsaString.string = remote_trust_info.netbios_name.string
2412 remote_old_dns = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2413 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2414 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2415 except NTSTATUSError as error:
2416 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2417 raise self.RemoteRuntimeError(self, error,
2418 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2422 local_netlogon = self.new_local_netlogon_connection()
2423 except RuntimeError as error:
2424 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2427 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
2428 except RuntimeError as error:
2429 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
2431 if remote_trust_info:
2433 remote_netlogon = self.new_remote_netlogon_connection()
2434 except RuntimeError as error:
2435 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2438 remote_netlogon_info = self.get_netlogon_dc_info(remote_netlogon, remote_server)
2439 except RuntimeError as error:
2440 raise self.RemoteRuntimeError(self, error, "failed to get netlogon dc info")
2442 def generate_AuthInOutBlob(secret, update_time):
2444 blob = drsblobs.trustAuthInOutBlob()
2449 clear = drsblobs.AuthInfoClear()
2450 clear.size = len(secret)
2451 clear.password = secret
2453 info = drsblobs.AuthenticationInformation()
2454 info.LastUpdateTime = samba.unix2nttime(update_time)
2455 info.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
2456 info.AuthInfo = clear
2458 array = drsblobs.AuthenticationInformationArray()
2460 array.array = [info]
2462 blob = drsblobs.trustAuthInOutBlob()
2464 blob.current = array
2468 def generate_AuthInfoInternal(session_key, incoming=None, outgoing=None):
2469 confounder = [0] * 512
2470 for i in range(len(confounder)):
2471 confounder[i] = random.randint(0, 255)
2473 trustpass = drsblobs.trustDomainPasswords()
2475 trustpass.confounder = confounder
2476 trustpass.outgoing = outgoing
2477 trustpass.incoming = incoming
2479 trustpass_blob = ndr_pack(trustpass)
2481 encrypted_trustpass = arcfour_encrypt(session_key, trustpass_blob)
2483 auth_blob = lsa.DATA_BUF2()
2484 auth_blob.size = len(encrypted_trustpass)
2485 auth_blob.data = string_to_byte_array(encrypted_trustpass)
2487 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
2488 auth_info.auth_blob = auth_blob
2492 update_time = samba.current_unix_time()
2493 incoming_blob = generate_AuthInOutBlob(incoming_secret, update_time)
2494 outgoing_blob = generate_AuthInOutBlob(outgoing_secret, update_time)
2496 local_tdo_handle = None
2497 remote_tdo_handle = None
2499 local_auth_info = generate_AuthInfoInternal(local_lsa.session_key,
2500 incoming=incoming_blob,
2501 outgoing=outgoing_blob)
2502 if remote_trust_info:
2503 remote_auth_info = generate_AuthInfoInternal(remote_lsa.session_key,
2504 incoming=outgoing_blob,
2505 outgoing=incoming_blob)
2508 if remote_trust_info:
2509 self.outf.write("Creating remote TDO.\n")
2510 current_request = { "location": "remote", "name": "CreateTrustedDomainEx2"}
2511 remote_tdo_handle = remote_lsa.CreateTrustedDomainEx2(remote_policy,
2514 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2515 self.outf.write("Remote TDO created.\n")
2517 self.outf.write("Setting supported encryption types on remote TDO.\n")
2518 current_request = { "location": "remote", "name": "SetInformationTrustedDomain"}
2519 remote_lsa.SetInformationTrustedDomain(remote_tdo_handle,
2520 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2523 self.outf.write("Creating local TDO.\n")
2524 current_request = { "location": "local", "name": "CreateTrustedDomainEx2"}
2525 local_tdo_handle = local_lsa.CreateTrustedDomainEx2(local_policy,
2528 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2529 self.outf.write("Local TDO created\n")
2531 self.outf.write("Setting supported encryption types on local TDO.\n")
2532 current_request = { "location": "local", "name": "SetInformationTrustedDomain"}
2533 local_lsa.SetInformationTrustedDomain(local_tdo_handle,
2534 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2536 except RuntimeError as error:
2537 self.outf.write("Error: %s failed %sly - cleaning up\n" % (
2538 current_request['name'], current_request['location']))
2539 if remote_tdo_handle:
2540 self.outf.write("Deleting remote TDO.\n")
2541 remote_lsa.DeleteObject(remote_tdo_handle)
2542 remote_tdo_handle = None
2543 if local_tdo_handle:
2544 self.outf.write("Deleting local TDO.\n")
2545 local_lsa.DeleteObject(local_tdo_handle)
2546 local_tdo_handle = None
2547 if current_request['location'] is "remote":
2548 raise self.RemoteRuntimeError(self, error, "%s" % (
2549 current_request['name']))
2550 raise self.LocalRuntimeError(self, error, "%s" % (
2551 current_request['name']))
2554 if local_trust_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2555 self.outf.write("Setup local forest trust information...\n")
2557 # get all information about the remote trust
2558 # this triggers netr_GetForestTrustInformation to the remote domain
2559 # and lsaRSetForestTrustInformation() locally, but new top level
2560 # names are disabled by default.
2561 local_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
2562 remote_lsa_info.dns_domain.string,
2563 netlogon.DS_GFTI_UPDATE_TDO)
2564 except RuntimeError as error:
2565 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2568 # here we try to enable all top level names
2569 local_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
2570 remote_lsa_info.dns_domain,
2571 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2574 except RuntimeError as error:
2575 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2577 self.write_forest_trust_info(local_forest_info,
2578 tln=remote_lsa_info.dns_domain.string,
2579 collisions=local_forest_collision)
2581 if remote_trust_info:
2582 self.outf.write("Setup remote forest trust information...\n")
2584 # get all information about the local trust (from the perspective of the remote domain)
2585 # this triggers netr_GetForestTrustInformation to our domain.
2586 # and lsaRSetForestTrustInformation() remotely, but new top level
2587 # names are disabled by default.
2588 remote_forest_info = remote_netlogon.netr_DsRGetForestTrustInformation(remote_netlogon_info.dc_unc,
2589 local_lsa_info.dns_domain.string,
2590 netlogon.DS_GFTI_UPDATE_TDO)
2591 except RuntimeError as error:
2592 raise self.RemoteRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2595 # here we try to enable all top level names
2596 remote_forest_collision = remote_lsa.lsaRSetForestTrustInformation(remote_policy,
2597 local_lsa_info.dns_domain,
2598 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2601 except RuntimeError as error:
2602 raise self.RemoteRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2604 self.write_forest_trust_info(remote_forest_info,
2605 tln=local_lsa_info.dns_domain.string,
2606 collisions=remote_forest_collision)
2608 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2609 self.outf.write("Validating outgoing trust...\n")
2611 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_netlogon_info.dc_unc,
2612 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2614 remote_lsa_info.dns_domain.string)
2615 except RuntimeError as error:
2616 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2618 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2619 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2621 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2622 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2623 local_trust_verify.trusted_dc_name,
2624 local_trust_verify.tc_connection_status[1],
2625 local_trust_verify.pdc_connection_status[1])
2627 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2628 local_trust_verify.trusted_dc_name,
2629 local_trust_verify.tc_connection_status[1],
2630 local_trust_verify.pdc_connection_status[1])
2632 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2633 raise CommandError(local_validation)
2635 self.outf.write("OK: %s\n" % local_validation)
2637 if remote_trust_info:
2638 if remote_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2639 self.outf.write("Validating incoming trust...\n")
2641 remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_netlogon_info.dc_unc,
2642 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2644 local_lsa_info.dns_domain.string)
2645 except RuntimeError as error:
2646 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2648 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2649 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2651 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2652 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2653 remote_trust_verify.trusted_dc_name,
2654 remote_trust_verify.tc_connection_status[1],
2655 remote_trust_verify.pdc_connection_status[1])
2657 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2658 remote_trust_verify.trusted_dc_name,
2659 remote_trust_verify.tc_connection_status[1],
2660 remote_trust_verify.pdc_connection_status[1])
2662 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
2663 raise CommandError(remote_validation)
2665 self.outf.write("OK: %s\n" % remote_validation)
2667 if remote_tdo_handle is not None:
2669 remote_lsa.Close(remote_tdo_handle)
2670 except RuntimeError as error:
2672 remote_tdo_handle = None
2673 if local_tdo_handle is not None:
2675 local_lsa.Close(local_tdo_handle)
2676 except RuntimeError as error:
2678 local_tdo_handle = None
2680 self.outf.write("Success.\n")
2683 class cmd_domain_trust_delete(DomainTrustCommand):
2684 """Delete a domain trust."""
2686 synopsis = "%prog DOMAIN [options]"
2688 takes_optiongroups = {
2689 "sambaopts": options.SambaOptions,
2690 "versionopts": options.VersionOptions,
2691 "credopts": options.CredentialsOptions,
2692 "localdcopts": LocalDCCredentialsOptions,
2696 Option("--delete-location", type="choice", metavar="LOCATION",
2697 choices=["local", "both"],
2698 help="Where to delete the trusted domain object: 'local' or 'both'.",
2699 dest='delete_location',
2703 takes_args = ["domain"]
2705 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2706 delete_location=None):
2708 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2709 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2710 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2712 if delete_location == "local":
2713 remote_policy_access = None
2715 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2716 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2717 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2719 local_server = self.setup_local_server(sambaopts, localdcopts)
2721 local_lsa = self.new_local_lsa_connection()
2722 except RuntimeError as error:
2723 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2726 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2727 except RuntimeError as error:
2728 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2730 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2731 local_lsa_info.name.string,
2732 local_lsa_info.dns_domain.string,
2733 local_lsa_info.sid))
2735 local_tdo_info = None
2736 local_tdo_handle = None
2737 remote_tdo_info = None
2738 remote_tdo_handle = None
2740 lsaString = lsa.String()
2742 lsaString.string = domain
2743 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2744 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2745 except NTSTATUSError as error:
2746 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2747 raise CommandError("Failed to find trust for domain '%s'" % domain)
2748 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2751 if remote_policy_access is not None:
2753 remote_server = self.setup_remote_server(credopts, domain)
2754 except RuntimeError as error:
2755 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2758 remote_lsa = self.new_remote_lsa_connection()
2759 except RuntimeError as error:
2760 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2763 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2764 except RuntimeError as error:
2765 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2767 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2768 remote_lsa_info.name.string,
2769 remote_lsa_info.dns_domain.string,
2770 remote_lsa_info.sid))
2772 if remote_lsa_info.sid != local_tdo_info.sid or \
2773 remote_lsa_info.name.string != local_tdo_info.netbios_name.string or \
2774 remote_lsa_info.dns_domain.string != local_tdo_info.domain_name.string:
2775 raise CommandError("LocalTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2776 local_tdo_info.netbios_name.string,
2777 local_tdo_info.domain_name.string,
2778 local_tdo_info.sid))
2781 lsaString.string = local_lsa_info.dns_domain.string
2782 remote_tdo_info = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2783 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2784 except NTSTATUSError as error:
2785 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2786 raise self.RemoteRuntimeError(self, error, "QueryTrustedDomainInfoByName(%s)" % (
2790 if remote_tdo_info is not None:
2791 if local_lsa_info.sid != remote_tdo_info.sid or \
2792 local_lsa_info.name.string != remote_tdo_info.netbios_name.string or \
2793 local_lsa_info.dns_domain.string != remote_tdo_info.domain_name.string:
2794 raise CommandError("RemoteTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2795 remote_tdo_info.netbios_name.string,
2796 remote_tdo_info.domain_name.string,
2797 remote_tdo_info.sid))
2799 if local_tdo_info is not None:
2801 lsaString.string = local_tdo_info.domain_name.string
2802 local_tdo_handle = local_lsa.OpenTrustedDomainByName(local_policy,
2804 security.SEC_STD_DELETE)
2805 except RuntimeError as error:
2806 raise self.LocalRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2809 local_lsa.DeleteObject(local_tdo_handle)
2810 local_tdo_handle = None
2812 if remote_tdo_info is not None:
2814 lsaString.string = remote_tdo_info.domain_name.string
2815 remote_tdo_handle = remote_lsa.OpenTrustedDomainByName(remote_policy,
2817 security.SEC_STD_DELETE)
2818 except RuntimeError as error:
2819 raise self.RemoteRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2822 if remote_tdo_handle is not None:
2824 remote_lsa.DeleteObject(remote_tdo_handle)
2825 remote_tdo_handle = None
2826 self.outf.write("RemoteTDO deleted.\n")
2827 except RuntimeError as error:
2828 self.outf.write("%s\n" % self.RemoteRuntimeError(self, error, "DeleteObject() failed"))
2830 if local_tdo_handle is not None:
2832 local_lsa.DeleteObject(local_tdo_handle)
2833 local_tdo_handle = None
2834 self.outf.write("LocalTDO deleted.\n")
2835 except RuntimeError as error:
2836 self.outf.write("%s\n" % self.LocalRuntimeError(self, error, "DeleteObject() failed"))
2840 class cmd_domain_trust_validate(DomainTrustCommand):
2841 """Validate a domain trust."""
2843 synopsis = "%prog DOMAIN [options]"
2845 takes_optiongroups = {
2846 "sambaopts": options.SambaOptions,
2847 "versionopts": options.VersionOptions,
2848 "credopts": options.CredentialsOptions,
2849 "localdcopts": LocalDCCredentialsOptions,
2853 Option("--validate-location", type="choice", metavar="LOCATION",
2854 choices=["local", "both"],
2855 help="Where to validate the trusted domain object: 'local' or 'both'.",
2856 dest='validate_location',
2860 takes_args = ["domain"]
2862 def run(self, domain, sambaopts=None, versionopts=None, credopts=None, localdcopts=None,
2863 validate_location=None):
2865 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2867 local_server = self.setup_local_server(sambaopts, localdcopts)
2869 local_lsa = self.new_local_lsa_connection()
2870 except RuntimeError as error:
2871 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2874 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2875 except RuntimeError as error:
2876 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2878 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2879 local_lsa_info.name.string,
2880 local_lsa_info.dns_domain.string,
2881 local_lsa_info.sid))
2884 lsaString = lsa.String()
2885 lsaString.string = domain
2886 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2887 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2888 except NTSTATUSError as error:
2889 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2890 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2892 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
2894 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
2895 local_tdo_info.netbios_name.string,
2896 local_tdo_info.domain_name.string,
2897 local_tdo_info.sid))
2900 local_netlogon = self.new_local_netlogon_connection()
2901 except RuntimeError as error:
2902 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2905 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_server,
2906 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2908 local_tdo_info.domain_name.string)
2909 except RuntimeError as error:
2910 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2912 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2913 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2915 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2916 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2917 local_trust_verify.trusted_dc_name,
2918 local_trust_verify.tc_connection_status[1],
2919 local_trust_verify.pdc_connection_status[1])
2921 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2922 local_trust_verify.trusted_dc_name,
2923 local_trust_verify.tc_connection_status[1],
2924 local_trust_verify.pdc_connection_status[1])
2926 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2927 raise CommandError(local_validation)
2929 self.outf.write("OK: %s\n" % local_validation)
2932 server = local_trust_verify.trusted_dc_name.replace('\\', '')
2933 domain_and_server = "%s\\%s" % (local_tdo_info.domain_name.string, server)
2934 local_trust_rediscover = local_netlogon.netr_LogonControl2Ex(local_server,
2935 netlogon.NETLOGON_CONTROL_REDISCOVER,
2938 except RuntimeError as error:
2939 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
2941 local_conn_status = self._uint32(local_trust_rediscover.tc_connection_status[0])
2942 local_rediscover = "LocalRediscover: DC[%s] CONNECTION[%s]" % (
2943 local_trust_rediscover.trusted_dc_name,
2944 local_trust_rediscover.tc_connection_status[1])
2946 if local_conn_status != werror.WERR_SUCCESS:
2947 raise CommandError(local_rediscover)
2949 self.outf.write("OK: %s\n" % local_rediscover)
2951 if validate_location != "local":
2953 remote_server = self.setup_remote_server(credopts, domain, require_pdc=False)
2954 except RuntimeError as error:
2955 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2958 remote_netlogon = self.new_remote_netlogon_connection()
2959 except RuntimeError as error:
2960 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2963 remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_server,
2964 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2966 local_lsa_info.dns_domain.string)
2967 except RuntimeError as error:
2968 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2970 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2971 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2973 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2974 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2975 remote_trust_verify.trusted_dc_name,
2976 remote_trust_verify.tc_connection_status[1],
2977 remote_trust_verify.pdc_connection_status[1])
2979 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2980 remote_trust_verify.trusted_dc_name,
2981 remote_trust_verify.tc_connection_status[1],
2982 remote_trust_verify.pdc_connection_status[1])
2984 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
2985 raise CommandError(remote_validation)
2987 self.outf.write("OK: %s\n" % remote_validation)
2990 server = remote_trust_verify.trusted_dc_name.replace('\\', '')
2991 domain_and_server = "%s\\%s" % (local_lsa_info.dns_domain.string, server)
2992 remote_trust_rediscover = remote_netlogon.netr_LogonControl2Ex(remote_server,
2993 netlogon.NETLOGON_CONTROL_REDISCOVER,
2996 except RuntimeError as error:
2997 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
2999 remote_conn_status = self._uint32(remote_trust_rediscover.tc_connection_status[0])
3001 remote_rediscover = "RemoteRediscover: DC[%s] CONNECTION[%s]" % (
3002 remote_trust_rediscover.trusted_dc_name,
3003 remote_trust_rediscover.tc_connection_status[1])
3005 if remote_conn_status != werror.WERR_SUCCESS:
3006 raise CommandError(remote_rediscover)
3008 self.outf.write("OK: %s\n" % remote_rediscover)
3012 class cmd_domain_trust_namespaces(DomainTrustCommand):
3013 """Manage forest trust namespaces."""
3015 synopsis = "%prog [DOMAIN] [options]"
3017 takes_optiongroups = {
3018 "sambaopts": options.SambaOptions,
3019 "versionopts": options.VersionOptions,
3020 "localdcopts": LocalDCCredentialsOptions,
3024 Option("--refresh", type="choice", metavar="check|store",
3025 choices=["check", "store", None],
3026 help="List and maybe store refreshed forest trust information: 'check' or 'store'.",
3029 Option("--enable-all", action="store_true",
3030 help="Try to update disabled entries, not allowed with --refresh=check.",
3033 Option("--enable-tln", action="append", metavar='DNSDOMAIN',
3034 help="Enable a top level name entry. Can be specified multiple times.",
3037 Option("--disable-tln", action="append", metavar='DNSDOMAIN',
3038 help="Disable a top level name entry. Can be specified multiple times.",
3041 Option("--add-tln-ex", action="append", metavar='DNSDOMAIN',
3042 help="Add a top level exclusion entry. Can be specified multiple times.",
3045 Option("--delete-tln-ex", action="append", metavar='DNSDOMAIN',
3046 help="Delete a top level exclusion entry. Can be specified multiple times.",
3047 dest='delete_tln_ex',
3049 Option("--enable-nb", action="append", metavar='NETBIOSDOMAIN',
3050 help="Enable a netbios name in a domain entry. Can be specified multiple times.",
3053 Option("--disable-nb", action="append", metavar='NETBIOSDOMAIN',
3054 help="Disable a netbios name in a domain entry. Can be specified multiple times.",
3057 Option("--enable-sid", action="append", metavar='DOMAINSID',
3058 help="Enable a SID in a domain entry. Can be specified multiple times.",
3059 dest='enable_sid_str',
3061 Option("--disable-sid", action="append", metavar='DOMAINSID',
3062 help="Disable a SID in a domain entry. Can be specified multiple times.",
3063 dest='disable_sid_str',
3065 Option("--add-upn-suffix", action="append", metavar='DNSDOMAIN',
3066 help="Add a new uPNSuffixes attribute for the local forest. Can be specified multiple times.",
3069 Option("--delete-upn-suffix", action="append", metavar='DNSDOMAIN',
3070 help="Delete an existing uPNSuffixes attribute of the local forest. Can be specified multiple times.",
3073 Option("--add-spn-suffix", action="append", metavar='DNSDOMAIN',
3074 help="Add a new msDS-SPNSuffixes attribute for the local forest. Can be specified multiple times.",
3077 Option("--delete-spn-suffix", action="append", metavar='DNSDOMAIN',
3078 help="Delete an existing msDS-SPNSuffixes attribute of the local forest. Can be specified multiple times.",
3083 takes_args = ["domain?"]
3085 def run(self, domain=None, sambaopts=None, localdcopts=None, versionopts=None,
3086 refresh=None, enable_all=False,
3087 enable_tln=[], disable_tln=[], add_tln_ex=[], delete_tln_ex=[],
3088 enable_sid_str=[], disable_sid_str=[], enable_nb=[], disable_nb=[],
3089 add_upn=[], delete_upn=[], add_spn=[], delete_spn=[]):
3091 require_update = False
3094 if refresh == "store":
3095 raise CommandError("--refresh=%s not allowed without DOMAIN" % refresh)
3098 raise CommandError("--enable-all not allowed without DOMAIN")
3100 if len(enable_tln) > 0:
3101 raise CommandError("--enable-tln not allowed without DOMAIN")
3102 if len(disable_tln) > 0:
3103 raise CommandError("--disable-tln not allowed without DOMAIN")
3105 if len(add_tln_ex) > 0:
3106 raise CommandError("--add-tln-ex not allowed without DOMAIN")
3107 if len(delete_tln_ex) > 0:
3108 raise CommandError("--delete-tln-ex not allowed without DOMAIN")
3110 if len(enable_nb) > 0:
3111 raise CommandError("--enable-nb not allowed without DOMAIN")
3112 if len(disable_nb) > 0:
3113 raise CommandError("--disable-nb not allowed without DOMAIN")
3115 if len(enable_sid_str) > 0:
3116 raise CommandError("--enable-sid not allowed without DOMAIN")
3117 if len(disable_sid_str) > 0:
3118 raise CommandError("--disable-sid not allowed without DOMAIN")
3120 if len(add_upn) > 0:
3122 if not n.startswith("*."):
3124 raise CommandError("value[%s] specified for --add-upn-suffix should not include with '*.'" % n)
3125 require_update = True
3126 if len(delete_upn) > 0:
3127 for n in delete_upn:
3128 if not n.startswith("*."):
3130 raise CommandError("value[%s] specified for --delete-upn-suffix should not include with '*.'" % n)
3131 require_update = True
3133 for d in delete_upn:
3134 if a.lower() != d.lower():
3136 raise CommandError("value[%s] specified for --add-upn-suffix and --delete-upn-suffix" % a)
3138 if len(add_spn) > 0:
3140 if not n.startswith("*."):
3142 raise CommandError("value[%s] specified for --add-spn-suffix should not include with '*.'" % n)
3143 require_update = True
3144 if len(delete_spn) > 0:
3145 for n in delete_spn:
3146 if not n.startswith("*."):
3148 raise CommandError("value[%s] specified for --delete-spn-suffix should not include with '*.'" % n)
3149 require_update = True
3151 for d in delete_spn:
3152 if a.lower() != d.lower():
3154 raise CommandError("value[%s] specified for --add-spn-suffix and --delete-spn-suffix" % a)
3156 if len(add_upn) > 0:
3157 raise CommandError("--add-upn-suffix not allowed together with DOMAIN")
3158 if len(delete_upn) > 0:
3159 raise CommandError("--delete-upn-suffix not allowed together with DOMAIN")
3160 if len(add_spn) > 0:
3161 raise CommandError("--add-spn-suffix not allowed together with DOMAIN")
3162 if len(delete_spn) > 0:
3163 raise CommandError("--delete-spn-suffix not allowed together with DOMAIN")
3165 if refresh is not None:
3166 if refresh == "store":
3167 require_update = True
3169 if enable_all and refresh != "store":
3170 raise CommandError("--enable-all not allowed together with --refresh=%s" % refresh)
3172 if len(enable_tln) > 0:
3173 raise CommandError("--enable-tln not allowed together with --refresh")
3174 if len(disable_tln) > 0:
3175 raise CommandError("--disable-tln not allowed together with --refresh")
3177 if len(add_tln_ex) > 0:
3178 raise CommandError("--add-tln-ex not allowed together with --refresh")
3179 if len(delete_tln_ex) > 0:
3180 raise CommandError("--delete-tln-ex not allowed together with --refresh")
3182 if len(enable_nb) > 0:
3183 raise CommandError("--enable-nb not allowed together with --refresh")
3184 if len(disable_nb) > 0:
3185 raise CommandError("--disable-nb not allowed together with --refresh")
3187 if len(enable_sid_str) > 0:
3188 raise CommandError("--enable-sid not allowed together with --refresh")
3189 if len(disable_sid_str) > 0:
3190 raise CommandError("--disable-sid not allowed together with --refresh")
3193 require_update = True
3195 if len(enable_tln) > 0:
3196 raise CommandError("--enable-tln not allowed together with --enable-all")
3198 if len(enable_nb) > 0:
3199 raise CommandError("--enable-nb not allowed together with --enable-all")
3201 if len(enable_sid_str) > 0:
3202 raise CommandError("--enable-sid not allowed together with --enable-all")
3204 if len(enable_tln) > 0:
3205 require_update = True
3206 if len(disable_tln) > 0:
3207 require_update = True
3208 for e in enable_tln:
3209 for d in disable_tln:
3210 if e.lower() != d.lower():
3212 raise CommandError("value[%s] specified for --enable-tln and --disable-tln" % e)
3214 if len(add_tln_ex) > 0:
3215 for n in add_tln_ex:
3216 if not n.startswith("*."):
3218 raise CommandError("value[%s] specified for --add-tln-ex should not include with '*.'" % n)
3219 require_update = True
3220 if len(delete_tln_ex) > 0:
3221 for n in delete_tln_ex:
3222 if not n.startswith("*."):
3224 raise CommandError("value[%s] specified for --delete-tln-ex should not include with '*.'" % n)
3225 require_update = True
3226 for a in add_tln_ex:
3227 for d in delete_tln_ex:
3228 if a.lower() != d.lower():
3230 raise CommandError("value[%s] specified for --add-tln-ex and --delete-tln-ex" % a)
3232 if len(enable_nb) > 0:
3233 require_update = True
3234 if len(disable_nb) > 0:
3235 require_update = True
3237 for d in disable_nb:
3238 if e.upper() != d.upper():
3240 raise CommandError("value[%s] specified for --enable-nb and --disable-nb" % e)
3243 for s in enable_sid_str:
3245 sid = security.dom_sid(s)
3246 except TypeError as error:
3247 raise CommandError("value[%s] specified for --enable-sid is not a valid SID" % s)
3248 enable_sid.append(sid)
3250 for s in disable_sid_str:
3252 sid = security.dom_sid(s)
3253 except TypeError as error:
3254 raise CommandError("value[%s] specified for --disable-sid is not a valid SID" % s)
3255 disable_sid.append(sid)
3256 if len(enable_sid) > 0:
3257 require_update = True
3258 if len(disable_sid) > 0:
3259 require_update = True
3260 for e in enable_sid:
3261 for d in disable_sid:
3264 raise CommandError("value[%s] specified for --enable-sid and --disable-sid" % e)
3266 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
3268 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
3270 local_server = self.setup_local_server(sambaopts, localdcopts)
3272 local_lsa = self.new_local_lsa_connection()
3273 except RuntimeError as error:
3274 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
3277 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
3278 except RuntimeError as error:
3279 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
3281 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
3282 local_lsa_info.name.string,
3283 local_lsa_info.dns_domain.string,
3284 local_lsa_info.sid))
3288 local_netlogon = self.new_local_netlogon_connection()
3289 except RuntimeError as error:
3290 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3293 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3294 except RuntimeError as error:
3295 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3297 if local_netlogon_info.domain_name != local_netlogon_info.forest_name:
3298 raise CommandError("The local domain [%s] is not the forest root [%s]" % (
3299 local_netlogon_info.domain_name,
3300 local_netlogon_info.forest_name))
3303 # get all information about our own forest
3304 own_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3306 except RuntimeError as error:
3307 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
3308 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3311 if self.check_runtime_error(error, werror.WERR_INVALID_FUNCTION):
3312 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3315 if self.check_runtime_error(error, werror.WERR_NERR_ACFNOTLOADED):
3316 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3319 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3321 self.outf.write("Own forest trust information...\n")
3322 self.write_forest_trust_info(own_forest_info,
3323 tln=local_lsa_info.dns_domain.string)
3326 local_samdb = self.new_local_ldap_connection()
3327 except RuntimeError as error:
3328 raise self.LocalRuntimeError(self, error, "failed to connect to SamDB")
3330 local_partitions_dn = "CN=Partitions,%s" % str(local_samdb.get_config_basedn())
3331 attrs = ['uPNSuffixes', 'msDS-SPNSuffixes']
3333 msgs = local_samdb.search(base=local_partitions_dn,
3334 scope=ldb.SCOPE_BASE,
3335 expression="(objectClass=crossRefContainer)",
3337 stored_msg = msgs[0]
3338 except ldb.LdbError as error:
3339 raise self.LocalLdbError(self, error, "failed to search partition dn")
3341 stored_upn_vals = []
3342 if 'uPNSuffixes' in stored_msg:
3343 stored_upn_vals.extend(stored_msg['uPNSuffixes'])
3345 stored_spn_vals = []
3346 if 'msDS-SPNSuffixes' in stored_msg:
3347 stored_spn_vals.extend(stored_msg['msDS-SPNSuffixes'])
3349 self.outf.write("Stored uPNSuffixes attributes[%d]:\n" % len(stored_upn_vals))
3350 for v in stored_upn_vals:
3351 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3352 self.outf.write("Stored msDS-SPNSuffixes attributes[%d]:\n" % len(stored_spn_vals))
3353 for v in stored_spn_vals:
3354 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3356 if not require_update:
3360 update_upn_vals = []
3361 update_upn_vals.extend(stored_upn_vals)
3364 update_spn_vals = []
3365 update_spn_vals.extend(stored_spn_vals)
3369 for i in xrange(0, len(update_upn_vals)):
3370 v = update_upn_vals[i]
3371 if v.lower() != upn.lower():
3376 raise CommandError("Entry already present for value[%s] specified for --add-upn-suffix" % upn)
3377 update_upn_vals.append(upn)
3380 for upn in delete_upn:
3382 for i in xrange(0, len(update_upn_vals)):
3383 v = update_upn_vals[i]
3384 if v.lower() != upn.lower():
3389 raise CommandError("Entry not found for value[%s] specified for --delete-upn-suffix" % upn)
3391 update_upn_vals.pop(idx)
3396 for i in xrange(0, len(update_spn_vals)):
3397 v = update_spn_vals[i]
3398 if v.lower() != spn.lower():
3403 raise CommandError("Entry already present for value[%s] specified for --add-spn-suffix" % spn)
3404 update_spn_vals.append(spn)
3407 for spn in delete_spn:
3409 for i in xrange(0, len(update_spn_vals)):
3410 v = update_spn_vals[i]
3411 if v.lower() != spn.lower():
3416 raise CommandError("Entry not found for value[%s] specified for --delete-spn-suffix" % spn)
3418 update_spn_vals.pop(idx)
3421 self.outf.write("Update uPNSuffixes attributes[%d]:\n" % len(update_upn_vals))
3422 for v in update_upn_vals:
3423 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3424 self.outf.write("Update msDS-SPNSuffixes attributes[%d]:\n" % len(update_spn_vals))
3425 for v in update_spn_vals:
3426 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3428 update_msg = ldb.Message()
3429 update_msg.dn = stored_msg.dn
3432 update_msg['uPNSuffixes'] = ldb.MessageElement(update_upn_vals,
3433 ldb.FLAG_MOD_REPLACE,
3436 update_msg['msDS-SPNSuffixes'] = ldb.MessageElement(update_spn_vals,
3437 ldb.FLAG_MOD_REPLACE,
3440 local_samdb.modify(update_msg)
3441 except ldb.LdbError as error:
3442 raise self.LocalLdbError(self, error, "failed to update partition dn")
3445 stored_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3447 except RuntimeError as error:
3448 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3450 self.outf.write("Stored forest trust information...\n")
3451 self.write_forest_trust_info(stored_forest_info,
3452 tln=local_lsa_info.dns_domain.string)
3456 lsaString = lsa.String()
3457 lsaString.string = domain
3458 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
3459 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
3460 except NTSTATUSError as error:
3461 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
3462 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
3464 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
3466 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
3467 local_tdo_info.netbios_name.string,
3468 local_tdo_info.domain_name.string,
3469 local_tdo_info.sid))
3471 if not local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
3472 raise CommandError("trusted domain object for domain [%s] is not marked as FOREST_TRANSITIVE." % domain)
3474 if refresh is not None:
3476 local_netlogon = self.new_local_netlogon_connection()
3477 except RuntimeError as error:
3478 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3481 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3482 except RuntimeError as error:
3483 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3485 lsa_update_check = 1
3486 if refresh == "store":
3487 netlogon_update_tdo = netlogon.DS_GFTI_UPDATE_TDO
3489 lsa_update_check = 0
3491 netlogon_update_tdo = 0
3494 # get all information about the remote trust
3495 # this triggers netr_GetForestTrustInformation to the remote domain
3496 # and lsaRSetForestTrustInformation() locally, but new top level
3497 # names are disabled by default.
3498 fresh_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3499 local_tdo_info.domain_name.string,
3500 netlogon_update_tdo)
3501 except RuntimeError as error:
3502 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3505 fresh_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3506 local_tdo_info.domain_name,
3507 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3510 except RuntimeError as error:
3511 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3513 self.outf.write("Fresh forest trust information...\n")
3514 self.write_forest_trust_info(fresh_forest_info,
3515 tln=local_tdo_info.domain_name.string,
3516 collisions=fresh_forest_collision)
3518 if refresh == "store":
3520 lsaString = lsa.String()
3521 lsaString.string = local_tdo_info.domain_name.string
3522 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3524 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3525 except RuntimeError as error:
3526 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3528 self.outf.write("Stored forest trust information...\n")
3529 self.write_forest_trust_info(stored_forest_info,
3530 tln=local_tdo_info.domain_name.string)
3535 # The none --refresh path
3539 lsaString = lsa.String()
3540 lsaString.string = local_tdo_info.domain_name.string
3541 local_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3543 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3544 except RuntimeError as error:
3545 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3547 self.outf.write("Local forest trust information...\n")
3548 self.write_forest_trust_info(local_forest_info,
3549 tln=local_tdo_info.domain_name.string)
3551 if not require_update:
3555 entries.extend(local_forest_info.entries)
3556 update_forest_info = lsa.ForestTrustInformation()
3557 update_forest_info.count = len(entries)
3558 update_forest_info.entries = entries
3561 for i in xrange(0, len(update_forest_info.entries)):
3562 r = update_forest_info.entries[i]
3563 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3565 if update_forest_info.entries[i].flags == 0:
3567 update_forest_info.entries[i].time = 0
3568 update_forest_info.entries[i].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3569 for i in xrange(0, len(update_forest_info.entries)):
3570 r = update_forest_info.entries[i]
3571 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3573 if update_forest_info.entries[i].flags == 0:
3575 update_forest_info.entries[i].time = 0
3576 update_forest_info.entries[i].flags &= ~lsa.LSA_NB_DISABLED_MASK
3577 update_forest_info.entries[i].flags &= ~lsa.LSA_SID_DISABLED_MASK
3579 for tln in enable_tln:
3581 for i in xrange(0, len(update_forest_info.entries)):
3582 r = update_forest_info.entries[i]
3583 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3585 if r.forest_trust_data.string.lower() != tln.lower():
3590 raise CommandError("Entry not found for value[%s] specified for --enable-tln" % tln)
3591 if not update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_MASK:
3592 raise CommandError("Entry found for value[%s] specified for --enable-tln is already enabled" % tln)
3593 update_forest_info.entries[idx].time = 0
3594 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3596 for tln in disable_tln:
3598 for i in xrange(0, len(update_forest_info.entries)):
3599 r = update_forest_info.entries[i]
3600 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3602 if r.forest_trust_data.string.lower() != tln.lower():
3607 raise CommandError("Entry not found for value[%s] specified for --disable-tln" % tln)
3608 if update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_ADMIN:
3609 raise CommandError("Entry found for value[%s] specified for --disable-tln is already disabled" % tln)
3610 update_forest_info.entries[idx].time = 0
3611 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3612 update_forest_info.entries[idx].flags |= lsa.LSA_TLN_DISABLED_ADMIN
3614 for tln_ex in add_tln_ex:
3616 for i in xrange(0, len(update_forest_info.entries)):
3617 r = update_forest_info.entries[i]
3618 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3620 if r.forest_trust_data.string.lower() != tln_ex.lower():
3625 raise CommandError("Entry already present for value[%s] specified for --add-tln-ex" % tln_ex)
3627 tln_dot = ".%s" % tln_ex.lower()
3629 for i in xrange(0, len(update_forest_info.entries)):
3630 r = update_forest_info.entries[i]
3631 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3633 r_dot = ".%s" % r.forest_trust_data.string.lower()
3634 if tln_dot == r_dot:
3635 raise CommandError("TLN entry present for value[%s] specified for --add-tln-ex" % tln_ex)
3636 if not tln_dot.endswith(r_dot):
3642 raise CommandError("No TLN parent present for value[%s] specified for --add-tln-ex" % tln_ex)
3644 r = lsa.ForestTrustRecord()
3645 r.type = lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX
3648 r.forest_trust_data.string = tln_ex
3651 entries.extend(update_forest_info.entries)
3652 entries.insert(idx + 1, r)
3653 update_forest_info.count = len(entries)
3654 update_forest_info.entries = entries
3656 for tln_ex in delete_tln_ex:
3658 for i in xrange(0, len(update_forest_info.entries)):
3659 r = update_forest_info.entries[i]
3660 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3662 if r.forest_trust_data.string.lower() != tln_ex.lower():
3667 raise CommandError("Entry not found for value[%s] specified for --delete-tln-ex" % tln_ex)
3670 entries.extend(update_forest_info.entries)
3672 update_forest_info.count = len(entries)
3673 update_forest_info.entries = entries
3675 for nb in enable_nb:
3677 for i in xrange(0, len(update_forest_info.entries)):
3678 r = update_forest_info.entries[i]
3679 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3681 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3686 raise CommandError("Entry not found for value[%s] specified for --enable-nb" % nb)
3687 if not update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_MASK:
3688 raise CommandError("Entry found for value[%s] specified for --enable-nb is already enabled" % nb)
3689 update_forest_info.entries[idx].time = 0
3690 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3692 for nb in disable_nb:
3694 for i in xrange(0, len(update_forest_info.entries)):
3695 r = update_forest_info.entries[i]
3696 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3698 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3703 raise CommandError("Entry not found for value[%s] specified for --delete-nb" % nb)
3704 if update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_ADMIN:
3705 raise CommandError("Entry found for value[%s] specified for --disable-nb is already disabled" % nb)
3706 update_forest_info.entries[idx].time = 0
3707 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3708 update_forest_info.entries[idx].flags |= lsa.LSA_NB_DISABLED_ADMIN
3710 for sid in enable_sid:
3712 for i in xrange(0, len(update_forest_info.entries)):
3713 r = update_forest_info.entries[i]
3714 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3716 if r.forest_trust_data.domain_sid != sid:
3721 raise CommandError("Entry not found for value[%s] specified for --enable-sid" % sid)
3722 if not update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_MASK:
3723 raise CommandError("Entry found for value[%s] specified for --enable-sid is already enabled" % nb)
3724 update_forest_info.entries[idx].time = 0
3725 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3727 for sid in disable_sid:
3729 for i in xrange(0, len(update_forest_info.entries)):
3730 r = update_forest_info.entries[i]
3731 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3733 if r.forest_trust_data.domain_sid != sid:
3738 raise CommandError("Entry not found for value[%s] specified for --delete-sid" % sid)
3739 if update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_ADMIN:
3740 raise CommandError("Entry found for value[%s] specified for --disable-sid is already disabled" % nb)
3741 update_forest_info.entries[idx].time = 0
3742 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3743 update_forest_info.entries[idx].flags |= lsa.LSA_SID_DISABLED_ADMIN
3746 update_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3747 local_tdo_info.domain_name,
3748 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3749 update_forest_info, 0)
3750 except RuntimeError as error:
3751 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3753 self.outf.write("Updated forest trust information...\n")
3754 self.write_forest_trust_info(update_forest_info,
3755 tln=local_tdo_info.domain_name.string,
3756 collisions=update_forest_collision)
3759 lsaString = lsa.String()
3760 lsaString.string = local_tdo_info.domain_name.string
3761 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3763 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3764 except RuntimeError as error:
3765 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3767 self.outf.write("Stored forest trust information...\n")
3768 self.write_forest_trust_info(stored_forest_info,
3769 tln=local_tdo_info.domain_name.string)
3772 class cmd_domain_tombstones_expunge(Command):
3773 """Expunge tombstones from the database.
3775 This command expunges tombstones from the database."""
3776 synopsis = "%prog NC [NC [...]] [options]"
3779 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3780 metavar="URL", dest="H"),
3781 Option("--current-time",
3782 help="The current time to evaluate the tombstone lifetime from, expressed as YYYY-MM-DD",
3784 Option("--tombstone-lifetime", help="Number of days a tombstone should be preserved for", type=int),
3787 takes_args = ["nc*"]
3789 takes_optiongroups = {
3790 "sambaopts": options.SambaOptions,
3791 "credopts": options.CredentialsOptions,
3792 "versionopts": options.VersionOptions,
3795 def run(self, *ncs, **kwargs):
3796 sambaopts = kwargs.get("sambaopts")
3797 credopts = kwargs.get("credopts")
3798 versionpts = kwargs.get("versionopts")
3800 current_time_string = kwargs.get("current_time")
3801 tombstone_lifetime = kwargs.get("tombstone_lifetime")
3802 lp = sambaopts.get_loadparm()
3803 creds = credopts.get_credentials(lp)
3804 samdb = SamDB(url=H, session_info=system_session(),
3805 credentials=creds, lp=lp)
3807 if current_time_string is not None:
3808 current_time_obj = time.strptime(current_time_string, "%Y-%m-%d")
3809 current_time = long(time.mktime(current_time_obj))
3812 current_time = long(time.time())
3815 res = samdb.search(expression="", base="", scope=ldb.SCOPE_BASE,
3816 attrs=["namingContexts"])
3819 for nc in res[0]["namingContexts"]:
3824 started_transaction = False
3826 samdb.transaction_start()
3827 started_transaction = True
3829 removed_links) = samdb.garbage_collect_tombstones(ncs,
3830 current_time=current_time,
3831 tombstone_lifetime=tombstone_lifetime)
3833 except Exception, err:
3834 if started_transaction:
3835 samdb.transaction_cancel()
3836 raise CommandError("Failed to expunge / garbage collect tombstones", err)
3838 samdb.transaction_commit()
3840 self.outf.write("Removed %d objects and %d links successfully\n"
3841 % (removed_objects, removed_links))
3845 class cmd_domain_trust(SuperCommand):
3846 """Domain and forest trust management."""
3849 subcommands["list"] = cmd_domain_trust_list()
3850 subcommands["show"] = cmd_domain_trust_show()
3851 subcommands["create"] = cmd_domain_trust_create()
3852 subcommands["delete"] = cmd_domain_trust_delete()
3853 subcommands["validate"] = cmd_domain_trust_validate()
3854 subcommands["namespaces"] = cmd_domain_trust_namespaces()
3856 class cmd_domain_tombstones(SuperCommand):
3857 """Domain tombstone and recycled object management."""
3860 subcommands["expunge"] = cmd_domain_tombstones_expunge()
3862 class ldif_schema_update:
3863 """Helper class for applying LDIF schema updates"""
3866 self.is_defunct = False
3867 self.unknown_oid = None
3871 def _ldap_schemaUpdateNow(self, samdb):
3875 add: schemaUpdateNow
3878 samdb.modify_ldif(ldif)
3880 def can_ignore_failure(self, error):
3881 """Checks if we can safely ignore failure to apply an LDIF update"""
3882 (num, errstr) = error.args
3884 # Microsoft has marked objects as defunct that Samba doesn't know about
3885 if num == ldb.ERR_NO_SUCH_OBJECT and self.is_defunct:
3886 print("Defunct object %s doesn't exist, skipping" % self.dn)
3888 elif self.unknown_oid is not None:
3889 print("Skipping unknown OID %s for object %s" %(self.unknown_oid, self.dn))
3894 def apply(self, samdb):
3895 """Applies a single LDIF update to the schema"""
3898 samdb.modify_ldif(self.ldif, controls=['relax:0'])
3899 except ldb.LdbError as e:
3900 if self.can_ignore_failure(e):
3903 print("Exception: %s" % e)
3904 print("Encountered while trying to apply the following LDIF")
3905 print("----------------------------------------------------")
3906 print("%s" % self.ldif)
3910 # REFRESH AFTER EVERY CHANGE
3911 # Otherwise the OID-to-attribute mapping in _apply_updates_in_file()
3912 # won't work, because it can't lookup the new OID in the schema
3913 self._ldap_schemaUpdateNow(samdb)
3917 class cmd_domain_schema_upgrade(Command):
3918 """Domain schema upgrading"""
3920 synopsis = "%prog [options]"
3922 takes_optiongroups = {
3923 "sambaopts": options.SambaOptions,
3924 "versionopts": options.VersionOptions,
3925 "credopts": options.CredentialsOptions,
3929 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3930 metavar="URL", dest="H"),
3931 Option("--quiet", help="Be quiet", action="store_true"),
3932 Option("--verbose", help="Be verbose", action="store_true"),
3933 Option("--schema", type="choice", metavar="SCHEMA",
3934 choices=["2012", "2012_R2"],
3935 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
3937 Option("--ldf-file", type=str, default=None,
3938 help="Just apply the schema updates in the adprep/.LDF file(s) specified"),
3939 Option("--base-dir", type=str, default=setup_path('adprep'),
3940 help="Location of ldf files Default is ${SETUPDIR}/adprep.")
3943 def _apply_updates_in_file(self, samdb, ldif_file):
3945 Applies a series of updates specified in an .LDIF file. The .LDIF file
3946 is based on the adprep Schema updates provided by Microsoft.
3949 ldif_op = ldif_schema_update()
3951 # parse the file line by line and work out each update operation to apply
3952 for line in ldif_file:
3954 line = line.rstrip()
3956 # the operations in the .LDIF file are separated by blank lines. If
3957 # we hit a blank line, try to apply the update we've parsed so far
3960 # keep going if we haven't parsed anything yet
3961 if ldif_op.ldif == '':
3964 # Apply the individual change
3965 count += ldif_op.apply(samdb)
3967 # start storing the next operation from scratch again
3968 ldif_op = ldif_schema_update()
3971 # replace the placeholder domain name in the .ldif file with the real domain
3972 if line.upper().endswith('DC=X'):
3973 line = line[:-len('DC=X')] + str(samdb.get_default_basedn())
3974 elif line.upper().endswith('CN=X'):
3975 line = line[:-len('CN=X')] + str(samdb.get_default_basedn())
3977 values = line.split(':')
3979 if values[0].lower() == 'dn':
3980 ldif_op.dn = values[1].strip()
3982 # replace the Windows-specific operation with the Samba one
3983 if values[0].lower() == 'changetype':
3984 line = line.lower().replace(': ntdsschemaadd',
3986 line = line.lower().replace(': ntdsschemamodify',
3989 if values[0].lower() in ['rdnattid', 'subclassof',
3990 'systemposssuperiors',
3992 'systemauxiliaryclass']:
3995 # The Microsoft updates contain some OIDs we don't recognize.
3996 # Query the DB to see if we can work out the OID this update is
3997 # referring to. If we find a match, then replace the OID with
3998 # the ldapDisplayname
4000 res = samdb.search(base=samdb.get_schema_basedn(),
4001 expression="(|(attributeId=%s)(governsId=%s))" %
4003 attrs=['ldapDisplayName'])
4006 ldif_op.unknown_oid = value
4008 display_name = res[0]['ldapDisplayName'][0]
4009 line = line.replace(value, ' ' + display_name)
4011 # Microsoft has marked objects as defunct that Samba doesn't know about
4012 if values[0].lower() == 'isdefunct' and values[1].strip().lower() == 'true':
4013 ldif_op.is_defunct = True
4015 # Samba has added the showInAdvancedViewOnly attribute to all objects,
4016 # so rather than doing an add, we need to do a replace
4017 if values[0].lower() == 'add' and values[1].strip().lower() == 'showinadvancedviewonly':
4018 line = 'replace: showInAdvancedViewOnly'
4020 # Add the line to the current LDIF operation (including the newline
4021 # we stripped off at the start of the loop)
4022 ldif_op.ldif += line + '\n'
4027 def _apply_update(self, samdb, update_file, base_dir):
4028 """Wrapper function for parsing an LDIF file and applying the updates"""
4030 print("Applying %s updates..." % update_file)
4034 ldif_file = open(os.path.join(base_dir, update_file))
4036 count = self._apply_updates_in_file(samdb, ldif_file)
4042 print("%u changes applied" % count)
4046 def run(self, **kwargs):
4047 from samba.schema import Schema
4049 updates_allowed_overriden = False
4050 sambaopts = kwargs.get("sambaopts")
4051 credopts = kwargs.get("credopts")
4052 versionpts = kwargs.get("versionopts")
4053 lp = sambaopts.get_loadparm()
4054 creds = credopts.get_credentials(lp)
4056 target_schema = kwargs.get("schema")
4057 ldf_files = kwargs.get("ldf_file")
4058 base_dir = kwargs.get("base_dir")
4060 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4062 # we're not going to get far if the config doesn't allow schema updates
4063 if lp.get("dsdb:schema update allowed") is None:
4064 lp.set("dsdb:schema update allowed", "yes")
4065 print("Temporarily overriding 'dsdb:schema update allowed' setting")
4066 updates_allowed_overriden = True
4068 # if specific LDIF files were specified, just apply them
4070 schema_updates = ldf_files.split(",")
4074 # work out the version of the target schema we're upgrading to
4075 end = Schema.get_version(target_schema)
4077 # work out the version of the schema we're currently using
4078 res = samdb.search(base=samdb.get_schema_basedn(),
4079 scope=ldb.SCOPE_BASE, attrs=['objectVersion'])
4082 raise CommandError('Could not determine current schema version')
4083 start = int(res[0]['objectVersion'][0]) + 1
4085 for version in range(start, end + 1):
4086 schema_updates.append('Sch%d.ldf' % version)
4088 samdb.transaction_start()
4090 error_encountered = False
4093 # Apply the schema updates needed to move to the new schema version
4094 for ldif_file in schema_updates:
4095 count += self._apply_update(samdb, ldif_file, base_dir)
4098 samdb.transaction_commit()
4099 print("Schema successfully updated")
4101 print("No changes applied to schema")
4102 samdb.transaction_cancel()
4103 except Exception as e:
4104 print("Exception: %s" % e)
4105 print("Error encountered, aborting schema upgrade")
4106 samdb.transaction_cancel()
4107 error_encountered = True
4109 if updates_allowed_overriden:
4110 lp.set("dsdb:schema update allowed", "no")
4112 if error_encountered:
4113 raise CommandError('Failed to upgrade schema')
4115 class cmd_domain(SuperCommand):
4116 """Domain management."""
4119 subcommands["demote"] = cmd_domain_demote()
4120 if cmd_domain_export_keytab is not None:
4121 subcommands["exportkeytab"] = cmd_domain_export_keytab()
4122 subcommands["info"] = cmd_domain_info()
4123 subcommands["provision"] = cmd_domain_provision()
4124 subcommands["join"] = cmd_domain_join()
4125 subcommands["dcpromo"] = cmd_domain_dcpromo()
4126 subcommands["level"] = cmd_domain_level()
4127 subcommands["passwordsettings"] = cmd_domain_passwordsettings()
4128 subcommands["classicupgrade"] = cmd_domain_classicupgrade()
4129 subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()
4130 subcommands["trust"] = cmd_domain_trust()
4131 subcommands["tombstones"] = cmd_domain_tombstones()
4132 subcommands["schemaupgrade"] = cmd_domain_schema_upgrade()