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("--next-rid", type="int", metavar="NEXTRID", default=1000,
237 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
238 Option("--partitions-only",
239 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"),
240 Option("--targetdir", type="string", metavar="DIR",
241 help="Set target directory"),
242 Option("--ol-mmr-urls", type="string", metavar="LDAPSERVER",
243 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\""),
244 Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
248 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",
249 action="store_true"),
250 Option("--slapd-path", type="string", metavar="SLAPD-PATH",
251 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."),
252 Option("--ldap-backend-extra-port", type="int", metavar="LDAP-BACKEND-EXTRA-PORT", help="Additional TCP port for LDAP backend server (to use for replication)"),
253 Option("--ldap-backend-forced-uri", type="string", metavar="LDAP-BACKEND-FORCED-URI",
254 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"),
255 Option("--ldap-backend-nosync", help="Configure LDAP backend not to call fsync() (for performance in test environments)", action="store_true"),
259 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
260 Option("--use-xattrs", type="choice", choices=["yes","no","auto"],
261 metavar="[yes|no|auto]",
262 help="Define if we should use the native fs capabilities or a tdb file for "
263 "storing attributes likes ntacl when --use-ntvfs is set. "
264 "auto tries to make an inteligent guess based on the user rights and system capabilities",
268 if os.getenv('TEST_LDAP', "no") == "yes":
269 takes_options.extend(openldap_options)
271 if samba.is_ntvfs_fileserver_built():
272 takes_options.extend(ntvfs_options)
276 def run(self, sambaopts=None, versionopts=None,
299 ldap_backend_type=None,
303 partitions_only=None,
310 ldap_backend_nosync=None,
311 ldap_backend_extra_port=None,
312 ldap_backend_forced_uri=None,
313 ldap_dryrun_mode=None):
315 self.logger = self.get_logger("provision")
317 self.logger.setLevel(logging.WARNING)
319 self.logger.setLevel(logging.INFO)
321 lp = sambaopts.get_loadparm()
322 smbconf = lp.configfile
324 if dns_forwarder is not None:
325 suggested_forwarder = dns_forwarder
327 suggested_forwarder = self._get_nameserver_ip()
328 if suggested_forwarder is None:
329 suggested_forwarder = "none"
331 if len(self.raw_argv) == 1:
335 from getpass import getpass
338 def ask(prompt, default=None):
339 if default is not None:
340 print "%s [%s]: " % (prompt, default),
342 print "%s: " % (prompt,),
343 return sys.stdin.readline().rstrip("\n") or default
346 default = socket.getfqdn().split(".", 1)[1].upper()
349 realm = ask("Realm", default)
350 if realm in (None, ""):
351 raise CommandError("No realm set!")
354 default = realm.split(".")[0]
357 domain = ask("Domain", default)
359 raise CommandError("No domain set!")
361 server_role = ask("Server Role (dc, member, standalone)", "dc")
363 dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
364 if dns_backend in (None, ''):
365 raise CommandError("No DNS backend set!")
367 if dns_backend == "SAMBA_INTERNAL":
368 dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
369 if dns_forwarder.lower() in (None, 'none'):
370 suggested_forwarder = None
374 adminpassplain = getpass("Administrator password: ")
375 issue = self._adminpass_issue(adminpassplain)
377 self.errf.write("%s.\n" % issue)
379 adminpassverify = getpass("Retype password: ")
380 if not adminpassplain == adminpassverify:
381 self.errf.write("Sorry, passwords do not match.\n")
383 adminpass = adminpassplain
387 realm = sambaopts._lp.get('realm')
389 raise CommandError("No realm set!")
391 raise CommandError("No domain set!")
394 issue = self._adminpass_issue(adminpass)
396 raise CommandError(issue)
398 self.logger.info("Administrator password will be set randomly!")
400 if function_level == "2000":
401 dom_for_fun_level = DS_DOMAIN_FUNCTION_2000
402 elif function_level == "2003":
403 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
404 elif function_level == "2008":
405 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
406 elif function_level == "2008_R2":
407 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
409 if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
410 dns_forwarder = suggested_forwarder
412 samdb_fill = FILL_FULL
414 samdb_fill = FILL_NT4SYNC
415 elif partitions_only:
416 samdb_fill = FILL_DRS
418 if targetdir is not None:
419 if not os.path.isdir(targetdir):
424 if use_xattrs == "yes":
426 elif use_xattrs == "auto" and use_ntvfs == False:
428 elif use_ntvfs == False:
429 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
430 "Please re-run with --use-xattrs omitted.")
431 elif use_xattrs == "auto" and not lp.get("posix:eadb"):
433 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
435 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
438 samba.ntacls.setntacl(lp, file.name,
439 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
442 self.logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. ")
447 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.")
448 if ldap_backend_type == "existing":
449 if ldap_backend_forced_uri is not None:
450 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)
452 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")
454 if ldap_backend_forced_uri is not None:
455 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")
457 if domain_sid is not None:
458 domain_sid = security.dom_sid(domain_sid)
460 session = system_session()
462 result = provision(self.logger,
463 session, smbconf=smbconf, targetdir=targetdir,
464 samdb_fill=samdb_fill, realm=realm, domain=domain,
465 domainguid=domain_guid, domainsid=domain_sid,
467 hostip=host_ip, hostip6=host_ip6,
468 sitename=site, ntdsguid=ntds_guid,
469 invocationid=invocationid, adminpass=adminpass,
470 krbtgtpass=krbtgtpass, machinepass=machinepass,
471 dns_backend=dns_backend, dns_forwarder=dns_forwarder,
472 dnspass=dnspass, root=root, nobody=nobody,
474 serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
475 backend_type=ldap_backend_type,
476 ldapadminpass=ldapadminpass, ol_mmr_urls=ol_mmr_urls, slapd_path=slapd_path,
477 useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
478 use_rfc2307=use_rfc2307, skip_sysvolacl=False,
479 ldap_backend_extra_port=ldap_backend_extra_port,
480 ldap_backend_forced_uri=ldap_backend_forced_uri,
481 nosync=ldap_backend_nosync, ldap_dryrun_mode=ldap_dryrun_mode)
483 except ProvisioningError, e:
484 raise CommandError("Provision failed", e)
486 result.report_logger(self.logger)
488 def _get_nameserver_ip(self):
489 """Grab the nameserver IP address from /etc/resolv.conf."""
491 RESOLV_CONF="/etc/resolv.conf"
493 if not path.isfile(RESOLV_CONF):
494 self.logger.warning("Failed to locate %s" % RESOLV_CONF)
499 handle = open(RESOLV_CONF, 'r')
501 if not line.startswith('nameserver'):
503 # we want the last non-space continuous string of the line
504 return line.strip().split()[-1]
506 if handle is not None:
509 self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
511 def _adminpass_issue(self, adminpass):
512 """Returns error string for a bad administrator password,
513 or None if acceptable"""
515 if len(adminpass.decode('utf-8')) < DEFAULT_MIN_PWD_LENGTH:
516 return "Administrator password does not meet the default minimum" \
517 " password length requirement (%d characters)" \
518 % DEFAULT_MIN_PWD_LENGTH
519 elif not samba.check_password_quality(adminpass):
520 return "Administrator password does not meet the default" \
526 class cmd_domain_dcpromo(Command):
527 """Promote an existing domain member or NT4 PDC to an AD DC."""
529 synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
531 takes_optiongroups = {
532 "sambaopts": options.SambaOptions,
533 "versionopts": options.VersionOptions,
534 "credopts": options.CredentialsOptions,
538 Option("--server", help="DC to join", type=str),
539 Option("--site", help="site to join", type=str),
540 Option("--targetdir", help="where to store provision", type=str),
541 Option("--domain-critical-only",
542 help="only replicate critical domain objects",
543 action="store_true"),
544 Option("--machinepass", type=str, metavar="PASSWORD",
545 help="choose machine password (otherwise random)"),
546 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
547 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
548 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
549 "BIND9_DLZ uses samba4 AD to store zone information, "
550 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
551 default="SAMBA_INTERNAL"),
552 Option("--quiet", help="Be quiet", action="store_true"),
553 Option("--verbose", help="Be verbose", action="store_true")
557 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
560 if samba.is_ntvfs_fileserver_built():
561 takes_options.extend(ntvfs_options)
564 takes_args = ["domain", "role?"]
566 def run(self, domain, role=None, sambaopts=None, credopts=None,
567 versionopts=None, server=None, site=None, targetdir=None,
568 domain_critical_only=False, parent_domain=None, machinepass=None,
569 use_ntvfs=False, dns_backend=None,
570 quiet=False, verbose=False):
571 lp = sambaopts.get_loadparm()
572 creds = credopts.get_credentials(lp)
573 net = Net(creds, lp, server=credopts.ipaddress)
575 logger = self.get_logger()
577 logger.setLevel(logging.DEBUG)
579 logger.setLevel(logging.WARNING)
581 logger.setLevel(logging.INFO)
583 netbios_name = lp.get("netbios name")
589 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
590 site=site, netbios_name=netbios_name, targetdir=targetdir,
591 domain_critical_only=domain_critical_only,
592 machinepass=machinepass, use_ntvfs=use_ntvfs,
593 dns_backend=dns_backend,
594 promote_existing=True)
596 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
597 site=site, netbios_name=netbios_name, targetdir=targetdir,
598 domain_critical_only=domain_critical_only,
599 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
600 promote_existing=True)
602 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
605 class cmd_domain_join(Command):
606 """Join domain as either member or backup domain controller."""
608 synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
610 takes_optiongroups = {
611 "sambaopts": options.SambaOptions,
612 "versionopts": options.VersionOptions,
613 "credopts": options.CredentialsOptions,
617 Option("--server", help="DC to join", type=str),
618 Option("--site", help="site to join", type=str),
619 Option("--targetdir", help="where to store provision", type=str),
620 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
621 Option("--domain-critical-only",
622 help="only replicate critical domain objects",
623 action="store_true"),
624 Option("--machinepass", type=str, metavar="PASSWORD",
625 help="choose machine password (otherwise random)"),
626 Option("--adminpass", type="string", metavar="PASSWORD",
627 help="choose adminstrator password when joining as a subdomain (otherwise random)"),
628 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
629 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
630 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
631 "BIND9_DLZ uses samba4 AD to store zone information, "
632 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
633 default="SAMBA_INTERNAL"),
634 Option("--quiet", help="Be quiet", action="store_true"),
635 Option("--verbose", help="Be verbose", action="store_true")
639 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
642 if samba.is_ntvfs_fileserver_built():
643 takes_options.extend(ntvfs_options)
645 takes_args = ["domain", "role?"]
647 def run(self, domain, role=None, sambaopts=None, credopts=None,
648 versionopts=None, server=None, site=None, targetdir=None,
649 domain_critical_only=False, parent_domain=None, machinepass=None,
650 use_ntvfs=False, dns_backend=None, adminpass=None,
651 quiet=False, verbose=False):
652 lp = sambaopts.get_loadparm()
653 creds = credopts.get_credentials(lp)
654 net = Net(creds, lp, server=credopts.ipaddress)
657 site = "Default-First-Site-Name"
659 logger = self.get_logger()
661 logger.setLevel(logging.DEBUG)
663 logger.setLevel(logging.WARNING)
665 logger.setLevel(logging.INFO)
667 netbios_name = lp.get("netbios name")
672 if role is None or role == "MEMBER":
673 (join_password, sid, domain_name) = net.join_member(
674 domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
675 machinepass=machinepass)
677 self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
679 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
680 site=site, netbios_name=netbios_name, targetdir=targetdir,
681 domain_critical_only=domain_critical_only,
682 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend)
684 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
685 site=site, netbios_name=netbios_name, targetdir=targetdir,
686 domain_critical_only=domain_critical_only,
687 machinepass=machinepass, use_ntvfs=use_ntvfs,
688 dns_backend=dns_backend)
689 elif role == "SUBDOMAIN":
691 logger.info("Administrator password will be set randomly!")
693 netbios_domain = lp.get("workgroup")
694 if parent_domain is None:
695 parent_domain = ".".join(domain.split(".")[1:])
696 join_subdomain(logger=logger, server=server, creds=creds, lp=lp, dnsdomain=domain,
697 parent_domain=parent_domain, site=site,
698 netbios_name=netbios_name, netbios_domain=netbios_domain,
699 targetdir=targetdir, machinepass=machinepass,
700 use_ntvfs=use_ntvfs, dns_backend=dns_backend,
703 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role)
706 class cmd_domain_demote(Command):
707 """Demote ourselves from the role of Domain Controller."""
709 synopsis = "%prog [options]"
712 Option("--server", help="writable DC to write demotion changes on", type=str),
713 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
714 metavar="URL", dest="H"),
715 Option("--remove-other-dead-server", help="Dead DC (name or NTDS GUID) "
716 "to remove ALL references to (rather than this DC)", type=str),
717 Option("--quiet", help="Be quiet", action="store_true"),
718 Option("--verbose", help="Be verbose", action="store_true"),
721 takes_optiongroups = {
722 "sambaopts": options.SambaOptions,
723 "credopts": options.CredentialsOptions,
724 "versionopts": options.VersionOptions,
727 def run(self, sambaopts=None, credopts=None,
728 versionopts=None, server=None,
729 remove_other_dead_server=None, H=None,
730 verbose=False, quiet=False):
731 lp = sambaopts.get_loadparm()
732 creds = credopts.get_credentials(lp)
733 net = Net(creds, lp, server=credopts.ipaddress)
735 logger = self.get_logger()
737 logger.setLevel(logging.DEBUG)
739 logger.setLevel(logging.WARNING)
741 logger.setLevel(logging.INFO)
743 if remove_other_dead_server is not None:
744 if server is not None:
745 samdb = SamDB(url="ldap://%s" % server,
746 session_info=system_session(),
747 credentials=creds, lp=lp)
749 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
751 remove_dc.remove_dc(samdb, logger, remove_other_dead_server)
752 except remove_dc.DemoteException as err:
753 raise CommandError("Demote failed: %s" % err)
756 netbios_name = lp.get("netbios name")
757 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
759 res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
761 raise CommandError("Unable to search for servers")
764 raise CommandError("You are the latest server in the domain")
768 if str(e["name"]).lower() != netbios_name.lower():
769 server = e["dnsHostName"]
772 ntds_guid = samdb.get_ntds_GUID()
773 msg = samdb.search(base=str(samdb.get_config_basedn()),
774 scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
776 if len(msg) == 0 or "options" not in msg[0]:
777 raise CommandError("Failed to find options on %s" % ntds_guid)
780 dsa_options = int(str(msg[0]['options']))
782 res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
783 controls=["search_options:1:2"])
786 raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res))
788 self.errf.write("Using %s as partner server for the demotion\n" %
790 (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
792 self.errf.write("Deactivating inbound replication\n")
797 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
798 dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
799 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
803 self.errf.write("Asking partner server %s to synchronize from us\n"
805 for part in (samdb.get_schema_basedn(),
806 samdb.get_config_basedn(),
807 samdb.get_root_basedn()):
808 nc = drsuapi.DsReplicaObjectIdentifier()
811 req1 = drsuapi.DsReplicaSyncRequest1()
812 req1.naming_context = nc;
813 req1.options = drsuapi.DRSUAPI_DRS_WRIT_REP
814 req1.source_dsa_guid = misc.GUID(ntds_guid)
817 drsuapiBind.DsReplicaSync(drsuapi_handle, 1, req1)
818 except RuntimeError as (werr, string):
819 if werr == werror.WERR_DS_DRA_NO_REPLICA:
823 "Error while replicating out last local changes from '%s' for demotion, "
824 "re-enabling inbound replication\n" % part)
825 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
826 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
828 raise CommandError("Error while sending a DsReplicaSync for partition '%s'" % str(part), string)
830 remote_samdb = SamDB(url="ldap://%s" % server,
831 session_info=system_session(),
832 credentials=creds, lp=lp)
834 self.errf.write("Changing userControl and container\n")
835 res = remote_samdb.search(base=str(remote_samdb.domain_dn()),
836 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
837 netbios_name.upper(),
838 attrs=["userAccountControl"])
840 uac = int(str(res[0]["userAccountControl"]))
843 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
845 "Error while demoting, re-enabling inbound replication\n")
846 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
847 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
849 raise CommandError("Error while changing account control", e)
852 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
854 "Error while demoting, re-enabling inbound replication")
855 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
856 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
858 raise CommandError("Unable to find object with samaccountName = %s$"
859 " in the remote dc" % netbios_name.upper())
863 uac &= ~(UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION|UF_PARTIAL_SECRETS_ACCOUNT)
864 uac |= UF_WORKSTATION_TRUST_ACCOUNT
869 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
870 ldb.FLAG_MOD_REPLACE,
871 "userAccountControl")
873 remote_samdb.modify(msg)
875 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
877 "Error while demoting, re-enabling inbound replication")
878 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
879 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
882 raise CommandError("Error while changing account control", e)
884 parent = msg.dn.parent()
885 dc_name = res[0].dn.get_rdn_value()
886 rdn = "CN=%s" % dc_name
888 # Let's move to the Computer container
892 computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.domain_dn()))
893 res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
896 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
897 scope=ldb.SCOPE_ONELEVEL)
898 while(len(res) != 0 and i < 100):
900 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
901 scope=ldb.SCOPE_ONELEVEL)
904 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
906 "Error while demoting, re-enabling inbound replication\n")
907 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
908 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
914 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
915 ldb.FLAG_MOD_REPLACE,
916 "userAccountControl")
918 remote_samdb.modify(msg)
920 raise CommandError("Unable to find a slot for renaming %s,"
921 " all names from %s-1 to %s-%d seemed used" %
922 (str(dc_dn), rdn, rdn, i - 9))
924 newrdn = "%s-%d" % (rdn, i)
927 newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
928 remote_samdb.rename(dc_dn, newdn)
930 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
932 "Error while demoting, re-enabling inbound replication\n")
933 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
934 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
940 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
941 ldb.FLAG_MOD_REPLACE,
942 "userAccountControl")
944 remote_samdb.modify(msg)
945 raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
948 server_dsa_dn = samdb.get_serverName()
949 domain = remote_samdb.get_root_basedn()
952 req1 = drsuapi.DsRemoveDSServerRequest1()
953 req1.server_dn = str(server_dsa_dn)
954 req1.domain_dn = str(domain)
957 drsuapiBind.DsRemoveDSServer(drsuapi_handle, 1, req1)
958 except RuntimeError as (werr, string):
959 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
961 "Error while demoting, re-enabling inbound replication\n")
962 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
963 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
969 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
970 ldb.FLAG_MOD_REPLACE,
971 "userAccountControl")
972 remote_samdb.modify(msg)
973 remote_samdb.rename(newdn, dc_dn)
974 if werr == werror.WERR_DS_DRA_NO_REPLICA:
975 raise CommandError("The DC %s is not present on (already removed from) the remote server: " % server_dsa_dn, e)
977 raise CommandError("Error while sending a removeDsServer of %s: " % server_dsa_dn, e)
979 remove_dc.remove_sysvol_references(remote_samdb, logger, dc_name)
981 # These are objects under the computer account that should be deleted
982 for s in ("CN=Enterprise,CN=NTFRS Subscriptions",
983 "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
984 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
985 "CN=NTFRS Subscriptions"):
987 remote_samdb.delete(ldb.Dn(remote_samdb,
988 "%s,%s" % (s, str(newdn))))
989 except ldb.LdbError, l:
992 self.errf.write("Demote successful\n")
995 class cmd_domain_level(Command):
996 """Raise domain and forest function levels."""
998 synopsis = "%prog (show|raise <options>) [options]"
1000 takes_optiongroups = {
1001 "sambaopts": options.SambaOptions,
1002 "credopts": options.CredentialsOptions,
1003 "versionopts": options.VersionOptions,
1007 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1008 metavar="URL", dest="H"),
1009 Option("--quiet", help="Be quiet", action="store_true"),
1010 Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1011 help="The forest function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)"),
1012 Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1013 help="The domain function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)")
1016 takes_args = ["subcommand"]
1018 def run(self, subcommand, H=None, forest_level=None, domain_level=None,
1019 quiet=False, credopts=None, sambaopts=None, versionopts=None):
1020 lp = sambaopts.get_loadparm()
1021 creds = credopts.get_credentials(lp, fallback_machine=True)
1023 samdb = SamDB(url=H, session_info=system_session(),
1024 credentials=creds, lp=lp)
1026 domain_dn = samdb.domain_dn()
1028 res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
1029 scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
1030 assert len(res_forest) == 1
1032 res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1033 attrs=["msDS-Behavior-Version", "nTMixedDomain"])
1034 assert len(res_domain) == 1
1036 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
1037 scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
1038 attrs=["msDS-Behavior-Version"])
1039 assert len(res_dc_s) >= 1
1041 # default values, since "msDS-Behavior-Version" does not exist on Windows 2000 AD
1042 level_forest = DS_DOMAIN_FUNCTION_2000
1043 level_domain = DS_DOMAIN_FUNCTION_2000
1045 if "msDS-Behavior-Version" in res_forest[0]:
1046 level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
1047 if "msDS-Behavior-Version" in res_domain[0]:
1048 level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
1049 level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
1052 for msg in res_dc_s:
1053 if "msDS-Behavior-Version" in msg:
1054 if min_level_dc is None or int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
1055 min_level_dc = int(msg["msDS-Behavior-Version"][0])
1057 min_level_dc = DS_DOMAIN_FUNCTION_2000
1058 # well, this is the least
1061 if level_forest < DS_DOMAIN_FUNCTION_2000 or level_domain < DS_DOMAIN_FUNCTION_2000:
1062 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
1063 if min_level_dc < DS_DOMAIN_FUNCTION_2000:
1064 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
1065 if level_forest > level_domain:
1066 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
1067 if level_domain > min_level_dc:
1068 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
1070 if subcommand == "show":
1071 self.message("Domain and forest function level for domain '%s'" % domain_dn)
1072 if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1073 self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1074 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1075 self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1076 if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1077 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)!")
1081 if level_forest == DS_DOMAIN_FUNCTION_2000:
1083 elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
1084 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1085 elif level_forest == DS_DOMAIN_FUNCTION_2003:
1087 elif level_forest == DS_DOMAIN_FUNCTION_2008:
1089 elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
1091 elif level_forest == DS_DOMAIN_FUNCTION_2012:
1093 elif level_forest == DS_DOMAIN_FUNCTION_2012_R2:
1096 outstr = "higher than 2012 R2"
1097 self.message("Forest function level: (Windows) " + outstr)
1099 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1100 outstr = "2000 mixed (NT4 DC support)"
1101 elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
1103 elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
1104 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1105 elif level_domain == DS_DOMAIN_FUNCTION_2003:
1107 elif level_domain == DS_DOMAIN_FUNCTION_2008:
1109 elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
1111 elif level_domain == DS_DOMAIN_FUNCTION_2012:
1113 elif level_domain == DS_DOMAIN_FUNCTION_2012_R2:
1116 outstr = "higher than 2012 R2"
1117 self.message("Domain function level: (Windows) " + outstr)
1119 if min_level_dc == DS_DOMAIN_FUNCTION_2000:
1121 elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
1123 elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
1125 elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
1127 elif min_level_dc == DS_DOMAIN_FUNCTION_2012:
1129 elif min_level_dc == DS_DOMAIN_FUNCTION_2012_R2:
1132 outstr = "higher than 2012 R2"
1133 self.message("Lowest function level of a DC: (Windows) " + outstr)
1135 elif subcommand == "raise":
1138 if domain_level is not None:
1139 if domain_level == "2003":
1140 new_level_domain = DS_DOMAIN_FUNCTION_2003
1141 elif domain_level == "2008":
1142 new_level_domain = DS_DOMAIN_FUNCTION_2008
1143 elif domain_level == "2008_R2":
1144 new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
1145 elif domain_level == "2012":
1146 new_level_domain = DS_DOMAIN_FUNCTION_2012
1147 elif domain_level == "2012_R2":
1148 new_level_domain = DS_DOMAIN_FUNCTION_2012_R2
1150 if new_level_domain <= level_domain and level_domain_mixed == 0:
1151 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
1152 if new_level_domain > min_level_dc:
1153 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
1155 # Deactivate mixed/interim domain support
1156 if level_domain_mixed != 0:
1157 # Directly on the base DN
1159 m.dn = ldb.Dn(samdb, domain_dn)
1160 m["nTMixedDomain"] = ldb.MessageElement("0",
1161 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1165 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
1166 m["nTMixedDomain"] = ldb.MessageElement("0",
1167 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1170 except ldb.LdbError, (enum, emsg):
1171 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1174 # Directly on the base DN
1176 m.dn = ldb.Dn(samdb, domain_dn)
1177 m["msDS-Behavior-Version"]= ldb.MessageElement(
1178 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1179 "msDS-Behavior-Version")
1183 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
1184 + ",CN=Partitions,%s" % samdb.get_config_basedn())
1185 m["msDS-Behavior-Version"]= ldb.MessageElement(
1186 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1187 "msDS-Behavior-Version")
1190 except ldb.LdbError, (enum, emsg):
1191 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1194 level_domain = new_level_domain
1195 msgs.append("Domain function level changed!")
1197 if forest_level is not None:
1198 if forest_level == "2003":
1199 new_level_forest = DS_DOMAIN_FUNCTION_2003
1200 elif forest_level == "2008":
1201 new_level_forest = DS_DOMAIN_FUNCTION_2008
1202 elif forest_level == "2008_R2":
1203 new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
1204 elif forest_level == "2012":
1205 new_level_forest = DS_DOMAIN_FUNCTION_2012
1206 elif forest_level == "2012_R2":
1207 new_level_forest = DS_DOMAIN_FUNCTION_2012_R2
1209 if new_level_forest <= level_forest:
1210 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1211 if new_level_forest > level_domain:
1212 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1215 m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
1216 m["msDS-Behavior-Version"]= ldb.MessageElement(
1217 str(new_level_forest), ldb.FLAG_MOD_REPLACE,
1218 "msDS-Behavior-Version")
1220 msgs.append("Forest function level changed!")
1221 msgs.append("All changes applied successfully!")
1222 self.message("\n".join(msgs))
1224 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
1227 class cmd_domain_passwordsettings(Command):
1228 """Set password settings.
1230 Password complexity, password lockout policy, history length,
1231 minimum password length, the minimum and maximum password age) on
1232 a Samba AD DC server.
1234 Use against a Windows DC is possible, but group policy will override it.
1237 synopsis = "%prog (show|set <options>) [options]"
1239 takes_optiongroups = {
1240 "sambaopts": options.SambaOptions,
1241 "versionopts": options.VersionOptions,
1242 "credopts": options.CredentialsOptions,
1246 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1247 metavar="URL", dest="H"),
1248 Option("--quiet", help="Be quiet", action="store_true"),
1249 Option("--complexity", type="choice", choices=["on","off","default"],
1250 help="The password complexity (on | off | default). Default is 'on'"),
1251 Option("--store-plaintext", type="choice", choices=["on","off","default"],
1252 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1253 Option("--history-length",
1254 help="The password history length (<integer> | default). Default is 24.", type=str),
1255 Option("--min-pwd-length",
1256 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1257 Option("--min-pwd-age",
1258 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1259 Option("--max-pwd-age",
1260 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1261 Option("--account-lockout-duration",
1262 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),
1263 Option("--account-lockout-threshold",
1264 help="The number of bad password attempts allowed before locking out the account (<integer> | default). Default is 0 (never lock out).", type=str),
1265 Option("--reset-account-lockout-after",
1266 help="After this time is elapsed, the recorded number of attempts restarts from zero (<integer> | default). Default is 30.", type=str),
1269 takes_args = ["subcommand"]
1271 def run(self, subcommand, H=None, min_pwd_age=None, max_pwd_age=None,
1272 quiet=False, complexity=None, store_plaintext=None, history_length=None,
1273 min_pwd_length=None, account_lockout_duration=None, account_lockout_threshold=None,
1274 reset_account_lockout_after=None, credopts=None, sambaopts=None,
1276 lp = sambaopts.get_loadparm()
1277 creds = credopts.get_credentials(lp)
1279 samdb = SamDB(url=H, session_info=system_session(),
1280 credentials=creds, lp=lp)
1282 domain_dn = samdb.domain_dn()
1283 res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1284 attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1285 "minPwdAge", "maxPwdAge", "lockoutDuration", "lockoutThreshold",
1286 "lockOutObservationWindow"])
1287 assert(len(res) == 1)
1289 pwd_props = int(res[0]["pwdProperties"][0])
1290 pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
1291 cur_min_pwd_len = int(res[0]["minPwdLength"][0])
1293 cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1294 if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000:
1297 cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1298 cur_account_lockout_threshold = int(res[0]["lockoutThreshold"][0])
1300 if int(res[0]["lockoutDuration"][0]) == -0x8000000000000000:
1301 cur_account_lockout_duration = 0
1303 cur_account_lockout_duration = abs(int(res[0]["lockoutDuration"][0])) / (1e7 * 60)
1304 cur_reset_account_lockout_after = abs(int(res[0]["lockOutObservationWindow"][0])) / (1e7 * 60)
1305 except Exception, e:
1306 raise CommandError("Could not retrieve password properties!", e)
1308 if subcommand == "show":
1309 self.message("Password informations for domain '%s'" % domain_dn)
1311 if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
1312 self.message("Password complexity: on")
1314 self.message("Password complexity: off")
1315 if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
1316 self.message("Store plaintext passwords: on")
1318 self.message("Store plaintext passwords: off")
1319 self.message("Password history length: %d" % pwd_hist_len)
1320 self.message("Minimum password length: %d" % cur_min_pwd_len)
1321 self.message("Minimum password age (days): %d" % cur_min_pwd_age)
1322 self.message("Maximum password age (days): %d" % cur_max_pwd_age)
1323 self.message("Account lockout duration (mins): %d" % cur_account_lockout_duration)
1324 self.message("Account lockout threshold (attempts): %d" % cur_account_lockout_threshold)
1325 self.message("Reset account lockout after (mins): %d" % cur_reset_account_lockout_after)
1326 elif subcommand == "set":
1329 m.dn = ldb.Dn(samdb, domain_dn)
1331 if complexity is not None:
1332 if complexity == "on" or complexity == "default":
1333 pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
1334 msgs.append("Password complexity activated!")
1335 elif complexity == "off":
1336 pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
1337 msgs.append("Password complexity deactivated!")
1339 if store_plaintext is not None:
1340 if store_plaintext == "on" or store_plaintext == "default":
1341 pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1342 msgs.append("Plaintext password storage for changed passwords activated!")
1343 elif store_plaintext == "off":
1344 pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
1345 msgs.append("Plaintext password storage for changed passwords deactivated!")
1347 if complexity is not None or store_plaintext is not None:
1348 m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
1349 ldb.FLAG_MOD_REPLACE, "pwdProperties")
1351 if history_length is not None:
1352 if history_length == "default":
1355 pwd_hist_len = int(history_length)
1357 if pwd_hist_len < 0 or pwd_hist_len > 24:
1358 raise CommandError("Password history length must be in the range of 0 to 24!")
1360 m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
1361 ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
1362 msgs.append("Password history length changed!")
1364 if min_pwd_length is not None:
1365 if min_pwd_length == "default":
1368 min_pwd_len = int(min_pwd_length)
1370 if min_pwd_len < 0 or min_pwd_len > 14:
1371 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1373 m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
1374 ldb.FLAG_MOD_REPLACE, "minPwdLength")
1375 msgs.append("Minimum password length changed!")
1377 if min_pwd_age is not None:
1378 if min_pwd_age == "default":
1381 min_pwd_age = int(min_pwd_age)
1383 if min_pwd_age < 0 or min_pwd_age > 998:
1384 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1387 min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
1389 m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
1390 ldb.FLAG_MOD_REPLACE, "minPwdAge")
1391 msgs.append("Minimum password age changed!")
1393 if max_pwd_age is not None:
1394 if max_pwd_age == "default":
1397 max_pwd_age = int(max_pwd_age)
1399 if max_pwd_age < 0 or max_pwd_age > 999:
1400 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1403 if max_pwd_age == 0:
1404 max_pwd_age_ticks = -0x8000000000000000
1406 max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
1408 m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
1409 ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1410 msgs.append("Maximum password age changed!")
1412 if account_lockout_duration is not None:
1413 if account_lockout_duration == "default":
1414 account_lockout_duration = 30
1416 account_lockout_duration = int(account_lockout_duration)
1418 if account_lockout_duration < 0 or account_lockout_duration > 99999:
1419 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1422 if account_lockout_duration == 0:
1423 account_lockout_duration_ticks = -0x8000000000000000
1425 account_lockout_duration_ticks = -int(account_lockout_duration * (60 * 1e7))
1427 m["lockoutDuration"] = ldb.MessageElement(str(account_lockout_duration_ticks),
1428 ldb.FLAG_MOD_REPLACE, "lockoutDuration")
1429 msgs.append("Account lockout duration changed!")
1431 if account_lockout_threshold is not None:
1432 if account_lockout_threshold == "default":
1433 account_lockout_threshold = 0
1435 account_lockout_threshold = int(account_lockout_threshold)
1437 m["lockoutThreshold"] = ldb.MessageElement(str(account_lockout_threshold),
1438 ldb.FLAG_MOD_REPLACE, "lockoutThreshold")
1439 msgs.append("Account lockout threshold changed!")
1441 if reset_account_lockout_after is not None:
1442 if reset_account_lockout_after == "default":
1443 reset_account_lockout_after = 30
1445 reset_account_lockout_after = int(reset_account_lockout_after)
1447 if reset_account_lockout_after < 0 or reset_account_lockout_after > 99999:
1448 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1451 if reset_account_lockout_after == 0:
1452 reset_account_lockout_after_ticks = -0x8000000000000000
1454 reset_account_lockout_after_ticks = -int(reset_account_lockout_after * (60 * 1e7))
1456 m["lockOutObservationWindow"] = ldb.MessageElement(str(reset_account_lockout_after_ticks),
1457 ldb.FLAG_MOD_REPLACE, "lockOutObservationWindow")
1458 msgs.append("Duration to reset account lockout after changed!")
1460 if max_pwd_age > 0 and min_pwd_age >= max_pwd_age:
1461 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
1464 raise CommandError("You must specify at least one option to set. Try --help")
1466 msgs.append("All changes applied successfully!")
1467 self.message("\n".join(msgs))
1469 raise CommandError("Wrong argument '%s'!" % subcommand)
1472 class cmd_domain_classicupgrade(Command):
1473 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1475 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1476 the testparm utility from your classic installation (with --testparm).
1479 synopsis = "%prog [options] <classic_smb_conf>"
1481 takes_optiongroups = {
1482 "sambaopts": options.SambaOptions,
1483 "versionopts": options.VersionOptions
1487 Option("--dbdir", type="string", metavar="DIR",
1488 help="Path to samba classic DC database directory"),
1489 Option("--testparm", type="string", metavar="PATH",
1490 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1491 Option("--targetdir", type="string", metavar="DIR",
1492 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1493 Option("--quiet", help="Be quiet", action="store_true"),
1494 Option("--verbose", help="Be verbose", action="store_true"),
1495 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
1496 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1497 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1498 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1499 "BIND9_DLZ uses samba4 AD to store zone information, "
1500 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1501 default="SAMBA_INTERNAL")
1505 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
1506 action="store_true"),
1507 Option("--use-xattrs", type="choice", choices=["yes","no","auto"],
1508 metavar="[yes|no|auto]",
1509 help="Define if we should use the native fs capabilities or a tdb file for "
1510 "storing attributes likes ntacl when --use-ntvfs is set. "
1511 "auto tries to make an inteligent guess based on the user rights and system capabilities",
1514 if samba.is_ntvfs_fileserver_built():
1515 takes_options.extend(ntvfs_options)
1517 takes_args = ["smbconf"]
1519 def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
1520 quiet=False, verbose=False, use_xattrs="auto", sambaopts=None, versionopts=None,
1521 dns_backend=None, use_ntvfs=False):
1523 if not os.path.exists(smbconf):
1524 raise CommandError("File %s does not exist" % smbconf)
1526 if testparm and not os.path.exists(testparm):
1527 raise CommandError("Testparm utility %s does not exist" % testparm)
1529 if dbdir and not os.path.exists(dbdir):
1530 raise CommandError("Directory %s does not exist" % dbdir)
1532 if not dbdir and not testparm:
1533 raise CommandError("Please specify either dbdir or testparm")
1535 logger = self.get_logger()
1537 logger.setLevel(logging.DEBUG)
1539 logger.setLevel(logging.WARNING)
1541 logger.setLevel(logging.INFO)
1543 if dbdir and testparm:
1544 logger.warning("both dbdir and testparm specified, ignoring dbdir.")
1547 lp = sambaopts.get_loadparm()
1549 s3conf = s3param.get_context()
1552 s3conf.set("realm", sambaopts.realm)
1554 if targetdir is not None:
1555 if not os.path.isdir(targetdir):
1559 if use_xattrs == "yes":
1561 elif use_xattrs == "auto" and use_ntvfs == False:
1563 elif use_ntvfs == False:
1564 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
1565 "Please re-run with --use-xattrs omitted.")
1566 elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
1568 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
1570 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
1573 samba.ntacls.setntacl(lp, tmpfile.name,
1574 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1577 # FIXME: Don't catch all exceptions here
1578 logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. "
1579 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1583 # Set correct default values from dbdir or testparm
1586 paths["state directory"] = dbdir
1587 paths["private dir"] = dbdir
1588 paths["lock directory"] = dbdir
1589 paths["smb passwd file"] = dbdir + "/smbpasswd"
1591 paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
1592 paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
1593 paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
1594 paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
1595 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1596 # "state directory", instead make use of "lock directory"
1597 if len(paths["state directory"]) == 0:
1598 paths["state directory"] = paths["lock directory"]
1601 s3conf.set(p, paths[p])
1603 # load smb.conf parameters
1604 logger.info("Reading smb.conf")
1605 s3conf.load(smbconf)
1606 samba3 = Samba3(smbconf, s3conf)
1608 logger.info("Provisioning")
1609 upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
1610 useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
1613 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
1614 __doc__ = cmd_domain_classicupgrade.__doc__
1616 # This command is present for backwards compatibility only,
1617 # and should not be shown.
1621 class LocalDCCredentialsOptions(options.CredentialsOptions):
1622 def __init__(self, parser):
1623 options.CredentialsOptions.__init__(self, parser, special_name="local-dc")
1625 class DomainTrustCommand(Command):
1626 """List domain trusts."""
1629 Command.__init__(self)
1630 self.local_lp = None
1632 self.local_server = None
1633 self.local_binding_string = None
1634 self.local_creds = None
1636 self.remote_server = None
1637 self.remote_binding_string = None
1638 self.remote_creds = None
1640 def _uint32(self, v):
1641 return ctypes.c_uint32(v).value
1643 def check_runtime_error(self, runtime, val):
1647 err32 = self._uint32(runtime[0])
1653 class LocalRuntimeError(CommandError):
1654 def __init__(exception_self, self, runtime, message):
1655 err32 = self._uint32(runtime[0])
1657 msg = "LOCAL_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1658 self.local_server, message, err32, errstr)
1659 CommandError.__init__(exception_self, msg)
1661 class RemoteRuntimeError(CommandError):
1662 def __init__(exception_self, self, runtime, message):
1663 err32 = self._uint32(runtime[0])
1665 msg = "REMOTE_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1666 self.remote_server, message, err32, errstr)
1667 CommandError.__init__(exception_self, msg)
1669 class LocalLdbError(CommandError):
1670 def __init__(exception_self, self, ldb_error, message):
1671 errval = ldb_error[0]
1672 errstr = ldb_error[1]
1673 msg = "LOCAL_DC[%s]: %s - ERROR(%d) - %s" % (
1674 self.local_server, message, errval, errstr)
1675 CommandError.__init__(exception_self, msg)
1677 def setup_local_server(self, sambaopts, localdcopts):
1678 if self.local_server is not None:
1679 return self.local_server
1681 lp = sambaopts.get_loadparm()
1683 local_server = localdcopts.ipaddress
1684 if local_server is None:
1685 server_role = lp.server_role()
1686 if server_role != "ROLE_ACTIVE_DIRECTORY_DC":
1687 raise CommandError("Invalid server_role %s" % (server_role))
1688 local_server = lp.get('netbios name')
1689 local_transport = "ncalrpc"
1690 local_binding_options = ""
1691 local_binding_options += ",auth_type=ncalrpc_as_system"
1692 local_ldap_url = None
1695 local_transport = "ncacn_np"
1696 local_binding_options = ""
1697 local_ldap_url = "ldap://%s" % local_server
1698 local_creds = localdcopts.get_credentials(lp)
1702 self.local_server = local_server
1703 self.local_binding_string = "%s:%s[%s]" % (local_transport, local_server, local_binding_options)
1704 self.local_ldap_url = local_ldap_url
1705 self.local_creds = local_creds
1706 return self.local_server
1708 def new_local_lsa_connection(self):
1709 return lsa.lsarpc(self.local_binding_string, self.local_lp, self.local_creds)
1711 def new_local_netlogon_connection(self):
1712 return netlogon.netlogon(self.local_binding_string, self.local_lp, self.local_creds)
1714 def new_local_ldap_connection(self):
1715 return SamDB(url=self.local_ldap_url,
1716 session_info=system_session(),
1717 credentials=self.local_creds,
1720 def setup_remote_server(self, credopts, domain,
1722 require_writable=True):
1725 assert require_writable
1727 if self.remote_server is not None:
1728 return self.remote_server
1730 self.remote_server = "__unknown__remote_server__.%s" % domain
1731 assert self.local_server is not None
1733 remote_creds = credopts.get_credentials(self.local_lp)
1734 remote_server = credopts.ipaddress
1735 remote_binding_options = ""
1737 # TODO: we should also support NT4 domains
1738 # we could use local_netlogon.netr_DsRGetDCNameEx2() with the remote domain name
1739 # and delegate NBT or CLDAP to the local netlogon server
1741 remote_net = Net(remote_creds, self.local_lp, server=remote_server)
1742 remote_flags = nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS
1743 if require_writable:
1744 remote_flags |= nbt.NBT_SERVER_WRITABLE
1746 remote_flags |= nbt.NBT_SERVER_PDC
1747 remote_info = remote_net.finddc(flags=remote_flags, domain=domain, address=remote_server)
1749 raise CommandError("Failed to find a writeable DC for domain '%s'" % domain)
1751 nbt.NBT_SERVER_PDC: "PDC",
1752 nbt.NBT_SERVER_GC: "GC",
1753 nbt.NBT_SERVER_LDAP: "LDAP",
1754 nbt.NBT_SERVER_DS: "DS",
1755 nbt.NBT_SERVER_KDC: "KDC",
1756 nbt.NBT_SERVER_TIMESERV: "TIMESERV",
1757 nbt.NBT_SERVER_CLOSEST: "CLOSEST",
1758 nbt.NBT_SERVER_WRITABLE: "WRITABLE",
1759 nbt.NBT_SERVER_GOOD_TIMESERV: "GOOD_TIMESERV",
1760 nbt.NBT_SERVER_NDNC: "NDNC",
1761 nbt.NBT_SERVER_SELECT_SECRET_DOMAIN_6: "SELECT_SECRET_DOMAIN_6",
1762 nbt.NBT_SERVER_FULL_SECRET_DOMAIN_6: "FULL_SECRET_DOMAIN_6",
1763 nbt.NBT_SERVER_ADS_WEB_SERVICE: "ADS_WEB_SERVICE",
1764 nbt.NBT_SERVER_DS_8: "DS_8",
1765 nbt.NBT_SERVER_HAS_DNS_NAME: "HAS_DNS_NAME",
1766 nbt.NBT_SERVER_IS_DEFAULT_NC: "IS_DEFAULT_NC",
1767 nbt.NBT_SERVER_FOREST_ROOT: "FOREST_ROOT",
1769 server_type_string = self.generic_bitmap_to_string(flag_map,
1770 remote_info.server_type, names_only=True)
1771 self.outf.write("RemoteDC Netbios[%s] DNS[%s] ServerType[%s]\n" % (
1772 remote_info.pdc_name,
1773 remote_info.pdc_dns_name,
1774 server_type_string))
1776 self.remote_server = remote_info.pdc_dns_name
1777 self.remote_binding_string="ncacn_np:%s[%s]" % (self.remote_server, remote_binding_options)
1778 self.remote_creds = remote_creds
1779 return self.remote_server
1781 def new_remote_lsa_connection(self):
1782 return lsa.lsarpc(self.remote_binding_string, self.local_lp, self.remote_creds)
1784 def new_remote_netlogon_connection(self):
1785 return netlogon.netlogon(self.remote_binding_string, self.local_lp, self.remote_creds)
1787 def get_lsa_info(self, conn, policy_access):
1788 objectAttr = lsa.ObjectAttribute()
1789 objectAttr.sec_qos = lsa.QosInfo()
1791 policy = conn.OpenPolicy2(''.decode('utf-8'),
1792 objectAttr, policy_access)
1794 info = conn.QueryInfoPolicy2(policy, lsa.LSA_POLICY_INFO_DNS)
1796 return (policy, info)
1798 def get_netlogon_dc_info(self, conn, server):
1799 info = conn.netr_DsRGetDCNameEx2(server,
1800 None, 0, None, None, None,
1801 netlogon.DS_RETURN_DNS_NAME)
1804 def netr_DomainTrust_to_name(self, t):
1805 if t.trust_type == lsa.LSA_TRUST_TYPE_DOWNLEVEL:
1806 return t.netbios_name
1810 def netr_DomainTrust_to_type(self, a, t):
1812 primary_parent = None
1814 if _t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
1816 if not _t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1817 primary_parent = a[_t.parent_index]
1820 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1821 if t is primary_parent:
1824 if t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1827 parent = a[t.parent_index]
1828 if parent is primary:
1833 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1838 def netr_DomainTrust_to_transitive(self, t):
1839 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1842 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE:
1845 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1850 def netr_DomainTrust_to_direction(self, t):
1851 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND and \
1852 t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1855 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND:
1858 if t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1863 def generic_enum_to_string(self, e_dict, v, names_only=False):
1867 v32 = self._uint32(v)
1868 w = "__unknown__%08X__" % v32
1870 r = "0x%x (%s)" % (v, w)
1873 def generic_bitmap_to_string(self, b_dict, v, names_only=False):
1878 for b in sorted(b_dict.keys()):
1885 c32 = self._uint32(c)
1886 s += ["__unknown_%08X__" % c32]
1891 r = "0x%x (%s)" % (v, w)
1894 def trustType_string(self, v):
1896 lsa.LSA_TRUST_TYPE_DOWNLEVEL : "DOWNLEVEL",
1897 lsa.LSA_TRUST_TYPE_UPLEVEL : "UPLEVEL",
1898 lsa.LSA_TRUST_TYPE_MIT : "MIT",
1899 lsa.LSA_TRUST_TYPE_DCE : "DCE",
1901 return self.generic_enum_to_string(types, v)
1903 def trustDirection_string(self, v):
1905 lsa.LSA_TRUST_DIRECTION_INBOUND |
1906 lsa.LSA_TRUST_DIRECTION_OUTBOUND : "BOTH",
1907 lsa.LSA_TRUST_DIRECTION_INBOUND : "INBOUND",
1908 lsa.LSA_TRUST_DIRECTION_OUTBOUND : "OUTBOUND",
1910 return self.generic_enum_to_string(directions, v)
1912 def trustAttributes_string(self, v):
1914 lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE : "NON_TRANSITIVE",
1915 lsa.LSA_TRUST_ATTRIBUTE_UPLEVEL_ONLY : "UPLEVEL_ONLY",
1916 lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN : "QUARANTINED_DOMAIN",
1917 lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE : "FOREST_TRANSITIVE",
1918 lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION : "CROSS_ORGANIZATION",
1919 lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST : "WITHIN_FOREST",
1920 lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL : "TREAT_AS_EXTERNAL",
1921 lsa.LSA_TRUST_ATTRIBUTE_USES_RC4_ENCRYPTION : "USES_RC4_ENCRYPTION",
1923 return self.generic_bitmap_to_string(attributes, v)
1925 def kerb_EncTypes_string(self, v):
1927 security.KERB_ENCTYPE_DES_CBC_CRC : "DES_CBC_CRC",
1928 security.KERB_ENCTYPE_DES_CBC_MD5 : "DES_CBC_MD5",
1929 security.KERB_ENCTYPE_RC4_HMAC_MD5 : "RC4_HMAC_MD5",
1930 security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96 : "AES128_CTS_HMAC_SHA1_96",
1931 security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96 : "AES256_CTS_HMAC_SHA1_96",
1932 security.KERB_ENCTYPE_FAST_SUPPORTED : "FAST_SUPPORTED",
1933 security.KERB_ENCTYPE_COMPOUND_IDENTITY_SUPPORTED : "COMPOUND_IDENTITY_SUPPORTED",
1934 security.KERB_ENCTYPE_CLAIMS_SUPPORTED : "CLAIMS_SUPPORTED",
1935 security.KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED : "RESOURCE_SID_COMPRESSION_DISABLED",
1937 return self.generic_bitmap_to_string(enctypes, v)
1939 def entry_tln_status(self, e_flags, ):
1941 return "Status[Enabled]"
1944 lsa.LSA_TLN_DISABLED_NEW : "Disabled-New",
1945 lsa.LSA_TLN_DISABLED_ADMIN : "Disabled",
1946 lsa.LSA_TLN_DISABLED_CONFLICT : "Disabled-Conflicting",
1948 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
1950 def entry_dom_status(self, e_flags):
1952 return "Status[Enabled]"
1955 lsa.LSA_SID_DISABLED_ADMIN : "Disabled-SID",
1956 lsa.LSA_SID_DISABLED_CONFLICT : "Disabled-SID-Conflicting",
1957 lsa.LSA_NB_DISABLED_ADMIN : "Disabled-NB",
1958 lsa.LSA_NB_DISABLED_CONFLICT : "Disabled-NB-Conflicting",
1960 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
1962 def write_forest_trust_info(self, fti, tln=None, collisions=None):
1964 tln_string = " TDO[%s]" % tln
1968 self.outf.write("Namespaces[%d]%s:\n" % (
1969 len(fti.entries), tln_string))
1971 for i in xrange(0, len(fti.entries)):
1975 collision_string = ""
1977 if collisions is not None:
1978 for c in collisions.entries:
1982 collision_string = " Collision[%s]" % (c.name.string)
1984 d = e.forest_trust_data
1985 if e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
1986 self.outf.write("TLN: %-32s DNS[*.%s]%s\n" % (
1987 self.entry_tln_status(flags),
1988 d.string, collision_string))
1989 elif e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
1990 self.outf.write("TLN_EX: %-29s DNS[*.%s]\n" % (
1992 elif e.type == lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
1993 self.outf.write("DOM: %-32s DNS[%s] Netbios[%s] SID[%s]%s\n" % (
1994 self.entry_dom_status(flags),
1995 d.dns_domain_name.string,
1996 d.netbios_domain_name.string,
1997 d.domain_sid, collision_string))
2000 class cmd_domain_trust_list(DomainTrustCommand):
2001 """List domain trusts."""
2003 synopsis = "%prog [options]"
2005 takes_optiongroups = {
2006 "sambaopts": options.SambaOptions,
2007 "versionopts": options.VersionOptions,
2008 "localdcopts": LocalDCCredentialsOptions,
2014 def run(self, sambaopts=None, versionopts=None, localdcopts=None):
2016 local_server = self.setup_local_server(sambaopts, localdcopts)
2018 local_netlogon = self.new_local_netlogon_connection()
2019 except RuntimeError as error:
2020 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2023 local_netlogon_trusts = local_netlogon.netr_DsrEnumerateDomainTrusts(local_server,
2024 netlogon.NETR_TRUST_FLAG_IN_FOREST |
2025 netlogon.NETR_TRUST_FLAG_OUTBOUND |
2026 netlogon.NETR_TRUST_FLAG_INBOUND)
2027 except RuntimeError as error:
2028 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
2029 # TODO: we could implement a fallback to lsa.EnumTrustDom()
2030 raise CommandError("LOCAL_DC[%s]: netr_DsrEnumerateDomainTrusts not supported." % (
2032 raise self.LocalRuntimeError(self, error, "netr_DsrEnumerateDomainTrusts failed")
2034 a = local_netlogon_trusts.array
2036 if t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
2038 self.outf.write("%-14s %-15s %-19s %s\n" % (
2039 "Type[%s]" % self.netr_DomainTrust_to_type(a, t),
2040 "Transitive[%s]" % self.netr_DomainTrust_to_transitive(t),
2041 "Direction[%s]" % self.netr_DomainTrust_to_direction(t),
2042 "Name[%s]" % self.netr_DomainTrust_to_name(t)))
2045 class cmd_domain_trust_show(DomainTrustCommand):
2046 """Show trusted domain details."""
2048 synopsis = "%prog NAME [options]"
2050 takes_optiongroups = {
2051 "sambaopts": options.SambaOptions,
2052 "versionopts": options.VersionOptions,
2053 "localdcopts": LocalDCCredentialsOptions,
2059 takes_args = ["domain"]
2061 def run(self, domain, sambaopts=None, versionopts=None, localdcopts=None):
2063 local_server = self.setup_local_server(sambaopts, localdcopts)
2065 local_lsa = self.new_local_lsa_connection()
2066 except RuntimeError as error:
2067 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2070 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2071 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2072 except RuntimeError as error:
2073 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2075 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2076 local_lsa_info.name.string,
2077 local_lsa_info.dns_domain.string,
2078 local_lsa_info.sid))
2080 lsaString = lsa.String()
2081 lsaString.string = domain
2083 local_tdo_full = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2084 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2085 local_tdo_info = local_tdo_full.info_ex
2086 local_tdo_posix = local_tdo_full.posix_offset
2087 except NTSTATUSError as error:
2088 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2089 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2091 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(FULL_INFO) failed")
2094 local_tdo_enctypes = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2095 lsaString, lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES)
2096 except NTSTATUSError as error:
2097 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_PARAMETER):
2099 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_INFO_CLASS):
2102 if error is not None:
2103 raise self.LocalRuntimeError(self, error,
2104 "QueryTrustedDomainInfoByName(SUPPORTED_ENCRYPTION_TYPES) failed")
2106 local_tdo_enctypes = lsa.TrustDomainInfoSupportedEncTypes()
2107 local_tdo_enctypes.enc_types = 0
2110 local_tdo_forest = None
2111 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2112 local_tdo_forest = local_lsa.lsaRQueryForestTrustInformation(local_policy,
2113 lsaString, lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
2114 except RuntimeError as error:
2115 if self.check_runtime_error(error, ntstatus.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE):
2117 if self.check_runtime_error(error, ntstatus.NT_STATUS_NOT_FOUND):
2119 if error is not None:
2120 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation failed")
2122 local_tdo_forest = lsa.ForestTrustInformation()
2123 local_tdo_forest.count = 0
2124 local_tdo_forest.entries = []
2126 self.outf.write("TrusteDomain:\n\n");
2127 self.outf.write("NetbiosName: %s\n" % local_tdo_info.netbios_name.string)
2128 if local_tdo_info.netbios_name.string != local_tdo_info.domain_name.string:
2129 self.outf.write("DnsName: %s\n" % local_tdo_info.domain_name.string)
2130 self.outf.write("SID: %s\n" % local_tdo_info.sid)
2131 self.outf.write("Type: %s\n" % self.trustType_string(local_tdo_info.trust_type))
2132 self.outf.write("Direction: %s\n" % self.trustDirection_string(local_tdo_info.trust_direction))
2133 self.outf.write("Attributes: %s\n" % self.trustAttributes_string(local_tdo_info.trust_attributes))
2134 posix_offset_u32 = ctypes.c_uint32(local_tdo_posix.posix_offset).value
2135 posix_offset_i32 = ctypes.c_int32(local_tdo_posix.posix_offset).value
2136 self.outf.write("PosixOffset: 0x%08X (%d)\n" % (posix_offset_u32, posix_offset_i32))
2137 self.outf.write("kerb_EncTypes: %s\n" % self.kerb_EncTypes_string(local_tdo_enctypes.enc_types))
2139 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2140 self.write_forest_trust_info(local_tdo_forest,
2141 tln=local_tdo_info.domain_name.string)
2145 class cmd_domain_trust_create(DomainTrustCommand):
2146 """Create a domain or forest trust."""
2148 synopsis = "%prog DOMAIN [options]"
2150 takes_optiongroups = {
2151 "sambaopts": options.SambaOptions,
2152 "versionopts": options.VersionOptions,
2153 "credopts": options.CredentialsOptions,
2154 "localdcopts": LocalDCCredentialsOptions,
2158 Option("--type", type="choice", metavar="TYPE",
2159 choices=["external", "forest"],
2160 help="The type of the trust: 'external' or 'forest'.",
2162 default="external"),
2163 Option("--direction", type="choice", metavar="DIRECTION",
2164 choices=["incoming", "outgoing", "both"],
2165 help="The trust direction: 'incoming', 'outgoing' or 'both'.",
2166 dest='trust_direction',
2168 Option("--create-location", type="choice", metavar="LOCATION",
2169 choices=["local", "both"],
2170 help="Where to create the trusted domain object: 'local' or 'both'.",
2171 dest='create_location',
2173 Option("--cross-organisation", action="store_true",
2174 help="The related domains does not belong to the same organisation.",
2175 dest='cross_organisation',
2177 Option("--quarantined", type="choice", metavar="yes|no",
2178 choices=["yes", "no", None],
2179 help="Special SID filtering rules are applied to the trust. "
2180 "With --type=external the default is yes. "
2181 "With --type=forest the default is no.",
2182 dest='quarantined_arg',
2184 Option("--not-transitive", action="store_true",
2185 help="The forest trust is not transitive.",
2186 dest='not_transitive',
2188 Option("--treat-as-external", action="store_true",
2189 help="The treat the forest trust as external.",
2190 dest='treat_as_external',
2192 Option("--no-aes-keys", action="store_false",
2193 help="The trust uses aes kerberos keys.",
2194 dest='use_aes_keys',
2196 Option("--skip-validation", action="store_false",
2197 help="Skip validation of the trust.",
2202 takes_args = ["domain"]
2204 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2205 trust_type=None, trust_direction=None, create_location=None,
2206 cross_organisation=False, quarantined_arg=None,
2207 not_transitive=False, treat_as_external=False,
2208 use_aes_keys=False, validate=True):
2210 lsaString = lsa.String()
2213 if quarantined_arg is None:
2214 if trust_type == 'external':
2216 elif quarantined_arg == 'yes':
2219 if trust_type != 'forest':
2221 raise CommandError("--not-transitive requires --type=forest")
2222 if treat_as_external:
2223 raise CommandError("--treat-as-external requires --type=forest")
2227 enc_types = lsa.TrustDomainInfoSupportedEncTypes()
2228 enc_types.enc_types = security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96
2229 enc_types.enc_types |= security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96
2231 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2232 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2233 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2235 local_trust_info = lsa.TrustDomainInfoInfoEx()
2236 local_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2237 local_trust_info.trust_direction = 0
2238 if trust_direction == "both":
2239 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2240 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2241 elif trust_direction == "incoming":
2242 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2243 elif trust_direction == "outgoing":
2244 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2245 local_trust_info.trust_attributes = 0
2246 if cross_organisation:
2247 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2249 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2250 if trust_type == "forest":
2251 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2253 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2254 if treat_as_external:
2255 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2257 def get_password(name):
2260 if password is not None and password is not '':
2262 password = getpass("New %s Password: " % name)
2263 passwordverify = getpass("Retype %s Password: " % name)
2264 if not password == passwordverify:
2266 self.outf.write("Sorry, passwords do not match.\n")
2268 incoming_secret = None
2269 outgoing_secret = None
2270 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2271 if create_location == "local":
2272 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2273 incoming_password = get_password("Incoming Trust")
2274 incoming_secret = string_to_byte_array(incoming_password.encode('utf-16-le'))
2275 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2276 outgoing_password = get_password("Outgoing Trust")
2277 outgoing_secret = string_to_byte_array(outgoing_password.encode('utf-16-le'))
2279 remote_trust_info = None
2281 # We use 240 random bytes.
2282 # Windows uses 28 or 240 random bytes. I guess it's
2283 # based on the trust type external vs. forest.
2285 # The initial trust password can be up to 512 bytes
2286 # while the versioned passwords used for periodic updates
2287 # can only be up to 498 bytes, as netr_ServerPasswordSet2()
2288 # needs to pass the NL_PASSWORD_VERSION structure within the
2289 # 512 bytes and a 2 bytes confounder is required.
2291 def random_trust_secret(length):
2292 pw = samba.generate_random_machine_password(length/2, length/2)
2293 return string_to_byte_array(pw.encode('utf-16-le'))
2295 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2296 incoming_secret = random_trust_secret(240)
2297 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2298 outgoing_secret = random_trust_secret(240)
2300 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2301 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2303 remote_trust_info = lsa.TrustDomainInfoInfoEx()
2304 remote_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2305 remote_trust_info.trust_direction = 0
2306 if trust_direction == "both":
2307 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2308 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2309 elif trust_direction == "incoming":
2310 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2311 elif trust_direction == "outgoing":
2312 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2313 remote_trust_info.trust_attributes = 0
2314 if cross_organisation:
2315 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2317 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2318 if trust_type == "forest":
2319 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2321 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2322 if treat_as_external:
2323 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2325 local_server = self.setup_local_server(sambaopts, localdcopts)
2327 local_lsa = self.new_local_lsa_connection()
2328 except RuntimeError as error:
2329 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2332 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2333 except RuntimeError as error:
2334 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2336 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2337 local_lsa_info.name.string,
2338 local_lsa_info.dns_domain.string,
2339 local_lsa_info.sid))
2342 remote_server = self.setup_remote_server(credopts, domain)
2343 except RuntimeError as error:
2344 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2347 remote_lsa = self.new_remote_lsa_connection()
2348 except RuntimeError as error:
2349 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2352 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2353 except RuntimeError as error:
2354 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2356 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2357 remote_lsa_info.name.string,
2358 remote_lsa_info.dns_domain.string,
2359 remote_lsa_info.sid))
2361 local_trust_info.domain_name.string = remote_lsa_info.dns_domain.string
2362 local_trust_info.netbios_name.string = remote_lsa_info.name.string
2363 local_trust_info.sid = remote_lsa_info.sid
2365 if remote_trust_info:
2366 remote_trust_info.domain_name.string = local_lsa_info.dns_domain.string
2367 remote_trust_info.netbios_name.string = local_lsa_info.name.string
2368 remote_trust_info.sid = local_lsa_info.sid
2371 lsaString.string = local_trust_info.domain_name.string
2372 local_old_netbios = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2373 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2374 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2375 except NTSTATUSError as error:
2376 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2377 raise self.LocalRuntimeError(self, error,
2378 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2382 lsaString.string = local_trust_info.netbios_name.string
2383 local_old_dns = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2384 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2385 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2386 except NTSTATUSError as error:
2387 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2388 raise self.LocalRuntimeError(self, error,
2389 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2392 if remote_trust_info:
2394 lsaString.string = remote_trust_info.domain_name.string
2395 remote_old_netbios = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2396 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2397 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2398 except NTSTATUSError as error:
2399 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2400 raise self.RemoteRuntimeError(self, error,
2401 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2405 lsaString.string = remote_trust_info.netbios_name.string
2406 remote_old_dns = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2407 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2408 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2409 except NTSTATUSError as error:
2410 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2411 raise self.RemoteRuntimeError(self, error,
2412 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2416 local_netlogon = self.new_local_netlogon_connection()
2417 except RuntimeError as error:
2418 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2421 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
2422 except RuntimeError as error:
2423 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
2425 if remote_trust_info:
2427 remote_netlogon = self.new_remote_netlogon_connection()
2428 except RuntimeError as error:
2429 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2432 remote_netlogon_info = self.get_netlogon_dc_info(remote_netlogon, remote_server)
2433 except RuntimeError as error:
2434 raise self.RemoteRuntimeError(self, error, "failed to get netlogon dc info")
2436 def generate_AuthInOutBlob(secret, update_time):
2438 blob = drsblobs.trustAuthInOutBlob()
2443 clear = drsblobs.AuthInfoClear()
2444 clear.size = len(secret)
2445 clear.password = secret
2447 info = drsblobs.AuthenticationInformation()
2448 info.LastUpdateTime = samba.unix2nttime(update_time)
2449 info.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
2450 info.AuthInfo = clear
2452 array = drsblobs.AuthenticationInformationArray()
2454 array.array = [info]
2456 blob = drsblobs.trustAuthInOutBlob()
2458 blob.current = array
2462 def generate_AuthInfoInternal(session_key, incoming=None, outgoing=None):
2463 confounder = [0] * 512
2464 for i in range(len(confounder)):
2465 confounder[i] = random.randint(0, 255)
2467 trustpass = drsblobs.trustDomainPasswords()
2469 trustpass.confounder = confounder
2470 trustpass.outgoing = outgoing
2471 trustpass.incoming = incoming
2473 trustpass_blob = ndr_pack(trustpass)
2475 encrypted_trustpass = arcfour_encrypt(session_key, trustpass_blob)
2477 auth_blob = lsa.DATA_BUF2()
2478 auth_blob.size = len(encrypted_trustpass)
2479 auth_blob.data = string_to_byte_array(encrypted_trustpass)
2481 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
2482 auth_info.auth_blob = auth_blob
2486 update_time = samba.current_unix_time()
2487 incoming_blob = generate_AuthInOutBlob(incoming_secret, update_time)
2488 outgoing_blob = generate_AuthInOutBlob(outgoing_secret, update_time)
2490 local_tdo_handle = None
2491 remote_tdo_handle = None
2493 local_auth_info = generate_AuthInfoInternal(local_lsa.session_key,
2494 incoming=incoming_blob,
2495 outgoing=outgoing_blob)
2496 if remote_trust_info:
2497 remote_auth_info = generate_AuthInfoInternal(remote_lsa.session_key,
2498 incoming=outgoing_blob,
2499 outgoing=incoming_blob)
2502 if remote_trust_info:
2503 self.outf.write("Creating remote TDO.\n")
2504 current_request = { "location": "remote", "name": "CreateTrustedDomainEx2"}
2505 remote_tdo_handle = remote_lsa.CreateTrustedDomainEx2(remote_policy,
2508 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2509 self.outf.write("Remote TDO created.\n")
2511 self.outf.write("Setting supported encryption types on remote TDO.\n")
2512 current_request = { "location": "remote", "name": "SetInformationTrustedDomain"}
2513 remote_lsa.SetInformationTrustedDomain(remote_tdo_handle,
2514 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2517 self.outf.write("Creating local TDO.\n")
2518 current_request = { "location": "local", "name": "CreateTrustedDomainEx2"}
2519 local_tdo_handle = local_lsa.CreateTrustedDomainEx2(local_policy,
2522 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2523 self.outf.write("Local TDO created\n")
2525 self.outf.write("Setting supported encryption types on local TDO.\n")
2526 current_request = { "location": "local", "name": "SetInformationTrustedDomain"}
2527 local_lsa.SetInformationTrustedDomain(local_tdo_handle,
2528 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2530 except RuntimeError as error:
2531 self.outf.write("Error: %s failed %sly - cleaning up\n" % (
2532 current_request['name'], current_request['location']))
2533 if remote_tdo_handle:
2534 self.outf.write("Deleting remote TDO.\n")
2535 remote_lsa.DeleteObject(remote_tdo_handle)
2536 remote_tdo_handle = None
2537 if local_tdo_handle:
2538 self.outf.write("Deleting local TDO.\n")
2539 local_lsa.DeleteObject(local_tdo_handle)
2540 local_tdo_handle = None
2541 if current_request['location'] is "remote":
2542 raise self.RemoteRuntimeError(self, error, "%s" % (
2543 current_request['name']))
2544 raise self.LocalRuntimeError(self, error, "%s" % (
2545 current_request['name']))
2548 if local_trust_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2549 self.outf.write("Setup local forest trust information...\n")
2551 # get all information about the remote trust
2552 # this triggers netr_GetForestTrustInformation to the remote domain
2553 # and lsaRSetForestTrustInformation() locally, but new top level
2554 # names are disabled by default.
2555 local_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
2556 remote_lsa_info.dns_domain.string,
2557 netlogon.DS_GFTI_UPDATE_TDO)
2558 except RuntimeError as error:
2559 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2562 # here we try to enable all top level names
2563 local_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
2564 remote_lsa_info.dns_domain,
2565 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2568 except RuntimeError as error:
2569 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2571 self.write_forest_trust_info(local_forest_info,
2572 tln=remote_lsa_info.dns_domain.string,
2573 collisions=local_forest_collision)
2575 if remote_trust_info:
2576 self.outf.write("Setup remote forest trust information...\n")
2578 # get all information about the local trust (from the perspective of the remote domain)
2579 # this triggers netr_GetForestTrustInformation to our domain.
2580 # and lsaRSetForestTrustInformation() remotely, but new top level
2581 # names are disabled by default.
2582 remote_forest_info = remote_netlogon.netr_DsRGetForestTrustInformation(remote_netlogon_info.dc_unc,
2583 local_lsa_info.dns_domain.string,
2584 netlogon.DS_GFTI_UPDATE_TDO)
2585 except RuntimeError as error:
2586 raise self.RemoteRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2589 # here we try to enable all top level names
2590 remote_forest_collision = remote_lsa.lsaRSetForestTrustInformation(remote_policy,
2591 local_lsa_info.dns_domain,
2592 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2595 except RuntimeError as error:
2596 raise self.RemoteRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2598 self.write_forest_trust_info(remote_forest_info,
2599 tln=local_lsa_info.dns_domain.string,
2600 collisions=remote_forest_collision)
2602 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2603 self.outf.write("Validating outgoing trust...\n")
2605 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_netlogon_info.dc_unc,
2606 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2608 remote_lsa_info.dns_domain.string)
2609 except RuntimeError as error:
2610 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2612 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2613 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2615 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2616 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2617 local_trust_verify.trusted_dc_name,
2618 local_trust_verify.tc_connection_status[1],
2619 local_trust_verify.pdc_connection_status[1])
2621 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2622 local_trust_verify.trusted_dc_name,
2623 local_trust_verify.tc_connection_status[1],
2624 local_trust_verify.pdc_connection_status[1])
2626 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2627 raise CommandError(local_validation)
2629 self.outf.write("OK: %s\n" % local_validation)
2631 if remote_trust_info:
2632 if remote_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2633 self.outf.write("Validating incoming trust...\n")
2635 remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_netlogon_info.dc_unc,
2636 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2638 local_lsa_info.dns_domain.string)
2639 except RuntimeError as error:
2640 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2642 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2643 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2645 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2646 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2647 remote_trust_verify.trusted_dc_name,
2648 remote_trust_verify.tc_connection_status[1],
2649 remote_trust_verify.pdc_connection_status[1])
2651 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2652 remote_trust_verify.trusted_dc_name,
2653 remote_trust_verify.tc_connection_status[1],
2654 remote_trust_verify.pdc_connection_status[1])
2656 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
2657 raise CommandError(remote_validation)
2659 self.outf.write("OK: %s\n" % remote_validation)
2661 if remote_tdo_handle is not None:
2663 remote_lsa.Close(remote_tdo_handle)
2664 except RuntimeError as error:
2666 remote_tdo_handle = None
2667 if local_tdo_handle is not None:
2669 local_lsa.Close(local_tdo_handle)
2670 except RuntimeError as error:
2672 local_tdo_handle = None
2674 self.outf.write("Success.\n")
2677 class cmd_domain_trust_delete(DomainTrustCommand):
2678 """Delete a domain trust."""
2680 synopsis = "%prog DOMAIN [options]"
2682 takes_optiongroups = {
2683 "sambaopts": options.SambaOptions,
2684 "versionopts": options.VersionOptions,
2685 "credopts": options.CredentialsOptions,
2686 "localdcopts": LocalDCCredentialsOptions,
2690 Option("--delete-location", type="choice", metavar="LOCATION",
2691 choices=["local", "both"],
2692 help="Where to delete the trusted domain object: 'local' or 'both'.",
2693 dest='delete_location',
2697 takes_args = ["domain"]
2699 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2700 delete_location=None):
2702 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2703 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2704 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2706 if delete_location == "local":
2707 remote_policy_access = None
2709 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2710 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2711 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2713 local_server = self.setup_local_server(sambaopts, localdcopts)
2715 local_lsa = self.new_local_lsa_connection()
2716 except RuntimeError as error:
2717 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2720 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2721 except RuntimeError as error:
2722 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2724 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2725 local_lsa_info.name.string,
2726 local_lsa_info.dns_domain.string,
2727 local_lsa_info.sid))
2729 local_tdo_info = None
2730 local_tdo_handle = None
2731 remote_tdo_info = None
2732 remote_tdo_handle = None
2734 lsaString = lsa.String()
2736 lsaString.string = domain
2737 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2738 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2739 except NTSTATUSError as error:
2740 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2741 raise CommandError("Failed to find trust for domain '%s'" % domain)
2742 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2745 if remote_policy_access is not None:
2747 remote_server = self.setup_remote_server(credopts, domain)
2748 except RuntimeError as error:
2749 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2752 remote_lsa = self.new_remote_lsa_connection()
2753 except RuntimeError as error:
2754 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2757 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2758 except RuntimeError as error:
2759 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2761 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2762 remote_lsa_info.name.string,
2763 remote_lsa_info.dns_domain.string,
2764 remote_lsa_info.sid))
2766 if remote_lsa_info.sid != local_tdo_info.sid or \
2767 remote_lsa_info.name.string != local_tdo_info.netbios_name.string or \
2768 remote_lsa_info.dns_domain.string != local_tdo_info.domain_name.string:
2769 raise CommandError("LocalTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2770 local_tdo_info.netbios_name.string,
2771 local_tdo_info.domain_name.string,
2772 local_tdo_info.sid))
2775 lsaString.string = local_lsa_info.dns_domain.string
2776 remote_tdo_info = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2777 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2778 except NTSTATUSError as error:
2779 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2780 raise self.RemoteRuntimeError(self, error, "QueryTrustedDomainInfoByName(%s)" % (
2784 if remote_tdo_info is not None:
2785 if local_lsa_info.sid != remote_tdo_info.sid or \
2786 local_lsa_info.name.string != remote_tdo_info.netbios_name.string or \
2787 local_lsa_info.dns_domain.string != remote_tdo_info.domain_name.string:
2788 raise CommandError("RemoteTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2789 remote_tdo_info.netbios_name.string,
2790 remote_tdo_info.domain_name.string,
2791 remote_tdo_info.sid))
2793 if local_tdo_info is not None:
2795 lsaString.string = local_tdo_info.domain_name.string
2796 local_tdo_handle = local_lsa.OpenTrustedDomainByName(local_policy,
2798 security.SEC_STD_DELETE)
2799 except RuntimeError as error:
2800 raise self.LocalRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2803 local_lsa.DeleteObject(local_tdo_handle)
2804 local_tdo_handle = None
2806 if remote_tdo_info is not None:
2808 lsaString.string = remote_tdo_info.domain_name.string
2809 remote_tdo_handle = remote_lsa.OpenTrustedDomainByName(remote_policy,
2811 security.SEC_STD_DELETE)
2812 except RuntimeError as error:
2813 raise self.RemoteRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2816 if remote_tdo_handle is not None:
2818 remote_lsa.DeleteObject(remote_tdo_handle)
2819 remote_tdo_handle = None
2820 self.outf.write("RemoteTDO deleted.\n")
2821 except RuntimeError as error:
2822 self.outf.write("%s\n" % self.RemoteRuntimeError(self, error, "DeleteObject() failed"))
2824 if local_tdo_handle is not None:
2826 local_lsa.DeleteObject(local_tdo_handle)
2827 local_tdo_handle = None
2828 self.outf.write("LocalTDO deleted.\n")
2829 except RuntimeError as error:
2830 self.outf.write("%s\n" % self.LocalRuntimeError(self, error, "DeleteObject() failed"))
2834 class cmd_domain_trust_validate(DomainTrustCommand):
2835 """Validate a domain trust."""
2837 synopsis = "%prog DOMAIN [options]"
2839 takes_optiongroups = {
2840 "sambaopts": options.SambaOptions,
2841 "versionopts": options.VersionOptions,
2842 "credopts": options.CredentialsOptions,
2843 "localdcopts": LocalDCCredentialsOptions,
2847 Option("--validate-location", type="choice", metavar="LOCATION",
2848 choices=["local", "both"],
2849 help="Where to validate the trusted domain object: 'local' or 'both'.",
2850 dest='validate_location',
2854 takes_args = ["domain"]
2856 def run(self, domain, sambaopts=None, versionopts=None, credopts=None, localdcopts=None,
2857 validate_location=None):
2859 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2861 local_server = self.setup_local_server(sambaopts, localdcopts)
2863 local_lsa = self.new_local_lsa_connection()
2864 except RuntimeError as error:
2865 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2868 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2869 except RuntimeError as error:
2870 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2872 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2873 local_lsa_info.name.string,
2874 local_lsa_info.dns_domain.string,
2875 local_lsa_info.sid))
2878 lsaString = lsa.String()
2879 lsaString.string = domain
2880 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2881 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2882 except NTSTATUSError as error:
2883 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2884 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2886 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
2888 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
2889 local_tdo_info.netbios_name.string,
2890 local_tdo_info.domain_name.string,
2891 local_tdo_info.sid))
2894 local_netlogon = self.new_local_netlogon_connection()
2895 except RuntimeError as error:
2896 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2899 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_server,
2900 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2902 local_tdo_info.domain_name.string)
2903 except RuntimeError as error:
2904 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2906 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2907 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2909 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2910 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2911 local_trust_verify.trusted_dc_name,
2912 local_trust_verify.tc_connection_status[1],
2913 local_trust_verify.pdc_connection_status[1])
2915 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2916 local_trust_verify.trusted_dc_name,
2917 local_trust_verify.tc_connection_status[1],
2918 local_trust_verify.pdc_connection_status[1])
2920 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2921 raise CommandError(local_validation)
2923 self.outf.write("OK: %s\n" % local_validation)
2926 server = local_trust_verify.trusted_dc_name.replace('\\', '')
2927 domain_and_server = "%s\\%s" % (local_tdo_info.domain_name.string, server)
2928 local_trust_rediscover = local_netlogon.netr_LogonControl2Ex(local_server,
2929 netlogon.NETLOGON_CONTROL_REDISCOVER,
2932 except RuntimeError as error:
2933 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
2935 local_conn_status = self._uint32(local_trust_rediscover.tc_connection_status[0])
2936 local_rediscover = "LocalRediscover: DC[%s] CONNECTION[%s]" % (
2937 local_trust_rediscover.trusted_dc_name,
2938 local_trust_rediscover.tc_connection_status[1])
2940 if local_conn_status != werror.WERR_SUCCESS:
2941 raise CommandError(local_rediscover)
2943 self.outf.write("OK: %s\n" % local_rediscover)
2945 if validate_location != "local":
2947 remote_server = self.setup_remote_server(credopts, domain, require_pdc=False)
2948 except RuntimeError as error:
2949 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2952 remote_netlogon = self.new_remote_netlogon_connection()
2953 except RuntimeError as error:
2954 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2957 remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_server,
2958 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2960 local_lsa_info.dns_domain.string)
2961 except RuntimeError as error:
2962 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2964 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2965 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2967 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2968 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2969 remote_trust_verify.trusted_dc_name,
2970 remote_trust_verify.tc_connection_status[1],
2971 remote_trust_verify.pdc_connection_status[1])
2973 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2974 remote_trust_verify.trusted_dc_name,
2975 remote_trust_verify.tc_connection_status[1],
2976 remote_trust_verify.pdc_connection_status[1])
2978 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
2979 raise CommandError(remote_validation)
2981 self.outf.write("OK: %s\n" % remote_validation)
2984 server = remote_trust_verify.trusted_dc_name.replace('\\', '')
2985 domain_and_server = "%s\\%s" % (local_lsa_info.dns_domain.string, server)
2986 remote_trust_rediscover = remote_netlogon.netr_LogonControl2Ex(remote_server,
2987 netlogon.NETLOGON_CONTROL_REDISCOVER,
2990 except RuntimeError as error:
2991 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
2993 remote_conn_status = self._uint32(remote_trust_rediscover.tc_connection_status[0])
2995 remote_rediscover = "RemoteRediscover: DC[%s] CONNECTION[%s]" % (
2996 remote_trust_rediscover.trusted_dc_name,
2997 remote_trust_rediscover.tc_connection_status[1])
2999 if remote_conn_status != werror.WERR_SUCCESS:
3000 raise CommandError(remote_rediscover)
3002 self.outf.write("OK: %s\n" % remote_rediscover)
3006 class cmd_domain_trust_namespaces(DomainTrustCommand):
3007 """Manage forest trust namespaces."""
3009 synopsis = "%prog [DOMAIN] [options]"
3011 takes_optiongroups = {
3012 "sambaopts": options.SambaOptions,
3013 "versionopts": options.VersionOptions,
3014 "localdcopts": LocalDCCredentialsOptions,
3018 Option("--refresh", type="choice", metavar="check|store",
3019 choices=["check", "store", None],
3020 help="List and maybe store refreshed forest trust information: 'check' or 'store'.",
3023 Option("--enable-all", action="store_true",
3024 help="Try to update disabled entries, not allowed with --refresh=check.",
3027 Option("--enable-tln", action="append", metavar='DNSDOMAIN',
3028 help="Enable a top level name entry. Can be specified multiple times.",
3031 Option("--disable-tln", action="append", metavar='DNSDOMAIN',
3032 help="Disable a top level name entry. Can be specified multiple times.",
3035 Option("--add-tln-ex", action="append", metavar='DNSDOMAIN',
3036 help="Add a top level exclusion entry. Can be specified multiple times.",
3039 Option("--delete-tln-ex", action="append", metavar='DNSDOMAIN',
3040 help="Delete a top level exclusion entry. Can be specified multiple times.",
3041 dest='delete_tln_ex',
3043 Option("--enable-nb", action="append", metavar='NETBIOSDOMAIN',
3044 help="Enable a netbios name in a domain entry. Can be specified multiple times.",
3047 Option("--disable-nb", action="append", metavar='NETBIOSDOMAIN',
3048 help="Disable a netbios name in a domain entry. Can be specified multiple times.",
3051 Option("--enable-sid", action="append", metavar='DOMAINSID',
3052 help="Enable a SID in a domain entry. Can be specified multiple times.",
3053 dest='enable_sid_str',
3055 Option("--disable-sid", action="append", metavar='DOMAINSID',
3056 help="Disable a SID in a domain entry. Can be specified multiple times.",
3057 dest='disable_sid_str',
3059 Option("--add-upn-suffix", action="append", metavar='DNSDOMAIN',
3060 help="Add a new uPNSuffixes attribute for the local forest. Can be specified multiple times.",
3063 Option("--delete-upn-suffix", action="append", metavar='DNSDOMAIN',
3064 help="Delete an existing uPNSuffixes attribute of the local forest. Can be specified multiple times.",
3067 Option("--add-spn-suffix", action="append", metavar='DNSDOMAIN',
3068 help="Add a new msDS-SPNSuffixes attribute for the local forest. Can be specified multiple times.",
3071 Option("--delete-spn-suffix", action="append", metavar='DNSDOMAIN',
3072 help="Delete an existing msDS-SPNSuffixes attribute of the local forest. Can be specified multiple times.",
3077 takes_args = ["domain?"]
3079 def run(self, domain=None, sambaopts=None, localdcopts=None, versionopts=None,
3080 refresh=None, enable_all=False,
3081 enable_tln=[], disable_tln=[], add_tln_ex=[], delete_tln_ex=[],
3082 enable_sid_str=[], disable_sid_str=[], enable_nb=[], disable_nb=[],
3083 add_upn=[], delete_upn=[], add_spn=[], delete_spn=[]):
3085 require_update = False
3088 if refresh == "store":
3089 raise CommandError("--refresh=%s not allowed without DOMAIN" % refresh)
3092 raise CommandError("--enable-all not allowed without DOMAIN")
3094 if len(enable_tln) > 0:
3095 raise CommandError("--enable-tln not allowed without DOMAIN")
3096 if len(disable_tln) > 0:
3097 raise CommandError("--disable-tln not allowed without DOMAIN")
3099 if len(add_tln_ex) > 0:
3100 raise CommandError("--add-tln-ex not allowed without DOMAIN")
3101 if len(delete_tln_ex) > 0:
3102 raise CommandError("--delete-tln-ex not allowed without DOMAIN")
3104 if len(enable_nb) > 0:
3105 raise CommandError("--enable-nb not allowed without DOMAIN")
3106 if len(disable_nb) > 0:
3107 raise CommandError("--disable-nb not allowed without DOMAIN")
3109 if len(enable_sid_str) > 0:
3110 raise CommandError("--enable-sid not allowed without DOMAIN")
3111 if len(disable_sid_str) > 0:
3112 raise CommandError("--disable-sid not allowed without DOMAIN")
3114 if len(add_upn) > 0:
3116 if not n.startswith("*."):
3118 raise CommandError("value[%s] specified for --add-upn-suffix should not include with '*.'" % n)
3119 require_update = True
3120 if len(delete_upn) > 0:
3121 for n in delete_upn:
3122 if not n.startswith("*."):
3124 raise CommandError("value[%s] specified for --delete-upn-suffix should not include with '*.'" % n)
3125 require_update = True
3127 for d in delete_upn:
3128 if a.lower() != d.lower():
3130 raise CommandError("value[%s] specified for --add-upn-suffix and --delete-upn-suffix" % a)
3132 if len(add_spn) > 0:
3134 if not n.startswith("*."):
3136 raise CommandError("value[%s] specified for --add-spn-suffix should not include with '*.'" % n)
3137 require_update = True
3138 if len(delete_spn) > 0:
3139 for n in delete_spn:
3140 if not n.startswith("*."):
3142 raise CommandError("value[%s] specified for --delete-spn-suffix should not include with '*.'" % n)
3143 require_update = True
3145 for d in delete_spn:
3146 if a.lower() != d.lower():
3148 raise CommandError("value[%s] specified for --add-spn-suffix and --delete-spn-suffix" % a)
3150 if len(add_upn) > 0:
3151 raise CommandError("--add-upn-suffix not allowed together with DOMAIN")
3152 if len(delete_upn) > 0:
3153 raise CommandError("--delete-upn-suffix not allowed together with DOMAIN")
3154 if len(add_spn) > 0:
3155 raise CommandError("--add-spn-suffix not allowed together with DOMAIN")
3156 if len(delete_spn) > 0:
3157 raise CommandError("--delete-spn-suffix not allowed together with DOMAIN")
3159 if refresh is not None:
3160 if refresh == "store":
3161 require_update = True
3163 if enable_all and refresh != "store":
3164 raise CommandError("--enable-all not allowed together with --refresh=%s" % refresh)
3166 if len(enable_tln) > 0:
3167 raise CommandError("--enable-tln not allowed together with --refresh")
3168 if len(disable_tln) > 0:
3169 raise CommandError("--disable-tln not allowed together with --refresh")
3171 if len(add_tln_ex) > 0:
3172 raise CommandError("--add-tln-ex not allowed together with --refresh")
3173 if len(delete_tln_ex) > 0:
3174 raise CommandError("--delete-tln-ex not allowed together with --refresh")
3176 if len(enable_nb) > 0:
3177 raise CommandError("--enable-nb not allowed together with --refresh")
3178 if len(disable_nb) > 0:
3179 raise CommandError("--disable-nb not allowed together with --refresh")
3181 if len(enable_sid_str) > 0:
3182 raise CommandError("--enable-sid not allowed together with --refresh")
3183 if len(disable_sid_str) > 0:
3184 raise CommandError("--disable-sid not allowed together with --refresh")
3187 require_update = True
3189 if len(enable_tln) > 0:
3190 raise CommandError("--enable-tln not allowed together with --enable-all")
3192 if len(enable_nb) > 0:
3193 raise CommandError("--enable-nb not allowed together with --enable-all")
3195 if len(enable_sid_str) > 0:
3196 raise CommandError("--enable-sid not allowed together with --enable-all")
3198 if len(enable_tln) > 0:
3199 require_update = True
3200 if len(disable_tln) > 0:
3201 require_update = True
3202 for e in enable_tln:
3203 for d in disable_tln:
3204 if e.lower() != d.lower():
3206 raise CommandError("value[%s] specified for --enable-tln and --disable-tln" % e)
3208 if len(add_tln_ex) > 0:
3209 for n in add_tln_ex:
3210 if not n.startswith("*."):
3212 raise CommandError("value[%s] specified for --add-tln-ex should not include with '*.'" % n)
3213 require_update = True
3214 if len(delete_tln_ex) > 0:
3215 for n in delete_tln_ex:
3216 if not n.startswith("*."):
3218 raise CommandError("value[%s] specified for --delete-tln-ex should not include with '*.'" % n)
3219 require_update = True
3220 for a in add_tln_ex:
3221 for d in delete_tln_ex:
3222 if a.lower() != d.lower():
3224 raise CommandError("value[%s] specified for --add-tln-ex and --delete-tln-ex" % a)
3226 if len(enable_nb) > 0:
3227 require_update = True
3228 if len(disable_nb) > 0:
3229 require_update = True
3231 for d in disable_nb:
3232 if e.upper() != d.upper():
3234 raise CommandError("value[%s] specified for --enable-nb and --disable-nb" % e)
3237 for s in enable_sid_str:
3239 sid = security.dom_sid(s)
3240 except TypeError as error:
3241 raise CommandError("value[%s] specified for --enable-sid is not a valid SID" % s)
3242 enable_sid.append(sid)
3244 for s in disable_sid_str:
3246 sid = security.dom_sid(s)
3247 except TypeError as error:
3248 raise CommandError("value[%s] specified for --disable-sid is not a valid SID" % s)
3249 disable_sid.append(sid)
3250 if len(enable_sid) > 0:
3251 require_update = True
3252 if len(disable_sid) > 0:
3253 require_update = True
3254 for e in enable_sid:
3255 for d in disable_sid:
3258 raise CommandError("value[%s] specified for --enable-sid and --disable-sid" % e)
3260 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
3262 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
3264 local_server = self.setup_local_server(sambaopts, localdcopts)
3266 local_lsa = self.new_local_lsa_connection()
3267 except RuntimeError as error:
3268 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
3271 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
3272 except RuntimeError as error:
3273 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
3275 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
3276 local_lsa_info.name.string,
3277 local_lsa_info.dns_domain.string,
3278 local_lsa_info.sid))
3282 local_netlogon = self.new_local_netlogon_connection()
3283 except RuntimeError as error:
3284 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3287 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3288 except RuntimeError as error:
3289 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3291 if local_netlogon_info.domain_name != local_netlogon_info.forest_name:
3292 raise CommandError("The local domain [%s] is not the forest root [%s]" % (
3293 local_netlogon_info.domain_name,
3294 local_netlogon_info.forest_name))
3297 # get all information about our own forest
3298 own_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3300 except RuntimeError as error:
3301 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
3302 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3305 if self.check_runtime_error(error, werror.WERR_INVALID_FUNCTION):
3306 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3309 if self.check_runtime_error(error, werror.WERR_NERR_ACFNOTLOADED):
3310 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3313 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3315 self.outf.write("Own forest trust information...\n")
3316 self.write_forest_trust_info(own_forest_info,
3317 tln=local_lsa_info.dns_domain.string)
3320 local_samdb = self.new_local_ldap_connection()
3321 except RuntimeError as error:
3322 raise self.LocalRuntimeError(self, error, "failed to connect to SamDB")
3324 local_partitions_dn = "CN=Partitions,%s" % str(local_samdb.get_config_basedn())
3325 attrs = ['uPNSuffixes', 'msDS-SPNSuffixes']
3327 msgs = local_samdb.search(base=local_partitions_dn,
3328 scope=ldb.SCOPE_BASE,
3329 expression="(objectClass=crossRefContainer)",
3331 stored_msg = msgs[0]
3332 except ldb.LdbError as error:
3333 raise self.LocalLdbError(self, error, "failed to search partition dn")
3335 stored_upn_vals = []
3336 if 'uPNSuffixes' in stored_msg:
3337 stored_upn_vals.extend(stored_msg['uPNSuffixes'])
3339 stored_spn_vals = []
3340 if 'msDS-SPNSuffixes' in stored_msg:
3341 stored_spn_vals.extend(stored_msg['msDS-SPNSuffixes'])
3343 self.outf.write("Stored uPNSuffixes attributes[%d]:\n" % len(stored_upn_vals))
3344 for v in stored_upn_vals:
3345 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3346 self.outf.write("Stored msDS-SPNSuffixes attributes[%d]:\n" % len(stored_spn_vals))
3347 for v in stored_spn_vals:
3348 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3350 if not require_update:
3354 update_upn_vals = []
3355 update_upn_vals.extend(stored_upn_vals)
3358 update_spn_vals = []
3359 update_spn_vals.extend(stored_spn_vals)
3363 for i in xrange(0, len(update_upn_vals)):
3364 v = update_upn_vals[i]
3365 if v.lower() != upn.lower():
3370 raise CommandError("Entry already present for value[%s] specified for --add-upn-suffix" % upn)
3371 update_upn_vals.append(upn)
3374 for upn in delete_upn:
3376 for i in xrange(0, len(update_upn_vals)):
3377 v = update_upn_vals[i]
3378 if v.lower() != upn.lower():
3383 raise CommandError("Entry not found for value[%s] specified for --delete-upn-suffix" % upn)
3385 update_upn_vals.pop(idx)
3390 for i in xrange(0, len(update_spn_vals)):
3391 v = update_spn_vals[i]
3392 if v.lower() != spn.lower():
3397 raise CommandError("Entry already present for value[%s] specified for --add-spn-suffix" % spn)
3398 update_spn_vals.append(spn)
3401 for spn in delete_spn:
3403 for i in xrange(0, len(update_spn_vals)):
3404 v = update_spn_vals[i]
3405 if v.lower() != spn.lower():
3410 raise CommandError("Entry not found for value[%s] specified for --delete-spn-suffix" % spn)
3412 update_spn_vals.pop(idx)
3415 self.outf.write("Update uPNSuffixes attributes[%d]:\n" % len(update_upn_vals))
3416 for v in update_upn_vals:
3417 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3418 self.outf.write("Update msDS-SPNSuffixes attributes[%d]:\n" % len(update_spn_vals))
3419 for v in update_spn_vals:
3420 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3422 update_msg = ldb.Message()
3423 update_msg.dn = stored_msg.dn
3426 update_msg['uPNSuffixes'] = ldb.MessageElement(update_upn_vals,
3427 ldb.FLAG_MOD_REPLACE,
3430 update_msg['msDS-SPNSuffixes'] = ldb.MessageElement(update_spn_vals,
3431 ldb.FLAG_MOD_REPLACE,
3434 local_samdb.modify(update_msg)
3435 except ldb.LdbError as error:
3436 raise self.LocalLdbError(self, error, "failed to update partition dn")
3439 stored_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3441 except RuntimeError as error:
3442 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3444 self.outf.write("Stored forest trust information...\n")
3445 self.write_forest_trust_info(stored_forest_info,
3446 tln=local_lsa_info.dns_domain.string)
3450 lsaString = lsa.String()
3451 lsaString.string = domain
3452 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
3453 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
3454 except NTSTATUSError as error:
3455 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
3456 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
3458 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
3460 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
3461 local_tdo_info.netbios_name.string,
3462 local_tdo_info.domain_name.string,
3463 local_tdo_info.sid))
3465 if not local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
3466 raise CommandError("trusted domain object for domain [%s] is not marked as FOREST_TRANSITIVE." % domain)
3468 if refresh is not None:
3470 local_netlogon = self.new_local_netlogon_connection()
3471 except RuntimeError as error:
3472 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3475 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3476 except RuntimeError as error:
3477 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3479 lsa_update_check = 1
3480 if refresh == "store":
3481 netlogon_update_tdo = netlogon.DS_GFTI_UPDATE_TDO
3483 lsa_update_check = 0
3485 netlogon_update_tdo = 0
3488 # get all information about the remote trust
3489 # this triggers netr_GetForestTrustInformation to the remote domain
3490 # and lsaRSetForestTrustInformation() locally, but new top level
3491 # names are disabled by default.
3492 fresh_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3493 local_tdo_info.domain_name.string,
3494 netlogon_update_tdo)
3495 except RuntimeError as error:
3496 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3499 fresh_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3500 local_tdo_info.domain_name,
3501 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3504 except RuntimeError as error:
3505 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3507 self.outf.write("Fresh forest trust information...\n")
3508 self.write_forest_trust_info(fresh_forest_info,
3509 tln=local_tdo_info.domain_name.string,
3510 collisions=fresh_forest_collision)
3512 if refresh == "store":
3514 lsaString = lsa.String()
3515 lsaString.string = local_tdo_info.domain_name.string
3516 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3518 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3519 except RuntimeError as error:
3520 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3522 self.outf.write("Stored forest trust information...\n")
3523 self.write_forest_trust_info(stored_forest_info,
3524 tln=local_tdo_info.domain_name.string)
3529 # The none --refresh path
3533 lsaString = lsa.String()
3534 lsaString.string = local_tdo_info.domain_name.string
3535 local_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3537 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3538 except RuntimeError as error:
3539 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3541 self.outf.write("Local forest trust information...\n")
3542 self.write_forest_trust_info(local_forest_info,
3543 tln=local_tdo_info.domain_name.string)
3545 if not require_update:
3549 entries.extend(local_forest_info.entries)
3550 update_forest_info = lsa.ForestTrustInformation()
3551 update_forest_info.count = len(entries)
3552 update_forest_info.entries = entries
3555 for i in xrange(0, len(update_forest_info.entries)):
3556 r = update_forest_info.entries[i]
3557 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3559 if update_forest_info.entries[i].flags == 0:
3561 update_forest_info.entries[i].time = 0
3562 update_forest_info.entries[i].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3563 for i in xrange(0, len(update_forest_info.entries)):
3564 r = update_forest_info.entries[i]
3565 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3567 if update_forest_info.entries[i].flags == 0:
3569 update_forest_info.entries[i].time = 0
3570 update_forest_info.entries[i].flags &= ~lsa.LSA_NB_DISABLED_MASK
3571 update_forest_info.entries[i].flags &= ~lsa.LSA_SID_DISABLED_MASK
3573 for tln in enable_tln:
3575 for i in xrange(0, len(update_forest_info.entries)):
3576 r = update_forest_info.entries[i]
3577 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3579 if r.forest_trust_data.string.lower() != tln.lower():
3584 raise CommandError("Entry not found for value[%s] specified for --enable-tln" % tln)
3585 if not update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_MASK:
3586 raise CommandError("Entry found for value[%s] specified for --enable-tln is already enabled" % tln)
3587 update_forest_info.entries[idx].time = 0
3588 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3590 for tln in disable_tln:
3592 for i in xrange(0, len(update_forest_info.entries)):
3593 r = update_forest_info.entries[i]
3594 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3596 if r.forest_trust_data.string.lower() != tln.lower():
3601 raise CommandError("Entry not found for value[%s] specified for --disable-tln" % tln)
3602 if update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_ADMIN:
3603 raise CommandError("Entry found for value[%s] specified for --disable-tln is already disabled" % tln)
3604 update_forest_info.entries[idx].time = 0
3605 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3606 update_forest_info.entries[idx].flags |= lsa.LSA_TLN_DISABLED_ADMIN
3608 for tln_ex in add_tln_ex:
3610 for i in xrange(0, len(update_forest_info.entries)):
3611 r = update_forest_info.entries[i]
3612 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3614 if r.forest_trust_data.string.lower() != tln_ex.lower():
3619 raise CommandError("Entry already present for value[%s] specified for --add-tln-ex" % tln_ex)
3621 tln_dot = ".%s" % tln_ex.lower()
3623 for i in xrange(0, len(update_forest_info.entries)):
3624 r = update_forest_info.entries[i]
3625 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3627 r_dot = ".%s" % r.forest_trust_data.string.lower()
3628 if tln_dot == r_dot:
3629 raise CommandError("TLN entry present for value[%s] specified for --add-tln-ex" % tln_ex)
3630 if not tln_dot.endswith(r_dot):
3636 raise CommandError("No TLN parent present for value[%s] specified for --add-tln-ex" % tln_ex)
3638 r = lsa.ForestTrustRecord()
3639 r.type = lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX
3642 r.forest_trust_data.string = tln_ex
3645 entries.extend(update_forest_info.entries)
3646 entries.insert(idx + 1, r)
3647 update_forest_info.count = len(entries)
3648 update_forest_info.entries = entries
3650 for tln_ex in delete_tln_ex:
3652 for i in xrange(0, len(update_forest_info.entries)):
3653 r = update_forest_info.entries[i]
3654 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3656 if r.forest_trust_data.string.lower() != tln_ex.lower():
3661 raise CommandError("Entry not found for value[%s] specified for --delete-tln-ex" % tln_ex)
3664 entries.extend(update_forest_info.entries)
3666 update_forest_info.count = len(entries)
3667 update_forest_info.entries = entries
3669 for nb in enable_nb:
3671 for i in xrange(0, len(update_forest_info.entries)):
3672 r = update_forest_info.entries[i]
3673 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3675 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3680 raise CommandError("Entry not found for value[%s] specified for --enable-nb" % nb)
3681 if not update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_MASK:
3682 raise CommandError("Entry found for value[%s] specified for --enable-nb is already enabled" % nb)
3683 update_forest_info.entries[idx].time = 0
3684 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3686 for nb in disable_nb:
3688 for i in xrange(0, len(update_forest_info.entries)):
3689 r = update_forest_info.entries[i]
3690 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3692 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3697 raise CommandError("Entry not found for value[%s] specified for --delete-nb" % nb)
3698 if update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_ADMIN:
3699 raise CommandError("Entry found for value[%s] specified for --disable-nb is already disabled" % nb)
3700 update_forest_info.entries[idx].time = 0
3701 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3702 update_forest_info.entries[idx].flags |= lsa.LSA_NB_DISABLED_ADMIN
3704 for sid in enable_sid:
3706 for i in xrange(0, len(update_forest_info.entries)):
3707 r = update_forest_info.entries[i]
3708 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3710 if r.forest_trust_data.domain_sid != sid:
3715 raise CommandError("Entry not found for value[%s] specified for --enable-sid" % sid)
3716 if not update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_MASK:
3717 raise CommandError("Entry found for value[%s] specified for --enable-sid is already enabled" % nb)
3718 update_forest_info.entries[idx].time = 0
3719 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3721 for sid in disable_sid:
3723 for i in xrange(0, len(update_forest_info.entries)):
3724 r = update_forest_info.entries[i]
3725 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3727 if r.forest_trust_data.domain_sid != sid:
3732 raise CommandError("Entry not found for value[%s] specified for --delete-sid" % sid)
3733 if update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_ADMIN:
3734 raise CommandError("Entry found for value[%s] specified for --disable-sid is already disabled" % nb)
3735 update_forest_info.entries[idx].time = 0
3736 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3737 update_forest_info.entries[idx].flags |= lsa.LSA_SID_DISABLED_ADMIN
3740 update_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3741 local_tdo_info.domain_name,
3742 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3743 update_forest_info, 0)
3744 except RuntimeError as error:
3745 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3747 self.outf.write("Updated forest trust information...\n")
3748 self.write_forest_trust_info(update_forest_info,
3749 tln=local_tdo_info.domain_name.string,
3750 collisions=update_forest_collision)
3753 lsaString = lsa.String()
3754 lsaString.string = local_tdo_info.domain_name.string
3755 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3757 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3758 except RuntimeError as error:
3759 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3761 self.outf.write("Stored forest trust information...\n")
3762 self.write_forest_trust_info(stored_forest_info,
3763 tln=local_tdo_info.domain_name.string)
3766 class cmd_domain_tombstones_expunge(Command):
3767 """Expunge tombstones from the database.
3769 This command expunges tombstones from the database."""
3770 synopsis = "%prog NC [NC [...]] [options]"
3773 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3774 metavar="URL", dest="H"),
3775 Option("--current-time",
3776 help="The current time to evaluate the tombstone lifetime from, expressed as YYYY-MM-DD",
3778 Option("--tombstone-lifetime", help="Number of days a tombstone should be preserved for", type=int),
3781 takes_args = ["nc*"]
3783 takes_optiongroups = {
3784 "sambaopts": options.SambaOptions,
3785 "credopts": options.CredentialsOptions,
3786 "versionopts": options.VersionOptions,
3789 def run(self, *ncs, **kwargs):
3790 sambaopts = kwargs.get("sambaopts")
3791 credopts = kwargs.get("credopts")
3792 versionpts = kwargs.get("versionopts")
3794 current_time_string = kwargs.get("current_time")
3795 tombstone_lifetime = kwargs.get("tombstone_lifetime")
3796 lp = sambaopts.get_loadparm()
3797 creds = credopts.get_credentials(lp)
3798 samdb = SamDB(url=H, session_info=system_session(),
3799 credentials=creds, lp=lp)
3801 if current_time_string is not None:
3802 current_time_obj = time.strptime(current_time_string, "%Y-%m-%d")
3803 current_time = long(time.mktime(current_time_obj))
3806 current_time = long(time.time())
3809 res = samdb.search(expression="", base="", scope=ldb.SCOPE_BASE,
3810 attrs=["namingContexts"])
3813 for nc in res[0]["namingContexts"]:
3818 started_transaction = False
3820 samdb.transaction_start()
3821 started_transaction = True
3823 removed_links) = samdb.garbage_collect_tombstones(ncs,
3824 current_time=current_time,
3825 tombstone_lifetime=tombstone_lifetime)
3827 except Exception, err:
3828 if started_transaction:
3829 samdb.transaction_cancel()
3830 raise CommandError("Failed to expunge / garbage collect tombstones", err)
3832 samdb.transaction_commit()
3834 self.outf.write("Removed %d objects and %d links successfully\n"
3835 % (removed_objects, removed_links))
3839 class cmd_domain_trust(SuperCommand):
3840 """Domain and forest trust management."""
3843 subcommands["list"] = cmd_domain_trust_list()
3844 subcommands["show"] = cmd_domain_trust_show()
3845 subcommands["create"] = cmd_domain_trust_create()
3846 subcommands["delete"] = cmd_domain_trust_delete()
3847 subcommands["validate"] = cmd_domain_trust_validate()
3848 subcommands["namespaces"] = cmd_domain_trust_namespaces()
3850 class cmd_domain_tombstones(SuperCommand):
3851 """Domain tombstone and recycled object management."""
3854 subcommands["expunge"] = cmd_domain_tombstones_expunge()
3856 class ldif_schema_update:
3857 """Helper class for applying LDIF schema updates"""
3860 self.is_defunct = False
3861 self.unknown_oid = None
3865 def _ldap_schemaUpdateNow(self, samdb):
3869 add: schemaUpdateNow
3872 samdb.modify_ldif(ldif)
3874 def can_ignore_failure(self, error):
3875 """Checks if we can safely ignore failure to apply an LDIF update"""
3876 (num, errstr) = error.args
3878 # Microsoft has marked objects as defunct that Samba doesn't know about
3879 if num == ldb.ERR_NO_SUCH_OBJECT and self.is_defunct:
3880 print("Defunct object %s doesn't exist, skipping" % self.dn)
3882 elif self.unknown_oid is not None:
3883 print("Skipping unknown OID %s for object %s" %(self.unknown_oid, self.dn))
3888 def apply(self, samdb):
3889 """Applies a single LDIF update to the schema"""
3892 samdb.modify_ldif(self.ldif, controls=['relax:0'])
3893 except ldb.LdbError as e:
3894 if self.can_ignore_failure(e):
3897 print("Exception: %s" % e)
3898 print("Encountered while trying to apply the following LDIF")
3899 print("----------------------------------------------------")
3900 print("%s" % self.ldif)
3904 # REFRESH AFTER EVERY CHANGE
3905 # Otherwise the OID-to-attribute mapping in _apply_updates_in_file()
3906 # won't work, because it can't lookup the new OID in the schema
3907 self._ldap_schemaUpdateNow(samdb)
3911 class cmd_domain_schema_upgrade(Command):
3912 """Domain schema upgrading"""
3914 synopsis = "%prog [options]"
3916 takes_optiongroups = {
3917 "sambaopts": options.SambaOptions,
3918 "versionopts": options.VersionOptions,
3919 "credopts": options.CredentialsOptions,
3923 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3924 metavar="URL", dest="H"),
3925 Option("--quiet", help="Be quiet", action="store_true"),
3926 Option("--verbose", help="Be verbose", action="store_true"),
3927 Option("--schema", type="choice", metavar="SCHEMA",
3928 choices=["2012", "2012_R2"],
3929 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
3933 def _apply_updates_in_file(self, samdb, ldif_file):
3935 Applies a series of updates specified in an .LDIF file. The .LDIF file
3936 is based on the adprep Schema updates provided by Microsoft.
3939 ldif_op = ldif_schema_update()
3941 # parse the file line by line and work out each update operation to apply
3942 for line in ldif_file:
3944 line = line.rstrip()
3946 # the operations in the .LDIF file are separated by blank lines. If
3947 # we hit a blank line, try to apply the update we've parsed so far
3950 # keep going if we haven't parsed anything yet
3951 if ldif_op.ldif == '':
3954 # Apply the individual change
3955 count += ldif_op.apply(samdb)
3957 # start storing the next operation from scratch again
3958 ldif_op = ldif_schema_update()
3961 # replace the placeholder domain name in the .ldif file with the real domain
3962 if line.upper().endswith('DC=X'):
3963 line = line[:-len('DC=X')] + str(samdb.get_default_basedn())
3964 elif line.upper().endswith('CN=X'):
3965 line = line[:-len('CN=X')] + str(samdb.get_default_basedn())
3967 values = line.split(':')
3969 if values[0].lower() == 'dn':
3970 ldif_op.dn = values[1].strip()
3972 # replace the Windows-specific operation with the Samba one
3973 if values[0].lower() == 'changetype':
3974 line = line.lower().replace(': ntdsschemaadd',
3976 line = line.lower().replace(': ntdsschemamodify',
3979 if values[0].lower() in ['rdnattid', 'subclassof',
3980 'systemposssuperiors',
3982 'systemauxiliaryclass']:
3985 # The Microsoft updates contain some OIDs we don't recognize.
3986 # Query the DB to see if we can work out the OID this update is
3987 # referring to. If we find a match, then replace the OID with
3988 # the ldapDisplayname
3990 res = samdb.search(base=samdb.get_schema_basedn(),
3991 expression="(|(attributeId=%s)(governsId=%s))" %
3993 attrs=['ldapDisplayName'])
3996 ldif_op.unknown_oid = value
3998 display_name = res[0]['ldapDisplayName'][0]
3999 line = line.replace(value, ' ' + display_name)
4001 # Microsoft has marked objects as defunct that Samba doesn't know about
4002 if values[0].lower() == 'isdefunct' and values[1].strip().lower() == 'true':
4003 ldif_op.is_defunct = True
4005 # Samba has added the showInAdvancedViewOnly attribute to all objects,
4006 # so rather than doing an add, we need to do a replace
4007 if values[0].lower() == 'add' and values[1].strip().lower() == 'showinadvancedviewonly':
4008 line = 'replace: showInAdvancedViewOnly'
4010 # Add the line to the current LDIF operation (including the newline
4011 # we stripped off at the start of the loop)
4012 ldif_op.ldif += line + '\n'
4017 def _apply_update(self, samdb, update_file):
4018 """Wrapper function for parsing an LDIF file and applying the updates"""
4020 print("Applying %s updates..." % update_file)
4021 path = setup_path('adprep')
4025 ldif_file = open(os.path.join(path, update_file))
4027 count = self._apply_updates_in_file(samdb, ldif_file)
4033 print("%u changes applied" % count)
4037 def run(self, **kwargs):
4038 from samba.schema import Schema
4040 sambaopts = kwargs.get("sambaopts")
4041 credopts = kwargs.get("credopts")
4042 versionpts = kwargs.get("versionopts")
4043 lp = sambaopts.get_loadparm()
4044 creds = credopts.get_credentials(lp)
4046 target_schema = kwargs.get("schema")
4048 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4050 # work out the version of the target schema we're upgrading to
4051 end = Schema.get_version(target_schema)
4053 # work out the version of the schema we're currently using
4054 res = samdb.search(base=samdb.get_schema_basedn(), scope=ldb.SCOPE_BASE,
4055 attrs=['objectVersion'])
4058 raise CommandError('Could not determine current schema version')
4059 start = int(res[0]['objectVersion'][0]) + 1
4061 samdb.transaction_start()
4065 # Apply the schema updates needed to move to the new schema version
4066 for version in range(start, end + 1):
4067 count += self._apply_update(samdb, 'Sch%d.ldf' % version)
4070 samdb.transaction_commit()
4071 print("Schema successfully updated")
4073 print("No changes applied to schema")
4074 samdb.transaction_cancel()
4075 except Exception as e:
4076 print("Exception: %s" % e)
4077 print("Error encountered, aborting schema upgrade")
4078 samdb.transaction_cancel()
4079 raise CommandError('Failed to upgrade schema')
4081 class cmd_domain(SuperCommand):
4082 """Domain management."""
4085 subcommands["demote"] = cmd_domain_demote()
4086 if cmd_domain_export_keytab is not None:
4087 subcommands["exportkeytab"] = cmd_domain_export_keytab()
4088 subcommands["info"] = cmd_domain_info()
4089 subcommands["provision"] = cmd_domain_provision()
4090 subcommands["join"] = cmd_domain_join()
4091 subcommands["dcpromo"] = cmd_domain_dcpromo()
4092 subcommands["level"] = cmd_domain_level()
4093 subcommands["passwordsettings"] = cmd_domain_passwordsettings()
4094 subcommands["classicupgrade"] = cmd_domain_classicupgrade()
4095 subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()
4096 subcommands["trust"] = cmd_domain_trust()
4097 subcommands["tombstones"] = cmd_domain_tombstones()
4098 subcommands["schemaupgrade"] = cmd_domain_schema_upgrade()