3 # Copyright Matthias Dieter Wallnoefer 2009
4 # Copyright Andrew Kroeger 2009
5 # Copyright Jelmer Vernooij 2007-2012
6 # Copyright Giampaolo Lauria 2011
7 # Copyright Matthieu Patou <mat@matws.net> 2011
8 # Copyright Andrew Bartlett 2008-2015
9 # Copyright Stefan Metzmacher 2012
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import samba.getopt as options
37 from samba import ntstatus
38 from samba import NTSTATUSError
39 from samba import werror
40 from getpass import getpass
41 from samba.net import Net, LIBNET_JOIN_AUTOMATIC
43 from samba.join import join_RODC, join_DC, join_subdomain
44 from samba.auth import system_session
45 from samba.samdb import SamDB
46 from samba.ndr import ndr_unpack, ndr_pack, ndr_print
47 from samba.dcerpc import drsuapi
48 from samba.dcerpc import drsblobs
49 from samba.dcerpc import lsa
50 from samba.dcerpc import netlogon
51 from samba.dcerpc import security
52 from samba.dcerpc import nbt
53 from samba.dcerpc import misc
54 from samba.dcerpc.samr import DOMAIN_PASSWORD_COMPLEX, DOMAIN_PASSWORD_STORE_CLEARTEXT
55 from samba.netcmd import (
61 from samba.netcmd.fsmo import get_fsmo_roleowner
62 from samba.netcmd.common import netcmd_get_domain_infos_via_cldap
63 from samba.samba3 import Samba3
64 from samba.samba3 import param as s3param
65 from samba.upgrade import upgrade_from_samba3
66 from samba.drs_utils import (
67 sendDsReplicaSync, drsuapi_connect, drsException,
69 from samba import remove_dc, arcfour_encrypt, string_to_byte_array
71 from samba.dsdb import (
72 DS_DOMAIN_FUNCTION_2000,
73 DS_DOMAIN_FUNCTION_2003,
74 DS_DOMAIN_FUNCTION_2003_MIXED,
75 DS_DOMAIN_FUNCTION_2008,
76 DS_DOMAIN_FUNCTION_2008_R2,
77 DS_DOMAIN_FUNCTION_2012,
78 DS_DOMAIN_FUNCTION_2012_R2,
79 DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL,
80 DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL,
81 UF_WORKSTATION_TRUST_ACCOUNT,
82 UF_SERVER_TRUST_ACCOUNT,
83 UF_TRUSTED_FOR_DELEGATION,
84 UF_PARTIAL_SECRETS_ACCOUNT
87 from samba.provision import (
90 DEFAULT_MIN_PWD_LENGTH,
94 from samba.provision.common import (
100 def get_testparm_var(testparm, smbconf, varname):
101 errfile = open(os.devnull, 'w')
102 p = subprocess.Popen([testparm, '-s', '-l',
103 '--parameter-name=%s' % varname, smbconf],
104 stdout=subprocess.PIPE, stderr=errfile)
105 (out,err) = p.communicate()
107 lines = out.split('\n')
109 return lines[0].strip()
113 import samba.dckeytab
115 cmd_domain_export_keytab = None
117 class cmd_domain_export_keytab(Command):
118 """Dump Kerberos keys of the domain into a keytab."""
120 synopsis = "%prog <keytab> [options]"
122 takes_optiongroups = {
123 "sambaopts": options.SambaOptions,
124 "credopts": options.CredentialsOptions,
125 "versionopts": options.VersionOptions,
129 Option("--principal", help="extract only this principal", type=str),
132 takes_args = ["keytab"]
134 def run(self, keytab, credopts=None, sambaopts=None, versionopts=None, principal=None):
135 lp = sambaopts.get_loadparm()
137 net.export_keytab(keytab=keytab, principal=principal)
140 class cmd_domain_info(Command):
141 """Print basic info about a domain and the DC passed as parameter."""
143 synopsis = "%prog <ip_address> [options]"
148 takes_optiongroups = {
149 "sambaopts": options.SambaOptions,
150 "credopts": options.CredentialsOptions,
151 "versionopts": options.VersionOptions,
154 takes_args = ["address"]
156 def run(self, address, credopts=None, sambaopts=None, versionopts=None):
157 lp = sambaopts.get_loadparm()
159 res = netcmd_get_domain_infos_via_cldap(lp, None, address)
161 raise CommandError("Invalid IP address '" + address + "'!")
162 self.outf.write("Forest : %s\n" % res.forest)
163 self.outf.write("Domain : %s\n" % res.dns_domain)
164 self.outf.write("Netbios domain : %s\n" % res.domain_name)
165 self.outf.write("DC name : %s\n" % res.pdc_dns_name)
166 self.outf.write("DC netbios name : %s\n" % res.pdc_name)
167 self.outf.write("Server site : %s\n" % res.server_site)
168 self.outf.write("Client site : %s\n" % res.client_site)
171 class cmd_domain_provision(Command):
172 """Provision a domain."""
174 synopsis = "%prog [options]"
176 takes_optiongroups = {
177 "sambaopts": options.SambaOptions,
178 "versionopts": options.VersionOptions,
182 Option("--interactive", help="Ask for names", action="store_true"),
183 Option("--domain", type="string", metavar="DOMAIN",
184 help="NetBIOS domain name to use"),
185 Option("--domain-guid", type="string", metavar="GUID",
186 help="set domainguid (otherwise random)"),
187 Option("--domain-sid", type="string", metavar="SID",
188 help="set domainsid (otherwise random)"),
189 Option("--ntds-guid", type="string", metavar="GUID",
190 help="set NTDS object GUID (otherwise random)"),
191 Option("--invocationid", type="string", metavar="GUID",
192 help="set invocationid (otherwise random)"),
193 Option("--host-name", type="string", metavar="HOSTNAME",
194 help="set hostname"),
195 Option("--host-ip", type="string", metavar="IPADDRESS",
196 help="set IPv4 ipaddress"),
197 Option("--host-ip6", type="string", metavar="IP6ADDRESS",
198 help="set IPv6 ipaddress"),
199 Option("--site", type="string", metavar="SITENAME",
200 help="set site name"),
201 Option("--adminpass", type="string", metavar="PASSWORD",
202 help="choose admin password (otherwise random)"),
203 Option("--krbtgtpass", type="string", metavar="PASSWORD",
204 help="choose krbtgt password (otherwise random)"),
205 Option("--machinepass", type="string", metavar="PASSWORD",
206 help="choose machine password (otherwise random)"),
207 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
208 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
209 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
210 "BIND9_FLATFILE uses bind9 text database to store zone information, "
211 "BIND9_DLZ uses samba4 AD to store zone information, "
212 "NONE skips the DNS setup entirely (not recommended)",
213 default="SAMBA_INTERNAL"),
214 Option("--dnspass", type="string", metavar="PASSWORD",
215 help="choose dns password (otherwise random)"),
216 Option("--ldapadminpass", type="string", metavar="PASSWORD",
217 help="choose password to set between Samba and its LDAP backend (otherwise random)"),
218 Option("--root", type="string", metavar="USERNAME",
219 help="choose 'root' unix username"),
220 Option("--nobody", type="string", metavar="USERNAME",
221 help="choose 'nobody' user"),
222 Option("--users", type="string", metavar="GROUPNAME",
223 help="choose 'users' group"),
224 Option("--quiet", help="Be quiet", action="store_true"),
225 Option("--blank", action="store_true",
226 help="do not add users or groups, just the structure"),
227 Option("--ldap-backend-type", type="choice", metavar="LDAP-BACKEND-TYPE",
228 help="Test initialisation support for unsupported LDAP backend type (fedora-ds or openldap) DO NOT USE",
229 choices=["fedora-ds", "openldap"]),
230 Option("--server-role", type="choice", metavar="ROLE",
231 choices=["domain controller", "dc", "member server", "member", "standalone"],
232 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
233 default="domain controller"),
234 Option("--function-level", type="choice", metavar="FOR-FUN-LEVEL",
235 choices=["2000", "2003", "2008", "2008_R2"],
236 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2008_R2 Native.",
238 Option("--base-schema", type="choice", metavar="BASE-SCHEMA",
239 choices=["2008_R2", "2008_R2_old", "2012", "2012_R2"],
240 help="The base schema files to use. Default is (Windows) 2008_R2.",
242 Option("--next-rid", type="int", metavar="NEXTRID", default=1000,
243 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
244 Option("--partitions-only",
245 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"),
246 Option("--targetdir", type="string", metavar="DIR",
247 help="Set target directory"),
248 Option("--ol-mmr-urls", type="string", metavar="LDAPSERVER",
249 help="List of LDAP-URLS [ ldap://<FQHN>:<PORT>/ (where <PORT> has to be different than 389!) ] separated with comma (\",\") for use with OpenLDAP-MMR (Multi-Master-Replication), e.g.: \"ldap://s4dc1:9000,ldap://s4dc2:9000\""),
250 Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
251 Option("--plaintext-secrets", action="store_true",
252 help="Store secret/sensitive values as plain text on disk" +
253 "(default is to encrypt secret/ensitive values)"),
257 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",
258 action="store_true"),
259 Option("--slapd-path", type="string", metavar="SLAPD-PATH",
260 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."),
261 Option("--ldap-backend-extra-port", type="int", metavar="LDAP-BACKEND-EXTRA-PORT", help="Additional TCP port for LDAP backend server (to use for replication)"),
262 Option("--ldap-backend-forced-uri", type="string", metavar="LDAP-BACKEND-FORCED-URI",
263 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"),
264 Option("--ldap-backend-nosync", help="Configure LDAP backend not to call fsync() (for performance in test environments)", action="store_true"),
268 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
269 Option("--use-xattrs", type="choice", choices=["yes","no","auto"],
270 metavar="[yes|no|auto]",
271 help="Define if we should use the native fs capabilities or a tdb file for "
272 "storing attributes likes ntacl when --use-ntvfs is set. "
273 "auto tries to make an inteligent guess based on the user rights and system capabilities",
277 if os.getenv('TEST_LDAP', "no") == "yes":
278 takes_options.extend(openldap_options)
280 if samba.is_ntvfs_fileserver_built():
281 takes_options.extend(ntvfs_options)
285 def run(self, sambaopts=None, versionopts=None,
308 ldap_backend_type=None,
312 partitions_only=None,
319 ldap_backend_nosync=None,
320 ldap_backend_extra_port=None,
321 ldap_backend_forced_uri=None,
322 ldap_dryrun_mode=None,
324 plaintext_secrets=False):
326 self.logger = self.get_logger("provision")
328 self.logger.setLevel(logging.WARNING)
330 self.logger.setLevel(logging.INFO)
332 lp = sambaopts.get_loadparm()
333 smbconf = lp.configfile
335 if dns_forwarder is not None:
336 suggested_forwarder = dns_forwarder
338 suggested_forwarder = self._get_nameserver_ip()
339 if suggested_forwarder is None:
340 suggested_forwarder = "none"
342 if len(self.raw_argv) == 1:
346 from getpass import getpass
349 def ask(prompt, default=None):
350 if default is not None:
351 print "%s [%s]: " % (prompt, default),
353 print "%s: " % (prompt,),
354 return sys.stdin.readline().rstrip("\n") or default
357 default = socket.getfqdn().split(".", 1)[1].upper()
360 realm = ask("Realm", default)
361 if realm in (None, ""):
362 raise CommandError("No realm set!")
365 default = realm.split(".")[0]
368 domain = ask("Domain", default)
370 raise CommandError("No domain set!")
372 server_role = ask("Server Role (dc, member, standalone)", "dc")
374 dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
375 if dns_backend in (None, ''):
376 raise CommandError("No DNS backend set!")
378 if dns_backend == "SAMBA_INTERNAL":
379 dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
380 if dns_forwarder.lower() in (None, 'none'):
381 suggested_forwarder = None
385 adminpassplain = getpass("Administrator password: ")
386 issue = self._adminpass_issue(adminpassplain)
388 self.errf.write("%s.\n" % issue)
390 adminpassverify = getpass("Retype password: ")
391 if not adminpassplain == adminpassverify:
392 self.errf.write("Sorry, passwords do not match.\n")
394 adminpass = adminpassplain
398 realm = sambaopts._lp.get('realm')
400 raise CommandError("No realm set!")
402 raise CommandError("No domain set!")
405 issue = self._adminpass_issue(adminpass)
407 raise CommandError(issue)
409 self.logger.info("Administrator password will be set randomly!")
411 if function_level == "2000":
412 dom_for_fun_level = DS_DOMAIN_FUNCTION_2000
413 elif function_level == "2003":
414 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
415 elif function_level == "2008":
416 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
417 elif function_level == "2008_R2":
418 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
420 if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
421 dns_forwarder = suggested_forwarder
423 samdb_fill = FILL_FULL
425 samdb_fill = FILL_NT4SYNC
426 elif partitions_only:
427 samdb_fill = FILL_DRS
429 if targetdir is not None:
430 if not os.path.isdir(targetdir):
435 if use_xattrs == "yes":
437 elif use_xattrs == "auto" and use_ntvfs == False:
439 elif use_ntvfs == False:
440 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
441 "Please re-run with --use-xattrs omitted.")
442 elif use_xattrs == "auto" and not lp.get("posix:eadb"):
444 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
446 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
449 samba.ntacls.setntacl(lp, file.name,
450 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
453 self.logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. ")
458 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.")
459 if ldap_backend_type == "existing":
460 if ldap_backend_forced_uri is not None:
461 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)
463 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")
465 if ldap_backend_forced_uri is not None:
466 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")
468 if domain_sid is not None:
469 domain_sid = security.dom_sid(domain_sid)
471 session = system_session()
473 result = provision(self.logger,
474 session, smbconf=smbconf, targetdir=targetdir,
475 samdb_fill=samdb_fill, realm=realm, domain=domain,
476 domainguid=domain_guid, domainsid=domain_sid,
478 hostip=host_ip, hostip6=host_ip6,
479 sitename=site, ntdsguid=ntds_guid,
480 invocationid=invocationid, adminpass=adminpass,
481 krbtgtpass=krbtgtpass, machinepass=machinepass,
482 dns_backend=dns_backend, dns_forwarder=dns_forwarder,
483 dnspass=dnspass, root=root, nobody=nobody,
485 serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
486 backend_type=ldap_backend_type,
487 ldapadminpass=ldapadminpass, ol_mmr_urls=ol_mmr_urls, slapd_path=slapd_path,
488 useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
489 use_rfc2307=use_rfc2307, skip_sysvolacl=False,
490 ldap_backend_extra_port=ldap_backend_extra_port,
491 ldap_backend_forced_uri=ldap_backend_forced_uri,
492 nosync=ldap_backend_nosync, ldap_dryrun_mode=ldap_dryrun_mode,
493 base_schema=base_schema,
494 plaintext_secrets=plaintext_secrets)
496 except ProvisioningError, e:
497 raise CommandError("Provision failed", e)
499 result.report_logger(self.logger)
501 def _get_nameserver_ip(self):
502 """Grab the nameserver IP address from /etc/resolv.conf."""
504 RESOLV_CONF="/etc/resolv.conf"
506 if not path.isfile(RESOLV_CONF):
507 self.logger.warning("Failed to locate %s" % RESOLV_CONF)
512 handle = open(RESOLV_CONF, 'r')
514 if not line.startswith('nameserver'):
516 # we want the last non-space continuous string of the line
517 return line.strip().split()[-1]
519 if handle is not None:
522 self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
524 def _adminpass_issue(self, adminpass):
525 """Returns error string for a bad administrator password,
526 or None if acceptable"""
528 if len(adminpass.decode('utf-8')) < DEFAULT_MIN_PWD_LENGTH:
529 return "Administrator password does not meet the default minimum" \
530 " password length requirement (%d characters)" \
531 % DEFAULT_MIN_PWD_LENGTH
532 elif not samba.check_password_quality(adminpass):
533 return "Administrator password does not meet the default" \
539 class cmd_domain_dcpromo(Command):
540 """Promote an existing domain member or NT4 PDC to an AD DC."""
542 synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
544 takes_optiongroups = {
545 "sambaopts": options.SambaOptions,
546 "versionopts": options.VersionOptions,
547 "credopts": options.CredentialsOptions,
551 Option("--server", help="DC to join", type=str),
552 Option("--site", help="site to join", type=str),
553 Option("--targetdir", help="where to store provision", type=str),
554 Option("--domain-critical-only",
555 help="only replicate critical domain objects",
556 action="store_true"),
557 Option("--machinepass", type=str, metavar="PASSWORD",
558 help="choose machine password (otherwise random)"),
559 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
560 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
561 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
562 "BIND9_DLZ uses samba4 AD to store zone information, "
563 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
564 default="SAMBA_INTERNAL"),
565 Option("--quiet", help="Be quiet", action="store_true"),
566 Option("--verbose", help="Be verbose", action="store_true")
570 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
573 if samba.is_ntvfs_fileserver_built():
574 takes_options.extend(ntvfs_options)
577 takes_args = ["domain", "role?"]
579 def run(self, domain, role=None, sambaopts=None, credopts=None,
580 versionopts=None, server=None, site=None, targetdir=None,
581 domain_critical_only=False, parent_domain=None, machinepass=None,
582 use_ntvfs=False, dns_backend=None,
583 quiet=False, verbose=False):
584 lp = sambaopts.get_loadparm()
585 creds = credopts.get_credentials(lp)
586 net = Net(creds, lp, server=credopts.ipaddress)
588 logger = self.get_logger()
590 logger.setLevel(logging.DEBUG)
592 logger.setLevel(logging.WARNING)
594 logger.setLevel(logging.INFO)
596 netbios_name = lp.get("netbios name")
602 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
603 site=site, netbios_name=netbios_name, targetdir=targetdir,
604 domain_critical_only=domain_critical_only,
605 machinepass=machinepass, use_ntvfs=use_ntvfs,
606 dns_backend=dns_backend,
607 promote_existing=True)
609 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
610 site=site, netbios_name=netbios_name, targetdir=targetdir,
611 domain_critical_only=domain_critical_only,
612 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
613 promote_existing=True)
615 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
618 class cmd_domain_join(Command):
619 """Join domain as either member or backup domain controller."""
621 synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
623 takes_optiongroups = {
624 "sambaopts": options.SambaOptions,
625 "versionopts": options.VersionOptions,
626 "credopts": options.CredentialsOptions,
630 Option("--server", help="DC to join", type=str),
631 Option("--site", help="site to join", type=str),
632 Option("--targetdir", help="where to store provision", type=str),
633 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
634 Option("--domain-critical-only",
635 help="only replicate critical domain objects",
636 action="store_true"),
637 Option("--machinepass", type=str, metavar="PASSWORD",
638 help="choose machine password (otherwise random)"),
639 Option("--adminpass", type="string", metavar="PASSWORD",
640 help="choose adminstrator password when joining as a subdomain (otherwise random)"),
641 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
642 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
643 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
644 "BIND9_DLZ uses samba4 AD to store zone information, "
645 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
646 default="SAMBA_INTERNAL"),
647 Option("--plaintext-secrets", action="store_true",
648 help="Store secret/sensitive values as plain text on disk" +
649 "(default is to encrypt secret/ensitive values)"),
650 Option("--quiet", help="Be quiet", action="store_true"),
651 Option("--verbose", help="Be verbose", action="store_true")
655 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
658 if samba.is_ntvfs_fileserver_built():
659 takes_options.extend(ntvfs_options)
661 takes_args = ["domain", "role?"]
663 def run(self, domain, role=None, sambaopts=None, credopts=None,
664 versionopts=None, server=None, site=None, targetdir=None,
665 domain_critical_only=False, parent_domain=None, machinepass=None,
666 use_ntvfs=False, dns_backend=None, adminpass=None,
667 quiet=False, verbose=False, plaintext_secrets=False):
668 lp = sambaopts.get_loadparm()
669 creds = credopts.get_credentials(lp)
670 net = Net(creds, lp, server=credopts.ipaddress)
673 site = "Default-First-Site-Name"
675 logger = self.get_logger()
677 logger.setLevel(logging.DEBUG)
679 logger.setLevel(logging.WARNING)
681 logger.setLevel(logging.INFO)
683 netbios_name = lp.get("netbios name")
688 if role is None or role == "MEMBER":
689 (join_password, sid, domain_name) = net.join_member(
690 domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
691 machinepass=machinepass)
693 self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
695 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
696 site=site, netbios_name=netbios_name, targetdir=targetdir,
697 domain_critical_only=domain_critical_only,
698 machinepass=machinepass, use_ntvfs=use_ntvfs,
699 dns_backend=dns_backend,
700 plaintext_secrets=plaintext_secrets)
702 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
703 site=site, netbios_name=netbios_name, targetdir=targetdir,
704 domain_critical_only=domain_critical_only,
705 machinepass=machinepass, use_ntvfs=use_ntvfs,
706 dns_backend=dns_backend,
707 plaintext_secrets=plaintext_secrets)
708 elif role == "SUBDOMAIN":
710 logger.info("Administrator password will be set randomly!")
712 netbios_domain = lp.get("workgroup")
713 if parent_domain is None:
714 parent_domain = ".".join(domain.split(".")[1:])
715 join_subdomain(logger=logger, server=server, creds=creds, lp=lp, dnsdomain=domain,
716 parent_domain=parent_domain, site=site,
717 netbios_name=netbios_name, netbios_domain=netbios_domain,
718 targetdir=targetdir, machinepass=machinepass,
719 use_ntvfs=use_ntvfs, dns_backend=dns_backend,
721 plaintext_secrets=plaintext_secrets)
723 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role)
726 class cmd_domain_demote(Command):
727 """Demote ourselves from the role of Domain Controller."""
729 synopsis = "%prog [options]"
732 Option("--server", help="writable DC to write demotion changes on", type=str),
733 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
734 metavar="URL", dest="H"),
735 Option("--remove-other-dead-server", help="Dead DC (name or NTDS GUID) "
736 "to remove ALL references to (rather than this DC)", type=str),
737 Option("--quiet", help="Be quiet", action="store_true"),
738 Option("--verbose", help="Be verbose", action="store_true"),
741 takes_optiongroups = {
742 "sambaopts": options.SambaOptions,
743 "credopts": options.CredentialsOptions,
744 "versionopts": options.VersionOptions,
747 def run(self, sambaopts=None, credopts=None,
748 versionopts=None, server=None,
749 remove_other_dead_server=None, H=None,
750 verbose=False, quiet=False):
751 lp = sambaopts.get_loadparm()
752 creds = credopts.get_credentials(lp)
753 net = Net(creds, lp, server=credopts.ipaddress)
755 logger = self.get_logger()
757 logger.setLevel(logging.DEBUG)
759 logger.setLevel(logging.WARNING)
761 logger.setLevel(logging.INFO)
763 if remove_other_dead_server is not None:
764 if server is not None:
765 samdb = SamDB(url="ldap://%s" % server,
766 session_info=system_session(),
767 credentials=creds, lp=lp)
769 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
771 remove_dc.remove_dc(samdb, logger, remove_other_dead_server)
772 except remove_dc.DemoteException as err:
773 raise CommandError("Demote failed: %s" % err)
776 netbios_name = lp.get("netbios name")
777 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
779 res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
781 raise CommandError("Unable to search for servers")
784 raise CommandError("You are the latest server in the domain")
788 if str(e["name"]).lower() != netbios_name.lower():
789 server = e["dnsHostName"]
792 ntds_guid = samdb.get_ntds_GUID()
793 msg = samdb.search(base=str(samdb.get_config_basedn()),
794 scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
796 if len(msg) == 0 or "options" not in msg[0]:
797 raise CommandError("Failed to find options on %s" % ntds_guid)
800 dsa_options = int(str(msg[0]['options']))
802 res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
803 controls=["search_options:1:2"])
806 raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res))
808 self.errf.write("Using %s as partner server for the demotion\n" %
810 (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
812 self.errf.write("Deactivating inbound replication\n")
817 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
818 dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
819 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
823 self.errf.write("Asking partner server %s to synchronize from us\n"
825 for part in (samdb.get_schema_basedn(),
826 samdb.get_config_basedn(),
827 samdb.get_root_basedn()):
828 nc = drsuapi.DsReplicaObjectIdentifier()
831 req1 = drsuapi.DsReplicaSyncRequest1()
832 req1.naming_context = nc;
833 req1.options = drsuapi.DRSUAPI_DRS_WRIT_REP
834 req1.source_dsa_guid = misc.GUID(ntds_guid)
837 drsuapiBind.DsReplicaSync(drsuapi_handle, 1, req1)
838 except RuntimeError as (werr, string):
839 if werr == werror.WERR_DS_DRA_NO_REPLICA:
843 "Error while replicating out last local changes from '%s' for demotion, "
844 "re-enabling inbound replication\n" % part)
845 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
846 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
848 raise CommandError("Error while sending a DsReplicaSync for partition '%s'" % str(part), string)
850 remote_samdb = SamDB(url="ldap://%s" % server,
851 session_info=system_session(),
852 credentials=creds, lp=lp)
854 self.errf.write("Changing userControl and container\n")
855 res = remote_samdb.search(base=str(remote_samdb.domain_dn()),
856 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
857 netbios_name.upper(),
858 attrs=["userAccountControl"])
860 uac = int(str(res[0]["userAccountControl"]))
863 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
865 "Error while demoting, re-enabling inbound replication\n")
866 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
867 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
869 raise CommandError("Error while changing account control", e)
872 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
874 "Error while demoting, re-enabling inbound replication")
875 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
876 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
878 raise CommandError("Unable to find object with samaccountName = %s$"
879 " in the remote dc" % netbios_name.upper())
883 uac &= ~(UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION|UF_PARTIAL_SECRETS_ACCOUNT)
884 uac |= UF_WORKSTATION_TRUST_ACCOUNT
889 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
890 ldb.FLAG_MOD_REPLACE,
891 "userAccountControl")
893 remote_samdb.modify(msg)
895 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
897 "Error while demoting, re-enabling inbound replication")
898 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
899 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
902 raise CommandError("Error while changing account control", e)
904 parent = msg.dn.parent()
905 dc_name = res[0].dn.get_rdn_value()
906 rdn = "CN=%s" % dc_name
908 # Let's move to the Computer container
912 computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.domain_dn()))
913 res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
916 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
917 scope=ldb.SCOPE_ONELEVEL)
918 while(len(res) != 0 and i < 100):
920 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
921 scope=ldb.SCOPE_ONELEVEL)
924 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
926 "Error while demoting, re-enabling inbound replication\n")
927 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
928 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
934 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
935 ldb.FLAG_MOD_REPLACE,
936 "userAccountControl")
938 remote_samdb.modify(msg)
940 raise CommandError("Unable to find a slot for renaming %s,"
941 " all names from %s-1 to %s-%d seemed used" %
942 (str(dc_dn), rdn, rdn, i - 9))
944 newrdn = "%s-%d" % (rdn, i)
947 newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
948 remote_samdb.rename(dc_dn, newdn)
950 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
952 "Error while demoting, re-enabling inbound replication\n")
953 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
954 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
960 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
961 ldb.FLAG_MOD_REPLACE,
962 "userAccountControl")
964 remote_samdb.modify(msg)
965 raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
968 server_dsa_dn = samdb.get_serverName()
969 domain = remote_samdb.get_root_basedn()
972 req1 = drsuapi.DsRemoveDSServerRequest1()
973 req1.server_dn = str(server_dsa_dn)
974 req1.domain_dn = str(domain)
977 drsuapiBind.DsRemoveDSServer(drsuapi_handle, 1, req1)
978 except RuntimeError as (werr, string):
979 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
981 "Error while demoting, re-enabling inbound replication\n")
982 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
983 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
989 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
990 ldb.FLAG_MOD_REPLACE,
991 "userAccountControl")
992 remote_samdb.modify(msg)
993 remote_samdb.rename(newdn, dc_dn)
994 if werr == werror.WERR_DS_DRA_NO_REPLICA:
995 raise CommandError("The DC %s is not present on (already removed from) the remote server: " % server_dsa_dn, e)
997 raise CommandError("Error while sending a removeDsServer of %s: " % server_dsa_dn, e)
999 remove_dc.remove_sysvol_references(remote_samdb, logger, dc_name)
1001 # These are objects under the computer account that should be deleted
1002 for s in ("CN=Enterprise,CN=NTFRS Subscriptions",
1003 "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
1004 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
1005 "CN=NTFRS Subscriptions"):
1007 remote_samdb.delete(ldb.Dn(remote_samdb,
1008 "%s,%s" % (s, str(newdn))))
1009 except ldb.LdbError, l:
1012 self.errf.write("Demote successful\n")
1015 class cmd_domain_level(Command):
1016 """Raise domain and forest function levels."""
1018 synopsis = "%prog (show|raise <options>) [options]"
1020 takes_optiongroups = {
1021 "sambaopts": options.SambaOptions,
1022 "credopts": options.CredentialsOptions,
1023 "versionopts": options.VersionOptions,
1027 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1028 metavar="URL", dest="H"),
1029 Option("--quiet", help="Be quiet", action="store_true"),
1030 Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1031 help="The forest function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)"),
1032 Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1033 help="The domain function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)")
1036 takes_args = ["subcommand"]
1038 def run(self, subcommand, H=None, forest_level=None, domain_level=None,
1039 quiet=False, credopts=None, sambaopts=None, versionopts=None):
1040 lp = sambaopts.get_loadparm()
1041 creds = credopts.get_credentials(lp, fallback_machine=True)
1043 samdb = SamDB(url=H, session_info=system_session(),
1044 credentials=creds, lp=lp)
1046 domain_dn = samdb.domain_dn()
1048 res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
1049 scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
1050 assert len(res_forest) == 1
1052 res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1053 attrs=["msDS-Behavior-Version", "nTMixedDomain"])
1054 assert len(res_domain) == 1
1056 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
1057 scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
1058 attrs=["msDS-Behavior-Version"])
1059 assert len(res_dc_s) >= 1
1061 # default values, since "msDS-Behavior-Version" does not exist on Windows 2000 AD
1062 level_forest = DS_DOMAIN_FUNCTION_2000
1063 level_domain = DS_DOMAIN_FUNCTION_2000
1065 if "msDS-Behavior-Version" in res_forest[0]:
1066 level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
1067 if "msDS-Behavior-Version" in res_domain[0]:
1068 level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
1069 level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
1072 for msg in res_dc_s:
1073 if "msDS-Behavior-Version" in msg:
1074 if min_level_dc is None or int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
1075 min_level_dc = int(msg["msDS-Behavior-Version"][0])
1077 min_level_dc = DS_DOMAIN_FUNCTION_2000
1078 # well, this is the least
1081 if level_forest < DS_DOMAIN_FUNCTION_2000 or level_domain < DS_DOMAIN_FUNCTION_2000:
1082 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
1083 if min_level_dc < DS_DOMAIN_FUNCTION_2000:
1084 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
1085 if level_forest > level_domain:
1086 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
1087 if level_domain > min_level_dc:
1088 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
1090 if subcommand == "show":
1091 self.message("Domain and forest function level for domain '%s'" % domain_dn)
1092 if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1093 self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1094 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1095 self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1096 if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1097 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)!")
1101 if level_forest == DS_DOMAIN_FUNCTION_2000:
1103 elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
1104 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1105 elif level_forest == DS_DOMAIN_FUNCTION_2003:
1107 elif level_forest == DS_DOMAIN_FUNCTION_2008:
1109 elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
1111 elif level_forest == DS_DOMAIN_FUNCTION_2012:
1113 elif level_forest == DS_DOMAIN_FUNCTION_2012_R2:
1116 outstr = "higher than 2012 R2"
1117 self.message("Forest function level: (Windows) " + outstr)
1119 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1120 outstr = "2000 mixed (NT4 DC support)"
1121 elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
1123 elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
1124 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1125 elif level_domain == DS_DOMAIN_FUNCTION_2003:
1127 elif level_domain == DS_DOMAIN_FUNCTION_2008:
1129 elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
1131 elif level_domain == DS_DOMAIN_FUNCTION_2012:
1133 elif level_domain == DS_DOMAIN_FUNCTION_2012_R2:
1136 outstr = "higher than 2012 R2"
1137 self.message("Domain function level: (Windows) " + outstr)
1139 if min_level_dc == DS_DOMAIN_FUNCTION_2000:
1141 elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
1143 elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
1145 elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
1147 elif min_level_dc == DS_DOMAIN_FUNCTION_2012:
1149 elif min_level_dc == DS_DOMAIN_FUNCTION_2012_R2:
1152 outstr = "higher than 2012 R2"
1153 self.message("Lowest function level of a DC: (Windows) " + outstr)
1155 elif subcommand == "raise":
1158 if domain_level is not None:
1159 if domain_level == "2003":
1160 new_level_domain = DS_DOMAIN_FUNCTION_2003
1161 elif domain_level == "2008":
1162 new_level_domain = DS_DOMAIN_FUNCTION_2008
1163 elif domain_level == "2008_R2":
1164 new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
1165 elif domain_level == "2012":
1166 new_level_domain = DS_DOMAIN_FUNCTION_2012
1167 elif domain_level == "2012_R2":
1168 new_level_domain = DS_DOMAIN_FUNCTION_2012_R2
1170 if new_level_domain <= level_domain and level_domain_mixed == 0:
1171 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
1172 if new_level_domain > min_level_dc:
1173 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
1175 # Deactivate mixed/interim domain support
1176 if level_domain_mixed != 0:
1177 # Directly on the base DN
1179 m.dn = ldb.Dn(samdb, domain_dn)
1180 m["nTMixedDomain"] = ldb.MessageElement("0",
1181 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1185 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
1186 m["nTMixedDomain"] = ldb.MessageElement("0",
1187 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1190 except ldb.LdbError, (enum, emsg):
1191 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1194 # Directly on the base DN
1196 m.dn = ldb.Dn(samdb, domain_dn)
1197 m["msDS-Behavior-Version"]= ldb.MessageElement(
1198 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1199 "msDS-Behavior-Version")
1203 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
1204 + ",CN=Partitions,%s" % samdb.get_config_basedn())
1205 m["msDS-Behavior-Version"]= ldb.MessageElement(
1206 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1207 "msDS-Behavior-Version")
1210 except ldb.LdbError, (enum, emsg):
1211 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1214 level_domain = new_level_domain
1215 msgs.append("Domain function level changed!")
1217 if forest_level is not None:
1218 if forest_level == "2003":
1219 new_level_forest = DS_DOMAIN_FUNCTION_2003
1220 elif forest_level == "2008":
1221 new_level_forest = DS_DOMAIN_FUNCTION_2008
1222 elif forest_level == "2008_R2":
1223 new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
1224 elif forest_level == "2012":
1225 new_level_forest = DS_DOMAIN_FUNCTION_2012
1226 elif forest_level == "2012_R2":
1227 new_level_forest = DS_DOMAIN_FUNCTION_2012_R2
1229 if new_level_forest <= level_forest:
1230 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1231 if new_level_forest > level_domain:
1232 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1235 m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
1236 m["msDS-Behavior-Version"]= ldb.MessageElement(
1237 str(new_level_forest), ldb.FLAG_MOD_REPLACE,
1238 "msDS-Behavior-Version")
1240 msgs.append("Forest function level changed!")
1241 msgs.append("All changes applied successfully!")
1242 self.message("\n".join(msgs))
1244 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
1247 class cmd_domain_passwordsettings(Command):
1248 """Set password settings.
1250 Password complexity, password lockout policy, history length,
1251 minimum password length, the minimum and maximum password age) on
1252 a Samba AD DC server.
1254 Use against a Windows DC is possible, but group policy will override it.
1257 synopsis = "%prog (show|set <options>) [options]"
1259 takes_optiongroups = {
1260 "sambaopts": options.SambaOptions,
1261 "versionopts": options.VersionOptions,
1262 "credopts": options.CredentialsOptions,
1266 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1267 metavar="URL", dest="H"),
1268 Option("--quiet", help="Be quiet", action="store_true"),
1269 Option("--complexity", type="choice", choices=["on","off","default"],
1270 help="The password complexity (on | off | default). Default is 'on'"),
1271 Option("--store-plaintext", type="choice", choices=["on","off","default"],
1272 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1273 Option("--history-length",
1274 help="The password history length (<integer> | default). Default is 24.", type=str),
1275 Option("--min-pwd-length",
1276 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1277 Option("--min-pwd-age",
1278 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1279 Option("--max-pwd-age",
1280 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1281 Option("--account-lockout-duration",
1282 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),
1283 Option("--account-lockout-threshold",
1284 help="The number of bad password attempts allowed before locking out the account (<integer> | default). Default is 0 (never lock out).", type=str),
1285 Option("--reset-account-lockout-after",
1286 help="After this time is elapsed, the recorded number of attempts restarts from zero (<integer> | default). Default is 30.", type=str),
1289 takes_args = ["subcommand"]
1291 def run(self, subcommand, H=None, min_pwd_age=None, max_pwd_age=None,
1292 quiet=False, complexity=None, store_plaintext=None, history_length=None,
1293 min_pwd_length=None, account_lockout_duration=None, account_lockout_threshold=None,
1294 reset_account_lockout_after=None, credopts=None, sambaopts=None,
1296 lp = sambaopts.get_loadparm()
1297 creds = credopts.get_credentials(lp)
1299 samdb = SamDB(url=H, session_info=system_session(),
1300 credentials=creds, lp=lp)
1302 domain_dn = samdb.domain_dn()
1303 res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1304 attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1305 "minPwdAge", "maxPwdAge", "lockoutDuration", "lockoutThreshold",
1306 "lockOutObservationWindow"])
1307 assert(len(res) == 1)
1309 pwd_props = int(res[0]["pwdProperties"][0])
1310 pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
1311 cur_min_pwd_len = int(res[0]["minPwdLength"][0])
1313 cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1314 if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000:
1317 cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1318 cur_account_lockout_threshold = int(res[0]["lockoutThreshold"][0])
1320 if int(res[0]["lockoutDuration"][0]) == -0x8000000000000000:
1321 cur_account_lockout_duration = 0
1323 cur_account_lockout_duration = abs(int(res[0]["lockoutDuration"][0])) / (1e7 * 60)
1324 cur_reset_account_lockout_after = abs(int(res[0]["lockOutObservationWindow"][0])) / (1e7 * 60)
1325 except Exception, e:
1326 raise CommandError("Could not retrieve password properties!", e)
1328 if subcommand == "show":
1329 self.message("Password informations for domain '%s'" % domain_dn)
1331 if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
1332 self.message("Password complexity: on")
1334 self.message("Password complexity: off")
1335 if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
1336 self.message("Store plaintext passwords: on")
1338 self.message("Store plaintext passwords: off")
1339 self.message("Password history length: %d" % pwd_hist_len)
1340 self.message("Minimum password length: %d" % cur_min_pwd_len)
1341 self.message("Minimum password age (days): %d" % cur_min_pwd_age)
1342 self.message("Maximum password age (days): %d" % cur_max_pwd_age)
1343 self.message("Account lockout duration (mins): %d" % cur_account_lockout_duration)
1344 self.message("Account lockout threshold (attempts): %d" % cur_account_lockout_threshold)
1345 self.message("Reset account lockout after (mins): %d" % cur_reset_account_lockout_after)
1346 elif subcommand == "set":
1349 m.dn = ldb.Dn(samdb, domain_dn)
1351 if complexity is not None:
1352 if complexity == "on" or complexity == "default":
1353 pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
1354 msgs.append("Password complexity activated!")
1355 elif complexity == "off":
1356 pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
1357 msgs.append("Password complexity deactivated!")
1359 if store_plaintext is not None:
1360 if store_plaintext == "on" or store_plaintext == "default":
1361 pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1362 msgs.append("Plaintext password storage for changed passwords activated!")
1363 elif store_plaintext == "off":
1364 pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
1365 msgs.append("Plaintext password storage for changed passwords deactivated!")
1367 if complexity is not None or store_plaintext is not None:
1368 m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
1369 ldb.FLAG_MOD_REPLACE, "pwdProperties")
1371 if history_length is not None:
1372 if history_length == "default":
1375 pwd_hist_len = int(history_length)
1377 if pwd_hist_len < 0 or pwd_hist_len > 24:
1378 raise CommandError("Password history length must be in the range of 0 to 24!")
1380 m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
1381 ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
1382 msgs.append("Password history length changed!")
1384 if min_pwd_length is not None:
1385 if min_pwd_length == "default":
1388 min_pwd_len = int(min_pwd_length)
1390 if min_pwd_len < 0 or min_pwd_len > 14:
1391 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1393 m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
1394 ldb.FLAG_MOD_REPLACE, "minPwdLength")
1395 msgs.append("Minimum password length changed!")
1397 if min_pwd_age is not None:
1398 if min_pwd_age == "default":
1401 min_pwd_age = int(min_pwd_age)
1403 if min_pwd_age < 0 or min_pwd_age > 998:
1404 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1407 min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
1409 m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
1410 ldb.FLAG_MOD_REPLACE, "minPwdAge")
1411 msgs.append("Minimum password age changed!")
1413 if max_pwd_age is not None:
1414 if max_pwd_age == "default":
1417 max_pwd_age = int(max_pwd_age)
1419 if max_pwd_age < 0 or max_pwd_age > 999:
1420 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1423 if max_pwd_age == 0:
1424 max_pwd_age_ticks = -0x8000000000000000
1426 max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
1428 m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
1429 ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1430 msgs.append("Maximum password age changed!")
1432 if account_lockout_duration is not None:
1433 if account_lockout_duration == "default":
1434 account_lockout_duration = 30
1436 account_lockout_duration = int(account_lockout_duration)
1438 if account_lockout_duration < 0 or account_lockout_duration > 99999:
1439 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1442 if account_lockout_duration == 0:
1443 account_lockout_duration_ticks = -0x8000000000000000
1445 account_lockout_duration_ticks = -int(account_lockout_duration * (60 * 1e7))
1447 m["lockoutDuration"] = ldb.MessageElement(str(account_lockout_duration_ticks),
1448 ldb.FLAG_MOD_REPLACE, "lockoutDuration")
1449 msgs.append("Account lockout duration changed!")
1451 if account_lockout_threshold is not None:
1452 if account_lockout_threshold == "default":
1453 account_lockout_threshold = 0
1455 account_lockout_threshold = int(account_lockout_threshold)
1457 m["lockoutThreshold"] = ldb.MessageElement(str(account_lockout_threshold),
1458 ldb.FLAG_MOD_REPLACE, "lockoutThreshold")
1459 msgs.append("Account lockout threshold changed!")
1461 if reset_account_lockout_after is not None:
1462 if reset_account_lockout_after == "default":
1463 reset_account_lockout_after = 30
1465 reset_account_lockout_after = int(reset_account_lockout_after)
1467 if reset_account_lockout_after < 0 or reset_account_lockout_after > 99999:
1468 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1471 if reset_account_lockout_after == 0:
1472 reset_account_lockout_after_ticks = -0x8000000000000000
1474 reset_account_lockout_after_ticks = -int(reset_account_lockout_after * (60 * 1e7))
1476 m["lockOutObservationWindow"] = ldb.MessageElement(str(reset_account_lockout_after_ticks),
1477 ldb.FLAG_MOD_REPLACE, "lockOutObservationWindow")
1478 msgs.append("Duration to reset account lockout after changed!")
1480 if max_pwd_age > 0 and min_pwd_age >= max_pwd_age:
1481 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
1484 raise CommandError("You must specify at least one option to set. Try --help")
1486 msgs.append("All changes applied successfully!")
1487 self.message("\n".join(msgs))
1489 raise CommandError("Wrong argument '%s'!" % subcommand)
1492 class cmd_domain_classicupgrade(Command):
1493 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1495 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1496 the testparm utility from your classic installation (with --testparm).
1499 synopsis = "%prog [options] <classic_smb_conf>"
1501 takes_optiongroups = {
1502 "sambaopts": options.SambaOptions,
1503 "versionopts": options.VersionOptions
1507 Option("--dbdir", type="string", metavar="DIR",
1508 help="Path to samba classic DC database directory"),
1509 Option("--testparm", type="string", metavar="PATH",
1510 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1511 Option("--targetdir", type="string", metavar="DIR",
1512 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1513 Option("--quiet", help="Be quiet", action="store_true"),
1514 Option("--verbose", help="Be verbose", action="store_true"),
1515 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
1516 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1517 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1518 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1519 "BIND9_DLZ uses samba4 AD to store zone information, "
1520 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1521 default="SAMBA_INTERNAL")
1525 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
1526 action="store_true"),
1527 Option("--use-xattrs", type="choice", choices=["yes","no","auto"],
1528 metavar="[yes|no|auto]",
1529 help="Define if we should use the native fs capabilities or a tdb file for "
1530 "storing attributes likes ntacl when --use-ntvfs is set. "
1531 "auto tries to make an inteligent guess based on the user rights and system capabilities",
1534 if samba.is_ntvfs_fileserver_built():
1535 takes_options.extend(ntvfs_options)
1537 takes_args = ["smbconf"]
1539 def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
1540 quiet=False, verbose=False, use_xattrs="auto", sambaopts=None, versionopts=None,
1541 dns_backend=None, use_ntvfs=False):
1543 if not os.path.exists(smbconf):
1544 raise CommandError("File %s does not exist" % smbconf)
1546 if testparm and not os.path.exists(testparm):
1547 raise CommandError("Testparm utility %s does not exist" % testparm)
1549 if dbdir and not os.path.exists(dbdir):
1550 raise CommandError("Directory %s does not exist" % dbdir)
1552 if not dbdir and not testparm:
1553 raise CommandError("Please specify either dbdir or testparm")
1555 logger = self.get_logger()
1557 logger.setLevel(logging.DEBUG)
1559 logger.setLevel(logging.WARNING)
1561 logger.setLevel(logging.INFO)
1563 if dbdir and testparm:
1564 logger.warning("both dbdir and testparm specified, ignoring dbdir.")
1567 lp = sambaopts.get_loadparm()
1569 s3conf = s3param.get_context()
1572 s3conf.set("realm", sambaopts.realm)
1574 if targetdir is not None:
1575 if not os.path.isdir(targetdir):
1579 if use_xattrs == "yes":
1581 elif use_xattrs == "auto" and use_ntvfs == False:
1583 elif use_ntvfs == False:
1584 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
1585 "Please re-run with --use-xattrs omitted.")
1586 elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
1588 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
1590 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
1593 samba.ntacls.setntacl(lp, tmpfile.name,
1594 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1597 # FIXME: Don't catch all exceptions here
1598 logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. "
1599 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1603 # Set correct default values from dbdir or testparm
1606 paths["state directory"] = dbdir
1607 paths["private dir"] = dbdir
1608 paths["lock directory"] = dbdir
1609 paths["smb passwd file"] = dbdir + "/smbpasswd"
1611 paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
1612 paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
1613 paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
1614 paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
1615 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1616 # "state directory", instead make use of "lock directory"
1617 if len(paths["state directory"]) == 0:
1618 paths["state directory"] = paths["lock directory"]
1621 s3conf.set(p, paths[p])
1623 # load smb.conf parameters
1624 logger.info("Reading smb.conf")
1625 s3conf.load(smbconf)
1626 samba3 = Samba3(smbconf, s3conf)
1628 logger.info("Provisioning")
1629 upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
1630 useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
1633 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
1634 __doc__ = cmd_domain_classicupgrade.__doc__
1636 # This command is present for backwards compatibility only,
1637 # and should not be shown.
1641 class LocalDCCredentialsOptions(options.CredentialsOptions):
1642 def __init__(self, parser):
1643 options.CredentialsOptions.__init__(self, parser, special_name="local-dc")
1645 class DomainTrustCommand(Command):
1646 """List domain trusts."""
1649 Command.__init__(self)
1650 self.local_lp = None
1652 self.local_server = None
1653 self.local_binding_string = None
1654 self.local_creds = None
1656 self.remote_server = None
1657 self.remote_binding_string = None
1658 self.remote_creds = None
1660 def _uint32(self, v):
1661 return ctypes.c_uint32(v).value
1663 def check_runtime_error(self, runtime, val):
1667 err32 = self._uint32(runtime[0])
1673 class LocalRuntimeError(CommandError):
1674 def __init__(exception_self, self, runtime, message):
1675 err32 = self._uint32(runtime[0])
1677 msg = "LOCAL_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1678 self.local_server, message, err32, errstr)
1679 CommandError.__init__(exception_self, msg)
1681 class RemoteRuntimeError(CommandError):
1682 def __init__(exception_self, self, runtime, message):
1683 err32 = self._uint32(runtime[0])
1685 msg = "REMOTE_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1686 self.remote_server, message, err32, errstr)
1687 CommandError.__init__(exception_self, msg)
1689 class LocalLdbError(CommandError):
1690 def __init__(exception_self, self, ldb_error, message):
1691 errval = ldb_error[0]
1692 errstr = ldb_error[1]
1693 msg = "LOCAL_DC[%s]: %s - ERROR(%d) - %s" % (
1694 self.local_server, message, errval, errstr)
1695 CommandError.__init__(exception_self, msg)
1697 def setup_local_server(self, sambaopts, localdcopts):
1698 if self.local_server is not None:
1699 return self.local_server
1701 lp = sambaopts.get_loadparm()
1703 local_server = localdcopts.ipaddress
1704 if local_server is None:
1705 server_role = lp.server_role()
1706 if server_role != "ROLE_ACTIVE_DIRECTORY_DC":
1707 raise CommandError("Invalid server_role %s" % (server_role))
1708 local_server = lp.get('netbios name')
1709 local_transport = "ncalrpc"
1710 local_binding_options = ""
1711 local_binding_options += ",auth_type=ncalrpc_as_system"
1712 local_ldap_url = None
1715 local_transport = "ncacn_np"
1716 local_binding_options = ""
1717 local_ldap_url = "ldap://%s" % local_server
1718 local_creds = localdcopts.get_credentials(lp)
1722 self.local_server = local_server
1723 self.local_binding_string = "%s:%s[%s]" % (local_transport, local_server, local_binding_options)
1724 self.local_ldap_url = local_ldap_url
1725 self.local_creds = local_creds
1726 return self.local_server
1728 def new_local_lsa_connection(self):
1729 return lsa.lsarpc(self.local_binding_string, self.local_lp, self.local_creds)
1731 def new_local_netlogon_connection(self):
1732 return netlogon.netlogon(self.local_binding_string, self.local_lp, self.local_creds)
1734 def new_local_ldap_connection(self):
1735 return SamDB(url=self.local_ldap_url,
1736 session_info=system_session(),
1737 credentials=self.local_creds,
1740 def setup_remote_server(self, credopts, domain,
1742 require_writable=True):
1745 assert require_writable
1747 if self.remote_server is not None:
1748 return self.remote_server
1750 self.remote_server = "__unknown__remote_server__.%s" % domain
1751 assert self.local_server is not None
1753 remote_creds = credopts.get_credentials(self.local_lp)
1754 remote_server = credopts.ipaddress
1755 remote_binding_options = ""
1757 # TODO: we should also support NT4 domains
1758 # we could use local_netlogon.netr_DsRGetDCNameEx2() with the remote domain name
1759 # and delegate NBT or CLDAP to the local netlogon server
1761 remote_net = Net(remote_creds, self.local_lp, server=remote_server)
1762 remote_flags = nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS
1763 if require_writable:
1764 remote_flags |= nbt.NBT_SERVER_WRITABLE
1766 remote_flags |= nbt.NBT_SERVER_PDC
1767 remote_info = remote_net.finddc(flags=remote_flags, domain=domain, address=remote_server)
1769 raise CommandError("Failed to find a writeable DC for domain '%s'" % domain)
1771 nbt.NBT_SERVER_PDC: "PDC",
1772 nbt.NBT_SERVER_GC: "GC",
1773 nbt.NBT_SERVER_LDAP: "LDAP",
1774 nbt.NBT_SERVER_DS: "DS",
1775 nbt.NBT_SERVER_KDC: "KDC",
1776 nbt.NBT_SERVER_TIMESERV: "TIMESERV",
1777 nbt.NBT_SERVER_CLOSEST: "CLOSEST",
1778 nbt.NBT_SERVER_WRITABLE: "WRITABLE",
1779 nbt.NBT_SERVER_GOOD_TIMESERV: "GOOD_TIMESERV",
1780 nbt.NBT_SERVER_NDNC: "NDNC",
1781 nbt.NBT_SERVER_SELECT_SECRET_DOMAIN_6: "SELECT_SECRET_DOMAIN_6",
1782 nbt.NBT_SERVER_FULL_SECRET_DOMAIN_6: "FULL_SECRET_DOMAIN_6",
1783 nbt.NBT_SERVER_ADS_WEB_SERVICE: "ADS_WEB_SERVICE",
1784 nbt.NBT_SERVER_DS_8: "DS_8",
1785 nbt.NBT_SERVER_HAS_DNS_NAME: "HAS_DNS_NAME",
1786 nbt.NBT_SERVER_IS_DEFAULT_NC: "IS_DEFAULT_NC",
1787 nbt.NBT_SERVER_FOREST_ROOT: "FOREST_ROOT",
1789 server_type_string = self.generic_bitmap_to_string(flag_map,
1790 remote_info.server_type, names_only=True)
1791 self.outf.write("RemoteDC Netbios[%s] DNS[%s] ServerType[%s]\n" % (
1792 remote_info.pdc_name,
1793 remote_info.pdc_dns_name,
1794 server_type_string))
1796 self.remote_server = remote_info.pdc_dns_name
1797 self.remote_binding_string="ncacn_np:%s[%s]" % (self.remote_server, remote_binding_options)
1798 self.remote_creds = remote_creds
1799 return self.remote_server
1801 def new_remote_lsa_connection(self):
1802 return lsa.lsarpc(self.remote_binding_string, self.local_lp, self.remote_creds)
1804 def new_remote_netlogon_connection(self):
1805 return netlogon.netlogon(self.remote_binding_string, self.local_lp, self.remote_creds)
1807 def get_lsa_info(self, conn, policy_access):
1808 objectAttr = lsa.ObjectAttribute()
1809 objectAttr.sec_qos = lsa.QosInfo()
1811 policy = conn.OpenPolicy2(''.decode('utf-8'),
1812 objectAttr, policy_access)
1814 info = conn.QueryInfoPolicy2(policy, lsa.LSA_POLICY_INFO_DNS)
1816 return (policy, info)
1818 def get_netlogon_dc_info(self, conn, server):
1819 info = conn.netr_DsRGetDCNameEx2(server,
1820 None, 0, None, None, None,
1821 netlogon.DS_RETURN_DNS_NAME)
1824 def netr_DomainTrust_to_name(self, t):
1825 if t.trust_type == lsa.LSA_TRUST_TYPE_DOWNLEVEL:
1826 return t.netbios_name
1830 def netr_DomainTrust_to_type(self, a, t):
1832 primary_parent = None
1834 if _t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
1836 if not _t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1837 primary_parent = a[_t.parent_index]
1840 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1841 if t is primary_parent:
1844 if t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1847 parent = a[t.parent_index]
1848 if parent is primary:
1853 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1858 def netr_DomainTrust_to_transitive(self, t):
1859 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1862 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE:
1865 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1870 def netr_DomainTrust_to_direction(self, t):
1871 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND and \
1872 t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1875 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND:
1878 if t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1883 def generic_enum_to_string(self, e_dict, v, names_only=False):
1887 v32 = self._uint32(v)
1888 w = "__unknown__%08X__" % v32
1890 r = "0x%x (%s)" % (v, w)
1893 def generic_bitmap_to_string(self, b_dict, v, names_only=False):
1898 for b in sorted(b_dict.keys()):
1905 c32 = self._uint32(c)
1906 s += ["__unknown_%08X__" % c32]
1911 r = "0x%x (%s)" % (v, w)
1914 def trustType_string(self, v):
1916 lsa.LSA_TRUST_TYPE_DOWNLEVEL : "DOWNLEVEL",
1917 lsa.LSA_TRUST_TYPE_UPLEVEL : "UPLEVEL",
1918 lsa.LSA_TRUST_TYPE_MIT : "MIT",
1919 lsa.LSA_TRUST_TYPE_DCE : "DCE",
1921 return self.generic_enum_to_string(types, v)
1923 def trustDirection_string(self, v):
1925 lsa.LSA_TRUST_DIRECTION_INBOUND |
1926 lsa.LSA_TRUST_DIRECTION_OUTBOUND : "BOTH",
1927 lsa.LSA_TRUST_DIRECTION_INBOUND : "INBOUND",
1928 lsa.LSA_TRUST_DIRECTION_OUTBOUND : "OUTBOUND",
1930 return self.generic_enum_to_string(directions, v)
1932 def trustAttributes_string(self, v):
1934 lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE : "NON_TRANSITIVE",
1935 lsa.LSA_TRUST_ATTRIBUTE_UPLEVEL_ONLY : "UPLEVEL_ONLY",
1936 lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN : "QUARANTINED_DOMAIN",
1937 lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE : "FOREST_TRANSITIVE",
1938 lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION : "CROSS_ORGANIZATION",
1939 lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST : "WITHIN_FOREST",
1940 lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL : "TREAT_AS_EXTERNAL",
1941 lsa.LSA_TRUST_ATTRIBUTE_USES_RC4_ENCRYPTION : "USES_RC4_ENCRYPTION",
1943 return self.generic_bitmap_to_string(attributes, v)
1945 def kerb_EncTypes_string(self, v):
1947 security.KERB_ENCTYPE_DES_CBC_CRC : "DES_CBC_CRC",
1948 security.KERB_ENCTYPE_DES_CBC_MD5 : "DES_CBC_MD5",
1949 security.KERB_ENCTYPE_RC4_HMAC_MD5 : "RC4_HMAC_MD5",
1950 security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96 : "AES128_CTS_HMAC_SHA1_96",
1951 security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96 : "AES256_CTS_HMAC_SHA1_96",
1952 security.KERB_ENCTYPE_FAST_SUPPORTED : "FAST_SUPPORTED",
1953 security.KERB_ENCTYPE_COMPOUND_IDENTITY_SUPPORTED : "COMPOUND_IDENTITY_SUPPORTED",
1954 security.KERB_ENCTYPE_CLAIMS_SUPPORTED : "CLAIMS_SUPPORTED",
1955 security.KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED : "RESOURCE_SID_COMPRESSION_DISABLED",
1957 return self.generic_bitmap_to_string(enctypes, v)
1959 def entry_tln_status(self, e_flags, ):
1961 return "Status[Enabled]"
1964 lsa.LSA_TLN_DISABLED_NEW : "Disabled-New",
1965 lsa.LSA_TLN_DISABLED_ADMIN : "Disabled",
1966 lsa.LSA_TLN_DISABLED_CONFLICT : "Disabled-Conflicting",
1968 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
1970 def entry_dom_status(self, e_flags):
1972 return "Status[Enabled]"
1975 lsa.LSA_SID_DISABLED_ADMIN : "Disabled-SID",
1976 lsa.LSA_SID_DISABLED_CONFLICT : "Disabled-SID-Conflicting",
1977 lsa.LSA_NB_DISABLED_ADMIN : "Disabled-NB",
1978 lsa.LSA_NB_DISABLED_CONFLICT : "Disabled-NB-Conflicting",
1980 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
1982 def write_forest_trust_info(self, fti, tln=None, collisions=None):
1984 tln_string = " TDO[%s]" % tln
1988 self.outf.write("Namespaces[%d]%s:\n" % (
1989 len(fti.entries), tln_string))
1991 for i in xrange(0, len(fti.entries)):
1995 collision_string = ""
1997 if collisions is not None:
1998 for c in collisions.entries:
2002 collision_string = " Collision[%s]" % (c.name.string)
2004 d = e.forest_trust_data
2005 if e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
2006 self.outf.write("TLN: %-32s DNS[*.%s]%s\n" % (
2007 self.entry_tln_status(flags),
2008 d.string, collision_string))
2009 elif e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
2010 self.outf.write("TLN_EX: %-29s DNS[*.%s]\n" % (
2012 elif e.type == lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
2013 self.outf.write("DOM: %-32s DNS[%s] Netbios[%s] SID[%s]%s\n" % (
2014 self.entry_dom_status(flags),
2015 d.dns_domain_name.string,
2016 d.netbios_domain_name.string,
2017 d.domain_sid, collision_string))
2020 class cmd_domain_trust_list(DomainTrustCommand):
2021 """List domain trusts."""
2023 synopsis = "%prog [options]"
2025 takes_optiongroups = {
2026 "sambaopts": options.SambaOptions,
2027 "versionopts": options.VersionOptions,
2028 "localdcopts": LocalDCCredentialsOptions,
2034 def run(self, sambaopts=None, versionopts=None, localdcopts=None):
2036 local_server = self.setup_local_server(sambaopts, localdcopts)
2038 local_netlogon = self.new_local_netlogon_connection()
2039 except RuntimeError as error:
2040 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2043 local_netlogon_trusts = local_netlogon.netr_DsrEnumerateDomainTrusts(local_server,
2044 netlogon.NETR_TRUST_FLAG_IN_FOREST |
2045 netlogon.NETR_TRUST_FLAG_OUTBOUND |
2046 netlogon.NETR_TRUST_FLAG_INBOUND)
2047 except RuntimeError as error:
2048 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
2049 # TODO: we could implement a fallback to lsa.EnumTrustDom()
2050 raise CommandError("LOCAL_DC[%s]: netr_DsrEnumerateDomainTrusts not supported." % (
2052 raise self.LocalRuntimeError(self, error, "netr_DsrEnumerateDomainTrusts failed")
2054 a = local_netlogon_trusts.array
2056 if t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
2058 self.outf.write("%-14s %-15s %-19s %s\n" % (
2059 "Type[%s]" % self.netr_DomainTrust_to_type(a, t),
2060 "Transitive[%s]" % self.netr_DomainTrust_to_transitive(t),
2061 "Direction[%s]" % self.netr_DomainTrust_to_direction(t),
2062 "Name[%s]" % self.netr_DomainTrust_to_name(t)))
2065 class cmd_domain_trust_show(DomainTrustCommand):
2066 """Show trusted domain details."""
2068 synopsis = "%prog NAME [options]"
2070 takes_optiongroups = {
2071 "sambaopts": options.SambaOptions,
2072 "versionopts": options.VersionOptions,
2073 "localdcopts": LocalDCCredentialsOptions,
2079 takes_args = ["domain"]
2081 def run(self, domain, sambaopts=None, versionopts=None, localdcopts=None):
2083 local_server = self.setup_local_server(sambaopts, localdcopts)
2085 local_lsa = self.new_local_lsa_connection()
2086 except RuntimeError as error:
2087 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2090 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2091 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2092 except RuntimeError as error:
2093 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2095 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2096 local_lsa_info.name.string,
2097 local_lsa_info.dns_domain.string,
2098 local_lsa_info.sid))
2100 lsaString = lsa.String()
2101 lsaString.string = domain
2103 local_tdo_full = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2104 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2105 local_tdo_info = local_tdo_full.info_ex
2106 local_tdo_posix = local_tdo_full.posix_offset
2107 except NTSTATUSError as error:
2108 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2109 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2111 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(FULL_INFO) failed")
2114 local_tdo_enctypes = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2115 lsaString, lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES)
2116 except NTSTATUSError as error:
2117 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_PARAMETER):
2119 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_INFO_CLASS):
2122 if error is not None:
2123 raise self.LocalRuntimeError(self, error,
2124 "QueryTrustedDomainInfoByName(SUPPORTED_ENCRYPTION_TYPES) failed")
2126 local_tdo_enctypes = lsa.TrustDomainInfoSupportedEncTypes()
2127 local_tdo_enctypes.enc_types = 0
2130 local_tdo_forest = None
2131 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2132 local_tdo_forest = local_lsa.lsaRQueryForestTrustInformation(local_policy,
2133 lsaString, lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
2134 except RuntimeError as error:
2135 if self.check_runtime_error(error, ntstatus.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE):
2137 if self.check_runtime_error(error, ntstatus.NT_STATUS_NOT_FOUND):
2139 if error is not None:
2140 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation failed")
2142 local_tdo_forest = lsa.ForestTrustInformation()
2143 local_tdo_forest.count = 0
2144 local_tdo_forest.entries = []
2146 self.outf.write("TrusteDomain:\n\n");
2147 self.outf.write("NetbiosName: %s\n" % local_tdo_info.netbios_name.string)
2148 if local_tdo_info.netbios_name.string != local_tdo_info.domain_name.string:
2149 self.outf.write("DnsName: %s\n" % local_tdo_info.domain_name.string)
2150 self.outf.write("SID: %s\n" % local_tdo_info.sid)
2151 self.outf.write("Type: %s\n" % self.trustType_string(local_tdo_info.trust_type))
2152 self.outf.write("Direction: %s\n" % self.trustDirection_string(local_tdo_info.trust_direction))
2153 self.outf.write("Attributes: %s\n" % self.trustAttributes_string(local_tdo_info.trust_attributes))
2154 posix_offset_u32 = ctypes.c_uint32(local_tdo_posix.posix_offset).value
2155 posix_offset_i32 = ctypes.c_int32(local_tdo_posix.posix_offset).value
2156 self.outf.write("PosixOffset: 0x%08X (%d)\n" % (posix_offset_u32, posix_offset_i32))
2157 self.outf.write("kerb_EncTypes: %s\n" % self.kerb_EncTypes_string(local_tdo_enctypes.enc_types))
2159 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2160 self.write_forest_trust_info(local_tdo_forest,
2161 tln=local_tdo_info.domain_name.string)
2165 class cmd_domain_trust_create(DomainTrustCommand):
2166 """Create a domain or forest trust."""
2168 synopsis = "%prog DOMAIN [options]"
2170 takes_optiongroups = {
2171 "sambaopts": options.SambaOptions,
2172 "versionopts": options.VersionOptions,
2173 "credopts": options.CredentialsOptions,
2174 "localdcopts": LocalDCCredentialsOptions,
2178 Option("--type", type="choice", metavar="TYPE",
2179 choices=["external", "forest"],
2180 help="The type of the trust: 'external' or 'forest'.",
2182 default="external"),
2183 Option("--direction", type="choice", metavar="DIRECTION",
2184 choices=["incoming", "outgoing", "both"],
2185 help="The trust direction: 'incoming', 'outgoing' or 'both'.",
2186 dest='trust_direction',
2188 Option("--create-location", type="choice", metavar="LOCATION",
2189 choices=["local", "both"],
2190 help="Where to create the trusted domain object: 'local' or 'both'.",
2191 dest='create_location',
2193 Option("--cross-organisation", action="store_true",
2194 help="The related domains does not belong to the same organisation.",
2195 dest='cross_organisation',
2197 Option("--quarantined", type="choice", metavar="yes|no",
2198 choices=["yes", "no", None],
2199 help="Special SID filtering rules are applied to the trust. "
2200 "With --type=external the default is yes. "
2201 "With --type=forest the default is no.",
2202 dest='quarantined_arg',
2204 Option("--not-transitive", action="store_true",
2205 help="The forest trust is not transitive.",
2206 dest='not_transitive',
2208 Option("--treat-as-external", action="store_true",
2209 help="The treat the forest trust as external.",
2210 dest='treat_as_external',
2212 Option("--no-aes-keys", action="store_false",
2213 help="The trust uses aes kerberos keys.",
2214 dest='use_aes_keys',
2216 Option("--skip-validation", action="store_false",
2217 help="Skip validation of the trust.",
2222 takes_args = ["domain"]
2224 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2225 trust_type=None, trust_direction=None, create_location=None,
2226 cross_organisation=False, quarantined_arg=None,
2227 not_transitive=False, treat_as_external=False,
2228 use_aes_keys=False, validate=True):
2230 lsaString = lsa.String()
2233 if quarantined_arg is None:
2234 if trust_type == 'external':
2236 elif quarantined_arg == 'yes':
2239 if trust_type != 'forest':
2241 raise CommandError("--not-transitive requires --type=forest")
2242 if treat_as_external:
2243 raise CommandError("--treat-as-external requires --type=forest")
2247 enc_types = lsa.TrustDomainInfoSupportedEncTypes()
2248 enc_types.enc_types = security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96
2249 enc_types.enc_types |= security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96
2251 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2252 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2253 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2255 local_trust_info = lsa.TrustDomainInfoInfoEx()
2256 local_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2257 local_trust_info.trust_direction = 0
2258 if trust_direction == "both":
2259 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2260 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2261 elif trust_direction == "incoming":
2262 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2263 elif trust_direction == "outgoing":
2264 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2265 local_trust_info.trust_attributes = 0
2266 if cross_organisation:
2267 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2269 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2270 if trust_type == "forest":
2271 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2273 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2274 if treat_as_external:
2275 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2277 def get_password(name):
2280 if password is not None and password is not '':
2282 password = getpass("New %s Password: " % name)
2283 passwordverify = getpass("Retype %s Password: " % name)
2284 if not password == passwordverify:
2286 self.outf.write("Sorry, passwords do not match.\n")
2288 incoming_secret = None
2289 outgoing_secret = None
2290 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2291 if create_location == "local":
2292 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2293 incoming_password = get_password("Incoming Trust")
2294 incoming_secret = string_to_byte_array(incoming_password.encode('utf-16-le'))
2295 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2296 outgoing_password = get_password("Outgoing Trust")
2297 outgoing_secret = string_to_byte_array(outgoing_password.encode('utf-16-le'))
2299 remote_trust_info = None
2301 # We use 240 random bytes.
2302 # Windows uses 28 or 240 random bytes. I guess it's
2303 # based on the trust type external vs. forest.
2305 # The initial trust password can be up to 512 bytes
2306 # while the versioned passwords used for periodic updates
2307 # can only be up to 498 bytes, as netr_ServerPasswordSet2()
2308 # needs to pass the NL_PASSWORD_VERSION structure within the
2309 # 512 bytes and a 2 bytes confounder is required.
2311 def random_trust_secret(length):
2312 pw = samba.generate_random_machine_password(length/2, length/2)
2313 return string_to_byte_array(pw.encode('utf-16-le'))
2315 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2316 incoming_secret = random_trust_secret(240)
2317 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2318 outgoing_secret = random_trust_secret(240)
2320 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2321 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2323 remote_trust_info = lsa.TrustDomainInfoInfoEx()
2324 remote_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2325 remote_trust_info.trust_direction = 0
2326 if trust_direction == "both":
2327 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2328 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2329 elif trust_direction == "incoming":
2330 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2331 elif trust_direction == "outgoing":
2332 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2333 remote_trust_info.trust_attributes = 0
2334 if cross_organisation:
2335 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2337 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2338 if trust_type == "forest":
2339 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2341 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2342 if treat_as_external:
2343 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2345 local_server = self.setup_local_server(sambaopts, localdcopts)
2347 local_lsa = self.new_local_lsa_connection()
2348 except RuntimeError as error:
2349 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2352 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2353 except RuntimeError as error:
2354 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2356 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2357 local_lsa_info.name.string,
2358 local_lsa_info.dns_domain.string,
2359 local_lsa_info.sid))
2362 remote_server = self.setup_remote_server(credopts, domain)
2363 except RuntimeError as error:
2364 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2367 remote_lsa = self.new_remote_lsa_connection()
2368 except RuntimeError as error:
2369 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2372 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2373 except RuntimeError as error:
2374 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2376 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2377 remote_lsa_info.name.string,
2378 remote_lsa_info.dns_domain.string,
2379 remote_lsa_info.sid))
2381 local_trust_info.domain_name.string = remote_lsa_info.dns_domain.string
2382 local_trust_info.netbios_name.string = remote_lsa_info.name.string
2383 local_trust_info.sid = remote_lsa_info.sid
2385 if remote_trust_info:
2386 remote_trust_info.domain_name.string = local_lsa_info.dns_domain.string
2387 remote_trust_info.netbios_name.string = local_lsa_info.name.string
2388 remote_trust_info.sid = local_lsa_info.sid
2391 lsaString.string = local_trust_info.domain_name.string
2392 local_old_netbios = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2393 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2394 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2395 except NTSTATUSError as error:
2396 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2397 raise self.LocalRuntimeError(self, error,
2398 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2402 lsaString.string = local_trust_info.netbios_name.string
2403 local_old_dns = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2404 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2405 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2406 except NTSTATUSError as error:
2407 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2408 raise self.LocalRuntimeError(self, error,
2409 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2412 if remote_trust_info:
2414 lsaString.string = remote_trust_info.domain_name.string
2415 remote_old_netbios = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2416 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2417 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2418 except NTSTATUSError as error:
2419 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2420 raise self.RemoteRuntimeError(self, error,
2421 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2425 lsaString.string = remote_trust_info.netbios_name.string
2426 remote_old_dns = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2427 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2428 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2429 except NTSTATUSError as error:
2430 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2431 raise self.RemoteRuntimeError(self, error,
2432 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2436 local_netlogon = self.new_local_netlogon_connection()
2437 except RuntimeError as error:
2438 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2441 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
2442 except RuntimeError as error:
2443 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
2445 if remote_trust_info:
2447 remote_netlogon = self.new_remote_netlogon_connection()
2448 except RuntimeError as error:
2449 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2452 remote_netlogon_info = self.get_netlogon_dc_info(remote_netlogon, remote_server)
2453 except RuntimeError as error:
2454 raise self.RemoteRuntimeError(self, error, "failed to get netlogon dc info")
2456 def generate_AuthInOutBlob(secret, update_time):
2458 blob = drsblobs.trustAuthInOutBlob()
2463 clear = drsblobs.AuthInfoClear()
2464 clear.size = len(secret)
2465 clear.password = secret
2467 info = drsblobs.AuthenticationInformation()
2468 info.LastUpdateTime = samba.unix2nttime(update_time)
2469 info.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
2470 info.AuthInfo = clear
2472 array = drsblobs.AuthenticationInformationArray()
2474 array.array = [info]
2476 blob = drsblobs.trustAuthInOutBlob()
2478 blob.current = array
2482 def generate_AuthInfoInternal(session_key, incoming=None, outgoing=None):
2483 confounder = [0] * 512
2484 for i in range(len(confounder)):
2485 confounder[i] = random.randint(0, 255)
2487 trustpass = drsblobs.trustDomainPasswords()
2489 trustpass.confounder = confounder
2490 trustpass.outgoing = outgoing
2491 trustpass.incoming = incoming
2493 trustpass_blob = ndr_pack(trustpass)
2495 encrypted_trustpass = arcfour_encrypt(session_key, trustpass_blob)
2497 auth_blob = lsa.DATA_BUF2()
2498 auth_blob.size = len(encrypted_trustpass)
2499 auth_blob.data = string_to_byte_array(encrypted_trustpass)
2501 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
2502 auth_info.auth_blob = auth_blob
2506 update_time = samba.current_unix_time()
2507 incoming_blob = generate_AuthInOutBlob(incoming_secret, update_time)
2508 outgoing_blob = generate_AuthInOutBlob(outgoing_secret, update_time)
2510 local_tdo_handle = None
2511 remote_tdo_handle = None
2513 local_auth_info = generate_AuthInfoInternal(local_lsa.session_key,
2514 incoming=incoming_blob,
2515 outgoing=outgoing_blob)
2516 if remote_trust_info:
2517 remote_auth_info = generate_AuthInfoInternal(remote_lsa.session_key,
2518 incoming=outgoing_blob,
2519 outgoing=incoming_blob)
2522 if remote_trust_info:
2523 self.outf.write("Creating remote TDO.\n")
2524 current_request = { "location": "remote", "name": "CreateTrustedDomainEx2"}
2525 remote_tdo_handle = remote_lsa.CreateTrustedDomainEx2(remote_policy,
2528 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2529 self.outf.write("Remote TDO created.\n")
2531 self.outf.write("Setting supported encryption types on remote TDO.\n")
2532 current_request = { "location": "remote", "name": "SetInformationTrustedDomain"}
2533 remote_lsa.SetInformationTrustedDomain(remote_tdo_handle,
2534 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2537 self.outf.write("Creating local TDO.\n")
2538 current_request = { "location": "local", "name": "CreateTrustedDomainEx2"}
2539 local_tdo_handle = local_lsa.CreateTrustedDomainEx2(local_policy,
2542 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2543 self.outf.write("Local TDO created\n")
2545 self.outf.write("Setting supported encryption types on local TDO.\n")
2546 current_request = { "location": "local", "name": "SetInformationTrustedDomain"}
2547 local_lsa.SetInformationTrustedDomain(local_tdo_handle,
2548 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2550 except RuntimeError as error:
2551 self.outf.write("Error: %s failed %sly - cleaning up\n" % (
2552 current_request['name'], current_request['location']))
2553 if remote_tdo_handle:
2554 self.outf.write("Deleting remote TDO.\n")
2555 remote_lsa.DeleteObject(remote_tdo_handle)
2556 remote_tdo_handle = None
2557 if local_tdo_handle:
2558 self.outf.write("Deleting local TDO.\n")
2559 local_lsa.DeleteObject(local_tdo_handle)
2560 local_tdo_handle = None
2561 if current_request['location'] is "remote":
2562 raise self.RemoteRuntimeError(self, error, "%s" % (
2563 current_request['name']))
2564 raise self.LocalRuntimeError(self, error, "%s" % (
2565 current_request['name']))
2568 if local_trust_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2569 self.outf.write("Setup local forest trust information...\n")
2571 # get all information about the remote trust
2572 # this triggers netr_GetForestTrustInformation to the remote domain
2573 # and lsaRSetForestTrustInformation() locally, but new top level
2574 # names are disabled by default.
2575 local_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
2576 remote_lsa_info.dns_domain.string,
2577 netlogon.DS_GFTI_UPDATE_TDO)
2578 except RuntimeError as error:
2579 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2582 # here we try to enable all top level names
2583 local_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
2584 remote_lsa_info.dns_domain,
2585 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2588 except RuntimeError as error:
2589 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2591 self.write_forest_trust_info(local_forest_info,
2592 tln=remote_lsa_info.dns_domain.string,
2593 collisions=local_forest_collision)
2595 if remote_trust_info:
2596 self.outf.write("Setup remote forest trust information...\n")
2598 # get all information about the local trust (from the perspective of the remote domain)
2599 # this triggers netr_GetForestTrustInformation to our domain.
2600 # and lsaRSetForestTrustInformation() remotely, but new top level
2601 # names are disabled by default.
2602 remote_forest_info = remote_netlogon.netr_DsRGetForestTrustInformation(remote_netlogon_info.dc_unc,
2603 local_lsa_info.dns_domain.string,
2604 netlogon.DS_GFTI_UPDATE_TDO)
2605 except RuntimeError as error:
2606 raise self.RemoteRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2609 # here we try to enable all top level names
2610 remote_forest_collision = remote_lsa.lsaRSetForestTrustInformation(remote_policy,
2611 local_lsa_info.dns_domain,
2612 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2615 except RuntimeError as error:
2616 raise self.RemoteRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2618 self.write_forest_trust_info(remote_forest_info,
2619 tln=local_lsa_info.dns_domain.string,
2620 collisions=remote_forest_collision)
2622 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2623 self.outf.write("Validating outgoing trust...\n")
2625 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_netlogon_info.dc_unc,
2626 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2628 remote_lsa_info.dns_domain.string)
2629 except RuntimeError as error:
2630 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2632 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2633 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2635 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2636 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2637 local_trust_verify.trusted_dc_name,
2638 local_trust_verify.tc_connection_status[1],
2639 local_trust_verify.pdc_connection_status[1])
2641 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2642 local_trust_verify.trusted_dc_name,
2643 local_trust_verify.tc_connection_status[1],
2644 local_trust_verify.pdc_connection_status[1])
2646 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2647 raise CommandError(local_validation)
2649 self.outf.write("OK: %s\n" % local_validation)
2651 if remote_trust_info:
2652 if remote_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2653 self.outf.write("Validating incoming trust...\n")
2655 remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_netlogon_info.dc_unc,
2656 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2658 local_lsa_info.dns_domain.string)
2659 except RuntimeError as error:
2660 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2662 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2663 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2665 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2666 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2667 remote_trust_verify.trusted_dc_name,
2668 remote_trust_verify.tc_connection_status[1],
2669 remote_trust_verify.pdc_connection_status[1])
2671 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2672 remote_trust_verify.trusted_dc_name,
2673 remote_trust_verify.tc_connection_status[1],
2674 remote_trust_verify.pdc_connection_status[1])
2676 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
2677 raise CommandError(remote_validation)
2679 self.outf.write("OK: %s\n" % remote_validation)
2681 if remote_tdo_handle is not None:
2683 remote_lsa.Close(remote_tdo_handle)
2684 except RuntimeError as error:
2686 remote_tdo_handle = None
2687 if local_tdo_handle is not None:
2689 local_lsa.Close(local_tdo_handle)
2690 except RuntimeError as error:
2692 local_tdo_handle = None
2694 self.outf.write("Success.\n")
2697 class cmd_domain_trust_delete(DomainTrustCommand):
2698 """Delete a domain trust."""
2700 synopsis = "%prog DOMAIN [options]"
2702 takes_optiongroups = {
2703 "sambaopts": options.SambaOptions,
2704 "versionopts": options.VersionOptions,
2705 "credopts": options.CredentialsOptions,
2706 "localdcopts": LocalDCCredentialsOptions,
2710 Option("--delete-location", type="choice", metavar="LOCATION",
2711 choices=["local", "both"],
2712 help="Where to delete the trusted domain object: 'local' or 'both'.",
2713 dest='delete_location',
2717 takes_args = ["domain"]
2719 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2720 delete_location=None):
2722 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2723 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2724 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2726 if delete_location == "local":
2727 remote_policy_access = None
2729 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2730 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2731 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2733 local_server = self.setup_local_server(sambaopts, localdcopts)
2735 local_lsa = self.new_local_lsa_connection()
2736 except RuntimeError as error:
2737 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2740 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2741 except RuntimeError as error:
2742 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2744 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2745 local_lsa_info.name.string,
2746 local_lsa_info.dns_domain.string,
2747 local_lsa_info.sid))
2749 local_tdo_info = None
2750 local_tdo_handle = None
2751 remote_tdo_info = None
2752 remote_tdo_handle = None
2754 lsaString = lsa.String()
2756 lsaString.string = domain
2757 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2758 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2759 except NTSTATUSError as error:
2760 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2761 raise CommandError("Failed to find trust for domain '%s'" % domain)
2762 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2765 if remote_policy_access is not None:
2767 remote_server = self.setup_remote_server(credopts, domain)
2768 except RuntimeError as error:
2769 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2772 remote_lsa = self.new_remote_lsa_connection()
2773 except RuntimeError as error:
2774 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2777 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2778 except RuntimeError as error:
2779 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2781 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2782 remote_lsa_info.name.string,
2783 remote_lsa_info.dns_domain.string,
2784 remote_lsa_info.sid))
2786 if remote_lsa_info.sid != local_tdo_info.sid or \
2787 remote_lsa_info.name.string != local_tdo_info.netbios_name.string or \
2788 remote_lsa_info.dns_domain.string != local_tdo_info.domain_name.string:
2789 raise CommandError("LocalTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2790 local_tdo_info.netbios_name.string,
2791 local_tdo_info.domain_name.string,
2792 local_tdo_info.sid))
2795 lsaString.string = local_lsa_info.dns_domain.string
2796 remote_tdo_info = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2797 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2798 except NTSTATUSError as error:
2799 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2800 raise self.RemoteRuntimeError(self, error, "QueryTrustedDomainInfoByName(%s)" % (
2804 if remote_tdo_info is not None:
2805 if local_lsa_info.sid != remote_tdo_info.sid or \
2806 local_lsa_info.name.string != remote_tdo_info.netbios_name.string or \
2807 local_lsa_info.dns_domain.string != remote_tdo_info.domain_name.string:
2808 raise CommandError("RemoteTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2809 remote_tdo_info.netbios_name.string,
2810 remote_tdo_info.domain_name.string,
2811 remote_tdo_info.sid))
2813 if local_tdo_info is not None:
2815 lsaString.string = local_tdo_info.domain_name.string
2816 local_tdo_handle = local_lsa.OpenTrustedDomainByName(local_policy,
2818 security.SEC_STD_DELETE)
2819 except RuntimeError as error:
2820 raise self.LocalRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2823 local_lsa.DeleteObject(local_tdo_handle)
2824 local_tdo_handle = None
2826 if remote_tdo_info is not None:
2828 lsaString.string = remote_tdo_info.domain_name.string
2829 remote_tdo_handle = remote_lsa.OpenTrustedDomainByName(remote_policy,
2831 security.SEC_STD_DELETE)
2832 except RuntimeError as error:
2833 raise self.RemoteRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2836 if remote_tdo_handle is not None:
2838 remote_lsa.DeleteObject(remote_tdo_handle)
2839 remote_tdo_handle = None
2840 self.outf.write("RemoteTDO deleted.\n")
2841 except RuntimeError as error:
2842 self.outf.write("%s\n" % self.RemoteRuntimeError(self, error, "DeleteObject() failed"))
2844 if local_tdo_handle is not None:
2846 local_lsa.DeleteObject(local_tdo_handle)
2847 local_tdo_handle = None
2848 self.outf.write("LocalTDO deleted.\n")
2849 except RuntimeError as error:
2850 self.outf.write("%s\n" % self.LocalRuntimeError(self, error, "DeleteObject() failed"))
2854 class cmd_domain_trust_validate(DomainTrustCommand):
2855 """Validate a domain trust."""
2857 synopsis = "%prog DOMAIN [options]"
2859 takes_optiongroups = {
2860 "sambaopts": options.SambaOptions,
2861 "versionopts": options.VersionOptions,
2862 "credopts": options.CredentialsOptions,
2863 "localdcopts": LocalDCCredentialsOptions,
2867 Option("--validate-location", type="choice", metavar="LOCATION",
2868 choices=["local", "both"],
2869 help="Where to validate the trusted domain object: 'local' or 'both'.",
2870 dest='validate_location',
2874 takes_args = ["domain"]
2876 def run(self, domain, sambaopts=None, versionopts=None, credopts=None, localdcopts=None,
2877 validate_location=None):
2879 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2881 local_server = self.setup_local_server(sambaopts, localdcopts)
2883 local_lsa = self.new_local_lsa_connection()
2884 except RuntimeError as error:
2885 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2888 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2889 except RuntimeError as error:
2890 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2892 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2893 local_lsa_info.name.string,
2894 local_lsa_info.dns_domain.string,
2895 local_lsa_info.sid))
2898 lsaString = lsa.String()
2899 lsaString.string = domain
2900 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2901 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2902 except NTSTATUSError as error:
2903 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2904 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2906 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
2908 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
2909 local_tdo_info.netbios_name.string,
2910 local_tdo_info.domain_name.string,
2911 local_tdo_info.sid))
2914 local_netlogon = self.new_local_netlogon_connection()
2915 except RuntimeError as error:
2916 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2919 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_server,
2920 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2922 local_tdo_info.domain_name.string)
2923 except RuntimeError as error:
2924 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2926 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2927 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2929 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2930 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2931 local_trust_verify.trusted_dc_name,
2932 local_trust_verify.tc_connection_status[1],
2933 local_trust_verify.pdc_connection_status[1])
2935 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2936 local_trust_verify.trusted_dc_name,
2937 local_trust_verify.tc_connection_status[1],
2938 local_trust_verify.pdc_connection_status[1])
2940 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2941 raise CommandError(local_validation)
2943 self.outf.write("OK: %s\n" % local_validation)
2946 server = local_trust_verify.trusted_dc_name.replace('\\', '')
2947 domain_and_server = "%s\\%s" % (local_tdo_info.domain_name.string, server)
2948 local_trust_rediscover = local_netlogon.netr_LogonControl2Ex(local_server,
2949 netlogon.NETLOGON_CONTROL_REDISCOVER,
2952 except RuntimeError as error:
2953 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
2955 local_conn_status = self._uint32(local_trust_rediscover.tc_connection_status[0])
2956 local_rediscover = "LocalRediscover: DC[%s] CONNECTION[%s]" % (
2957 local_trust_rediscover.trusted_dc_name,
2958 local_trust_rediscover.tc_connection_status[1])
2960 if local_conn_status != werror.WERR_SUCCESS:
2961 raise CommandError(local_rediscover)
2963 self.outf.write("OK: %s\n" % local_rediscover)
2965 if validate_location != "local":
2967 remote_server = self.setup_remote_server(credopts, domain, require_pdc=False)
2968 except RuntimeError as error:
2969 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2972 remote_netlogon = self.new_remote_netlogon_connection()
2973 except RuntimeError as error:
2974 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2977 remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_server,
2978 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2980 local_lsa_info.dns_domain.string)
2981 except RuntimeError as error:
2982 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2984 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2985 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2987 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2988 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2989 remote_trust_verify.trusted_dc_name,
2990 remote_trust_verify.tc_connection_status[1],
2991 remote_trust_verify.pdc_connection_status[1])
2993 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2994 remote_trust_verify.trusted_dc_name,
2995 remote_trust_verify.tc_connection_status[1],
2996 remote_trust_verify.pdc_connection_status[1])
2998 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
2999 raise CommandError(remote_validation)
3001 self.outf.write("OK: %s\n" % remote_validation)
3004 server = remote_trust_verify.trusted_dc_name.replace('\\', '')
3005 domain_and_server = "%s\\%s" % (local_lsa_info.dns_domain.string, server)
3006 remote_trust_rediscover = remote_netlogon.netr_LogonControl2Ex(remote_server,
3007 netlogon.NETLOGON_CONTROL_REDISCOVER,
3010 except RuntimeError as error:
3011 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
3013 remote_conn_status = self._uint32(remote_trust_rediscover.tc_connection_status[0])
3015 remote_rediscover = "RemoteRediscover: DC[%s] CONNECTION[%s]" % (
3016 remote_trust_rediscover.trusted_dc_name,
3017 remote_trust_rediscover.tc_connection_status[1])
3019 if remote_conn_status != werror.WERR_SUCCESS:
3020 raise CommandError(remote_rediscover)
3022 self.outf.write("OK: %s\n" % remote_rediscover)
3026 class cmd_domain_trust_namespaces(DomainTrustCommand):
3027 """Manage forest trust namespaces."""
3029 synopsis = "%prog [DOMAIN] [options]"
3031 takes_optiongroups = {
3032 "sambaopts": options.SambaOptions,
3033 "versionopts": options.VersionOptions,
3034 "localdcopts": LocalDCCredentialsOptions,
3038 Option("--refresh", type="choice", metavar="check|store",
3039 choices=["check", "store", None],
3040 help="List and maybe store refreshed forest trust information: 'check' or 'store'.",
3043 Option("--enable-all", action="store_true",
3044 help="Try to update disabled entries, not allowed with --refresh=check.",
3047 Option("--enable-tln", action="append", metavar='DNSDOMAIN',
3048 help="Enable a top level name entry. Can be specified multiple times.",
3051 Option("--disable-tln", action="append", metavar='DNSDOMAIN',
3052 help="Disable a top level name entry. Can be specified multiple times.",
3055 Option("--add-tln-ex", action="append", metavar='DNSDOMAIN',
3056 help="Add a top level exclusion entry. Can be specified multiple times.",
3059 Option("--delete-tln-ex", action="append", metavar='DNSDOMAIN',
3060 help="Delete a top level exclusion entry. Can be specified multiple times.",
3061 dest='delete_tln_ex',
3063 Option("--enable-nb", action="append", metavar='NETBIOSDOMAIN',
3064 help="Enable a netbios name in a domain entry. Can be specified multiple times.",
3067 Option("--disable-nb", action="append", metavar='NETBIOSDOMAIN',
3068 help="Disable a netbios name in a domain entry. Can be specified multiple times.",
3071 Option("--enable-sid", action="append", metavar='DOMAINSID',
3072 help="Enable a SID in a domain entry. Can be specified multiple times.",
3073 dest='enable_sid_str',
3075 Option("--disable-sid", action="append", metavar='DOMAINSID',
3076 help="Disable a SID in a domain entry. Can be specified multiple times.",
3077 dest='disable_sid_str',
3079 Option("--add-upn-suffix", action="append", metavar='DNSDOMAIN',
3080 help="Add a new uPNSuffixes attribute for the local forest. Can be specified multiple times.",
3083 Option("--delete-upn-suffix", action="append", metavar='DNSDOMAIN',
3084 help="Delete an existing uPNSuffixes attribute of the local forest. Can be specified multiple times.",
3087 Option("--add-spn-suffix", action="append", metavar='DNSDOMAIN',
3088 help="Add a new msDS-SPNSuffixes attribute for the local forest. Can be specified multiple times.",
3091 Option("--delete-spn-suffix", action="append", metavar='DNSDOMAIN',
3092 help="Delete an existing msDS-SPNSuffixes attribute of the local forest. Can be specified multiple times.",
3097 takes_args = ["domain?"]
3099 def run(self, domain=None, sambaopts=None, localdcopts=None, versionopts=None,
3100 refresh=None, enable_all=False,
3101 enable_tln=[], disable_tln=[], add_tln_ex=[], delete_tln_ex=[],
3102 enable_sid_str=[], disable_sid_str=[], enable_nb=[], disable_nb=[],
3103 add_upn=[], delete_upn=[], add_spn=[], delete_spn=[]):
3105 require_update = False
3108 if refresh == "store":
3109 raise CommandError("--refresh=%s not allowed without DOMAIN" % refresh)
3112 raise CommandError("--enable-all not allowed without DOMAIN")
3114 if len(enable_tln) > 0:
3115 raise CommandError("--enable-tln not allowed without DOMAIN")
3116 if len(disable_tln) > 0:
3117 raise CommandError("--disable-tln not allowed without DOMAIN")
3119 if len(add_tln_ex) > 0:
3120 raise CommandError("--add-tln-ex not allowed without DOMAIN")
3121 if len(delete_tln_ex) > 0:
3122 raise CommandError("--delete-tln-ex not allowed without DOMAIN")
3124 if len(enable_nb) > 0:
3125 raise CommandError("--enable-nb not allowed without DOMAIN")
3126 if len(disable_nb) > 0:
3127 raise CommandError("--disable-nb not allowed without DOMAIN")
3129 if len(enable_sid_str) > 0:
3130 raise CommandError("--enable-sid not allowed without DOMAIN")
3131 if len(disable_sid_str) > 0:
3132 raise CommandError("--disable-sid not allowed without DOMAIN")
3134 if len(add_upn) > 0:
3136 if not n.startswith("*."):
3138 raise CommandError("value[%s] specified for --add-upn-suffix should not include with '*.'" % n)
3139 require_update = True
3140 if len(delete_upn) > 0:
3141 for n in delete_upn:
3142 if not n.startswith("*."):
3144 raise CommandError("value[%s] specified for --delete-upn-suffix should not include with '*.'" % n)
3145 require_update = True
3147 for d in delete_upn:
3148 if a.lower() != d.lower():
3150 raise CommandError("value[%s] specified for --add-upn-suffix and --delete-upn-suffix" % a)
3152 if len(add_spn) > 0:
3154 if not n.startswith("*."):
3156 raise CommandError("value[%s] specified for --add-spn-suffix should not include with '*.'" % n)
3157 require_update = True
3158 if len(delete_spn) > 0:
3159 for n in delete_spn:
3160 if not n.startswith("*."):
3162 raise CommandError("value[%s] specified for --delete-spn-suffix should not include with '*.'" % n)
3163 require_update = True
3165 for d in delete_spn:
3166 if a.lower() != d.lower():
3168 raise CommandError("value[%s] specified for --add-spn-suffix and --delete-spn-suffix" % a)
3170 if len(add_upn) > 0:
3171 raise CommandError("--add-upn-suffix not allowed together with DOMAIN")
3172 if len(delete_upn) > 0:
3173 raise CommandError("--delete-upn-suffix not allowed together with DOMAIN")
3174 if len(add_spn) > 0:
3175 raise CommandError("--add-spn-suffix not allowed together with DOMAIN")
3176 if len(delete_spn) > 0:
3177 raise CommandError("--delete-spn-suffix not allowed together with DOMAIN")
3179 if refresh is not None:
3180 if refresh == "store":
3181 require_update = True
3183 if enable_all and refresh != "store":
3184 raise CommandError("--enable-all not allowed together with --refresh=%s" % refresh)
3186 if len(enable_tln) > 0:
3187 raise CommandError("--enable-tln not allowed together with --refresh")
3188 if len(disable_tln) > 0:
3189 raise CommandError("--disable-tln not allowed together with --refresh")
3191 if len(add_tln_ex) > 0:
3192 raise CommandError("--add-tln-ex not allowed together with --refresh")
3193 if len(delete_tln_ex) > 0:
3194 raise CommandError("--delete-tln-ex not allowed together with --refresh")
3196 if len(enable_nb) > 0:
3197 raise CommandError("--enable-nb not allowed together with --refresh")
3198 if len(disable_nb) > 0:
3199 raise CommandError("--disable-nb not allowed together with --refresh")
3201 if len(enable_sid_str) > 0:
3202 raise CommandError("--enable-sid not allowed together with --refresh")
3203 if len(disable_sid_str) > 0:
3204 raise CommandError("--disable-sid not allowed together with --refresh")
3207 require_update = True
3209 if len(enable_tln) > 0:
3210 raise CommandError("--enable-tln not allowed together with --enable-all")
3212 if len(enable_nb) > 0:
3213 raise CommandError("--enable-nb not allowed together with --enable-all")
3215 if len(enable_sid_str) > 0:
3216 raise CommandError("--enable-sid not allowed together with --enable-all")
3218 if len(enable_tln) > 0:
3219 require_update = True
3220 if len(disable_tln) > 0:
3221 require_update = True
3222 for e in enable_tln:
3223 for d in disable_tln:
3224 if e.lower() != d.lower():
3226 raise CommandError("value[%s] specified for --enable-tln and --disable-tln" % e)
3228 if len(add_tln_ex) > 0:
3229 for n in add_tln_ex:
3230 if not n.startswith("*."):
3232 raise CommandError("value[%s] specified for --add-tln-ex should not include with '*.'" % n)
3233 require_update = True
3234 if len(delete_tln_ex) > 0:
3235 for n in delete_tln_ex:
3236 if not n.startswith("*."):
3238 raise CommandError("value[%s] specified for --delete-tln-ex should not include with '*.'" % n)
3239 require_update = True
3240 for a in add_tln_ex:
3241 for d in delete_tln_ex:
3242 if a.lower() != d.lower():
3244 raise CommandError("value[%s] specified for --add-tln-ex and --delete-tln-ex" % a)
3246 if len(enable_nb) > 0:
3247 require_update = True
3248 if len(disable_nb) > 0:
3249 require_update = True
3251 for d in disable_nb:
3252 if e.upper() != d.upper():
3254 raise CommandError("value[%s] specified for --enable-nb and --disable-nb" % e)
3257 for s in enable_sid_str:
3259 sid = security.dom_sid(s)
3260 except TypeError as error:
3261 raise CommandError("value[%s] specified for --enable-sid is not a valid SID" % s)
3262 enable_sid.append(sid)
3264 for s in disable_sid_str:
3266 sid = security.dom_sid(s)
3267 except TypeError as error:
3268 raise CommandError("value[%s] specified for --disable-sid is not a valid SID" % s)
3269 disable_sid.append(sid)
3270 if len(enable_sid) > 0:
3271 require_update = True
3272 if len(disable_sid) > 0:
3273 require_update = True
3274 for e in enable_sid:
3275 for d in disable_sid:
3278 raise CommandError("value[%s] specified for --enable-sid and --disable-sid" % e)
3280 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
3282 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
3284 local_server = self.setup_local_server(sambaopts, localdcopts)
3286 local_lsa = self.new_local_lsa_connection()
3287 except RuntimeError as error:
3288 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
3291 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
3292 except RuntimeError as error:
3293 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
3295 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
3296 local_lsa_info.name.string,
3297 local_lsa_info.dns_domain.string,
3298 local_lsa_info.sid))
3302 local_netlogon = self.new_local_netlogon_connection()
3303 except RuntimeError as error:
3304 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3307 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3308 except RuntimeError as error:
3309 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3311 if local_netlogon_info.domain_name != local_netlogon_info.forest_name:
3312 raise CommandError("The local domain [%s] is not the forest root [%s]" % (
3313 local_netlogon_info.domain_name,
3314 local_netlogon_info.forest_name))
3317 # get all information about our own forest
3318 own_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3320 except RuntimeError as error:
3321 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
3322 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3325 if self.check_runtime_error(error, werror.WERR_INVALID_FUNCTION):
3326 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3329 if self.check_runtime_error(error, werror.WERR_NERR_ACFNOTLOADED):
3330 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3333 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3335 self.outf.write("Own forest trust information...\n")
3336 self.write_forest_trust_info(own_forest_info,
3337 tln=local_lsa_info.dns_domain.string)
3340 local_samdb = self.new_local_ldap_connection()
3341 except RuntimeError as error:
3342 raise self.LocalRuntimeError(self, error, "failed to connect to SamDB")
3344 local_partitions_dn = "CN=Partitions,%s" % str(local_samdb.get_config_basedn())
3345 attrs = ['uPNSuffixes', 'msDS-SPNSuffixes']
3347 msgs = local_samdb.search(base=local_partitions_dn,
3348 scope=ldb.SCOPE_BASE,
3349 expression="(objectClass=crossRefContainer)",
3351 stored_msg = msgs[0]
3352 except ldb.LdbError as error:
3353 raise self.LocalLdbError(self, error, "failed to search partition dn")
3355 stored_upn_vals = []
3356 if 'uPNSuffixes' in stored_msg:
3357 stored_upn_vals.extend(stored_msg['uPNSuffixes'])
3359 stored_spn_vals = []
3360 if 'msDS-SPNSuffixes' in stored_msg:
3361 stored_spn_vals.extend(stored_msg['msDS-SPNSuffixes'])
3363 self.outf.write("Stored uPNSuffixes attributes[%d]:\n" % len(stored_upn_vals))
3364 for v in stored_upn_vals:
3365 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3366 self.outf.write("Stored msDS-SPNSuffixes attributes[%d]:\n" % len(stored_spn_vals))
3367 for v in stored_spn_vals:
3368 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3370 if not require_update:
3374 update_upn_vals = []
3375 update_upn_vals.extend(stored_upn_vals)
3378 update_spn_vals = []
3379 update_spn_vals.extend(stored_spn_vals)
3383 for i in xrange(0, len(update_upn_vals)):
3384 v = update_upn_vals[i]
3385 if v.lower() != upn.lower():
3390 raise CommandError("Entry already present for value[%s] specified for --add-upn-suffix" % upn)
3391 update_upn_vals.append(upn)
3394 for upn in delete_upn:
3396 for i in xrange(0, len(update_upn_vals)):
3397 v = update_upn_vals[i]
3398 if v.lower() != upn.lower():
3403 raise CommandError("Entry not found for value[%s] specified for --delete-upn-suffix" % upn)
3405 update_upn_vals.pop(idx)
3410 for i in xrange(0, len(update_spn_vals)):
3411 v = update_spn_vals[i]
3412 if v.lower() != spn.lower():
3417 raise CommandError("Entry already present for value[%s] specified for --add-spn-suffix" % spn)
3418 update_spn_vals.append(spn)
3421 for spn in delete_spn:
3423 for i in xrange(0, len(update_spn_vals)):
3424 v = update_spn_vals[i]
3425 if v.lower() != spn.lower():
3430 raise CommandError("Entry not found for value[%s] specified for --delete-spn-suffix" % spn)
3432 update_spn_vals.pop(idx)
3435 self.outf.write("Update uPNSuffixes attributes[%d]:\n" % len(update_upn_vals))
3436 for v in update_upn_vals:
3437 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3438 self.outf.write("Update msDS-SPNSuffixes attributes[%d]:\n" % len(update_spn_vals))
3439 for v in update_spn_vals:
3440 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3442 update_msg = ldb.Message()
3443 update_msg.dn = stored_msg.dn
3446 update_msg['uPNSuffixes'] = ldb.MessageElement(update_upn_vals,
3447 ldb.FLAG_MOD_REPLACE,
3450 update_msg['msDS-SPNSuffixes'] = ldb.MessageElement(update_spn_vals,
3451 ldb.FLAG_MOD_REPLACE,
3454 local_samdb.modify(update_msg)
3455 except ldb.LdbError as error:
3456 raise self.LocalLdbError(self, error, "failed to update partition dn")
3459 stored_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3461 except RuntimeError as error:
3462 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3464 self.outf.write("Stored forest trust information...\n")
3465 self.write_forest_trust_info(stored_forest_info,
3466 tln=local_lsa_info.dns_domain.string)
3470 lsaString = lsa.String()
3471 lsaString.string = domain
3472 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
3473 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
3474 except NTSTATUSError as error:
3475 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
3476 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
3478 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
3480 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
3481 local_tdo_info.netbios_name.string,
3482 local_tdo_info.domain_name.string,
3483 local_tdo_info.sid))
3485 if not local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
3486 raise CommandError("trusted domain object for domain [%s] is not marked as FOREST_TRANSITIVE." % domain)
3488 if refresh is not None:
3490 local_netlogon = self.new_local_netlogon_connection()
3491 except RuntimeError as error:
3492 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3495 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3496 except RuntimeError as error:
3497 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3499 lsa_update_check = 1
3500 if refresh == "store":
3501 netlogon_update_tdo = netlogon.DS_GFTI_UPDATE_TDO
3503 lsa_update_check = 0
3505 netlogon_update_tdo = 0
3508 # get all information about the remote trust
3509 # this triggers netr_GetForestTrustInformation to the remote domain
3510 # and lsaRSetForestTrustInformation() locally, but new top level
3511 # names are disabled by default.
3512 fresh_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3513 local_tdo_info.domain_name.string,
3514 netlogon_update_tdo)
3515 except RuntimeError as error:
3516 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3519 fresh_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3520 local_tdo_info.domain_name,
3521 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3524 except RuntimeError as error:
3525 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3527 self.outf.write("Fresh forest trust information...\n")
3528 self.write_forest_trust_info(fresh_forest_info,
3529 tln=local_tdo_info.domain_name.string,
3530 collisions=fresh_forest_collision)
3532 if refresh == "store":
3534 lsaString = lsa.String()
3535 lsaString.string = local_tdo_info.domain_name.string
3536 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3538 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3539 except RuntimeError as error:
3540 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3542 self.outf.write("Stored forest trust information...\n")
3543 self.write_forest_trust_info(stored_forest_info,
3544 tln=local_tdo_info.domain_name.string)
3549 # The none --refresh path
3553 lsaString = lsa.String()
3554 lsaString.string = local_tdo_info.domain_name.string
3555 local_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3557 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3558 except RuntimeError as error:
3559 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3561 self.outf.write("Local forest trust information...\n")
3562 self.write_forest_trust_info(local_forest_info,
3563 tln=local_tdo_info.domain_name.string)
3565 if not require_update:
3569 entries.extend(local_forest_info.entries)
3570 update_forest_info = lsa.ForestTrustInformation()
3571 update_forest_info.count = len(entries)
3572 update_forest_info.entries = entries
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 update_forest_info.entries[i].flags == 0:
3581 update_forest_info.entries[i].time = 0
3582 update_forest_info.entries[i].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3583 for i in xrange(0, len(update_forest_info.entries)):
3584 r = update_forest_info.entries[i]
3585 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3587 if update_forest_info.entries[i].flags == 0:
3589 update_forest_info.entries[i].time = 0
3590 update_forest_info.entries[i].flags &= ~lsa.LSA_NB_DISABLED_MASK
3591 update_forest_info.entries[i].flags &= ~lsa.LSA_SID_DISABLED_MASK
3593 for tln in enable_tln:
3595 for i in xrange(0, len(update_forest_info.entries)):
3596 r = update_forest_info.entries[i]
3597 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3599 if r.forest_trust_data.string.lower() != tln.lower():
3604 raise CommandError("Entry not found for value[%s] specified for --enable-tln" % tln)
3605 if not update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_MASK:
3606 raise CommandError("Entry found for value[%s] specified for --enable-tln is already enabled" % tln)
3607 update_forest_info.entries[idx].time = 0
3608 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3610 for tln in disable_tln:
3612 for i in xrange(0, len(update_forest_info.entries)):
3613 r = update_forest_info.entries[i]
3614 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3616 if r.forest_trust_data.string.lower() != tln.lower():
3621 raise CommandError("Entry not found for value[%s] specified for --disable-tln" % tln)
3622 if update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_ADMIN:
3623 raise CommandError("Entry found for value[%s] specified for --disable-tln is already disabled" % tln)
3624 update_forest_info.entries[idx].time = 0
3625 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3626 update_forest_info.entries[idx].flags |= lsa.LSA_TLN_DISABLED_ADMIN
3628 for tln_ex in add_tln_ex:
3630 for i in xrange(0, len(update_forest_info.entries)):
3631 r = update_forest_info.entries[i]
3632 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3634 if r.forest_trust_data.string.lower() != tln_ex.lower():
3639 raise CommandError("Entry already present for value[%s] specified for --add-tln-ex" % tln_ex)
3641 tln_dot = ".%s" % tln_ex.lower()
3643 for i in xrange(0, len(update_forest_info.entries)):
3644 r = update_forest_info.entries[i]
3645 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3647 r_dot = ".%s" % r.forest_trust_data.string.lower()
3648 if tln_dot == r_dot:
3649 raise CommandError("TLN entry present for value[%s] specified for --add-tln-ex" % tln_ex)
3650 if not tln_dot.endswith(r_dot):
3656 raise CommandError("No TLN parent present for value[%s] specified for --add-tln-ex" % tln_ex)
3658 r = lsa.ForestTrustRecord()
3659 r.type = lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX
3662 r.forest_trust_data.string = tln_ex
3665 entries.extend(update_forest_info.entries)
3666 entries.insert(idx + 1, r)
3667 update_forest_info.count = len(entries)
3668 update_forest_info.entries = entries
3670 for tln_ex in delete_tln_ex:
3672 for i in xrange(0, len(update_forest_info.entries)):
3673 r = update_forest_info.entries[i]
3674 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3676 if r.forest_trust_data.string.lower() != tln_ex.lower():
3681 raise CommandError("Entry not found for value[%s] specified for --delete-tln-ex" % tln_ex)
3684 entries.extend(update_forest_info.entries)
3686 update_forest_info.count = len(entries)
3687 update_forest_info.entries = entries
3689 for nb in enable_nb:
3691 for i in xrange(0, len(update_forest_info.entries)):
3692 r = update_forest_info.entries[i]
3693 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3695 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3700 raise CommandError("Entry not found for value[%s] specified for --enable-nb" % nb)
3701 if not update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_MASK:
3702 raise CommandError("Entry found for value[%s] specified for --enable-nb is already enabled" % nb)
3703 update_forest_info.entries[idx].time = 0
3704 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3706 for nb in disable_nb:
3708 for i in xrange(0, len(update_forest_info.entries)):
3709 r = update_forest_info.entries[i]
3710 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3712 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3717 raise CommandError("Entry not found for value[%s] specified for --delete-nb" % nb)
3718 if update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_ADMIN:
3719 raise CommandError("Entry found for value[%s] specified for --disable-nb is already disabled" % nb)
3720 update_forest_info.entries[idx].time = 0
3721 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3722 update_forest_info.entries[idx].flags |= lsa.LSA_NB_DISABLED_ADMIN
3724 for sid in enable_sid:
3726 for i in xrange(0, len(update_forest_info.entries)):
3727 r = update_forest_info.entries[i]
3728 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3730 if r.forest_trust_data.domain_sid != sid:
3735 raise CommandError("Entry not found for value[%s] specified for --enable-sid" % sid)
3736 if not update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_MASK:
3737 raise CommandError("Entry found for value[%s] specified for --enable-sid is already enabled" % nb)
3738 update_forest_info.entries[idx].time = 0
3739 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3741 for sid in disable_sid:
3743 for i in xrange(0, len(update_forest_info.entries)):
3744 r = update_forest_info.entries[i]
3745 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3747 if r.forest_trust_data.domain_sid != sid:
3752 raise CommandError("Entry not found for value[%s] specified for --delete-sid" % sid)
3753 if update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_ADMIN:
3754 raise CommandError("Entry found for value[%s] specified for --disable-sid is already disabled" % nb)
3755 update_forest_info.entries[idx].time = 0
3756 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3757 update_forest_info.entries[idx].flags |= lsa.LSA_SID_DISABLED_ADMIN
3760 update_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3761 local_tdo_info.domain_name,
3762 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3763 update_forest_info, 0)
3764 except RuntimeError as error:
3765 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3767 self.outf.write("Updated forest trust information...\n")
3768 self.write_forest_trust_info(update_forest_info,
3769 tln=local_tdo_info.domain_name.string,
3770 collisions=update_forest_collision)
3773 lsaString = lsa.String()
3774 lsaString.string = local_tdo_info.domain_name.string
3775 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3777 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3778 except RuntimeError as error:
3779 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3781 self.outf.write("Stored forest trust information...\n")
3782 self.write_forest_trust_info(stored_forest_info,
3783 tln=local_tdo_info.domain_name.string)
3786 class cmd_domain_tombstones_expunge(Command):
3787 """Expunge tombstones from the database.
3789 This command expunges tombstones from the database."""
3790 synopsis = "%prog NC [NC [...]] [options]"
3793 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3794 metavar="URL", dest="H"),
3795 Option("--current-time",
3796 help="The current time to evaluate the tombstone lifetime from, expressed as YYYY-MM-DD",
3798 Option("--tombstone-lifetime", help="Number of days a tombstone should be preserved for", type=int),
3801 takes_args = ["nc*"]
3803 takes_optiongroups = {
3804 "sambaopts": options.SambaOptions,
3805 "credopts": options.CredentialsOptions,
3806 "versionopts": options.VersionOptions,
3809 def run(self, *ncs, **kwargs):
3810 sambaopts = kwargs.get("sambaopts")
3811 credopts = kwargs.get("credopts")
3812 versionpts = kwargs.get("versionopts")
3814 current_time_string = kwargs.get("current_time")
3815 tombstone_lifetime = kwargs.get("tombstone_lifetime")
3816 lp = sambaopts.get_loadparm()
3817 creds = credopts.get_credentials(lp)
3818 samdb = SamDB(url=H, session_info=system_session(),
3819 credentials=creds, lp=lp)
3821 if current_time_string is not None:
3822 current_time_obj = time.strptime(current_time_string, "%Y-%m-%d")
3823 current_time = long(time.mktime(current_time_obj))
3826 current_time = long(time.time())
3829 res = samdb.search(expression="", base="", scope=ldb.SCOPE_BASE,
3830 attrs=["namingContexts"])
3833 for nc in res[0]["namingContexts"]:
3838 started_transaction = False
3840 samdb.transaction_start()
3841 started_transaction = True
3843 removed_links) = samdb.garbage_collect_tombstones(ncs,
3844 current_time=current_time,
3845 tombstone_lifetime=tombstone_lifetime)
3847 except Exception, err:
3848 if started_transaction:
3849 samdb.transaction_cancel()
3850 raise CommandError("Failed to expunge / garbage collect tombstones", err)
3852 samdb.transaction_commit()
3854 self.outf.write("Removed %d objects and %d links successfully\n"
3855 % (removed_objects, removed_links))
3859 class cmd_domain_trust(SuperCommand):
3860 """Domain and forest trust management."""
3863 subcommands["list"] = cmd_domain_trust_list()
3864 subcommands["show"] = cmd_domain_trust_show()
3865 subcommands["create"] = cmd_domain_trust_create()
3866 subcommands["delete"] = cmd_domain_trust_delete()
3867 subcommands["validate"] = cmd_domain_trust_validate()
3868 subcommands["namespaces"] = cmd_domain_trust_namespaces()
3870 class cmd_domain_tombstones(SuperCommand):
3871 """Domain tombstone and recycled object management."""
3874 subcommands["expunge"] = cmd_domain_tombstones_expunge()
3876 class ldif_schema_update:
3877 """Helper class for applying LDIF schema updates"""
3880 self.is_defunct = False
3881 self.unknown_oid = None
3885 def _ldap_schemaUpdateNow(self, samdb):
3889 add: schemaUpdateNow
3892 samdb.modify_ldif(ldif)
3894 def can_ignore_failure(self, error):
3895 """Checks if we can safely ignore failure to apply an LDIF update"""
3896 (num, errstr) = error.args
3898 # Microsoft has marked objects as defunct that Samba doesn't know about
3899 if num == ldb.ERR_NO_SUCH_OBJECT and self.is_defunct:
3900 print("Defunct object %s doesn't exist, skipping" % self.dn)
3902 elif self.unknown_oid is not None:
3903 print("Skipping unknown OID %s for object %s" %(self.unknown_oid, self.dn))
3908 def apply(self, samdb):
3909 """Applies a single LDIF update to the schema"""
3912 samdb.modify_ldif(self.ldif, controls=['relax:0'])
3913 except ldb.LdbError as e:
3914 if self.can_ignore_failure(e):
3917 print("Exception: %s" % e)
3918 print("Encountered while trying to apply the following LDIF")
3919 print("----------------------------------------------------")
3920 print("%s" % self.ldif)
3924 # REFRESH AFTER EVERY CHANGE
3925 # Otherwise the OID-to-attribute mapping in _apply_updates_in_file()
3926 # won't work, because it can't lookup the new OID in the schema
3927 self._ldap_schemaUpdateNow(samdb)
3931 class cmd_domain_schema_upgrade(Command):
3932 """Domain schema upgrading"""
3934 synopsis = "%prog [options]"
3936 takes_optiongroups = {
3937 "sambaopts": options.SambaOptions,
3938 "versionopts": options.VersionOptions,
3939 "credopts": options.CredentialsOptions,
3943 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3944 metavar="URL", dest="H"),
3945 Option("--quiet", help="Be quiet", action="store_true"),
3946 Option("--verbose", help="Be verbose", action="store_true"),
3947 Option("--schema", type="choice", metavar="SCHEMA",
3948 choices=["2012", "2012_R2"],
3949 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
3951 Option("--ldf-file", type=str, default=None,
3952 help="Just apply the schema updates in the adprep/.LDF file(s) specified"),
3953 Option("--base-dir", type=str, default=None,
3954 help="Location of ldf files Default is ${SETUPDIR}/adprep.")
3957 def _apply_updates_in_file(self, samdb, ldif_file):
3959 Applies a series of updates specified in an .LDIF file. The .LDIF file
3960 is based on the adprep Schema updates provided by Microsoft.
3963 ldif_op = ldif_schema_update()
3965 # parse the file line by line and work out each update operation to apply
3966 for line in ldif_file:
3968 line = line.rstrip()
3970 # the operations in the .LDIF file are separated by blank lines. If
3971 # we hit a blank line, try to apply the update we've parsed so far
3974 # keep going if we haven't parsed anything yet
3975 if ldif_op.ldif == '':
3978 # Apply the individual change
3979 count += ldif_op.apply(samdb)
3981 # start storing the next operation from scratch again
3982 ldif_op = ldif_schema_update()
3985 # replace the placeholder domain name in the .ldif file with the real domain
3986 if line.upper().endswith('DC=X'):
3987 line = line[:-len('DC=X')] + str(samdb.get_default_basedn())
3988 elif line.upper().endswith('CN=X'):
3989 line = line[:-len('CN=X')] + str(samdb.get_default_basedn())
3991 values = line.split(':')
3993 if values[0].lower() == 'dn':
3994 ldif_op.dn = values[1].strip()
3996 # replace the Windows-specific operation with the Samba one
3997 if values[0].lower() == 'changetype':
3998 line = line.lower().replace(': ntdsschemaadd',
4000 line = line.lower().replace(': ntdsschemamodify',
4003 if values[0].lower() in ['rdnattid', 'subclassof',
4004 'systemposssuperiors',
4006 'systemauxiliaryclass']:
4009 # The Microsoft updates contain some OIDs we don't recognize.
4010 # Query the DB to see if we can work out the OID this update is
4011 # referring to. If we find a match, then replace the OID with
4012 # the ldapDisplayname
4014 res = samdb.search(base=samdb.get_schema_basedn(),
4015 expression="(|(attributeId=%s)(governsId=%s))" %
4017 attrs=['ldapDisplayName'])
4020 ldif_op.unknown_oid = value
4022 display_name = res[0]['ldapDisplayName'][0]
4023 line = line.replace(value, ' ' + display_name)
4025 # Microsoft has marked objects as defunct that Samba doesn't know about
4026 if values[0].lower() == 'isdefunct' and values[1].strip().lower() == 'true':
4027 ldif_op.is_defunct = True
4029 # Samba has added the showInAdvancedViewOnly attribute to all objects,
4030 # so rather than doing an add, we need to do a replace
4031 if values[0].lower() == 'add' and values[1].strip().lower() == 'showinadvancedviewonly':
4032 line = 'replace: showInAdvancedViewOnly'
4034 # Add the line to the current LDIF operation (including the newline
4035 # we stripped off at the start of the loop)
4036 ldif_op.ldif += line + '\n'
4041 def _apply_update(self, samdb, update_file, base_dir):
4042 """Wrapper function for parsing an LDIF file and applying the updates"""
4044 print("Applying %s updates..." % update_file)
4048 ldif_file = open(os.path.join(base_dir, update_file))
4050 count = self._apply_updates_in_file(samdb, ldif_file)
4056 print("%u changes applied" % count)
4060 def run(self, **kwargs):
4061 from samba.ms_schema_markdown import read_ms_markdown
4062 from samba.schema import Schema
4064 updates_allowed_overriden = False
4065 sambaopts = kwargs.get("sambaopts")
4066 credopts = kwargs.get("credopts")
4067 versionpts = kwargs.get("versionopts")
4068 lp = sambaopts.get_loadparm()
4069 creds = credopts.get_credentials(lp)
4071 target_schema = kwargs.get("schema")
4072 ldf_files = kwargs.get("ldf_file")
4073 base_dir = kwargs.get("base_dir")
4077 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4079 # we're not going to get far if the config doesn't allow schema updates
4080 if lp.get("dsdb:schema update allowed") is None:
4081 lp.set("dsdb:schema update allowed", "yes")
4082 print("Temporarily overriding 'dsdb:schema update allowed' setting")
4083 updates_allowed_overriden = True
4085 own_dn = ldb.Dn(samdb, samdb.get_dsServiceName())
4086 master = get_fsmo_roleowner(samdb, str(samdb.get_schema_basedn()),
4088 if own_dn != master:
4089 raise CommandError("This server is not the schema master.")
4091 # if specific LDIF files were specified, just apply them
4093 schema_updates = ldf_files.split(",")
4097 # work out the version of the target schema we're upgrading to
4098 end = Schema.get_version(target_schema)
4100 # work out the version of the schema we're currently using
4101 res = samdb.search(base=samdb.get_schema_basedn(),
4102 scope=ldb.SCOPE_BASE, attrs=['objectVersion'])
4105 raise CommandError('Could not determine current schema version')
4106 start = int(res[0]['objectVersion'][0]) + 1
4108 diff_dir = setup_path("adprep/WindowsServerDocs")
4109 if base_dir is None:
4110 # Read from the Schema-Updates.md file
4111 temp_folder = tempfile.mkdtemp()
4113 update_file = setup_path("adprep/WindowsServerDocs/Schema-Updates.md")
4116 read_ms_markdown(update_file, temp_folder)
4117 except Exception as e:
4118 print("Exception in markdown parsing: %s" % e)
4119 shutil.rmtree(temp_folder)
4120 raise CommandError('Failed to upgrade schema')
4122 base_dir = temp_folder
4124 for version in range(start, end + 1):
4125 update = 'Sch%d.ldf' % version
4126 schema_updates.append(update)
4128 # Apply patches if we parsed the Schema-Updates.md file
4129 diff = os.path.abspath(os.path.join(diff_dir, update + '.diff'))
4130 if temp_folder and os.path.exists(diff):
4131 p = subprocess.Popen(['patch', update, '-i', diff],
4132 stdout=subprocess.PIPE,
4133 stderr=subprocess.PIPE, cwd=temp_folder)
4134 stdout, stderr = p.communicate()
4137 print("Exception in patch: %s\n%s" % (stdout, stderr))
4138 shutil.rmtree(temp_folder)
4139 raise CommandError('Failed to upgrade schema')
4141 print("Patched %s using %s" % (update, diff))
4143 if base_dir is None:
4144 base_dir = setup_path("adprep")
4146 samdb.transaction_start()
4148 error_encountered = False
4151 # Apply the schema updates needed to move to the new schema version
4152 for ldif_file in schema_updates:
4153 count += self._apply_update(samdb, ldif_file, base_dir)
4156 samdb.transaction_commit()
4157 print("Schema successfully updated")
4159 print("No changes applied to schema")
4160 samdb.transaction_cancel()
4161 except Exception as e:
4162 print("Exception: %s" % e)
4163 print("Error encountered, aborting schema upgrade")
4164 samdb.transaction_cancel()
4165 error_encountered = True
4167 if updates_allowed_overriden:
4168 lp.set("dsdb:schema update allowed", "no")
4171 shutil.rmtree(temp_folder)
4173 if error_encountered:
4174 raise CommandError('Failed to upgrade schema')
4176 class cmd_domain(SuperCommand):
4177 """Domain management."""
4180 subcommands["demote"] = cmd_domain_demote()
4181 if cmd_domain_export_keytab is not None:
4182 subcommands["exportkeytab"] = cmd_domain_export_keytab()
4183 subcommands["info"] = cmd_domain_info()
4184 subcommands["provision"] = cmd_domain_provision()
4185 subcommands["join"] = cmd_domain_join()
4186 subcommands["dcpromo"] = cmd_domain_dcpromo()
4187 subcommands["level"] = cmd_domain_level()
4188 subcommands["passwordsettings"] = cmd_domain_passwordsettings()
4189 subcommands["classicupgrade"] = cmd_domain_classicupgrade()
4190 subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()
4191 subcommands["trust"] = cmd_domain_trust()
4192 subcommands["tombstones"] = cmd_domain_tombstones()
4193 subcommands["schemaupgrade"] = cmd_domain_schema_upgrade()