samba-tool domain: improve error message when `patch` fails
[Samba.git] / python / samba / netcmd / domain.py
blob91c82297e26ede2c0bd91017bfce61262b96287d
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-2015
9 # Copyright Stefan Metzmacher 2012
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import samba.getopt as options
26 import ldb
27 import os
28 import sys
29 import ctypes
30 import random
31 import tempfile
32 import logging
33 import subprocess
34 import time
35 import shutil
36 from samba import ntstatus
37 from samba import NTSTATUSError
38 from samba import werror
39 from getpass import getpass
40 from samba.net import Net, LIBNET_JOIN_AUTOMATIC
41 from samba import enable_net_export_keytab
42 import samba.ntacls
43 from samba.join import join_RODC, join_DC
44 from samba.auth import system_session
45 from samba.samdb import SamDB, get_default_backend_store
46 from samba.ndr import ndr_pack, ndr_print
47 from samba.dcerpc import drsuapi
48 from samba.dcerpc import drsblobs
49 from samba.dcerpc import lsa
50 from samba.dcerpc import netlogon
51 from samba.dcerpc import security
52 from samba.dcerpc import nbt
53 from samba.dcerpc import misc
54 from samba.dcerpc.samr import DOMAIN_PASSWORD_COMPLEX, DOMAIN_PASSWORD_STORE_CLEARTEXT
55 from samba.netcmd import (
56 Command,
57 CommandError,
58 SuperCommand,
59 Option
61 from samba.netcmd.fsmo import get_fsmo_roleowner
62 from samba.netcmd.common import netcmd_get_domain_infos_via_cldap
63 from samba.netcmd.common import (NEVER_TIMESTAMP,
64 timestamp_to_mins,
65 timestamp_to_days)
66 from samba.samba3 import Samba3
67 from samba.samba3 import param as s3param
68 from samba.upgrade import upgrade_from_samba3
69 from samba.drs_utils import drsuapi_connect
70 from samba import remove_dc, arcfour_encrypt, string_to_byte_array
71 from samba.auth_util import system_session_unix
72 from samba.net_s3 import Net as s3_Net
73 from samba.param import default_path
75 from samba.dsdb import (
76 DS_DOMAIN_FUNCTION_2000,
77 DS_DOMAIN_FUNCTION_2003,
78 DS_DOMAIN_FUNCTION_2003_MIXED,
79 DS_DOMAIN_FUNCTION_2008,
80 DS_DOMAIN_FUNCTION_2008_R2,
81 DS_DOMAIN_FUNCTION_2012,
82 DS_DOMAIN_FUNCTION_2012_R2,
83 DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL,
84 DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL,
85 UF_WORKSTATION_TRUST_ACCOUNT,
86 UF_SERVER_TRUST_ACCOUNT,
87 UF_TRUSTED_FOR_DELEGATION,
88 UF_PARTIAL_SECRETS_ACCOUNT
91 from samba.provision import (
92 provision,
93 ProvisioningError,
94 DEFAULT_MIN_PWD_LENGTH,
95 setup_path
98 from samba.provision.common import (
99 FILL_FULL,
100 FILL_NT4SYNC,
101 FILL_DRS
104 from samba.netcmd.pso import cmd_domain_passwordsettings_pso
105 from samba.netcmd.domain_backup import cmd_domain_backup
107 from samba.common import get_string
108 from samba.trust_utils import CreateTrustedDomainRelax
110 string_version_to_constant = {
111 "2008_R2": DS_DOMAIN_FUNCTION_2008_R2,
112 "2012": DS_DOMAIN_FUNCTION_2012,
113 "2012_R2": DS_DOMAIN_FUNCTION_2012_R2,
116 common_provision_join_options = [
117 Option("--machinepass", type="string", metavar="PASSWORD",
118 help="choose machine password (otherwise random)"),
119 Option("--plaintext-secrets", action="store_true",
120 help="Store secret/sensitive values as plain text on disk" +
121 "(default is to encrypt secret/ensitive values)"),
122 Option("--backend-store", type="choice", metavar="BACKENDSTORE",
123 choices=["tdb", "mdb"],
124 help="Specify the database backend to be used "
125 "(default is %s)" % get_default_backend_store()),
126 Option("--backend-store-size", type="bytes", metavar="SIZE",
127 help="Specify the size of the backend database, currently only " +
128 "supported by lmdb backends (default is 8 Gb)."),
129 Option("--targetdir", metavar="DIR",
130 help="Set target directory (where to store provision)", type=str),
131 Option("-q", "--quiet", help="Be quiet", action="store_true"),
134 common_join_options = [
135 Option("--server", help="DC to join", type=str),
136 Option("--site", help="site to join", type=str),
137 Option("--domain-critical-only",
138 help="only replicate critical domain objects",
139 action="store_true"),
140 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
141 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
142 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
143 "BIND9_DLZ uses samba4 AD to store zone information, "
144 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
145 default="SAMBA_INTERNAL"),
146 Option("-v", "--verbose", help="Be verbose", action="store_true")
149 common_ntvfs_options = [
150 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
151 action="store_true")
155 def get_testparm_var(testparm, smbconf, varname):
156 errfile = open(os.devnull, 'w')
157 p = subprocess.Popen([testparm, '-s', '-l',
158 '--parameter-name=%s' % varname, smbconf],
159 stdout=subprocess.PIPE, stderr=errfile)
160 (out, err) = p.communicate()
161 errfile.close()
162 lines = out.split(b'\n')
163 if lines:
164 return get_string(lines[0]).strip()
165 return ""
168 try:
169 enable_net_export_keytab()
170 except ImportError:
171 cmd_domain_export_keytab = None
172 else:
173 class cmd_domain_export_keytab(Command):
174 """Dump Kerberos keys of the domain into a keytab."""
176 synopsis = "%prog <keytab> [options]"
178 takes_optiongroups = {
179 "sambaopts": options.SambaOptions,
180 "credopts": options.CredentialsOptions,
181 "versionopts": options.VersionOptions,
184 takes_options = [
185 Option("--principal", help="extract only this principal", type=str),
188 takes_args = ["keytab"]
190 def run(self, keytab, credopts=None, sambaopts=None, versionopts=None, principal=None):
191 lp = sambaopts.get_loadparm()
192 net = Net(None, lp)
193 net.export_keytab(keytab=keytab, principal=principal)
196 class cmd_domain_info(Command):
197 """Print basic info about a domain and the DC passed as parameter."""
199 synopsis = "%prog <ip_address> [options]"
201 takes_options = [
204 takes_optiongroups = {
205 "sambaopts": options.SambaOptions,
206 "credopts": options.CredentialsOptions,
207 "versionopts": options.VersionOptions,
210 takes_args = ["address"]
212 def run(self, address, credopts=None, sambaopts=None, versionopts=None):
213 lp = sambaopts.get_loadparm()
214 try:
215 res = netcmd_get_domain_infos_via_cldap(lp, None, address)
216 except RuntimeError:
217 raise CommandError("Invalid IP address '" + address + "'!")
218 self.outf.write("Forest : %s\n" % res.forest)
219 self.outf.write("Domain : %s\n" % res.dns_domain)
220 self.outf.write("Netbios domain : %s\n" % res.domain_name)
221 self.outf.write("DC name : %s\n" % res.pdc_dns_name)
222 self.outf.write("DC netbios name : %s\n" % res.pdc_name)
223 self.outf.write("Server site : %s\n" % res.server_site)
224 self.outf.write("Client site : %s\n" % res.client_site)
227 class cmd_domain_provision(Command):
228 """Provision a domain."""
230 synopsis = "%prog [options]"
232 takes_optiongroups = {
233 "sambaopts": options.SambaOptions,
234 "versionopts": options.VersionOptions,
237 takes_options = [
238 Option("--interactive", help="Ask for names", action="store_true"),
239 Option("--domain", type="string", metavar="DOMAIN",
240 help="NetBIOS domain name to use"),
241 Option("--domain-guid", type="string", metavar="GUID",
242 help="set domainguid (otherwise random)"),
243 Option("--domain-sid", type="string", metavar="SID",
244 help="set domainsid (otherwise random)"),
245 Option("--ntds-guid", type="string", metavar="GUID",
246 help="set NTDS object GUID (otherwise random)"),
247 Option("--invocationid", type="string", metavar="GUID",
248 help="set invocationid (otherwise random)"),
249 Option("--host-name", type="string", metavar="HOSTNAME",
250 help="set hostname"),
251 Option("--host-ip", type="string", metavar="IPADDRESS",
252 help="set IPv4 ipaddress"),
253 Option("--host-ip6", type="string", metavar="IP6ADDRESS",
254 help="set IPv6 ipaddress"),
255 Option("--site", type="string", metavar="SITENAME",
256 help="set site name"),
257 Option("--adminpass", type="string", metavar="PASSWORD",
258 help="choose admin password (otherwise random)"),
259 Option("--krbtgtpass", type="string", metavar="PASSWORD",
260 help="choose krbtgt password (otherwise random)"),
261 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
262 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
263 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
264 "BIND9_FLATFILE uses bind9 text database to store zone information, "
265 "BIND9_DLZ uses samba4 AD to store zone information, "
266 "NONE skips the DNS setup entirely (not recommended)",
267 default="SAMBA_INTERNAL"),
268 Option("--dnspass", type="string", metavar="PASSWORD",
269 help="choose dns password (otherwise random)"),
270 Option("--root", type="string", metavar="USERNAME",
271 help="choose 'root' unix username"),
272 Option("--nobody", type="string", metavar="USERNAME",
273 help="choose 'nobody' user"),
274 Option("--users", type="string", metavar="GROUPNAME",
275 help="choose 'users' group"),
276 Option("--blank", action="store_true",
277 help="do not add users or groups, just the structure"),
278 Option("--server-role", type="choice", metavar="ROLE",
279 choices=["domain controller", "dc", "member server", "member", "standalone"],
280 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
281 default="domain controller"),
282 Option("--function-level", type="choice", metavar="FOR-FUN-LEVEL",
283 choices=["2000", "2003", "2008", "2008_R2"],
284 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2008_R2 Native.",
285 default="2008_R2"),
286 Option("--base-schema", type="choice", metavar="BASE-SCHEMA",
287 choices=["2008_R2", "2008_R2_old", "2012", "2012_R2"],
288 help="The base schema files to use. Default is (Windows) 2012_R2.",
289 default="2012_R2"),
290 Option("--next-rid", type="int", metavar="NEXTRID", default=1000,
291 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
292 Option("--partitions-only",
293 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"),
294 Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
297 ntvfs_options = [
298 Option("--use-xattrs", type="choice", choices=["yes", "no", "auto"],
299 metavar="[yes|no|auto]",
300 help="Define if we should use the native fs capabilities or a tdb file for "
301 "storing attributes likes ntacl when --use-ntvfs is set. "
302 "auto tries to make an inteligent guess based on the user rights and system capabilities",
303 default="auto")
306 takes_options.extend(common_provision_join_options)
308 if samba.is_ntvfs_fileserver_built():
309 takes_options.extend(common_ntvfs_options)
310 takes_options.extend(ntvfs_options)
312 takes_args = []
314 def run(self, sambaopts=None, versionopts=None,
315 interactive=None,
316 domain=None,
317 domain_guid=None,
318 domain_sid=None,
319 ntds_guid=None,
320 invocationid=None,
321 host_name=None,
322 host_ip=None,
323 host_ip6=None,
324 adminpass=None,
325 site=None,
326 krbtgtpass=None,
327 machinepass=None,
328 dns_backend=None,
329 dns_forwarder=None,
330 dnspass=None,
331 ldapadminpass=None,
332 root=None,
333 nobody=None,
334 users=None,
335 quiet=None,
336 blank=None,
337 server_role=None,
338 function_level=None,
339 next_rid=None,
340 partitions_only=None,
341 targetdir=None,
342 use_xattrs="auto",
343 use_ntvfs=False,
344 use_rfc2307=None,
345 base_schema=None,
346 plaintext_secrets=False,
347 backend_store=None,
348 backend_store_size=None):
350 self.logger = self.get_logger(name="provision", quiet=quiet)
352 lp = sambaopts.get_loadparm()
353 smbconf = lp.configfile
355 if dns_forwarder is not None:
356 suggested_forwarder = dns_forwarder
357 else:
358 suggested_forwarder = self._get_nameserver_ip()
359 if suggested_forwarder is None:
360 suggested_forwarder = "none"
362 if len(self.raw_argv) == 1:
363 interactive = True
365 if interactive:
366 from getpass import getpass
367 import socket
369 def ask(prompt, default=None):
370 if default is not None:
371 print("%s [%s]: " % (prompt, default), end=' ')
372 else:
373 print("%s: " % (prompt,), end=' ')
374 sys.stdout.flush()
375 return sys.stdin.readline().rstrip("\n") or default
377 try:
378 default = socket.getfqdn().split(".", 1)[1].upper()
379 except IndexError:
380 default = None
381 realm = ask("Realm", default)
382 if realm in (None, ""):
383 raise CommandError("No realm set!")
385 try:
386 default = realm.split(".")[0]
387 except IndexError:
388 default = None
389 domain = ask("Domain", default)
390 if domain is None:
391 raise CommandError("No domain set!")
393 server_role = ask("Server Role (dc, member, standalone)", "dc")
395 dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
396 if dns_backend in (None, ''):
397 raise CommandError("No DNS backend set!")
399 if dns_backend == "SAMBA_INTERNAL":
400 dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
401 if dns_forwarder.lower() in (None, 'none'):
402 suggested_forwarder = None
403 dns_forwarder = None
405 while True:
406 adminpassplain = getpass("Administrator password: ")
407 issue = self._adminpass_issue(adminpassplain)
408 if issue:
409 self.errf.write("%s.\n" % issue)
410 else:
411 adminpassverify = getpass("Retype password: ")
412 if not adminpassplain == adminpassverify:
413 self.errf.write("Sorry, passwords do not match.\n")
414 else:
415 adminpass = adminpassplain
416 break
418 else:
419 realm = sambaopts._lp.get('realm')
420 if realm is None:
421 raise CommandError("No realm set!")
422 if domain is None:
423 raise CommandError("No domain set!")
425 if adminpass:
426 issue = self._adminpass_issue(adminpass)
427 if issue:
428 raise CommandError(issue)
429 else:
430 self.logger.info("Administrator password will be set randomly!")
432 if function_level == "2000":
433 dom_for_fun_level = DS_DOMAIN_FUNCTION_2000
434 elif function_level == "2003":
435 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
436 elif function_level == "2008":
437 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
438 elif function_level == "2008_R2":
439 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
441 if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
442 dns_forwarder = suggested_forwarder
444 samdb_fill = FILL_FULL
445 if blank:
446 samdb_fill = FILL_NT4SYNC
447 elif partitions_only:
448 samdb_fill = FILL_DRS
450 if targetdir is not None:
451 if not os.path.isdir(targetdir):
452 os.makedirs(targetdir)
454 eadb = True
456 if use_xattrs == "yes":
457 eadb = False
458 elif use_xattrs == "auto" and use_ntvfs == False:
459 eadb = False
460 elif use_ntvfs == False:
461 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
462 "Please re-run with --use-xattrs omitted.")
463 elif use_xattrs == "auto" and not lp.get("posix:eadb"):
464 if targetdir:
465 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
466 else:
467 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
468 try:
469 try:
470 samba.ntacls.setntacl(lp, file.name,
471 "O:S-1-5-32G:S-1-5-32",
472 "S-1-5-32",
473 system_session_unix(),
474 "native")
475 eadb = False
476 except Exception:
477 self.logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. ")
478 finally:
479 file.close()
481 if eadb:
482 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.")
484 if domain_sid is not None:
485 domain_sid = security.dom_sid(domain_sid)
487 session = system_session()
488 if backend_store is None:
489 backend_store = get_default_backend_store()
490 try:
491 result = provision(self.logger,
492 session, smbconf=smbconf, targetdir=targetdir,
493 samdb_fill=samdb_fill, realm=realm, domain=domain,
494 domainguid=domain_guid, domainsid=domain_sid,
495 hostname=host_name,
496 hostip=host_ip, hostip6=host_ip6,
497 sitename=site, ntdsguid=ntds_guid,
498 invocationid=invocationid, adminpass=adminpass,
499 krbtgtpass=krbtgtpass, machinepass=machinepass,
500 dns_backend=dns_backend, dns_forwarder=dns_forwarder,
501 dnspass=dnspass, root=root, nobody=nobody,
502 users=users,
503 serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
504 useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
505 use_rfc2307=use_rfc2307, skip_sysvolacl=False,
506 base_schema=base_schema,
507 plaintext_secrets=plaintext_secrets,
508 backend_store=backend_store,
509 backend_store_size=backend_store_size)
511 except ProvisioningError as e:
512 raise CommandError("Provision failed", e)
514 result.report_logger(self.logger)
516 def _get_nameserver_ip(self):
517 """Grab the nameserver IP address from /etc/resolv.conf."""
518 from os import path
519 RESOLV_CONF = "/etc/resolv.conf"
521 if not path.isfile(RESOLV_CONF):
522 self.logger.warning("Failed to locate %s" % RESOLV_CONF)
523 return None
525 handle = None
526 try:
527 handle = open(RESOLV_CONF, 'r')
528 for line in handle:
529 if not line.startswith('nameserver'):
530 continue
531 # we want the last non-space continuous string of the line
532 return line.strip().split()[-1]
533 finally:
534 if handle is not None:
535 handle.close()
537 self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
539 def _adminpass_issue(self, adminpass):
540 """Returns error string for a bad administrator password,
541 or None if acceptable"""
542 if isinstance(adminpass, bytes):
543 adminpass = adminpass.decode('utf8')
544 if len(adminpass) < DEFAULT_MIN_PWD_LENGTH:
545 return "Administrator password does not meet the default minimum" \
546 " password length requirement (%d characters)" \
547 % DEFAULT_MIN_PWD_LENGTH
548 elif not samba.check_password_quality(adminpass):
549 return "Administrator password does not meet the default" \
550 " quality standards"
551 else:
552 return None
555 class cmd_domain_dcpromo(Command):
556 """Promote an existing domain member or NT4 PDC to an AD DC."""
558 synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
560 takes_optiongroups = {
561 "sambaopts": options.SambaOptions,
562 "versionopts": options.VersionOptions,
563 "credopts": options.CredentialsOptions,
566 takes_options = []
567 takes_options.extend(common_join_options)
569 takes_options.extend(common_provision_join_options)
571 if samba.is_ntvfs_fileserver_built():
572 takes_options.extend(common_ntvfs_options)
574 takes_args = ["domain", "role?"]
576 def run(self, domain, role=None, sambaopts=None, credopts=None,
577 versionopts=None, server=None, site=None, targetdir=None,
578 domain_critical_only=False, machinepass=None,
579 use_ntvfs=False, dns_backend=None,
580 quiet=False, verbose=False, plaintext_secrets=False,
581 backend_store=None, backend_store_size=None):
582 lp = sambaopts.get_loadparm()
583 creds = credopts.get_credentials(lp)
584 net = Net(creds, lp, server=credopts.ipaddress)
586 logger = self.get_logger(verbose=verbose, quiet=quiet)
588 netbios_name = lp.get("netbios name")
590 if role is not None:
591 role = role.upper()
593 if role == "DC":
594 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
595 site=site, netbios_name=netbios_name, targetdir=targetdir,
596 domain_critical_only=domain_critical_only,
597 machinepass=machinepass, use_ntvfs=use_ntvfs,
598 dns_backend=dns_backend,
599 promote_existing=True, plaintext_secrets=plaintext_secrets,
600 backend_store=backend_store,
601 backend_store_size=backend_store_size)
602 elif role == "RODC":
603 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
604 site=site, netbios_name=netbios_name, targetdir=targetdir,
605 domain_critical_only=domain_critical_only,
606 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
607 promote_existing=True, plaintext_secrets=plaintext_secrets,
608 backend_store=backend_store,
609 backend_store_size=backend_store_size)
610 else:
611 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
614 class cmd_domain_join(Command):
615 """Join domain as either member or backup domain controller."""
617 synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER] [options]"
619 takes_optiongroups = {
620 "sambaopts": options.SambaOptions,
621 "versionopts": options.VersionOptions,
622 "credopts": options.CredentialsOptions,
625 ntvfs_options = [
626 Option(
627 "--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
628 action="store_true")
631 selftest_options = [
632 Option("--experimental-s4-member", action="store_true",
633 help="Perform member joins using the s4 Net join_member. "
634 "Don't choose this unless you know what you're doing")
637 takes_options = []
638 takes_options.extend(common_join_options)
639 takes_options.extend(common_provision_join_options)
641 if samba.is_ntvfs_fileserver_built():
642 takes_options.extend(ntvfs_options)
644 if samba.is_selftest_enabled():
645 takes_options.extend(selftest_options)
647 takes_args = ["domain", "role?"]
649 def run(self, domain, role=None, sambaopts=None, credopts=None,
650 versionopts=None, server=None, site=None, targetdir=None,
651 domain_critical_only=False, machinepass=None,
652 use_ntvfs=False, experimental_s4_member=False, dns_backend=None,
653 quiet=False, verbose=False,
654 plaintext_secrets=False,
655 backend_store=None, backend_store_size=None):
656 lp = sambaopts.get_loadparm()
657 creds = credopts.get_credentials(lp)
658 net = Net(creds, lp, server=credopts.ipaddress)
660 logger = self.get_logger(verbose=verbose, quiet=quiet)
662 netbios_name = lp.get("netbios name")
664 if role is not None:
665 role = role.upper()
667 if role is None or role == "MEMBER":
668 if experimental_s4_member:
669 (join_password, sid, domain_name) = net.join_member(
670 domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
671 machinepass=machinepass)
672 else:
673 lp.set('realm', domain)
674 if lp.get('workgroup') == 'WORKGROUP':
675 lp.set('workgroup', net.finddc(domain=domain,
676 flags=(nbt.NBT_SERVER_LDAP |
677 nbt.NBT_SERVER_DS)).domain_name)
678 lp.set('server role', 'member server')
679 smb_conf = lp.configfile if lp.configfile else default_path()
680 with tempfile.NamedTemporaryFile(delete=False,
681 dir=os.path.dirname(smb_conf)) as f:
682 lp.dump(False, f.name)
683 if os.path.exists(smb_conf):
684 mode = os.stat(smb_conf).st_mode
685 os.chmod(f.name, mode)
686 os.rename(f.name, smb_conf)
687 s3_lp = s3param.get_context()
688 s3_lp.load(smb_conf)
689 if machinepass is None:
690 machinepass = samba.generate_random_machine_password(14, 40)
691 s3_net = s3_Net(creds, s3_lp, server=server)
692 (sid, domain_name) = s3_net.join_member(netbios_name,
693 machinepass=machinepass,
694 debug=verbose)
696 self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
697 elif role == "DC":
698 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
699 site=site, netbios_name=netbios_name, targetdir=targetdir,
700 domain_critical_only=domain_critical_only,
701 machinepass=machinepass, use_ntvfs=use_ntvfs,
702 dns_backend=dns_backend,
703 plaintext_secrets=plaintext_secrets,
704 backend_store=backend_store,
705 backend_store_size=backend_store_size)
706 elif role == "RODC":
707 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
708 site=site, netbios_name=netbios_name, targetdir=targetdir,
709 domain_critical_only=domain_critical_only,
710 machinepass=machinepass, use_ntvfs=use_ntvfs,
711 dns_backend=dns_backend,
712 plaintext_secrets=plaintext_secrets,
713 backend_store=backend_store,
714 backend_store_size=backend_store_size)
715 else:
716 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC)" % role)
719 class cmd_domain_demote(Command):
720 """Demote ourselves from the role of Domain Controller."""
722 synopsis = "%prog [options]"
724 takes_options = [
725 Option("--server", help="writable DC to write demotion changes on", type=str),
726 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
727 metavar="URL", dest="H"),
728 Option("--remove-other-dead-server", help="Dead DC (name or NTDS GUID) "
729 "to remove ALL references to (rather than this DC)", type=str),
730 Option("-q", "--quiet", help="Be quiet", action="store_true"),
731 Option("-v", "--verbose", help="Be verbose", action="store_true"),
734 takes_optiongroups = {
735 "sambaopts": options.SambaOptions,
736 "credopts": options.CredentialsOptions,
737 "versionopts": options.VersionOptions,
740 def run(self, sambaopts=None, credopts=None,
741 versionopts=None, server=None,
742 remove_other_dead_server=None, H=None,
743 verbose=False, quiet=False):
744 lp = sambaopts.get_loadparm()
745 creds = credopts.get_credentials(lp)
746 net = Net(creds, lp, server=credopts.ipaddress)
748 logger = self.get_logger(verbose=verbose, quiet=quiet)
750 if remove_other_dead_server is not None:
751 if server is not None:
752 samdb = SamDB(url="ldap://%s" % server,
753 session_info=system_session(),
754 credentials=creds, lp=lp)
755 else:
756 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
757 try:
758 remove_dc.remove_dc(samdb, logger, remove_other_dead_server)
759 except remove_dc.DemoteException as err:
760 raise CommandError("Demote failed: %s" % err)
761 return
763 netbios_name = lp.get("netbios name")
764 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
765 if not server:
766 res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
767 if (len(res) == 0):
768 raise CommandError("Unable to search for servers")
770 if (len(res) == 1):
771 raise CommandError("You are the last server in the domain")
773 server = None
774 for e in res:
775 if str(e["name"]).lower() != netbios_name.lower():
776 server = e["dnsHostName"]
777 break
779 ntds_guid = samdb.get_ntds_GUID()
780 msg = samdb.search(base=str(samdb.get_config_basedn()),
781 scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
782 attrs=['options'])
783 if len(msg) == 0 or "options" not in msg[0]:
784 raise CommandError("Failed to find options on %s" % ntds_guid)
786 ntds_dn = msg[0].dn
787 dsa_options = int(str(msg[0]['options']))
789 res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
790 controls=["search_options:1:2"])
792 if len(res) != 0:
793 raise CommandError("Current DC is still the owner of %d role(s), "
794 "use the role command to transfer roles to "
795 "another DC" %
796 len(res))
798 self.errf.write("Using %s as partner server for the demotion\n" %
799 server)
800 (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
802 self.errf.write("Deactivating inbound replication\n")
804 nmsg = ldb.Message()
805 nmsg.dn = msg[0].dn
807 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
808 dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
809 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
810 samdb.modify(nmsg)
812 self.errf.write("Asking partner server %s to synchronize from us\n"
813 % server)
814 for part in (samdb.get_schema_basedn(),
815 samdb.get_config_basedn(),
816 samdb.get_root_basedn()):
817 nc = drsuapi.DsReplicaObjectIdentifier()
818 nc.dn = str(part)
820 req1 = drsuapi.DsReplicaSyncRequest1()
821 req1.naming_context = nc
822 req1.options = drsuapi.DRSUAPI_DRS_WRIT_REP
823 req1.source_dsa_guid = misc.GUID(ntds_guid)
825 try:
826 drsuapiBind.DsReplicaSync(drsuapi_handle, 1, req1)
827 except RuntimeError as e1:
828 (werr, string) = e1.args
829 if werr == werror.WERR_DS_DRA_NO_REPLICA:
830 pass
831 else:
832 self.errf.write(
833 "Error while replicating out last local changes from '%s' for demotion, "
834 "re-enabling inbound replication\n" % part)
835 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
836 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
837 samdb.modify(nmsg)
838 raise CommandError("Error while sending a DsReplicaSync for partition '%s'" % str(part), string)
839 try:
840 remote_samdb = SamDB(url="ldap://%s" % server,
841 session_info=system_session(),
842 credentials=creds, lp=lp)
844 self.errf.write("Changing userControl and container\n")
845 res = remote_samdb.search(base=str(remote_samdb.domain_dn()),
846 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
847 netbios_name.upper(),
848 attrs=["userAccountControl"])
849 dc_dn = res[0].dn
850 uac = int(str(res[0]["userAccountControl"]))
852 except Exception as e:
853 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
854 self.errf.write(
855 "Error while demoting, re-enabling inbound replication\n")
856 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
857 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
858 samdb.modify(nmsg)
859 raise CommandError("Error while changing account control", e)
861 if (len(res) != 1):
862 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
863 self.errf.write(
864 "Error while demoting, re-enabling inbound replication")
865 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
866 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
867 samdb.modify(nmsg)
868 raise CommandError("Unable to find object with samaccountName = %s$"
869 " in the remote dc" % netbios_name.upper())
871 olduac = uac
873 uac &= ~(UF_SERVER_TRUST_ACCOUNT |
874 UF_TRUSTED_FOR_DELEGATION |
875 UF_PARTIAL_SECRETS_ACCOUNT)
876 uac |= UF_WORKSTATION_TRUST_ACCOUNT
878 msg = ldb.Message()
879 msg.dn = dc_dn
881 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
882 ldb.FLAG_MOD_REPLACE,
883 "userAccountControl")
884 try:
885 remote_samdb.modify(msg)
886 except Exception as e:
887 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
888 self.errf.write(
889 "Error while demoting, re-enabling inbound replication")
890 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
891 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
892 samdb.modify(nmsg)
894 raise CommandError("Error while changing account control", e)
896 parent = msg.dn.parent()
897 dc_name = res[0].dn.get_rdn_value()
898 rdn = "CN=%s" % dc_name
900 # Let's move to the Computer container
901 i = 0
902 newrdn = str(rdn)
904 computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.domain_dn()))
905 res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
907 if (len(res) != 0):
908 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
909 scope=ldb.SCOPE_ONELEVEL)
910 while(len(res) != 0 and i < 100):
911 i = i + 1
912 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
913 scope=ldb.SCOPE_ONELEVEL)
915 if i == 100:
916 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
917 self.errf.write(
918 "Error while demoting, re-enabling inbound replication\n")
919 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
920 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
921 samdb.modify(nmsg)
923 msg = ldb.Message()
924 msg.dn = dc_dn
926 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
927 ldb.FLAG_MOD_REPLACE,
928 "userAccountControl")
930 remote_samdb.modify(msg)
932 raise CommandError("Unable to find a slot for renaming %s,"
933 " all names from %s-1 to %s-%d seemed used" %
934 (str(dc_dn), rdn, rdn, i - 9))
936 newrdn = "%s-%d" % (rdn, i)
938 try:
939 newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
940 remote_samdb.rename(dc_dn, newdn)
941 except Exception as e:
942 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
943 self.errf.write(
944 "Error while demoting, re-enabling inbound replication\n")
945 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
946 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
947 samdb.modify(nmsg)
949 msg = ldb.Message()
950 msg.dn = dc_dn
952 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
953 ldb.FLAG_MOD_REPLACE,
954 "userAccountControl")
956 remote_samdb.modify(msg)
957 raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
959 server_dsa_dn = samdb.get_serverName()
960 domain = remote_samdb.get_root_basedn()
962 try:
963 req1 = drsuapi.DsRemoveDSServerRequest1()
964 req1.server_dn = str(server_dsa_dn)
965 req1.domain_dn = str(domain)
966 req1.commit = 1
968 drsuapiBind.DsRemoveDSServer(drsuapi_handle, 1, req1)
969 except RuntimeError as e3:
970 (werr, string) = e3.args
971 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
972 self.errf.write(
973 "Error while demoting, re-enabling inbound replication\n")
974 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
975 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
976 samdb.modify(nmsg)
978 msg = ldb.Message()
979 msg.dn = newdn
981 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
982 ldb.FLAG_MOD_REPLACE,
983 "userAccountControl")
984 remote_samdb.modify(msg)
985 remote_samdb.rename(newdn, dc_dn)
986 if werr == werror.WERR_DS_DRA_NO_REPLICA:
987 raise CommandError("The DC %s is not present on (already "
988 "removed from) the remote server: %s" %
989 (server_dsa_dn, e3))
990 else:
991 raise CommandError("Error while sending a removeDsServer "
992 "of %s: %s" %
993 (server_dsa_dn, e3))
995 remove_dc.remove_sysvol_references(remote_samdb, logger, dc_name)
997 # These are objects under the computer account that should be deleted
998 for s in ("CN=Enterprise,CN=NTFRS Subscriptions",
999 "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
1000 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
1001 "CN=NTFRS Subscriptions"):
1002 try:
1003 remote_samdb.delete(ldb.Dn(remote_samdb,
1004 "%s,%s" % (s, str(newdn))))
1005 except ldb.LdbError as l:
1006 pass
1008 # get dns host name for target server to demote, remove dns references
1009 remove_dc.remove_dns_references(remote_samdb, logger, samdb.host_dns_name(),
1010 ignore_no_name=True)
1012 self.errf.write("Demote successful\n")
1015 class cmd_domain_level(Command):
1016 """Raise domain and forest function levels."""
1018 synopsis = "%prog (show|raise <options>) [options]"
1020 takes_optiongroups = {
1021 "sambaopts": options.SambaOptions,
1022 "credopts": options.CredentialsOptions,
1023 "versionopts": options.VersionOptions,
1026 takes_options = [
1027 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1028 metavar="URL", dest="H"),
1029 Option("-q", "--quiet", help="Be quiet", action="store_true"), # unused
1030 Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1031 help="The forest function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)"),
1032 Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1033 help="The domain function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)")
1036 takes_args = ["subcommand"]
1038 def run(self, subcommand, H=None, forest_level=None, domain_level=None,
1039 quiet=False, credopts=None, sambaopts=None, versionopts=None):
1040 lp = sambaopts.get_loadparm()
1041 creds = credopts.get_credentials(lp, fallback_machine=True)
1043 samdb = SamDB(url=H, session_info=system_session(),
1044 credentials=creds, lp=lp)
1046 domain_dn = samdb.domain_dn()
1048 res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
1049 scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
1050 assert len(res_forest) == 1
1052 res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1053 attrs=["msDS-Behavior-Version", "nTMixedDomain"])
1054 assert len(res_domain) == 1
1056 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
1057 scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
1058 attrs=["msDS-Behavior-Version"])
1059 assert len(res_dc_s) >= 1
1061 # default values, since "msDS-Behavior-Version" does not exist on Windows 2000 AD
1062 level_forest = DS_DOMAIN_FUNCTION_2000
1063 level_domain = DS_DOMAIN_FUNCTION_2000
1065 if "msDS-Behavior-Version" in res_forest[0]:
1066 level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
1067 if "msDS-Behavior-Version" in res_domain[0]:
1068 level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
1069 level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
1071 min_level_dc = None
1072 for msg in res_dc_s:
1073 if "msDS-Behavior-Version" in msg:
1074 if min_level_dc is None or int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
1075 min_level_dc = int(msg["msDS-Behavior-Version"][0])
1076 else:
1077 min_level_dc = DS_DOMAIN_FUNCTION_2000
1078 # well, this is the least
1079 break
1081 if level_forest < DS_DOMAIN_FUNCTION_2000 or level_domain < DS_DOMAIN_FUNCTION_2000:
1082 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
1083 if min_level_dc < DS_DOMAIN_FUNCTION_2000:
1084 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
1085 if level_forest > level_domain:
1086 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
1087 if level_domain > min_level_dc:
1088 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
1090 if subcommand == "show":
1091 self.message("Domain and forest function level for domain '%s'" % domain_dn)
1092 if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1093 self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1094 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1095 self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1096 if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1097 self.message("\nATTENTION: You run SAMBA 4 on a lowest function level of a DC lower than Windows 2003. This isn't supported! Please step-up or upgrade the concerning DC(s)!")
1099 self.message("")
1101 if level_forest == DS_DOMAIN_FUNCTION_2000:
1102 outstr = "2000"
1103 elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
1104 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1105 elif level_forest == DS_DOMAIN_FUNCTION_2003:
1106 outstr = "2003"
1107 elif level_forest == DS_DOMAIN_FUNCTION_2008:
1108 outstr = "2008"
1109 elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
1110 outstr = "2008 R2"
1111 elif level_forest == DS_DOMAIN_FUNCTION_2012:
1112 outstr = "2012"
1113 elif level_forest == DS_DOMAIN_FUNCTION_2012_R2:
1114 outstr = "2012 R2"
1115 else:
1116 outstr = "higher than 2012 R2"
1117 self.message("Forest function level: (Windows) " + outstr)
1119 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1120 outstr = "2000 mixed (NT4 DC support)"
1121 elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
1122 outstr = "2000"
1123 elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
1124 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1125 elif level_domain == DS_DOMAIN_FUNCTION_2003:
1126 outstr = "2003"
1127 elif level_domain == DS_DOMAIN_FUNCTION_2008:
1128 outstr = "2008"
1129 elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
1130 outstr = "2008 R2"
1131 elif level_domain == DS_DOMAIN_FUNCTION_2012:
1132 outstr = "2012"
1133 elif level_domain == DS_DOMAIN_FUNCTION_2012_R2:
1134 outstr = "2012 R2"
1135 else:
1136 outstr = "higher than 2012 R2"
1137 self.message("Domain function level: (Windows) " + outstr)
1139 if min_level_dc == DS_DOMAIN_FUNCTION_2000:
1140 outstr = "2000"
1141 elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
1142 outstr = "2003"
1143 elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
1144 outstr = "2008"
1145 elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
1146 outstr = "2008 R2"
1147 elif min_level_dc == DS_DOMAIN_FUNCTION_2012:
1148 outstr = "2012"
1149 elif min_level_dc == DS_DOMAIN_FUNCTION_2012_R2:
1150 outstr = "2012 R2"
1151 else:
1152 outstr = "higher than 2012 R2"
1153 self.message("Lowest function level of a DC: (Windows) " + outstr)
1155 elif subcommand == "raise":
1156 msgs = []
1158 if domain_level is not None:
1159 if domain_level == "2003":
1160 new_level_domain = DS_DOMAIN_FUNCTION_2003
1161 elif domain_level == "2008":
1162 new_level_domain = DS_DOMAIN_FUNCTION_2008
1163 elif domain_level == "2008_R2":
1164 new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
1165 elif domain_level == "2012":
1166 new_level_domain = DS_DOMAIN_FUNCTION_2012
1167 elif domain_level == "2012_R2":
1168 new_level_domain = DS_DOMAIN_FUNCTION_2012_R2
1170 if new_level_domain <= level_domain and level_domain_mixed == 0:
1171 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
1172 if new_level_domain > min_level_dc:
1173 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
1175 # Deactivate mixed/interim domain support
1176 if level_domain_mixed != 0:
1177 # Directly on the base DN
1178 m = ldb.Message()
1179 m.dn = ldb.Dn(samdb, domain_dn)
1180 m["nTMixedDomain"] = ldb.MessageElement("0",
1181 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1182 samdb.modify(m)
1183 # Under partitions
1184 m = ldb.Message()
1185 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
1186 m["nTMixedDomain"] = ldb.MessageElement("0",
1187 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1188 try:
1189 samdb.modify(m)
1190 except ldb.LdbError as e:
1191 (enum, emsg) = e.args
1192 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1193 raise
1195 # Directly on the base DN
1196 m = ldb.Message()
1197 m.dn = ldb.Dn(samdb, domain_dn)
1198 m["msDS-Behavior-Version"] = ldb.MessageElement(
1199 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1200 "msDS-Behavior-Version")
1201 samdb.modify(m)
1202 # Under partitions
1203 m = ldb.Message()
1204 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
1205 + ",CN=Partitions,%s" % samdb.get_config_basedn())
1206 m["msDS-Behavior-Version"] = ldb.MessageElement(
1207 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1208 "msDS-Behavior-Version")
1209 try:
1210 samdb.modify(m)
1211 except ldb.LdbError as e2:
1212 (enum, emsg) = e2.args
1213 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1214 raise
1216 level_domain = new_level_domain
1217 msgs.append("Domain function level changed!")
1219 if forest_level is not None:
1220 if forest_level == "2003":
1221 new_level_forest = DS_DOMAIN_FUNCTION_2003
1222 elif forest_level == "2008":
1223 new_level_forest = DS_DOMAIN_FUNCTION_2008
1224 elif forest_level == "2008_R2":
1225 new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
1226 elif forest_level == "2012":
1227 new_level_forest = DS_DOMAIN_FUNCTION_2012
1228 elif forest_level == "2012_R2":
1229 new_level_forest = DS_DOMAIN_FUNCTION_2012_R2
1231 if new_level_forest <= level_forest:
1232 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1233 if new_level_forest > level_domain:
1234 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1236 m = ldb.Message()
1237 m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
1238 m["msDS-Behavior-Version"] = ldb.MessageElement(
1239 str(new_level_forest), ldb.FLAG_MOD_REPLACE,
1240 "msDS-Behavior-Version")
1241 samdb.modify(m)
1242 msgs.append("Forest function level changed!")
1243 msgs.append("All changes applied successfully!")
1244 self.message("\n".join(msgs))
1245 else:
1246 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
1249 class cmd_domain_passwordsettings_show(Command):
1250 """Display current password settings for the domain."""
1252 synopsis = "%prog [options]"
1254 takes_optiongroups = {
1255 "sambaopts": options.SambaOptions,
1256 "versionopts": options.VersionOptions,
1257 "credopts": options.CredentialsOptions,
1260 takes_options = [
1261 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1262 metavar="URL", dest="H"),
1265 def run(self, H=None, credopts=None, sambaopts=None, versionopts=None):
1266 lp = sambaopts.get_loadparm()
1267 creds = credopts.get_credentials(lp)
1269 samdb = SamDB(url=H, session_info=system_session(),
1270 credentials=creds, lp=lp)
1272 domain_dn = samdb.domain_dn()
1273 res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1274 attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1275 "minPwdAge", "maxPwdAge", "lockoutDuration", "lockoutThreshold",
1276 "lockOutObservationWindow"])
1277 assert(len(res) == 1)
1278 try:
1279 pwd_props = int(res[0]["pwdProperties"][0])
1280 pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
1281 cur_min_pwd_len = int(res[0]["minPwdLength"][0])
1282 # ticks -> days
1283 cur_min_pwd_age = timestamp_to_days(res[0]["minPwdAge"][0])
1284 cur_max_pwd_age = timestamp_to_days(res[0]["maxPwdAge"][0])
1286 cur_account_lockout_threshold = int(res[0]["lockoutThreshold"][0])
1288 # ticks -> mins
1289 cur_account_lockout_duration = timestamp_to_mins(res[0]["lockoutDuration"][0])
1290 cur_reset_account_lockout_after = timestamp_to_mins(res[0]["lockOutObservationWindow"][0])
1291 except Exception as e:
1292 raise CommandError("Could not retrieve password properties!", e)
1294 self.message("Password information for domain '%s'" % domain_dn)
1295 self.message("")
1296 if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
1297 self.message("Password complexity: on")
1298 else:
1299 self.message("Password complexity: off")
1300 if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
1301 self.message("Store plaintext passwords: on")
1302 else:
1303 self.message("Store plaintext passwords: off")
1304 self.message("Password history length: %d" % pwd_hist_len)
1305 self.message("Minimum password length: %d" % cur_min_pwd_len)
1306 self.message("Minimum password age (days): %d" % cur_min_pwd_age)
1307 self.message("Maximum password age (days): %d" % cur_max_pwd_age)
1308 self.message("Account lockout duration (mins): %d" % cur_account_lockout_duration)
1309 self.message("Account lockout threshold (attempts): %d" % cur_account_lockout_threshold)
1310 self.message("Reset account lockout after (mins): %d" % cur_reset_account_lockout_after)
1313 class cmd_domain_passwordsettings_set(Command):
1314 """Set password settings.
1316 Password complexity, password lockout policy, history length,
1317 minimum password length, the minimum and maximum password age) on
1318 a Samba AD DC server.
1320 Use against a Windows DC is possible, but group policy will override it.
1323 synopsis = "%prog <options> [options]"
1325 takes_optiongroups = {
1326 "sambaopts": options.SambaOptions,
1327 "versionopts": options.VersionOptions,
1328 "credopts": options.CredentialsOptions,
1331 takes_options = [
1332 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1333 metavar="URL", dest="H"),
1334 Option("-q", "--quiet", help="Be quiet", action="store_true"), # unused
1335 Option("--complexity", type="choice", choices=["on", "off", "default"],
1336 help="The password complexity (on | off | default). Default is 'on'"),
1337 Option("--store-plaintext", type="choice", choices=["on", "off", "default"],
1338 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1339 Option("--history-length",
1340 help="The password history length (<integer> | default). Default is 24.", type=str),
1341 Option("--min-pwd-length",
1342 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1343 Option("--min-pwd-age",
1344 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1345 Option("--max-pwd-age",
1346 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1347 Option("--account-lockout-duration",
1348 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),
1349 Option("--account-lockout-threshold",
1350 help="The number of bad password attempts allowed before locking out the account (<integer> | default). Default is 0 (never lock out).", type=str),
1351 Option("--reset-account-lockout-after",
1352 help="After this time is elapsed, the recorded number of attempts restarts from zero (<integer> | default). Default is 30.", type=str),
1355 def run(self, H=None, min_pwd_age=None, max_pwd_age=None,
1356 quiet=False, complexity=None, store_plaintext=None, history_length=None,
1357 min_pwd_length=None, account_lockout_duration=None, account_lockout_threshold=None,
1358 reset_account_lockout_after=None, credopts=None, sambaopts=None,
1359 versionopts=None):
1360 lp = sambaopts.get_loadparm()
1361 creds = credopts.get_credentials(lp)
1363 samdb = SamDB(url=H, session_info=system_session(),
1364 credentials=creds, lp=lp)
1366 domain_dn = samdb.domain_dn()
1367 msgs = []
1368 m = ldb.Message()
1369 m.dn = ldb.Dn(samdb, domain_dn)
1370 pwd_props = int(samdb.get_pwdProperties())
1372 # get the current password age settings
1373 max_pwd_age_ticks = samdb.get_maxPwdAge()
1374 min_pwd_age_ticks = samdb.get_minPwdAge()
1376 if complexity is not None:
1377 if complexity == "on" or complexity == "default":
1378 pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
1379 msgs.append("Password complexity activated!")
1380 elif complexity == "off":
1381 pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
1382 msgs.append("Password complexity deactivated!")
1384 if store_plaintext is not None:
1385 if store_plaintext == "on" or store_plaintext == "default":
1386 pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1387 msgs.append("Plaintext password storage for changed passwords activated!")
1388 elif store_plaintext == "off":
1389 pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
1390 msgs.append("Plaintext password storage for changed passwords deactivated!")
1392 if complexity is not None or store_plaintext is not None:
1393 m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
1394 ldb.FLAG_MOD_REPLACE, "pwdProperties")
1396 if history_length is not None:
1397 if history_length == "default":
1398 pwd_hist_len = 24
1399 else:
1400 pwd_hist_len = int(history_length)
1402 if pwd_hist_len < 0 or pwd_hist_len > 24:
1403 raise CommandError("Password history length must be in the range of 0 to 24!")
1405 m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
1406 ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
1407 msgs.append("Password history length changed!")
1409 if min_pwd_length is not None:
1410 if min_pwd_length == "default":
1411 min_pwd_len = 7
1412 else:
1413 min_pwd_len = int(min_pwd_length)
1415 if min_pwd_len < 0 or min_pwd_len > 14:
1416 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1418 m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
1419 ldb.FLAG_MOD_REPLACE, "minPwdLength")
1420 msgs.append("Minimum password length changed!")
1422 if min_pwd_age is not None:
1423 if min_pwd_age == "default":
1424 min_pwd_age = 1
1425 else:
1426 min_pwd_age = int(min_pwd_age)
1428 if min_pwd_age < 0 or min_pwd_age > 998:
1429 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1431 # days -> ticks
1432 min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
1434 m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
1435 ldb.FLAG_MOD_REPLACE, "minPwdAge")
1436 msgs.append("Minimum password age changed!")
1438 if max_pwd_age is not None:
1439 if max_pwd_age == "default":
1440 max_pwd_age = 43
1441 else:
1442 max_pwd_age = int(max_pwd_age)
1444 if max_pwd_age < 0 or max_pwd_age > 999:
1445 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1447 # days -> ticks
1448 if max_pwd_age == 0:
1449 max_pwd_age_ticks = NEVER_TIMESTAMP
1450 else:
1451 max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
1453 m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
1454 ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1455 msgs.append("Maximum password age changed!")
1457 if account_lockout_duration is not None:
1458 if account_lockout_duration == "default":
1459 account_lockout_duration = 30
1460 else:
1461 account_lockout_duration = int(account_lockout_duration)
1463 if account_lockout_duration < 0 or account_lockout_duration > 99999:
1464 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1466 # minutes -> ticks
1467 if account_lockout_duration == 0:
1468 account_lockout_duration_ticks = NEVER_TIMESTAMP
1469 else:
1470 account_lockout_duration_ticks = -int(account_lockout_duration * (60 * 1e7))
1472 m["lockoutDuration"] = ldb.MessageElement(str(account_lockout_duration_ticks),
1473 ldb.FLAG_MOD_REPLACE, "lockoutDuration")
1474 msgs.append("Account lockout duration changed!")
1476 if account_lockout_threshold is not None:
1477 if account_lockout_threshold == "default":
1478 account_lockout_threshold = 0
1479 else:
1480 account_lockout_threshold = int(account_lockout_threshold)
1482 m["lockoutThreshold"] = ldb.MessageElement(str(account_lockout_threshold),
1483 ldb.FLAG_MOD_REPLACE, "lockoutThreshold")
1484 msgs.append("Account lockout threshold changed!")
1486 if reset_account_lockout_after is not None:
1487 if reset_account_lockout_after == "default":
1488 reset_account_lockout_after = 30
1489 else:
1490 reset_account_lockout_after = int(reset_account_lockout_after)
1492 if reset_account_lockout_after < 0 or reset_account_lockout_after > 99999:
1493 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1495 # minutes -> ticks
1496 if reset_account_lockout_after == 0:
1497 reset_account_lockout_after_ticks = NEVER_TIMESTAMP
1498 else:
1499 reset_account_lockout_after_ticks = -int(reset_account_lockout_after * (60 * 1e7))
1501 m["lockOutObservationWindow"] = ldb.MessageElement(str(reset_account_lockout_after_ticks),
1502 ldb.FLAG_MOD_REPLACE, "lockOutObservationWindow")
1503 msgs.append("Duration to reset account lockout after changed!")
1505 if max_pwd_age or min_pwd_age:
1506 # If we're setting either min or max password, make sure the max is
1507 # still greater overall. As either setting could be None, we use the
1508 # ticks here (which are always set) and work backwards.
1509 max_pwd_age = timestamp_to_days(max_pwd_age_ticks)
1510 min_pwd_age = timestamp_to_days(min_pwd_age_ticks)
1511 if max_pwd_age != 0 and min_pwd_age >= max_pwd_age:
1512 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
1514 if len(m) == 0:
1515 raise CommandError("You must specify at least one option to set. Try --help")
1516 samdb.modify(m)
1517 msgs.append("All changes applied successfully!")
1518 self.message("\n".join(msgs))
1521 class cmd_domain_passwordsettings(SuperCommand):
1522 """Manage password policy settings."""
1524 subcommands = {}
1525 subcommands["pso"] = cmd_domain_passwordsettings_pso()
1526 subcommands["show"] = cmd_domain_passwordsettings_show()
1527 subcommands["set"] = cmd_domain_passwordsettings_set()
1530 class cmd_domain_classicupgrade(Command):
1531 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1533 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1534 the testparm utility from your classic installation (with --testparm).
1537 synopsis = "%prog [options] <classic_smb_conf>"
1539 takes_optiongroups = {
1540 "sambaopts": options.SambaOptions,
1541 "versionopts": options.VersionOptions
1544 takes_options = [
1545 Option("--dbdir", type="string", metavar="DIR",
1546 help="Path to samba classic DC database directory"),
1547 Option("--testparm", type="string", metavar="PATH",
1548 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1549 Option("--targetdir", type="string", metavar="DIR",
1550 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1551 Option("-q", "--quiet", help="Be quiet", action="store_true"),
1552 Option("-v", "--verbose", help="Be verbose", action="store_true"),
1553 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
1554 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1555 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1556 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1557 "BIND9_DLZ uses samba4 AD to store zone information, "
1558 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1559 default="SAMBA_INTERNAL")
1562 ntvfs_options = [
1563 Option("--use-xattrs", type="choice", choices=["yes", "no", "auto"],
1564 metavar="[yes|no|auto]",
1565 help="Define if we should use the native fs capabilities or a tdb file for "
1566 "storing attributes likes ntacl when --use-ntvfs is set. "
1567 "auto tries to make an inteligent guess based on the user rights and system capabilities",
1568 default="auto")
1570 if samba.is_ntvfs_fileserver_built():
1571 takes_options.extend(common_ntvfs_options)
1572 takes_options.extend(ntvfs_options)
1574 takes_args = ["smbconf"]
1576 def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
1577 quiet=False, verbose=False, use_xattrs="auto", sambaopts=None, versionopts=None,
1578 dns_backend=None, use_ntvfs=False):
1580 if not os.path.exists(smbconf):
1581 raise CommandError("File %s does not exist" % smbconf)
1583 if testparm and not os.path.exists(testparm):
1584 raise CommandError("Testparm utility %s does not exist" % testparm)
1586 if dbdir and not os.path.exists(dbdir):
1587 raise CommandError("Directory %s does not exist" % dbdir)
1589 if not dbdir and not testparm:
1590 raise CommandError("Please specify either dbdir or testparm")
1592 logger = self.get_logger(verbose=verbose, quiet=quiet)
1594 if dbdir and testparm:
1595 logger.warning("both dbdir and testparm specified, ignoring dbdir.")
1596 dbdir = None
1598 lp = sambaopts.get_loadparm()
1600 s3conf = s3param.get_context()
1602 if sambaopts.realm:
1603 s3conf.set("realm", sambaopts.realm)
1605 if targetdir is not None:
1606 if not os.path.isdir(targetdir):
1607 os.mkdir(targetdir)
1609 eadb = True
1610 if use_xattrs == "yes":
1611 eadb = False
1612 elif use_xattrs == "auto" and use_ntvfs == False:
1613 eadb = False
1614 elif use_ntvfs == False:
1615 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
1616 "Please re-run with --use-xattrs omitted.")
1617 elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
1618 if targetdir:
1619 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
1620 else:
1621 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
1622 try:
1623 try:
1624 samba.ntacls.setntacl(lp, tmpfile.name,
1625 "O:S-1-5-32G:S-1-5-32",
1626 "S-1-5-32",
1627 system_session_unix(),
1628 "native")
1629 eadb = False
1630 except Exception:
1631 # FIXME: Don't catch all exceptions here
1632 logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. "
1633 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1634 finally:
1635 tmpfile.close()
1637 # Set correct default values from dbdir or testparm
1638 paths = {}
1639 if dbdir:
1640 paths["state directory"] = dbdir
1641 paths["private dir"] = dbdir
1642 paths["lock directory"] = dbdir
1643 paths["smb passwd file"] = dbdir + "/smbpasswd"
1644 else:
1645 paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
1646 paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
1647 paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
1648 paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
1649 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1650 # "state directory", instead make use of "lock directory"
1651 if len(paths["state directory"]) == 0:
1652 paths["state directory"] = paths["lock directory"]
1654 for p in paths:
1655 s3conf.set(p, paths[p])
1657 # load smb.conf parameters
1658 logger.info("Reading smb.conf")
1659 s3conf.load(smbconf)
1660 samba3 = Samba3(smbconf, s3conf)
1662 logger.info("Provisioning")
1663 upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
1664 useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
1667 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
1668 __doc__ = cmd_domain_classicupgrade.__doc__
1670 # This command is present for backwards compatibility only,
1671 # and should not be shown.
1673 hidden = True
1676 class LocalDCCredentialsOptions(options.CredentialsOptions):
1677 def __init__(self, parser):
1678 options.CredentialsOptions.__init__(self, parser, special_name="local-dc")
1681 class DomainTrustCommand(Command):
1682 """List domain trusts."""
1684 def __init__(self):
1685 Command.__init__(self)
1686 self.local_lp = None
1688 self.local_server = None
1689 self.local_binding_string = None
1690 self.local_creds = None
1692 self.remote_server = None
1693 self.remote_binding_string = None
1694 self.remote_creds = None
1696 def _uint32(self, v):
1697 return ctypes.c_uint32(v).value
1699 def check_runtime_error(self, runtime, val):
1700 if runtime is None:
1701 return False
1703 err32 = self._uint32(runtime.args[0])
1704 if err32 == val:
1705 return True
1707 return False
1709 class LocalRuntimeError(CommandError):
1710 def __init__(exception_self, self, runtime, message):
1711 err32 = self._uint32(runtime.args[0])
1712 errstr = runtime.args[1]
1713 msg = "LOCAL_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1714 self.local_server, message, err32, errstr)
1715 CommandError.__init__(exception_self, msg)
1717 class RemoteRuntimeError(CommandError):
1718 def __init__(exception_self, self, runtime, message):
1719 err32 = self._uint32(runtime.args[0])
1720 errstr = runtime.args[1]
1721 msg = "REMOTE_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1722 self.remote_server, message, err32, errstr)
1723 CommandError.__init__(exception_self, msg)
1725 class LocalLdbError(CommandError):
1726 def __init__(exception_self, self, ldb_error, message):
1727 errval = ldb_error.args[0]
1728 errstr = ldb_error.args[1]
1729 msg = "LOCAL_DC[%s]: %s - ERROR(%d) - %s" % (
1730 self.local_server, message, errval, errstr)
1731 CommandError.__init__(exception_self, msg)
1733 def setup_local_server(self, sambaopts, localdcopts):
1734 if self.local_server is not None:
1735 return self.local_server
1737 lp = sambaopts.get_loadparm()
1739 local_server = localdcopts.ipaddress
1740 if local_server is None:
1741 server_role = lp.server_role()
1742 if server_role != "ROLE_ACTIVE_DIRECTORY_DC":
1743 raise CommandError("Invalid server_role %s" % (server_role))
1744 local_server = lp.get('netbios name')
1745 local_transport = "ncalrpc"
1746 local_binding_options = ""
1747 local_binding_options += ",auth_type=ncalrpc_as_system"
1748 local_ldap_url = None
1749 local_creds = None
1750 else:
1751 local_transport = "ncacn_np"
1752 local_binding_options = ""
1753 local_ldap_url = "ldap://%s" % local_server
1754 local_creds = localdcopts.get_credentials(lp)
1756 self.local_lp = lp
1758 self.local_server = local_server
1759 self.local_binding_string = "%s:%s[%s]" % (local_transport, local_server, local_binding_options)
1760 self.local_ldap_url = local_ldap_url
1761 self.local_creds = local_creds
1762 return self.local_server
1764 def new_local_lsa_connection(self):
1765 return lsa.lsarpc(self.local_binding_string, self.local_lp, self.local_creds)
1767 def new_local_netlogon_connection(self):
1768 return netlogon.netlogon(self.local_binding_string, self.local_lp, self.local_creds)
1770 def new_local_ldap_connection(self):
1771 return SamDB(url=self.local_ldap_url,
1772 session_info=system_session(),
1773 credentials=self.local_creds,
1774 lp=self.local_lp)
1776 def setup_remote_server(self, credopts, domain,
1777 require_pdc=True,
1778 require_writable=True):
1780 if require_pdc:
1781 assert require_writable
1783 if self.remote_server is not None:
1784 return self.remote_server
1786 self.remote_server = "__unknown__remote_server__.%s" % domain
1787 assert self.local_server is not None
1789 remote_creds = credopts.get_credentials(self.local_lp)
1790 remote_server = credopts.ipaddress
1791 remote_binding_options = ""
1793 # TODO: we should also support NT4 domains
1794 # we could use local_netlogon.netr_DsRGetDCNameEx2() with the remote domain name
1795 # and delegate NBT or CLDAP to the local netlogon server
1796 try:
1797 remote_net = Net(remote_creds, self.local_lp, server=remote_server)
1798 remote_flags = nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS
1799 if require_writable:
1800 remote_flags |= nbt.NBT_SERVER_WRITABLE
1801 if require_pdc:
1802 remote_flags |= nbt.NBT_SERVER_PDC
1803 remote_info = remote_net.finddc(flags=remote_flags, domain=domain, address=remote_server)
1804 except NTSTATUSError as error:
1805 raise CommandError("Failed to find a writeable DC for domain '%s': %s" %
1806 (domain, error.args[1]))
1807 except Exception:
1808 raise CommandError("Failed to find a writeable DC for domain '%s'" % domain)
1809 flag_map = {
1810 nbt.NBT_SERVER_PDC: "PDC",
1811 nbt.NBT_SERVER_GC: "GC",
1812 nbt.NBT_SERVER_LDAP: "LDAP",
1813 nbt.NBT_SERVER_DS: "DS",
1814 nbt.NBT_SERVER_KDC: "KDC",
1815 nbt.NBT_SERVER_TIMESERV: "TIMESERV",
1816 nbt.NBT_SERVER_CLOSEST: "CLOSEST",
1817 nbt.NBT_SERVER_WRITABLE: "WRITABLE",
1818 nbt.NBT_SERVER_GOOD_TIMESERV: "GOOD_TIMESERV",
1819 nbt.NBT_SERVER_NDNC: "NDNC",
1820 nbt.NBT_SERVER_SELECT_SECRET_DOMAIN_6: "SELECT_SECRET_DOMAIN_6",
1821 nbt.NBT_SERVER_FULL_SECRET_DOMAIN_6: "FULL_SECRET_DOMAIN_6",
1822 nbt.NBT_SERVER_ADS_WEB_SERVICE: "ADS_WEB_SERVICE",
1823 nbt.NBT_SERVER_DS_8: "DS_8",
1824 nbt.NBT_SERVER_HAS_DNS_NAME: "HAS_DNS_NAME",
1825 nbt.NBT_SERVER_IS_DEFAULT_NC: "IS_DEFAULT_NC",
1826 nbt.NBT_SERVER_FOREST_ROOT: "FOREST_ROOT",
1828 server_type_string = self.generic_bitmap_to_string(flag_map,
1829 remote_info.server_type, names_only=True)
1830 self.outf.write("RemoteDC Netbios[%s] DNS[%s] ServerType[%s]\n" % (
1831 remote_info.pdc_name,
1832 remote_info.pdc_dns_name,
1833 server_type_string))
1835 self.remote_server = remote_info.pdc_dns_name
1836 self.remote_binding_string = "ncacn_np:%s[%s]" % (self.remote_server, remote_binding_options)
1837 self.remote_creds = remote_creds
1838 return self.remote_server
1840 def new_remote_lsa_connection(self):
1841 return lsa.lsarpc(self.remote_binding_string, self.local_lp, self.remote_creds)
1843 def new_remote_netlogon_connection(self):
1844 return netlogon.netlogon(self.remote_binding_string, self.local_lp, self.remote_creds)
1846 def get_lsa_info(self, conn, policy_access):
1847 objectAttr = lsa.ObjectAttribute()
1848 objectAttr.sec_qos = lsa.QosInfo()
1850 policy = conn.OpenPolicy2(b''.decode('utf-8'),
1851 objectAttr, policy_access)
1853 info = conn.QueryInfoPolicy2(policy, lsa.LSA_POLICY_INFO_DNS)
1855 return (policy, info)
1857 def get_netlogon_dc_unc(self, conn, server, domain):
1858 try:
1859 info = conn.netr_DsRGetDCNameEx2(server,
1860 None, 0, None, None, None,
1861 netlogon.DS_RETURN_DNS_NAME)
1862 return info.dc_unc
1863 except RuntimeError:
1864 return conn.netr_GetDcName(server, domain)
1866 def get_netlogon_dc_info(self, conn, server):
1867 info = conn.netr_DsRGetDCNameEx2(server,
1868 None, 0, None, None, None,
1869 netlogon.DS_RETURN_DNS_NAME)
1870 return info
1872 def netr_DomainTrust_to_name(self, t):
1873 if t.trust_type == lsa.LSA_TRUST_TYPE_DOWNLEVEL:
1874 return t.netbios_name
1876 return t.dns_name
1878 def netr_DomainTrust_to_type(self, a, t):
1879 primary = None
1880 primary_parent = None
1881 for _t in a:
1882 if _t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
1883 primary = _t
1884 if not _t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1885 primary_parent = a[_t.parent_index]
1886 break
1888 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1889 if t is primary_parent:
1890 return "Parent"
1892 if t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1893 return "TreeRoot"
1895 parent = a[t.parent_index]
1896 if parent is primary:
1897 return "Child"
1899 return "Shortcut"
1901 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1902 return "Forest"
1904 return "External"
1906 def netr_DomainTrust_to_transitive(self, t):
1907 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1908 return "Yes"
1910 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE:
1911 return "No"
1913 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1914 return "Yes"
1916 return "No"
1918 def netr_DomainTrust_to_direction(self, t):
1919 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND and \
1920 t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1921 return "BOTH"
1923 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND:
1924 return "INCOMING"
1926 if t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1927 return "OUTGOING"
1929 return "INVALID"
1931 def generic_enum_to_string(self, e_dict, v, names_only=False):
1932 try:
1933 w = e_dict[v]
1934 except KeyError:
1935 v32 = self._uint32(v)
1936 w = "__unknown__%08X__" % v32
1938 r = "0x%x (%s)" % (v, w)
1939 return r
1941 def generic_bitmap_to_string(self, b_dict, v, names_only=False):
1943 s = []
1945 c = v
1946 for b in sorted(b_dict.keys()):
1947 if not (c & b):
1948 continue
1949 c &= ~b
1950 s += [b_dict[b]]
1952 if c != 0:
1953 c32 = self._uint32(c)
1954 s += ["__unknown_%08X__" % c32]
1956 w = ",".join(s)
1957 if names_only:
1958 return w
1959 r = "0x%x (%s)" % (v, w)
1960 return r
1962 def trustType_string(self, v):
1963 types = {
1964 lsa.LSA_TRUST_TYPE_DOWNLEVEL: "DOWNLEVEL",
1965 lsa.LSA_TRUST_TYPE_UPLEVEL: "UPLEVEL",
1966 lsa.LSA_TRUST_TYPE_MIT: "MIT",
1967 lsa.LSA_TRUST_TYPE_DCE: "DCE",
1969 return self.generic_enum_to_string(types, v)
1971 def trustDirection_string(self, v):
1972 directions = {
1973 lsa.LSA_TRUST_DIRECTION_INBOUND |
1974 lsa.LSA_TRUST_DIRECTION_OUTBOUND: "BOTH",
1975 lsa.LSA_TRUST_DIRECTION_INBOUND: "INBOUND",
1976 lsa.LSA_TRUST_DIRECTION_OUTBOUND: "OUTBOUND",
1978 return self.generic_enum_to_string(directions, v)
1980 def trustAttributes_string(self, v):
1981 attributes = {
1982 lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE: "NON_TRANSITIVE",
1983 lsa.LSA_TRUST_ATTRIBUTE_UPLEVEL_ONLY: "UPLEVEL_ONLY",
1984 lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN: "QUARANTINED_DOMAIN",
1985 lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE: "FOREST_TRANSITIVE",
1986 lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION: "CROSS_ORGANIZATION",
1987 lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST: "WITHIN_FOREST",
1988 lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL: "TREAT_AS_EXTERNAL",
1989 lsa.LSA_TRUST_ATTRIBUTE_USES_RC4_ENCRYPTION: "USES_RC4_ENCRYPTION",
1991 return self.generic_bitmap_to_string(attributes, v)
1993 def kerb_EncTypes_string(self, v):
1994 enctypes = {
1995 security.KERB_ENCTYPE_DES_CBC_CRC: "DES_CBC_CRC",
1996 security.KERB_ENCTYPE_DES_CBC_MD5: "DES_CBC_MD5",
1997 security.KERB_ENCTYPE_RC4_HMAC_MD5: "RC4_HMAC_MD5",
1998 security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96: "AES128_CTS_HMAC_SHA1_96",
1999 security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96: "AES256_CTS_HMAC_SHA1_96",
2000 security.KERB_ENCTYPE_FAST_SUPPORTED: "FAST_SUPPORTED",
2001 security.KERB_ENCTYPE_COMPOUND_IDENTITY_SUPPORTED: "COMPOUND_IDENTITY_SUPPORTED",
2002 security.KERB_ENCTYPE_CLAIMS_SUPPORTED: "CLAIMS_SUPPORTED",
2003 security.KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED: "RESOURCE_SID_COMPRESSION_DISABLED",
2005 return self.generic_bitmap_to_string(enctypes, v)
2007 def entry_tln_status(self, e_flags, ):
2008 if e_flags == 0:
2009 return "Status[Enabled]"
2011 flags = {
2012 lsa.LSA_TLN_DISABLED_NEW: "Disabled-New",
2013 lsa.LSA_TLN_DISABLED_ADMIN: "Disabled",
2014 lsa.LSA_TLN_DISABLED_CONFLICT: "Disabled-Conflicting",
2016 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
2018 def entry_dom_status(self, e_flags):
2019 if e_flags == 0:
2020 return "Status[Enabled]"
2022 flags = {
2023 lsa.LSA_SID_DISABLED_ADMIN: "Disabled-SID",
2024 lsa.LSA_SID_DISABLED_CONFLICT: "Disabled-SID-Conflicting",
2025 lsa.LSA_NB_DISABLED_ADMIN: "Disabled-NB",
2026 lsa.LSA_NB_DISABLED_CONFLICT: "Disabled-NB-Conflicting",
2028 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
2030 def write_forest_trust_info(self, fti, tln=None, collisions=None):
2031 if tln is not None:
2032 tln_string = " TDO[%s]" % tln
2033 else:
2034 tln_string = ""
2036 self.outf.write("Namespaces[%d]%s:\n" % (
2037 len(fti.entries), tln_string))
2039 for i, e in enumerate(fti.entries):
2041 flags = e.flags
2042 collision_string = ""
2044 if collisions is not None:
2045 for c in collisions.entries:
2046 if c.index != i:
2047 continue
2048 flags = c.flags
2049 collision_string = " Collision[%s]" % (c.name.string)
2051 d = e.forest_trust_data
2052 if e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
2053 self.outf.write("TLN: %-32s DNS[*.%s]%s\n" % (
2054 self.entry_tln_status(flags),
2055 d.string, collision_string))
2056 elif e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
2057 self.outf.write("TLN_EX: %-29s DNS[*.%s]\n" % (
2058 "", d.string))
2059 elif e.type == lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
2060 self.outf.write("DOM: %-32s DNS[%s] Netbios[%s] SID[%s]%s\n" % (
2061 self.entry_dom_status(flags),
2062 d.dns_domain_name.string,
2063 d.netbios_domain_name.string,
2064 d.domain_sid, collision_string))
2065 return
2068 class cmd_domain_trust_list(DomainTrustCommand):
2069 """List domain trusts."""
2071 synopsis = "%prog [options]"
2073 takes_optiongroups = {
2074 "sambaopts": options.SambaOptions,
2075 "versionopts": options.VersionOptions,
2076 "localdcopts": LocalDCCredentialsOptions,
2079 takes_options = [
2082 def run(self, sambaopts=None, versionopts=None, localdcopts=None):
2084 local_server = self.setup_local_server(sambaopts, localdcopts)
2085 try:
2086 local_netlogon = self.new_local_netlogon_connection()
2087 except RuntimeError as error:
2088 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2090 try:
2091 local_netlogon_trusts = \
2092 local_netlogon.netr_DsrEnumerateDomainTrusts(local_server,
2093 netlogon.NETR_TRUST_FLAG_IN_FOREST |
2094 netlogon.NETR_TRUST_FLAG_OUTBOUND |
2095 netlogon.NETR_TRUST_FLAG_INBOUND)
2096 except RuntimeError as error:
2097 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
2098 # TODO: we could implement a fallback to lsa.EnumTrustDom()
2099 raise CommandError("LOCAL_DC[%s]: netr_DsrEnumerateDomainTrusts not supported." % (
2100 self.local_server))
2101 raise self.LocalRuntimeError(self, error, "netr_DsrEnumerateDomainTrusts failed")
2103 a = local_netlogon_trusts.array
2104 for t in a:
2105 if t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
2106 continue
2107 self.outf.write("%-14s %-15s %-19s %s\n" % (
2108 "Type[%s]" % self.netr_DomainTrust_to_type(a, t),
2109 "Transitive[%s]" % self.netr_DomainTrust_to_transitive(t),
2110 "Direction[%s]" % self.netr_DomainTrust_to_direction(t),
2111 "Name[%s]" % self.netr_DomainTrust_to_name(t)))
2112 return
2115 class cmd_domain_trust_show(DomainTrustCommand):
2116 """Show trusted domain details."""
2118 synopsis = "%prog NAME [options]"
2120 takes_optiongroups = {
2121 "sambaopts": options.SambaOptions,
2122 "versionopts": options.VersionOptions,
2123 "localdcopts": LocalDCCredentialsOptions,
2126 takes_options = [
2129 takes_args = ["domain"]
2131 def run(self, domain, sambaopts=None, versionopts=None, localdcopts=None):
2133 local_server = self.setup_local_server(sambaopts, localdcopts)
2134 try:
2135 local_lsa = self.new_local_lsa_connection()
2136 except RuntimeError as error:
2137 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2139 try:
2140 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2141 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2142 except RuntimeError as error:
2143 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2145 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2146 local_lsa_info.name.string,
2147 local_lsa_info.dns_domain.string,
2148 local_lsa_info.sid))
2150 lsaString = lsa.String()
2151 lsaString.string = domain
2152 try:
2153 local_tdo_full = \
2154 local_lsa.QueryTrustedDomainInfoByName(local_policy,
2155 lsaString,
2156 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2157 local_tdo_info = local_tdo_full.info_ex
2158 local_tdo_posix = local_tdo_full.posix_offset
2159 except NTSTATUSError as error:
2160 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2161 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2163 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(FULL_INFO) failed")
2165 try:
2166 local_tdo_enctypes = \
2167 local_lsa.QueryTrustedDomainInfoByName(local_policy,
2168 lsaString,
2169 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES)
2170 except NTSTATUSError as error:
2171 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_PARAMETER):
2172 error = None
2173 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_INFO_CLASS):
2174 error = None
2176 if error is not None:
2177 raise self.LocalRuntimeError(self, error,
2178 "QueryTrustedDomainInfoByName(SUPPORTED_ENCRYPTION_TYPES) failed")
2180 local_tdo_enctypes = lsa.TrustDomainInfoSupportedEncTypes()
2181 local_tdo_enctypes.enc_types = 0
2183 try:
2184 local_tdo_forest = None
2185 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2186 local_tdo_forest = \
2187 local_lsa.lsaRQueryForestTrustInformation(local_policy,
2188 lsaString,
2189 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
2190 except RuntimeError as error:
2191 if self.check_runtime_error(error, ntstatus.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE):
2192 error = None
2193 if self.check_runtime_error(error, ntstatus.NT_STATUS_NOT_FOUND):
2194 error = None
2195 if error is not None:
2196 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation failed")
2198 local_tdo_forest = lsa.ForestTrustInformation()
2199 local_tdo_forest.count = 0
2200 local_tdo_forest.entries = []
2202 self.outf.write("TrustedDomain:\n\n")
2203 self.outf.write("NetbiosName: %s\n" % local_tdo_info.netbios_name.string)
2204 if local_tdo_info.netbios_name.string != local_tdo_info.domain_name.string:
2205 self.outf.write("DnsName: %s\n" % local_tdo_info.domain_name.string)
2206 self.outf.write("SID: %s\n" % local_tdo_info.sid)
2207 self.outf.write("Type: %s\n" % self.trustType_string(local_tdo_info.trust_type))
2208 self.outf.write("Direction: %s\n" % self.trustDirection_string(local_tdo_info.trust_direction))
2209 self.outf.write("Attributes: %s\n" % self.trustAttributes_string(local_tdo_info.trust_attributes))
2210 posix_offset_u32 = ctypes.c_uint32(local_tdo_posix.posix_offset).value
2211 posix_offset_i32 = ctypes.c_int32(local_tdo_posix.posix_offset).value
2212 self.outf.write("PosixOffset: 0x%08X (%d)\n" % (posix_offset_u32, posix_offset_i32))
2213 self.outf.write("kerb_EncTypes: %s\n" % self.kerb_EncTypes_string(local_tdo_enctypes.enc_types))
2215 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2216 self.write_forest_trust_info(local_tdo_forest,
2217 tln=local_tdo_info.domain_name.string)
2219 return
2222 class cmd_domain_trust_create(DomainTrustCommand):
2223 """Create a domain or forest trust."""
2225 synopsis = "%prog DOMAIN [options]"
2227 takes_optiongroups = {
2228 "sambaopts": options.SambaOptions,
2229 "versionopts": options.VersionOptions,
2230 "credopts": options.CredentialsOptions,
2231 "localdcopts": LocalDCCredentialsOptions,
2234 takes_options = [
2235 Option("--type", type="choice", metavar="TYPE",
2236 choices=["external", "forest"],
2237 help="The type of the trust: 'external' or 'forest'.",
2238 dest='trust_type',
2239 default="external"),
2240 Option("--direction", type="choice", metavar="DIRECTION",
2241 choices=["incoming", "outgoing", "both"],
2242 help="The trust direction: 'incoming', 'outgoing' or 'both'.",
2243 dest='trust_direction',
2244 default="both"),
2245 Option("--create-location", type="choice", metavar="LOCATION",
2246 choices=["local", "both"],
2247 help="Where to create the trusted domain object: 'local' or 'both'.",
2248 dest='create_location',
2249 default="both"),
2250 Option("--cross-organisation", action="store_true",
2251 help="The related domains does not belong to the same organisation.",
2252 dest='cross_organisation',
2253 default=False),
2254 Option("--quarantined", type="choice", metavar="yes|no",
2255 choices=["yes", "no", None],
2256 help="Special SID filtering rules are applied to the trust. "
2257 "With --type=external the default is yes. "
2258 "With --type=forest the default is no.",
2259 dest='quarantined_arg',
2260 default=None),
2261 Option("--not-transitive", action="store_true",
2262 help="The forest trust is not transitive.",
2263 dest='not_transitive',
2264 default=False),
2265 Option("--treat-as-external", action="store_true",
2266 help="The treat the forest trust as external.",
2267 dest='treat_as_external',
2268 default=False),
2269 Option("--no-aes-keys", action="store_false",
2270 help="The trust uses aes kerberos keys.",
2271 dest='use_aes_keys',
2272 default=True),
2273 Option("--skip-validation", action="store_false",
2274 help="Skip validation of the trust.",
2275 dest='validate',
2276 default=True),
2279 takes_args = ["domain"]
2281 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2282 trust_type=None, trust_direction=None, create_location=None,
2283 cross_organisation=False, quarantined_arg=None,
2284 not_transitive=False, treat_as_external=False,
2285 use_aes_keys=False, validate=True):
2287 lsaString = lsa.String()
2289 quarantined = False
2290 if quarantined_arg is None:
2291 if trust_type == 'external':
2292 quarantined = True
2293 elif quarantined_arg == 'yes':
2294 quarantined = True
2296 if trust_type != 'forest':
2297 if not_transitive:
2298 raise CommandError("--not-transitive requires --type=forest")
2299 if treat_as_external:
2300 raise CommandError("--treat-as-external requires --type=forest")
2302 enc_types = None
2303 if use_aes_keys:
2304 enc_types = lsa.TrustDomainInfoSupportedEncTypes()
2305 enc_types.enc_types = security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96
2306 enc_types.enc_types |= security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96
2308 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2309 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2310 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2312 local_trust_info = lsa.TrustDomainInfoInfoEx()
2313 local_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2314 local_trust_info.trust_direction = 0
2315 if trust_direction == "both":
2316 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2317 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2318 elif trust_direction == "incoming":
2319 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2320 elif trust_direction == "outgoing":
2321 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2322 local_trust_info.trust_attributes = 0
2323 if cross_organisation:
2324 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2325 if quarantined:
2326 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2327 if trust_type == "forest":
2328 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2329 if not_transitive:
2330 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2331 if treat_as_external:
2332 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2334 def get_password(name):
2335 password = None
2336 while True:
2337 if password is not None and password != '':
2338 return password
2339 password = getpass("New %s Password: " % name)
2340 passwordverify = getpass("Retype %s Password: " % name)
2341 if not password == passwordverify:
2342 password = None
2343 self.outf.write("Sorry, passwords do not match.\n")
2345 incoming_secret = None
2346 outgoing_secret = None
2347 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2348 if create_location == "local":
2349 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2350 incoming_password = get_password("Incoming Trust")
2351 incoming_secret = string_to_byte_array(incoming_password.encode('utf-16-le'))
2352 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2353 outgoing_password = get_password("Outgoing Trust")
2354 outgoing_secret = string_to_byte_array(outgoing_password.encode('utf-16-le'))
2356 remote_trust_info = None
2357 else:
2358 # We use 240 random bytes.
2359 # Windows uses 28 or 240 random bytes. I guess it's
2360 # based on the trust type external vs. forest.
2362 # The initial trust password can be up to 512 bytes
2363 # while the versioned passwords used for periodic updates
2364 # can only be up to 498 bytes, as netr_ServerPasswordSet2()
2365 # needs to pass the NL_PASSWORD_VERSION structure within the
2366 # 512 bytes and a 2 bytes confounder is required.
2368 def random_trust_secret(length):
2369 pw = samba.generate_random_machine_password(length // 2, length // 2)
2370 return string_to_byte_array(pw.encode('utf-16-le'))
2372 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2373 incoming_secret = random_trust_secret(240)
2374 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2375 outgoing_secret = random_trust_secret(240)
2377 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2378 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2380 remote_trust_info = lsa.TrustDomainInfoInfoEx()
2381 remote_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2382 remote_trust_info.trust_direction = 0
2383 if trust_direction == "both":
2384 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2385 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2386 elif trust_direction == "incoming":
2387 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2388 elif trust_direction == "outgoing":
2389 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2390 remote_trust_info.trust_attributes = 0
2391 if cross_organisation:
2392 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2393 if quarantined:
2394 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2395 if trust_type == "forest":
2396 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2397 if not_transitive:
2398 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2399 if treat_as_external:
2400 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2402 local_server = self.setup_local_server(sambaopts, localdcopts)
2403 try:
2404 local_lsa = self.new_local_lsa_connection()
2405 except RuntimeError as error:
2406 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2408 try:
2409 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2410 except RuntimeError as error:
2411 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2413 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2414 local_lsa_info.name.string,
2415 local_lsa_info.dns_domain.string,
2416 local_lsa_info.sid))
2418 try:
2419 remote_server = self.setup_remote_server(credopts, domain)
2420 except RuntimeError as error:
2421 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2423 try:
2424 remote_lsa = self.new_remote_lsa_connection()
2425 except RuntimeError as error:
2426 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2428 try:
2429 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2430 except RuntimeError as error:
2431 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2433 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2434 remote_lsa_info.name.string,
2435 remote_lsa_info.dns_domain.string,
2436 remote_lsa_info.sid))
2438 local_trust_info.domain_name.string = remote_lsa_info.dns_domain.string
2439 local_trust_info.netbios_name.string = remote_lsa_info.name.string
2440 local_trust_info.sid = remote_lsa_info.sid
2442 if remote_trust_info:
2443 remote_trust_info.domain_name.string = local_lsa_info.dns_domain.string
2444 remote_trust_info.netbios_name.string = local_lsa_info.name.string
2445 remote_trust_info.sid = local_lsa_info.sid
2447 try:
2448 lsaString.string = local_trust_info.domain_name.string
2449 local_old_netbios = \
2450 local_lsa.QueryTrustedDomainInfoByName(local_policy,
2451 lsaString,
2452 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2453 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2454 except NTSTATUSError as error:
2455 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2456 raise self.LocalRuntimeError(self, error,
2457 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2458 lsaString.string))
2460 try:
2461 lsaString.string = local_trust_info.netbios_name.string
2462 local_old_dns = \
2463 local_lsa.QueryTrustedDomainInfoByName(local_policy,
2464 lsaString,
2465 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2466 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2467 except NTSTATUSError as error:
2468 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2469 raise self.LocalRuntimeError(self, error,
2470 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2471 lsaString.string))
2473 if remote_trust_info:
2474 try:
2475 lsaString.string = remote_trust_info.domain_name.string
2476 remote_old_netbios = \
2477 remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2478 lsaString,
2479 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2480 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2481 except NTSTATUSError as error:
2482 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2483 raise self.RemoteRuntimeError(self, error,
2484 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2485 lsaString.string))
2487 try:
2488 lsaString.string = remote_trust_info.netbios_name.string
2489 remote_old_dns = \
2490 remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2491 lsaString,
2492 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2493 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2494 except NTSTATUSError as error:
2495 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2496 raise self.RemoteRuntimeError(self, error,
2497 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2498 lsaString.string))
2500 try:
2501 local_netlogon = self.new_local_netlogon_connection()
2502 except RuntimeError as error:
2503 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2505 try:
2506 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
2507 except RuntimeError as error:
2508 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
2510 if remote_trust_info:
2511 try:
2512 remote_netlogon = self.new_remote_netlogon_connection()
2513 except RuntimeError as error:
2514 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2516 try:
2517 remote_netlogon_dc_unc = self.get_netlogon_dc_unc(remote_netlogon,
2518 remote_server, domain)
2519 except RuntimeError as error:
2520 raise self.RemoteRuntimeError(self, error, "failed to get netlogon dc info")
2522 def generate_AuthInOutBlob(secret, update_time):
2523 if secret is None:
2524 blob = drsblobs.trustAuthInOutBlob()
2525 blob.count = 0
2527 return blob
2529 clear = drsblobs.AuthInfoClear()
2530 clear.size = len(secret)
2531 clear.password = secret
2533 info = drsblobs.AuthenticationInformation()
2534 info.LastUpdateTime = samba.unix2nttime(update_time)
2535 info.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
2536 info.AuthInfo = clear
2538 array = drsblobs.AuthenticationInformationArray()
2539 array.count = 1
2540 array.array = [info]
2542 blob = drsblobs.trustAuthInOutBlob()
2543 blob.count = 1
2544 blob.current = array
2546 return blob
2548 update_time = samba.current_unix_time()
2549 incoming_blob = generate_AuthInOutBlob(incoming_secret, update_time)
2550 outgoing_blob = generate_AuthInOutBlob(outgoing_secret, update_time)
2552 try:
2553 if remote_trust_info:
2554 self.outf.write("Creating remote TDO.\n")
2555 current_request = {"location": "remote", "name": "CreateTrustedDomainEx2"}
2556 remote_tdo_handle = CreateTrustedDomainRelax(remote_lsa,
2557 remote_policy,
2558 remote_trust_info,
2559 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS,
2560 outgoing_blob,
2561 incoming_blob)
2562 self.outf.write("Remote TDO created.\n")
2563 if enc_types:
2564 self.outf.write("Setting supported encryption types on remote TDO.\n")
2565 current_request = {"location": "remote", "name": "SetInformationTrustedDomain"}
2566 remote_lsa.SetInformationTrustedDomain(remote_tdo_handle,
2567 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2568 enc_types)
2570 self.outf.write("Creating local TDO.\n")
2571 current_request = {"location": "local", "name": "CreateTrustedDomainEx2"}
2572 local_tdo_handle = CreateTrustedDomainRelax(local_lsa,
2573 local_policy,
2574 local_trust_info,
2575 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS,
2576 incoming_blob,
2577 outgoing_blob)
2578 self.outf.write("Local TDO created\n")
2579 if enc_types:
2580 self.outf.write("Setting supported encryption types on local TDO.\n")
2581 current_request = {"location": "local", "name": "SetInformationTrustedDomain"}
2582 local_lsa.SetInformationTrustedDomain(local_tdo_handle,
2583 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2584 enc_types)
2585 except RuntimeError as error:
2586 self.outf.write("Error: %s failed %sly - cleaning up\n" % (
2587 current_request['name'], current_request['location']))
2588 if remote_tdo_handle:
2589 self.outf.write("Deleting remote TDO.\n")
2590 remote_lsa.DeleteObject(remote_tdo_handle)
2591 remote_tdo_handle = None
2592 if local_tdo_handle:
2593 self.outf.write("Deleting local TDO.\n")
2594 local_lsa.DeleteObject(local_tdo_handle)
2595 local_tdo_handle = None
2596 if current_request['location'] == "remote":
2597 raise self.RemoteRuntimeError(self, error, "%s" % (
2598 current_request['name']))
2599 raise self.LocalRuntimeError(self, error, "%s" % (
2600 current_request['name']))
2602 if validate:
2603 if local_trust_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2604 self.outf.write("Setup local forest trust information...\n")
2605 try:
2606 # get all information about the remote trust
2607 # this triggers netr_GetForestTrustInformation to the remote domain
2608 # and lsaRSetForestTrustInformation() locally, but new top level
2609 # names are disabled by default.
2610 local_forest_info = \
2611 local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
2612 remote_lsa_info.dns_domain.string,
2613 netlogon.DS_GFTI_UPDATE_TDO)
2614 except RuntimeError as error:
2615 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2617 try:
2618 # here we try to enable all top level names
2619 local_forest_collision = \
2620 local_lsa.lsaRSetForestTrustInformation(local_policy,
2621 remote_lsa_info.dns_domain,
2622 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2623 local_forest_info,
2625 except RuntimeError as error:
2626 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2628 self.write_forest_trust_info(local_forest_info,
2629 tln=remote_lsa_info.dns_domain.string,
2630 collisions=local_forest_collision)
2632 if remote_trust_info:
2633 self.outf.write("Setup remote forest trust information...\n")
2634 try:
2635 # get all information about the local trust (from the perspective of the remote domain)
2636 # this triggers netr_GetForestTrustInformation to our domain.
2637 # and lsaRSetForestTrustInformation() remotely, but new top level
2638 # names are disabled by default.
2639 remote_forest_info = \
2640 remote_netlogon.netr_DsRGetForestTrustInformation(remote_netlogon_dc_unc,
2641 local_lsa_info.dns_domain.string,
2642 netlogon.DS_GFTI_UPDATE_TDO)
2643 except RuntimeError as error:
2644 raise self.RemoteRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2646 try:
2647 # here we try to enable all top level names
2648 remote_forest_collision = \
2649 remote_lsa.lsaRSetForestTrustInformation(remote_policy,
2650 local_lsa_info.dns_domain,
2651 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2652 remote_forest_info,
2654 except RuntimeError as error:
2655 raise self.RemoteRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2657 self.write_forest_trust_info(remote_forest_info,
2658 tln=local_lsa_info.dns_domain.string,
2659 collisions=remote_forest_collision)
2661 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2662 self.outf.write("Validating outgoing trust...\n")
2663 try:
2664 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_netlogon_info.dc_unc,
2665 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2667 remote_lsa_info.dns_domain.string)
2668 except RuntimeError as error:
2669 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2671 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2672 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2674 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2675 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2676 local_trust_verify.trusted_dc_name,
2677 local_trust_verify.tc_connection_status[1],
2678 local_trust_verify.pdc_connection_status[1])
2679 else:
2680 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2681 local_trust_verify.trusted_dc_name,
2682 local_trust_verify.tc_connection_status[1],
2683 local_trust_verify.pdc_connection_status[1])
2685 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2686 raise CommandError(local_validation)
2687 else:
2688 self.outf.write("OK: %s\n" % local_validation)
2690 if remote_trust_info:
2691 if remote_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2692 self.outf.write("Validating incoming trust...\n")
2693 try:
2694 remote_trust_verify = \
2695 remote_netlogon.netr_LogonControl2Ex(remote_netlogon_dc_unc,
2696 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2698 local_lsa_info.dns_domain.string)
2699 except RuntimeError as error:
2700 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2702 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2703 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2705 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2706 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2707 remote_trust_verify.trusted_dc_name,
2708 remote_trust_verify.tc_connection_status[1],
2709 remote_trust_verify.pdc_connection_status[1])
2710 else:
2711 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2712 remote_trust_verify.trusted_dc_name,
2713 remote_trust_verify.tc_connection_status[1],
2714 remote_trust_verify.pdc_connection_status[1])
2716 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
2717 raise CommandError(remote_validation)
2718 else:
2719 self.outf.write("OK: %s\n" % remote_validation)
2721 if remote_tdo_handle is not None:
2722 try:
2723 remote_lsa.Close(remote_tdo_handle)
2724 except RuntimeError as error:
2725 pass
2726 remote_tdo_handle = None
2727 if local_tdo_handle is not None:
2728 try:
2729 local_lsa.Close(local_tdo_handle)
2730 except RuntimeError as error:
2731 pass
2732 local_tdo_handle = None
2734 self.outf.write("Success.\n")
2735 return
2738 class cmd_domain_trust_delete(DomainTrustCommand):
2739 """Delete a domain trust."""
2741 synopsis = "%prog DOMAIN [options]"
2743 takes_optiongroups = {
2744 "sambaopts": options.SambaOptions,
2745 "versionopts": options.VersionOptions,
2746 "credopts": options.CredentialsOptions,
2747 "localdcopts": LocalDCCredentialsOptions,
2750 takes_options = [
2751 Option("--delete-location", type="choice", metavar="LOCATION",
2752 choices=["local", "both"],
2753 help="Where to delete the trusted domain object: 'local' or 'both'.",
2754 dest='delete_location',
2755 default="both"),
2758 takes_args = ["domain"]
2760 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2761 delete_location=None):
2763 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2764 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2765 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2767 if delete_location == "local":
2768 remote_policy_access = None
2769 else:
2770 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2771 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2772 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2774 local_server = self.setup_local_server(sambaopts, localdcopts)
2775 try:
2776 local_lsa = self.new_local_lsa_connection()
2777 except RuntimeError as error:
2778 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2780 try:
2781 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2782 except RuntimeError as error:
2783 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2785 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2786 local_lsa_info.name.string,
2787 local_lsa_info.dns_domain.string,
2788 local_lsa_info.sid))
2790 local_tdo_info = None
2791 local_tdo_handle = None
2792 remote_tdo_info = None
2793 remote_tdo_handle = None
2795 lsaString = lsa.String()
2796 try:
2797 lsaString.string = domain
2798 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2799 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2800 except NTSTATUSError as error:
2801 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2802 raise CommandError("Failed to find trust for domain '%s'" % domain)
2803 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2805 if remote_policy_access is not None:
2806 try:
2807 remote_server = self.setup_remote_server(credopts, domain)
2808 except RuntimeError as error:
2809 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2811 try:
2812 remote_lsa = self.new_remote_lsa_connection()
2813 except RuntimeError as error:
2814 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2816 try:
2817 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2818 except RuntimeError as error:
2819 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2821 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2822 remote_lsa_info.name.string,
2823 remote_lsa_info.dns_domain.string,
2824 remote_lsa_info.sid))
2826 if remote_lsa_info.sid != local_tdo_info.sid or \
2827 remote_lsa_info.name.string != local_tdo_info.netbios_name.string or \
2828 remote_lsa_info.dns_domain.string != local_tdo_info.domain_name.string:
2829 raise CommandError("LocalTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2830 local_tdo_info.netbios_name.string,
2831 local_tdo_info.domain_name.string,
2832 local_tdo_info.sid))
2834 try:
2835 lsaString.string = local_lsa_info.dns_domain.string
2836 remote_tdo_info = \
2837 remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2838 lsaString,
2839 lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2840 except NTSTATUSError as error:
2841 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2842 raise self.RemoteRuntimeError(self, error, "QueryTrustedDomainInfoByName(%s)" % (
2843 lsaString.string))
2844 pass
2846 if remote_tdo_info is not None:
2847 if local_lsa_info.sid != remote_tdo_info.sid or \
2848 local_lsa_info.name.string != remote_tdo_info.netbios_name.string or \
2849 local_lsa_info.dns_domain.string != remote_tdo_info.domain_name.string:
2850 raise CommandError("RemoteTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2851 remote_tdo_info.netbios_name.string,
2852 remote_tdo_info.domain_name.string,
2853 remote_tdo_info.sid))
2855 if local_tdo_info is not None:
2856 try:
2857 lsaString.string = local_tdo_info.domain_name.string
2858 local_tdo_handle = \
2859 local_lsa.OpenTrustedDomainByName(local_policy,
2860 lsaString,
2861 security.SEC_STD_DELETE)
2862 except RuntimeError as error:
2863 raise self.LocalRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2864 lsaString.string))
2866 local_lsa.DeleteObject(local_tdo_handle)
2867 local_tdo_handle = None
2869 if remote_tdo_info is not None:
2870 try:
2871 lsaString.string = remote_tdo_info.domain_name.string
2872 remote_tdo_handle = \
2873 remote_lsa.OpenTrustedDomainByName(remote_policy,
2874 lsaString,
2875 security.SEC_STD_DELETE)
2876 except RuntimeError as error:
2877 raise self.RemoteRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2878 lsaString.string))
2880 if remote_tdo_handle is not None:
2881 try:
2882 remote_lsa.DeleteObject(remote_tdo_handle)
2883 remote_tdo_handle = None
2884 self.outf.write("RemoteTDO deleted.\n")
2885 except RuntimeError as error:
2886 self.outf.write("%s\n" % self.RemoteRuntimeError(self, error, "DeleteObject() failed"))
2888 if local_tdo_handle is not None:
2889 try:
2890 local_lsa.DeleteObject(local_tdo_handle)
2891 local_tdo_handle = None
2892 self.outf.write("LocalTDO deleted.\n")
2893 except RuntimeError as error:
2894 self.outf.write("%s\n" % self.LocalRuntimeError(self, error, "DeleteObject() failed"))
2896 return
2899 class cmd_domain_trust_validate(DomainTrustCommand):
2900 """Validate a domain trust."""
2902 synopsis = "%prog DOMAIN [options]"
2904 takes_optiongroups = {
2905 "sambaopts": options.SambaOptions,
2906 "versionopts": options.VersionOptions,
2907 "credopts": options.CredentialsOptions,
2908 "localdcopts": LocalDCCredentialsOptions,
2911 takes_options = [
2912 Option("--validate-location", type="choice", metavar="LOCATION",
2913 choices=["local", "both"],
2914 help="Where to validate the trusted domain object: 'local' or 'both'.",
2915 dest='validate_location',
2916 default="both"),
2919 takes_args = ["domain"]
2921 def run(self, domain, sambaopts=None, versionopts=None, credopts=None, localdcopts=None,
2922 validate_location=None):
2924 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2926 local_server = self.setup_local_server(sambaopts, localdcopts)
2927 try:
2928 local_lsa = self.new_local_lsa_connection()
2929 except RuntimeError as error:
2930 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2932 try:
2933 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2934 except RuntimeError as error:
2935 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2937 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2938 local_lsa_info.name.string,
2939 local_lsa_info.dns_domain.string,
2940 local_lsa_info.sid))
2942 try:
2943 lsaString = lsa.String()
2944 lsaString.string = domain
2945 local_tdo_info = \
2946 local_lsa.QueryTrustedDomainInfoByName(local_policy,
2947 lsaString,
2948 lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2949 except NTSTATUSError as error:
2950 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2951 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2953 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
2955 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
2956 local_tdo_info.netbios_name.string,
2957 local_tdo_info.domain_name.string,
2958 local_tdo_info.sid))
2960 try:
2961 local_netlogon = self.new_local_netlogon_connection()
2962 except RuntimeError as error:
2963 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2965 try:
2966 local_trust_verify = \
2967 local_netlogon.netr_LogonControl2Ex(local_server,
2968 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2970 local_tdo_info.domain_name.string)
2971 except RuntimeError as error:
2972 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2974 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2975 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2977 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2978 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2979 local_trust_verify.trusted_dc_name,
2980 local_trust_verify.tc_connection_status[1],
2981 local_trust_verify.pdc_connection_status[1])
2982 else:
2983 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2984 local_trust_verify.trusted_dc_name,
2985 local_trust_verify.tc_connection_status[1],
2986 local_trust_verify.pdc_connection_status[1])
2988 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2989 raise CommandError(local_validation)
2990 else:
2991 self.outf.write("OK: %s\n" % local_validation)
2993 try:
2994 server = local_trust_verify.trusted_dc_name.replace('\\', '')
2995 domain_and_server = "%s\\%s" % (local_tdo_info.domain_name.string, server)
2996 local_trust_rediscover = \
2997 local_netlogon.netr_LogonControl2Ex(local_server,
2998 netlogon.NETLOGON_CONTROL_REDISCOVER,
3000 domain_and_server)
3001 except RuntimeError as error:
3002 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
3004 local_conn_status = self._uint32(local_trust_rediscover.tc_connection_status[0])
3005 local_rediscover = "LocalRediscover: DC[%s] CONNECTION[%s]" % (
3006 local_trust_rediscover.trusted_dc_name,
3007 local_trust_rediscover.tc_connection_status[1])
3009 if local_conn_status != werror.WERR_SUCCESS:
3010 raise CommandError(local_rediscover)
3011 else:
3012 self.outf.write("OK: %s\n" % local_rediscover)
3014 if validate_location != "local":
3015 try:
3016 remote_server = self.setup_remote_server(credopts, domain, require_pdc=False)
3017 except RuntimeError as error:
3018 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
3020 try:
3021 remote_netlogon = self.new_remote_netlogon_connection()
3022 except RuntimeError as error:
3023 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
3025 try:
3026 remote_trust_verify = \
3027 remote_netlogon.netr_LogonControl2Ex(remote_server,
3028 netlogon.NETLOGON_CONTROL_TC_VERIFY,
3030 local_lsa_info.dns_domain.string)
3031 except RuntimeError as error:
3032 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
3034 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
3035 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
3037 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
3038 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
3039 remote_trust_verify.trusted_dc_name,
3040 remote_trust_verify.tc_connection_status[1],
3041 remote_trust_verify.pdc_connection_status[1])
3042 else:
3043 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
3044 remote_trust_verify.trusted_dc_name,
3045 remote_trust_verify.tc_connection_status[1],
3046 remote_trust_verify.pdc_connection_status[1])
3048 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
3049 raise CommandError(remote_validation)
3050 else:
3051 self.outf.write("OK: %s\n" % remote_validation)
3053 try:
3054 server = remote_trust_verify.trusted_dc_name.replace('\\', '')
3055 domain_and_server = "%s\\%s" % (local_lsa_info.dns_domain.string, server)
3056 remote_trust_rediscover = \
3057 remote_netlogon.netr_LogonControl2Ex(remote_server,
3058 netlogon.NETLOGON_CONTROL_REDISCOVER,
3060 domain_and_server)
3061 except RuntimeError as error:
3062 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
3064 remote_conn_status = self._uint32(remote_trust_rediscover.tc_connection_status[0])
3066 remote_rediscover = "RemoteRediscover: DC[%s] CONNECTION[%s]" % (
3067 remote_trust_rediscover.trusted_dc_name,
3068 remote_trust_rediscover.tc_connection_status[1])
3070 if remote_conn_status != werror.WERR_SUCCESS:
3071 raise CommandError(remote_rediscover)
3072 else:
3073 self.outf.write("OK: %s\n" % remote_rediscover)
3075 return
3078 class cmd_domain_trust_namespaces(DomainTrustCommand):
3079 """Manage forest trust namespaces."""
3081 synopsis = "%prog [DOMAIN] [options]"
3083 takes_optiongroups = {
3084 "sambaopts": options.SambaOptions,
3085 "versionopts": options.VersionOptions,
3086 "localdcopts": LocalDCCredentialsOptions,
3089 takes_options = [
3090 Option("--refresh", type="choice", metavar="check|store",
3091 choices=["check", "store", None],
3092 help="List and maybe store refreshed forest trust information: 'check' or 'store'.",
3093 dest='refresh',
3094 default=None),
3095 Option("--enable-all", action="store_true",
3096 help="Try to update disabled entries, not allowed with --refresh=check.",
3097 dest='enable_all',
3098 default=False),
3099 Option("--enable-tln", action="append", metavar='DNSDOMAIN',
3100 help="Enable a top level name entry. Can be specified multiple times.",
3101 dest='enable_tln',
3102 default=[]),
3103 Option("--disable-tln", action="append", metavar='DNSDOMAIN',
3104 help="Disable a top level name entry. Can be specified multiple times.",
3105 dest='disable_tln',
3106 default=[]),
3107 Option("--add-tln-ex", action="append", metavar='DNSDOMAIN',
3108 help="Add a top level exclusion entry. Can be specified multiple times.",
3109 dest='add_tln_ex',
3110 default=[]),
3111 Option("--delete-tln-ex", action="append", metavar='DNSDOMAIN',
3112 help="Delete a top level exclusion entry. Can be specified multiple times.",
3113 dest='delete_tln_ex',
3114 default=[]),
3115 Option("--enable-nb", action="append", metavar='NETBIOSDOMAIN',
3116 help="Enable a netbios name in a domain entry. Can be specified multiple times.",
3117 dest='enable_nb',
3118 default=[]),
3119 Option("--disable-nb", action="append", metavar='NETBIOSDOMAIN',
3120 help="Disable a netbios name in a domain entry. Can be specified multiple times.",
3121 dest='disable_nb',
3122 default=[]),
3123 Option("--enable-sid", action="append", metavar='DOMAINSID',
3124 help="Enable a SID in a domain entry. Can be specified multiple times.",
3125 dest='enable_sid_str',
3126 default=[]),
3127 Option("--disable-sid", action="append", metavar='DOMAINSID',
3128 help="Disable a SID in a domain entry. Can be specified multiple times.",
3129 dest='disable_sid_str',
3130 default=[]),
3131 Option("--add-upn-suffix", action="append", metavar='DNSDOMAIN',
3132 help="Add a new uPNSuffixes attribute for the local forest. Can be specified multiple times.",
3133 dest='add_upn',
3134 default=[]),
3135 Option("--delete-upn-suffix", action="append", metavar='DNSDOMAIN',
3136 help="Delete an existing uPNSuffixes attribute of the local forest. Can be specified multiple times.",
3137 dest='delete_upn',
3138 default=[]),
3139 Option("--add-spn-suffix", action="append", metavar='DNSDOMAIN',
3140 help="Add a new msDS-SPNSuffixes attribute for the local forest. Can be specified multiple times.",
3141 dest='add_spn',
3142 default=[]),
3143 Option("--delete-spn-suffix", action="append", metavar='DNSDOMAIN',
3144 help="Delete an existing msDS-SPNSuffixes attribute of the local forest. Can be specified multiple times.",
3145 dest='delete_spn',
3146 default=[]),
3149 takes_args = ["domain?"]
3151 def run(self, domain=None, sambaopts=None, localdcopts=None, versionopts=None,
3152 refresh=None, enable_all=False,
3153 enable_tln=[], disable_tln=[], add_tln_ex=[], delete_tln_ex=[],
3154 enable_sid_str=[], disable_sid_str=[], enable_nb=[], disable_nb=[],
3155 add_upn=[], delete_upn=[], add_spn=[], delete_spn=[]):
3157 require_update = False
3159 if domain is None:
3160 if refresh == "store":
3161 raise CommandError("--refresh=%s not allowed without DOMAIN" % refresh)
3163 if enable_all:
3164 raise CommandError("--enable-all not allowed without DOMAIN")
3166 if len(enable_tln) > 0:
3167 raise CommandError("--enable-tln not allowed without DOMAIN")
3168 if len(disable_tln) > 0:
3169 raise CommandError("--disable-tln not allowed without DOMAIN")
3171 if len(add_tln_ex) > 0:
3172 raise CommandError("--add-tln-ex not allowed without DOMAIN")
3173 if len(delete_tln_ex) > 0:
3174 raise CommandError("--delete-tln-ex not allowed without DOMAIN")
3176 if len(enable_nb) > 0:
3177 raise CommandError("--enable-nb not allowed without DOMAIN")
3178 if len(disable_nb) > 0:
3179 raise CommandError("--disable-nb not allowed without DOMAIN")
3181 if len(enable_sid_str) > 0:
3182 raise CommandError("--enable-sid not allowed without DOMAIN")
3183 if len(disable_sid_str) > 0:
3184 raise CommandError("--disable-sid not allowed without DOMAIN")
3186 if len(add_upn) > 0:
3187 for n in add_upn:
3188 if not n.startswith("*."):
3189 continue
3190 raise CommandError("value[%s] specified for --add-upn-suffix should not include with '*.'" % n)
3191 require_update = True
3192 if len(delete_upn) > 0:
3193 for n in delete_upn:
3194 if not n.startswith("*."):
3195 continue
3196 raise CommandError("value[%s] specified for --delete-upn-suffix should not include with '*.'" % n)
3197 require_update = True
3198 for a in add_upn:
3199 for d in delete_upn:
3200 if a.lower() != d.lower():
3201 continue
3202 raise CommandError("value[%s] specified for --add-upn-suffix and --delete-upn-suffix" % a)
3204 if len(add_spn) > 0:
3205 for n in add_spn:
3206 if not n.startswith("*."):
3207 continue
3208 raise CommandError("value[%s] specified for --add-spn-suffix should not include with '*.'" % n)
3209 require_update = True
3210 if len(delete_spn) > 0:
3211 for n in delete_spn:
3212 if not n.startswith("*."):
3213 continue
3214 raise CommandError("value[%s] specified for --delete-spn-suffix should not include with '*.'" % n)
3215 require_update = True
3216 for a in add_spn:
3217 for d in delete_spn:
3218 if a.lower() != d.lower():
3219 continue
3220 raise CommandError("value[%s] specified for --add-spn-suffix and --delete-spn-suffix" % a)
3221 else:
3222 if len(add_upn) > 0:
3223 raise CommandError("--add-upn-suffix not allowed together with DOMAIN")
3224 if len(delete_upn) > 0:
3225 raise CommandError("--delete-upn-suffix not allowed together with DOMAIN")
3226 if len(add_spn) > 0:
3227 raise CommandError("--add-spn-suffix not allowed together with DOMAIN")
3228 if len(delete_spn) > 0:
3229 raise CommandError("--delete-spn-suffix not allowed together with DOMAIN")
3231 if refresh is not None:
3232 if refresh == "store":
3233 require_update = True
3235 if enable_all and refresh != "store":
3236 raise CommandError("--enable-all not allowed together with --refresh=%s" % refresh)
3238 if len(enable_tln) > 0:
3239 raise CommandError("--enable-tln not allowed together with --refresh")
3240 if len(disable_tln) > 0:
3241 raise CommandError("--disable-tln not allowed together with --refresh")
3243 if len(add_tln_ex) > 0:
3244 raise CommandError("--add-tln-ex not allowed together with --refresh")
3245 if len(delete_tln_ex) > 0:
3246 raise CommandError("--delete-tln-ex not allowed together with --refresh")
3248 if len(enable_nb) > 0:
3249 raise CommandError("--enable-nb not allowed together with --refresh")
3250 if len(disable_nb) > 0:
3251 raise CommandError("--disable-nb not allowed together with --refresh")
3253 if len(enable_sid_str) > 0:
3254 raise CommandError("--enable-sid not allowed together with --refresh")
3255 if len(disable_sid_str) > 0:
3256 raise CommandError("--disable-sid not allowed together with --refresh")
3257 else:
3258 if enable_all:
3259 require_update = True
3261 if len(enable_tln) > 0:
3262 raise CommandError("--enable-tln not allowed together with --enable-all")
3264 if len(enable_nb) > 0:
3265 raise CommandError("--enable-nb not allowed together with --enable-all")
3267 if len(enable_sid_str) > 0:
3268 raise CommandError("--enable-sid not allowed together with --enable-all")
3270 if len(enable_tln) > 0:
3271 require_update = True
3272 if len(disable_tln) > 0:
3273 require_update = True
3274 for e in enable_tln:
3275 for d in disable_tln:
3276 if e.lower() != d.lower():
3277 continue
3278 raise CommandError("value[%s] specified for --enable-tln and --disable-tln" % e)
3280 if len(add_tln_ex) > 0:
3281 for n in add_tln_ex:
3282 if not n.startswith("*."):
3283 continue
3284 raise CommandError("value[%s] specified for --add-tln-ex should not include with '*.'" % n)
3285 require_update = True
3286 if len(delete_tln_ex) > 0:
3287 for n in delete_tln_ex:
3288 if not n.startswith("*."):
3289 continue
3290 raise CommandError("value[%s] specified for --delete-tln-ex should not include with '*.'" % n)
3291 require_update = True
3292 for a in add_tln_ex:
3293 for d in delete_tln_ex:
3294 if a.lower() != d.lower():
3295 continue
3296 raise CommandError("value[%s] specified for --add-tln-ex and --delete-tln-ex" % a)
3298 if len(enable_nb) > 0:
3299 require_update = True
3300 if len(disable_nb) > 0:
3301 require_update = True
3302 for e in enable_nb:
3303 for d in disable_nb:
3304 if e.upper() != d.upper():
3305 continue
3306 raise CommandError("value[%s] specified for --enable-nb and --disable-nb" % e)
3308 enable_sid = []
3309 for s in enable_sid_str:
3310 try:
3311 sid = security.dom_sid(s)
3312 except TypeError as error:
3313 raise CommandError("value[%s] specified for --enable-sid is not a valid SID" % s)
3314 enable_sid.append(sid)
3315 disable_sid = []
3316 for s in disable_sid_str:
3317 try:
3318 sid = security.dom_sid(s)
3319 except TypeError as error:
3320 raise CommandError("value[%s] specified for --disable-sid is not a valid SID" % s)
3321 disable_sid.append(sid)
3322 if len(enable_sid) > 0:
3323 require_update = True
3324 if len(disable_sid) > 0:
3325 require_update = True
3326 for e in enable_sid:
3327 for d in disable_sid:
3328 if e != d:
3329 continue
3330 raise CommandError("value[%s] specified for --enable-sid and --disable-sid" % e)
3332 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
3333 if require_update:
3334 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
3336 local_server = self.setup_local_server(sambaopts, localdcopts)
3337 try:
3338 local_lsa = self.new_local_lsa_connection()
3339 except RuntimeError as error:
3340 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
3342 try:
3343 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
3344 except RuntimeError as error:
3345 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
3347 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
3348 local_lsa_info.name.string,
3349 local_lsa_info.dns_domain.string,
3350 local_lsa_info.sid))
3352 if domain is None:
3353 try:
3354 local_netlogon = self.new_local_netlogon_connection()
3355 except RuntimeError as error:
3356 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3358 try:
3359 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3360 except RuntimeError as error:
3361 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3363 if local_netlogon_info.domain_name != local_netlogon_info.forest_name:
3364 raise CommandError("The local domain [%s] is not the forest root [%s]" % (
3365 local_netlogon_info.domain_name,
3366 local_netlogon_info.forest_name))
3368 try:
3369 # get all information about our own forest
3370 own_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3371 None, 0)
3372 except RuntimeError as error:
3373 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
3374 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3375 self.local_server))
3377 if self.check_runtime_error(error, werror.WERR_INVALID_FUNCTION):
3378 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3379 self.local_server))
3381 if self.check_runtime_error(error, werror.WERR_NERR_ACFNOTLOADED):
3382 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3383 self.local_server))
3385 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3387 self.outf.write("Own forest trust information...\n")
3388 self.write_forest_trust_info(own_forest_info,
3389 tln=local_lsa_info.dns_domain.string)
3391 try:
3392 local_samdb = self.new_local_ldap_connection()
3393 except RuntimeError as error:
3394 raise self.LocalRuntimeError(self, error, "failed to connect to SamDB")
3396 local_partitions_dn = "CN=Partitions,%s" % str(local_samdb.get_config_basedn())
3397 attrs = ['uPNSuffixes', 'msDS-SPNSuffixes']
3398 try:
3399 msgs = local_samdb.search(base=local_partitions_dn,
3400 scope=ldb.SCOPE_BASE,
3401 expression="(objectClass=crossRefContainer)",
3402 attrs=attrs)
3403 stored_msg = msgs[0]
3404 except ldb.LdbError as error:
3405 raise self.LocalLdbError(self, error, "failed to search partition dn")
3407 stored_upn_vals = []
3408 if 'uPNSuffixes' in stored_msg:
3409 stored_upn_vals.extend(stored_msg['uPNSuffixes'])
3411 stored_spn_vals = []
3412 if 'msDS-SPNSuffixes' in stored_msg:
3413 stored_spn_vals.extend(stored_msg['msDS-SPNSuffixes'])
3415 self.outf.write("Stored uPNSuffixes attributes[%d]:\n" % len(stored_upn_vals))
3416 for v in stored_upn_vals:
3417 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3418 self.outf.write("Stored msDS-SPNSuffixes attributes[%d]:\n" % len(stored_spn_vals))
3419 for v in stored_spn_vals:
3420 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3422 if not require_update:
3423 return
3425 replace_upn = False
3426 update_upn_vals = []
3427 update_upn_vals.extend(stored_upn_vals)
3429 replace_spn = False
3430 update_spn_vals = []
3431 update_spn_vals.extend(stored_spn_vals)
3433 for upn in add_upn:
3434 for i, v in enumerate(update_upn_vals):
3435 if str(v).lower() == upn.lower():
3436 raise CommandError("Entry already present for "
3437 "value[%s] specified for "
3438 "--add-upn-suffix" % upn)
3439 update_upn_vals.append(upn)
3440 replace_upn = True
3442 for upn in delete_upn:
3443 idx = None
3444 for i, v in enumerate(update_upn_vals):
3445 if str(v).lower() != upn.lower():
3446 continue
3447 idx = i
3448 break
3449 if idx is None:
3450 raise CommandError("Entry not found for value[%s] specified for --delete-upn-suffix" % upn)
3452 update_upn_vals.pop(idx)
3453 replace_upn = True
3455 for spn in add_spn:
3456 for i, v in enumerate(update_spn_vals):
3457 if str(v).lower() == spn.lower():
3458 raise CommandError("Entry already present for "
3459 "value[%s] specified for "
3460 "--add-spn-suffix" % spn)
3461 update_spn_vals.append(spn)
3462 replace_spn = True
3464 for spn in delete_spn:
3465 idx = None
3466 for i, v in enumerate(update_spn_vals):
3467 if str(v).lower() != spn.lower():
3468 continue
3469 idx = i
3470 break
3471 if idx is None:
3472 raise CommandError("Entry not found for value[%s] specified for --delete-spn-suffix" % spn)
3474 update_spn_vals.pop(idx)
3475 replace_spn = True
3477 self.outf.write("Update uPNSuffixes attributes[%d]:\n" % len(update_upn_vals))
3478 for v in update_upn_vals:
3479 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3480 self.outf.write("Update msDS-SPNSuffixes attributes[%d]:\n" % len(update_spn_vals))
3481 for v in update_spn_vals:
3482 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3484 update_msg = ldb.Message()
3485 update_msg.dn = stored_msg.dn
3487 if replace_upn:
3488 update_msg['uPNSuffixes'] = ldb.MessageElement(update_upn_vals,
3489 ldb.FLAG_MOD_REPLACE,
3490 'uPNSuffixes')
3491 if replace_spn:
3492 update_msg['msDS-SPNSuffixes'] = ldb.MessageElement(update_spn_vals,
3493 ldb.FLAG_MOD_REPLACE,
3494 'msDS-SPNSuffixes')
3495 try:
3496 local_samdb.modify(update_msg)
3497 except ldb.LdbError as error:
3498 raise self.LocalLdbError(self, error, "failed to update partition dn")
3500 try:
3501 stored_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3502 None, 0)
3503 except RuntimeError as error:
3504 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3506 self.outf.write("Stored forest trust information...\n")
3507 self.write_forest_trust_info(stored_forest_info,
3508 tln=local_lsa_info.dns_domain.string)
3509 return
3511 try:
3512 lsaString = lsa.String()
3513 lsaString.string = domain
3514 local_tdo_info = \
3515 local_lsa.QueryTrustedDomainInfoByName(local_policy,
3516 lsaString,
3517 lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
3518 except NTSTATUSError as error:
3519 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
3520 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
3522 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
3524 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
3525 local_tdo_info.netbios_name.string,
3526 local_tdo_info.domain_name.string,
3527 local_tdo_info.sid))
3529 if not local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
3530 raise CommandError("trusted domain object for domain [%s] is not marked as FOREST_TRANSITIVE." % domain)
3532 if refresh is not None:
3533 try:
3534 local_netlogon = self.new_local_netlogon_connection()
3535 except RuntimeError as error:
3536 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3538 try:
3539 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3540 except RuntimeError as error:
3541 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3543 lsa_update_check = 1
3544 if refresh == "store":
3545 netlogon_update_tdo = netlogon.DS_GFTI_UPDATE_TDO
3546 if enable_all:
3547 lsa_update_check = 0
3548 else:
3549 netlogon_update_tdo = 0
3551 try:
3552 # get all information about the remote trust
3553 # this triggers netr_GetForestTrustInformation to the remote domain
3554 # and lsaRSetForestTrustInformation() locally, but new top level
3555 # names are disabled by default.
3556 fresh_forest_info = \
3557 local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3558 local_tdo_info.domain_name.string,
3559 netlogon_update_tdo)
3560 except RuntimeError as error:
3561 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3563 try:
3564 fresh_forest_collision = \
3565 local_lsa.lsaRSetForestTrustInformation(local_policy,
3566 local_tdo_info.domain_name,
3567 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3568 fresh_forest_info,
3569 lsa_update_check)
3570 except RuntimeError as error:
3571 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3573 self.outf.write("Fresh forest trust information...\n")
3574 self.write_forest_trust_info(fresh_forest_info,
3575 tln=local_tdo_info.domain_name.string,
3576 collisions=fresh_forest_collision)
3578 if refresh == "store":
3579 try:
3580 lsaString = lsa.String()
3581 lsaString.string = local_tdo_info.domain_name.string
3582 stored_forest_info = \
3583 local_lsa.lsaRQueryForestTrustInformation(local_policy,
3584 lsaString,
3585 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3586 except RuntimeError as error:
3587 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3589 self.outf.write("Stored forest trust information...\n")
3590 self.write_forest_trust_info(stored_forest_info,
3591 tln=local_tdo_info.domain_name.string)
3593 return
3596 # The none --refresh path
3599 try:
3600 lsaString = lsa.String()
3601 lsaString.string = local_tdo_info.domain_name.string
3602 local_forest_info = \
3603 local_lsa.lsaRQueryForestTrustInformation(local_policy,
3604 lsaString,
3605 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3606 except RuntimeError as error:
3607 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3609 self.outf.write("Local forest trust information...\n")
3610 self.write_forest_trust_info(local_forest_info,
3611 tln=local_tdo_info.domain_name.string)
3613 if not require_update:
3614 return
3616 entries = []
3617 entries.extend(local_forest_info.entries)
3618 update_forest_info = lsa.ForestTrustInformation()
3619 update_forest_info.count = len(entries)
3620 update_forest_info.entries = entries
3622 if enable_all:
3623 for i, r in enumerate(update_forest_info.entries):
3624 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3625 continue
3626 if update_forest_info.entries[i].flags == 0:
3627 continue
3628 update_forest_info.entries[i].time = 0
3629 update_forest_info.entries[i].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3630 for i, r in enumerate(update_forest_info.entries):
3631 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3632 continue
3633 if update_forest_info.entries[i].flags == 0:
3634 continue
3635 update_forest_info.entries[i].time = 0
3636 update_forest_info.entries[i].flags &= ~lsa.LSA_NB_DISABLED_MASK
3637 update_forest_info.entries[i].flags &= ~lsa.LSA_SID_DISABLED_MASK
3639 for tln in enable_tln:
3640 idx = None
3641 for i, r in enumerate(update_forest_info.entries):
3642 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3643 continue
3644 if r.forest_trust_data.string.lower() != tln.lower():
3645 continue
3646 idx = i
3647 break
3648 if idx is None:
3649 raise CommandError("Entry not found for value[%s] specified for --enable-tln" % tln)
3650 if not update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_MASK:
3651 raise CommandError("Entry found for value[%s] specified for --enable-tln is already enabled" % tln)
3652 update_forest_info.entries[idx].time = 0
3653 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3655 for tln in disable_tln:
3656 idx = None
3657 for i, r in enumerate(update_forest_info.entries):
3658 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3659 continue
3660 if r.forest_trust_data.string.lower() != tln.lower():
3661 continue
3662 idx = i
3663 break
3664 if idx is None:
3665 raise CommandError("Entry not found for value[%s] specified for --disable-tln" % tln)
3666 if update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_ADMIN:
3667 raise CommandError("Entry found for value[%s] specified for --disable-tln is already disabled" % tln)
3668 update_forest_info.entries[idx].time = 0
3669 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3670 update_forest_info.entries[idx].flags |= lsa.LSA_TLN_DISABLED_ADMIN
3672 for tln_ex in add_tln_ex:
3673 idx = None
3674 for i, r in enumerate(update_forest_info.entries):
3675 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3676 continue
3677 if r.forest_trust_data.string.lower() != tln_ex.lower():
3678 continue
3679 idx = i
3680 break
3681 if idx is not None:
3682 raise CommandError("Entry already present for value[%s] specified for --add-tln-ex" % tln_ex)
3684 tln_dot = ".%s" % tln_ex.lower()
3685 idx = None
3686 for i, r in enumerate(update_forest_info.entries):
3687 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3688 continue
3689 r_dot = ".%s" % r.forest_trust_data.string.lower()
3690 if tln_dot == r_dot:
3691 raise CommandError("TLN entry present for value[%s] specified for --add-tln-ex" % tln_ex)
3692 if not tln_dot.endswith(r_dot):
3693 continue
3694 idx = i
3695 break
3697 if idx is None:
3698 raise CommandError("No TLN parent present for value[%s] specified for --add-tln-ex" % tln_ex)
3700 r = lsa.ForestTrustRecord()
3701 r.type = lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX
3702 r.flags = 0
3703 r.time = 0
3704 r.forest_trust_data.string = tln_ex
3706 entries = []
3707 entries.extend(update_forest_info.entries)
3708 entries.insert(idx + 1, r)
3709 update_forest_info.count = len(entries)
3710 update_forest_info.entries = entries
3712 for tln_ex in delete_tln_ex:
3713 idx = None
3714 for i, r in enumerate(update_forest_info.entries):
3715 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3716 continue
3717 if r.forest_trust_data.string.lower() != tln_ex.lower():
3718 continue
3719 idx = i
3720 break
3721 if idx is None:
3722 raise CommandError("Entry not found for value[%s] specified for --delete-tln-ex" % tln_ex)
3724 entries = []
3725 entries.extend(update_forest_info.entries)
3726 entries.pop(idx)
3727 update_forest_info.count = len(entries)
3728 update_forest_info.entries = entries
3730 for nb in enable_nb:
3731 idx = None
3732 for i, r in enumerate(update_forest_info.entries):
3733 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3734 continue
3735 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3736 continue
3737 idx = i
3738 break
3739 if idx is None:
3740 raise CommandError("Entry not found for value[%s] specified for --enable-nb" % nb)
3741 if not update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_MASK:
3742 raise CommandError("Entry found for value[%s] specified for --enable-nb is already enabled" % nb)
3743 update_forest_info.entries[idx].time = 0
3744 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3746 for nb in disable_nb:
3747 idx = None
3748 for i, r in enumerate(update_forest_info.entries):
3749 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3750 continue
3751 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3752 continue
3753 idx = i
3754 break
3755 if idx is None:
3756 raise CommandError("Entry not found for value[%s] specified for --delete-nb" % nb)
3757 if update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_ADMIN:
3758 raise CommandError("Entry found for value[%s] specified for --disable-nb is already disabled" % nb)
3759 update_forest_info.entries[idx].time = 0
3760 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3761 update_forest_info.entries[idx].flags |= lsa.LSA_NB_DISABLED_ADMIN
3763 for sid in enable_sid:
3764 idx = None
3765 for i, r in enumerate(update_forest_info.entries):
3766 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3767 continue
3768 if r.forest_trust_data.domain_sid != sid:
3769 continue
3770 idx = i
3771 break
3772 if idx is None:
3773 raise CommandError("Entry not found for value[%s] specified for --enable-sid" % sid)
3774 if not update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_MASK:
3775 raise CommandError("Entry found for value[%s] specified for --enable-sid is already enabled" % nb)
3776 update_forest_info.entries[idx].time = 0
3777 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3779 for sid in disable_sid:
3780 idx = None
3781 for i, r in enumerate(update_forest_info.entries):
3782 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3783 continue
3784 if r.forest_trust_data.domain_sid != sid:
3785 continue
3786 idx = i
3787 break
3788 if idx is None:
3789 raise CommandError("Entry not found for value[%s] specified for --delete-sid" % sid)
3790 if update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_ADMIN:
3791 raise CommandError("Entry found for value[%s] specified for --disable-sid is already disabled" % nb)
3792 update_forest_info.entries[idx].time = 0
3793 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3794 update_forest_info.entries[idx].flags |= lsa.LSA_SID_DISABLED_ADMIN
3796 try:
3797 update_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3798 local_tdo_info.domain_name,
3799 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3800 update_forest_info, 0)
3801 except RuntimeError as error:
3802 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3804 self.outf.write("Updated forest trust information...\n")
3805 self.write_forest_trust_info(update_forest_info,
3806 tln=local_tdo_info.domain_name.string,
3807 collisions=update_forest_collision)
3809 try:
3810 lsaString = lsa.String()
3811 lsaString.string = local_tdo_info.domain_name.string
3812 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3813 lsaString,
3814 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3815 except RuntimeError as error:
3816 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3818 self.outf.write("Stored forest trust information...\n")
3819 self.write_forest_trust_info(stored_forest_info,
3820 tln=local_tdo_info.domain_name.string)
3821 return
3824 class cmd_domain_tombstones_expunge(Command):
3825 """Expunge tombstones from the database.
3827 This command expunges tombstones from the database."""
3828 synopsis = "%prog NC [NC [...]] [options]"
3830 takes_options = [
3831 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3832 metavar="URL", dest="H"),
3833 Option("--current-time",
3834 help="The current time to evaluate the tombstone lifetime from, expressed as YYYY-MM-DD",
3835 type=str),
3836 Option("--tombstone-lifetime", help="Number of days a tombstone should be preserved for", type=int),
3839 takes_args = ["nc*"]
3841 takes_optiongroups = {
3842 "sambaopts": options.SambaOptions,
3843 "credopts": options.CredentialsOptions,
3844 "versionopts": options.VersionOptions,
3847 def run(self, *ncs, **kwargs):
3848 sambaopts = kwargs.get("sambaopts")
3849 credopts = kwargs.get("credopts")
3850 H = kwargs.get("H")
3851 current_time_string = kwargs.get("current_time")
3852 tombstone_lifetime = kwargs.get("tombstone_lifetime")
3853 lp = sambaopts.get_loadparm()
3854 creds = credopts.get_credentials(lp)
3855 samdb = SamDB(url=H, session_info=system_session(),
3856 credentials=creds, lp=lp)
3858 if current_time_string is None and tombstone_lifetime is None:
3859 print("Note: without --current-time or --tombstone-lifetime "
3860 "only tombstones already scheduled for deletion will "
3861 "be deleted.", file=self.outf)
3862 print("To remove all tombstones, use --tombstone-lifetime=0.",
3863 file=self.outf)
3865 if current_time_string is not None:
3866 current_time_obj = time.strptime(current_time_string, "%Y-%m-%d")
3867 current_time = int(time.mktime(current_time_obj))
3869 else:
3870 current_time = int(time.time())
3872 if len(ncs) == 0:
3873 res = samdb.search(expression="", base="", scope=ldb.SCOPE_BASE,
3874 attrs=["namingContexts"])
3876 ncs = []
3877 for nc in res[0]["namingContexts"]:
3878 ncs.append(str(nc))
3879 else:
3880 ncs = list(ncs)
3882 started_transaction = False
3883 try:
3884 samdb.transaction_start()
3885 started_transaction = True
3886 (removed_objects,
3887 removed_links) = samdb.garbage_collect_tombstones(ncs,
3888 current_time=current_time,
3889 tombstone_lifetime=tombstone_lifetime)
3891 except Exception as err:
3892 if started_transaction:
3893 samdb.transaction_cancel()
3894 raise CommandError("Failed to expunge / garbage collect tombstones", err)
3896 samdb.transaction_commit()
3898 self.outf.write("Removed %d objects and %d links successfully\n"
3899 % (removed_objects, removed_links))
3902 class cmd_domain_trust(SuperCommand):
3903 """Domain and forest trust management."""
3905 subcommands = {}
3906 subcommands["list"] = cmd_domain_trust_list()
3907 subcommands["show"] = cmd_domain_trust_show()
3908 subcommands["create"] = cmd_domain_trust_create()
3909 subcommands["delete"] = cmd_domain_trust_delete()
3910 subcommands["validate"] = cmd_domain_trust_validate()
3911 subcommands["namespaces"] = cmd_domain_trust_namespaces()
3914 class cmd_domain_tombstones(SuperCommand):
3915 """Domain tombstone and recycled object management."""
3917 subcommands = {}
3918 subcommands["expunge"] = cmd_domain_tombstones_expunge()
3921 class ldif_schema_update:
3922 """Helper class for applying LDIF schema updates"""
3924 def __init__(self):
3925 self.is_defunct = False
3926 self.unknown_oid = None
3927 self.dn = None
3928 self.ldif = ""
3930 def can_ignore_failure(self, error):
3931 """Checks if we can safely ignore failure to apply an LDIF update"""
3932 (num, errstr) = error.args
3934 # Microsoft has marked objects as defunct that Samba doesn't know about
3935 if num == ldb.ERR_NO_SUCH_OBJECT and self.is_defunct:
3936 print("Defunct object %s doesn't exist, skipping" % self.dn)
3937 return True
3938 elif self.unknown_oid is not None:
3939 print("Skipping unknown OID %s for object %s" % (self.unknown_oid, self.dn))
3940 return True
3942 return False
3944 def apply(self, samdb):
3945 """Applies a single LDIF update to the schema"""
3947 try:
3948 try:
3949 samdb.modify_ldif(self.ldif, controls=['relax:0'])
3950 except ldb.LdbError as e:
3951 if e.args[0] == ldb.ERR_INVALID_ATTRIBUTE_SYNTAX:
3953 # REFRESH after a failed change
3955 # Otherwise the OID-to-attribute mapping in
3956 # _apply_updates_in_file() won't work, because it
3957 # can't lookup the new OID in the schema
3958 samdb.set_schema_update_now()
3960 samdb.modify_ldif(self.ldif, controls=['relax:0'])
3961 else:
3962 raise
3963 except ldb.LdbError as e:
3964 if self.can_ignore_failure(e):
3965 return 0
3966 else:
3967 print("Exception: %s" % e)
3968 print("Encountered while trying to apply the following LDIF")
3969 print("----------------------------------------------------")
3970 print("%s" % self.ldif)
3972 raise
3974 return 1
3977 class cmd_domain_schema_upgrade(Command):
3978 """Domain schema upgrading"""
3980 synopsis = "%prog [options]"
3982 takes_optiongroups = {
3983 "sambaopts": options.SambaOptions,
3984 "versionopts": options.VersionOptions,
3985 "credopts": options.CredentialsOptions,
3988 takes_options = [
3989 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3990 metavar="URL", dest="H"),
3991 Option("-q", "--quiet", help="Be quiet", action="store_true"), # unused
3992 Option("-v", "--verbose", help="Be verbose", action="store_true"),
3993 Option("--schema", type="choice", metavar="SCHEMA",
3994 choices=["2012", "2012_R2"],
3995 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
3996 default="2012_R2"),
3997 Option("--ldf-file", type=str, default=None,
3998 help="Just apply the schema updates in the adprep/.LDF file(s) specified"),
3999 Option("--base-dir", type=str, default=None,
4000 help="Location of ldf files Default is ${SETUPDIR}/adprep.")
4003 def _apply_updates_in_file(self, samdb, ldif_file):
4005 Applies a series of updates specified in an .LDIF file. The .LDIF file
4006 is based on the adprep Schema updates provided by Microsoft.
4008 count = 0
4009 ldif_op = ldif_schema_update()
4011 # parse the file line by line and work out each update operation to apply
4012 for line in ldif_file:
4014 line = line.rstrip()
4016 # the operations in the .LDIF file are separated by blank lines. If
4017 # we hit a blank line, try to apply the update we've parsed so far
4018 if line == '':
4020 # keep going if we haven't parsed anything yet
4021 if ldif_op.ldif == '':
4022 continue
4024 # Apply the individual change
4025 count += ldif_op.apply(samdb)
4027 # start storing the next operation from scratch again
4028 ldif_op = ldif_schema_update()
4029 continue
4031 # replace the placeholder domain name in the .ldif file with the real domain
4032 if line.upper().endswith('DC=X'):
4033 line = line[:-len('DC=X')] + str(samdb.get_default_basedn())
4034 elif line.upper().endswith('CN=X'):
4035 line = line[:-len('CN=X')] + str(samdb.get_default_basedn())
4037 values = line.split(':')
4039 if values[0].lower() == 'dn':
4040 ldif_op.dn = values[1].strip()
4042 # replace the Windows-specific operation with the Samba one
4043 if values[0].lower() == 'changetype':
4044 line = line.lower().replace(': ntdsschemaadd',
4045 ': add')
4046 line = line.lower().replace(': ntdsschemamodify',
4047 ': modify')
4049 if values[0].lower() in ['rdnattid', 'subclassof',
4050 'systemposssuperiors',
4051 'systemmaycontain',
4052 'systemauxiliaryclass']:
4053 _, value = values
4055 # The Microsoft updates contain some OIDs we don't recognize.
4056 # Query the DB to see if we can work out the OID this update is
4057 # referring to. If we find a match, then replace the OID with
4058 # the ldapDisplayname
4059 if '.' in value:
4060 res = samdb.search(base=samdb.get_schema_basedn(),
4061 expression="(|(attributeId=%s)(governsId=%s))" %
4062 (value, value),
4063 attrs=['ldapDisplayName'])
4065 if len(res) != 1:
4066 ldif_op.unknown_oid = value
4067 else:
4068 display_name = str(res[0]['ldapDisplayName'][0])
4069 line = line.replace(value, ' ' + display_name)
4071 # Microsoft has marked objects as defunct that Samba doesn't know about
4072 if values[0].lower() == 'isdefunct' and values[1].strip().lower() == 'true':
4073 ldif_op.is_defunct = True
4075 # Samba has added the showInAdvancedViewOnly attribute to all objects,
4076 # so rather than doing an add, we need to do a replace
4077 if values[0].lower() == 'add' and values[1].strip().lower() == 'showinadvancedviewonly':
4078 line = 'replace: showInAdvancedViewOnly'
4080 # Add the line to the current LDIF operation (including the newline
4081 # we stripped off at the start of the loop)
4082 ldif_op.ldif += line + '\n'
4084 return count
4086 def _apply_update(self, samdb, update_file, base_dir):
4087 """Wrapper function for parsing an LDIF file and applying the updates"""
4089 print("Applying %s updates..." % update_file)
4091 ldif_file = None
4092 try:
4093 ldif_file = open(os.path.join(base_dir, update_file))
4095 count = self._apply_updates_in_file(samdb, ldif_file)
4097 finally:
4098 if ldif_file:
4099 ldif_file.close()
4101 print("%u changes applied" % count)
4103 return count
4105 def run(self, **kwargs):
4106 try:
4107 from samba.ms_schema_markdown import read_ms_markdown
4108 except ImportError as e:
4109 self.outf.write("Exception in importing markdown: %s" % e)
4110 raise CommandError('Failed to import module markdown')
4111 from samba.schema import Schema
4113 updates_allowed_overridden = False
4114 sambaopts = kwargs.get("sambaopts")
4115 credopts = kwargs.get("credopts")
4116 lp = sambaopts.get_loadparm()
4117 creds = credopts.get_credentials(lp)
4118 H = kwargs.get("H")
4119 target_schema = kwargs.get("schema")
4120 ldf_files = kwargs.get("ldf_file")
4121 base_dir = kwargs.get("base_dir")
4123 temp_folder = None
4125 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4127 # we're not going to get far if the config doesn't allow schema updates
4128 if lp.get("dsdb:schema update allowed") is None:
4129 lp.set("dsdb:schema update allowed", "yes")
4130 print("Temporarily overriding 'dsdb:schema update allowed' setting")
4131 updates_allowed_overridden = True
4133 own_dn = ldb.Dn(samdb, samdb.get_dsServiceName())
4134 master = get_fsmo_roleowner(samdb, str(samdb.get_schema_basedn()),
4135 'schema')
4136 if own_dn != master:
4137 raise CommandError("This server is not the schema master.")
4139 # if specific LDIF files were specified, just apply them
4140 if ldf_files:
4141 schema_updates = ldf_files.split(",")
4142 else:
4143 schema_updates = []
4145 # work out the version of the target schema we're upgrading to
4146 end = Schema.get_version(target_schema)
4148 # work out the version of the schema we're currently using
4149 res = samdb.search(base=samdb.get_schema_basedn(),
4150 scope=ldb.SCOPE_BASE, attrs=['objectVersion'])
4152 if len(res) != 1:
4153 raise CommandError('Could not determine current schema version')
4154 start = int(res[0]['objectVersion'][0]) + 1
4156 diff_dir = setup_path("adprep/WindowsServerDocs")
4157 if base_dir is None:
4158 # Read from the Schema-Updates.md file
4159 temp_folder = tempfile.mkdtemp()
4161 update_file = setup_path("adprep/WindowsServerDocs/Schema-Updates.md")
4163 try:
4164 read_ms_markdown(update_file, temp_folder)
4165 except Exception as e:
4166 print("Exception in markdown parsing: %s" % e)
4167 shutil.rmtree(temp_folder)
4168 raise CommandError('Failed to upgrade schema')
4170 base_dir = temp_folder
4172 for version in range(start, end + 1):
4173 update = 'Sch%d.ldf' % version
4174 schema_updates.append(update)
4176 # Apply patches if we parsed the Schema-Updates.md file
4177 diff = os.path.abspath(os.path.join(diff_dir, update + '.diff'))
4178 if temp_folder and os.path.exists(diff):
4179 try:
4180 p = subprocess.Popen(['patch', update, '-i', diff],
4181 stdout=subprocess.PIPE,
4182 stderr=subprocess.PIPE, cwd=temp_folder)
4183 except (OSError, IOError):
4184 shutil.rmtree(temp_folder)
4185 raise CommandError("Failed to upgrade schema. "
4186 "Is '/usr/bin/patch' missing?")
4188 stdout, stderr = p.communicate()
4190 if p.returncode:
4191 print("Exception in patch: %s\n%s" % (stdout, stderr))
4192 shutil.rmtree(temp_folder)
4193 raise CommandError('Failed to upgrade schema')
4195 print("Patched %s using %s" % (update, diff))
4197 if base_dir is None:
4198 base_dir = setup_path("adprep")
4200 samdb.transaction_start()
4201 count = 0
4202 error_encountered = False
4204 try:
4205 # Apply the schema updates needed to move to the new schema version
4206 for ldif_file in schema_updates:
4207 count += self._apply_update(samdb, ldif_file, base_dir)
4209 if count > 0:
4210 samdb.transaction_commit()
4211 print("Schema successfully updated")
4212 else:
4213 print("No changes applied to schema")
4214 samdb.transaction_cancel()
4215 except Exception as e:
4216 print("Exception: %s" % e)
4217 print("Error encountered, aborting schema upgrade")
4218 samdb.transaction_cancel()
4219 error_encountered = True
4221 if updates_allowed_overridden:
4222 lp.set("dsdb:schema update allowed", "no")
4224 if temp_folder:
4225 shutil.rmtree(temp_folder)
4227 if error_encountered:
4228 raise CommandError('Failed to upgrade schema')
4231 class cmd_domain_functional_prep(Command):
4232 """Domain functional level preparation"""
4234 synopsis = "%prog [options]"
4236 takes_optiongroups = {
4237 "sambaopts": options.SambaOptions,
4238 "versionopts": options.VersionOptions,
4239 "credopts": options.CredentialsOptions,
4242 takes_options = [
4243 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
4244 metavar="URL", dest="H"),
4245 Option("-q", "--quiet", help="Be quiet", action="store_true"),
4246 Option("-v", "--verbose", help="Be verbose", action="store_true"),
4247 Option("--function-level", type="choice", metavar="FUNCTION_LEVEL",
4248 choices=["2008_R2", "2012", "2012_R2"],
4249 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
4250 default="2012_R2"),
4251 Option("--forest-prep", action="store_true",
4252 help="Run the forest prep (by default, both the domain and forest prep are run)."),
4253 Option("--domain-prep", action="store_true",
4254 help="Run the domain prep (by default, both the domain and forest prep are run).")
4257 def run(self, **kwargs):
4258 updates_allowed_overridden = False
4259 sambaopts = kwargs.get("sambaopts")
4260 credopts = kwargs.get("credopts")
4261 lp = sambaopts.get_loadparm()
4262 creds = credopts.get_credentials(lp)
4263 H = kwargs.get("H")
4264 target_level = string_version_to_constant[kwargs.get("function_level")]
4265 forest_prep = kwargs.get("forest_prep")
4266 domain_prep = kwargs.get("domain_prep")
4268 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4270 # we're not going to get far if the config doesn't allow schema updates
4271 if lp.get("dsdb:schema update allowed") is None:
4272 lp.set("dsdb:schema update allowed", "yes")
4273 print("Temporarily overriding 'dsdb:schema update allowed' setting")
4274 updates_allowed_overridden = True
4276 if forest_prep is None and domain_prep is None:
4277 forest_prep = True
4278 domain_prep = True
4280 own_dn = ldb.Dn(samdb, samdb.get_dsServiceName())
4281 if forest_prep:
4282 master = get_fsmo_roleowner(samdb, str(samdb.get_schema_basedn()),
4283 'schema')
4284 if own_dn != master:
4285 raise CommandError("This server is not the schema master.")
4287 if domain_prep:
4288 domain_dn = samdb.domain_dn()
4289 infrastructure_dn = "CN=Infrastructure," + domain_dn
4290 master = get_fsmo_roleowner(samdb, infrastructure_dn,
4291 'infrastructure')
4292 if own_dn != master:
4293 raise CommandError("This server is not the infrastructure master.")
4295 if forest_prep:
4296 samdb.transaction_start()
4297 error_encountered = False
4298 try:
4299 from samba.forest_update import ForestUpdate
4300 forest = ForestUpdate(samdb, fix=True)
4302 forest.check_updates_iterator([53, 79, 80, 81, 82, 83])
4303 forest.check_updates_functional_level(target_level,
4304 DS_DOMAIN_FUNCTION_2008_R2,
4305 update_revision=True)
4307 samdb.transaction_commit()
4308 except Exception as e:
4309 print("Exception: %s" % e)
4310 samdb.transaction_cancel()
4311 error_encountered = True
4313 if domain_prep:
4314 samdb.transaction_start()
4315 error_encountered = False
4316 try:
4317 from samba.domain_update import DomainUpdate
4319 domain = DomainUpdate(samdb, fix=True)
4320 domain.check_updates_functional_level(target_level,
4321 DS_DOMAIN_FUNCTION_2008,
4322 update_revision=True)
4324 samdb.transaction_commit()
4325 except Exception as e:
4326 print("Exception: %s" % e)
4327 samdb.transaction_cancel()
4328 error_encountered = True
4330 if updates_allowed_overridden:
4331 lp.set("dsdb:schema update allowed", "no")
4333 if error_encountered:
4334 raise CommandError('Failed to perform functional prep')
4337 class cmd_domain(SuperCommand):
4338 """Domain management."""
4340 subcommands = {}
4341 subcommands["demote"] = cmd_domain_demote()
4342 if cmd_domain_export_keytab is not None:
4343 subcommands["exportkeytab"] = cmd_domain_export_keytab()
4344 subcommands["info"] = cmd_domain_info()
4345 subcommands["provision"] = cmd_domain_provision()
4346 subcommands["join"] = cmd_domain_join()
4347 subcommands["dcpromo"] = cmd_domain_dcpromo()
4348 subcommands["level"] = cmd_domain_level()
4349 subcommands["passwordsettings"] = cmd_domain_passwordsettings()
4350 subcommands["classicupgrade"] = cmd_domain_classicupgrade()
4351 subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()
4352 subcommands["trust"] = cmd_domain_trust()
4353 subcommands["tombstones"] = cmd_domain_tombstones()
4354 subcommands["schemaupgrade"] = cmd_domain_schema_upgrade()
4355 subcommands["functionalprep"] = cmd_domain_functional_prep()
4356 subcommands["backup"] = cmd_domain_backup()