dbcheck: Add explict tests for unknown and unsorted attributeID values
[Samba.git] / python / samba / netcmd / domain.py
blobf0710f281f035bad586cb1ce79b779afc9fec1b4
1 # domain management
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
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
26 import ldb
27 import string
28 import os
29 import sys
30 import ctypes
31 import random
32 import tempfile
33 import logging
34 from getpass import getpass
35 from samba.net import Net, LIBNET_JOIN_AUTOMATIC
36 import samba.ntacls
37 from samba.join import join_RODC, join_DC, join_subdomain
38 from samba.auth import system_session
39 from samba.samdb import SamDB
40 from samba.ndr import ndr_unpack, ndr_pack, ndr_print
41 from samba.dcerpc import drsuapi
42 from samba.dcerpc import drsblobs
43 from samba.dcerpc import lsa
44 from samba.dcerpc import netlogon
45 from samba.dcerpc import security
46 from samba.dcerpc import nbt
47 from samba.dcerpc.samr import DOMAIN_PASSWORD_COMPLEX, DOMAIN_PASSWORD_STORE_CLEARTEXT
48 from samba.netcmd import (
49 Command,
50 CommandError,
51 SuperCommand,
52 Option
54 from samba.netcmd.common import netcmd_get_domain_infos_via_cldap
55 from samba.samba3 import Samba3
56 from samba.samba3 import param as s3param
57 from samba.upgrade import upgrade_from_samba3
58 from samba.drs_utils import (
59 sendDsReplicaSync, drsuapi_connect, drsException,
60 sendRemoveDsServer)
63 from samba.dsdb import (
64 DS_DOMAIN_FUNCTION_2000,
65 DS_DOMAIN_FUNCTION_2003,
66 DS_DOMAIN_FUNCTION_2003_MIXED,
67 DS_DOMAIN_FUNCTION_2008,
68 DS_DOMAIN_FUNCTION_2008_R2,
69 DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL,
70 DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL,
71 UF_WORKSTATION_TRUST_ACCOUNT,
72 UF_SERVER_TRUST_ACCOUNT,
73 UF_TRUSTED_FOR_DELEGATION
76 from samba.provision import (
77 provision,
78 ProvisioningError
81 from samba.provision.common import (
82 FILL_FULL,
83 FILL_NT4SYNC,
84 FILL_DRS
87 def get_testparm_var(testparm, smbconf, varname):
88 cmd = "%s -s -l --parameter-name='%s' %s 2>/dev/null" % (testparm, varname, smbconf)
89 output = os.popen(cmd, 'r').readline()
90 return output.strip()
92 try:
93 import samba.dckeytab
94 except ImportError:
95 cmd_domain_export_keytab = None
96 else:
97 class cmd_domain_export_keytab(Command):
98 """Dump Kerberos keys of the domain into a keytab."""
100 synopsis = "%prog <keytab> [options]"
102 takes_optiongroups = {
103 "sambaopts": options.SambaOptions,
104 "credopts": options.CredentialsOptions,
105 "versionopts": options.VersionOptions,
108 takes_options = [
109 Option("--principal", help="extract only this principal", type=str),
112 takes_args = ["keytab"]
114 def run(self, keytab, credopts=None, sambaopts=None, versionopts=None, principal=None):
115 lp = sambaopts.get_loadparm()
116 net = Net(None, lp)
117 net.export_keytab(keytab=keytab, principal=principal)
120 class cmd_domain_info(Command):
121 """Print basic info about a domain and the DC passed as parameter."""
123 synopsis = "%prog <ip_address> [options]"
125 takes_options = [
128 takes_optiongroups = {
129 "sambaopts": options.SambaOptions,
130 "credopts": options.CredentialsOptions,
131 "versionopts": options.VersionOptions,
134 takes_args = ["address"]
136 def run(self, address, credopts=None, sambaopts=None, versionopts=None):
137 lp = sambaopts.get_loadparm()
138 try:
139 res = netcmd_get_domain_infos_via_cldap(lp, None, address)
140 except RuntimeError:
141 raise CommandError("Invalid IP address '" + address + "'!")
142 self.outf.write("Forest : %s\n" % res.forest)
143 self.outf.write("Domain : %s\n" % res.dns_domain)
144 self.outf.write("Netbios domain : %s\n" % res.domain_name)
145 self.outf.write("DC name : %s\n" % res.pdc_dns_name)
146 self.outf.write("DC netbios name : %s\n" % res.pdc_name)
147 self.outf.write("Server site : %s\n" % res.server_site)
148 self.outf.write("Client site : %s\n" % res.client_site)
151 class cmd_domain_provision(Command):
152 """Provision a domain."""
154 synopsis = "%prog [options]"
156 takes_optiongroups = {
157 "sambaopts": options.SambaOptions,
158 "versionopts": options.VersionOptions,
161 takes_options = [
162 Option("--interactive", help="Ask for names", action="store_true"),
163 Option("--domain", type="string", metavar="DOMAIN",
164 help="set domain"),
165 Option("--domain-guid", type="string", metavar="GUID",
166 help="set domainguid (otherwise random)"),
167 Option("--domain-sid", type="string", metavar="SID",
168 help="set domainsid (otherwise random)"),
169 Option("--ntds-guid", type="string", metavar="GUID",
170 help="set NTDS object GUID (otherwise random)"),
171 Option("--invocationid", type="string", metavar="GUID",
172 help="set invocationid (otherwise random)"),
173 Option("--host-name", type="string", metavar="HOSTNAME",
174 help="set hostname"),
175 Option("--host-ip", type="string", metavar="IPADDRESS",
176 help="set IPv4 ipaddress"),
177 Option("--host-ip6", type="string", metavar="IP6ADDRESS",
178 help="set IPv6 ipaddress"),
179 Option("--site", type="string", metavar="SITENAME",
180 help="set site name"),
181 Option("--adminpass", type="string", metavar="PASSWORD",
182 help="choose admin password (otherwise random)"),
183 Option("--krbtgtpass", type="string", metavar="PASSWORD",
184 help="choose krbtgt password (otherwise random)"),
185 Option("--machinepass", type="string", metavar="PASSWORD",
186 help="choose machine password (otherwise random)"),
187 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
188 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
189 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
190 "BIND9_FLATFILE uses bind9 text database to store zone information, "
191 "BIND9_DLZ uses samba4 AD to store zone information, "
192 "NONE skips the DNS setup entirely (not recommended)",
193 default="SAMBA_INTERNAL"),
194 Option("--dnspass", type="string", metavar="PASSWORD",
195 help="choose dns password (otherwise random)"),
196 Option("--ldapadminpass", type="string", metavar="PASSWORD",
197 help="choose password to set between Samba and it's LDAP backend (otherwise random)"),
198 Option("--root", type="string", metavar="USERNAME",
199 help="choose 'root' unix username"),
200 Option("--nobody", type="string", metavar="USERNAME",
201 help="choose 'nobody' user"),
202 Option("--users", type="string", metavar="GROUPNAME",
203 help="choose 'users' group"),
204 Option("--quiet", help="Be quiet", action="store_true"),
205 Option("--blank", action="store_true",
206 help="do not add users or groups, just the structure"),
207 Option("--ldap-backend-type", type="choice", metavar="LDAP-BACKEND-TYPE",
208 help="Test initialisation support for unsupported LDAP backend type (fedora-ds or openldap) DO NOT USE",
209 choices=["fedora-ds", "openldap"]),
210 Option("--server-role", type="choice", metavar="ROLE",
211 choices=["domain controller", "dc", "member server", "member", "standalone"],
212 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
213 default="domain controller"),
214 Option("--function-level", type="choice", metavar="FOR-FUN-LEVEL",
215 choices=["2000", "2003", "2008", "2008_R2"],
216 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2008_R2 Native.",
217 default="2008_R2"),
218 Option("--next-rid", type="int", metavar="NEXTRID", default=1000,
219 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
220 Option("--partitions-only",
221 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"),
222 Option("--targetdir", type="string", metavar="DIR",
223 help="Set target directory"),
224 Option("--ol-mmr-urls", type="string", metavar="LDAPSERVER",
225 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\""),
226 Option("--use-xattrs", type="choice", choices=["yes", "no", "auto"], help="Define if we should use the native fs capabilities or a tdb file for storing attributes likes ntacl, auto tries to make an inteligent guess based on the user rights and system capabilities", default="auto"),
227 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
228 Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
231 openldap_options = [
232 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",
233 action="store_true"),
234 Option("--slapd-path", type="string", metavar="SLAPD-PATH",
235 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."),
236 Option("--ldap-backend-extra-port", type="int", metavar="LDAP-BACKEND-EXTRA-PORT", help="Additional TCP port for LDAP backend server (to use for replication)"),
237 Option("--ldap-backend-forced-uri", type="string", metavar="LDAP-BACKEND-FORCED-URI",
238 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"),
239 Option("--ldap-backend-nosync", help="Configure LDAP backend not to call fsync() (for performance in test environments)", action="store_true"),
242 if os.getenv('TEST_LDAP', "no") == "yes":
243 takes_options.extend(openldap_options)
245 takes_args = []
247 def run(self, sambaopts=None, versionopts=None,
248 interactive=None,
249 domain=None,
250 domain_guid=None,
251 domain_sid=None,
252 ntds_guid=None,
253 invocationid=None,
254 host_name=None,
255 host_ip=None,
256 host_ip6=None,
257 adminpass=None,
258 site=None,
259 krbtgtpass=None,
260 machinepass=None,
261 dns_backend=None,
262 dns_forwarder=None,
263 dnspass=None,
264 ldapadminpass=None,
265 root=None,
266 nobody=None,
267 users=None,
268 quiet=None,
269 blank=None,
270 ldap_backend_type=None,
271 server_role=None,
272 function_level=None,
273 next_rid=None,
274 partitions_only=None,
275 targetdir=None,
276 ol_mmr_urls=None,
277 use_xattrs=None,
278 slapd_path=None,
279 use_ntvfs=None,
280 use_rfc2307=None,
281 ldap_backend_nosync=None,
282 ldap_backend_extra_port=None,
283 ldap_backend_forced_uri=None,
284 ldap_dryrun_mode=None):
286 self.logger = self.get_logger("provision")
287 if quiet:
288 self.logger.setLevel(logging.WARNING)
289 else:
290 self.logger.setLevel(logging.INFO)
292 lp = sambaopts.get_loadparm()
293 smbconf = lp.configfile
295 if dns_forwarder is not None:
296 suggested_forwarder = dns_forwarder
297 else:
298 suggested_forwarder = self._get_nameserver_ip()
299 if suggested_forwarder is None:
300 suggested_forwarder = "none"
302 if len(self.raw_argv) == 1:
303 interactive = True
305 if interactive:
306 from getpass import getpass
307 import socket
309 def ask(prompt, default=None):
310 if default is not None:
311 print "%s [%s]: " % (prompt, default),
312 else:
313 print "%s: " % (prompt,),
314 return sys.stdin.readline().rstrip("\n") or default
316 try:
317 default = socket.getfqdn().split(".", 1)[1].upper()
318 except IndexError:
319 default = None
320 realm = ask("Realm", default)
321 if realm in (None, ""):
322 raise CommandError("No realm set!")
324 try:
325 default = realm.split(".")[0]
326 except IndexError:
327 default = None
328 domain = ask("Domain", default)
329 if domain is None:
330 raise CommandError("No domain set!")
332 server_role = ask("Server Role (dc, member, standalone)", "dc")
334 dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
335 if dns_backend in (None, ''):
336 raise CommandError("No DNS backend set!")
338 if dns_backend == "SAMBA_INTERNAL":
339 dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
340 if dns_forwarder.lower() in (None, 'none'):
341 suggested_forwarder = None
342 dns_forwarder = None
344 while True:
345 adminpassplain = getpass("Administrator password: ")
346 if not adminpassplain:
347 self.errf.write("Invalid administrator password.\n")
348 else:
349 adminpassverify = getpass("Retype password: ")
350 if not adminpassplain == adminpassverify:
351 self.errf.write("Sorry, passwords do not match.\n")
352 else:
353 adminpass = adminpassplain
354 break
356 else:
357 realm = sambaopts._lp.get('realm')
358 if realm is None:
359 raise CommandError("No realm set!")
360 if domain is None:
361 raise CommandError("No domain set!")
363 if not adminpass:
364 self.logger.info("Administrator password will be set randomly!")
366 if function_level == "2000":
367 dom_for_fun_level = DS_DOMAIN_FUNCTION_2000
368 elif function_level == "2003":
369 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
370 elif function_level == "2008":
371 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
372 elif function_level == "2008_R2":
373 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
375 if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
376 dns_forwarder = suggested_forwarder
378 samdb_fill = FILL_FULL
379 if blank:
380 samdb_fill = FILL_NT4SYNC
381 elif partitions_only:
382 samdb_fill = FILL_DRS
384 if targetdir is not None:
385 if not os.path.isdir(targetdir):
386 os.mkdir(targetdir)
388 eadb = True
390 if use_xattrs == "yes":
391 eadb = False
392 elif use_xattrs == "auto" and not lp.get("posix:eadb"):
393 if targetdir:
394 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
395 else:
396 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
397 try:
398 try:
399 samba.ntacls.setntacl(lp, file.name,
400 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
401 eadb = False
402 except Exception:
403 self.logger.info("You are not root or your system do not support xattr, using tdb backend for attributes. ")
404 finally:
405 file.close()
407 if eadb:
408 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.")
409 if ldap_backend_type == "existing":
410 if ldap_backend_forced_uri is not None:
411 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)
412 else:
413 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")
414 else:
415 if ldap_backend_forced_uri is not None:
416 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")
418 if domain_sid is not None:
419 domain_sid = security.dom_sid(domain_sid)
421 session = system_session()
422 try:
423 result = provision(self.logger,
424 session, smbconf=smbconf, targetdir=targetdir,
425 samdb_fill=samdb_fill, realm=realm, domain=domain,
426 domainguid=domain_guid, domainsid=domain_sid,
427 hostname=host_name,
428 hostip=host_ip, hostip6=host_ip6,
429 sitename=site, ntdsguid=ntds_guid,
430 invocationid=invocationid, adminpass=adminpass,
431 krbtgtpass=krbtgtpass, machinepass=machinepass,
432 dns_backend=dns_backend, dns_forwarder=dns_forwarder,
433 dnspass=dnspass, root=root, nobody=nobody,
434 users=users,
435 serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
436 backend_type=ldap_backend_type,
437 ldapadminpass=ldapadminpass, ol_mmr_urls=ol_mmr_urls, slapd_path=slapd_path,
438 useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
439 use_rfc2307=use_rfc2307, skip_sysvolacl=False,
440 ldap_backend_extra_port=ldap_backend_extra_port,
441 ldap_backend_forced_uri=ldap_backend_forced_uri,
442 nosync=ldap_backend_nosync, ldap_dryrun_mode=ldap_dryrun_mode)
444 except ProvisioningError, e:
445 raise CommandError("Provision failed", e)
447 result.report_logger(self.logger)
449 def _get_nameserver_ip(self):
450 """Grab the nameserver IP address from /etc/resolv.conf."""
451 from os import path
452 RESOLV_CONF="/etc/resolv.conf"
454 if not path.isfile(RESOLV_CONF):
455 self.logger.warning("Failed to locate %s" % RESOLV_CONF)
456 return None
458 handle = None
459 try:
460 handle = open(RESOLV_CONF, 'r')
461 for line in handle:
462 if not line.startswith('nameserver'):
463 continue
464 # we want the last non-space continuous string of the line
465 return line.strip().split()[-1]
466 finally:
467 if handle is not None:
468 handle.close()
470 self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
473 class cmd_domain_dcpromo(Command):
474 """Promote an existing domain member or NT4 PDC to an AD DC."""
476 synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
478 takes_optiongroups = {
479 "sambaopts": options.SambaOptions,
480 "versionopts": options.VersionOptions,
481 "credopts": options.CredentialsOptions,
484 takes_options = [
485 Option("--server", help="DC to join", type=str),
486 Option("--site", help="site to join", type=str),
487 Option("--targetdir", help="where to store provision", type=str),
488 Option("--domain-critical-only",
489 help="only replicate critical domain objects",
490 action="store_true"),
491 Option("--machinepass", type=str, metavar="PASSWORD",
492 help="choose machine password (otherwise random)"),
493 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
494 action="store_true"),
495 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
496 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
497 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
498 "BIND9_DLZ uses samba4 AD to store zone information, "
499 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
500 default="SAMBA_INTERNAL"),
501 Option("--quiet", help="Be quiet", action="store_true"),
502 Option("--verbose", help="Be verbose", action="store_true")
505 takes_args = ["domain", "role?"]
507 def run(self, domain, role=None, sambaopts=None, credopts=None,
508 versionopts=None, server=None, site=None, targetdir=None,
509 domain_critical_only=False, parent_domain=None, machinepass=None,
510 use_ntvfs=False, dns_backend=None,
511 quiet=False, verbose=False):
512 lp = sambaopts.get_loadparm()
513 creds = credopts.get_credentials(lp)
514 net = Net(creds, lp, server=credopts.ipaddress)
516 if site is None:
517 site = "Default-First-Site-Name"
519 logger = self.get_logger()
520 if verbose:
521 logger.setLevel(logging.DEBUG)
522 elif quiet:
523 logger.setLevel(logging.WARNING)
524 else:
525 logger.setLevel(logging.INFO)
527 netbios_name = lp.get("netbios name")
529 if not role is None:
530 role = role.upper()
532 if role == "DC":
533 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
534 site=site, netbios_name=netbios_name, targetdir=targetdir,
535 domain_critical_only=domain_critical_only,
536 machinepass=machinepass, use_ntvfs=use_ntvfs,
537 dns_backend=dns_backend,
538 promote_existing=True)
539 elif role == "RODC":
540 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
541 site=site, netbios_name=netbios_name, targetdir=targetdir,
542 domain_critical_only=domain_critical_only,
543 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
544 promote_existing=True)
545 else:
546 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
549 class cmd_domain_join(Command):
550 """Join domain as either member or backup domain controller."""
552 synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
554 takes_optiongroups = {
555 "sambaopts": options.SambaOptions,
556 "versionopts": options.VersionOptions,
557 "credopts": options.CredentialsOptions,
560 takes_options = [
561 Option("--server", help="DC to join", type=str),
562 Option("--site", help="site to join", type=str),
563 Option("--targetdir", help="where to store provision", type=str),
564 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
565 Option("--domain-critical-only",
566 help="only replicate critical domain objects",
567 action="store_true"),
568 Option("--machinepass", type=str, metavar="PASSWORD",
569 help="choose machine password (otherwise random)"),
570 Option("--adminpass", type="string", metavar="PASSWORD",
571 help="choose adminstrator password when joining as a subdomain (otherwise random)"),
572 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
573 action="store_true"),
574 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
575 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
576 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
577 "BIND9_DLZ uses samba4 AD to store zone information, "
578 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
579 default="SAMBA_INTERNAL"),
580 Option("--quiet", help="Be quiet", action="store_true"),
581 Option("--verbose", help="Be verbose", action="store_true")
584 takes_args = ["domain", "role?"]
586 def run(self, domain, role=None, sambaopts=None, credopts=None,
587 versionopts=None, server=None, site=None, targetdir=None,
588 domain_critical_only=False, parent_domain=None, machinepass=None,
589 use_ntvfs=False, dns_backend=None, adminpass=None,
590 quiet=False, verbose=False):
591 lp = sambaopts.get_loadparm()
592 creds = credopts.get_credentials(lp)
593 net = Net(creds, lp, server=credopts.ipaddress)
595 if site is None:
596 site = "Default-First-Site-Name"
598 logger = self.get_logger()
599 if verbose:
600 logger.setLevel(logging.DEBUG)
601 elif quiet:
602 logger.setLevel(logging.WARNING)
603 else:
604 logger.setLevel(logging.INFO)
606 netbios_name = lp.get("netbios name")
608 if not role is None:
609 role = role.upper()
611 if role is None or role == "MEMBER":
612 (join_password, sid, domain_name) = net.join_member(
613 domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
614 machinepass=machinepass)
616 self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
617 elif role == "DC":
618 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
619 site=site, netbios_name=netbios_name, targetdir=targetdir,
620 domain_critical_only=domain_critical_only,
621 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend)
622 elif role == "RODC":
623 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
624 site=site, netbios_name=netbios_name, targetdir=targetdir,
625 domain_critical_only=domain_critical_only,
626 machinepass=machinepass, use_ntvfs=use_ntvfs,
627 dns_backend=dns_backend)
628 elif role == "SUBDOMAIN":
629 if not adminpass:
630 logger.info("Administrator password will be set randomly!")
632 netbios_domain = lp.get("workgroup")
633 if parent_domain is None:
634 parent_domain = ".".join(domain.split(".")[1:])
635 join_subdomain(logger=logger, server=server, creds=creds, lp=lp, dnsdomain=domain,
636 parent_domain=parent_domain, site=site,
637 netbios_name=netbios_name, netbios_domain=netbios_domain,
638 targetdir=targetdir, machinepass=machinepass,
639 use_ntvfs=use_ntvfs, dns_backend=dns_backend,
640 adminpass=adminpass)
641 else:
642 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role)
645 class cmd_domain_demote(Command):
646 """Demote ourselves from the role of Domain Controller."""
648 synopsis = "%prog [options]"
650 takes_options = [
651 Option("--server", help="DC to force replication before demote", type=str),
652 Option("--targetdir", help="where provision is stored", type=str),
655 takes_optiongroups = {
656 "sambaopts": options.SambaOptions,
657 "credopts": options.CredentialsOptions,
658 "versionopts": options.VersionOptions,
661 def run(self, sambaopts=None, credopts=None,
662 versionopts=None, server=None, targetdir=None):
663 lp = sambaopts.get_loadparm()
664 creds = credopts.get_credentials(lp)
665 net = Net(creds, lp, server=credopts.ipaddress)
667 netbios_name = lp.get("netbios name")
668 samdb = SamDB(session_info=system_session(), credentials=creds, lp=lp)
669 if not server:
670 res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
671 if (len(res) == 0):
672 raise CommandError("Unable to search for servers")
674 if (len(res) == 1):
675 raise CommandError("You are the latest server in the domain")
677 server = None
678 for e in res:
679 if str(e["name"]).lower() != netbios_name.lower():
680 server = e["dnsHostName"]
681 break
683 ntds_guid = samdb.get_ntds_GUID()
684 msg = samdb.search(base=str(samdb.get_config_basedn()),
685 scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
686 attrs=['options'])
687 if len(msg) == 0 or "options" not in msg[0]:
688 raise CommandError("Failed to find options on %s" % ntds_guid)
690 ntds_dn = msg[0].dn
691 dsa_options = int(str(msg[0]['options']))
693 res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
694 controls=["search_options:1:2"])
696 if len(res) != 0:
697 raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res))
699 self.errf.write("Using %s as partner server for the demotion\n" %
700 server)
701 (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
703 self.errf.write("Deactivating inbound replication\n")
705 nmsg = ldb.Message()
706 nmsg.dn = msg[0].dn
708 dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
709 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
710 samdb.modify(nmsg)
712 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
714 self.errf.write("Asking partner server %s to synchronize from us\n"
715 % server)
716 for part in (samdb.get_schema_basedn(),
717 samdb.get_config_basedn(),
718 samdb.get_root_basedn()):
719 try:
720 sendDsReplicaSync(drsuapiBind, drsuapi_handle, ntds_guid, str(part), drsuapi.DRSUAPI_DRS_WRIT_REP)
721 except drsException, e:
722 self.errf.write(
723 "Error while demoting, "
724 "re-enabling inbound replication\n")
725 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
726 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
727 samdb.modify(nmsg)
728 raise CommandError("Error while sending a DsReplicaSync for partion %s" % str(part), e)
729 try:
730 remote_samdb = SamDB(url="ldap://%s" % server,
731 session_info=system_session(),
732 credentials=creds, lp=lp)
734 self.errf.write("Changing userControl and container\n")
735 res = remote_samdb.search(base=str(remote_samdb.get_root_basedn()),
736 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
737 netbios_name.upper(),
738 attrs=["userAccountControl"])
739 dc_dn = res[0].dn
740 uac = int(str(res[0]["userAccountControl"]))
742 except Exception, e:
743 self.errf.write(
744 "Error while demoting, re-enabling inbound replication\n")
745 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
746 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
747 samdb.modify(nmsg)
748 raise CommandError("Error while changing account control", e)
750 if (len(res) != 1):
751 self.errf.write(
752 "Error while demoting, re-enabling inbound replication")
753 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
754 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
755 samdb.modify(nmsg)
756 raise CommandError("Unable to find object with samaccountName = %s$"
757 " in the remote dc" % netbios_name.upper())
759 olduac = uac
761 uac ^= (UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION)
762 uac |= UF_WORKSTATION_TRUST_ACCOUNT
764 msg = ldb.Message()
765 msg.dn = dc_dn
767 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
768 ldb.FLAG_MOD_REPLACE,
769 "userAccountControl")
770 try:
771 remote_samdb.modify(msg)
772 except Exception, e:
773 self.errf.write(
774 "Error while demoting, re-enabling inbound replication")
775 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
776 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
777 samdb.modify(nmsg)
779 raise CommandError("Error while changing account control", e)
781 parent = msg.dn.parent()
782 rdn = str(res[0].dn)
783 rdn = string.replace(rdn, ",%s" % str(parent), "")
784 # Let's move to the Computer container
785 i = 0
786 newrdn = rdn
788 computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.get_root_basedn()))
789 res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
791 if (len(res) != 0):
792 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
793 scope=ldb.SCOPE_ONELEVEL)
794 while(len(res) != 0 and i < 100):
795 i = i + 1
796 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
797 scope=ldb.SCOPE_ONELEVEL)
799 if i == 100:
800 self.errf.write(
801 "Error while demoting, re-enabling inbound replication\n")
802 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
803 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
804 samdb.modify(nmsg)
806 msg = ldb.Message()
807 msg.dn = dc_dn
809 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
810 ldb.FLAG_MOD_REPLACE,
811 "userAccountControl")
813 remote_samdb.modify(msg)
815 raise CommandError("Unable to find a slot for renaming %s,"
816 " all names from %s-1 to %s-%d seemed used" %
817 (str(dc_dn), rdn, rdn, i - 9))
819 newrdn = "%s-%d" % (rdn, i)
821 try:
822 newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
823 remote_samdb.rename(dc_dn, newdn)
824 except Exception, e:
825 self.errf.write(
826 "Error while demoting, re-enabling inbound replication\n")
827 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
828 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
829 samdb.modify(nmsg)
831 msg = ldb.Message()
832 msg.dn = dc_dn
834 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
835 ldb.FLAG_MOD_REPLACE,
836 "userAccountControl")
838 remote_samdb.modify(msg)
839 raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
842 server_dsa_dn = samdb.get_serverName()
843 domain = remote_samdb.get_root_basedn()
845 try:
846 sendRemoveDsServer(drsuapiBind, drsuapi_handle, server_dsa_dn, domain)
847 except drsException, e:
848 self.errf.write(
849 "Error while demoting, re-enabling inbound replication\n")
850 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
851 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
852 samdb.modify(nmsg)
854 msg = ldb.Message()
855 msg.dn = newdn
857 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
858 ldb.FLAG_MOD_REPLACE,
859 "userAccountControl")
860 print str(dc_dn)
861 remote_samdb.modify(msg)
862 remote_samdb.rename(newdn, dc_dn)
863 raise CommandError("Error while sending a removeDsServer", e)
865 for s in ("CN=Enterprise,CN=Microsoft System Volumes,CN=System,CN=Configuration",
866 "CN=%s,CN=Microsoft System Volumes,CN=System,CN=Configuration" % lp.get("realm"),
867 "CN=Domain System Volumes (SYSVOL share),CN=File Replication Service,CN=System"):
868 try:
869 remote_samdb.delete(ldb.Dn(remote_samdb,
870 "%s,%s,%s" % (str(rdn), s, str(remote_samdb.get_root_basedn()))))
871 except ldb.LdbError, l:
872 pass
874 for s in ("CN=Enterprise,CN=NTFRS Subscriptions",
875 "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
876 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
877 "CN=NTFRS Subscriptions"):
878 try:
879 remote_samdb.delete(ldb.Dn(remote_samdb,
880 "%s,%s" % (s, str(newdn))))
881 except ldb.LdbError, l:
882 pass
884 self.errf.write("Demote successful\n")
887 class cmd_domain_level(Command):
888 """Raise domain and forest function levels."""
890 synopsis = "%prog (show|raise <options>) [options]"
892 takes_optiongroups = {
893 "sambaopts": options.SambaOptions,
894 "credopts": options.CredentialsOptions,
895 "versionopts": options.VersionOptions,
898 takes_options = [
899 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
900 metavar="URL", dest="H"),
901 Option("--quiet", help="Be quiet", action="store_true"),
902 Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2"],
903 help="The forest function level (2003 | 2008 | 2008_R2)"),
904 Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2"],
905 help="The domain function level (2003 | 2008 | 2008_R2)")
908 takes_args = ["subcommand"]
910 def run(self, subcommand, H=None, forest_level=None, domain_level=None,
911 quiet=False, credopts=None, sambaopts=None, versionopts=None):
912 lp = sambaopts.get_loadparm()
913 creds = credopts.get_credentials(lp, fallback_machine=True)
915 samdb = SamDB(url=H, session_info=system_session(),
916 credentials=creds, lp=lp)
918 domain_dn = samdb.domain_dn()
920 res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
921 scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
922 assert len(res_forest) == 1
924 res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
925 attrs=["msDS-Behavior-Version", "nTMixedDomain"])
926 assert len(res_domain) == 1
928 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
929 scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
930 attrs=["msDS-Behavior-Version"])
931 assert len(res_dc_s) >= 1
933 try:
934 level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
935 level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
936 level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
938 min_level_dc = int(res_dc_s[0]["msDS-Behavior-Version"][0]) # Init value
939 for msg in res_dc_s:
940 if int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
941 min_level_dc = int(msg["msDS-Behavior-Version"][0])
943 if level_forest < 0 or level_domain < 0:
944 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
945 if min_level_dc < 0:
946 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
947 if level_forest > level_domain:
948 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
949 if level_domain > min_level_dc:
950 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
952 except KeyError:
953 raise CommandError("Could not retrieve the actual domain, forest level and/or lowest DC function level!")
955 if subcommand == "show":
956 self.message("Domain and forest function level for domain '%s'" % domain_dn)
957 if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
958 self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
959 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
960 self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
961 if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
962 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)!")
964 self.message("")
966 if level_forest == DS_DOMAIN_FUNCTION_2000:
967 outstr = "2000"
968 elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
969 outstr = "2003 with mixed domains/interim (NT4 DC support)"
970 elif level_forest == DS_DOMAIN_FUNCTION_2003:
971 outstr = "2003"
972 elif level_forest == DS_DOMAIN_FUNCTION_2008:
973 outstr = "2008"
974 elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
975 outstr = "2008 R2"
976 else:
977 outstr = "higher than 2008 R2"
978 self.message("Forest function level: (Windows) " + outstr)
980 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
981 outstr = "2000 mixed (NT4 DC support)"
982 elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
983 outstr = "2000"
984 elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
985 outstr = "2003 with mixed domains/interim (NT4 DC support)"
986 elif level_domain == DS_DOMAIN_FUNCTION_2003:
987 outstr = "2003"
988 elif level_domain == DS_DOMAIN_FUNCTION_2008:
989 outstr = "2008"
990 elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
991 outstr = "2008 R2"
992 else:
993 outstr = "higher than 2008 R2"
994 self.message("Domain function level: (Windows) " + outstr)
996 if min_level_dc == DS_DOMAIN_FUNCTION_2000:
997 outstr = "2000"
998 elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
999 outstr = "2003"
1000 elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
1001 outstr = "2008"
1002 elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
1003 outstr = "2008 R2"
1004 else:
1005 outstr = "higher than 2008 R2"
1006 self.message("Lowest function level of a DC: (Windows) " + outstr)
1008 elif subcommand == "raise":
1009 msgs = []
1011 if domain_level is not None:
1012 if domain_level == "2003":
1013 new_level_domain = DS_DOMAIN_FUNCTION_2003
1014 elif domain_level == "2008":
1015 new_level_domain = DS_DOMAIN_FUNCTION_2008
1016 elif domain_level == "2008_R2":
1017 new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
1019 if new_level_domain <= level_domain and level_domain_mixed == 0:
1020 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
1022 if new_level_domain > min_level_dc:
1023 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
1025 # Deactivate mixed/interim domain support
1026 if level_domain_mixed != 0:
1027 # Directly on the base DN
1028 m = ldb.Message()
1029 m.dn = ldb.Dn(samdb, domain_dn)
1030 m["nTMixedDomain"] = ldb.MessageElement("0",
1031 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1032 samdb.modify(m)
1033 # Under partitions
1034 m = ldb.Message()
1035 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
1036 m["nTMixedDomain"] = ldb.MessageElement("0",
1037 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1038 try:
1039 samdb.modify(m)
1040 except ldb.LdbError, (enum, emsg):
1041 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1042 raise
1044 # Directly on the base DN
1045 m = ldb.Message()
1046 m.dn = ldb.Dn(samdb, domain_dn)
1047 m["msDS-Behavior-Version"]= ldb.MessageElement(
1048 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1049 "msDS-Behavior-Version")
1050 samdb.modify(m)
1051 # Under partitions
1052 m = ldb.Message()
1053 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
1054 + ",CN=Partitions,%s" % samdb.get_config_basedn())
1055 m["msDS-Behavior-Version"]= ldb.MessageElement(
1056 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1057 "msDS-Behavior-Version")
1058 try:
1059 samdb.modify(m)
1060 except ldb.LdbError, (enum, emsg):
1061 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1062 raise
1064 level_domain = new_level_domain
1065 msgs.append("Domain function level changed!")
1067 if forest_level is not None:
1068 if forest_level == "2003":
1069 new_level_forest = DS_DOMAIN_FUNCTION_2003
1070 elif forest_level == "2008":
1071 new_level_forest = DS_DOMAIN_FUNCTION_2008
1072 elif forest_level == "2008_R2":
1073 new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
1074 if new_level_forest <= level_forest:
1075 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1076 if new_level_forest > level_domain:
1077 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1078 m = ldb.Message()
1079 m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
1080 m["msDS-Behavior-Version"]= ldb.MessageElement(
1081 str(new_level_forest), ldb.FLAG_MOD_REPLACE,
1082 "msDS-Behavior-Version")
1083 samdb.modify(m)
1084 msgs.append("Forest function level changed!")
1085 msgs.append("All changes applied successfully!")
1086 self.message("\n".join(msgs))
1087 else:
1088 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
1091 class cmd_domain_passwordsettings(Command):
1092 """Set password settings.
1094 Password complexity, password lockout policy, history length,
1095 minimum password length, the minimum and maximum password age) on
1096 a Samba AD DC server.
1098 Use against a Windows DC is possible, but group policy will override it.
1101 synopsis = "%prog (show|set <options>) [options]"
1103 takes_optiongroups = {
1104 "sambaopts": options.SambaOptions,
1105 "versionopts": options.VersionOptions,
1106 "credopts": options.CredentialsOptions,
1109 takes_options = [
1110 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1111 metavar="URL", dest="H"),
1112 Option("--quiet", help="Be quiet", action="store_true"),
1113 Option("--complexity", type="choice", choices=["on","off","default"],
1114 help="The password complexity (on | off | default). Default is 'on'"),
1115 Option("--store-plaintext", type="choice", choices=["on","off","default"],
1116 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1117 Option("--history-length",
1118 help="The password history length (<integer> | default). Default is 24.", type=str),
1119 Option("--min-pwd-length",
1120 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1121 Option("--min-pwd-age",
1122 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1123 Option("--max-pwd-age",
1124 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1125 Option("--account-lockout-duration",
1126 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),
1127 Option("--account-lockout-threshold",
1128 help="The number of bad password attempts allowed before locking out the account (<integer> | default). Default is 0 (never lock out).", type=str),
1129 Option("--reset-account-lockout-after",
1130 help="After this time is elapsed, the recorded number of attempts restarts from zero (<integer> | default). Default is 30.", type=str),
1133 takes_args = ["subcommand"]
1135 def run(self, subcommand, H=None, min_pwd_age=None, max_pwd_age=None,
1136 quiet=False, complexity=None, store_plaintext=None, history_length=None,
1137 min_pwd_length=None, account_lockout_duration=None, account_lockout_threshold=None,
1138 reset_account_lockout_after=None, credopts=None, sambaopts=None,
1139 versionopts=None):
1140 lp = sambaopts.get_loadparm()
1141 creds = credopts.get_credentials(lp)
1143 samdb = SamDB(url=H, session_info=system_session(),
1144 credentials=creds, lp=lp)
1146 domain_dn = samdb.domain_dn()
1147 res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1148 attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1149 "minPwdAge", "maxPwdAge", "lockoutDuration", "lockoutThreshold",
1150 "lockOutObservationWindow"])
1151 assert(len(res) == 1)
1152 try:
1153 pwd_props = int(res[0]["pwdProperties"][0])
1154 pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
1155 cur_min_pwd_len = int(res[0]["minPwdLength"][0])
1156 # ticks -> days
1157 cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1158 if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000:
1159 cur_max_pwd_age = 0
1160 else:
1161 cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1162 cur_account_lockout_threshold = int(res[0]["lockoutThreshold"][0])
1163 # ticks -> mins
1164 if int(res[0]["lockoutDuration"][0]) == -0x8000000000000000:
1165 cur_account_lockout_duration = 0
1166 else:
1167 cur_account_lockout_duration = abs(int(res[0]["lockoutDuration"][0])) / (1e7 * 60)
1168 cur_reset_account_lockout_after = abs(int(res[0]["lockOutObservationWindow"][0])) / (1e7 * 60)
1169 except Exception, e:
1170 raise CommandError("Could not retrieve password properties!", e)
1172 if subcommand == "show":
1173 self.message("Password informations for domain '%s'" % domain_dn)
1174 self.message("")
1175 if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
1176 self.message("Password complexity: on")
1177 else:
1178 self.message("Password complexity: off")
1179 if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
1180 self.message("Store plaintext passwords: on")
1181 else:
1182 self.message("Store plaintext passwords: off")
1183 self.message("Password history length: %d" % pwd_hist_len)
1184 self.message("Minimum password length: %d" % cur_min_pwd_len)
1185 self.message("Minimum password age (days): %d" % cur_min_pwd_age)
1186 self.message("Maximum password age (days): %d" % cur_max_pwd_age)
1187 self.message("Account lockout duration (mins): %d" % cur_account_lockout_duration)
1188 self.message("Account lockout threshold (attempts): %d" % cur_account_lockout_threshold)
1189 self.message("Reset account lockout after (mins): %d" % cur_reset_account_lockout_after)
1190 elif subcommand == "set":
1191 msgs = []
1192 m = ldb.Message()
1193 m.dn = ldb.Dn(samdb, domain_dn)
1195 if complexity is not None:
1196 if complexity == "on" or complexity == "default":
1197 pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
1198 msgs.append("Password complexity activated!")
1199 elif complexity == "off":
1200 pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
1201 msgs.append("Password complexity deactivated!")
1203 if store_plaintext is not None:
1204 if store_plaintext == "on" or store_plaintext == "default":
1205 pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1206 msgs.append("Plaintext password storage for changed passwords activated!")
1207 elif store_plaintext == "off":
1208 pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
1209 msgs.append("Plaintext password storage for changed passwords deactivated!")
1211 if complexity is not None or store_plaintext is not None:
1212 m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
1213 ldb.FLAG_MOD_REPLACE, "pwdProperties")
1215 if history_length is not None:
1216 if history_length == "default":
1217 pwd_hist_len = 24
1218 else:
1219 pwd_hist_len = int(history_length)
1221 if pwd_hist_len < 0 or pwd_hist_len > 24:
1222 raise CommandError("Password history length must be in the range of 0 to 24!")
1224 m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
1225 ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
1226 msgs.append("Password history length changed!")
1228 if min_pwd_length is not None:
1229 if min_pwd_length == "default":
1230 min_pwd_len = 7
1231 else:
1232 min_pwd_len = int(min_pwd_length)
1234 if min_pwd_len < 0 or min_pwd_len > 14:
1235 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1237 m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
1238 ldb.FLAG_MOD_REPLACE, "minPwdLength")
1239 msgs.append("Minimum password length changed!")
1241 if min_pwd_age is not None:
1242 if min_pwd_age == "default":
1243 min_pwd_age = 1
1244 else:
1245 min_pwd_age = int(min_pwd_age)
1247 if min_pwd_age < 0 or min_pwd_age > 998:
1248 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1250 # days -> ticks
1251 min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
1253 m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
1254 ldb.FLAG_MOD_REPLACE, "minPwdAge")
1255 msgs.append("Minimum password age changed!")
1257 if max_pwd_age is not None:
1258 if max_pwd_age == "default":
1259 max_pwd_age = 43
1260 else:
1261 max_pwd_age = int(max_pwd_age)
1263 if max_pwd_age < 0 or max_pwd_age > 999:
1264 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1266 # days -> ticks
1267 if max_pwd_age == 0:
1268 max_pwd_age_ticks = -0x8000000000000000
1269 else:
1270 max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
1272 m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
1273 ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1274 msgs.append("Maximum password age changed!")
1276 if account_lockout_duration is not None:
1277 if account_lockout_duration == "default":
1278 account_lockout_duration = 30
1279 else:
1280 account_lockout_duration = int(account_lockout_duration)
1282 if account_lockout_duration < 0 or account_lockout_duration > 99999:
1283 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1285 # days -> ticks
1286 if account_lockout_duration == 0:
1287 account_lockout_duration_ticks = -0x8000000000000000
1288 else:
1289 account_lockout_duration_ticks = -int(account_lockout_duration * (60 * 1e7))
1291 m["lockoutDuration"] = ldb.MessageElement(str(account_lockout_duration_ticks),
1292 ldb.FLAG_MOD_REPLACE, "lockoutDuration")
1293 msgs.append("Account lockout duration changed!")
1295 if account_lockout_threshold is not None:
1296 if account_lockout_threshold == "default":
1297 account_lockout_threshold = 0
1298 else:
1299 account_lockout_threshold = int(account_lockout_threshold)
1301 m["lockoutThreshold"] = ldb.MessageElement(str(account_lockout_threshold),
1302 ldb.FLAG_MOD_REPLACE, "lockoutThreshold")
1303 msgs.append("Account lockout threshold changed!")
1305 if reset_account_lockout_after is not None:
1306 if reset_account_lockout_after == "default":
1307 reset_account_lockout_after = 30
1308 else:
1309 reset_account_lockout_after = int(reset_account_lockout_after)
1311 if reset_account_lockout_after < 0 or reset_account_lockout_after > 99999:
1312 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1314 # days -> ticks
1315 if reset_account_lockout_after == 0:
1316 reset_account_lockout_after_ticks = -0x8000000000000000
1317 else:
1318 reset_account_lockout_after_ticks = -int(reset_account_lockout_after * (60 * 1e7))
1320 m["lockOutObservationWindow"] = ldb.MessageElement(str(reset_account_lockout_after_ticks),
1321 ldb.FLAG_MOD_REPLACE, "lockOutObservationWindow")
1322 msgs.append("Duration to reset account lockout after changed!")
1324 if max_pwd_age > 0 and min_pwd_age >= max_pwd_age:
1325 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
1327 if len(m) == 0:
1328 raise CommandError("You must specify at least one option to set. Try --help")
1329 samdb.modify(m)
1330 msgs.append("All changes applied successfully!")
1331 self.message("\n".join(msgs))
1332 else:
1333 raise CommandError("Wrong argument '%s'!" % subcommand)
1336 class cmd_domain_classicupgrade(Command):
1337 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1339 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1340 the testparm utility from your classic installation (with --testparm).
1343 synopsis = "%prog [options] <classic_smb_conf>"
1345 takes_optiongroups = {
1346 "sambaopts": options.SambaOptions,
1347 "versionopts": options.VersionOptions
1350 takes_options = [
1351 Option("--dbdir", type="string", metavar="DIR",
1352 help="Path to samba classic DC database directory"),
1353 Option("--testparm", type="string", metavar="PATH",
1354 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1355 Option("--targetdir", type="string", metavar="DIR",
1356 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1357 Option("--quiet", help="Be quiet", action="store_true"),
1358 Option("--verbose", help="Be verbose", action="store_true"),
1359 Option("--use-xattrs", type="choice", choices=["yes","no","auto"], metavar="[yes|no|auto]",
1360 help="Define if we should use the native fs capabilities or a tdb file for storing attributes likes ntacl, auto tries to make an inteligent guess based on the user rights and system capabilities", default="auto"),
1361 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
1362 action="store_true"),
1363 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
1364 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1365 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1366 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1367 "BIND9_DLZ uses samba4 AD to store zone information, "
1368 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1369 default="SAMBA_INTERNAL")
1372 takes_args = ["smbconf"]
1374 def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
1375 quiet=False, verbose=False, use_xattrs=None, sambaopts=None, versionopts=None,
1376 dns_backend=None, use_ntvfs=False):
1378 if not os.path.exists(smbconf):
1379 raise CommandError("File %s does not exist" % smbconf)
1381 if testparm and not os.path.exists(testparm):
1382 raise CommandError("Testparm utility %s does not exist" % testparm)
1384 if dbdir and not os.path.exists(dbdir):
1385 raise CommandError("Directory %s does not exist" % dbdir)
1387 if not dbdir and not testparm:
1388 raise CommandError("Please specify either dbdir or testparm")
1390 logger = self.get_logger()
1391 if verbose:
1392 logger.setLevel(logging.DEBUG)
1393 elif quiet:
1394 logger.setLevel(logging.WARNING)
1395 else:
1396 logger.setLevel(logging.INFO)
1398 if dbdir and testparm:
1399 logger.warning("both dbdir and testparm specified, ignoring dbdir.")
1400 dbdir = None
1402 lp = sambaopts.get_loadparm()
1404 s3conf = s3param.get_context()
1406 if sambaopts.realm:
1407 s3conf.set("realm", sambaopts.realm)
1409 if targetdir is not None:
1410 if not os.path.isdir(targetdir):
1411 os.mkdir(targetdir)
1413 eadb = True
1414 if use_xattrs == "yes":
1415 eadb = False
1416 elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
1417 if targetdir:
1418 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
1419 else:
1420 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
1421 try:
1422 try:
1423 samba.ntacls.setntacl(lp, tmpfile.name,
1424 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1425 eadb = False
1426 except Exception:
1427 # FIXME: Don't catch all exceptions here
1428 logger.info("You are not root or your system do not support xattr, using tdb backend for attributes. "
1429 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1430 finally:
1431 tmpfile.close()
1433 # Set correct default values from dbdir or testparm
1434 paths = {}
1435 if dbdir:
1436 paths["state directory"] = dbdir
1437 paths["private dir"] = dbdir
1438 paths["lock directory"] = dbdir
1439 paths["smb passwd file"] = dbdir + "/smbpasswd"
1440 else:
1441 paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
1442 paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
1443 paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
1444 paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
1445 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1446 # "state directory", instead make use of "lock directory"
1447 if len(paths["state directory"]) == 0:
1448 paths["state directory"] = paths["lock directory"]
1450 for p in paths:
1451 s3conf.set(p, paths[p])
1453 # load smb.conf parameters
1454 logger.info("Reading smb.conf")
1455 s3conf.load(smbconf)
1456 samba3 = Samba3(smbconf, s3conf)
1458 logger.info("Provisioning")
1459 upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
1460 useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
1463 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
1464 __doc__ = cmd_domain_classicupgrade.__doc__
1466 # This command is present for backwards compatibility only,
1467 # and should not be shown.
1469 hidden = True
1471 class LocalDCCredentialsOptions(options.CredentialsOptions):
1472 def __init__(self, parser):
1473 options.CredentialsOptions.__init__(self, parser, special_name="local-dc")
1475 class DomainTrustCommand(Command):
1476 """List domain trusts."""
1478 def __init__(self):
1479 Command.__init__(self)
1480 self.local_lp = None
1482 self.local_server = None
1483 self.local_binding_string = None
1484 self.local_creds = None
1486 self.remote_server = None
1487 self.remote_binding_string = None
1488 self.remote_creds = None
1490 WERR_OK = 0x00000000
1491 WERR_INVALID_FUNCTION = 0x00000001
1492 WERR_NERR_ACFNOTLOADED = 0x000008B3
1494 NT_STATUS_NOT_FOUND = 0xC0000225
1495 NT_STATUS_OBJECT_NAME_NOT_FOUND = 0xC0000034
1496 NT_STATUS_INVALID_PARAMETER = 0xC000000D
1497 NT_STATUS_INVALID_INFO_CLASS = 0xC0000003
1498 NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE = 0xC002002E
1500 def _uint32(self, v):
1501 return ctypes.c_uint32(v).value
1503 def check_runtime_error(self, runtime, val):
1504 if runtime is None:
1505 return False
1507 err32 = self._uint32(runtime[0])
1508 if err32 == val:
1509 return True
1511 return False
1513 class LocalRuntimeError(CommandError):
1514 def __init__(exception_self, self, runtime, message):
1515 err32 = self._uint32(runtime[0])
1516 errstr = runtime[1]
1517 msg = "LOCAL_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1518 self.local_server, message, err32, errstr)
1519 CommandError.__init__(exception_self, msg)
1521 class RemoteRuntimeError(CommandError):
1522 def __init__(exception_self, self, runtime, message):
1523 err32 = self._uint32(runtime[0])
1524 errstr = runtime[1]
1525 msg = "REMOTE_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1526 self.remote_server, message, err32, errstr)
1527 CommandError.__init__(exception_self, msg)
1529 class LocalLdbError(CommandError):
1530 def __init__(exception_self, self, ldb_error, message):
1531 errval = ldb_error[0]
1532 errstr = ldb_error[1]
1533 msg = "LOCAL_DC[%s]: %s - ERROR(%d) - %s" % (
1534 self.local_server, message, errval, errstr)
1535 CommandError.__init__(exception_self, msg)
1537 def setup_local_server(self, sambaopts, localdcopts):
1538 if self.local_server is not None:
1539 return self.local_server
1541 lp = sambaopts.get_loadparm()
1543 local_server = localdcopts.ipaddress
1544 if local_server is None:
1545 server_role = lp.server_role()
1546 if server_role != "ROLE_ACTIVE_DIRECTORY_DC":
1547 raise CommandError("Invalid server_role %s" % (server_role))
1548 local_server = lp.get('netbios name')
1549 local_transport = "ncalrpc"
1550 local_binding_options = ""
1551 local_binding_options += ",auth_type=ncalrpc_as_system"
1552 local_ldap_url = None
1553 local_creds = None
1554 else:
1555 local_transport = "ncacn_np"
1556 local_binding_options = ""
1557 local_ldap_url = "ldap://%s" % local_server
1558 local_creds = localdcopts.get_credentials(lp)
1560 self.local_lp = lp
1562 self.local_server = local_server
1563 self.local_binding_string = "%s:%s[%s]" % (local_transport, local_server, local_binding_options)
1564 self.local_ldap_url = local_ldap_url
1565 self.local_creds = local_creds
1566 return self.local_server
1568 def new_local_lsa_connection(self):
1569 return lsa.lsarpc(self.local_binding_string, self.local_lp, self.local_creds)
1571 def new_local_netlogon_connection(self):
1572 return netlogon.netlogon(self.local_binding_string, self.local_lp, self.local_creds)
1574 def new_local_ldap_connection(self):
1575 return SamDB(url=self.local_ldap_url,
1576 session_info=system_session(),
1577 credentials=self.local_creds,
1578 lp=self.local_lp)
1580 def setup_remote_server(self, credopts, domain,
1581 require_pdc=True,
1582 require_writable=True):
1584 if require_pdc:
1585 assert require_writable
1587 if self.remote_server is not None:
1588 return self.remote_server
1590 self.remote_server = "__unknown__remote_server__.%s" % domain
1591 assert self.local_server is not None
1593 remote_creds = credopts.get_credentials(self.local_lp)
1594 remote_server = credopts.ipaddress
1595 remote_binding_options = ""
1597 # TODO: we should also support NT4 domains
1598 # we could use local_netlogon.netr_DsRGetDCNameEx2() with the remote domain name
1599 # and delegate NBT or CLDAP to the local netlogon server
1600 try:
1601 remote_net = Net(remote_creds, self.local_lp, server=remote_server)
1602 remote_flags = nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS
1603 if require_writable:
1604 remote_flags |= nbt.NBT_SERVER_WRITABLE
1605 if require_pdc:
1606 remote_flags |= nbt.NBT_SERVER_PDC
1607 remote_info = remote_net.finddc(flags=remote_flags, domain=domain, address=remote_server)
1608 except Exception:
1609 raise CommandError("Failed to find a writeable DC for domain '%s'" % domain)
1610 flag_map = {
1611 nbt.NBT_SERVER_PDC: "PDC",
1612 nbt.NBT_SERVER_GC: "GC",
1613 nbt.NBT_SERVER_LDAP: "LDAP",
1614 nbt.NBT_SERVER_DS: "DS",
1615 nbt.NBT_SERVER_KDC: "KDC",
1616 nbt.NBT_SERVER_TIMESERV: "TIMESERV",
1617 nbt.NBT_SERVER_CLOSEST: "CLOSEST",
1618 nbt.NBT_SERVER_WRITABLE: "WRITABLE",
1619 nbt.NBT_SERVER_GOOD_TIMESERV: "GOOD_TIMESERV",
1620 nbt.NBT_SERVER_NDNC: "NDNC",
1621 nbt.NBT_SERVER_SELECT_SECRET_DOMAIN_6: "SELECT_SECRET_DOMAIN_6",
1622 nbt.NBT_SERVER_FULL_SECRET_DOMAIN_6: "FULL_SECRET_DOMAIN_6",
1623 nbt.NBT_SERVER_ADS_WEB_SERVICE: "ADS_WEB_SERVICE",
1624 nbt.NBT_SERVER_DS_8: "DS_8",
1625 nbt.NBT_SERVER_HAS_DNS_NAME: "HAS_DNS_NAME",
1626 nbt.NBT_SERVER_IS_DEFAULT_NC: "IS_DEFAULT_NC",
1627 nbt.NBT_SERVER_FOREST_ROOT: "FOREST_ROOT",
1629 server_type_string = self.generic_bitmap_to_string(flag_map,
1630 remote_info.server_type, names_only=True)
1631 self.outf.write("RemoteDC Netbios[%s] DNS[%s] ServerType[%s]\n" % (
1632 remote_info.pdc_name,
1633 remote_info.pdc_dns_name,
1634 server_type_string))
1636 self.remote_server = remote_info.pdc_dns_name
1637 self.remote_binding_string="ncacn_np:%s[%s]" % (self.remote_server, remote_binding_options)
1638 self.remote_creds = remote_creds
1639 return self.remote_server
1641 def new_remote_lsa_connection(self):
1642 return lsa.lsarpc(self.remote_binding_string, self.local_lp, self.remote_creds)
1644 def new_remote_netlogon_connection(self):
1645 return netlogon.netlogon(self.remote_binding_string, self.local_lp, self.remote_creds)
1647 def get_lsa_info(self, conn, policy_access):
1648 objectAttr = lsa.ObjectAttribute()
1649 objectAttr.sec_qos = lsa.QosInfo()
1651 policy = conn.OpenPolicy2(''.decode('utf-8'),
1652 objectAttr, policy_access)
1654 info = conn.QueryInfoPolicy2(policy, lsa.LSA_POLICY_INFO_DNS)
1656 return (policy, info)
1658 def get_netlogon_dc_info(self, conn, server):
1659 info = conn.netr_DsRGetDCNameEx2(server,
1660 None, 0, None, None, None,
1661 netlogon.DS_RETURN_DNS_NAME)
1662 return info
1664 def netr_DomainTrust_to_name(self, t):
1665 if t.trust_type == lsa.LSA_TRUST_TYPE_DOWNLEVEL:
1666 return t.netbios_name
1668 return t.dns_name
1670 def netr_DomainTrust_to_type(self, a, t):
1671 primary = None
1672 primary_parent = None
1673 for _t in a:
1674 if _t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
1675 primary = _t
1676 if not _t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1677 primary_parent = a[_t.parent_index]
1678 break
1680 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1681 if t is primary_parent:
1682 return "Parent"
1684 if t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1685 return "TreeRoot"
1687 parent = a[t.parent_index]
1688 if parent is primary:
1689 return "Child"
1691 return "Shortcut"
1693 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1694 return "Forest"
1696 return "External"
1698 def netr_DomainTrust_to_transitive(self, t):
1699 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1700 return "Yes"
1702 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE:
1703 return "No"
1705 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1706 return "Yes"
1708 return "No"
1710 def netr_DomainTrust_to_direction(self, t):
1711 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND and \
1712 t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1713 return "BOTH"
1715 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND:
1716 return "INCOMING"
1718 if t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1719 return "OUTGOING"
1721 return "INVALID"
1723 def generic_enum_to_string(self, e_dict, v, names_only=False):
1724 try:
1725 w = e_dict[v]
1726 except KeyError:
1727 v32 = self._uint32(v)
1728 w = "__unknown__%08X__" % v32
1730 r = "0x%x (%s)" % (v, w)
1731 return r;
1733 def generic_bitmap_to_string(self, b_dict, v, names_only=False):
1735 s = []
1737 c = v
1738 for b in sorted(b_dict.keys()):
1739 if not (c & b):
1740 continue
1741 c &= ~b
1742 s += [b_dict[b]]
1744 if c != 0:
1745 c32 = self._uint32(c)
1746 s += ["__unknown_%08X__" % c32]
1748 w = ",".join(s)
1749 if names_only:
1750 return w
1751 r = "0x%x (%s)" % (v, w)
1752 return r;
1754 def trustType_string(self, v):
1755 types = {
1756 lsa.LSA_TRUST_TYPE_DOWNLEVEL : "DOWNLEVEL",
1757 lsa.LSA_TRUST_TYPE_UPLEVEL : "UPLEVEL",
1758 lsa.LSA_TRUST_TYPE_MIT : "MIT",
1759 lsa.LSA_TRUST_TYPE_DCE : "DCE",
1761 return self.generic_enum_to_string(types, v)
1763 def trustDirection_string(self, v):
1764 directions = {
1765 lsa.LSA_TRUST_DIRECTION_INBOUND |
1766 lsa.LSA_TRUST_DIRECTION_OUTBOUND : "BOTH",
1767 lsa.LSA_TRUST_DIRECTION_INBOUND : "INBOUND",
1768 lsa.LSA_TRUST_DIRECTION_OUTBOUND : "OUTBOUND",
1770 return self.generic_enum_to_string(directions, v)
1772 def trustAttributes_string(self, v):
1773 attributes = {
1774 lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE : "NON_TRANSITIVE",
1775 lsa.LSA_TRUST_ATTRIBUTE_UPLEVEL_ONLY : "UPLEVEL_ONLY",
1776 lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN : "QUARANTINED_DOMAIN",
1777 lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE : "FOREST_TRANSITIVE",
1778 lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION : "CROSS_ORGANIZATION",
1779 lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST : "WITHIN_FOREST",
1780 lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL : "TREAT_AS_EXTERNAL",
1781 lsa.LSA_TRUST_ATTRIBUTE_USES_RC4_ENCRYPTION : "USES_RC4_ENCRYPTION",
1783 return self.generic_bitmap_to_string(attributes, v)
1785 def kerb_EncTypes_string(self, v):
1786 enctypes = {
1787 security.KERB_ENCTYPE_DES_CBC_CRC : "DES_CBC_CRC",
1788 security.KERB_ENCTYPE_DES_CBC_MD5 : "DES_CBC_MD5",
1789 security.KERB_ENCTYPE_RC4_HMAC_MD5 : "RC4_HMAC_MD5",
1790 security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96 : "AES128_CTS_HMAC_SHA1_96",
1791 security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96 : "AES256_CTS_HMAC_SHA1_96",
1792 security.KERB_ENCTYPE_FAST_SUPPORTED : "FAST_SUPPORTED",
1793 security.KERB_ENCTYPE_COMPOUND_IDENTITY_SUPPORTED : "COMPOUND_IDENTITY_SUPPORTED",
1794 security.KERB_ENCTYPE_CLAIMS_SUPPORTED : "CLAIMS_SUPPORTED",
1795 security.KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED : "RESOURCE_SID_COMPRESSION_DISABLED",
1797 return self.generic_bitmap_to_string(enctypes, v)
1799 def entry_tln_status(self, e_flags, ):
1800 if e_flags == 0:
1801 return "Status[Enabled]"
1803 flags = {
1804 lsa.LSA_TLN_DISABLED_NEW : "Disabled-New",
1805 lsa.LSA_TLN_DISABLED_ADMIN : "Disabled",
1806 lsa.LSA_TLN_DISABLED_CONFLICT : "Disabled-Conflicting",
1808 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
1810 def entry_dom_status(self, e_flags):
1811 if e_flags == 0:
1812 return "Status[Enabled]"
1814 flags = {
1815 lsa.LSA_SID_DISABLED_ADMIN : "Disabled-SID",
1816 lsa.LSA_SID_DISABLED_CONFLICT : "Disabled-SID-Conflicting",
1817 lsa.LSA_NB_DISABLED_ADMIN : "Disabled-NB",
1818 lsa.LSA_NB_DISABLED_CONFLICT : "Disabled-NB-Conflicting",
1820 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
1822 def write_forest_trust_info(self, fti, tln=None, collisions=None):
1823 if tln is not None:
1824 tln_string = " TDO[%s]" % tln
1825 else:
1826 tln_string = ""
1828 self.outf.write("Namespaces[%d]%s:\n" % (
1829 len(fti.entries), tln_string))
1831 for i in xrange(0, len(fti.entries)):
1832 e = fti.entries[i]
1834 flags = e.flags
1835 collision_string = ""
1837 if collisions is not None:
1838 for c in collisions.entries:
1839 if c.index != i:
1840 continue
1841 flags = c.flags
1842 collision_string = " Collision[%s]" % (c.name.string)
1844 d = e.forest_trust_data
1845 if e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
1846 self.outf.write("TLN: %-32s DNS[*.%s]%s\n" % (
1847 self.entry_tln_status(flags),
1848 d.string, collision_string))
1849 elif e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
1850 self.outf.write("TLN_EX: %-29s DNS[*.%s]\n" % (
1851 "", d.string))
1852 elif e.type == lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
1853 self.outf.write("DOM: %-32s DNS[%s] Netbios[%s] SID[%s]%s\n" % (
1854 self.entry_dom_status(flags),
1855 d.dns_domain_name.string,
1856 d.netbios_domain_name.string,
1857 d.domain_sid, collision_string))
1858 return
1860 class cmd_domain_trust_list(DomainTrustCommand):
1861 """List domain trusts."""
1863 synopsis = "%prog [options]"
1865 takes_optiongroups = {
1866 "sambaopts": options.SambaOptions,
1867 "versionopts": options.VersionOptions,
1868 "localdcopts": LocalDCCredentialsOptions,
1871 takes_options = [
1874 def run(self, sambaopts=None, versionopts=None, localdcopts=None):
1876 local_server = self.setup_local_server(sambaopts, localdcopts)
1877 try:
1878 local_netlogon = self.new_local_netlogon_connection()
1879 except RuntimeError as error:
1880 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
1882 try:
1883 local_netlogon_trusts = local_netlogon.netr_DsrEnumerateDomainTrusts(local_server,
1884 netlogon.NETR_TRUST_FLAG_IN_FOREST |
1885 netlogon.NETR_TRUST_FLAG_OUTBOUND |
1886 netlogon.NETR_TRUST_FLAG_INBOUND)
1887 except RuntimeError as error:
1888 if self.check_runtime_error(error, self.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE):
1889 # TODO: we could implement a fallback to lsa.EnumTrustDom()
1890 raise CommandError("LOCAL_DC[%s]: netr_DsrEnumerateDomainTrusts not supported." % (
1891 self.local_server))
1892 raise self.LocalRuntimeError(self, error, "netr_DsrEnumerateDomainTrusts failed")
1894 a = local_netlogon_trusts.array
1895 for t in a:
1896 if t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
1897 continue
1898 self.outf.write("%-14s %-15s %-19s %s\n" % (
1899 "Type[%s]" % self.netr_DomainTrust_to_type(a, t),
1900 "Transitive[%s]" % self.netr_DomainTrust_to_transitive(t),
1901 "Direction[%s]" % self.netr_DomainTrust_to_direction(t),
1902 "Name[%s]" % self.netr_DomainTrust_to_name(t)))
1903 return
1905 class cmd_domain_trust_show(DomainTrustCommand):
1906 """Show trusted domain details."""
1908 synopsis = "%prog NAME [options]"
1910 takes_optiongroups = {
1911 "sambaopts": options.SambaOptions,
1912 "versionopts": options.VersionOptions,
1913 "localdcopts": LocalDCCredentialsOptions,
1916 takes_options = [
1919 takes_args = ["domain"]
1921 def run(self, domain, sambaopts=None, versionopts=None, localdcopts=None):
1923 local_server = self.setup_local_server(sambaopts, localdcopts)
1924 try:
1925 local_lsa = self.new_local_lsa_connection()
1926 except RuntimeError as error:
1927 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
1929 try:
1930 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
1931 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
1932 except RuntimeError as error:
1933 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
1935 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
1936 local_lsa_info.name.string,
1937 local_lsa_info.dns_domain.string,
1938 local_lsa_info.sid))
1940 lsaString = lsa.String()
1941 lsaString.string = domain
1942 try:
1943 local_tdo_full = local_lsa.QueryTrustedDomainInfoByName(local_policy,
1944 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
1945 local_tdo_info = local_tdo_full.info_ex
1946 local_tdo_posix = local_tdo_full.posix_offset
1947 except RuntimeError as error:
1948 if self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND):
1949 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
1951 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(FULL_INFO) failed")
1953 try:
1954 local_tdo_enctypes = local_lsa.QueryTrustedDomainInfoByName(local_policy,
1955 lsaString, lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES)
1956 except RuntimeError as error:
1957 if self.check_runtime_error(error, self.NT_STATUS_INVALID_PARAMETER):
1958 error = None
1959 if self.check_runtime_error(error, self.NT_STATUS_INVALID_INFO_CLASS):
1960 error = None
1962 if error is not None:
1963 raise self.LocalRuntimeError(self, error,
1964 "QueryTrustedDomainInfoByName(SUPPORTED_ENCRYPTION_TYPES) failed")
1966 local_tdo_enctypes = lsa.TrustDomainInfoSupportedEncTypes()
1967 local_tdo_enctypes.enc_types = 0
1969 try:
1970 local_tdo_forest = None
1971 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1972 local_tdo_forest = local_lsa.lsaRQueryForestTrustInformation(local_policy,
1973 lsaString, lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
1974 except RuntimeError as error:
1975 if self.check_runtime_error(error, self.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE):
1976 error = None
1977 if self.check_runtime_error(error, self.NT_STATUS_NOT_FOUND):
1978 error = None
1979 if error is not None:
1980 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation failed")
1982 local_tdo_forest = lsa.ForestTrustInformation()
1983 local_tdo_forest.count = 0
1984 local_tdo_forest.entries = []
1986 self.outf.write("TrusteDomain:\n\n");
1987 self.outf.write("NetbiosName: %s\n" % local_tdo_info.netbios_name.string)
1988 if local_tdo_info.netbios_name.string != local_tdo_info.domain_name.string:
1989 self.outf.write("DnsName: %s\n" % local_tdo_info.domain_name.string)
1990 self.outf.write("SID: %s\n" % local_tdo_info.sid)
1991 self.outf.write("Type: %s\n" % self.trustType_string(local_tdo_info.trust_type))
1992 self.outf.write("Direction: %s\n" % self.trustDirection_string(local_tdo_info.trust_direction))
1993 self.outf.write("Attributes: %s\n" % self.trustAttributes_string(local_tdo_info.trust_attributes))
1994 posix_offset_u32 = ctypes.c_uint32(local_tdo_posix.posix_offset).value
1995 posix_offset_i32 = ctypes.c_int32(local_tdo_posix.posix_offset).value
1996 self.outf.write("PosixOffset: 0x%08X (%d)\n" % (posix_offset_u32, posix_offset_i32))
1997 self.outf.write("kerb_EncTypes: %s\n" % self.kerb_EncTypes_string(local_tdo_enctypes.enc_types))
1999 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2000 self.write_forest_trust_info(local_tdo_forest,
2001 tln=local_tdo_info.domain_name.string)
2003 return
2005 class cmd_domain_trust_create(DomainTrustCommand):
2006 """Create a domain or forest trust."""
2008 synopsis = "%prog DOMAIN [options]"
2010 takes_optiongroups = {
2011 "sambaopts": options.SambaOptions,
2012 "versionopts": options.VersionOptions,
2013 "credopts": options.CredentialsOptions,
2014 "localdcopts": LocalDCCredentialsOptions,
2017 takes_options = [
2018 Option("--type", type="choice", metavar="TYPE",
2019 choices=["external", "forest"],
2020 help="The type of the trust: 'external' or 'forest'.",
2021 dest='trust_type',
2022 default="external"),
2023 Option("--direction", type="choice", metavar="DIRECTION",
2024 choices=["incoming", "outgoing", "both"],
2025 help="The trust direction: 'incoming', 'outgoing' or 'both'.",
2026 dest='trust_direction',
2027 default="both"),
2028 Option("--create-location", type="choice", metavar="LOCATION",
2029 choices=["local", "both"],
2030 help="Where to create the trusted domain object: 'local' or 'both'.",
2031 dest='create_location',
2032 default="both"),
2033 Option("--cross-organisation", action="store_true",
2034 help="The related domains does not belong to the same organisation.",
2035 dest='cross_organisation',
2036 default=False),
2037 Option("--quarantined", type="choice", metavar="yes|no",
2038 choices=["yes", "no", None],
2039 help="Special SID filtering rules are applied to the trust. "
2040 "With --type=external the default is yes. "
2041 "With --type=forest the default is no.",
2042 dest='quarantined_arg',
2043 default=None),
2044 Option("--not-transitive", action="store_true",
2045 help="The forest trust is not transitive.",
2046 dest='not_transitive',
2047 default=False),
2048 Option("--treat-as-external", action="store_true",
2049 help="The treat the forest trust as external.",
2050 dest='treat_as_external',
2051 default=False),
2052 Option("--no-aes-keys", action="store_false",
2053 help="The trust uses aes kerberos keys.",
2054 dest='use_aes_keys',
2055 default=True),
2056 Option("--skip-validation", action="store_false",
2057 help="Skip validation of the trust.",
2058 dest='validate',
2059 default=True),
2062 takes_args = ["domain"]
2064 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2065 trust_type=None, trust_direction=None, create_location=None,
2066 cross_organisation=False, quarantined_arg=None,
2067 not_transitive=False, treat_as_external=False,
2068 use_aes_keys=False, validate=True):
2070 lsaString = lsa.String()
2072 quarantined = False
2073 if quarantined_arg is None:
2074 if trust_type == 'external':
2075 quarantined = True
2076 elif quarantined_arg == 'yes':
2077 quarantined = True
2079 if trust_type != 'forest':
2080 if not_transitive:
2081 raise CommandError("--not-transitive requires --type=forest")
2082 if treat_as_external:
2083 raise CommandError("--treat-as-external requires --type=forest")
2085 enc_types = None
2086 if use_aes_keys:
2087 enc_types = lsa.TrustDomainInfoSupportedEncTypes()
2088 enc_types.enc_types = security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96
2089 enc_types.enc_types |= security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96
2091 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2092 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2093 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2095 local_trust_info = lsa.TrustDomainInfoInfoEx()
2096 local_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2097 local_trust_info.trust_direction = 0
2098 if trust_direction == "both":
2099 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2100 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2101 elif trust_direction == "incoming":
2102 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2103 elif trust_direction == "outgoing":
2104 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2105 local_trust_info.trust_attributes = 0
2106 if cross_organisation:
2107 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2108 if quarantined:
2109 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2110 if trust_type == "forest":
2111 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2112 if not_transitive:
2113 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2114 if treat_as_external:
2115 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2117 def get_password(name):
2118 password = None
2119 while True:
2120 if password is not None and password is not '':
2121 return password
2122 password = getpass("New %s Password: " % name)
2123 passwordverify = getpass("Retype %s Password: " % name)
2124 if not password == passwordverify:
2125 password = None
2126 self.outf.write("Sorry, passwords do not match.\n")
2128 def string_to_array(string):
2129 blob = [0] * len(string)
2131 for i in range(len(string)):
2132 blob[i] = ord(string[i])
2134 return blob
2136 incoming_secret = None
2137 outgoing_secret = None
2138 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2139 if create_location == "local":
2140 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2141 incoming_password = get_password("Incoming Trust")
2142 incoming_secret = string_to_array(incoming_password.encode('utf-16-le'))
2143 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2144 outgoing_password = get_password("Outgoing Trust")
2145 outgoing_secret = string_to_array(outgoing_password.encode('utf-16-le'))
2147 remote_trust_info = None
2148 else:
2149 # We use 240 random bytes.
2150 # Windows uses 28 or 240 random bytes. I guess it's
2151 # based on the trust type external vs. forest.
2153 # The initial trust password can be up to 512 bytes
2154 # while the versioned passwords used for periodic updates
2155 # can only be up to 498 bytes, as netr_ServerPasswordSet2()
2156 # needs to pass the NL_PASSWORD_VERSION structure within the
2157 # 512 bytes and a 2 bytes confounder is required.
2159 def random_trust_secret(length, use_aes_keys=True):
2160 secret = [0] * length
2162 pw1 = samba.generate_random_password(length/2, length/2)
2163 if not use_aes_keys:
2164 # With arcfour-hmac-md5 we have to use valid utf16
2165 # in order to generate the correct pre-auth key
2166 # based on a utf8 password.
2168 # We can remove this once our client libraries
2169 # support using the correct NTHASH.
2170 return string_to_array(pw1.encode('utf-16-le'))
2172 # We mix characters from generate_random_password
2173 # with random numbers from random.randint()
2174 for i in range(len(secret)):
2175 if len(pw1) > i:
2176 secret[i] = ord(pw1[i])
2177 else:
2178 secret[i] = random.randint(0, 255)
2180 return secret
2182 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2183 incoming_secret = random_trust_secret(240, use_aes_keys=use_aes_keys)
2184 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2185 outgoing_secret = random_trust_secret(240, use_aes_keys=use_aes_keys)
2187 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2188 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2190 remote_trust_info = lsa.TrustDomainInfoInfoEx()
2191 remote_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2192 remote_trust_info.trust_direction = 0
2193 if trust_direction == "both":
2194 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2195 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2196 elif trust_direction == "incoming":
2197 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2198 elif trust_direction == "outgoing":
2199 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2200 remote_trust_info.trust_attributes = 0
2201 if cross_organisation:
2202 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2203 if quarantined:
2204 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2205 if trust_type == "forest":
2206 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2207 if not_transitive:
2208 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2209 if treat_as_external:
2210 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2212 local_server = self.setup_local_server(sambaopts, localdcopts)
2213 try:
2214 local_lsa = self.new_local_lsa_connection()
2215 except RuntimeError as error:
2216 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2218 try:
2219 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2220 except RuntimeError as error:
2221 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2223 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2224 local_lsa_info.name.string,
2225 local_lsa_info.dns_domain.string,
2226 local_lsa_info.sid))
2228 try:
2229 remote_server = self.setup_remote_server(credopts, domain)
2230 except RuntimeError as error:
2231 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2233 try:
2234 remote_lsa = self.new_remote_lsa_connection()
2235 except RuntimeError as error:
2236 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2238 try:
2239 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2240 except RuntimeError as error:
2241 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2243 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2244 remote_lsa_info.name.string,
2245 remote_lsa_info.dns_domain.string,
2246 remote_lsa_info.sid))
2248 local_trust_info.domain_name.string = remote_lsa_info.dns_domain.string
2249 local_trust_info.netbios_name.string = remote_lsa_info.name.string
2250 local_trust_info.sid = remote_lsa_info.sid
2252 if remote_trust_info:
2253 remote_trust_info.domain_name.string = local_lsa_info.dns_domain.string
2254 remote_trust_info.netbios_name.string = local_lsa_info.name.string
2255 remote_trust_info.sid = local_lsa_info.sid
2257 try:
2258 lsaString.string = local_trust_info.domain_name.string
2259 local_old_netbios = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2260 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2261 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2262 except RuntimeError as error:
2263 if not self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2264 raise self.LocalRuntimeError(self, error,
2265 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2266 lsaString.string))
2268 try:
2269 lsaString.string = local_trust_info.netbios_name.string
2270 local_old_dns = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2271 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2272 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2273 except RuntimeError as error:
2274 if not self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2275 raise self.LocalRuntimeError(self, error,
2276 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2277 lsaString.string))
2279 if remote_trust_info:
2280 try:
2281 lsaString.string = remote_trust_info.domain_name.string
2282 remote_old_netbios = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2283 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2284 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2285 except RuntimeError as error:
2286 if not self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2287 raise self.RemoteRuntimeError(self, error,
2288 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2289 lsaString.string))
2291 try:
2292 lsaString.string = remote_trust_info.netbios_name.string
2293 remote_old_dns = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2294 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2295 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2296 except RuntimeError as error:
2297 if not self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2298 raise self.RemoteRuntimeError(self, error,
2299 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2300 lsaString.string))
2302 try:
2303 local_netlogon = self.new_local_netlogon_connection()
2304 except RuntimeError as error:
2305 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2307 try:
2308 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
2309 except RuntimeError as error:
2310 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
2312 if remote_trust_info:
2313 try:
2314 remote_netlogon = self.new_remote_netlogon_connection()
2315 except RuntimeError as error:
2316 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2318 try:
2319 remote_netlogon_info = self.get_netlogon_dc_info(remote_netlogon, remote_server)
2320 except RuntimeError as error:
2321 raise self.RemoteRuntimeError(self, error, "failed to get netlogon dc info")
2323 def arcfour_encrypt(key, data):
2324 from Crypto.Cipher import ARC4
2325 c = ARC4.new(key)
2326 return c.encrypt(data)
2328 def generate_AuthInOutBlob(secret, update_time):
2329 if secret is None:
2330 blob = drsblobs.trustAuthInOutBlob()
2331 blob.count = 0
2333 return blob
2335 clear = drsblobs.AuthInfoClear()
2336 clear.size = len(secret)
2337 clear.password = secret
2339 info = drsblobs.AuthenticationInformation()
2340 info.LastUpdateTime = samba.unix2nttime(update_time)
2341 info.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
2342 info.AuthInfo = clear
2344 array = drsblobs.AuthenticationInformationArray()
2345 array.count = 1
2346 array.array = [info]
2348 blob = drsblobs.trustAuthInOutBlob()
2349 blob.count = 1
2350 blob.current = array
2352 return blob
2354 def generate_AuthInfoInternal(session_key, incoming=None, outgoing=None):
2355 confounder = [0] * 512
2356 for i in range(len(confounder)):
2357 confounder[i] = random.randint(0, 255)
2359 trustpass = drsblobs.trustDomainPasswords()
2361 trustpass.confounder = confounder
2362 trustpass.outgoing = outgoing
2363 trustpass.incoming = incoming
2365 trustpass_blob = ndr_pack(trustpass)
2367 encrypted_trustpass = arcfour_encrypt(session_key, trustpass_blob)
2369 auth_blob = lsa.DATA_BUF2()
2370 auth_blob.size = len(encrypted_trustpass)
2371 auth_blob.data = string_to_array(encrypted_trustpass)
2373 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
2374 auth_info.auth_blob = auth_blob
2376 return auth_info
2378 update_time = samba.current_unix_time()
2379 incoming_blob = generate_AuthInOutBlob(incoming_secret, update_time)
2380 outgoing_blob = generate_AuthInOutBlob(outgoing_secret, update_time)
2382 local_tdo_handle = None
2383 remote_tdo_handle = None
2385 local_auth_info = generate_AuthInfoInternal(local_lsa.session_key,
2386 incoming=incoming_blob,
2387 outgoing=outgoing_blob)
2388 if remote_trust_info:
2389 remote_auth_info = generate_AuthInfoInternal(remote_lsa.session_key,
2390 incoming=outgoing_blob,
2391 outgoing=incoming_blob)
2393 try:
2394 if remote_trust_info:
2395 self.outf.write("Creating remote TDO.\n")
2396 current_request = { "location": "remote", "name": "CreateTrustedDomainEx2"}
2397 remote_tdo_handle = remote_lsa.CreateTrustedDomainEx2(remote_policy,
2398 remote_trust_info,
2399 remote_auth_info,
2400 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2401 self.outf.write("Remote TDO created.\n")
2402 if enc_types:
2403 self.outf.write("Setting supported encryption types on remote TDO.\n")
2404 current_request = { "location": "remote", "name": "SetInformationTrustedDomain"}
2405 remote_lsa.SetInformationTrustedDomain(remote_tdo_handle,
2406 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2407 enc_types)
2409 self.outf.write("Creating local TDO.\n")
2410 current_request = { "location": "local", "name": "CreateTrustedDomainEx2"}
2411 local_tdo_handle = local_lsa.CreateTrustedDomainEx2(local_policy,
2412 local_trust_info,
2413 local_auth_info,
2414 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2415 self.outf.write("Local TDO created\n")
2416 if enc_types:
2417 self.outf.write("Setting supported encryption types on local TDO.\n")
2418 current_request = { "location": "local", "name": "SetInformationTrustedDomain"}
2419 local_lsa.SetInformationTrustedDomain(local_tdo_handle,
2420 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2421 enc_types)
2422 except RuntimeError as error:
2423 self.outf.write("Error: %s failed %sly - cleaning up\n" % (
2424 current_request['name'], current_request['location']))
2425 if remote_tdo_handle:
2426 self.outf.write("Deleting remote TDO.\n")
2427 remote_lsa.DeleteObject(remote_tdo_handle)
2428 remote_tdo_handle = None
2429 if local_tdo_handle:
2430 self.outf.write("Deleting local TDO.\n")
2431 local_lsa.DeleteObject(local_tdo_handle)
2432 local_tdo_handle = None
2433 if current_request['location'] is "remote":
2434 raise self.RemoteRuntimeError(self, error, "%s" % (
2435 current_request['name']))
2436 raise self.LocalRuntimeError(self, error, "%s" % (
2437 current_request['name']))
2439 if validate:
2440 if local_trust_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2441 self.outf.write("Setup local forest trust information...\n")
2442 try:
2443 # get all information about the remote trust
2444 # this triggers netr_GetForestTrustInformation to the remote domain
2445 # and lsaRSetForestTrustInformation() locally, but new top level
2446 # names are disabled by default.
2447 local_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
2448 remote_lsa_info.dns_domain.string,
2449 netlogon.DS_GFTI_UPDATE_TDO)
2450 except RuntimeError as error:
2451 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2453 try:
2454 # here we try to enable all top level names
2455 local_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
2456 remote_lsa_info.dns_domain,
2457 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2458 local_forest_info,
2460 except RuntimeError as error:
2461 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2463 self.write_forest_trust_info(local_forest_info,
2464 tln=remote_lsa_info.dns_domain.string,
2465 collisions=local_forest_collision)
2467 if remote_trust_info:
2468 self.outf.write("Setup remote forest trust information...\n")
2469 try:
2470 # get all information about the local trust (from the perspective of the remote domain)
2471 # this triggers netr_GetForestTrustInformation to our domain.
2472 # and lsaRSetForestTrustInformation() remotely, but new top level
2473 # names are disabled by default.
2474 remote_forest_info = remote_netlogon.netr_DsRGetForestTrustInformation(remote_netlogon_info.dc_unc,
2475 local_lsa_info.dns_domain.string,
2476 netlogon.DS_GFTI_UPDATE_TDO)
2477 except RuntimeError as error:
2478 raise self.RemoteRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2480 try:
2481 # here we try to enable all top level names
2482 remote_forest_collision = remote_lsa.lsaRSetForestTrustInformation(remote_policy,
2483 local_lsa_info.dns_domain,
2484 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2485 remote_forest_info,
2487 except RuntimeError as error:
2488 raise self.RemoteRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2490 self.write_forest_trust_info(remote_forest_info,
2491 tln=local_lsa_info.dns_domain.string,
2492 collisions=remote_forest_collision)
2494 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2495 self.outf.write("Validating outgoing trust...\n")
2496 try:
2497 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_netlogon_info.dc_unc,
2498 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2500 remote_lsa_info.dns_domain.string)
2501 except RuntimeError as error:
2502 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2504 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2505 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2507 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2508 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2509 local_trust_verify.trusted_dc_name,
2510 local_trust_verify.tc_connection_status[1],
2511 local_trust_verify.pdc_connection_status[1])
2512 else:
2513 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2514 local_trust_verify.trusted_dc_name,
2515 local_trust_verify.tc_connection_status[1],
2516 local_trust_verify.pdc_connection_status[1])
2518 if local_trust_status != self.WERR_OK or local_conn_status != self.WERR_OK:
2519 raise CommandError(local_validation)
2520 else:
2521 self.outf.write("OK: %s\n" % local_validation)
2523 if remote_trust_info:
2524 if remote_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2525 self.outf.write("Validating incoming trust...\n")
2526 try:
2527 remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_netlogon_info.dc_unc,
2528 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2530 local_lsa_info.dns_domain.string)
2531 except RuntimeError as error:
2532 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2534 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2535 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2537 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2538 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2539 remote_trust_verify.trusted_dc_name,
2540 remote_trust_verify.tc_connection_status[1],
2541 remote_trust_verify.pdc_connection_status[1])
2542 else:
2543 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2544 remote_trust_verify.trusted_dc_name,
2545 remote_trust_verify.tc_connection_status[1],
2546 remote_trust_verify.pdc_connection_status[1])
2548 if remote_trust_status != self.WERR_OK or remote_conn_status != self.WERR_OK:
2549 raise CommandError(remote_validation)
2550 else:
2551 self.outf.write("OK: %s\n" % remote_validation)
2553 if remote_tdo_handle is not None:
2554 try:
2555 remote_lsa.Close(remote_tdo_handle)
2556 except RuntimeError as error:
2557 pass
2558 remote_tdo_handle = None
2559 if local_tdo_handle is not None:
2560 try:
2561 local_lsa.Close(local_tdo_handle)
2562 except RuntimeError as error:
2563 pass
2564 local_tdo_handle = None
2566 self.outf.write("Success.\n")
2567 return
2569 class cmd_domain_trust_delete(DomainTrustCommand):
2570 """Delete a domain trust."""
2572 synopsis = "%prog DOMAIN [options]"
2574 takes_optiongroups = {
2575 "sambaopts": options.SambaOptions,
2576 "versionopts": options.VersionOptions,
2577 "credopts": options.CredentialsOptions,
2578 "localdcopts": LocalDCCredentialsOptions,
2581 takes_options = [
2582 Option("--delete-location", type="choice", metavar="LOCATION",
2583 choices=["local", "both"],
2584 help="Where to delete the trusted domain object: 'local' or 'both'.",
2585 dest='delete_location',
2586 default="both"),
2589 takes_args = ["domain"]
2591 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2592 delete_location=None):
2594 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2595 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2596 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2598 if delete_location == "local":
2599 remote_policy_access = None
2600 else:
2601 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2602 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2603 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2605 local_server = self.setup_local_server(sambaopts, localdcopts)
2606 try:
2607 local_lsa = self.new_local_lsa_connection()
2608 except RuntimeError as error:
2609 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2611 try:
2612 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2613 except RuntimeError as error:
2614 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2616 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2617 local_lsa_info.name.string,
2618 local_lsa_info.dns_domain.string,
2619 local_lsa_info.sid))
2621 local_tdo_info = None
2622 local_tdo_handle = None
2623 remote_tdo_info = None
2624 remote_tdo_handle = None
2626 lsaString = lsa.String()
2627 try:
2628 lsaString.string = domain
2629 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2630 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2631 except RuntimeError as error:
2632 if self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2633 raise CommandError("Failed to find trust for domain '%s'" % domain)
2634 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2637 if remote_policy_access is not None:
2638 try:
2639 remote_server = self.setup_remote_server(credopts, domain)
2640 except RuntimeError as error:
2641 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2643 try:
2644 remote_lsa = self.new_remote_lsa_connection()
2645 except RuntimeError as error:
2646 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2648 try:
2649 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2650 except RuntimeError as error:
2651 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2653 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2654 remote_lsa_info.name.string,
2655 remote_lsa_info.dns_domain.string,
2656 remote_lsa_info.sid))
2658 if remote_lsa_info.sid != local_tdo_info.sid or \
2659 remote_lsa_info.name.string != local_tdo_info.netbios_name.string or \
2660 remote_lsa_info.dns_domain.string != local_tdo_info.domain_name.string:
2661 raise CommandError("LocalTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2662 local_tdo_info.netbios_name.string,
2663 local_tdo_info.domain_name.string,
2664 local_tdo_info.sid))
2666 try:
2667 lsaString.string = local_lsa_info.dns_domain.string
2668 remote_tdo_info = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2669 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2670 except RuntimeError as error:
2671 if not self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2672 raise self.RemoteRuntimeError(self, error, "QueryTrustedDomainInfoByName(%s)" % (
2673 lsaString.string))
2674 pass
2676 if remote_tdo_info is not None:
2677 if local_lsa_info.sid != remote_tdo_info.sid or \
2678 local_lsa_info.name.string != remote_tdo_info.netbios_name.string or \
2679 local_lsa_info.dns_domain.string != remote_tdo_info.domain_name.string:
2680 raise CommandError("RemoteTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2681 remote_tdo_info.netbios_name.string,
2682 remote_tdo_info.domain_name.string,
2683 remote_tdo_info.sid))
2685 if local_tdo_info is not None:
2686 try:
2687 lsaString.string = local_tdo_info.domain_name.string
2688 local_tdo_handle = local_lsa.OpenTrustedDomainByName(local_policy,
2689 lsaString,
2690 security.SEC_STD_DELETE)
2691 except RuntimeError as error:
2692 raise self.LocalRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2693 lsaString.string))
2695 local_lsa.DeleteObject(local_tdo_handle)
2696 local_tdo_handle = None
2698 if remote_tdo_info is not None:
2699 try:
2700 lsaString.string = remote_tdo_info.domain_name.string
2701 remote_tdo_handle = remote_lsa.OpenTrustedDomainByName(remote_policy,
2702 lsaString,
2703 security.SEC_STD_DELETE)
2704 except RuntimeError as error:
2705 raise self.RemoteRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2706 lsaString.string))
2708 if remote_tdo_handle is not None:
2709 try:
2710 remote_lsa.DeleteObject(remote_tdo_handle)
2711 remote_tdo_handle = None
2712 self.outf.write("RemoteTDO deleted.\n")
2713 except RuntimeError as error:
2714 self.outf.write("%s\n" % self.RemoteRuntimeError(self, error, "DeleteObject() failed"))
2716 if local_tdo_handle is not None:
2717 try:
2718 local_lsa.DeleteObject(local_tdo_handle)
2719 local_tdo_handle = None
2720 self.outf.write("LocalTDO deleted.\n")
2721 except RuntimeError as error:
2722 self.outf.write("%s\n" % self.LocalRuntimeError(self, error, "DeleteObject() failed"))
2724 return
2726 class cmd_domain_trust_validate(DomainTrustCommand):
2727 """Validate a domain trust."""
2729 synopsis = "%prog DOMAIN [options]"
2731 takes_optiongroups = {
2732 "sambaopts": options.SambaOptions,
2733 "versionopts": options.VersionOptions,
2734 "credopts": options.CredentialsOptions,
2735 "localdcopts": LocalDCCredentialsOptions,
2738 takes_options = [
2739 Option("--validate-location", type="choice", metavar="LOCATION",
2740 choices=["local", "both"],
2741 help="Where to validate the trusted domain object: 'local' or 'both'.",
2742 dest='validate_location',
2743 default="both"),
2746 takes_args = ["domain"]
2748 def run(self, domain, sambaopts=None, versionopts=None, credopts=None, localdcopts=None,
2749 validate_location=None):
2751 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2753 local_server = self.setup_local_server(sambaopts, localdcopts)
2754 try:
2755 local_lsa = self.new_local_lsa_connection()
2756 except RuntimeError as error:
2757 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2759 try:
2760 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2761 except RuntimeError as error:
2762 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2764 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2765 local_lsa_info.name.string,
2766 local_lsa_info.dns_domain.string,
2767 local_lsa_info.sid))
2769 try:
2770 lsaString = lsa.String()
2771 lsaString.string = domain
2772 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2773 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2774 except RuntimeError as error:
2775 if self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2776 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2778 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
2780 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
2781 local_tdo_info.netbios_name.string,
2782 local_tdo_info.domain_name.string,
2783 local_tdo_info.sid))
2785 try:
2786 local_netlogon = self.new_local_netlogon_connection()
2787 except RuntimeError as error:
2788 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2790 try:
2791 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_server,
2792 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2794 local_tdo_info.domain_name.string)
2795 except RuntimeError as error:
2796 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2798 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2799 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2801 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2802 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2803 local_trust_verify.trusted_dc_name,
2804 local_trust_verify.tc_connection_status[1],
2805 local_trust_verify.pdc_connection_status[1])
2806 else:
2807 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2808 local_trust_verify.trusted_dc_name,
2809 local_trust_verify.tc_connection_status[1],
2810 local_trust_verify.pdc_connection_status[1])
2812 if local_trust_status != self.WERR_OK or local_conn_status != self.WERR_OK:
2813 raise CommandError(local_validation)
2814 else:
2815 self.outf.write("OK: %s\n" % local_validation)
2817 try:
2818 server = local_trust_verify.trusted_dc_name.replace('\\', '')
2819 domain_and_server = "%s\\%s" % (local_tdo_info.domain_name.string, server)
2820 local_trust_rediscover = local_netlogon.netr_LogonControl2Ex(local_server,
2821 netlogon.NETLOGON_CONTROL_REDISCOVER,
2823 domain_and_server)
2824 except RuntimeError as error:
2825 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
2827 local_conn_status = self._uint32(local_trust_rediscover.tc_connection_status[0])
2828 local_rediscover = "LocalRediscover: DC[%s] CONNECTION[%s]" % (
2829 local_trust_rediscover.trusted_dc_name,
2830 local_trust_rediscover.tc_connection_status[1])
2832 if local_conn_status != self.WERR_OK:
2833 raise CommandError(local_rediscover)
2834 else:
2835 self.outf.write("OK: %s\n" % local_rediscover)
2837 if validate_location != "local":
2838 try:
2839 remote_server = self.setup_remote_server(credopts, domain, require_pdc=False)
2840 except RuntimeError as error:
2841 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2843 try:
2844 remote_netlogon = self.new_remote_netlogon_connection()
2845 except RuntimeError as error:
2846 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2848 try:
2849 remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_server,
2850 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2852 local_lsa_info.dns_domain.string)
2853 except RuntimeError as error:
2854 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2856 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2857 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2859 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2860 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2861 remote_trust_verify.trusted_dc_name,
2862 remote_trust_verify.tc_connection_status[1],
2863 remote_trust_verify.pdc_connection_status[1])
2864 else:
2865 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2866 remote_trust_verify.trusted_dc_name,
2867 remote_trust_verify.tc_connection_status[1],
2868 remote_trust_verify.pdc_connection_status[1])
2870 if remote_trust_status != self.WERR_OK or remote_conn_status != self.WERR_OK:
2871 raise CommandError(remote_validation)
2872 else:
2873 self.outf.write("OK: %s\n" % remote_validation)
2875 try:
2876 server = remote_trust_verify.trusted_dc_name.replace('\\', '')
2877 domain_and_server = "%s\\%s" % (local_lsa_info.dns_domain.string, server)
2878 remote_trust_rediscover = remote_netlogon.netr_LogonControl2Ex(remote_server,
2879 netlogon.NETLOGON_CONTROL_REDISCOVER,
2881 domain_and_server)
2882 except RuntimeError as error:
2883 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
2885 remote_conn_status = self._uint32(remote_trust_rediscover.tc_connection_status[0])
2887 remote_rediscover = "RemoteRediscover: DC[%s] CONNECTION[%s]" % (
2888 remote_trust_rediscover.trusted_dc_name,
2889 remote_trust_rediscover.tc_connection_status[1])
2891 if remote_conn_status != self.WERR_OK:
2892 raise CommandError(remote_rediscover)
2893 else:
2894 self.outf.write("OK: %s\n" % remote_rediscover)
2896 return
2898 class cmd_domain_trust_namespaces(DomainTrustCommand):
2899 """Manage forest trust namespaces."""
2901 synopsis = "%prog [DOMAIN] [options]"
2903 takes_optiongroups = {
2904 "sambaopts": options.SambaOptions,
2905 "versionopts": options.VersionOptions,
2906 "localdcopts": LocalDCCredentialsOptions,
2909 takes_options = [
2910 Option("--refresh", type="choice", metavar="check|store",
2911 choices=["check", "store", None],
2912 help="List and maybe store refreshed forest trust information: 'check' or 'store'.",
2913 dest='refresh',
2914 default=None),
2915 Option("--enable-all", action="store_true",
2916 help="Try to update disabled entries, not allowed with --refresh=check.",
2917 dest='enable_all',
2918 default=False),
2919 Option("--enable-tln", action="append", metavar='DNSDOMAIN',
2920 help="Enable a top level name entry. Can be specified multiple times.",
2921 dest='enable_tln',
2922 default=[]),
2923 Option("--disable-tln", action="append", metavar='DNSDOMAIN',
2924 help="Disable a top level name entry. Can be specified multiple times.",
2925 dest='disable_tln',
2926 default=[]),
2927 Option("--add-tln-ex", action="append", metavar='DNSDOMAIN',
2928 help="Add a top level exclusion entry. Can be specified multiple times.",
2929 dest='add_tln_ex',
2930 default=[]),
2931 Option("--delete-tln-ex", action="append", metavar='DNSDOMAIN',
2932 help="Delete a top level exclusion entry. Can be specified multiple times.",
2933 dest='delete_tln_ex',
2934 default=[]),
2935 Option("--enable-nb", action="append", metavar='NETBIOSDOMAIN',
2936 help="Enable a netbios name in a domain entry. Can be specified multiple times.",
2937 dest='enable_nb',
2938 default=[]),
2939 Option("--disable-nb", action="append", metavar='NETBIOSDOMAIN',
2940 help="Disable a netbios name in a domain entry. Can be specified multiple times.",
2941 dest='disable_nb',
2942 default=[]),
2943 Option("--enable-sid", action="append", metavar='DOMAINSID',
2944 help="Enable a SID in a domain entry. Can be specified multiple times.",
2945 dest='enable_sid_str',
2946 default=[]),
2947 Option("--disable-sid", action="append", metavar='DOMAINSID',
2948 help="Disable a SID in a domain entry. Can be specified multiple times.",
2949 dest='disable_sid_str',
2950 default=[]),
2951 Option("--add-upn-suffix", action="append", metavar='DNSDOMAIN',
2952 help="Add a new uPNSuffixes attribute for the local forest. Can be specified multiple times.",
2953 dest='add_upn',
2954 default=[]),
2955 Option("--delete-upn-suffix", action="append", metavar='DNSDOMAIN',
2956 help="Delete an existing uPNSuffixes attribute of the local forest. Can be specified multiple times.",
2957 dest='delete_upn',
2958 default=[]),
2959 Option("--add-spn-suffix", action="append", metavar='DNSDOMAIN',
2960 help="Add a new msDS-SPNSuffixes attribute for the local forest. Can be specified multiple times.",
2961 dest='add_spn',
2962 default=[]),
2963 Option("--delete-spn-suffix", action="append", metavar='DNSDOMAIN',
2964 help="Delete an existing msDS-SPNSuffixes attribute of the local forest. Can be specified multiple times.",
2965 dest='delete_spn',
2966 default=[]),
2969 takes_args = ["domain?"]
2971 def run(self, domain=None, sambaopts=None, localdcopts=None, versionopts=None,
2972 refresh=None, enable_all=False,
2973 enable_tln=[], disable_tln=[], add_tln_ex=[], delete_tln_ex=[],
2974 enable_sid_str=[], disable_sid_str=[], enable_nb=[], disable_nb=[],
2975 add_upn=[], delete_upn=[], add_spn=[], delete_spn=[]):
2977 require_update = False
2979 if domain is None:
2980 if refresh == "store":
2981 raise CommandError("--refresh=%s not allowed without DOMAIN" % refresh)
2983 if enable_all:
2984 raise CommandError("--enable-all not allowed without DOMAIN")
2986 if len(enable_tln) > 0:
2987 raise CommandError("--enable-tln not allowed without DOMAIN")
2988 if len(disable_tln) > 0:
2989 raise CommandError("--disable-tln not allowed without DOMAIN")
2991 if len(add_tln_ex) > 0:
2992 raise CommandError("--add-tln-ex not allowed without DOMAIN")
2993 if len(delete_tln_ex) > 0:
2994 raise CommandError("--delete-tln-ex not allowed without DOMAIN")
2996 if len(enable_nb) > 0:
2997 raise CommandError("--enable-nb not allowed without DOMAIN")
2998 if len(disable_nb) > 0:
2999 raise CommandError("--disable-nb not allowed without DOMAIN")
3001 if len(enable_sid_str) > 0:
3002 raise CommandError("--enable-sid not allowed without DOMAIN")
3003 if len(disable_sid_str) > 0:
3004 raise CommandError("--disable-sid not allowed without DOMAIN")
3006 if len(add_upn) > 0:
3007 for n in add_upn:
3008 if not n.startswith("*."):
3009 continue
3010 raise CommandError("value[%s] specified for --add-upn-suffix should not include with '*.'" % n)
3011 require_update = True
3012 if len(delete_upn) > 0:
3013 for n in delete_upn:
3014 if not n.startswith("*."):
3015 continue
3016 raise CommandError("value[%s] specified for --delete-upn-suffix should not include with '*.'" % n)
3017 require_update = True
3018 for a in add_upn:
3019 for d in delete_upn:
3020 if a.lower() != d.lower():
3021 continue
3022 raise CommandError("value[%s] specified for --add-upn-suffix and --delete-upn-suffix" % a)
3024 if len(add_spn) > 0:
3025 for n in add_spn:
3026 if not n.startswith("*."):
3027 continue
3028 raise CommandError("value[%s] specified for --add-spn-suffix should not include with '*.'" % n)
3029 require_update = True
3030 if len(delete_spn) > 0:
3031 for n in delete_spn:
3032 if not n.startswith("*."):
3033 continue
3034 raise CommandError("value[%s] specified for --delete-spn-suffix should not include with '*.'" % n)
3035 require_update = True
3036 for a in add_spn:
3037 for d in delete_spn:
3038 if a.lower() != d.lower():
3039 continue
3040 raise CommandError("value[%s] specified for --add-spn-suffix and --delete-spn-suffix" % a)
3041 else:
3042 if len(add_upn) > 0:
3043 raise CommandError("--add-upn-suffix not allowed together with DOMAIN")
3044 if len(delete_upn) > 0:
3045 raise CommandError("--delete-upn-suffix not allowed together with DOMAIN")
3046 if len(add_spn) > 0:
3047 raise CommandError("--add-spn-suffix not allowed together with DOMAIN")
3048 if len(delete_spn) > 0:
3049 raise CommandError("--delete-spn-suffix not allowed together with DOMAIN")
3051 if refresh is not None:
3052 if refresh == "store":
3053 require_update = True
3055 if enable_all and refresh != "store":
3056 raise CommandError("--enable-all not allowed together with --refresh=%s" % refresh)
3058 if len(enable_tln) > 0:
3059 raise CommandError("--enable-tln not allowed together with --refresh")
3060 if len(disable_tln) > 0:
3061 raise CommandError("--disable-tln not allowed together with --refresh")
3063 if len(add_tln_ex) > 0:
3064 raise CommandError("--add-tln-ex not allowed together with --refresh")
3065 if len(delete_tln_ex) > 0:
3066 raise CommandError("--delete-tln-ex not allowed together with --refresh")
3068 if len(enable_nb) > 0:
3069 raise CommandError("--enable-nb not allowed together with --refresh")
3070 if len(disable_nb) > 0:
3071 raise CommandError("--disable-nb not allowed together with --refresh")
3073 if len(enable_sid_str) > 0:
3074 raise CommandError("--enable-sid not allowed together with --refresh")
3075 if len(disable_sid_str) > 0:
3076 raise CommandError("--disable-sid not allowed together with --refresh")
3077 else:
3078 if enable_all:
3079 require_update = True
3081 if len(enable_tln) > 0:
3082 raise CommandError("--enable-tln not allowed together with --enable-all")
3084 if len(enable_nb) > 0:
3085 raise CommandError("--enable-nb not allowed together with --enable-all")
3087 if len(enable_sid) > 0:
3088 raise CommandError("--enable-sid not allowed together with --enable-all")
3090 if len(enable_tln) > 0:
3091 require_update = True
3092 if len(disable_tln) > 0:
3093 require_update = True
3094 for e in enable_tln:
3095 for d in disable_tln:
3096 if e.lower() != d.lower():
3097 continue
3098 raise CommandError("value[%s] specified for --enable-tln and --disable-tln" % e)
3100 if len(add_tln_ex) > 0:
3101 for n in add_tln_ex:
3102 if not n.startswith("*."):
3103 continue
3104 raise CommandError("value[%s] specified for --add-tln-ex should not include with '*.'" % n)
3105 require_update = True
3106 if len(delete_tln_ex) > 0:
3107 for n in delete_tln_ex:
3108 if not n.startswith("*."):
3109 continue
3110 raise CommandError("value[%s] specified for --delete-tln-ex should not include with '*.'" % n)
3111 require_update = True
3112 for a in add_tln_ex:
3113 for d in delete_tln_ex:
3114 if a.lower() != d.lower():
3115 continue
3116 raise CommandError("value[%s] specified for --add-tln-ex and --delete-tln-ex" % a)
3118 if len(enable_nb) > 0:
3119 require_update = True
3120 if len(disable_nb) > 0:
3121 require_update = True
3122 for e in enable_nb:
3123 for d in disable_nb:
3124 if e.upper() != d.upper():
3125 continue
3126 raise CommandError("value[%s] specified for --enable-nb and --disable-nb" % e)
3128 enable_sid = []
3129 for s in enable_sid_str:
3130 try:
3131 sid = security.dom_sid(s)
3132 except TypeError as error:
3133 raise CommandError("value[%s] specified for --enable-sid is not a valid SID" % s)
3134 enable_sid.append(sid)
3135 disable_sid = []
3136 for s in disable_sid_str:
3137 try:
3138 sid = security.dom_sid(s)
3139 except TypeError as error:
3140 raise CommandError("value[%s] specified for --disable-sid is not a valid SID" % s)
3141 disable_sid.append(sid)
3142 if len(enable_sid) > 0:
3143 require_update = True
3144 if len(disable_sid) > 0:
3145 require_update = True
3146 for e in enable_sid:
3147 for d in disable_sid:
3148 if e != d:
3149 continue
3150 raise CommandError("value[%s] specified for --enable-sid and --disable-sid" % e)
3152 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
3153 if require_update:
3154 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
3156 local_server = self.setup_local_server(sambaopts, localdcopts)
3157 try:
3158 local_lsa = self.new_local_lsa_connection()
3159 except RuntimeError as error:
3160 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
3162 try:
3163 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
3164 except RuntimeError as error:
3165 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
3167 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
3168 local_lsa_info.name.string,
3169 local_lsa_info.dns_domain.string,
3170 local_lsa_info.sid))
3172 if domain is None:
3173 try:
3174 local_netlogon = self.new_local_netlogon_connection()
3175 except RuntimeError as error:
3176 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3178 try:
3179 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3180 except RuntimeError as error:
3181 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3183 if local_netlogon_info.domain_name != local_netlogon_info.forest_name:
3184 raise CommandError("The local domain [%s] is not the forest root [%s]" % (
3185 local_netlogon_info.domain_name,
3186 local_netlogon_info.forest_name))
3188 try:
3189 # get all information about our own forest
3190 own_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3191 None, 0)
3192 except RuntimeError as error:
3193 if self.check_runtime_error(error, self.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE):
3194 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3195 self.local_server))
3197 if self.check_runtime_error(error, self.WERR_INVALID_FUNCTION):
3198 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3199 self.local_server))
3201 if self.check_runtime_error(error, self.WERR_NERR_ACFNOTLOADED):
3202 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3203 self.local_server))
3205 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3207 self.outf.write("Own forest trust information...\n")
3208 self.write_forest_trust_info(own_forest_info,
3209 tln=local_lsa_info.dns_domain.string)
3211 try:
3212 local_samdb = self.new_local_ldap_connection()
3213 except RuntimeError as error:
3214 raise self.LocalRuntimeError(self, error, "failed to connect to SamDB")
3216 local_partitions_dn = "CN=Partitions,%s" % str(local_samdb.get_config_basedn())
3217 attrs = ['uPNSuffixes', 'msDS-SPNSuffixes']
3218 try:
3219 msgs = local_samdb.search(base=local_partitions_dn,
3220 scope=ldb.SCOPE_BASE,
3221 expression="(objectClass=crossRefContainer)",
3222 attrs=attrs)
3223 stored_msg = msgs[0]
3224 except ldb.LdbError as error:
3225 raise self.LocalLdbError(self, error, "failed to search partition dn")
3227 stored_upn_vals = []
3228 if 'uPNSuffixes' in stored_msg:
3229 stored_upn_vals.extend(stored_msg['uPNSuffixes'])
3231 stored_spn_vals = []
3232 if 'msDS-SPNSuffixes' in stored_msg:
3233 stored_spn_vals.extend(stored_msg['msDS-SPNSuffixes'])
3235 self.outf.write("Stored uPNSuffixes attributes[%d]:\n" % len(stored_upn_vals))
3236 for v in stored_upn_vals:
3237 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3238 self.outf.write("Stored msDS-SPNSuffixes attributes[%d]:\n" % len(stored_spn_vals))
3239 for v in stored_spn_vals:
3240 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3242 if not require_update:
3243 return
3245 replace_upn = False
3246 update_upn_vals = []
3247 update_upn_vals.extend(stored_upn_vals)
3249 replace_spn = False
3250 update_spn_vals = []
3251 update_spn_vals.extend(stored_spn_vals)
3253 for upn in add_upn:
3254 idx = None
3255 for i in xrange(0, len(update_upn_vals)):
3256 v = update_upn_vals[i]
3257 if v.lower() != upn.lower():
3258 continue
3259 idx = i
3260 break
3261 if idx is not None:
3262 raise CommandError("Entry already present for value[%s] specified for --add-upn-suffix" % upn)
3263 update_upn_vals.append(upn)
3264 replace_upn = True
3266 for upn in delete_upn:
3267 idx = None
3268 for i in xrange(0, len(update_upn_vals)):
3269 v = update_upn_vals[i]
3270 if v.lower() != upn.lower():
3271 continue
3272 idx = i
3273 break
3274 if idx is None:
3275 raise CommandError("Entry not found for value[%s] specified for --delete-upn-suffix" % upn)
3277 update_upn_vals.pop(idx)
3278 replace_upn = True
3280 for spn in add_spn:
3281 idx = None
3282 for i in xrange(0, len(update_spn_vals)):
3283 v = update_spn_vals[i]
3284 if v.lower() != spn.lower():
3285 continue
3286 idx = i
3287 break
3288 if idx is not None:
3289 raise CommandError("Entry already present for value[%s] specified for --add-spn-suffix" % spn)
3290 update_spn_vals.append(spn)
3291 replace_spn = True
3293 for spn in delete_spn:
3294 idx = None
3295 for i in xrange(0, len(update_spn_vals)):
3296 v = update_spn_vals[i]
3297 if v.lower() != spn.lower():
3298 continue
3299 idx = i
3300 break
3301 if idx is None:
3302 raise CommandError("Entry not found for value[%s] specified for --delete-spn-suffix" % spn)
3304 update_spn_vals.pop(idx)
3305 replace_spn = True
3307 self.outf.write("Update uPNSuffixes attributes[%d]:\n" % len(update_upn_vals))
3308 for v in update_upn_vals:
3309 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3310 self.outf.write("Update msDS-SPNSuffixes attributes[%d]:\n" % len(update_spn_vals))
3311 for v in update_spn_vals:
3312 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3314 update_msg = ldb.Message()
3315 update_msg.dn = stored_msg.dn
3317 if replace_upn:
3318 update_msg['uPNSuffixes'] = ldb.MessageElement(update_upn_vals,
3319 ldb.FLAG_MOD_REPLACE,
3320 'uPNSuffixes')
3321 if replace_spn:
3322 update_msg['msDS-SPNSuffixes'] = ldb.MessageElement(update_spn_vals,
3323 ldb.FLAG_MOD_REPLACE,
3324 'msDS-SPNSuffixes')
3325 try:
3326 local_samdb.modify(update_msg)
3327 except ldb.LdbError as error:
3328 raise self.LocalLdbError(self, error, "failed to update partition dn")
3330 try:
3331 stored_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3332 None, 0)
3333 except RuntimeError as error:
3334 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3336 self.outf.write("Stored forest trust information...\n")
3337 self.write_forest_trust_info(stored_forest_info,
3338 tln=local_lsa_info.dns_domain.string)
3339 return
3341 try:
3342 lsaString = lsa.String()
3343 lsaString.string = domain
3344 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
3345 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
3346 except RuntimeError as error:
3347 if self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND):
3348 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
3350 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
3352 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
3353 local_tdo_info.netbios_name.string,
3354 local_tdo_info.domain_name.string,
3355 local_tdo_info.sid))
3357 if not local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
3358 raise CommandError("trusted domain object for domain [%s] is not marked as FOREST_TRANSITIVE." % domain)
3360 if refresh is not None:
3361 try:
3362 local_netlogon = self.new_local_netlogon_connection()
3363 except RuntimeError as error:
3364 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3366 try:
3367 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3368 except RuntimeError as error:
3369 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3371 lsa_update_check = 1
3372 if refresh == "store":
3373 netlogon_update_tdo = netlogon.DS_GFTI_UPDATE_TDO
3374 if enable_all:
3375 lsa_update_check = 0
3376 else:
3377 netlogon_update_tdo = 0
3379 try:
3380 # get all information about the remote trust
3381 # this triggers netr_GetForestTrustInformation to the remote domain
3382 # and lsaRSetForestTrustInformation() locally, but new top level
3383 # names are disabled by default.
3384 fresh_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3385 local_tdo_info.domain_name.string,
3386 netlogon_update_tdo)
3387 except RuntimeError as error:
3388 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3390 try:
3391 fresh_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3392 local_tdo_info.domain_name,
3393 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3394 fresh_forest_info,
3395 lsa_update_check)
3396 except RuntimeError as error:
3397 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3399 self.outf.write("Fresh forest trust information...\n")
3400 self.write_forest_trust_info(fresh_forest_info,
3401 tln=local_tdo_info.domain_name.string,
3402 collisions=fresh_forest_collision)
3404 if refresh == "store":
3405 try:
3406 lsaString = lsa.String()
3407 lsaString.string = local_tdo_info.domain_name.string
3408 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3409 lsaString,
3410 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3411 except RuntimeError as error:
3412 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3414 self.outf.write("Stored forest trust information...\n")
3415 self.write_forest_trust_info(stored_forest_info,
3416 tln=local_tdo_info.domain_name.string)
3418 return
3421 # The none --refresh path
3424 try:
3425 lsaString = lsa.String()
3426 lsaString.string = local_tdo_info.domain_name.string
3427 local_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3428 lsaString,
3429 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3430 except RuntimeError as error:
3431 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3433 self.outf.write("Local forest trust information...\n")
3434 self.write_forest_trust_info(local_forest_info,
3435 tln=local_tdo_info.domain_name.string)
3437 if not require_update:
3438 return
3440 entries = []
3441 entries.extend(local_forest_info.entries)
3442 update_forest_info = lsa.ForestTrustInformation()
3443 update_forest_info.count = len(entries)
3444 update_forest_info.entries = entries
3446 if enable_all:
3447 for i in xrange(0, len(update_forest_info.entries)):
3448 r = update_forest_info.entries[i]
3449 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3450 continue
3451 if update_forest_info.entries[i].flags == 0:
3452 continue
3453 update_forest_info.entries[i].time = 0
3454 update_forest_info.entries[i].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3455 for i in xrange(0, len(update_forest_info.entries)):
3456 r = update_forest_info.entries[i]
3457 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3458 continue
3459 if update_forest_info.entries[i].flags == 0:
3460 continue
3461 update_forest_info.entries[i].time = 0
3462 update_forest_info.entries[i].flags &= ~lsa.LSA_NB_DISABLED_MASK
3463 update_forest_info.entries[i].flags &= ~lsa.LSA_SID_DISABLED_MASK
3465 for tln in enable_tln:
3466 idx = None
3467 for i in xrange(0, len(update_forest_info.entries)):
3468 r = update_forest_info.entries[i]
3469 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3470 continue
3471 if r.forest_trust_data.string.lower() != tln.lower():
3472 continue
3473 idx = i
3474 break
3475 if idx is None:
3476 raise CommandError("Entry not found for value[%s] specified for --enable-tln" % tln)
3477 if not update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_MASK:
3478 raise CommandError("Entry found for value[%s] specified for --enable-tln is already enabled" % tln)
3479 update_forest_info.entries[idx].time = 0
3480 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3482 for tln in disable_tln:
3483 idx = None
3484 for i in xrange(0, len(update_forest_info.entries)):
3485 r = update_forest_info.entries[i]
3486 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3487 continue
3488 if r.forest_trust_data.string.lower() != tln.lower():
3489 continue
3490 idx = i
3491 break
3492 if idx is None:
3493 raise CommandError("Entry not found for value[%s] specified for --disable-tln" % tln)
3494 if update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_ADMIN:
3495 raise CommandError("Entry found for value[%s] specified for --disable-tln is already disabled" % tln)
3496 update_forest_info.entries[idx].time = 0
3497 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3498 update_forest_info.entries[idx].flags |= lsa.LSA_TLN_DISABLED_ADMIN
3500 for tln_ex in add_tln_ex:
3501 idx = None
3502 for i in xrange(0, len(update_forest_info.entries)):
3503 r = update_forest_info.entries[i]
3504 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3505 continue
3506 if r.forest_trust_data.string.lower() != tln_ex.lower():
3507 continue
3508 idx = i
3509 break
3510 if idx is not None:
3511 raise CommandError("Entry already present for value[%s] specified for --add-tln-ex" % tln_ex)
3513 tln_dot = ".%s" % tln_ex.lower()
3514 idx = None
3515 for i in xrange(0, len(update_forest_info.entries)):
3516 r = update_forest_info.entries[i]
3517 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3518 continue
3519 r_dot = ".%s" % r.forest_trust_data.string.lower()
3520 if tln_dot == r_dot:
3521 raise CommandError("TLN entry present for value[%s] specified for --add-tln-ex" % tln_ex)
3522 if not tln_dot.endswith(r_dot):
3523 continue
3524 idx = i
3525 break
3527 if idx is None:
3528 raise CommandError("No TLN parent present for value[%s] specified for --add-tln-ex" % tln_ex)
3530 r = lsa.ForestTrustRecord()
3531 r.type = lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX
3532 r.flags = 0
3533 r.time = 0
3534 r.forest_trust_data.string = tln_ex
3536 entries = []
3537 entries.extend(update_forest_info.entries)
3538 entries.insert(idx + 1, r)
3539 update_forest_info.count = len(entries)
3540 update_forest_info.entries = entries
3542 for tln_ex in delete_tln_ex:
3543 idx = None
3544 for i in xrange(0, len(update_forest_info.entries)):
3545 r = update_forest_info.entries[i]
3546 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3547 continue
3548 if r.forest_trust_data.string.lower() != tln_ex.lower():
3549 continue
3550 idx = i
3551 break
3552 if idx is None:
3553 raise CommandError("Entry not found for value[%s] specified for --delete-tln-ex" % tln_ex)
3555 entries = []
3556 entries.extend(update_forest_info.entries)
3557 entries.pop(idx)
3558 update_forest_info.count = len(entries)
3559 update_forest_info.entries = entries
3561 for nb in enable_nb:
3562 idx = None
3563 for i in xrange(0, len(update_forest_info.entries)):
3564 r = update_forest_info.entries[i]
3565 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3566 continue
3567 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3568 continue
3569 idx = i
3570 break
3571 if idx is None:
3572 raise CommandError("Entry not found for value[%s] specified for --enable-nb" % nb)
3573 if not update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_MASK:
3574 raise CommandError("Entry found for value[%s] specified for --enable-nb is already enabled" % nb)
3575 update_forest_info.entries[idx].time = 0
3576 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3578 for nb in disable_nb:
3579 idx = None
3580 for i in xrange(0, len(update_forest_info.entries)):
3581 r = update_forest_info.entries[i]
3582 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3583 continue
3584 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3585 continue
3586 idx = i
3587 break
3588 if idx is None:
3589 raise CommandError("Entry not found for value[%s] specified for --delete-nb" % nb)
3590 if update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_ADMIN:
3591 raise CommandError("Entry found for value[%s] specified for --disable-nb is already disabled" % nb)
3592 update_forest_info.entries[idx].time = 0
3593 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3594 update_forest_info.entries[idx].flags |= lsa.LSA_NB_DISABLED_ADMIN
3596 for sid in enable_sid:
3597 idx = None
3598 for i in xrange(0, len(update_forest_info.entries)):
3599 r = update_forest_info.entries[i]
3600 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3601 continue
3602 if r.forest_trust_data.domain_sid != sid:
3603 continue
3604 idx = i
3605 break
3606 if idx is None:
3607 raise CommandError("Entry not found for value[%s] specified for --enable-sid" % sid)
3608 if not update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_MASK:
3609 raise CommandError("Entry found for value[%s] specified for --enable-sid is already enabled" % nb)
3610 update_forest_info.entries[idx].time = 0
3611 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3613 for sid in disable_sid:
3614 idx = None
3615 for i in xrange(0, len(update_forest_info.entries)):
3616 r = update_forest_info.entries[i]
3617 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3618 continue
3619 if r.forest_trust_data.domain_sid != sid:
3620 continue
3621 idx = i
3622 break
3623 if idx is None:
3624 raise CommandError("Entry not found for value[%s] specified for --delete-sid" % sid)
3625 if update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_ADMIN:
3626 raise CommandError("Entry found for value[%s] specified for --disable-sid is already disabled" % nb)
3627 update_forest_info.entries[idx].time = 0
3628 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3629 update_forest_info.entries[idx].flags |= lsa.LSA_SID_DISABLED_ADMIN
3631 try:
3632 update_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3633 local_tdo_info.domain_name,
3634 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3635 update_forest_info, 0)
3636 except RuntimeError as error:
3637 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3639 self.outf.write("Updated forest trust information...\n")
3640 self.write_forest_trust_info(update_forest_info,
3641 tln=local_tdo_info.domain_name.string,
3642 collisions=update_forest_collision)
3644 try:
3645 lsaString = lsa.String()
3646 lsaString.string = local_tdo_info.domain_name.string
3647 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3648 lsaString,
3649 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3650 except RuntimeError as error:
3651 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3653 self.outf.write("Stored forest trust information...\n")
3654 self.write_forest_trust_info(stored_forest_info,
3655 tln=local_tdo_info.domain_name.string)
3656 return
3658 class cmd_domain_trust(SuperCommand):
3659 """Domain and forest trust management."""
3661 subcommands = {}
3662 subcommands["list"] = cmd_domain_trust_list()
3663 subcommands["show"] = cmd_domain_trust_show()
3664 subcommands["create"] = cmd_domain_trust_create()
3665 subcommands["delete"] = cmd_domain_trust_delete()
3666 subcommands["validate"] = cmd_domain_trust_validate()
3667 subcommands["namespaces"] = cmd_domain_trust_namespaces()
3669 class cmd_domain(SuperCommand):
3670 """Domain management."""
3672 subcommands = {}
3673 subcommands["demote"] = cmd_domain_demote()
3674 if cmd_domain_export_keytab is not None:
3675 subcommands["exportkeytab"] = cmd_domain_export_keytab()
3676 subcommands["info"] = cmd_domain_info()
3677 subcommands["provision"] = cmd_domain_provision()
3678 subcommands["join"] = cmd_domain_join()
3679 subcommands["dcpromo"] = cmd_domain_dcpromo()
3680 subcommands["level"] = cmd_domain_level()
3681 subcommands["passwordsettings"] = cmd_domain_passwordsettings()
3682 subcommands["classicupgrade"] = cmd_domain_classicupgrade()
3683 subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()
3684 subcommands["trust"] = cmd_domain_trust()