3 # Copyright Matthias Dieter Wallnoefer 2009
4 # Copyright Andrew Kroeger 2009
5 # Copyright Jelmer Vernooij 2007-2012
6 # Copyright Giampaolo Lauria 2011
7 # Copyright Matthieu Patou <mat@matws.net> 2011
8 # Copyright Andrew Bartlett 2008-2015
9 # Copyright Stefan Metzmacher 2012
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import samba.getopt as options
37 from samba import ntstatus
38 from samba import NTSTATUSError
39 from samba import werror
40 from getpass import getpass
41 from samba.net import Net, LIBNET_JOIN_AUTOMATIC
43 from samba.join import join_RODC, join_DC, join_subdomain
44 from samba.auth import system_session
45 from samba.samdb import SamDB
46 from samba.ndr import ndr_unpack, ndr_pack, ndr_print
47 from samba.dcerpc import drsuapi
48 from samba.dcerpc import drsblobs
49 from samba.dcerpc import lsa
50 from samba.dcerpc import netlogon
51 from samba.dcerpc import security
52 from samba.dcerpc import nbt
53 from samba.dcerpc import misc
54 from samba.dcerpc.samr import DOMAIN_PASSWORD_COMPLEX, DOMAIN_PASSWORD_STORE_CLEARTEXT
55 from samba.netcmd import (
61 from samba.netcmd.common import netcmd_get_domain_infos_via_cldap
62 from samba.samba3 import Samba3
63 from samba.samba3 import param as s3param
64 from samba.upgrade import upgrade_from_samba3
65 from samba.drs_utils import (
66 sendDsReplicaSync, drsuapi_connect, drsException,
68 from samba import remove_dc, arcfour_encrypt, string_to_byte_array
70 from samba.dsdb import (
71 DS_DOMAIN_FUNCTION_2000,
72 DS_DOMAIN_FUNCTION_2003,
73 DS_DOMAIN_FUNCTION_2003_MIXED,
74 DS_DOMAIN_FUNCTION_2008,
75 DS_DOMAIN_FUNCTION_2008_R2,
76 DS_DOMAIN_FUNCTION_2012,
77 DS_DOMAIN_FUNCTION_2012_R2,
78 DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL,
79 DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL,
80 UF_WORKSTATION_TRUST_ACCOUNT,
81 UF_SERVER_TRUST_ACCOUNT,
82 UF_TRUSTED_FOR_DELEGATION,
83 UF_PARTIAL_SECRETS_ACCOUNT
86 from samba.provision import (
89 DEFAULT_MIN_PWD_LENGTH,
93 from samba.provision.common import (
99 def get_testparm_var(testparm, smbconf, varname):
100 errfile = open(os.devnull, 'w')
101 p = subprocess.Popen([testparm, '-s', '-l',
102 '--parameter-name=%s' % varname, smbconf],
103 stdout=subprocess.PIPE, stderr=errfile)
104 (out,err) = p.communicate()
106 lines = out.split('\n')
108 return lines[0].strip()
112 import samba.dckeytab
114 cmd_domain_export_keytab = None
116 class cmd_domain_export_keytab(Command):
117 """Dump Kerberos keys of the domain into a keytab."""
119 synopsis = "%prog <keytab> [options]"
121 takes_optiongroups = {
122 "sambaopts": options.SambaOptions,
123 "credopts": options.CredentialsOptions,
124 "versionopts": options.VersionOptions,
128 Option("--principal", help="extract only this principal", type=str),
131 takes_args = ["keytab"]
133 def run(self, keytab, credopts=None, sambaopts=None, versionopts=None, principal=None):
134 lp = sambaopts.get_loadparm()
136 net.export_keytab(keytab=keytab, principal=principal)
139 class cmd_domain_info(Command):
140 """Print basic info about a domain and the DC passed as parameter."""
142 synopsis = "%prog <ip_address> [options]"
147 takes_optiongroups = {
148 "sambaopts": options.SambaOptions,
149 "credopts": options.CredentialsOptions,
150 "versionopts": options.VersionOptions,
153 takes_args = ["address"]
155 def run(self, address, credopts=None, sambaopts=None, versionopts=None):
156 lp = sambaopts.get_loadparm()
158 res = netcmd_get_domain_infos_via_cldap(lp, None, address)
160 raise CommandError("Invalid IP address '" + address + "'!")
161 self.outf.write("Forest : %s\n" % res.forest)
162 self.outf.write("Domain : %s\n" % res.dns_domain)
163 self.outf.write("Netbios domain : %s\n" % res.domain_name)
164 self.outf.write("DC name : %s\n" % res.pdc_dns_name)
165 self.outf.write("DC netbios name : %s\n" % res.pdc_name)
166 self.outf.write("Server site : %s\n" % res.server_site)
167 self.outf.write("Client site : %s\n" % res.client_site)
170 class cmd_domain_provision(Command):
171 """Provision a domain."""
173 synopsis = "%prog [options]"
175 takes_optiongroups = {
176 "sambaopts": options.SambaOptions,
177 "versionopts": options.VersionOptions,
181 Option("--interactive", help="Ask for names", action="store_true"),
182 Option("--domain", type="string", metavar="DOMAIN",
183 help="NetBIOS domain name to use"),
184 Option("--domain-guid", type="string", metavar="GUID",
185 help="set domainguid (otherwise random)"),
186 Option("--domain-sid", type="string", metavar="SID",
187 help="set domainsid (otherwise random)"),
188 Option("--ntds-guid", type="string", metavar="GUID",
189 help="set NTDS object GUID (otherwise random)"),
190 Option("--invocationid", type="string", metavar="GUID",
191 help="set invocationid (otherwise random)"),
192 Option("--host-name", type="string", metavar="HOSTNAME",
193 help="set hostname"),
194 Option("--host-ip", type="string", metavar="IPADDRESS",
195 help="set IPv4 ipaddress"),
196 Option("--host-ip6", type="string", metavar="IP6ADDRESS",
197 help="set IPv6 ipaddress"),
198 Option("--site", type="string", metavar="SITENAME",
199 help="set site name"),
200 Option("--adminpass", type="string", metavar="PASSWORD",
201 help="choose admin password (otherwise random)"),
202 Option("--krbtgtpass", type="string", metavar="PASSWORD",
203 help="choose krbtgt password (otherwise random)"),
204 Option("--machinepass", type="string", metavar="PASSWORD",
205 help="choose machine password (otherwise random)"),
206 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
207 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
208 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
209 "BIND9_FLATFILE uses bind9 text database to store zone information, "
210 "BIND9_DLZ uses samba4 AD to store zone information, "
211 "NONE skips the DNS setup entirely (not recommended)",
212 default="SAMBA_INTERNAL"),
213 Option("--dnspass", type="string", metavar="PASSWORD",
214 help="choose dns password (otherwise random)"),
215 Option("--ldapadminpass", type="string", metavar="PASSWORD",
216 help="choose password to set between Samba and its LDAP backend (otherwise random)"),
217 Option("--root", type="string", metavar="USERNAME",
218 help="choose 'root' unix username"),
219 Option("--nobody", type="string", metavar="USERNAME",
220 help="choose 'nobody' user"),
221 Option("--users", type="string", metavar="GROUPNAME",
222 help="choose 'users' group"),
223 Option("--quiet", help="Be quiet", action="store_true"),
224 Option("--blank", action="store_true",
225 help="do not add users or groups, just the structure"),
226 Option("--ldap-backend-type", type="choice", metavar="LDAP-BACKEND-TYPE",
227 help="Test initialisation support for unsupported LDAP backend type (fedora-ds or openldap) DO NOT USE",
228 choices=["fedora-ds", "openldap"]),
229 Option("--server-role", type="choice", metavar="ROLE",
230 choices=["domain controller", "dc", "member server", "member", "standalone"],
231 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
232 default="domain controller"),
233 Option("--function-level", type="choice", metavar="FOR-FUN-LEVEL",
234 choices=["2000", "2003", "2008", "2008_R2"],
235 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2008_R2 Native.",
237 Option("--base-schema", type="choice", metavar="BASE-SCHEMA",
238 choices=["2008_R2", "2008_R2_old", "2012", "2012_R2"],
239 help="The base schema files to use. Default is (Windows) 2008_R2.",
241 Option("--next-rid", type="int", metavar="NEXTRID", default=1000,
242 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
243 Option("--partitions-only",
244 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"),
245 Option("--targetdir", type="string", metavar="DIR",
246 help="Set target directory"),
247 Option("--ol-mmr-urls", type="string", metavar="LDAPSERVER",
248 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\""),
249 Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
250 Option("--plaintext-secrets", action="store_true",
251 help="Store secret/sensitive values as plain text on disk" +
252 "(default is to encrypt secret/ensitive values)"),
256 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",
257 action="store_true"),
258 Option("--slapd-path", type="string", metavar="SLAPD-PATH",
259 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."),
260 Option("--ldap-backend-extra-port", type="int", metavar="LDAP-BACKEND-EXTRA-PORT", help="Additional TCP port for LDAP backend server (to use for replication)"),
261 Option("--ldap-backend-forced-uri", type="string", metavar="LDAP-BACKEND-FORCED-URI",
262 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"),
263 Option("--ldap-backend-nosync", help="Configure LDAP backend not to call fsync() (for performance in test environments)", action="store_true"),
267 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
268 Option("--use-xattrs", type="choice", choices=["yes","no","auto"],
269 metavar="[yes|no|auto]",
270 help="Define if we should use the native fs capabilities or a tdb file for "
271 "storing attributes likes ntacl when --use-ntvfs is set. "
272 "auto tries to make an inteligent guess based on the user rights and system capabilities",
276 if os.getenv('TEST_LDAP', "no") == "yes":
277 takes_options.extend(openldap_options)
279 if samba.is_ntvfs_fileserver_built():
280 takes_options.extend(ntvfs_options)
284 def run(self, sambaopts=None, versionopts=None,
307 ldap_backend_type=None,
311 partitions_only=None,
318 ldap_backend_nosync=None,
319 ldap_backend_extra_port=None,
320 ldap_backend_forced_uri=None,
321 ldap_dryrun_mode=None,
323 plaintext_secrets=False):
325 self.logger = self.get_logger("provision")
327 self.logger.setLevel(logging.WARNING)
329 self.logger.setLevel(logging.INFO)
331 lp = sambaopts.get_loadparm()
332 smbconf = lp.configfile
334 if dns_forwarder is not None:
335 suggested_forwarder = dns_forwarder
337 suggested_forwarder = self._get_nameserver_ip()
338 if suggested_forwarder is None:
339 suggested_forwarder = "none"
341 if len(self.raw_argv) == 1:
345 from getpass import getpass
348 def ask(prompt, default=None):
349 if default is not None:
350 print "%s [%s]: " % (prompt, default),
352 print "%s: " % (prompt,),
353 return sys.stdin.readline().rstrip("\n") or default
356 default = socket.getfqdn().split(".", 1)[1].upper()
359 realm = ask("Realm", default)
360 if realm in (None, ""):
361 raise CommandError("No realm set!")
364 default = realm.split(".")[0]
367 domain = ask("Domain", default)
369 raise CommandError("No domain set!")
371 server_role = ask("Server Role (dc, member, standalone)", "dc")
373 dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
374 if dns_backend in (None, ''):
375 raise CommandError("No DNS backend set!")
377 if dns_backend == "SAMBA_INTERNAL":
378 dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
379 if dns_forwarder.lower() in (None, 'none'):
380 suggested_forwarder = None
384 adminpassplain = getpass("Administrator password: ")
385 issue = self._adminpass_issue(adminpassplain)
387 self.errf.write("%s.\n" % issue)
389 adminpassverify = getpass("Retype password: ")
390 if not adminpassplain == adminpassverify:
391 self.errf.write("Sorry, passwords do not match.\n")
393 adminpass = adminpassplain
397 realm = sambaopts._lp.get('realm')
399 raise CommandError("No realm set!")
401 raise CommandError("No domain set!")
404 issue = self._adminpass_issue(adminpass)
406 raise CommandError(issue)
408 self.logger.info("Administrator password will be set randomly!")
410 if function_level == "2000":
411 dom_for_fun_level = DS_DOMAIN_FUNCTION_2000
412 elif function_level == "2003":
413 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
414 elif function_level == "2008":
415 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
416 elif function_level == "2008_R2":
417 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
419 if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
420 dns_forwarder = suggested_forwarder
422 samdb_fill = FILL_FULL
424 samdb_fill = FILL_NT4SYNC
425 elif partitions_only:
426 samdb_fill = FILL_DRS
428 if targetdir is not None:
429 if not os.path.isdir(targetdir):
434 if use_xattrs == "yes":
436 elif use_xattrs == "auto" and use_ntvfs == False:
438 elif use_ntvfs == False:
439 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
440 "Please re-run with --use-xattrs omitted.")
441 elif use_xattrs == "auto" and not lp.get("posix:eadb"):
443 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
445 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
448 samba.ntacls.setntacl(lp, file.name,
449 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
452 self.logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. ")
457 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.")
458 if ldap_backend_type == "existing":
459 if ldap_backend_forced_uri is not None:
460 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)
462 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")
464 if ldap_backend_forced_uri is not None:
465 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")
467 if domain_sid is not None:
468 domain_sid = security.dom_sid(domain_sid)
470 session = system_session()
472 result = provision(self.logger,
473 session, smbconf=smbconf, targetdir=targetdir,
474 samdb_fill=samdb_fill, realm=realm, domain=domain,
475 domainguid=domain_guid, domainsid=domain_sid,
477 hostip=host_ip, hostip6=host_ip6,
478 sitename=site, ntdsguid=ntds_guid,
479 invocationid=invocationid, adminpass=adminpass,
480 krbtgtpass=krbtgtpass, machinepass=machinepass,
481 dns_backend=dns_backend, dns_forwarder=dns_forwarder,
482 dnspass=dnspass, root=root, nobody=nobody,
484 serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
485 backend_type=ldap_backend_type,
486 ldapadminpass=ldapadminpass, ol_mmr_urls=ol_mmr_urls, slapd_path=slapd_path,
487 useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
488 use_rfc2307=use_rfc2307, skip_sysvolacl=False,
489 ldap_backend_extra_port=ldap_backend_extra_port,
490 ldap_backend_forced_uri=ldap_backend_forced_uri,
491 nosync=ldap_backend_nosync, ldap_dryrun_mode=ldap_dryrun_mode,
492 base_schema=base_schema,
493 plaintext_secrets=plaintext_secrets)
495 except ProvisioningError, e:
496 raise CommandError("Provision failed", e)
498 result.report_logger(self.logger)
500 def _get_nameserver_ip(self):
501 """Grab the nameserver IP address from /etc/resolv.conf."""
503 RESOLV_CONF="/etc/resolv.conf"
505 if not path.isfile(RESOLV_CONF):
506 self.logger.warning("Failed to locate %s" % RESOLV_CONF)
511 handle = open(RESOLV_CONF, 'r')
513 if not line.startswith('nameserver'):
515 # we want the last non-space continuous string of the line
516 return line.strip().split()[-1]
518 if handle is not None:
521 self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
523 def _adminpass_issue(self, adminpass):
524 """Returns error string for a bad administrator password,
525 or None if acceptable"""
527 if len(adminpass.decode('utf-8')) < DEFAULT_MIN_PWD_LENGTH:
528 return "Administrator password does not meet the default minimum" \
529 " password length requirement (%d characters)" \
530 % DEFAULT_MIN_PWD_LENGTH
531 elif not samba.check_password_quality(adminpass):
532 return "Administrator password does not meet the default" \
538 class cmd_domain_dcpromo(Command):
539 """Promote an existing domain member or NT4 PDC to an AD DC."""
541 synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
543 takes_optiongroups = {
544 "sambaopts": options.SambaOptions,
545 "versionopts": options.VersionOptions,
546 "credopts": options.CredentialsOptions,
550 Option("--server", help="DC to join", type=str),
551 Option("--site", help="site to join", type=str),
552 Option("--targetdir", help="where to store provision", type=str),
553 Option("--domain-critical-only",
554 help="only replicate critical domain objects",
555 action="store_true"),
556 Option("--machinepass", type=str, metavar="PASSWORD",
557 help="choose machine password (otherwise random)"),
558 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
559 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
560 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
561 "BIND9_DLZ uses samba4 AD to store zone information, "
562 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
563 default="SAMBA_INTERNAL"),
564 Option("--quiet", help="Be quiet", action="store_true"),
565 Option("--verbose", help="Be verbose", action="store_true")
569 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
572 if samba.is_ntvfs_fileserver_built():
573 takes_options.extend(ntvfs_options)
576 takes_args = ["domain", "role?"]
578 def run(self, domain, role=None, sambaopts=None, credopts=None,
579 versionopts=None, server=None, site=None, targetdir=None,
580 domain_critical_only=False, parent_domain=None, machinepass=None,
581 use_ntvfs=False, dns_backend=None,
582 quiet=False, verbose=False):
583 lp = sambaopts.get_loadparm()
584 creds = credopts.get_credentials(lp)
585 net = Net(creds, lp, server=credopts.ipaddress)
587 logger = self.get_logger()
589 logger.setLevel(logging.DEBUG)
591 logger.setLevel(logging.WARNING)
593 logger.setLevel(logging.INFO)
595 netbios_name = lp.get("netbios name")
601 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
602 site=site, netbios_name=netbios_name, targetdir=targetdir,
603 domain_critical_only=domain_critical_only,
604 machinepass=machinepass, use_ntvfs=use_ntvfs,
605 dns_backend=dns_backend,
606 promote_existing=True)
608 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
609 site=site, netbios_name=netbios_name, targetdir=targetdir,
610 domain_critical_only=domain_critical_only,
611 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
612 promote_existing=True)
614 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
617 class cmd_domain_join(Command):
618 """Join domain as either member or backup domain controller."""
620 synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
622 takes_optiongroups = {
623 "sambaopts": options.SambaOptions,
624 "versionopts": options.VersionOptions,
625 "credopts": options.CredentialsOptions,
629 Option("--server", help="DC to join", type=str),
630 Option("--site", help="site to join", type=str),
631 Option("--targetdir", help="where to store provision", type=str),
632 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
633 Option("--domain-critical-only",
634 help="only replicate critical domain objects",
635 action="store_true"),
636 Option("--machinepass", type=str, metavar="PASSWORD",
637 help="choose machine password (otherwise random)"),
638 Option("--adminpass", type="string", metavar="PASSWORD",
639 help="choose adminstrator password when joining as a subdomain (otherwise random)"),
640 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
641 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
642 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
643 "BIND9_DLZ uses samba4 AD to store zone information, "
644 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
645 default="SAMBA_INTERNAL"),
646 Option("--plaintext-secrets", action="store_true",
647 help="Store secret/sensitive values as plain text on disk" +
648 "(default is to encrypt secret/ensitive values)"),
649 Option("--quiet", help="Be quiet", action="store_true"),
650 Option("--verbose", help="Be verbose", action="store_true")
654 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
657 if samba.is_ntvfs_fileserver_built():
658 takes_options.extend(ntvfs_options)
660 takes_args = ["domain", "role?"]
662 def run(self, domain, role=None, sambaopts=None, credopts=None,
663 versionopts=None, server=None, site=None, targetdir=None,
664 domain_critical_only=False, parent_domain=None, machinepass=None,
665 use_ntvfs=False, dns_backend=None, adminpass=None,
666 quiet=False, verbose=False, plaintext_secrets=False):
667 lp = sambaopts.get_loadparm()
668 creds = credopts.get_credentials(lp)
669 net = Net(creds, lp, server=credopts.ipaddress)
672 site = "Default-First-Site-Name"
674 logger = self.get_logger()
676 logger.setLevel(logging.DEBUG)
678 logger.setLevel(logging.WARNING)
680 logger.setLevel(logging.INFO)
682 netbios_name = lp.get("netbios name")
687 if role is None or role == "MEMBER":
688 (join_password, sid, domain_name) = net.join_member(
689 domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
690 machinepass=machinepass)
692 self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
694 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
695 site=site, netbios_name=netbios_name, targetdir=targetdir,
696 domain_critical_only=domain_critical_only,
697 machinepass=machinepass, use_ntvfs=use_ntvfs,
698 dns_backend=dns_backend,
699 plaintext_secrets=plaintext_secrets)
701 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
702 site=site, netbios_name=netbios_name, targetdir=targetdir,
703 domain_critical_only=domain_critical_only,
704 machinepass=machinepass, use_ntvfs=use_ntvfs,
705 dns_backend=dns_backend,
706 plaintext_secrets=plaintext_secrets)
707 elif role == "SUBDOMAIN":
709 logger.info("Administrator password will be set randomly!")
711 netbios_domain = lp.get("workgroup")
712 if parent_domain is None:
713 parent_domain = ".".join(domain.split(".")[1:])
714 join_subdomain(logger=logger, server=server, creds=creds, lp=lp, dnsdomain=domain,
715 parent_domain=parent_domain, site=site,
716 netbios_name=netbios_name, netbios_domain=netbios_domain,
717 targetdir=targetdir, machinepass=machinepass,
718 use_ntvfs=use_ntvfs, dns_backend=dns_backend,
720 plaintext_secrets=plaintext_secrets)
722 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role)
725 class cmd_domain_demote(Command):
726 """Demote ourselves from the role of Domain Controller."""
728 synopsis = "%prog [options]"
731 Option("--server", help="writable DC to write demotion changes on", type=str),
732 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
733 metavar="URL", dest="H"),
734 Option("--remove-other-dead-server", help="Dead DC (name or NTDS GUID) "
735 "to remove ALL references to (rather than this DC)", type=str),
736 Option("--quiet", help="Be quiet", action="store_true"),
737 Option("--verbose", help="Be verbose", action="store_true"),
740 takes_optiongroups = {
741 "sambaopts": options.SambaOptions,
742 "credopts": options.CredentialsOptions,
743 "versionopts": options.VersionOptions,
746 def run(self, sambaopts=None, credopts=None,
747 versionopts=None, server=None,
748 remove_other_dead_server=None, H=None,
749 verbose=False, quiet=False):
750 lp = sambaopts.get_loadparm()
751 creds = credopts.get_credentials(lp)
752 net = Net(creds, lp, server=credopts.ipaddress)
754 logger = self.get_logger()
756 logger.setLevel(logging.DEBUG)
758 logger.setLevel(logging.WARNING)
760 logger.setLevel(logging.INFO)
762 if remove_other_dead_server is not None:
763 if server is not None:
764 samdb = SamDB(url="ldap://%s" % server,
765 session_info=system_session(),
766 credentials=creds, lp=lp)
768 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
770 remove_dc.remove_dc(samdb, logger, remove_other_dead_server)
771 except remove_dc.DemoteException as err:
772 raise CommandError("Demote failed: %s" % err)
775 netbios_name = lp.get("netbios name")
776 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
778 res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
780 raise CommandError("Unable to search for servers")
783 raise CommandError("You are the latest server in the domain")
787 if str(e["name"]).lower() != netbios_name.lower():
788 server = e["dnsHostName"]
791 ntds_guid = samdb.get_ntds_GUID()
792 msg = samdb.search(base=str(samdb.get_config_basedn()),
793 scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
795 if len(msg) == 0 or "options" not in msg[0]:
796 raise CommandError("Failed to find options on %s" % ntds_guid)
799 dsa_options = int(str(msg[0]['options']))
801 res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
802 controls=["search_options:1:2"])
805 raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res))
807 self.errf.write("Using %s as partner server for the demotion\n" %
809 (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
811 self.errf.write("Deactivating inbound replication\n")
816 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
817 dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
818 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
822 self.errf.write("Asking partner server %s to synchronize from us\n"
824 for part in (samdb.get_schema_basedn(),
825 samdb.get_config_basedn(),
826 samdb.get_root_basedn()):
827 nc = drsuapi.DsReplicaObjectIdentifier()
830 req1 = drsuapi.DsReplicaSyncRequest1()
831 req1.naming_context = nc;
832 req1.options = drsuapi.DRSUAPI_DRS_WRIT_REP
833 req1.source_dsa_guid = misc.GUID(ntds_guid)
836 drsuapiBind.DsReplicaSync(drsuapi_handle, 1, req1)
837 except RuntimeError as (werr, string):
838 if werr == werror.WERR_DS_DRA_NO_REPLICA:
842 "Error while replicating out last local changes from '%s' for demotion, "
843 "re-enabling inbound replication\n" % part)
844 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
845 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
847 raise CommandError("Error while sending a DsReplicaSync for partition '%s'" % str(part), string)
849 remote_samdb = SamDB(url="ldap://%s" % server,
850 session_info=system_session(),
851 credentials=creds, lp=lp)
853 self.errf.write("Changing userControl and container\n")
854 res = remote_samdb.search(base=str(remote_samdb.domain_dn()),
855 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
856 netbios_name.upper(),
857 attrs=["userAccountControl"])
859 uac = int(str(res[0]["userAccountControl"]))
862 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
864 "Error while demoting, re-enabling inbound replication\n")
865 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
866 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
868 raise CommandError("Error while changing account control", e)
871 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
873 "Error while demoting, re-enabling inbound replication")
874 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
875 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
877 raise CommandError("Unable to find object with samaccountName = %s$"
878 " in the remote dc" % netbios_name.upper())
882 uac &= ~(UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION|UF_PARTIAL_SECRETS_ACCOUNT)
883 uac |= UF_WORKSTATION_TRUST_ACCOUNT
888 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
889 ldb.FLAG_MOD_REPLACE,
890 "userAccountControl")
892 remote_samdb.modify(msg)
894 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
896 "Error while demoting, re-enabling inbound replication")
897 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
898 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
901 raise CommandError("Error while changing account control", e)
903 parent = msg.dn.parent()
904 dc_name = res[0].dn.get_rdn_value()
905 rdn = "CN=%s" % dc_name
907 # Let's move to the Computer container
911 computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.domain_dn()))
912 res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
915 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
916 scope=ldb.SCOPE_ONELEVEL)
917 while(len(res) != 0 and i < 100):
919 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
920 scope=ldb.SCOPE_ONELEVEL)
923 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
925 "Error while demoting, re-enabling inbound replication\n")
926 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
927 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
933 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
934 ldb.FLAG_MOD_REPLACE,
935 "userAccountControl")
937 remote_samdb.modify(msg)
939 raise CommandError("Unable to find a slot for renaming %s,"
940 " all names from %s-1 to %s-%d seemed used" %
941 (str(dc_dn), rdn, rdn, i - 9))
943 newrdn = "%s-%d" % (rdn, i)
946 newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
947 remote_samdb.rename(dc_dn, newdn)
949 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
951 "Error while demoting, re-enabling inbound replication\n")
952 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
953 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
959 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
960 ldb.FLAG_MOD_REPLACE,
961 "userAccountControl")
963 remote_samdb.modify(msg)
964 raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
967 server_dsa_dn = samdb.get_serverName()
968 domain = remote_samdb.get_root_basedn()
971 req1 = drsuapi.DsRemoveDSServerRequest1()
972 req1.server_dn = str(server_dsa_dn)
973 req1.domain_dn = str(domain)
976 drsuapiBind.DsRemoveDSServer(drsuapi_handle, 1, req1)
977 except RuntimeError as (werr, string):
978 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
980 "Error while demoting, re-enabling inbound replication\n")
981 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
982 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
988 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
989 ldb.FLAG_MOD_REPLACE,
990 "userAccountControl")
991 remote_samdb.modify(msg)
992 remote_samdb.rename(newdn, dc_dn)
993 if werr == werror.WERR_DS_DRA_NO_REPLICA:
994 raise CommandError("The DC %s is not present on (already removed from) the remote server: " % server_dsa_dn, e)
996 raise CommandError("Error while sending a removeDsServer of %s: " % server_dsa_dn, e)
998 remove_dc.remove_sysvol_references(remote_samdb, logger, dc_name)
1000 # These are objects under the computer account that should be deleted
1001 for s in ("CN=Enterprise,CN=NTFRS Subscriptions",
1002 "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
1003 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
1004 "CN=NTFRS Subscriptions"):
1006 remote_samdb.delete(ldb.Dn(remote_samdb,
1007 "%s,%s" % (s, str(newdn))))
1008 except ldb.LdbError, l:
1011 self.errf.write("Demote successful\n")
1014 class cmd_domain_level(Command):
1015 """Raise domain and forest function levels."""
1017 synopsis = "%prog (show|raise <options>) [options]"
1019 takes_optiongroups = {
1020 "sambaopts": options.SambaOptions,
1021 "credopts": options.CredentialsOptions,
1022 "versionopts": options.VersionOptions,
1026 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1027 metavar="URL", dest="H"),
1028 Option("--quiet", help="Be quiet", action="store_true"),
1029 Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1030 help="The forest function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)"),
1031 Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1032 help="The domain function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)")
1035 takes_args = ["subcommand"]
1037 def run(self, subcommand, H=None, forest_level=None, domain_level=None,
1038 quiet=False, credopts=None, sambaopts=None, versionopts=None):
1039 lp = sambaopts.get_loadparm()
1040 creds = credopts.get_credentials(lp, fallback_machine=True)
1042 samdb = SamDB(url=H, session_info=system_session(),
1043 credentials=creds, lp=lp)
1045 domain_dn = samdb.domain_dn()
1047 res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
1048 scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
1049 assert len(res_forest) == 1
1051 res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1052 attrs=["msDS-Behavior-Version", "nTMixedDomain"])
1053 assert len(res_domain) == 1
1055 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
1056 scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
1057 attrs=["msDS-Behavior-Version"])
1058 assert len(res_dc_s) >= 1
1060 # default values, since "msDS-Behavior-Version" does not exist on Windows 2000 AD
1061 level_forest = DS_DOMAIN_FUNCTION_2000
1062 level_domain = DS_DOMAIN_FUNCTION_2000
1064 if "msDS-Behavior-Version" in res_forest[0]:
1065 level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
1066 if "msDS-Behavior-Version" in res_domain[0]:
1067 level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
1068 level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
1071 for msg in res_dc_s:
1072 if "msDS-Behavior-Version" in msg:
1073 if min_level_dc is None or int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
1074 min_level_dc = int(msg["msDS-Behavior-Version"][0])
1076 min_level_dc = DS_DOMAIN_FUNCTION_2000
1077 # well, this is the least
1080 if level_forest < DS_DOMAIN_FUNCTION_2000 or level_domain < DS_DOMAIN_FUNCTION_2000:
1081 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
1082 if min_level_dc < DS_DOMAIN_FUNCTION_2000:
1083 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
1084 if level_forest > level_domain:
1085 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
1086 if level_domain > min_level_dc:
1087 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
1089 if subcommand == "show":
1090 self.message("Domain and forest function level for domain '%s'" % domain_dn)
1091 if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1092 self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1093 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1094 self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1095 if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1096 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)!")
1100 if level_forest == DS_DOMAIN_FUNCTION_2000:
1102 elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
1103 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1104 elif level_forest == DS_DOMAIN_FUNCTION_2003:
1106 elif level_forest == DS_DOMAIN_FUNCTION_2008:
1108 elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
1110 elif level_forest == DS_DOMAIN_FUNCTION_2012:
1112 elif level_forest == DS_DOMAIN_FUNCTION_2012_R2:
1115 outstr = "higher than 2012 R2"
1116 self.message("Forest function level: (Windows) " + outstr)
1118 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1119 outstr = "2000 mixed (NT4 DC support)"
1120 elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
1122 elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
1123 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1124 elif level_domain == DS_DOMAIN_FUNCTION_2003:
1126 elif level_domain == DS_DOMAIN_FUNCTION_2008:
1128 elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
1130 elif level_domain == DS_DOMAIN_FUNCTION_2012:
1132 elif level_domain == DS_DOMAIN_FUNCTION_2012_R2:
1135 outstr = "higher than 2012 R2"
1136 self.message("Domain function level: (Windows) " + outstr)
1138 if min_level_dc == DS_DOMAIN_FUNCTION_2000:
1140 elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
1142 elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
1144 elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
1146 elif min_level_dc == DS_DOMAIN_FUNCTION_2012:
1148 elif min_level_dc == DS_DOMAIN_FUNCTION_2012_R2:
1151 outstr = "higher than 2012 R2"
1152 self.message("Lowest function level of a DC: (Windows) " + outstr)
1154 elif subcommand == "raise":
1157 if domain_level is not None:
1158 if domain_level == "2003":
1159 new_level_domain = DS_DOMAIN_FUNCTION_2003
1160 elif domain_level == "2008":
1161 new_level_domain = DS_DOMAIN_FUNCTION_2008
1162 elif domain_level == "2008_R2":
1163 new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
1164 elif domain_level == "2012":
1165 new_level_domain = DS_DOMAIN_FUNCTION_2012
1166 elif domain_level == "2012_R2":
1167 new_level_domain = DS_DOMAIN_FUNCTION_2012_R2
1169 if new_level_domain <= level_domain and level_domain_mixed == 0:
1170 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
1171 if new_level_domain > min_level_dc:
1172 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
1174 # Deactivate mixed/interim domain support
1175 if level_domain_mixed != 0:
1176 # Directly on the base DN
1178 m.dn = ldb.Dn(samdb, domain_dn)
1179 m["nTMixedDomain"] = ldb.MessageElement("0",
1180 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1184 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
1185 m["nTMixedDomain"] = ldb.MessageElement("0",
1186 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1189 except ldb.LdbError, (enum, emsg):
1190 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1193 # Directly on the base DN
1195 m.dn = ldb.Dn(samdb, domain_dn)
1196 m["msDS-Behavior-Version"]= ldb.MessageElement(
1197 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1198 "msDS-Behavior-Version")
1202 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
1203 + ",CN=Partitions,%s" % samdb.get_config_basedn())
1204 m["msDS-Behavior-Version"]= ldb.MessageElement(
1205 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1206 "msDS-Behavior-Version")
1209 except ldb.LdbError, (enum, emsg):
1210 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1213 level_domain = new_level_domain
1214 msgs.append("Domain function level changed!")
1216 if forest_level is not None:
1217 if forest_level == "2003":
1218 new_level_forest = DS_DOMAIN_FUNCTION_2003
1219 elif forest_level == "2008":
1220 new_level_forest = DS_DOMAIN_FUNCTION_2008
1221 elif forest_level == "2008_R2":
1222 new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
1223 elif forest_level == "2012":
1224 new_level_forest = DS_DOMAIN_FUNCTION_2012
1225 elif forest_level == "2012_R2":
1226 new_level_forest = DS_DOMAIN_FUNCTION_2012_R2
1228 if new_level_forest <= level_forest:
1229 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1230 if new_level_forest > level_domain:
1231 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1234 m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
1235 m["msDS-Behavior-Version"]= ldb.MessageElement(
1236 str(new_level_forest), ldb.FLAG_MOD_REPLACE,
1237 "msDS-Behavior-Version")
1239 msgs.append("Forest function level changed!")
1240 msgs.append("All changes applied successfully!")
1241 self.message("\n".join(msgs))
1243 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
1246 class cmd_domain_passwordsettings(Command):
1247 """Set password settings.
1249 Password complexity, password lockout policy, history length,
1250 minimum password length, the minimum and maximum password age) on
1251 a Samba AD DC server.
1253 Use against a Windows DC is possible, but group policy will override it.
1256 synopsis = "%prog (show|set <options>) [options]"
1258 takes_optiongroups = {
1259 "sambaopts": options.SambaOptions,
1260 "versionopts": options.VersionOptions,
1261 "credopts": options.CredentialsOptions,
1265 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1266 metavar="URL", dest="H"),
1267 Option("--quiet", help="Be quiet", action="store_true"),
1268 Option("--complexity", type="choice", choices=["on","off","default"],
1269 help="The password complexity (on | off | default). Default is 'on'"),
1270 Option("--store-plaintext", type="choice", choices=["on","off","default"],
1271 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1272 Option("--history-length",
1273 help="The password history length (<integer> | default). Default is 24.", type=str),
1274 Option("--min-pwd-length",
1275 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1276 Option("--min-pwd-age",
1277 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1278 Option("--max-pwd-age",
1279 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1280 Option("--account-lockout-duration",
1281 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),
1282 Option("--account-lockout-threshold",
1283 help="The number of bad password attempts allowed before locking out the account (<integer> | default). Default is 0 (never lock out).", type=str),
1284 Option("--reset-account-lockout-after",
1285 help="After this time is elapsed, the recorded number of attempts restarts from zero (<integer> | default). Default is 30.", type=str),
1288 takes_args = ["subcommand"]
1290 def run(self, subcommand, H=None, min_pwd_age=None, max_pwd_age=None,
1291 quiet=False, complexity=None, store_plaintext=None, history_length=None,
1292 min_pwd_length=None, account_lockout_duration=None, account_lockout_threshold=None,
1293 reset_account_lockout_after=None, credopts=None, sambaopts=None,
1295 lp = sambaopts.get_loadparm()
1296 creds = credopts.get_credentials(lp)
1298 samdb = SamDB(url=H, session_info=system_session(),
1299 credentials=creds, lp=lp)
1301 domain_dn = samdb.domain_dn()
1302 res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1303 attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1304 "minPwdAge", "maxPwdAge", "lockoutDuration", "lockoutThreshold",
1305 "lockOutObservationWindow"])
1306 assert(len(res) == 1)
1308 pwd_props = int(res[0]["pwdProperties"][0])
1309 pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
1310 cur_min_pwd_len = int(res[0]["minPwdLength"][0])
1312 cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1313 if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000:
1316 cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1317 cur_account_lockout_threshold = int(res[0]["lockoutThreshold"][0])
1319 if int(res[0]["lockoutDuration"][0]) == -0x8000000000000000:
1320 cur_account_lockout_duration = 0
1322 cur_account_lockout_duration = abs(int(res[0]["lockoutDuration"][0])) / (1e7 * 60)
1323 cur_reset_account_lockout_after = abs(int(res[0]["lockOutObservationWindow"][0])) / (1e7 * 60)
1324 except Exception, e:
1325 raise CommandError("Could not retrieve password properties!", e)
1327 if subcommand == "show":
1328 self.message("Password informations for domain '%s'" % domain_dn)
1330 if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
1331 self.message("Password complexity: on")
1333 self.message("Password complexity: off")
1334 if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
1335 self.message("Store plaintext passwords: on")
1337 self.message("Store plaintext passwords: off")
1338 self.message("Password history length: %d" % pwd_hist_len)
1339 self.message("Minimum password length: %d" % cur_min_pwd_len)
1340 self.message("Minimum password age (days): %d" % cur_min_pwd_age)
1341 self.message("Maximum password age (days): %d" % cur_max_pwd_age)
1342 self.message("Account lockout duration (mins): %d" % cur_account_lockout_duration)
1343 self.message("Account lockout threshold (attempts): %d" % cur_account_lockout_threshold)
1344 self.message("Reset account lockout after (mins): %d" % cur_reset_account_lockout_after)
1345 elif subcommand == "set":
1348 m.dn = ldb.Dn(samdb, domain_dn)
1350 if complexity is not None:
1351 if complexity == "on" or complexity == "default":
1352 pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
1353 msgs.append("Password complexity activated!")
1354 elif complexity == "off":
1355 pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
1356 msgs.append("Password complexity deactivated!")
1358 if store_plaintext is not None:
1359 if store_plaintext == "on" or store_plaintext == "default":
1360 pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1361 msgs.append("Plaintext password storage for changed passwords activated!")
1362 elif store_plaintext == "off":
1363 pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
1364 msgs.append("Plaintext password storage for changed passwords deactivated!")
1366 if complexity is not None or store_plaintext is not None:
1367 m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
1368 ldb.FLAG_MOD_REPLACE, "pwdProperties")
1370 if history_length is not None:
1371 if history_length == "default":
1374 pwd_hist_len = int(history_length)
1376 if pwd_hist_len < 0 or pwd_hist_len > 24:
1377 raise CommandError("Password history length must be in the range of 0 to 24!")
1379 m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
1380 ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
1381 msgs.append("Password history length changed!")
1383 if min_pwd_length is not None:
1384 if min_pwd_length == "default":
1387 min_pwd_len = int(min_pwd_length)
1389 if min_pwd_len < 0 or min_pwd_len > 14:
1390 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1392 m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
1393 ldb.FLAG_MOD_REPLACE, "minPwdLength")
1394 msgs.append("Minimum password length changed!")
1396 if min_pwd_age is not None:
1397 if min_pwd_age == "default":
1400 min_pwd_age = int(min_pwd_age)
1402 if min_pwd_age < 0 or min_pwd_age > 998:
1403 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1406 min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
1408 m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
1409 ldb.FLAG_MOD_REPLACE, "minPwdAge")
1410 msgs.append("Minimum password age changed!")
1412 if max_pwd_age is not None:
1413 if max_pwd_age == "default":
1416 max_pwd_age = int(max_pwd_age)
1418 if max_pwd_age < 0 or max_pwd_age > 999:
1419 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1422 if max_pwd_age == 0:
1423 max_pwd_age_ticks = -0x8000000000000000
1425 max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
1427 m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
1428 ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1429 msgs.append("Maximum password age changed!")
1431 if account_lockout_duration is not None:
1432 if account_lockout_duration == "default":
1433 account_lockout_duration = 30
1435 account_lockout_duration = int(account_lockout_duration)
1437 if account_lockout_duration < 0 or account_lockout_duration > 99999:
1438 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1441 if account_lockout_duration == 0:
1442 account_lockout_duration_ticks = -0x8000000000000000
1444 account_lockout_duration_ticks = -int(account_lockout_duration * (60 * 1e7))
1446 m["lockoutDuration"] = ldb.MessageElement(str(account_lockout_duration_ticks),
1447 ldb.FLAG_MOD_REPLACE, "lockoutDuration")
1448 msgs.append("Account lockout duration changed!")
1450 if account_lockout_threshold is not None:
1451 if account_lockout_threshold == "default":
1452 account_lockout_threshold = 0
1454 account_lockout_threshold = int(account_lockout_threshold)
1456 m["lockoutThreshold"] = ldb.MessageElement(str(account_lockout_threshold),
1457 ldb.FLAG_MOD_REPLACE, "lockoutThreshold")
1458 msgs.append("Account lockout threshold changed!")
1460 if reset_account_lockout_after is not None:
1461 if reset_account_lockout_after == "default":
1462 reset_account_lockout_after = 30
1464 reset_account_lockout_after = int(reset_account_lockout_after)
1466 if reset_account_lockout_after < 0 or reset_account_lockout_after > 99999:
1467 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1470 if reset_account_lockout_after == 0:
1471 reset_account_lockout_after_ticks = -0x8000000000000000
1473 reset_account_lockout_after_ticks = -int(reset_account_lockout_after * (60 * 1e7))
1475 m["lockOutObservationWindow"] = ldb.MessageElement(str(reset_account_lockout_after_ticks),
1476 ldb.FLAG_MOD_REPLACE, "lockOutObservationWindow")
1477 msgs.append("Duration to reset account lockout after changed!")
1479 if max_pwd_age > 0 and min_pwd_age >= max_pwd_age:
1480 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
1483 raise CommandError("You must specify at least one option to set. Try --help")
1485 msgs.append("All changes applied successfully!")
1486 self.message("\n".join(msgs))
1488 raise CommandError("Wrong argument '%s'!" % subcommand)
1491 class cmd_domain_classicupgrade(Command):
1492 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1494 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1495 the testparm utility from your classic installation (with --testparm).
1498 synopsis = "%prog [options] <classic_smb_conf>"
1500 takes_optiongroups = {
1501 "sambaopts": options.SambaOptions,
1502 "versionopts": options.VersionOptions
1506 Option("--dbdir", type="string", metavar="DIR",
1507 help="Path to samba classic DC database directory"),
1508 Option("--testparm", type="string", metavar="PATH",
1509 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1510 Option("--targetdir", type="string", metavar="DIR",
1511 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1512 Option("--quiet", help="Be quiet", action="store_true"),
1513 Option("--verbose", help="Be verbose", action="store_true"),
1514 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
1515 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1516 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1517 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1518 "BIND9_DLZ uses samba4 AD to store zone information, "
1519 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1520 default="SAMBA_INTERNAL")
1524 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
1525 action="store_true"),
1526 Option("--use-xattrs", type="choice", choices=["yes","no","auto"],
1527 metavar="[yes|no|auto]",
1528 help="Define if we should use the native fs capabilities or a tdb file for "
1529 "storing attributes likes ntacl when --use-ntvfs is set. "
1530 "auto tries to make an inteligent guess based on the user rights and system capabilities",
1533 if samba.is_ntvfs_fileserver_built():
1534 takes_options.extend(ntvfs_options)
1536 takes_args = ["smbconf"]
1538 def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
1539 quiet=False, verbose=False, use_xattrs="auto", sambaopts=None, versionopts=None,
1540 dns_backend=None, use_ntvfs=False):
1542 if not os.path.exists(smbconf):
1543 raise CommandError("File %s does not exist" % smbconf)
1545 if testparm and not os.path.exists(testparm):
1546 raise CommandError("Testparm utility %s does not exist" % testparm)
1548 if dbdir and not os.path.exists(dbdir):
1549 raise CommandError("Directory %s does not exist" % dbdir)
1551 if not dbdir and not testparm:
1552 raise CommandError("Please specify either dbdir or testparm")
1554 logger = self.get_logger()
1556 logger.setLevel(logging.DEBUG)
1558 logger.setLevel(logging.WARNING)
1560 logger.setLevel(logging.INFO)
1562 if dbdir and testparm:
1563 logger.warning("both dbdir and testparm specified, ignoring dbdir.")
1566 lp = sambaopts.get_loadparm()
1568 s3conf = s3param.get_context()
1571 s3conf.set("realm", sambaopts.realm)
1573 if targetdir is not None:
1574 if not os.path.isdir(targetdir):
1578 if use_xattrs == "yes":
1580 elif use_xattrs == "auto" and use_ntvfs == False:
1582 elif use_ntvfs == False:
1583 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
1584 "Please re-run with --use-xattrs omitted.")
1585 elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
1587 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
1589 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
1592 samba.ntacls.setntacl(lp, tmpfile.name,
1593 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1596 # FIXME: Don't catch all exceptions here
1597 logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. "
1598 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1602 # Set correct default values from dbdir or testparm
1605 paths["state directory"] = dbdir
1606 paths["private dir"] = dbdir
1607 paths["lock directory"] = dbdir
1608 paths["smb passwd file"] = dbdir + "/smbpasswd"
1610 paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
1611 paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
1612 paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
1613 paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
1614 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1615 # "state directory", instead make use of "lock directory"
1616 if len(paths["state directory"]) == 0:
1617 paths["state directory"] = paths["lock directory"]
1620 s3conf.set(p, paths[p])
1622 # load smb.conf parameters
1623 logger.info("Reading smb.conf")
1624 s3conf.load(smbconf)
1625 samba3 = Samba3(smbconf, s3conf)
1627 logger.info("Provisioning")
1628 upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
1629 useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
1632 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
1633 __doc__ = cmd_domain_classicupgrade.__doc__
1635 # This command is present for backwards compatibility only,
1636 # and should not be shown.
1640 class LocalDCCredentialsOptions(options.CredentialsOptions):
1641 def __init__(self, parser):
1642 options.CredentialsOptions.__init__(self, parser, special_name="local-dc")
1644 class DomainTrustCommand(Command):
1645 """List domain trusts."""
1648 Command.__init__(self)
1649 self.local_lp = None
1651 self.local_server = None
1652 self.local_binding_string = None
1653 self.local_creds = None
1655 self.remote_server = None
1656 self.remote_binding_string = None
1657 self.remote_creds = None
1659 def _uint32(self, v):
1660 return ctypes.c_uint32(v).value
1662 def check_runtime_error(self, runtime, val):
1666 err32 = self._uint32(runtime[0])
1672 class LocalRuntimeError(CommandError):
1673 def __init__(exception_self, self, runtime, message):
1674 err32 = self._uint32(runtime[0])
1676 msg = "LOCAL_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1677 self.local_server, message, err32, errstr)
1678 CommandError.__init__(exception_self, msg)
1680 class RemoteRuntimeError(CommandError):
1681 def __init__(exception_self, self, runtime, message):
1682 err32 = self._uint32(runtime[0])
1684 msg = "REMOTE_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1685 self.remote_server, message, err32, errstr)
1686 CommandError.__init__(exception_self, msg)
1688 class LocalLdbError(CommandError):
1689 def __init__(exception_self, self, ldb_error, message):
1690 errval = ldb_error[0]
1691 errstr = ldb_error[1]
1692 msg = "LOCAL_DC[%s]: %s - ERROR(%d) - %s" % (
1693 self.local_server, message, errval, errstr)
1694 CommandError.__init__(exception_self, msg)
1696 def setup_local_server(self, sambaopts, localdcopts):
1697 if self.local_server is not None:
1698 return self.local_server
1700 lp = sambaopts.get_loadparm()
1702 local_server = localdcopts.ipaddress
1703 if local_server is None:
1704 server_role = lp.server_role()
1705 if server_role != "ROLE_ACTIVE_DIRECTORY_DC":
1706 raise CommandError("Invalid server_role %s" % (server_role))
1707 local_server = lp.get('netbios name')
1708 local_transport = "ncalrpc"
1709 local_binding_options = ""
1710 local_binding_options += ",auth_type=ncalrpc_as_system"
1711 local_ldap_url = None
1714 local_transport = "ncacn_np"
1715 local_binding_options = ""
1716 local_ldap_url = "ldap://%s" % local_server
1717 local_creds = localdcopts.get_credentials(lp)
1721 self.local_server = local_server
1722 self.local_binding_string = "%s:%s[%s]" % (local_transport, local_server, local_binding_options)
1723 self.local_ldap_url = local_ldap_url
1724 self.local_creds = local_creds
1725 return self.local_server
1727 def new_local_lsa_connection(self):
1728 return lsa.lsarpc(self.local_binding_string, self.local_lp, self.local_creds)
1730 def new_local_netlogon_connection(self):
1731 return netlogon.netlogon(self.local_binding_string, self.local_lp, self.local_creds)
1733 def new_local_ldap_connection(self):
1734 return SamDB(url=self.local_ldap_url,
1735 session_info=system_session(),
1736 credentials=self.local_creds,
1739 def setup_remote_server(self, credopts, domain,
1741 require_writable=True):
1744 assert require_writable
1746 if self.remote_server is not None:
1747 return self.remote_server
1749 self.remote_server = "__unknown__remote_server__.%s" % domain
1750 assert self.local_server is not None
1752 remote_creds = credopts.get_credentials(self.local_lp)
1753 remote_server = credopts.ipaddress
1754 remote_binding_options = ""
1756 # TODO: we should also support NT4 domains
1757 # we could use local_netlogon.netr_DsRGetDCNameEx2() with the remote domain name
1758 # and delegate NBT or CLDAP to the local netlogon server
1760 remote_net = Net(remote_creds, self.local_lp, server=remote_server)
1761 remote_flags = nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS
1762 if require_writable:
1763 remote_flags |= nbt.NBT_SERVER_WRITABLE
1765 remote_flags |= nbt.NBT_SERVER_PDC
1766 remote_info = remote_net.finddc(flags=remote_flags, domain=domain, address=remote_server)
1768 raise CommandError("Failed to find a writeable DC for domain '%s'" % domain)
1770 nbt.NBT_SERVER_PDC: "PDC",
1771 nbt.NBT_SERVER_GC: "GC",
1772 nbt.NBT_SERVER_LDAP: "LDAP",
1773 nbt.NBT_SERVER_DS: "DS",
1774 nbt.NBT_SERVER_KDC: "KDC",
1775 nbt.NBT_SERVER_TIMESERV: "TIMESERV",
1776 nbt.NBT_SERVER_CLOSEST: "CLOSEST",
1777 nbt.NBT_SERVER_WRITABLE: "WRITABLE",
1778 nbt.NBT_SERVER_GOOD_TIMESERV: "GOOD_TIMESERV",
1779 nbt.NBT_SERVER_NDNC: "NDNC",
1780 nbt.NBT_SERVER_SELECT_SECRET_DOMAIN_6: "SELECT_SECRET_DOMAIN_6",
1781 nbt.NBT_SERVER_FULL_SECRET_DOMAIN_6: "FULL_SECRET_DOMAIN_6",
1782 nbt.NBT_SERVER_ADS_WEB_SERVICE: "ADS_WEB_SERVICE",
1783 nbt.NBT_SERVER_DS_8: "DS_8",
1784 nbt.NBT_SERVER_HAS_DNS_NAME: "HAS_DNS_NAME",
1785 nbt.NBT_SERVER_IS_DEFAULT_NC: "IS_DEFAULT_NC",
1786 nbt.NBT_SERVER_FOREST_ROOT: "FOREST_ROOT",
1788 server_type_string = self.generic_bitmap_to_string(flag_map,
1789 remote_info.server_type, names_only=True)
1790 self.outf.write("RemoteDC Netbios[%s] DNS[%s] ServerType[%s]\n" % (
1791 remote_info.pdc_name,
1792 remote_info.pdc_dns_name,
1793 server_type_string))
1795 self.remote_server = remote_info.pdc_dns_name
1796 self.remote_binding_string="ncacn_np:%s[%s]" % (self.remote_server, remote_binding_options)
1797 self.remote_creds = remote_creds
1798 return self.remote_server
1800 def new_remote_lsa_connection(self):
1801 return lsa.lsarpc(self.remote_binding_string, self.local_lp, self.remote_creds)
1803 def new_remote_netlogon_connection(self):
1804 return netlogon.netlogon(self.remote_binding_string, self.local_lp, self.remote_creds)
1806 def get_lsa_info(self, conn, policy_access):
1807 objectAttr = lsa.ObjectAttribute()
1808 objectAttr.sec_qos = lsa.QosInfo()
1810 policy = conn.OpenPolicy2(''.decode('utf-8'),
1811 objectAttr, policy_access)
1813 info = conn.QueryInfoPolicy2(policy, lsa.LSA_POLICY_INFO_DNS)
1815 return (policy, info)
1817 def get_netlogon_dc_info(self, conn, server):
1818 info = conn.netr_DsRGetDCNameEx2(server,
1819 None, 0, None, None, None,
1820 netlogon.DS_RETURN_DNS_NAME)
1823 def netr_DomainTrust_to_name(self, t):
1824 if t.trust_type == lsa.LSA_TRUST_TYPE_DOWNLEVEL:
1825 return t.netbios_name
1829 def netr_DomainTrust_to_type(self, a, t):
1831 primary_parent = None
1833 if _t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
1835 if not _t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1836 primary_parent = a[_t.parent_index]
1839 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1840 if t is primary_parent:
1843 if t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1846 parent = a[t.parent_index]
1847 if parent is primary:
1852 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1857 def netr_DomainTrust_to_transitive(self, t):
1858 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1861 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE:
1864 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1869 def netr_DomainTrust_to_direction(self, t):
1870 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND and \
1871 t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1874 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND:
1877 if t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1882 def generic_enum_to_string(self, e_dict, v, names_only=False):
1886 v32 = self._uint32(v)
1887 w = "__unknown__%08X__" % v32
1889 r = "0x%x (%s)" % (v, w)
1892 def generic_bitmap_to_string(self, b_dict, v, names_only=False):
1897 for b in sorted(b_dict.keys()):
1904 c32 = self._uint32(c)
1905 s += ["__unknown_%08X__" % c32]
1910 r = "0x%x (%s)" % (v, w)
1913 def trustType_string(self, v):
1915 lsa.LSA_TRUST_TYPE_DOWNLEVEL : "DOWNLEVEL",
1916 lsa.LSA_TRUST_TYPE_UPLEVEL : "UPLEVEL",
1917 lsa.LSA_TRUST_TYPE_MIT : "MIT",
1918 lsa.LSA_TRUST_TYPE_DCE : "DCE",
1920 return self.generic_enum_to_string(types, v)
1922 def trustDirection_string(self, v):
1924 lsa.LSA_TRUST_DIRECTION_INBOUND |
1925 lsa.LSA_TRUST_DIRECTION_OUTBOUND : "BOTH",
1926 lsa.LSA_TRUST_DIRECTION_INBOUND : "INBOUND",
1927 lsa.LSA_TRUST_DIRECTION_OUTBOUND : "OUTBOUND",
1929 return self.generic_enum_to_string(directions, v)
1931 def trustAttributes_string(self, v):
1933 lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE : "NON_TRANSITIVE",
1934 lsa.LSA_TRUST_ATTRIBUTE_UPLEVEL_ONLY : "UPLEVEL_ONLY",
1935 lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN : "QUARANTINED_DOMAIN",
1936 lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE : "FOREST_TRANSITIVE",
1937 lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION : "CROSS_ORGANIZATION",
1938 lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST : "WITHIN_FOREST",
1939 lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL : "TREAT_AS_EXTERNAL",
1940 lsa.LSA_TRUST_ATTRIBUTE_USES_RC4_ENCRYPTION : "USES_RC4_ENCRYPTION",
1942 return self.generic_bitmap_to_string(attributes, v)
1944 def kerb_EncTypes_string(self, v):
1946 security.KERB_ENCTYPE_DES_CBC_CRC : "DES_CBC_CRC",
1947 security.KERB_ENCTYPE_DES_CBC_MD5 : "DES_CBC_MD5",
1948 security.KERB_ENCTYPE_RC4_HMAC_MD5 : "RC4_HMAC_MD5",
1949 security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96 : "AES128_CTS_HMAC_SHA1_96",
1950 security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96 : "AES256_CTS_HMAC_SHA1_96",
1951 security.KERB_ENCTYPE_FAST_SUPPORTED : "FAST_SUPPORTED",
1952 security.KERB_ENCTYPE_COMPOUND_IDENTITY_SUPPORTED : "COMPOUND_IDENTITY_SUPPORTED",
1953 security.KERB_ENCTYPE_CLAIMS_SUPPORTED : "CLAIMS_SUPPORTED",
1954 security.KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED : "RESOURCE_SID_COMPRESSION_DISABLED",
1956 return self.generic_bitmap_to_string(enctypes, v)
1958 def entry_tln_status(self, e_flags, ):
1960 return "Status[Enabled]"
1963 lsa.LSA_TLN_DISABLED_NEW : "Disabled-New",
1964 lsa.LSA_TLN_DISABLED_ADMIN : "Disabled",
1965 lsa.LSA_TLN_DISABLED_CONFLICT : "Disabled-Conflicting",
1967 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
1969 def entry_dom_status(self, e_flags):
1971 return "Status[Enabled]"
1974 lsa.LSA_SID_DISABLED_ADMIN : "Disabled-SID",
1975 lsa.LSA_SID_DISABLED_CONFLICT : "Disabled-SID-Conflicting",
1976 lsa.LSA_NB_DISABLED_ADMIN : "Disabled-NB",
1977 lsa.LSA_NB_DISABLED_CONFLICT : "Disabled-NB-Conflicting",
1979 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
1981 def write_forest_trust_info(self, fti, tln=None, collisions=None):
1983 tln_string = " TDO[%s]" % tln
1987 self.outf.write("Namespaces[%d]%s:\n" % (
1988 len(fti.entries), tln_string))
1990 for i in xrange(0, len(fti.entries)):
1994 collision_string = ""
1996 if collisions is not None:
1997 for c in collisions.entries:
2001 collision_string = " Collision[%s]" % (c.name.string)
2003 d = e.forest_trust_data
2004 if e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
2005 self.outf.write("TLN: %-32s DNS[*.%s]%s\n" % (
2006 self.entry_tln_status(flags),
2007 d.string, collision_string))
2008 elif e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
2009 self.outf.write("TLN_EX: %-29s DNS[*.%s]\n" % (
2011 elif e.type == lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
2012 self.outf.write("DOM: %-32s DNS[%s] Netbios[%s] SID[%s]%s\n" % (
2013 self.entry_dom_status(flags),
2014 d.dns_domain_name.string,
2015 d.netbios_domain_name.string,
2016 d.domain_sid, collision_string))
2019 class cmd_domain_trust_list(DomainTrustCommand):
2020 """List domain trusts."""
2022 synopsis = "%prog [options]"
2024 takes_optiongroups = {
2025 "sambaopts": options.SambaOptions,
2026 "versionopts": options.VersionOptions,
2027 "localdcopts": LocalDCCredentialsOptions,
2033 def run(self, sambaopts=None, versionopts=None, localdcopts=None):
2035 local_server = self.setup_local_server(sambaopts, localdcopts)
2037 local_netlogon = self.new_local_netlogon_connection()
2038 except RuntimeError as error:
2039 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2042 local_netlogon_trusts = local_netlogon.netr_DsrEnumerateDomainTrusts(local_server,
2043 netlogon.NETR_TRUST_FLAG_IN_FOREST |
2044 netlogon.NETR_TRUST_FLAG_OUTBOUND |
2045 netlogon.NETR_TRUST_FLAG_INBOUND)
2046 except RuntimeError as error:
2047 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
2048 # TODO: we could implement a fallback to lsa.EnumTrustDom()
2049 raise CommandError("LOCAL_DC[%s]: netr_DsrEnumerateDomainTrusts not supported." % (
2051 raise self.LocalRuntimeError(self, error, "netr_DsrEnumerateDomainTrusts failed")
2053 a = local_netlogon_trusts.array
2055 if t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
2057 self.outf.write("%-14s %-15s %-19s %s\n" % (
2058 "Type[%s]" % self.netr_DomainTrust_to_type(a, t),
2059 "Transitive[%s]" % self.netr_DomainTrust_to_transitive(t),
2060 "Direction[%s]" % self.netr_DomainTrust_to_direction(t),
2061 "Name[%s]" % self.netr_DomainTrust_to_name(t)))
2064 class cmd_domain_trust_show(DomainTrustCommand):
2065 """Show trusted domain details."""
2067 synopsis = "%prog NAME [options]"
2069 takes_optiongroups = {
2070 "sambaopts": options.SambaOptions,
2071 "versionopts": options.VersionOptions,
2072 "localdcopts": LocalDCCredentialsOptions,
2078 takes_args = ["domain"]
2080 def run(self, domain, sambaopts=None, versionopts=None, localdcopts=None):
2082 local_server = self.setup_local_server(sambaopts, localdcopts)
2084 local_lsa = self.new_local_lsa_connection()
2085 except RuntimeError as error:
2086 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2089 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2090 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2091 except RuntimeError as error:
2092 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2094 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2095 local_lsa_info.name.string,
2096 local_lsa_info.dns_domain.string,
2097 local_lsa_info.sid))
2099 lsaString = lsa.String()
2100 lsaString.string = domain
2102 local_tdo_full = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2103 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2104 local_tdo_info = local_tdo_full.info_ex
2105 local_tdo_posix = local_tdo_full.posix_offset
2106 except NTSTATUSError as error:
2107 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2108 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2110 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(FULL_INFO) failed")
2113 local_tdo_enctypes = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2114 lsaString, lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES)
2115 except NTSTATUSError as error:
2116 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_PARAMETER):
2118 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_INFO_CLASS):
2121 if error is not None:
2122 raise self.LocalRuntimeError(self, error,
2123 "QueryTrustedDomainInfoByName(SUPPORTED_ENCRYPTION_TYPES) failed")
2125 local_tdo_enctypes = lsa.TrustDomainInfoSupportedEncTypes()
2126 local_tdo_enctypes.enc_types = 0
2129 local_tdo_forest = None
2130 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2131 local_tdo_forest = local_lsa.lsaRQueryForestTrustInformation(local_policy,
2132 lsaString, lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
2133 except RuntimeError as error:
2134 if self.check_runtime_error(error, ntstatus.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE):
2136 if self.check_runtime_error(error, ntstatus.NT_STATUS_NOT_FOUND):
2138 if error is not None:
2139 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation failed")
2141 local_tdo_forest = lsa.ForestTrustInformation()
2142 local_tdo_forest.count = 0
2143 local_tdo_forest.entries = []
2145 self.outf.write("TrusteDomain:\n\n");
2146 self.outf.write("NetbiosName: %s\n" % local_tdo_info.netbios_name.string)
2147 if local_tdo_info.netbios_name.string != local_tdo_info.domain_name.string:
2148 self.outf.write("DnsName: %s\n" % local_tdo_info.domain_name.string)
2149 self.outf.write("SID: %s\n" % local_tdo_info.sid)
2150 self.outf.write("Type: %s\n" % self.trustType_string(local_tdo_info.trust_type))
2151 self.outf.write("Direction: %s\n" % self.trustDirection_string(local_tdo_info.trust_direction))
2152 self.outf.write("Attributes: %s\n" % self.trustAttributes_string(local_tdo_info.trust_attributes))
2153 posix_offset_u32 = ctypes.c_uint32(local_tdo_posix.posix_offset).value
2154 posix_offset_i32 = ctypes.c_int32(local_tdo_posix.posix_offset).value
2155 self.outf.write("PosixOffset: 0x%08X (%d)\n" % (posix_offset_u32, posix_offset_i32))
2156 self.outf.write("kerb_EncTypes: %s\n" % self.kerb_EncTypes_string(local_tdo_enctypes.enc_types))
2158 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2159 self.write_forest_trust_info(local_tdo_forest,
2160 tln=local_tdo_info.domain_name.string)
2164 class cmd_domain_trust_create(DomainTrustCommand):
2165 """Create a domain or forest trust."""
2167 synopsis = "%prog DOMAIN [options]"
2169 takes_optiongroups = {
2170 "sambaopts": options.SambaOptions,
2171 "versionopts": options.VersionOptions,
2172 "credopts": options.CredentialsOptions,
2173 "localdcopts": LocalDCCredentialsOptions,
2177 Option("--type", type="choice", metavar="TYPE",
2178 choices=["external", "forest"],
2179 help="The type of the trust: 'external' or 'forest'.",
2181 default="external"),
2182 Option("--direction", type="choice", metavar="DIRECTION",
2183 choices=["incoming", "outgoing", "both"],
2184 help="The trust direction: 'incoming', 'outgoing' or 'both'.",
2185 dest='trust_direction',
2187 Option("--create-location", type="choice", metavar="LOCATION",
2188 choices=["local", "both"],
2189 help="Where to create the trusted domain object: 'local' or 'both'.",
2190 dest='create_location',
2192 Option("--cross-organisation", action="store_true",
2193 help="The related domains does not belong to the same organisation.",
2194 dest='cross_organisation',
2196 Option("--quarantined", type="choice", metavar="yes|no",
2197 choices=["yes", "no", None],
2198 help="Special SID filtering rules are applied to the trust. "
2199 "With --type=external the default is yes. "
2200 "With --type=forest the default is no.",
2201 dest='quarantined_arg',
2203 Option("--not-transitive", action="store_true",
2204 help="The forest trust is not transitive.",
2205 dest='not_transitive',
2207 Option("--treat-as-external", action="store_true",
2208 help="The treat the forest trust as external.",
2209 dest='treat_as_external',
2211 Option("--no-aes-keys", action="store_false",
2212 help="The trust uses aes kerberos keys.",
2213 dest='use_aes_keys',
2215 Option("--skip-validation", action="store_false",
2216 help="Skip validation of the trust.",
2221 takes_args = ["domain"]
2223 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2224 trust_type=None, trust_direction=None, create_location=None,
2225 cross_organisation=False, quarantined_arg=None,
2226 not_transitive=False, treat_as_external=False,
2227 use_aes_keys=False, validate=True):
2229 lsaString = lsa.String()
2232 if quarantined_arg is None:
2233 if trust_type == 'external':
2235 elif quarantined_arg == 'yes':
2238 if trust_type != 'forest':
2240 raise CommandError("--not-transitive requires --type=forest")
2241 if treat_as_external:
2242 raise CommandError("--treat-as-external requires --type=forest")
2246 enc_types = lsa.TrustDomainInfoSupportedEncTypes()
2247 enc_types.enc_types = security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96
2248 enc_types.enc_types |= security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96
2250 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2251 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2252 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2254 local_trust_info = lsa.TrustDomainInfoInfoEx()
2255 local_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2256 local_trust_info.trust_direction = 0
2257 if trust_direction == "both":
2258 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2259 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2260 elif trust_direction == "incoming":
2261 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2262 elif trust_direction == "outgoing":
2263 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2264 local_trust_info.trust_attributes = 0
2265 if cross_organisation:
2266 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2268 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2269 if trust_type == "forest":
2270 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2272 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2273 if treat_as_external:
2274 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2276 def get_password(name):
2279 if password is not None and password is not '':
2281 password = getpass("New %s Password: " % name)
2282 passwordverify = getpass("Retype %s Password: " % name)
2283 if not password == passwordverify:
2285 self.outf.write("Sorry, passwords do not match.\n")
2287 incoming_secret = None
2288 outgoing_secret = None
2289 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2290 if create_location == "local":
2291 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2292 incoming_password = get_password("Incoming Trust")
2293 incoming_secret = string_to_byte_array(incoming_password.encode('utf-16-le'))
2294 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2295 outgoing_password = get_password("Outgoing Trust")
2296 outgoing_secret = string_to_byte_array(outgoing_password.encode('utf-16-le'))
2298 remote_trust_info = None
2300 # We use 240 random bytes.
2301 # Windows uses 28 or 240 random bytes. I guess it's
2302 # based on the trust type external vs. forest.
2304 # The initial trust password can be up to 512 bytes
2305 # while the versioned passwords used for periodic updates
2306 # can only be up to 498 bytes, as netr_ServerPasswordSet2()
2307 # needs to pass the NL_PASSWORD_VERSION structure within the
2308 # 512 bytes and a 2 bytes confounder is required.
2310 def random_trust_secret(length):
2311 pw = samba.generate_random_machine_password(length/2, length/2)
2312 return string_to_byte_array(pw.encode('utf-16-le'))
2314 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2315 incoming_secret = random_trust_secret(240)
2316 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2317 outgoing_secret = random_trust_secret(240)
2319 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2320 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2322 remote_trust_info = lsa.TrustDomainInfoInfoEx()
2323 remote_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2324 remote_trust_info.trust_direction = 0
2325 if trust_direction == "both":
2326 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2327 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2328 elif trust_direction == "incoming":
2329 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2330 elif trust_direction == "outgoing":
2331 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2332 remote_trust_info.trust_attributes = 0
2333 if cross_organisation:
2334 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2336 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2337 if trust_type == "forest":
2338 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2340 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2341 if treat_as_external:
2342 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2344 local_server = self.setup_local_server(sambaopts, localdcopts)
2346 local_lsa = self.new_local_lsa_connection()
2347 except RuntimeError as error:
2348 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2351 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2352 except RuntimeError as error:
2353 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2355 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2356 local_lsa_info.name.string,
2357 local_lsa_info.dns_domain.string,
2358 local_lsa_info.sid))
2361 remote_server = self.setup_remote_server(credopts, domain)
2362 except RuntimeError as error:
2363 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2366 remote_lsa = self.new_remote_lsa_connection()
2367 except RuntimeError as error:
2368 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2371 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2372 except RuntimeError as error:
2373 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2375 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2376 remote_lsa_info.name.string,
2377 remote_lsa_info.dns_domain.string,
2378 remote_lsa_info.sid))
2380 local_trust_info.domain_name.string = remote_lsa_info.dns_domain.string
2381 local_trust_info.netbios_name.string = remote_lsa_info.name.string
2382 local_trust_info.sid = remote_lsa_info.sid
2384 if remote_trust_info:
2385 remote_trust_info.domain_name.string = local_lsa_info.dns_domain.string
2386 remote_trust_info.netbios_name.string = local_lsa_info.name.string
2387 remote_trust_info.sid = local_lsa_info.sid
2390 lsaString.string = local_trust_info.domain_name.string
2391 local_old_netbios = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2392 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2393 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2394 except NTSTATUSError as error:
2395 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2396 raise self.LocalRuntimeError(self, error,
2397 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2401 lsaString.string = local_trust_info.netbios_name.string
2402 local_old_dns = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2403 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2404 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2405 except NTSTATUSError as error:
2406 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2407 raise self.LocalRuntimeError(self, error,
2408 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2411 if remote_trust_info:
2413 lsaString.string = remote_trust_info.domain_name.string
2414 remote_old_netbios = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2415 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2416 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2417 except NTSTATUSError as error:
2418 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2419 raise self.RemoteRuntimeError(self, error,
2420 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2424 lsaString.string = remote_trust_info.netbios_name.string
2425 remote_old_dns = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2426 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2427 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2428 except NTSTATUSError as error:
2429 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2430 raise self.RemoteRuntimeError(self, error,
2431 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2435 local_netlogon = self.new_local_netlogon_connection()
2436 except RuntimeError as error:
2437 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2440 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
2441 except RuntimeError as error:
2442 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
2444 if remote_trust_info:
2446 remote_netlogon = self.new_remote_netlogon_connection()
2447 except RuntimeError as error:
2448 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2451 remote_netlogon_info = self.get_netlogon_dc_info(remote_netlogon, remote_server)
2452 except RuntimeError as error:
2453 raise self.RemoteRuntimeError(self, error, "failed to get netlogon dc info")
2455 def generate_AuthInOutBlob(secret, update_time):
2457 blob = drsblobs.trustAuthInOutBlob()
2462 clear = drsblobs.AuthInfoClear()
2463 clear.size = len(secret)
2464 clear.password = secret
2466 info = drsblobs.AuthenticationInformation()
2467 info.LastUpdateTime = samba.unix2nttime(update_time)
2468 info.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
2469 info.AuthInfo = clear
2471 array = drsblobs.AuthenticationInformationArray()
2473 array.array = [info]
2475 blob = drsblobs.trustAuthInOutBlob()
2477 blob.current = array
2481 def generate_AuthInfoInternal(session_key, incoming=None, outgoing=None):
2482 confounder = [0] * 512
2483 for i in range(len(confounder)):
2484 confounder[i] = random.randint(0, 255)
2486 trustpass = drsblobs.trustDomainPasswords()
2488 trustpass.confounder = confounder
2489 trustpass.outgoing = outgoing
2490 trustpass.incoming = incoming
2492 trustpass_blob = ndr_pack(trustpass)
2494 encrypted_trustpass = arcfour_encrypt(session_key, trustpass_blob)
2496 auth_blob = lsa.DATA_BUF2()
2497 auth_blob.size = len(encrypted_trustpass)
2498 auth_blob.data = string_to_byte_array(encrypted_trustpass)
2500 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
2501 auth_info.auth_blob = auth_blob
2505 update_time = samba.current_unix_time()
2506 incoming_blob = generate_AuthInOutBlob(incoming_secret, update_time)
2507 outgoing_blob = generate_AuthInOutBlob(outgoing_secret, update_time)
2509 local_tdo_handle = None
2510 remote_tdo_handle = None
2512 local_auth_info = generate_AuthInfoInternal(local_lsa.session_key,
2513 incoming=incoming_blob,
2514 outgoing=outgoing_blob)
2515 if remote_trust_info:
2516 remote_auth_info = generate_AuthInfoInternal(remote_lsa.session_key,
2517 incoming=outgoing_blob,
2518 outgoing=incoming_blob)
2521 if remote_trust_info:
2522 self.outf.write("Creating remote TDO.\n")
2523 current_request = { "location": "remote", "name": "CreateTrustedDomainEx2"}
2524 remote_tdo_handle = remote_lsa.CreateTrustedDomainEx2(remote_policy,
2527 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2528 self.outf.write("Remote TDO created.\n")
2530 self.outf.write("Setting supported encryption types on remote TDO.\n")
2531 current_request = { "location": "remote", "name": "SetInformationTrustedDomain"}
2532 remote_lsa.SetInformationTrustedDomain(remote_tdo_handle,
2533 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2536 self.outf.write("Creating local TDO.\n")
2537 current_request = { "location": "local", "name": "CreateTrustedDomainEx2"}
2538 local_tdo_handle = local_lsa.CreateTrustedDomainEx2(local_policy,
2541 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2542 self.outf.write("Local TDO created\n")
2544 self.outf.write("Setting supported encryption types on local TDO.\n")
2545 current_request = { "location": "local", "name": "SetInformationTrustedDomain"}
2546 local_lsa.SetInformationTrustedDomain(local_tdo_handle,
2547 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2549 except RuntimeError as error:
2550 self.outf.write("Error: %s failed %sly - cleaning up\n" % (
2551 current_request['name'], current_request['location']))
2552 if remote_tdo_handle:
2553 self.outf.write("Deleting remote TDO.\n")
2554 remote_lsa.DeleteObject(remote_tdo_handle)
2555 remote_tdo_handle = None
2556 if local_tdo_handle:
2557 self.outf.write("Deleting local TDO.\n")
2558 local_lsa.DeleteObject(local_tdo_handle)
2559 local_tdo_handle = None
2560 if current_request['location'] is "remote":
2561 raise self.RemoteRuntimeError(self, error, "%s" % (
2562 current_request['name']))
2563 raise self.LocalRuntimeError(self, error, "%s" % (
2564 current_request['name']))
2567 if local_trust_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2568 self.outf.write("Setup local forest trust information...\n")
2570 # get all information about the remote trust
2571 # this triggers netr_GetForestTrustInformation to the remote domain
2572 # and lsaRSetForestTrustInformation() locally, but new top level
2573 # names are disabled by default.
2574 local_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
2575 remote_lsa_info.dns_domain.string,
2576 netlogon.DS_GFTI_UPDATE_TDO)
2577 except RuntimeError as error:
2578 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2581 # here we try to enable all top level names
2582 local_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
2583 remote_lsa_info.dns_domain,
2584 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2587 except RuntimeError as error:
2588 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2590 self.write_forest_trust_info(local_forest_info,
2591 tln=remote_lsa_info.dns_domain.string,
2592 collisions=local_forest_collision)
2594 if remote_trust_info:
2595 self.outf.write("Setup remote forest trust information...\n")
2597 # get all information about the local trust (from the perspective of the remote domain)
2598 # this triggers netr_GetForestTrustInformation to our domain.
2599 # and lsaRSetForestTrustInformation() remotely, but new top level
2600 # names are disabled by default.
2601 remote_forest_info = remote_netlogon.netr_DsRGetForestTrustInformation(remote_netlogon_info.dc_unc,
2602 local_lsa_info.dns_domain.string,
2603 netlogon.DS_GFTI_UPDATE_TDO)
2604 except RuntimeError as error:
2605 raise self.RemoteRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2608 # here we try to enable all top level names
2609 remote_forest_collision = remote_lsa.lsaRSetForestTrustInformation(remote_policy,
2610 local_lsa_info.dns_domain,
2611 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2614 except RuntimeError as error:
2615 raise self.RemoteRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2617 self.write_forest_trust_info(remote_forest_info,
2618 tln=local_lsa_info.dns_domain.string,
2619 collisions=remote_forest_collision)
2621 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2622 self.outf.write("Validating outgoing trust...\n")
2624 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_netlogon_info.dc_unc,
2625 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2627 remote_lsa_info.dns_domain.string)
2628 except RuntimeError as error:
2629 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2631 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2632 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2634 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2635 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2636 local_trust_verify.trusted_dc_name,
2637 local_trust_verify.tc_connection_status[1],
2638 local_trust_verify.pdc_connection_status[1])
2640 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2641 local_trust_verify.trusted_dc_name,
2642 local_trust_verify.tc_connection_status[1],
2643 local_trust_verify.pdc_connection_status[1])
2645 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2646 raise CommandError(local_validation)
2648 self.outf.write("OK: %s\n" % local_validation)
2650 if remote_trust_info:
2651 if remote_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2652 self.outf.write("Validating incoming trust...\n")
2654 remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_netlogon_info.dc_unc,
2655 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2657 local_lsa_info.dns_domain.string)
2658 except RuntimeError as error:
2659 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2661 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2662 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2664 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2665 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2666 remote_trust_verify.trusted_dc_name,
2667 remote_trust_verify.tc_connection_status[1],
2668 remote_trust_verify.pdc_connection_status[1])
2670 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2671 remote_trust_verify.trusted_dc_name,
2672 remote_trust_verify.tc_connection_status[1],
2673 remote_trust_verify.pdc_connection_status[1])
2675 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
2676 raise CommandError(remote_validation)
2678 self.outf.write("OK: %s\n" % remote_validation)
2680 if remote_tdo_handle is not None:
2682 remote_lsa.Close(remote_tdo_handle)
2683 except RuntimeError as error:
2685 remote_tdo_handle = None
2686 if local_tdo_handle is not None:
2688 local_lsa.Close(local_tdo_handle)
2689 except RuntimeError as error:
2691 local_tdo_handle = None
2693 self.outf.write("Success.\n")
2696 class cmd_domain_trust_delete(DomainTrustCommand):
2697 """Delete a domain trust."""
2699 synopsis = "%prog DOMAIN [options]"
2701 takes_optiongroups = {
2702 "sambaopts": options.SambaOptions,
2703 "versionopts": options.VersionOptions,
2704 "credopts": options.CredentialsOptions,
2705 "localdcopts": LocalDCCredentialsOptions,
2709 Option("--delete-location", type="choice", metavar="LOCATION",
2710 choices=["local", "both"],
2711 help="Where to delete the trusted domain object: 'local' or 'both'.",
2712 dest='delete_location',
2716 takes_args = ["domain"]
2718 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2719 delete_location=None):
2721 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2722 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2723 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2725 if delete_location == "local":
2726 remote_policy_access = None
2728 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2729 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2730 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2732 local_server = self.setup_local_server(sambaopts, localdcopts)
2734 local_lsa = self.new_local_lsa_connection()
2735 except RuntimeError as error:
2736 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2739 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2740 except RuntimeError as error:
2741 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2743 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2744 local_lsa_info.name.string,
2745 local_lsa_info.dns_domain.string,
2746 local_lsa_info.sid))
2748 local_tdo_info = None
2749 local_tdo_handle = None
2750 remote_tdo_info = None
2751 remote_tdo_handle = None
2753 lsaString = lsa.String()
2755 lsaString.string = domain
2756 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2757 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2758 except NTSTATUSError as error:
2759 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2760 raise CommandError("Failed to find trust for domain '%s'" % domain)
2761 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2764 if remote_policy_access is not None:
2766 remote_server = self.setup_remote_server(credopts, domain)
2767 except RuntimeError as error:
2768 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2771 remote_lsa = self.new_remote_lsa_connection()
2772 except RuntimeError as error:
2773 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2776 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2777 except RuntimeError as error:
2778 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2780 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2781 remote_lsa_info.name.string,
2782 remote_lsa_info.dns_domain.string,
2783 remote_lsa_info.sid))
2785 if remote_lsa_info.sid != local_tdo_info.sid or \
2786 remote_lsa_info.name.string != local_tdo_info.netbios_name.string or \
2787 remote_lsa_info.dns_domain.string != local_tdo_info.domain_name.string:
2788 raise CommandError("LocalTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2789 local_tdo_info.netbios_name.string,
2790 local_tdo_info.domain_name.string,
2791 local_tdo_info.sid))
2794 lsaString.string = local_lsa_info.dns_domain.string
2795 remote_tdo_info = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2796 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2797 except NTSTATUSError as error:
2798 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2799 raise self.RemoteRuntimeError(self, error, "QueryTrustedDomainInfoByName(%s)" % (
2803 if remote_tdo_info is not None:
2804 if local_lsa_info.sid != remote_tdo_info.sid or \
2805 local_lsa_info.name.string != remote_tdo_info.netbios_name.string or \
2806 local_lsa_info.dns_domain.string != remote_tdo_info.domain_name.string:
2807 raise CommandError("RemoteTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2808 remote_tdo_info.netbios_name.string,
2809 remote_tdo_info.domain_name.string,
2810 remote_tdo_info.sid))
2812 if local_tdo_info is not None:
2814 lsaString.string = local_tdo_info.domain_name.string
2815 local_tdo_handle = local_lsa.OpenTrustedDomainByName(local_policy,
2817 security.SEC_STD_DELETE)
2818 except RuntimeError as error:
2819 raise self.LocalRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2822 local_lsa.DeleteObject(local_tdo_handle)
2823 local_tdo_handle = None
2825 if remote_tdo_info is not None:
2827 lsaString.string = remote_tdo_info.domain_name.string
2828 remote_tdo_handle = remote_lsa.OpenTrustedDomainByName(remote_policy,
2830 security.SEC_STD_DELETE)
2831 except RuntimeError as error:
2832 raise self.RemoteRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2835 if remote_tdo_handle is not None:
2837 remote_lsa.DeleteObject(remote_tdo_handle)
2838 remote_tdo_handle = None
2839 self.outf.write("RemoteTDO deleted.\n")
2840 except RuntimeError as error:
2841 self.outf.write("%s\n" % self.RemoteRuntimeError(self, error, "DeleteObject() failed"))
2843 if local_tdo_handle is not None:
2845 local_lsa.DeleteObject(local_tdo_handle)
2846 local_tdo_handle = None
2847 self.outf.write("LocalTDO deleted.\n")
2848 except RuntimeError as error:
2849 self.outf.write("%s\n" % self.LocalRuntimeError(self, error, "DeleteObject() failed"))
2853 class cmd_domain_trust_validate(DomainTrustCommand):
2854 """Validate a domain trust."""
2856 synopsis = "%prog DOMAIN [options]"
2858 takes_optiongroups = {
2859 "sambaopts": options.SambaOptions,
2860 "versionopts": options.VersionOptions,
2861 "credopts": options.CredentialsOptions,
2862 "localdcopts": LocalDCCredentialsOptions,
2866 Option("--validate-location", type="choice", metavar="LOCATION",
2867 choices=["local", "both"],
2868 help="Where to validate the trusted domain object: 'local' or 'both'.",
2869 dest='validate_location',
2873 takes_args = ["domain"]
2875 def run(self, domain, sambaopts=None, versionopts=None, credopts=None, localdcopts=None,
2876 validate_location=None):
2878 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2880 local_server = self.setup_local_server(sambaopts, localdcopts)
2882 local_lsa = self.new_local_lsa_connection()
2883 except RuntimeError as error:
2884 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2887 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2888 except RuntimeError as error:
2889 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2891 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2892 local_lsa_info.name.string,
2893 local_lsa_info.dns_domain.string,
2894 local_lsa_info.sid))
2897 lsaString = lsa.String()
2898 lsaString.string = domain
2899 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2900 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2901 except NTSTATUSError as error:
2902 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2903 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2905 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
2907 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
2908 local_tdo_info.netbios_name.string,
2909 local_tdo_info.domain_name.string,
2910 local_tdo_info.sid))
2913 local_netlogon = self.new_local_netlogon_connection()
2914 except RuntimeError as error:
2915 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2918 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_server,
2919 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2921 local_tdo_info.domain_name.string)
2922 except RuntimeError as error:
2923 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2925 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2926 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2928 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2929 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2930 local_trust_verify.trusted_dc_name,
2931 local_trust_verify.tc_connection_status[1],
2932 local_trust_verify.pdc_connection_status[1])
2934 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2935 local_trust_verify.trusted_dc_name,
2936 local_trust_verify.tc_connection_status[1],
2937 local_trust_verify.pdc_connection_status[1])
2939 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2940 raise CommandError(local_validation)
2942 self.outf.write("OK: %s\n" % local_validation)
2945 server = local_trust_verify.trusted_dc_name.replace('\\', '')
2946 domain_and_server = "%s\\%s" % (local_tdo_info.domain_name.string, server)
2947 local_trust_rediscover = local_netlogon.netr_LogonControl2Ex(local_server,
2948 netlogon.NETLOGON_CONTROL_REDISCOVER,
2951 except RuntimeError as error:
2952 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
2954 local_conn_status = self._uint32(local_trust_rediscover.tc_connection_status[0])
2955 local_rediscover = "LocalRediscover: DC[%s] CONNECTION[%s]" % (
2956 local_trust_rediscover.trusted_dc_name,
2957 local_trust_rediscover.tc_connection_status[1])
2959 if local_conn_status != werror.WERR_SUCCESS:
2960 raise CommandError(local_rediscover)
2962 self.outf.write("OK: %s\n" % local_rediscover)
2964 if validate_location != "local":
2966 remote_server = self.setup_remote_server(credopts, domain, require_pdc=False)
2967 except RuntimeError as error:
2968 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2971 remote_netlogon = self.new_remote_netlogon_connection()
2972 except RuntimeError as error:
2973 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2976 remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_server,
2977 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2979 local_lsa_info.dns_domain.string)
2980 except RuntimeError as error:
2981 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2983 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2984 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2986 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2987 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2988 remote_trust_verify.trusted_dc_name,
2989 remote_trust_verify.tc_connection_status[1],
2990 remote_trust_verify.pdc_connection_status[1])
2992 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2993 remote_trust_verify.trusted_dc_name,
2994 remote_trust_verify.tc_connection_status[1],
2995 remote_trust_verify.pdc_connection_status[1])
2997 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
2998 raise CommandError(remote_validation)
3000 self.outf.write("OK: %s\n" % remote_validation)
3003 server = remote_trust_verify.trusted_dc_name.replace('\\', '')
3004 domain_and_server = "%s\\%s" % (local_lsa_info.dns_domain.string, server)
3005 remote_trust_rediscover = remote_netlogon.netr_LogonControl2Ex(remote_server,
3006 netlogon.NETLOGON_CONTROL_REDISCOVER,
3009 except RuntimeError as error:
3010 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
3012 remote_conn_status = self._uint32(remote_trust_rediscover.tc_connection_status[0])
3014 remote_rediscover = "RemoteRediscover: DC[%s] CONNECTION[%s]" % (
3015 remote_trust_rediscover.trusted_dc_name,
3016 remote_trust_rediscover.tc_connection_status[1])
3018 if remote_conn_status != werror.WERR_SUCCESS:
3019 raise CommandError(remote_rediscover)
3021 self.outf.write("OK: %s\n" % remote_rediscover)
3025 class cmd_domain_trust_namespaces(DomainTrustCommand):
3026 """Manage forest trust namespaces."""
3028 synopsis = "%prog [DOMAIN] [options]"
3030 takes_optiongroups = {
3031 "sambaopts": options.SambaOptions,
3032 "versionopts": options.VersionOptions,
3033 "localdcopts": LocalDCCredentialsOptions,
3037 Option("--refresh", type="choice", metavar="check|store",
3038 choices=["check", "store", None],
3039 help="List and maybe store refreshed forest trust information: 'check' or 'store'.",
3042 Option("--enable-all", action="store_true",
3043 help="Try to update disabled entries, not allowed with --refresh=check.",
3046 Option("--enable-tln", action="append", metavar='DNSDOMAIN',
3047 help="Enable a top level name entry. Can be specified multiple times.",
3050 Option("--disable-tln", action="append", metavar='DNSDOMAIN',
3051 help="Disable a top level name entry. Can be specified multiple times.",
3054 Option("--add-tln-ex", action="append", metavar='DNSDOMAIN',
3055 help="Add a top level exclusion entry. Can be specified multiple times.",
3058 Option("--delete-tln-ex", action="append", metavar='DNSDOMAIN',
3059 help="Delete a top level exclusion entry. Can be specified multiple times.",
3060 dest='delete_tln_ex',
3062 Option("--enable-nb", action="append", metavar='NETBIOSDOMAIN',
3063 help="Enable a netbios name in a domain entry. Can be specified multiple times.",
3066 Option("--disable-nb", action="append", metavar='NETBIOSDOMAIN',
3067 help="Disable a netbios name in a domain entry. Can be specified multiple times.",
3070 Option("--enable-sid", action="append", metavar='DOMAINSID',
3071 help="Enable a SID in a domain entry. Can be specified multiple times.",
3072 dest='enable_sid_str',
3074 Option("--disable-sid", action="append", metavar='DOMAINSID',
3075 help="Disable a SID in a domain entry. Can be specified multiple times.",
3076 dest='disable_sid_str',
3078 Option("--add-upn-suffix", action="append", metavar='DNSDOMAIN',
3079 help="Add a new uPNSuffixes attribute for the local forest. Can be specified multiple times.",
3082 Option("--delete-upn-suffix", action="append", metavar='DNSDOMAIN',
3083 help="Delete an existing uPNSuffixes attribute of the local forest. Can be specified multiple times.",
3086 Option("--add-spn-suffix", action="append", metavar='DNSDOMAIN',
3087 help="Add a new msDS-SPNSuffixes attribute for the local forest. Can be specified multiple times.",
3090 Option("--delete-spn-suffix", action="append", metavar='DNSDOMAIN',
3091 help="Delete an existing msDS-SPNSuffixes attribute of the local forest. Can be specified multiple times.",
3096 takes_args = ["domain?"]
3098 def run(self, domain=None, sambaopts=None, localdcopts=None, versionopts=None,
3099 refresh=None, enable_all=False,
3100 enable_tln=[], disable_tln=[], add_tln_ex=[], delete_tln_ex=[],
3101 enable_sid_str=[], disable_sid_str=[], enable_nb=[], disable_nb=[],
3102 add_upn=[], delete_upn=[], add_spn=[], delete_spn=[]):
3104 require_update = False
3107 if refresh == "store":
3108 raise CommandError("--refresh=%s not allowed without DOMAIN" % refresh)
3111 raise CommandError("--enable-all not allowed without DOMAIN")
3113 if len(enable_tln) > 0:
3114 raise CommandError("--enable-tln not allowed without DOMAIN")
3115 if len(disable_tln) > 0:
3116 raise CommandError("--disable-tln not allowed without DOMAIN")
3118 if len(add_tln_ex) > 0:
3119 raise CommandError("--add-tln-ex not allowed without DOMAIN")
3120 if len(delete_tln_ex) > 0:
3121 raise CommandError("--delete-tln-ex not allowed without DOMAIN")
3123 if len(enable_nb) > 0:
3124 raise CommandError("--enable-nb not allowed without DOMAIN")
3125 if len(disable_nb) > 0:
3126 raise CommandError("--disable-nb not allowed without DOMAIN")
3128 if len(enable_sid_str) > 0:
3129 raise CommandError("--enable-sid not allowed without DOMAIN")
3130 if len(disable_sid_str) > 0:
3131 raise CommandError("--disable-sid not allowed without DOMAIN")
3133 if len(add_upn) > 0:
3135 if not n.startswith("*."):
3137 raise CommandError("value[%s] specified for --add-upn-suffix should not include with '*.'" % n)
3138 require_update = True
3139 if len(delete_upn) > 0:
3140 for n in delete_upn:
3141 if not n.startswith("*."):
3143 raise CommandError("value[%s] specified for --delete-upn-suffix should not include with '*.'" % n)
3144 require_update = True
3146 for d in delete_upn:
3147 if a.lower() != d.lower():
3149 raise CommandError("value[%s] specified for --add-upn-suffix and --delete-upn-suffix" % a)
3151 if len(add_spn) > 0:
3153 if not n.startswith("*."):
3155 raise CommandError("value[%s] specified for --add-spn-suffix should not include with '*.'" % n)
3156 require_update = True
3157 if len(delete_spn) > 0:
3158 for n in delete_spn:
3159 if not n.startswith("*."):
3161 raise CommandError("value[%s] specified for --delete-spn-suffix should not include with '*.'" % n)
3162 require_update = True
3164 for d in delete_spn:
3165 if a.lower() != d.lower():
3167 raise CommandError("value[%s] specified for --add-spn-suffix and --delete-spn-suffix" % a)
3169 if len(add_upn) > 0:
3170 raise CommandError("--add-upn-suffix not allowed together with DOMAIN")
3171 if len(delete_upn) > 0:
3172 raise CommandError("--delete-upn-suffix not allowed together with DOMAIN")
3173 if len(add_spn) > 0:
3174 raise CommandError("--add-spn-suffix not allowed together with DOMAIN")
3175 if len(delete_spn) > 0:
3176 raise CommandError("--delete-spn-suffix not allowed together with DOMAIN")
3178 if refresh is not None:
3179 if refresh == "store":
3180 require_update = True
3182 if enable_all and refresh != "store":
3183 raise CommandError("--enable-all not allowed together with --refresh=%s" % refresh)
3185 if len(enable_tln) > 0:
3186 raise CommandError("--enable-tln not allowed together with --refresh")
3187 if len(disable_tln) > 0:
3188 raise CommandError("--disable-tln not allowed together with --refresh")
3190 if len(add_tln_ex) > 0:
3191 raise CommandError("--add-tln-ex not allowed together with --refresh")
3192 if len(delete_tln_ex) > 0:
3193 raise CommandError("--delete-tln-ex not allowed together with --refresh")
3195 if len(enable_nb) > 0:
3196 raise CommandError("--enable-nb not allowed together with --refresh")
3197 if len(disable_nb) > 0:
3198 raise CommandError("--disable-nb not allowed together with --refresh")
3200 if len(enable_sid_str) > 0:
3201 raise CommandError("--enable-sid not allowed together with --refresh")
3202 if len(disable_sid_str) > 0:
3203 raise CommandError("--disable-sid not allowed together with --refresh")
3206 require_update = True
3208 if len(enable_tln) > 0:
3209 raise CommandError("--enable-tln not allowed together with --enable-all")
3211 if len(enable_nb) > 0:
3212 raise CommandError("--enable-nb not allowed together with --enable-all")
3214 if len(enable_sid_str) > 0:
3215 raise CommandError("--enable-sid not allowed together with --enable-all")
3217 if len(enable_tln) > 0:
3218 require_update = True
3219 if len(disable_tln) > 0:
3220 require_update = True
3221 for e in enable_tln:
3222 for d in disable_tln:
3223 if e.lower() != d.lower():
3225 raise CommandError("value[%s] specified for --enable-tln and --disable-tln" % e)
3227 if len(add_tln_ex) > 0:
3228 for n in add_tln_ex:
3229 if not n.startswith("*."):
3231 raise CommandError("value[%s] specified for --add-tln-ex should not include with '*.'" % n)
3232 require_update = True
3233 if len(delete_tln_ex) > 0:
3234 for n in delete_tln_ex:
3235 if not n.startswith("*."):
3237 raise CommandError("value[%s] specified for --delete-tln-ex should not include with '*.'" % n)
3238 require_update = True
3239 for a in add_tln_ex:
3240 for d in delete_tln_ex:
3241 if a.lower() != d.lower():
3243 raise CommandError("value[%s] specified for --add-tln-ex and --delete-tln-ex" % a)
3245 if len(enable_nb) > 0:
3246 require_update = True
3247 if len(disable_nb) > 0:
3248 require_update = True
3250 for d in disable_nb:
3251 if e.upper() != d.upper():
3253 raise CommandError("value[%s] specified for --enable-nb and --disable-nb" % e)
3256 for s in enable_sid_str:
3258 sid = security.dom_sid(s)
3259 except TypeError as error:
3260 raise CommandError("value[%s] specified for --enable-sid is not a valid SID" % s)
3261 enable_sid.append(sid)
3263 for s in disable_sid_str:
3265 sid = security.dom_sid(s)
3266 except TypeError as error:
3267 raise CommandError("value[%s] specified for --disable-sid is not a valid SID" % s)
3268 disable_sid.append(sid)
3269 if len(enable_sid) > 0:
3270 require_update = True
3271 if len(disable_sid) > 0:
3272 require_update = True
3273 for e in enable_sid:
3274 for d in disable_sid:
3277 raise CommandError("value[%s] specified for --enable-sid and --disable-sid" % e)
3279 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
3281 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
3283 local_server = self.setup_local_server(sambaopts, localdcopts)
3285 local_lsa = self.new_local_lsa_connection()
3286 except RuntimeError as error:
3287 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
3290 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
3291 except RuntimeError as error:
3292 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
3294 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
3295 local_lsa_info.name.string,
3296 local_lsa_info.dns_domain.string,
3297 local_lsa_info.sid))
3301 local_netlogon = self.new_local_netlogon_connection()
3302 except RuntimeError as error:
3303 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3306 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3307 except RuntimeError as error:
3308 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3310 if local_netlogon_info.domain_name != local_netlogon_info.forest_name:
3311 raise CommandError("The local domain [%s] is not the forest root [%s]" % (
3312 local_netlogon_info.domain_name,
3313 local_netlogon_info.forest_name))
3316 # get all information about our own forest
3317 own_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3319 except RuntimeError as error:
3320 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
3321 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3324 if self.check_runtime_error(error, werror.WERR_INVALID_FUNCTION):
3325 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3328 if self.check_runtime_error(error, werror.WERR_NERR_ACFNOTLOADED):
3329 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3332 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3334 self.outf.write("Own forest trust information...\n")
3335 self.write_forest_trust_info(own_forest_info,
3336 tln=local_lsa_info.dns_domain.string)
3339 local_samdb = self.new_local_ldap_connection()
3340 except RuntimeError as error:
3341 raise self.LocalRuntimeError(self, error, "failed to connect to SamDB")
3343 local_partitions_dn = "CN=Partitions,%s" % str(local_samdb.get_config_basedn())
3344 attrs = ['uPNSuffixes', 'msDS-SPNSuffixes']
3346 msgs = local_samdb.search(base=local_partitions_dn,
3347 scope=ldb.SCOPE_BASE,
3348 expression="(objectClass=crossRefContainer)",
3350 stored_msg = msgs[0]
3351 except ldb.LdbError as error:
3352 raise self.LocalLdbError(self, error, "failed to search partition dn")
3354 stored_upn_vals = []
3355 if 'uPNSuffixes' in stored_msg:
3356 stored_upn_vals.extend(stored_msg['uPNSuffixes'])
3358 stored_spn_vals = []
3359 if 'msDS-SPNSuffixes' in stored_msg:
3360 stored_spn_vals.extend(stored_msg['msDS-SPNSuffixes'])
3362 self.outf.write("Stored uPNSuffixes attributes[%d]:\n" % len(stored_upn_vals))
3363 for v in stored_upn_vals:
3364 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3365 self.outf.write("Stored msDS-SPNSuffixes attributes[%d]:\n" % len(stored_spn_vals))
3366 for v in stored_spn_vals:
3367 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3369 if not require_update:
3373 update_upn_vals = []
3374 update_upn_vals.extend(stored_upn_vals)
3377 update_spn_vals = []
3378 update_spn_vals.extend(stored_spn_vals)
3382 for i in xrange(0, len(update_upn_vals)):
3383 v = update_upn_vals[i]
3384 if v.lower() != upn.lower():
3389 raise CommandError("Entry already present for value[%s] specified for --add-upn-suffix" % upn)
3390 update_upn_vals.append(upn)
3393 for upn in delete_upn:
3395 for i in xrange(0, len(update_upn_vals)):
3396 v = update_upn_vals[i]
3397 if v.lower() != upn.lower():
3402 raise CommandError("Entry not found for value[%s] specified for --delete-upn-suffix" % upn)
3404 update_upn_vals.pop(idx)
3409 for i in xrange(0, len(update_spn_vals)):
3410 v = update_spn_vals[i]
3411 if v.lower() != spn.lower():
3416 raise CommandError("Entry already present for value[%s] specified for --add-spn-suffix" % spn)
3417 update_spn_vals.append(spn)
3420 for spn in delete_spn:
3422 for i in xrange(0, len(update_spn_vals)):
3423 v = update_spn_vals[i]
3424 if v.lower() != spn.lower():
3429 raise CommandError("Entry not found for value[%s] specified for --delete-spn-suffix" % spn)
3431 update_spn_vals.pop(idx)
3434 self.outf.write("Update uPNSuffixes attributes[%d]:\n" % len(update_upn_vals))
3435 for v in update_upn_vals:
3436 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3437 self.outf.write("Update msDS-SPNSuffixes attributes[%d]:\n" % len(update_spn_vals))
3438 for v in update_spn_vals:
3439 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3441 update_msg = ldb.Message()
3442 update_msg.dn = stored_msg.dn
3445 update_msg['uPNSuffixes'] = ldb.MessageElement(update_upn_vals,
3446 ldb.FLAG_MOD_REPLACE,
3449 update_msg['msDS-SPNSuffixes'] = ldb.MessageElement(update_spn_vals,
3450 ldb.FLAG_MOD_REPLACE,
3453 local_samdb.modify(update_msg)
3454 except ldb.LdbError as error:
3455 raise self.LocalLdbError(self, error, "failed to update partition dn")
3458 stored_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3460 except RuntimeError as error:
3461 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3463 self.outf.write("Stored forest trust information...\n")
3464 self.write_forest_trust_info(stored_forest_info,
3465 tln=local_lsa_info.dns_domain.string)
3469 lsaString = lsa.String()
3470 lsaString.string = domain
3471 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
3472 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
3473 except NTSTATUSError as error:
3474 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
3475 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
3477 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
3479 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
3480 local_tdo_info.netbios_name.string,
3481 local_tdo_info.domain_name.string,
3482 local_tdo_info.sid))
3484 if not local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
3485 raise CommandError("trusted domain object for domain [%s] is not marked as FOREST_TRANSITIVE." % domain)
3487 if refresh is not None:
3489 local_netlogon = self.new_local_netlogon_connection()
3490 except RuntimeError as error:
3491 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3494 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3495 except RuntimeError as error:
3496 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3498 lsa_update_check = 1
3499 if refresh == "store":
3500 netlogon_update_tdo = netlogon.DS_GFTI_UPDATE_TDO
3502 lsa_update_check = 0
3504 netlogon_update_tdo = 0
3507 # get all information about the remote trust
3508 # this triggers netr_GetForestTrustInformation to the remote domain
3509 # and lsaRSetForestTrustInformation() locally, but new top level
3510 # names are disabled by default.
3511 fresh_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3512 local_tdo_info.domain_name.string,
3513 netlogon_update_tdo)
3514 except RuntimeError as error:
3515 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3518 fresh_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3519 local_tdo_info.domain_name,
3520 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3523 except RuntimeError as error:
3524 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3526 self.outf.write("Fresh forest trust information...\n")
3527 self.write_forest_trust_info(fresh_forest_info,
3528 tln=local_tdo_info.domain_name.string,
3529 collisions=fresh_forest_collision)
3531 if refresh == "store":
3533 lsaString = lsa.String()
3534 lsaString.string = local_tdo_info.domain_name.string
3535 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3537 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3538 except RuntimeError as error:
3539 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3541 self.outf.write("Stored forest trust information...\n")
3542 self.write_forest_trust_info(stored_forest_info,
3543 tln=local_tdo_info.domain_name.string)
3548 # The none --refresh path
3552 lsaString = lsa.String()
3553 lsaString.string = local_tdo_info.domain_name.string
3554 local_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3556 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3557 except RuntimeError as error:
3558 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3560 self.outf.write("Local forest trust information...\n")
3561 self.write_forest_trust_info(local_forest_info,
3562 tln=local_tdo_info.domain_name.string)
3564 if not require_update:
3568 entries.extend(local_forest_info.entries)
3569 update_forest_info = lsa.ForestTrustInformation()
3570 update_forest_info.count = len(entries)
3571 update_forest_info.entries = entries
3574 for i in xrange(0, len(update_forest_info.entries)):
3575 r = update_forest_info.entries[i]
3576 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3578 if update_forest_info.entries[i].flags == 0:
3580 update_forest_info.entries[i].time = 0
3581 update_forest_info.entries[i].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3582 for i in xrange(0, len(update_forest_info.entries)):
3583 r = update_forest_info.entries[i]
3584 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3586 if update_forest_info.entries[i].flags == 0:
3588 update_forest_info.entries[i].time = 0
3589 update_forest_info.entries[i].flags &= ~lsa.LSA_NB_DISABLED_MASK
3590 update_forest_info.entries[i].flags &= ~lsa.LSA_SID_DISABLED_MASK
3592 for tln in enable_tln:
3594 for i in xrange(0, len(update_forest_info.entries)):
3595 r = update_forest_info.entries[i]
3596 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3598 if r.forest_trust_data.string.lower() != tln.lower():
3603 raise CommandError("Entry not found for value[%s] specified for --enable-tln" % tln)
3604 if not update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_MASK:
3605 raise CommandError("Entry found for value[%s] specified for --enable-tln is already enabled" % tln)
3606 update_forest_info.entries[idx].time = 0
3607 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3609 for tln in disable_tln:
3611 for i in xrange(0, len(update_forest_info.entries)):
3612 r = update_forest_info.entries[i]
3613 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3615 if r.forest_trust_data.string.lower() != tln.lower():
3620 raise CommandError("Entry not found for value[%s] specified for --disable-tln" % tln)
3621 if update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_ADMIN:
3622 raise CommandError("Entry found for value[%s] specified for --disable-tln is already disabled" % tln)
3623 update_forest_info.entries[idx].time = 0
3624 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3625 update_forest_info.entries[idx].flags |= lsa.LSA_TLN_DISABLED_ADMIN
3627 for tln_ex in add_tln_ex:
3629 for i in xrange(0, len(update_forest_info.entries)):
3630 r = update_forest_info.entries[i]
3631 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3633 if r.forest_trust_data.string.lower() != tln_ex.lower():
3638 raise CommandError("Entry already present for value[%s] specified for --add-tln-ex" % tln_ex)
3640 tln_dot = ".%s" % tln_ex.lower()
3642 for i in xrange(0, len(update_forest_info.entries)):
3643 r = update_forest_info.entries[i]
3644 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3646 r_dot = ".%s" % r.forest_trust_data.string.lower()
3647 if tln_dot == r_dot:
3648 raise CommandError("TLN entry present for value[%s] specified for --add-tln-ex" % tln_ex)
3649 if not tln_dot.endswith(r_dot):
3655 raise CommandError("No TLN parent present for value[%s] specified for --add-tln-ex" % tln_ex)
3657 r = lsa.ForestTrustRecord()
3658 r.type = lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX
3661 r.forest_trust_data.string = tln_ex
3664 entries.extend(update_forest_info.entries)
3665 entries.insert(idx + 1, r)
3666 update_forest_info.count = len(entries)
3667 update_forest_info.entries = entries
3669 for tln_ex in delete_tln_ex:
3671 for i in xrange(0, len(update_forest_info.entries)):
3672 r = update_forest_info.entries[i]
3673 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3675 if r.forest_trust_data.string.lower() != tln_ex.lower():
3680 raise CommandError("Entry not found for value[%s] specified for --delete-tln-ex" % tln_ex)
3683 entries.extend(update_forest_info.entries)
3685 update_forest_info.count = len(entries)
3686 update_forest_info.entries = entries
3688 for nb in enable_nb:
3690 for i in xrange(0, len(update_forest_info.entries)):
3691 r = update_forest_info.entries[i]
3692 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3694 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3699 raise CommandError("Entry not found for value[%s] specified for --enable-nb" % nb)
3700 if not update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_MASK:
3701 raise CommandError("Entry found for value[%s] specified for --enable-nb is already enabled" % nb)
3702 update_forest_info.entries[idx].time = 0
3703 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3705 for nb in disable_nb:
3707 for i in xrange(0, len(update_forest_info.entries)):
3708 r = update_forest_info.entries[i]
3709 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3711 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3716 raise CommandError("Entry not found for value[%s] specified for --delete-nb" % nb)
3717 if update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_ADMIN:
3718 raise CommandError("Entry found for value[%s] specified for --disable-nb is already disabled" % nb)
3719 update_forest_info.entries[idx].time = 0
3720 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3721 update_forest_info.entries[idx].flags |= lsa.LSA_NB_DISABLED_ADMIN
3723 for sid in enable_sid:
3725 for i in xrange(0, len(update_forest_info.entries)):
3726 r = update_forest_info.entries[i]
3727 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3729 if r.forest_trust_data.domain_sid != sid:
3734 raise CommandError("Entry not found for value[%s] specified for --enable-sid" % sid)
3735 if not update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_MASK:
3736 raise CommandError("Entry found for value[%s] specified for --enable-sid is already enabled" % nb)
3737 update_forest_info.entries[idx].time = 0
3738 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3740 for sid in disable_sid:
3742 for i in xrange(0, len(update_forest_info.entries)):
3743 r = update_forest_info.entries[i]
3744 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3746 if r.forest_trust_data.domain_sid != sid:
3751 raise CommandError("Entry not found for value[%s] specified for --delete-sid" % sid)
3752 if update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_ADMIN:
3753 raise CommandError("Entry found for value[%s] specified for --disable-sid is already disabled" % nb)
3754 update_forest_info.entries[idx].time = 0
3755 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3756 update_forest_info.entries[idx].flags |= lsa.LSA_SID_DISABLED_ADMIN
3759 update_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3760 local_tdo_info.domain_name,
3761 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3762 update_forest_info, 0)
3763 except RuntimeError as error:
3764 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3766 self.outf.write("Updated forest trust information...\n")
3767 self.write_forest_trust_info(update_forest_info,
3768 tln=local_tdo_info.domain_name.string,
3769 collisions=update_forest_collision)
3772 lsaString = lsa.String()
3773 lsaString.string = local_tdo_info.domain_name.string
3774 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3776 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3777 except RuntimeError as error:
3778 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3780 self.outf.write("Stored forest trust information...\n")
3781 self.write_forest_trust_info(stored_forest_info,
3782 tln=local_tdo_info.domain_name.string)
3785 class cmd_domain_tombstones_expunge(Command):
3786 """Expunge tombstones from the database.
3788 This command expunges tombstones from the database."""
3789 synopsis = "%prog NC [NC [...]] [options]"
3792 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3793 metavar="URL", dest="H"),
3794 Option("--current-time",
3795 help="The current time to evaluate the tombstone lifetime from, expressed as YYYY-MM-DD",
3797 Option("--tombstone-lifetime", help="Number of days a tombstone should be preserved for", type=int),
3800 takes_args = ["nc*"]
3802 takes_optiongroups = {
3803 "sambaopts": options.SambaOptions,
3804 "credopts": options.CredentialsOptions,
3805 "versionopts": options.VersionOptions,
3808 def run(self, *ncs, **kwargs):
3809 sambaopts = kwargs.get("sambaopts")
3810 credopts = kwargs.get("credopts")
3811 versionpts = kwargs.get("versionopts")
3813 current_time_string = kwargs.get("current_time")
3814 tombstone_lifetime = kwargs.get("tombstone_lifetime")
3815 lp = sambaopts.get_loadparm()
3816 creds = credopts.get_credentials(lp)
3817 samdb = SamDB(url=H, session_info=system_session(),
3818 credentials=creds, lp=lp)
3820 if current_time_string is not None:
3821 current_time_obj = time.strptime(current_time_string, "%Y-%m-%d")
3822 current_time = long(time.mktime(current_time_obj))
3825 current_time = long(time.time())
3828 res = samdb.search(expression="", base="", scope=ldb.SCOPE_BASE,
3829 attrs=["namingContexts"])
3832 for nc in res[0]["namingContexts"]:
3837 started_transaction = False
3839 samdb.transaction_start()
3840 started_transaction = True
3842 removed_links) = samdb.garbage_collect_tombstones(ncs,
3843 current_time=current_time,
3844 tombstone_lifetime=tombstone_lifetime)
3846 except Exception, err:
3847 if started_transaction:
3848 samdb.transaction_cancel()
3849 raise CommandError("Failed to expunge / garbage collect tombstones", err)
3851 samdb.transaction_commit()
3853 self.outf.write("Removed %d objects and %d links successfully\n"
3854 % (removed_objects, removed_links))
3858 class cmd_domain_trust(SuperCommand):
3859 """Domain and forest trust management."""
3862 subcommands["list"] = cmd_domain_trust_list()
3863 subcommands["show"] = cmd_domain_trust_show()
3864 subcommands["create"] = cmd_domain_trust_create()
3865 subcommands["delete"] = cmd_domain_trust_delete()
3866 subcommands["validate"] = cmd_domain_trust_validate()
3867 subcommands["namespaces"] = cmd_domain_trust_namespaces()
3869 class cmd_domain_tombstones(SuperCommand):
3870 """Domain tombstone and recycled object management."""
3873 subcommands["expunge"] = cmd_domain_tombstones_expunge()
3875 class ldif_schema_update:
3876 """Helper class for applying LDIF schema updates"""
3879 self.is_defunct = False
3880 self.unknown_oid = None
3884 def _ldap_schemaUpdateNow(self, samdb):
3888 add: schemaUpdateNow
3891 samdb.modify_ldif(ldif)
3893 def can_ignore_failure(self, error):
3894 """Checks if we can safely ignore failure to apply an LDIF update"""
3895 (num, errstr) = error.args
3897 # Microsoft has marked objects as defunct that Samba doesn't know about
3898 if num == ldb.ERR_NO_SUCH_OBJECT and self.is_defunct:
3899 print("Defunct object %s doesn't exist, skipping" % self.dn)
3901 elif self.unknown_oid is not None:
3902 print("Skipping unknown OID %s for object %s" %(self.unknown_oid, self.dn))
3907 def apply(self, samdb):
3908 """Applies a single LDIF update to the schema"""
3911 samdb.modify_ldif(self.ldif, controls=['relax:0'])
3912 except ldb.LdbError as e:
3913 if self.can_ignore_failure(e):
3916 print("Exception: %s" % e)
3917 print("Encountered while trying to apply the following LDIF")
3918 print("----------------------------------------------------")
3919 print("%s" % self.ldif)
3923 # REFRESH AFTER EVERY CHANGE
3924 # Otherwise the OID-to-attribute mapping in _apply_updates_in_file()
3925 # won't work, because it can't lookup the new OID in the schema
3926 self._ldap_schemaUpdateNow(samdb)
3930 class cmd_domain_schema_upgrade(Command):
3931 """Domain schema upgrading"""
3933 synopsis = "%prog [options]"
3935 takes_optiongroups = {
3936 "sambaopts": options.SambaOptions,
3937 "versionopts": options.VersionOptions,
3938 "credopts": options.CredentialsOptions,
3942 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3943 metavar="URL", dest="H"),
3944 Option("--quiet", help="Be quiet", action="store_true"),
3945 Option("--verbose", help="Be verbose", action="store_true"),
3946 Option("--schema", type="choice", metavar="SCHEMA",
3947 choices=["2012", "2012_R2"],
3948 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
3950 Option("--ldf-file", type=str, default=None,
3951 help="Just apply the schema updates in the adprep/.LDF file(s) specified"),
3952 Option("--base-dir", type=str, default=None,
3953 help="Location of ldf files Default is ${SETUPDIR}/adprep.")
3956 def _apply_updates_in_file(self, samdb, ldif_file):
3958 Applies a series of updates specified in an .LDIF file. The .LDIF file
3959 is based on the adprep Schema updates provided by Microsoft.
3962 ldif_op = ldif_schema_update()
3964 # parse the file line by line and work out each update operation to apply
3965 for line in ldif_file:
3967 line = line.rstrip()
3969 # the operations in the .LDIF file are separated by blank lines. If
3970 # we hit a blank line, try to apply the update we've parsed so far
3973 # keep going if we haven't parsed anything yet
3974 if ldif_op.ldif == '':
3977 # Apply the individual change
3978 count += ldif_op.apply(samdb)
3980 # start storing the next operation from scratch again
3981 ldif_op = ldif_schema_update()
3984 # replace the placeholder domain name in the .ldif file with the real domain
3985 if line.upper().endswith('DC=X'):
3986 line = line[:-len('DC=X')] + str(samdb.get_default_basedn())
3987 elif line.upper().endswith('CN=X'):
3988 line = line[:-len('CN=X')] + str(samdb.get_default_basedn())
3990 values = line.split(':')
3992 if values[0].lower() == 'dn':
3993 ldif_op.dn = values[1].strip()
3995 # replace the Windows-specific operation with the Samba one
3996 if values[0].lower() == 'changetype':
3997 line = line.lower().replace(': ntdsschemaadd',
3999 line = line.lower().replace(': ntdsschemamodify',
4002 if values[0].lower() in ['rdnattid', 'subclassof',
4003 'systemposssuperiors',
4005 'systemauxiliaryclass']:
4008 # The Microsoft updates contain some OIDs we don't recognize.
4009 # Query the DB to see if we can work out the OID this update is
4010 # referring to. If we find a match, then replace the OID with
4011 # the ldapDisplayname
4013 res = samdb.search(base=samdb.get_schema_basedn(),
4014 expression="(|(attributeId=%s)(governsId=%s))" %
4016 attrs=['ldapDisplayName'])
4019 ldif_op.unknown_oid = value
4021 display_name = res[0]['ldapDisplayName'][0]
4022 line = line.replace(value, ' ' + display_name)
4024 # Microsoft has marked objects as defunct that Samba doesn't know about
4025 if values[0].lower() == 'isdefunct' and values[1].strip().lower() == 'true':
4026 ldif_op.is_defunct = True
4028 # Samba has added the showInAdvancedViewOnly attribute to all objects,
4029 # so rather than doing an add, we need to do a replace
4030 if values[0].lower() == 'add' and values[1].strip().lower() == 'showinadvancedviewonly':
4031 line = 'replace: showInAdvancedViewOnly'
4033 # Add the line to the current LDIF operation (including the newline
4034 # we stripped off at the start of the loop)
4035 ldif_op.ldif += line + '\n'
4040 def _apply_update(self, samdb, update_file, base_dir):
4041 """Wrapper function for parsing an LDIF file and applying the updates"""
4043 print("Applying %s updates..." % update_file)
4047 ldif_file = open(os.path.join(base_dir, update_file))
4049 count = self._apply_updates_in_file(samdb, ldif_file)
4055 print("%u changes applied" % count)
4059 def run(self, **kwargs):
4060 from samba.ms_schema_markdown import read_ms_markdown
4061 from samba.schema import Schema
4063 updates_allowed_overriden = False
4064 sambaopts = kwargs.get("sambaopts")
4065 credopts = kwargs.get("credopts")
4066 versionpts = kwargs.get("versionopts")
4067 lp = sambaopts.get_loadparm()
4068 creds = credopts.get_credentials(lp)
4070 target_schema = kwargs.get("schema")
4071 ldf_files = kwargs.get("ldf_file")
4072 base_dir = kwargs.get("base_dir")
4076 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4078 # we're not going to get far if the config doesn't allow schema updates
4079 if lp.get("dsdb:schema update allowed") is None:
4080 lp.set("dsdb:schema update allowed", "yes")
4081 print("Temporarily overriding 'dsdb:schema update allowed' setting")
4082 updates_allowed_overriden = True
4084 # if specific LDIF files were specified, just apply them
4086 schema_updates = ldf_files.split(",")
4090 # work out the version of the target schema we're upgrading to
4091 end = Schema.get_version(target_schema)
4093 # work out the version of the schema we're currently using
4094 res = samdb.search(base=samdb.get_schema_basedn(),
4095 scope=ldb.SCOPE_BASE, attrs=['objectVersion'])
4098 raise CommandError('Could not determine current schema version')
4099 start = int(res[0]['objectVersion'][0]) + 1
4101 diff_dir = setup_path("adprep/WindowsServerDocs")
4102 if base_dir is None:
4103 # Read from the Schema-Updates.md file
4104 temp_folder = tempfile.mkdtemp()
4106 update_file = setup_path("adprep/WindowsServerDocs/Schema-Updates.md")
4109 read_ms_markdown(update_file, temp_folder)
4110 except Exception as e:
4111 print("Exception in markdown parsing: %s" % e)
4112 shutil.rmtree(temp_folder)
4113 raise CommandError('Failed to upgrade schema')
4115 base_dir = temp_folder
4117 for version in range(start, end + 1):
4118 update = 'Sch%d.ldf' % version
4119 schema_updates.append(update)
4121 # Apply patches if we parsed the Schema-Updates.md file
4122 diff = os.path.abspath(os.path.join(diff_dir, update + '.diff'))
4123 if temp_folder and os.path.exists(diff):
4124 p = subprocess.Popen(['patch', update, '-i', diff],
4125 stdout=subprocess.PIPE,
4126 stderr=subprocess.PIPE, cwd=temp_folder)
4127 stdout, stderr = p.communicate()
4130 print("Exception in patch: %s\n%s" % (stdout, stderr))
4131 shutil.rmtree(temp_folder)
4132 raise CommandError('Failed to upgrade schema')
4134 print("Patched %s using %s" % (update, diff))
4136 if base_dir is None:
4137 base_dir = setup_path("adprep")
4139 samdb.transaction_start()
4141 error_encountered = False
4144 # Apply the schema updates needed to move to the new schema version
4145 for ldif_file in schema_updates:
4146 count += self._apply_update(samdb, ldif_file, base_dir)
4149 samdb.transaction_commit()
4150 print("Schema successfully updated")
4152 print("No changes applied to schema")
4153 samdb.transaction_cancel()
4154 except Exception as e:
4155 print("Exception: %s" % e)
4156 print("Error encountered, aborting schema upgrade")
4157 samdb.transaction_cancel()
4158 error_encountered = True
4160 if updates_allowed_overriden:
4161 lp.set("dsdb:schema update allowed", "no")
4164 shutil.rmtree(temp_folder)
4166 if error_encountered:
4167 raise CommandError('Failed to upgrade schema')
4169 class cmd_domain(SuperCommand):
4170 """Domain management."""
4173 subcommands["demote"] = cmd_domain_demote()
4174 if cmd_domain_export_keytab is not None:
4175 subcommands["exportkeytab"] = cmd_domain_export_keytab()
4176 subcommands["info"] = cmd_domain_info()
4177 subcommands["provision"] = cmd_domain_provision()
4178 subcommands["join"] = cmd_domain_join()
4179 subcommands["dcpromo"] = cmd_domain_dcpromo()
4180 subcommands["level"] = cmd_domain_level()
4181 subcommands["passwordsettings"] = cmd_domain_passwordsettings()
4182 subcommands["classicupgrade"] = cmd_domain_classicupgrade()
4183 subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()
4184 subcommands["trust"] = cmd_domain_trust()
4185 subcommands["tombstones"] = cmd_domain_tombstones()
4186 subcommands["schemaupgrade"] = cmd_domain_schema_upgrade()