domain.py: Give some advice if the schema upgrade command fails
[Samba.git] / python / samba / netcmd / domain.py
blobd3b9b0ccf6fd9af38859e88bca2848f42aaa6cb8
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 string
28 import os
29 import sys
30 import ctypes
31 import random
32 import tempfile
33 import logging
34 import subprocess
35 import time
36 import shutil
37 from samba import ntstatus
38 from samba import NTSTATUSError
39 from samba import werror
40 from getpass import getpass
41 from samba.net import Net, LIBNET_JOIN_AUTOMATIC
42 import samba.ntacls
43 from samba.join import join_RODC, join_DC, join_subdomain
44 from samba.auth import system_session
45 from samba.samdb import SamDB
46 from samba.ndr import ndr_unpack, ndr_pack, ndr_print
47 from samba.dcerpc import drsuapi
48 from samba.dcerpc import drsblobs
49 from samba.dcerpc import lsa
50 from samba.dcerpc import netlogon
51 from samba.dcerpc import security
52 from samba.dcerpc import nbt
53 from samba.dcerpc import misc
54 from samba.dcerpc.samr import DOMAIN_PASSWORD_COMPLEX, DOMAIN_PASSWORD_STORE_CLEARTEXT
55 from samba.netcmd import (
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.samba3 import Samba3
64 from samba.samba3 import param as s3param
65 from samba.upgrade import upgrade_from_samba3
66 from samba.drs_utils import (
67 sendDsReplicaSync, drsuapi_connect, drsException,
68 sendRemoveDsServer)
69 from samba import remove_dc, arcfour_encrypt, string_to_byte_array
71 from samba.dsdb import (
72 DS_DOMAIN_FUNCTION_2000,
73 DS_DOMAIN_FUNCTION_2003,
74 DS_DOMAIN_FUNCTION_2003_MIXED,
75 DS_DOMAIN_FUNCTION_2008,
76 DS_DOMAIN_FUNCTION_2008_R2,
77 DS_DOMAIN_FUNCTION_2012,
78 DS_DOMAIN_FUNCTION_2012_R2,
79 DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL,
80 DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL,
81 UF_WORKSTATION_TRUST_ACCOUNT,
82 UF_SERVER_TRUST_ACCOUNT,
83 UF_TRUSTED_FOR_DELEGATION,
84 UF_PARTIAL_SECRETS_ACCOUNT
87 from samba.provision import (
88 provision,
89 ProvisioningError,
90 DEFAULT_MIN_PWD_LENGTH,
91 setup_path
94 from samba.provision.common import (
95 FILL_FULL,
96 FILL_NT4SYNC,
97 FILL_DRS
100 string_version_to_constant = {
101 "2008_R2" : DS_DOMAIN_FUNCTION_2008_R2,
102 "2012": DS_DOMAIN_FUNCTION_2012,
103 "2012_R2": DS_DOMAIN_FUNCTION_2012_R2,
106 def get_testparm_var(testparm, smbconf, varname):
107 errfile = open(os.devnull, 'w')
108 p = subprocess.Popen([testparm, '-s', '-l',
109 '--parameter-name=%s' % varname, smbconf],
110 stdout=subprocess.PIPE, stderr=errfile)
111 (out,err) = p.communicate()
112 errfile.close()
113 lines = out.split('\n')
114 if lines:
115 return lines[0].strip()
116 return ""
118 try:
119 import samba.dckeytab
120 except ImportError:
121 cmd_domain_export_keytab = None
122 else:
123 class cmd_domain_export_keytab(Command):
124 """Dump Kerberos keys of the domain into a keytab."""
126 synopsis = "%prog <keytab> [options]"
128 takes_optiongroups = {
129 "sambaopts": options.SambaOptions,
130 "credopts": options.CredentialsOptions,
131 "versionopts": options.VersionOptions,
134 takes_options = [
135 Option("--principal", help="extract only this principal", type=str),
138 takes_args = ["keytab"]
140 def run(self, keytab, credopts=None, sambaopts=None, versionopts=None, principal=None):
141 lp = sambaopts.get_loadparm()
142 net = Net(None, lp)
143 net.export_keytab(keytab=keytab, principal=principal)
146 class cmd_domain_info(Command):
147 """Print basic info about a domain and the DC passed as parameter."""
149 synopsis = "%prog <ip_address> [options]"
151 takes_options = [
154 takes_optiongroups = {
155 "sambaopts": options.SambaOptions,
156 "credopts": options.CredentialsOptions,
157 "versionopts": options.VersionOptions,
160 takes_args = ["address"]
162 def run(self, address, credopts=None, sambaopts=None, versionopts=None):
163 lp = sambaopts.get_loadparm()
164 try:
165 res = netcmd_get_domain_infos_via_cldap(lp, None, address)
166 except RuntimeError:
167 raise CommandError("Invalid IP address '" + address + "'!")
168 self.outf.write("Forest : %s\n" % res.forest)
169 self.outf.write("Domain : %s\n" % res.dns_domain)
170 self.outf.write("Netbios domain : %s\n" % res.domain_name)
171 self.outf.write("DC name : %s\n" % res.pdc_dns_name)
172 self.outf.write("DC netbios name : %s\n" % res.pdc_name)
173 self.outf.write("Server site : %s\n" % res.server_site)
174 self.outf.write("Client site : %s\n" % res.client_site)
177 class cmd_domain_provision(Command):
178 """Provision a domain."""
180 synopsis = "%prog [options]"
182 takes_optiongroups = {
183 "sambaopts": options.SambaOptions,
184 "versionopts": options.VersionOptions,
187 takes_options = [
188 Option("--interactive", help="Ask for names", action="store_true"),
189 Option("--domain", type="string", metavar="DOMAIN",
190 help="NetBIOS domain name to use"),
191 Option("--domain-guid", type="string", metavar="GUID",
192 help="set domainguid (otherwise random)"),
193 Option("--domain-sid", type="string", metavar="SID",
194 help="set domainsid (otherwise random)"),
195 Option("--ntds-guid", type="string", metavar="GUID",
196 help="set NTDS object GUID (otherwise random)"),
197 Option("--invocationid", type="string", metavar="GUID",
198 help="set invocationid (otherwise random)"),
199 Option("--host-name", type="string", metavar="HOSTNAME",
200 help="set hostname"),
201 Option("--host-ip", type="string", metavar="IPADDRESS",
202 help="set IPv4 ipaddress"),
203 Option("--host-ip6", type="string", metavar="IP6ADDRESS",
204 help="set IPv6 ipaddress"),
205 Option("--site", type="string", metavar="SITENAME",
206 help="set site name"),
207 Option("--adminpass", type="string", metavar="PASSWORD",
208 help="choose admin password (otherwise random)"),
209 Option("--krbtgtpass", type="string", metavar="PASSWORD",
210 help="choose krbtgt password (otherwise random)"),
211 Option("--machinepass", type="string", metavar="PASSWORD",
212 help="choose machine password (otherwise random)"),
213 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
214 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
215 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
216 "BIND9_FLATFILE uses bind9 text database to store zone information, "
217 "BIND9_DLZ uses samba4 AD to store zone information, "
218 "NONE skips the DNS setup entirely (not recommended)",
219 default="SAMBA_INTERNAL"),
220 Option("--dnspass", type="string", metavar="PASSWORD",
221 help="choose dns password (otherwise random)"),
222 Option("--ldapadminpass", type="string", metavar="PASSWORD",
223 help="choose password to set between Samba and its LDAP backend (otherwise random)"),
224 Option("--root", type="string", metavar="USERNAME",
225 help="choose 'root' unix username"),
226 Option("--nobody", type="string", metavar="USERNAME",
227 help="choose 'nobody' user"),
228 Option("--users", type="string", metavar="GROUPNAME",
229 help="choose 'users' group"),
230 Option("--quiet", help="Be quiet", action="store_true"),
231 Option("--blank", action="store_true",
232 help="do not add users or groups, just the structure"),
233 Option("--ldap-backend-type", type="choice", metavar="LDAP-BACKEND-TYPE",
234 help="Test initialisation support for unsupported LDAP backend type (fedora-ds or openldap) DO NOT USE",
235 choices=["fedora-ds", "openldap"]),
236 Option("--server-role", type="choice", metavar="ROLE",
237 choices=["domain controller", "dc", "member server", "member", "standalone"],
238 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
239 default="domain controller"),
240 Option("--function-level", type="choice", metavar="FOR-FUN-LEVEL",
241 choices=["2000", "2003", "2008", "2008_R2"],
242 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2008_R2 Native.",
243 default="2008_R2"),
244 Option("--base-schema", type="choice", metavar="BASE-SCHEMA",
245 choices=["2008_R2", "2008_R2_old", "2012", "2012_R2"],
246 help="The base schema files to use. Default is (Windows) 2008_R2.",
247 default="2008_R2"),
248 Option("--next-rid", type="int", metavar="NEXTRID", default=1000,
249 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
250 Option("--partitions-only",
251 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"),
252 Option("--targetdir", type="string", metavar="DIR",
253 help="Set target directory"),
254 Option("--ol-mmr-urls", type="string", metavar="LDAPSERVER",
255 help="List of LDAP-URLS [ ldap://<FQHN>:<PORT>/ (where <PORT> has to be different than 389!) ] separated with comma (\",\") for use with OpenLDAP-MMR (Multi-Master-Replication), e.g.: \"ldap://s4dc1:9000,ldap://s4dc2:9000\""),
256 Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
257 Option("--plaintext-secrets", action="store_true",
258 help="Store secret/sensitive values as plain text on disk" +
259 "(default is to encrypt secret/ensitive values)"),
262 openldap_options = [
263 Option("--ldap-dryrun-mode", help="Configure LDAP backend, but do not run any binaries and exit early. Used only for the test environment. DO NOT USE",
264 action="store_true"),
265 Option("--slapd-path", type="string", metavar="SLAPD-PATH",
266 help="Path to slapd for LDAP backend [e.g.:'/usr/local/libexec/slapd']. Required for Setup with LDAP-Backend. OpenLDAP Version >= 2.4.17 should be used."),
267 Option("--ldap-backend-extra-port", type="int", metavar="LDAP-BACKEND-EXTRA-PORT", help="Additional TCP port for LDAP backend server (to use for replication)"),
268 Option("--ldap-backend-forced-uri", type="string", metavar="LDAP-BACKEND-FORCED-URI",
269 help="Force the LDAP backend connection to be to a particular URI. Use this ONLY for 'existing' backends, or when debugging the interaction with the LDAP backend and you need to intercept the LDA"),
270 Option("--ldap-backend-nosync", help="Configure LDAP backend not to call fsync() (for performance in test environments)", action="store_true"),
273 ntvfs_options = [
274 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
275 Option("--use-xattrs", type="choice", choices=["yes","no","auto"],
276 metavar="[yes|no|auto]",
277 help="Define if we should use the native fs capabilities or a tdb file for "
278 "storing attributes likes ntacl when --use-ntvfs is set. "
279 "auto tries to make an inteligent guess based on the user rights and system capabilities",
280 default="auto")
283 if os.getenv('TEST_LDAP', "no") == "yes":
284 takes_options.extend(openldap_options)
286 if samba.is_ntvfs_fileserver_built():
287 takes_options.extend(ntvfs_options)
289 takes_args = []
291 def run(self, sambaopts=None, versionopts=None,
292 interactive=None,
293 domain=None,
294 domain_guid=None,
295 domain_sid=None,
296 ntds_guid=None,
297 invocationid=None,
298 host_name=None,
299 host_ip=None,
300 host_ip6=None,
301 adminpass=None,
302 site=None,
303 krbtgtpass=None,
304 machinepass=None,
305 dns_backend=None,
306 dns_forwarder=None,
307 dnspass=None,
308 ldapadminpass=None,
309 root=None,
310 nobody=None,
311 users=None,
312 quiet=None,
313 blank=None,
314 ldap_backend_type=None,
315 server_role=None,
316 function_level=None,
317 next_rid=None,
318 partitions_only=None,
319 targetdir=None,
320 ol_mmr_urls=None,
321 use_xattrs="auto",
322 slapd_path=None,
323 use_ntvfs=False,
324 use_rfc2307=None,
325 ldap_backend_nosync=None,
326 ldap_backend_extra_port=None,
327 ldap_backend_forced_uri=None,
328 ldap_dryrun_mode=None,
329 base_schema=None,
330 plaintext_secrets=False):
332 self.logger = self.get_logger("provision")
333 if quiet:
334 self.logger.setLevel(logging.WARNING)
335 else:
336 self.logger.setLevel(logging.INFO)
338 lp = sambaopts.get_loadparm()
339 smbconf = lp.configfile
341 if dns_forwarder is not None:
342 suggested_forwarder = dns_forwarder
343 else:
344 suggested_forwarder = self._get_nameserver_ip()
345 if suggested_forwarder is None:
346 suggested_forwarder = "none"
348 if len(self.raw_argv) == 1:
349 interactive = True
351 if interactive:
352 from getpass import getpass
353 import socket
355 def ask(prompt, default=None):
356 if default is not None:
357 print "%s [%s]: " % (prompt, default),
358 else:
359 print "%s: " % (prompt,),
360 return sys.stdin.readline().rstrip("\n") or default
362 try:
363 default = socket.getfqdn().split(".", 1)[1].upper()
364 except IndexError:
365 default = None
366 realm = ask("Realm", default)
367 if realm in (None, ""):
368 raise CommandError("No realm set!")
370 try:
371 default = realm.split(".")[0]
372 except IndexError:
373 default = None
374 domain = ask("Domain", default)
375 if domain is None:
376 raise CommandError("No domain set!")
378 server_role = ask("Server Role (dc, member, standalone)", "dc")
380 dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
381 if dns_backend in (None, ''):
382 raise CommandError("No DNS backend set!")
384 if dns_backend == "SAMBA_INTERNAL":
385 dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
386 if dns_forwarder.lower() in (None, 'none'):
387 suggested_forwarder = None
388 dns_forwarder = None
390 while True:
391 adminpassplain = getpass("Administrator password: ")
392 issue = self._adminpass_issue(adminpassplain)
393 if issue:
394 self.errf.write("%s.\n" % issue)
395 else:
396 adminpassverify = getpass("Retype password: ")
397 if not adminpassplain == adminpassverify:
398 self.errf.write("Sorry, passwords do not match.\n")
399 else:
400 adminpass = adminpassplain
401 break
403 else:
404 realm = sambaopts._lp.get('realm')
405 if realm is None:
406 raise CommandError("No realm set!")
407 if domain is None:
408 raise CommandError("No domain set!")
410 if adminpass:
411 issue = self._adminpass_issue(adminpass)
412 if issue:
413 raise CommandError(issue)
414 else:
415 self.logger.info("Administrator password will be set randomly!")
417 if function_level == "2000":
418 dom_for_fun_level = DS_DOMAIN_FUNCTION_2000
419 elif function_level == "2003":
420 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
421 elif function_level == "2008":
422 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
423 elif function_level == "2008_R2":
424 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
426 if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
427 dns_forwarder = suggested_forwarder
429 samdb_fill = FILL_FULL
430 if blank:
431 samdb_fill = FILL_NT4SYNC
432 elif partitions_only:
433 samdb_fill = FILL_DRS
435 if targetdir is not None:
436 if not os.path.isdir(targetdir):
437 os.mkdir(targetdir)
439 eadb = True
441 if use_xattrs == "yes":
442 eadb = False
443 elif use_xattrs == "auto" and use_ntvfs == False:
444 eadb = False
445 elif use_ntvfs == False:
446 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
447 "Please re-run with --use-xattrs omitted.")
448 elif use_xattrs == "auto" and not lp.get("posix:eadb"):
449 if targetdir:
450 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
451 else:
452 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
453 try:
454 try:
455 samba.ntacls.setntacl(lp, file.name,
456 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
457 eadb = False
458 except Exception:
459 self.logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. ")
460 finally:
461 file.close()
463 if eadb:
464 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.")
465 if ldap_backend_type == "existing":
466 if ldap_backend_forced_uri is not None:
467 self.logger.warn("You have specified to use an existing LDAP server as the backend, please make sure an LDAP server is running at %s" % ldap_backend_forced_uri)
468 else:
469 self.logger.info("You have specified to use an existing LDAP server as the backend, please make sure an LDAP server is running at the default location")
470 else:
471 if ldap_backend_forced_uri is not None:
472 self.logger.warn("You have specified to use an fixed URI %s for connecting to your LDAP server backend. This is NOT RECOMMENDED, as our default communiation over ldapi:// is more secure and much less")
474 if domain_sid is not None:
475 domain_sid = security.dom_sid(domain_sid)
477 session = system_session()
478 try:
479 result = provision(self.logger,
480 session, smbconf=smbconf, targetdir=targetdir,
481 samdb_fill=samdb_fill, realm=realm, domain=domain,
482 domainguid=domain_guid, domainsid=domain_sid,
483 hostname=host_name,
484 hostip=host_ip, hostip6=host_ip6,
485 sitename=site, ntdsguid=ntds_guid,
486 invocationid=invocationid, adminpass=adminpass,
487 krbtgtpass=krbtgtpass, machinepass=machinepass,
488 dns_backend=dns_backend, dns_forwarder=dns_forwarder,
489 dnspass=dnspass, root=root, nobody=nobody,
490 users=users,
491 serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
492 backend_type=ldap_backend_type,
493 ldapadminpass=ldapadminpass, ol_mmr_urls=ol_mmr_urls, slapd_path=slapd_path,
494 useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
495 use_rfc2307=use_rfc2307, skip_sysvolacl=False,
496 ldap_backend_extra_port=ldap_backend_extra_port,
497 ldap_backend_forced_uri=ldap_backend_forced_uri,
498 nosync=ldap_backend_nosync, ldap_dryrun_mode=ldap_dryrun_mode,
499 base_schema=base_schema,
500 plaintext_secrets=plaintext_secrets)
502 except ProvisioningError as e:
503 raise CommandError("Provision failed", e)
505 result.report_logger(self.logger)
507 def _get_nameserver_ip(self):
508 """Grab the nameserver IP address from /etc/resolv.conf."""
509 from os import path
510 RESOLV_CONF="/etc/resolv.conf"
512 if not path.isfile(RESOLV_CONF):
513 self.logger.warning("Failed to locate %s" % RESOLV_CONF)
514 return None
516 handle = None
517 try:
518 handle = open(RESOLV_CONF, 'r')
519 for line in handle:
520 if not line.startswith('nameserver'):
521 continue
522 # we want the last non-space continuous string of the line
523 return line.strip().split()[-1]
524 finally:
525 if handle is not None:
526 handle.close()
528 self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
530 def _adminpass_issue(self, adminpass):
531 """Returns error string for a bad administrator password,
532 or None if acceptable"""
534 if len(adminpass.decode('utf-8')) < DEFAULT_MIN_PWD_LENGTH:
535 return "Administrator password does not meet the default minimum" \
536 " password length requirement (%d characters)" \
537 % DEFAULT_MIN_PWD_LENGTH
538 elif not samba.check_password_quality(adminpass):
539 return "Administrator password does not meet the default" \
540 " quality standards"
541 else:
542 return None
545 class cmd_domain_dcpromo(Command):
546 """Promote an existing domain member or NT4 PDC to an AD DC."""
548 synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
550 takes_optiongroups = {
551 "sambaopts": options.SambaOptions,
552 "versionopts": options.VersionOptions,
553 "credopts": options.CredentialsOptions,
556 takes_options = [
557 Option("--server", help="DC to join", type=str),
558 Option("--site", help="site to join", type=str),
559 Option("--targetdir", help="where to store provision", type=str),
560 Option("--domain-critical-only",
561 help="only replicate critical domain objects",
562 action="store_true"),
563 Option("--machinepass", type=str, metavar="PASSWORD",
564 help="choose machine password (otherwise random)"),
565 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
566 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
567 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
568 "BIND9_DLZ uses samba4 AD to store zone information, "
569 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
570 default="SAMBA_INTERNAL"),
571 Option("--quiet", help="Be quiet", action="store_true"),
572 Option("--verbose", help="Be verbose", action="store_true")
575 ntvfs_options = [
576 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
579 if samba.is_ntvfs_fileserver_built():
580 takes_options.extend(ntvfs_options)
583 takes_args = ["domain", "role?"]
585 def run(self, domain, role=None, sambaopts=None, credopts=None,
586 versionopts=None, server=None, site=None, targetdir=None,
587 domain_critical_only=False, parent_domain=None, machinepass=None,
588 use_ntvfs=False, dns_backend=None,
589 quiet=False, verbose=False):
590 lp = sambaopts.get_loadparm()
591 creds = credopts.get_credentials(lp)
592 net = Net(creds, lp, server=credopts.ipaddress)
594 logger = self.get_logger()
595 if verbose:
596 logger.setLevel(logging.DEBUG)
597 elif quiet:
598 logger.setLevel(logging.WARNING)
599 else:
600 logger.setLevel(logging.INFO)
602 netbios_name = lp.get("netbios name")
604 if not role is None:
605 role = role.upper()
607 if role == "DC":
608 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
609 site=site, netbios_name=netbios_name, targetdir=targetdir,
610 domain_critical_only=domain_critical_only,
611 machinepass=machinepass, use_ntvfs=use_ntvfs,
612 dns_backend=dns_backend,
613 promote_existing=True)
614 elif role == "RODC":
615 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
616 site=site, netbios_name=netbios_name, targetdir=targetdir,
617 domain_critical_only=domain_critical_only,
618 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
619 promote_existing=True)
620 else:
621 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
624 class cmd_domain_join(Command):
625 """Join domain as either member or backup domain controller."""
627 synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
629 takes_optiongroups = {
630 "sambaopts": options.SambaOptions,
631 "versionopts": options.VersionOptions,
632 "credopts": options.CredentialsOptions,
635 takes_options = [
636 Option("--server", help="DC to join", type=str),
637 Option("--site", help="site to join", type=str),
638 Option("--targetdir", help="where to store provision", type=str),
639 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
640 Option("--domain-critical-only",
641 help="only replicate critical domain objects",
642 action="store_true"),
643 Option("--machinepass", type=str, metavar="PASSWORD",
644 help="choose machine password (otherwise random)"),
645 Option("--adminpass", type="string", metavar="PASSWORD",
646 help="choose adminstrator password when joining as a subdomain (otherwise random)"),
647 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
648 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
649 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
650 "BIND9_DLZ uses samba4 AD to store zone information, "
651 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
652 default="SAMBA_INTERNAL"),
653 Option("--plaintext-secrets", action="store_true",
654 help="Store secret/sensitive values as plain text on disk" +
655 "(default is to encrypt secret/ensitive values)"),
656 Option("--quiet", help="Be quiet", action="store_true"),
657 Option("--verbose", help="Be verbose", action="store_true")
660 ntvfs_options = [
661 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
662 action="store_true")
664 if samba.is_ntvfs_fileserver_built():
665 takes_options.extend(ntvfs_options)
667 takes_args = ["domain", "role?"]
669 def run(self, domain, role=None, sambaopts=None, credopts=None,
670 versionopts=None, server=None, site=None, targetdir=None,
671 domain_critical_only=False, parent_domain=None, machinepass=None,
672 use_ntvfs=False, dns_backend=None, adminpass=None,
673 quiet=False, verbose=False, plaintext_secrets=False):
674 lp = sambaopts.get_loadparm()
675 creds = credopts.get_credentials(lp)
676 net = Net(creds, lp, server=credopts.ipaddress)
678 if site is None:
679 site = "Default-First-Site-Name"
681 logger = self.get_logger()
682 if verbose:
683 logger.setLevel(logging.DEBUG)
684 elif quiet:
685 logger.setLevel(logging.WARNING)
686 else:
687 logger.setLevel(logging.INFO)
689 netbios_name = lp.get("netbios name")
691 if not role is None:
692 role = role.upper()
694 if role is None or role == "MEMBER":
695 (join_password, sid, domain_name) = net.join_member(
696 domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
697 machinepass=machinepass)
699 self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
700 elif role == "DC":
701 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
702 site=site, netbios_name=netbios_name, targetdir=targetdir,
703 domain_critical_only=domain_critical_only,
704 machinepass=machinepass, use_ntvfs=use_ntvfs,
705 dns_backend=dns_backend,
706 plaintext_secrets=plaintext_secrets)
707 elif role == "RODC":
708 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
709 site=site, netbios_name=netbios_name, targetdir=targetdir,
710 domain_critical_only=domain_critical_only,
711 machinepass=machinepass, use_ntvfs=use_ntvfs,
712 dns_backend=dns_backend,
713 plaintext_secrets=plaintext_secrets)
714 elif role == "SUBDOMAIN":
715 if not adminpass:
716 logger.info("Administrator password will be set randomly!")
718 netbios_domain = lp.get("workgroup")
719 if parent_domain is None:
720 parent_domain = ".".join(domain.split(".")[1:])
721 join_subdomain(logger=logger, server=server, creds=creds, lp=lp, dnsdomain=domain,
722 parent_domain=parent_domain, site=site,
723 netbios_name=netbios_name, netbios_domain=netbios_domain,
724 targetdir=targetdir, machinepass=machinepass,
725 use_ntvfs=use_ntvfs, dns_backend=dns_backend,
726 adminpass=adminpass,
727 plaintext_secrets=plaintext_secrets)
728 else:
729 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role)
732 class cmd_domain_demote(Command):
733 """Demote ourselves from the role of Domain Controller."""
735 synopsis = "%prog [options]"
737 takes_options = [
738 Option("--server", help="writable DC to write demotion changes on", type=str),
739 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
740 metavar="URL", dest="H"),
741 Option("--remove-other-dead-server", help="Dead DC (name or NTDS GUID) "
742 "to remove ALL references to (rather than this DC)", type=str),
743 Option("--quiet", help="Be quiet", action="store_true"),
744 Option("--verbose", help="Be verbose", action="store_true"),
747 takes_optiongroups = {
748 "sambaopts": options.SambaOptions,
749 "credopts": options.CredentialsOptions,
750 "versionopts": options.VersionOptions,
753 def run(self, sambaopts=None, credopts=None,
754 versionopts=None, server=None,
755 remove_other_dead_server=None, H=None,
756 verbose=False, quiet=False):
757 lp = sambaopts.get_loadparm()
758 creds = credopts.get_credentials(lp)
759 net = Net(creds, lp, server=credopts.ipaddress)
761 logger = self.get_logger()
762 if verbose:
763 logger.setLevel(logging.DEBUG)
764 elif quiet:
765 logger.setLevel(logging.WARNING)
766 else:
767 logger.setLevel(logging.INFO)
769 if remove_other_dead_server is not None:
770 if server is not None:
771 samdb = SamDB(url="ldap://%s" % server,
772 session_info=system_session(),
773 credentials=creds, lp=lp)
774 else:
775 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
776 try:
777 remove_dc.remove_dc(samdb, logger, remove_other_dead_server)
778 except remove_dc.DemoteException as err:
779 raise CommandError("Demote failed: %s" % err)
780 return
782 netbios_name = lp.get("netbios name")
783 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
784 if not server:
785 res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
786 if (len(res) == 0):
787 raise CommandError("Unable to search for servers")
789 if (len(res) == 1):
790 raise CommandError("You are the latest server in the domain")
792 server = None
793 for e in res:
794 if str(e["name"]).lower() != netbios_name.lower():
795 server = e["dnsHostName"]
796 break
798 ntds_guid = samdb.get_ntds_GUID()
799 msg = samdb.search(base=str(samdb.get_config_basedn()),
800 scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
801 attrs=['options'])
802 if len(msg) == 0 or "options" not in msg[0]:
803 raise CommandError("Failed to find options on %s" % ntds_guid)
805 ntds_dn = msg[0].dn
806 dsa_options = int(str(msg[0]['options']))
808 res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
809 controls=["search_options:1:2"])
811 if len(res) != 0:
812 raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res))
814 self.errf.write("Using %s as partner server for the demotion\n" %
815 server)
816 (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
818 self.errf.write("Deactivating inbound replication\n")
820 nmsg = ldb.Message()
821 nmsg.dn = msg[0].dn
823 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
824 dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
825 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
826 samdb.modify(nmsg)
829 self.errf.write("Asking partner server %s to synchronize from us\n"
830 % server)
831 for part in (samdb.get_schema_basedn(),
832 samdb.get_config_basedn(),
833 samdb.get_root_basedn()):
834 nc = drsuapi.DsReplicaObjectIdentifier()
835 nc.dn = str(part)
837 req1 = drsuapi.DsReplicaSyncRequest1()
838 req1.naming_context = nc;
839 req1.options = drsuapi.DRSUAPI_DRS_WRIT_REP
840 req1.source_dsa_guid = misc.GUID(ntds_guid)
842 try:
843 drsuapiBind.DsReplicaSync(drsuapi_handle, 1, req1)
844 except RuntimeError as e1:
845 (werr, string) = e1.args
846 if werr == werror.WERR_DS_DRA_NO_REPLICA:
847 pass
848 else:
849 self.errf.write(
850 "Error while replicating out last local changes from '%s' for demotion, "
851 "re-enabling inbound replication\n" % part)
852 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
853 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
854 samdb.modify(nmsg)
855 raise CommandError("Error while sending a DsReplicaSync for partition '%s'" % str(part), string)
856 try:
857 remote_samdb = SamDB(url="ldap://%s" % server,
858 session_info=system_session(),
859 credentials=creds, lp=lp)
861 self.errf.write("Changing userControl and container\n")
862 res = remote_samdb.search(base=str(remote_samdb.domain_dn()),
863 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
864 netbios_name.upper(),
865 attrs=["userAccountControl"])
866 dc_dn = res[0].dn
867 uac = int(str(res[0]["userAccountControl"]))
869 except Exception as e:
870 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
871 self.errf.write(
872 "Error while demoting, re-enabling inbound replication\n")
873 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
874 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
875 samdb.modify(nmsg)
876 raise CommandError("Error while changing account control", e)
878 if (len(res) != 1):
879 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
880 self.errf.write(
881 "Error while demoting, re-enabling inbound replication")
882 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
883 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
884 samdb.modify(nmsg)
885 raise CommandError("Unable to find object with samaccountName = %s$"
886 " in the remote dc" % netbios_name.upper())
888 olduac = uac
890 uac &= ~(UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION|UF_PARTIAL_SECRETS_ACCOUNT)
891 uac |= UF_WORKSTATION_TRUST_ACCOUNT
893 msg = ldb.Message()
894 msg.dn = dc_dn
896 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
897 ldb.FLAG_MOD_REPLACE,
898 "userAccountControl")
899 try:
900 remote_samdb.modify(msg)
901 except Exception as e:
902 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
903 self.errf.write(
904 "Error while demoting, re-enabling inbound replication")
905 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
906 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
907 samdb.modify(nmsg)
909 raise CommandError("Error while changing account control", e)
911 parent = msg.dn.parent()
912 dc_name = res[0].dn.get_rdn_value()
913 rdn = "CN=%s" % dc_name
915 # Let's move to the Computer container
916 i = 0
917 newrdn = str(rdn)
919 computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.domain_dn()))
920 res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
922 if (len(res) != 0):
923 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
924 scope=ldb.SCOPE_ONELEVEL)
925 while(len(res) != 0 and i < 100):
926 i = i + 1
927 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
928 scope=ldb.SCOPE_ONELEVEL)
930 if i == 100:
931 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
932 self.errf.write(
933 "Error while demoting, re-enabling inbound replication\n")
934 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
935 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
936 samdb.modify(nmsg)
938 msg = ldb.Message()
939 msg.dn = dc_dn
941 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
942 ldb.FLAG_MOD_REPLACE,
943 "userAccountControl")
945 remote_samdb.modify(msg)
947 raise CommandError("Unable to find a slot for renaming %s,"
948 " all names from %s-1 to %s-%d seemed used" %
949 (str(dc_dn), rdn, rdn, i - 9))
951 newrdn = "%s-%d" % (rdn, i)
953 try:
954 newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
955 remote_samdb.rename(dc_dn, newdn)
956 except Exception as e:
957 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
958 self.errf.write(
959 "Error while demoting, re-enabling inbound replication\n")
960 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
961 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
962 samdb.modify(nmsg)
964 msg = ldb.Message()
965 msg.dn = dc_dn
967 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
968 ldb.FLAG_MOD_REPLACE,
969 "userAccountControl")
971 remote_samdb.modify(msg)
972 raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
975 server_dsa_dn = samdb.get_serverName()
976 domain = remote_samdb.get_root_basedn()
978 try:
979 req1 = drsuapi.DsRemoveDSServerRequest1()
980 req1.server_dn = str(server_dsa_dn)
981 req1.domain_dn = str(domain)
982 req1.commit = 1
984 drsuapiBind.DsRemoveDSServer(drsuapi_handle, 1, req1)
985 except RuntimeError as e3:
986 (werr, string) = e3.args
987 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
988 self.errf.write(
989 "Error while demoting, re-enabling inbound replication\n")
990 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
991 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
992 samdb.modify(nmsg)
994 msg = ldb.Message()
995 msg.dn = newdn
997 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
998 ldb.FLAG_MOD_REPLACE,
999 "userAccountControl")
1000 remote_samdb.modify(msg)
1001 remote_samdb.rename(newdn, dc_dn)
1002 if werr == werror.WERR_DS_DRA_NO_REPLICA:
1003 raise CommandError("The DC %s is not present on (already removed from) the remote server: " % server_dsa_dn, e)
1004 else:
1005 raise CommandError("Error while sending a removeDsServer of %s: " % server_dsa_dn, e)
1007 remove_dc.remove_sysvol_references(remote_samdb, logger, dc_name)
1009 # These are objects under the computer account that should be deleted
1010 for s in ("CN=Enterprise,CN=NTFRS Subscriptions",
1011 "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
1012 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
1013 "CN=NTFRS Subscriptions"):
1014 try:
1015 remote_samdb.delete(ldb.Dn(remote_samdb,
1016 "%s,%s" % (s, str(newdn))))
1017 except ldb.LdbError as l:
1018 pass
1020 self.errf.write("Demote successful\n")
1023 class cmd_domain_level(Command):
1024 """Raise domain and forest function levels."""
1026 synopsis = "%prog (show|raise <options>) [options]"
1028 takes_optiongroups = {
1029 "sambaopts": options.SambaOptions,
1030 "credopts": options.CredentialsOptions,
1031 "versionopts": options.VersionOptions,
1034 takes_options = [
1035 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1036 metavar="URL", dest="H"),
1037 Option("--quiet", help="Be quiet", action="store_true"),
1038 Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1039 help="The forest function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)"),
1040 Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1041 help="The domain function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)")
1044 takes_args = ["subcommand"]
1046 def run(self, subcommand, H=None, forest_level=None, domain_level=None,
1047 quiet=False, credopts=None, sambaopts=None, versionopts=None):
1048 lp = sambaopts.get_loadparm()
1049 creds = credopts.get_credentials(lp, fallback_machine=True)
1051 samdb = SamDB(url=H, session_info=system_session(),
1052 credentials=creds, lp=lp)
1054 domain_dn = samdb.domain_dn()
1056 res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
1057 scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
1058 assert len(res_forest) == 1
1060 res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1061 attrs=["msDS-Behavior-Version", "nTMixedDomain"])
1062 assert len(res_domain) == 1
1064 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
1065 scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
1066 attrs=["msDS-Behavior-Version"])
1067 assert len(res_dc_s) >= 1
1069 # default values, since "msDS-Behavior-Version" does not exist on Windows 2000 AD
1070 level_forest = DS_DOMAIN_FUNCTION_2000
1071 level_domain = DS_DOMAIN_FUNCTION_2000
1073 if "msDS-Behavior-Version" in res_forest[0]:
1074 level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
1075 if "msDS-Behavior-Version" in res_domain[0]:
1076 level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
1077 level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
1079 min_level_dc = None
1080 for msg in res_dc_s:
1081 if "msDS-Behavior-Version" in msg:
1082 if min_level_dc is None or int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
1083 min_level_dc = int(msg["msDS-Behavior-Version"][0])
1084 else:
1085 min_level_dc = DS_DOMAIN_FUNCTION_2000
1086 # well, this is the least
1087 break
1089 if level_forest < DS_DOMAIN_FUNCTION_2000 or level_domain < DS_DOMAIN_FUNCTION_2000:
1090 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
1091 if min_level_dc < DS_DOMAIN_FUNCTION_2000:
1092 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
1093 if level_forest > level_domain:
1094 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
1095 if level_domain > min_level_dc:
1096 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
1098 if subcommand == "show":
1099 self.message("Domain and forest function level for domain '%s'" % domain_dn)
1100 if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1101 self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1102 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1103 self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1104 if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1105 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)!")
1107 self.message("")
1109 if level_forest == DS_DOMAIN_FUNCTION_2000:
1110 outstr = "2000"
1111 elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
1112 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1113 elif level_forest == DS_DOMAIN_FUNCTION_2003:
1114 outstr = "2003"
1115 elif level_forest == DS_DOMAIN_FUNCTION_2008:
1116 outstr = "2008"
1117 elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
1118 outstr = "2008 R2"
1119 elif level_forest == DS_DOMAIN_FUNCTION_2012:
1120 outstr = "2012"
1121 elif level_forest == DS_DOMAIN_FUNCTION_2012_R2:
1122 outstr = "2012 R2"
1123 else:
1124 outstr = "higher than 2012 R2"
1125 self.message("Forest function level: (Windows) " + outstr)
1127 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1128 outstr = "2000 mixed (NT4 DC support)"
1129 elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
1130 outstr = "2000"
1131 elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
1132 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1133 elif level_domain == DS_DOMAIN_FUNCTION_2003:
1134 outstr = "2003"
1135 elif level_domain == DS_DOMAIN_FUNCTION_2008:
1136 outstr = "2008"
1137 elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
1138 outstr = "2008 R2"
1139 elif level_domain == DS_DOMAIN_FUNCTION_2012:
1140 outstr = "2012"
1141 elif level_domain == DS_DOMAIN_FUNCTION_2012_R2:
1142 outstr = "2012 R2"
1143 else:
1144 outstr = "higher than 2012 R2"
1145 self.message("Domain function level: (Windows) " + outstr)
1147 if min_level_dc == DS_DOMAIN_FUNCTION_2000:
1148 outstr = "2000"
1149 elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
1150 outstr = "2003"
1151 elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
1152 outstr = "2008"
1153 elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
1154 outstr = "2008 R2"
1155 elif min_level_dc == DS_DOMAIN_FUNCTION_2012:
1156 outstr = "2012"
1157 elif min_level_dc == DS_DOMAIN_FUNCTION_2012_R2:
1158 outstr = "2012 R2"
1159 else:
1160 outstr = "higher than 2012 R2"
1161 self.message("Lowest function level of a DC: (Windows) " + outstr)
1163 elif subcommand == "raise":
1164 msgs = []
1166 if domain_level is not None:
1167 if domain_level == "2003":
1168 new_level_domain = DS_DOMAIN_FUNCTION_2003
1169 elif domain_level == "2008":
1170 new_level_domain = DS_DOMAIN_FUNCTION_2008
1171 elif domain_level == "2008_R2":
1172 new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
1173 elif domain_level == "2012":
1174 new_level_domain = DS_DOMAIN_FUNCTION_2012
1175 elif domain_level == "2012_R2":
1176 new_level_domain = DS_DOMAIN_FUNCTION_2012_R2
1178 if new_level_domain <= level_domain and level_domain_mixed == 0:
1179 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
1180 if new_level_domain > min_level_dc:
1181 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
1183 # Deactivate mixed/interim domain support
1184 if level_domain_mixed != 0:
1185 # Directly on the base DN
1186 m = ldb.Message()
1187 m.dn = ldb.Dn(samdb, domain_dn)
1188 m["nTMixedDomain"] = ldb.MessageElement("0",
1189 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1190 samdb.modify(m)
1191 # Under partitions
1192 m = ldb.Message()
1193 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
1194 m["nTMixedDomain"] = ldb.MessageElement("0",
1195 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1196 try:
1197 samdb.modify(m)
1198 except ldb.LdbError as e:
1199 (enum, emsg) = e.args
1200 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1201 raise
1203 # Directly on the base DN
1204 m = ldb.Message()
1205 m.dn = ldb.Dn(samdb, domain_dn)
1206 m["msDS-Behavior-Version"]= ldb.MessageElement(
1207 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1208 "msDS-Behavior-Version")
1209 samdb.modify(m)
1210 # Under partitions
1211 m = ldb.Message()
1212 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
1213 + ",CN=Partitions,%s" % samdb.get_config_basedn())
1214 m["msDS-Behavior-Version"]= ldb.MessageElement(
1215 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1216 "msDS-Behavior-Version")
1217 try:
1218 samdb.modify(m)
1219 except ldb.LdbError as e2:
1220 (enum, emsg) = e2.args
1221 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1222 raise
1224 level_domain = new_level_domain
1225 msgs.append("Domain function level changed!")
1227 if forest_level is not None:
1228 if forest_level == "2003":
1229 new_level_forest = DS_DOMAIN_FUNCTION_2003
1230 elif forest_level == "2008":
1231 new_level_forest = DS_DOMAIN_FUNCTION_2008
1232 elif forest_level == "2008_R2":
1233 new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
1234 elif forest_level == "2012":
1235 new_level_forest = DS_DOMAIN_FUNCTION_2012
1236 elif forest_level == "2012_R2":
1237 new_level_forest = DS_DOMAIN_FUNCTION_2012_R2
1239 if new_level_forest <= level_forest:
1240 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1241 if new_level_forest > level_domain:
1242 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1244 m = ldb.Message()
1245 m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
1246 m["msDS-Behavior-Version"]= ldb.MessageElement(
1247 str(new_level_forest), ldb.FLAG_MOD_REPLACE,
1248 "msDS-Behavior-Version")
1249 samdb.modify(m)
1250 msgs.append("Forest function level changed!")
1251 msgs.append("All changes applied successfully!")
1252 self.message("\n".join(msgs))
1253 else:
1254 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
1257 class cmd_domain_passwordsettings(Command):
1258 """Set password settings.
1260 Password complexity, password lockout policy, history length,
1261 minimum password length, the minimum and maximum password age) on
1262 a Samba AD DC server.
1264 Use against a Windows DC is possible, but group policy will override it.
1267 synopsis = "%prog (show|set <options>) [options]"
1269 takes_optiongroups = {
1270 "sambaopts": options.SambaOptions,
1271 "versionopts": options.VersionOptions,
1272 "credopts": options.CredentialsOptions,
1275 takes_options = [
1276 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1277 metavar="URL", dest="H"),
1278 Option("--quiet", help="Be quiet", action="store_true"),
1279 Option("--complexity", type="choice", choices=["on","off","default"],
1280 help="The password complexity (on | off | default). Default is 'on'"),
1281 Option("--store-plaintext", type="choice", choices=["on","off","default"],
1282 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1283 Option("--history-length",
1284 help="The password history length (<integer> | default). Default is 24.", type=str),
1285 Option("--min-pwd-length",
1286 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1287 Option("--min-pwd-age",
1288 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1289 Option("--max-pwd-age",
1290 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1291 Option("--account-lockout-duration",
1292 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),
1293 Option("--account-lockout-threshold",
1294 help="The number of bad password attempts allowed before locking out the account (<integer> | default). Default is 0 (never lock out).", type=str),
1295 Option("--reset-account-lockout-after",
1296 help="After this time is elapsed, the recorded number of attempts restarts from zero (<integer> | default). Default is 30.", type=str),
1299 takes_args = ["subcommand"]
1301 def run(self, subcommand, H=None, min_pwd_age=None, max_pwd_age=None,
1302 quiet=False, complexity=None, store_plaintext=None, history_length=None,
1303 min_pwd_length=None, account_lockout_duration=None, account_lockout_threshold=None,
1304 reset_account_lockout_after=None, credopts=None, sambaopts=None,
1305 versionopts=None):
1306 lp = sambaopts.get_loadparm()
1307 creds = credopts.get_credentials(lp)
1309 samdb = SamDB(url=H, session_info=system_session(),
1310 credentials=creds, lp=lp)
1312 domain_dn = samdb.domain_dn()
1313 res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1314 attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1315 "minPwdAge", "maxPwdAge", "lockoutDuration", "lockoutThreshold",
1316 "lockOutObservationWindow"])
1317 assert(len(res) == 1)
1318 try:
1319 pwd_props = int(res[0]["pwdProperties"][0])
1320 pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
1321 cur_min_pwd_len = int(res[0]["minPwdLength"][0])
1322 # ticks -> days
1323 cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1324 if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000:
1325 cur_max_pwd_age = 0
1326 else:
1327 cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1328 cur_account_lockout_threshold = int(res[0]["lockoutThreshold"][0])
1329 # ticks -> mins
1330 if int(res[0]["lockoutDuration"][0]) == -0x8000000000000000:
1331 cur_account_lockout_duration = 0
1332 else:
1333 cur_account_lockout_duration = abs(int(res[0]["lockoutDuration"][0])) / (1e7 * 60)
1334 cur_reset_account_lockout_after = abs(int(res[0]["lockOutObservationWindow"][0])) / (1e7 * 60)
1335 except Exception as e:
1336 raise CommandError("Could not retrieve password properties!", e)
1338 if subcommand == "show":
1339 self.message("Password informations for domain '%s'" % domain_dn)
1340 self.message("")
1341 if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
1342 self.message("Password complexity: on")
1343 else:
1344 self.message("Password complexity: off")
1345 if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
1346 self.message("Store plaintext passwords: on")
1347 else:
1348 self.message("Store plaintext passwords: off")
1349 self.message("Password history length: %d" % pwd_hist_len)
1350 self.message("Minimum password length: %d" % cur_min_pwd_len)
1351 self.message("Minimum password age (days): %d" % cur_min_pwd_age)
1352 self.message("Maximum password age (days): %d" % cur_max_pwd_age)
1353 self.message("Account lockout duration (mins): %d" % cur_account_lockout_duration)
1354 self.message("Account lockout threshold (attempts): %d" % cur_account_lockout_threshold)
1355 self.message("Reset account lockout after (mins): %d" % cur_reset_account_lockout_after)
1356 elif subcommand == "set":
1357 msgs = []
1358 m = ldb.Message()
1359 m.dn = ldb.Dn(samdb, domain_dn)
1361 if complexity is not None:
1362 if complexity == "on" or complexity == "default":
1363 pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
1364 msgs.append("Password complexity activated!")
1365 elif complexity == "off":
1366 pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
1367 msgs.append("Password complexity deactivated!")
1369 if store_plaintext is not None:
1370 if store_plaintext == "on" or store_plaintext == "default":
1371 pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1372 msgs.append("Plaintext password storage for changed passwords activated!")
1373 elif store_plaintext == "off":
1374 pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
1375 msgs.append("Plaintext password storage for changed passwords deactivated!")
1377 if complexity is not None or store_plaintext is not None:
1378 m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
1379 ldb.FLAG_MOD_REPLACE, "pwdProperties")
1381 if history_length is not None:
1382 if history_length == "default":
1383 pwd_hist_len = 24
1384 else:
1385 pwd_hist_len = int(history_length)
1387 if pwd_hist_len < 0 or pwd_hist_len > 24:
1388 raise CommandError("Password history length must be in the range of 0 to 24!")
1390 m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
1391 ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
1392 msgs.append("Password history length changed!")
1394 if min_pwd_length is not None:
1395 if min_pwd_length == "default":
1396 min_pwd_len = 7
1397 else:
1398 min_pwd_len = int(min_pwd_length)
1400 if min_pwd_len < 0 or min_pwd_len > 14:
1401 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1403 m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
1404 ldb.FLAG_MOD_REPLACE, "minPwdLength")
1405 msgs.append("Minimum password length changed!")
1407 if min_pwd_age is not None:
1408 if min_pwd_age == "default":
1409 min_pwd_age = 1
1410 else:
1411 min_pwd_age = int(min_pwd_age)
1413 if min_pwd_age < 0 or min_pwd_age > 998:
1414 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1416 # days -> ticks
1417 min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
1419 m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
1420 ldb.FLAG_MOD_REPLACE, "minPwdAge")
1421 msgs.append("Minimum password age changed!")
1423 if max_pwd_age is not None:
1424 if max_pwd_age == "default":
1425 max_pwd_age = 43
1426 else:
1427 max_pwd_age = int(max_pwd_age)
1429 if max_pwd_age < 0 or max_pwd_age > 999:
1430 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1432 # days -> ticks
1433 if max_pwd_age == 0:
1434 max_pwd_age_ticks = -0x8000000000000000
1435 else:
1436 max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
1438 m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
1439 ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1440 msgs.append("Maximum password age changed!")
1442 if account_lockout_duration is not None:
1443 if account_lockout_duration == "default":
1444 account_lockout_duration = 30
1445 else:
1446 account_lockout_duration = int(account_lockout_duration)
1448 if account_lockout_duration < 0 or account_lockout_duration > 99999:
1449 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1451 # days -> ticks
1452 if account_lockout_duration == 0:
1453 account_lockout_duration_ticks = -0x8000000000000000
1454 else:
1455 account_lockout_duration_ticks = -int(account_lockout_duration * (60 * 1e7))
1457 m["lockoutDuration"] = ldb.MessageElement(str(account_lockout_duration_ticks),
1458 ldb.FLAG_MOD_REPLACE, "lockoutDuration")
1459 msgs.append("Account lockout duration changed!")
1461 if account_lockout_threshold is not None:
1462 if account_lockout_threshold == "default":
1463 account_lockout_threshold = 0
1464 else:
1465 account_lockout_threshold = int(account_lockout_threshold)
1467 m["lockoutThreshold"] = ldb.MessageElement(str(account_lockout_threshold),
1468 ldb.FLAG_MOD_REPLACE, "lockoutThreshold")
1469 msgs.append("Account lockout threshold changed!")
1471 if reset_account_lockout_after is not None:
1472 if reset_account_lockout_after == "default":
1473 reset_account_lockout_after = 30
1474 else:
1475 reset_account_lockout_after = int(reset_account_lockout_after)
1477 if reset_account_lockout_after < 0 or reset_account_lockout_after > 99999:
1478 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1480 # days -> ticks
1481 if reset_account_lockout_after == 0:
1482 reset_account_lockout_after_ticks = -0x8000000000000000
1483 else:
1484 reset_account_lockout_after_ticks = -int(reset_account_lockout_after * (60 * 1e7))
1486 m["lockOutObservationWindow"] = ldb.MessageElement(str(reset_account_lockout_after_ticks),
1487 ldb.FLAG_MOD_REPLACE, "lockOutObservationWindow")
1488 msgs.append("Duration to reset account lockout after changed!")
1490 if max_pwd_age > 0 and min_pwd_age >= max_pwd_age:
1491 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
1493 if len(m) == 0:
1494 raise CommandError("You must specify at least one option to set. Try --help")
1495 samdb.modify(m)
1496 msgs.append("All changes applied successfully!")
1497 self.message("\n".join(msgs))
1498 else:
1499 raise CommandError("Wrong argument '%s'!" % subcommand)
1502 class cmd_domain_classicupgrade(Command):
1503 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1505 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1506 the testparm utility from your classic installation (with --testparm).
1509 synopsis = "%prog [options] <classic_smb_conf>"
1511 takes_optiongroups = {
1512 "sambaopts": options.SambaOptions,
1513 "versionopts": options.VersionOptions
1516 takes_options = [
1517 Option("--dbdir", type="string", metavar="DIR",
1518 help="Path to samba classic DC database directory"),
1519 Option("--testparm", type="string", metavar="PATH",
1520 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1521 Option("--targetdir", type="string", metavar="DIR",
1522 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1523 Option("--quiet", help="Be quiet", action="store_true"),
1524 Option("--verbose", help="Be verbose", action="store_true"),
1525 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
1526 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1527 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1528 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1529 "BIND9_DLZ uses samba4 AD to store zone information, "
1530 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1531 default="SAMBA_INTERNAL")
1534 ntvfs_options = [
1535 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
1536 action="store_true"),
1537 Option("--use-xattrs", type="choice", choices=["yes","no","auto"],
1538 metavar="[yes|no|auto]",
1539 help="Define if we should use the native fs capabilities or a tdb file for "
1540 "storing attributes likes ntacl when --use-ntvfs is set. "
1541 "auto tries to make an inteligent guess based on the user rights and system capabilities",
1542 default="auto")
1544 if samba.is_ntvfs_fileserver_built():
1545 takes_options.extend(ntvfs_options)
1547 takes_args = ["smbconf"]
1549 def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
1550 quiet=False, verbose=False, use_xattrs="auto", sambaopts=None, versionopts=None,
1551 dns_backend=None, use_ntvfs=False):
1553 if not os.path.exists(smbconf):
1554 raise CommandError("File %s does not exist" % smbconf)
1556 if testparm and not os.path.exists(testparm):
1557 raise CommandError("Testparm utility %s does not exist" % testparm)
1559 if dbdir and not os.path.exists(dbdir):
1560 raise CommandError("Directory %s does not exist" % dbdir)
1562 if not dbdir and not testparm:
1563 raise CommandError("Please specify either dbdir or testparm")
1565 logger = self.get_logger()
1566 if verbose:
1567 logger.setLevel(logging.DEBUG)
1568 elif quiet:
1569 logger.setLevel(logging.WARNING)
1570 else:
1571 logger.setLevel(logging.INFO)
1573 if dbdir and testparm:
1574 logger.warning("both dbdir and testparm specified, ignoring dbdir.")
1575 dbdir = None
1577 lp = sambaopts.get_loadparm()
1579 s3conf = s3param.get_context()
1581 if sambaopts.realm:
1582 s3conf.set("realm", sambaopts.realm)
1584 if targetdir is not None:
1585 if not os.path.isdir(targetdir):
1586 os.mkdir(targetdir)
1588 eadb = True
1589 if use_xattrs == "yes":
1590 eadb = False
1591 elif use_xattrs == "auto" and use_ntvfs == False:
1592 eadb = False
1593 elif use_ntvfs == False:
1594 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
1595 "Please re-run with --use-xattrs omitted.")
1596 elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
1597 if targetdir:
1598 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
1599 else:
1600 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
1601 try:
1602 try:
1603 samba.ntacls.setntacl(lp, tmpfile.name,
1604 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1605 eadb = False
1606 except Exception:
1607 # FIXME: Don't catch all exceptions here
1608 logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. "
1609 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1610 finally:
1611 tmpfile.close()
1613 # Set correct default values from dbdir or testparm
1614 paths = {}
1615 if dbdir:
1616 paths["state directory"] = dbdir
1617 paths["private dir"] = dbdir
1618 paths["lock directory"] = dbdir
1619 paths["smb passwd file"] = dbdir + "/smbpasswd"
1620 else:
1621 paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
1622 paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
1623 paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
1624 paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
1625 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1626 # "state directory", instead make use of "lock directory"
1627 if len(paths["state directory"]) == 0:
1628 paths["state directory"] = paths["lock directory"]
1630 for p in paths:
1631 s3conf.set(p, paths[p])
1633 # load smb.conf parameters
1634 logger.info("Reading smb.conf")
1635 s3conf.load(smbconf)
1636 samba3 = Samba3(smbconf, s3conf)
1638 logger.info("Provisioning")
1639 upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
1640 useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
1643 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
1644 __doc__ = cmd_domain_classicupgrade.__doc__
1646 # This command is present for backwards compatibility only,
1647 # and should not be shown.
1649 hidden = True
1651 class LocalDCCredentialsOptions(options.CredentialsOptions):
1652 def __init__(self, parser):
1653 options.CredentialsOptions.__init__(self, parser, special_name="local-dc")
1655 class DomainTrustCommand(Command):
1656 """List domain trusts."""
1658 def __init__(self):
1659 Command.__init__(self)
1660 self.local_lp = None
1662 self.local_server = None
1663 self.local_binding_string = None
1664 self.local_creds = None
1666 self.remote_server = None
1667 self.remote_binding_string = None
1668 self.remote_creds = None
1670 def _uint32(self, v):
1671 return ctypes.c_uint32(v).value
1673 def check_runtime_error(self, runtime, val):
1674 if runtime is None:
1675 return False
1677 err32 = self._uint32(runtime[0])
1678 if err32 == val:
1679 return True
1681 return False
1683 class LocalRuntimeError(CommandError):
1684 def __init__(exception_self, self, runtime, message):
1685 err32 = self._uint32(runtime[0])
1686 errstr = runtime[1]
1687 msg = "LOCAL_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1688 self.local_server, message, err32, errstr)
1689 CommandError.__init__(exception_self, msg)
1691 class RemoteRuntimeError(CommandError):
1692 def __init__(exception_self, self, runtime, message):
1693 err32 = self._uint32(runtime[0])
1694 errstr = runtime[1]
1695 msg = "REMOTE_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1696 self.remote_server, message, err32, errstr)
1697 CommandError.__init__(exception_self, msg)
1699 class LocalLdbError(CommandError):
1700 def __init__(exception_self, self, ldb_error, message):
1701 errval = ldb_error[0]
1702 errstr = ldb_error[1]
1703 msg = "LOCAL_DC[%s]: %s - ERROR(%d) - %s" % (
1704 self.local_server, message, errval, errstr)
1705 CommandError.__init__(exception_self, msg)
1707 def setup_local_server(self, sambaopts, localdcopts):
1708 if self.local_server is not None:
1709 return self.local_server
1711 lp = sambaopts.get_loadparm()
1713 local_server = localdcopts.ipaddress
1714 if local_server is None:
1715 server_role = lp.server_role()
1716 if server_role != "ROLE_ACTIVE_DIRECTORY_DC":
1717 raise CommandError("Invalid server_role %s" % (server_role))
1718 local_server = lp.get('netbios name')
1719 local_transport = "ncalrpc"
1720 local_binding_options = ""
1721 local_binding_options += ",auth_type=ncalrpc_as_system"
1722 local_ldap_url = None
1723 local_creds = None
1724 else:
1725 local_transport = "ncacn_np"
1726 local_binding_options = ""
1727 local_ldap_url = "ldap://%s" % local_server
1728 local_creds = localdcopts.get_credentials(lp)
1730 self.local_lp = lp
1732 self.local_server = local_server
1733 self.local_binding_string = "%s:%s[%s]" % (local_transport, local_server, local_binding_options)
1734 self.local_ldap_url = local_ldap_url
1735 self.local_creds = local_creds
1736 return self.local_server
1738 def new_local_lsa_connection(self):
1739 return lsa.lsarpc(self.local_binding_string, self.local_lp, self.local_creds)
1741 def new_local_netlogon_connection(self):
1742 return netlogon.netlogon(self.local_binding_string, self.local_lp, self.local_creds)
1744 def new_local_ldap_connection(self):
1745 return SamDB(url=self.local_ldap_url,
1746 session_info=system_session(),
1747 credentials=self.local_creds,
1748 lp=self.local_lp)
1750 def setup_remote_server(self, credopts, domain,
1751 require_pdc=True,
1752 require_writable=True):
1754 if require_pdc:
1755 assert require_writable
1757 if self.remote_server is not None:
1758 return self.remote_server
1760 self.remote_server = "__unknown__remote_server__.%s" % domain
1761 assert self.local_server is not None
1763 remote_creds = credopts.get_credentials(self.local_lp)
1764 remote_server = credopts.ipaddress
1765 remote_binding_options = ""
1767 # TODO: we should also support NT4 domains
1768 # we could use local_netlogon.netr_DsRGetDCNameEx2() with the remote domain name
1769 # and delegate NBT or CLDAP to the local netlogon server
1770 try:
1771 remote_net = Net(remote_creds, self.local_lp, server=remote_server)
1772 remote_flags = nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS
1773 if require_writable:
1774 remote_flags |= nbt.NBT_SERVER_WRITABLE
1775 if require_pdc:
1776 remote_flags |= nbt.NBT_SERVER_PDC
1777 remote_info = remote_net.finddc(flags=remote_flags, domain=domain, address=remote_server)
1778 except NTSTATUSError as error:
1779 raise CommandError("Failed to find a writeable DC for domain '%s': %s" %
1780 (domain, error[1]))
1781 except Exception:
1782 raise CommandError("Failed to find a writeable DC for domain '%s'" % domain)
1783 flag_map = {
1784 nbt.NBT_SERVER_PDC: "PDC",
1785 nbt.NBT_SERVER_GC: "GC",
1786 nbt.NBT_SERVER_LDAP: "LDAP",
1787 nbt.NBT_SERVER_DS: "DS",
1788 nbt.NBT_SERVER_KDC: "KDC",
1789 nbt.NBT_SERVER_TIMESERV: "TIMESERV",
1790 nbt.NBT_SERVER_CLOSEST: "CLOSEST",
1791 nbt.NBT_SERVER_WRITABLE: "WRITABLE",
1792 nbt.NBT_SERVER_GOOD_TIMESERV: "GOOD_TIMESERV",
1793 nbt.NBT_SERVER_NDNC: "NDNC",
1794 nbt.NBT_SERVER_SELECT_SECRET_DOMAIN_6: "SELECT_SECRET_DOMAIN_6",
1795 nbt.NBT_SERVER_FULL_SECRET_DOMAIN_6: "FULL_SECRET_DOMAIN_6",
1796 nbt.NBT_SERVER_ADS_WEB_SERVICE: "ADS_WEB_SERVICE",
1797 nbt.NBT_SERVER_DS_8: "DS_8",
1798 nbt.NBT_SERVER_HAS_DNS_NAME: "HAS_DNS_NAME",
1799 nbt.NBT_SERVER_IS_DEFAULT_NC: "IS_DEFAULT_NC",
1800 nbt.NBT_SERVER_FOREST_ROOT: "FOREST_ROOT",
1802 server_type_string = self.generic_bitmap_to_string(flag_map,
1803 remote_info.server_type, names_only=True)
1804 self.outf.write("RemoteDC Netbios[%s] DNS[%s] ServerType[%s]\n" % (
1805 remote_info.pdc_name,
1806 remote_info.pdc_dns_name,
1807 server_type_string))
1809 self.remote_server = remote_info.pdc_dns_name
1810 self.remote_binding_string="ncacn_np:%s[%s]" % (self.remote_server, remote_binding_options)
1811 self.remote_creds = remote_creds
1812 return self.remote_server
1814 def new_remote_lsa_connection(self):
1815 return lsa.lsarpc(self.remote_binding_string, self.local_lp, self.remote_creds)
1817 def new_remote_netlogon_connection(self):
1818 return netlogon.netlogon(self.remote_binding_string, self.local_lp, self.remote_creds)
1820 def get_lsa_info(self, conn, policy_access):
1821 objectAttr = lsa.ObjectAttribute()
1822 objectAttr.sec_qos = lsa.QosInfo()
1824 policy = conn.OpenPolicy2(''.decode('utf-8'),
1825 objectAttr, policy_access)
1827 info = conn.QueryInfoPolicy2(policy, lsa.LSA_POLICY_INFO_DNS)
1829 return (policy, info)
1831 def get_netlogon_dc_info(self, conn, server):
1832 info = conn.netr_DsRGetDCNameEx2(server,
1833 None, 0, None, None, None,
1834 netlogon.DS_RETURN_DNS_NAME)
1835 return info
1837 def netr_DomainTrust_to_name(self, t):
1838 if t.trust_type == lsa.LSA_TRUST_TYPE_DOWNLEVEL:
1839 return t.netbios_name
1841 return t.dns_name
1843 def netr_DomainTrust_to_type(self, a, t):
1844 primary = None
1845 primary_parent = None
1846 for _t in a:
1847 if _t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
1848 primary = _t
1849 if not _t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1850 primary_parent = a[_t.parent_index]
1851 break
1853 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1854 if t is primary_parent:
1855 return "Parent"
1857 if t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1858 return "TreeRoot"
1860 parent = a[t.parent_index]
1861 if parent is primary:
1862 return "Child"
1864 return "Shortcut"
1866 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1867 return "Forest"
1869 return "External"
1871 def netr_DomainTrust_to_transitive(self, t):
1872 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1873 return "Yes"
1875 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE:
1876 return "No"
1878 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1879 return "Yes"
1881 return "No"
1883 def netr_DomainTrust_to_direction(self, t):
1884 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND and \
1885 t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1886 return "BOTH"
1888 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND:
1889 return "INCOMING"
1891 if t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1892 return "OUTGOING"
1894 return "INVALID"
1896 def generic_enum_to_string(self, e_dict, v, names_only=False):
1897 try:
1898 w = e_dict[v]
1899 except KeyError:
1900 v32 = self._uint32(v)
1901 w = "__unknown__%08X__" % v32
1903 r = "0x%x (%s)" % (v, w)
1904 return r;
1906 def generic_bitmap_to_string(self, b_dict, v, names_only=False):
1908 s = []
1910 c = v
1911 for b in sorted(b_dict.keys()):
1912 if not (c & b):
1913 continue
1914 c &= ~b
1915 s += [b_dict[b]]
1917 if c != 0:
1918 c32 = self._uint32(c)
1919 s += ["__unknown_%08X__" % c32]
1921 w = ",".join(s)
1922 if names_only:
1923 return w
1924 r = "0x%x (%s)" % (v, w)
1925 return r;
1927 def trustType_string(self, v):
1928 types = {
1929 lsa.LSA_TRUST_TYPE_DOWNLEVEL : "DOWNLEVEL",
1930 lsa.LSA_TRUST_TYPE_UPLEVEL : "UPLEVEL",
1931 lsa.LSA_TRUST_TYPE_MIT : "MIT",
1932 lsa.LSA_TRUST_TYPE_DCE : "DCE",
1934 return self.generic_enum_to_string(types, v)
1936 def trustDirection_string(self, v):
1937 directions = {
1938 lsa.LSA_TRUST_DIRECTION_INBOUND |
1939 lsa.LSA_TRUST_DIRECTION_OUTBOUND : "BOTH",
1940 lsa.LSA_TRUST_DIRECTION_INBOUND : "INBOUND",
1941 lsa.LSA_TRUST_DIRECTION_OUTBOUND : "OUTBOUND",
1943 return self.generic_enum_to_string(directions, v)
1945 def trustAttributes_string(self, v):
1946 attributes = {
1947 lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE : "NON_TRANSITIVE",
1948 lsa.LSA_TRUST_ATTRIBUTE_UPLEVEL_ONLY : "UPLEVEL_ONLY",
1949 lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN : "QUARANTINED_DOMAIN",
1950 lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE : "FOREST_TRANSITIVE",
1951 lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION : "CROSS_ORGANIZATION",
1952 lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST : "WITHIN_FOREST",
1953 lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL : "TREAT_AS_EXTERNAL",
1954 lsa.LSA_TRUST_ATTRIBUTE_USES_RC4_ENCRYPTION : "USES_RC4_ENCRYPTION",
1956 return self.generic_bitmap_to_string(attributes, v)
1958 def kerb_EncTypes_string(self, v):
1959 enctypes = {
1960 security.KERB_ENCTYPE_DES_CBC_CRC : "DES_CBC_CRC",
1961 security.KERB_ENCTYPE_DES_CBC_MD5 : "DES_CBC_MD5",
1962 security.KERB_ENCTYPE_RC4_HMAC_MD5 : "RC4_HMAC_MD5",
1963 security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96 : "AES128_CTS_HMAC_SHA1_96",
1964 security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96 : "AES256_CTS_HMAC_SHA1_96",
1965 security.KERB_ENCTYPE_FAST_SUPPORTED : "FAST_SUPPORTED",
1966 security.KERB_ENCTYPE_COMPOUND_IDENTITY_SUPPORTED : "COMPOUND_IDENTITY_SUPPORTED",
1967 security.KERB_ENCTYPE_CLAIMS_SUPPORTED : "CLAIMS_SUPPORTED",
1968 security.KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED : "RESOURCE_SID_COMPRESSION_DISABLED",
1970 return self.generic_bitmap_to_string(enctypes, v)
1972 def entry_tln_status(self, e_flags, ):
1973 if e_flags == 0:
1974 return "Status[Enabled]"
1976 flags = {
1977 lsa.LSA_TLN_DISABLED_NEW : "Disabled-New",
1978 lsa.LSA_TLN_DISABLED_ADMIN : "Disabled",
1979 lsa.LSA_TLN_DISABLED_CONFLICT : "Disabled-Conflicting",
1981 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
1983 def entry_dom_status(self, e_flags):
1984 if e_flags == 0:
1985 return "Status[Enabled]"
1987 flags = {
1988 lsa.LSA_SID_DISABLED_ADMIN : "Disabled-SID",
1989 lsa.LSA_SID_DISABLED_CONFLICT : "Disabled-SID-Conflicting",
1990 lsa.LSA_NB_DISABLED_ADMIN : "Disabled-NB",
1991 lsa.LSA_NB_DISABLED_CONFLICT : "Disabled-NB-Conflicting",
1993 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
1995 def write_forest_trust_info(self, fti, tln=None, collisions=None):
1996 if tln is not None:
1997 tln_string = " TDO[%s]" % tln
1998 else:
1999 tln_string = ""
2001 self.outf.write("Namespaces[%d]%s:\n" % (
2002 len(fti.entries), tln_string))
2004 for i in xrange(0, len(fti.entries)):
2005 e = fti.entries[i]
2007 flags = e.flags
2008 collision_string = ""
2010 if collisions is not None:
2011 for c in collisions.entries:
2012 if c.index != i:
2013 continue
2014 flags = c.flags
2015 collision_string = " Collision[%s]" % (c.name.string)
2017 d = e.forest_trust_data
2018 if e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
2019 self.outf.write("TLN: %-32s DNS[*.%s]%s\n" % (
2020 self.entry_tln_status(flags),
2021 d.string, collision_string))
2022 elif e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
2023 self.outf.write("TLN_EX: %-29s DNS[*.%s]\n" % (
2024 "", d.string))
2025 elif e.type == lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
2026 self.outf.write("DOM: %-32s DNS[%s] Netbios[%s] SID[%s]%s\n" % (
2027 self.entry_dom_status(flags),
2028 d.dns_domain_name.string,
2029 d.netbios_domain_name.string,
2030 d.domain_sid, collision_string))
2031 return
2033 class cmd_domain_trust_list(DomainTrustCommand):
2034 """List domain trusts."""
2036 synopsis = "%prog [options]"
2038 takes_optiongroups = {
2039 "sambaopts": options.SambaOptions,
2040 "versionopts": options.VersionOptions,
2041 "localdcopts": LocalDCCredentialsOptions,
2044 takes_options = [
2047 def run(self, sambaopts=None, versionopts=None, localdcopts=None):
2049 local_server = self.setup_local_server(sambaopts, localdcopts)
2050 try:
2051 local_netlogon = self.new_local_netlogon_connection()
2052 except RuntimeError as error:
2053 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2055 try:
2056 local_netlogon_trusts = local_netlogon.netr_DsrEnumerateDomainTrusts(local_server,
2057 netlogon.NETR_TRUST_FLAG_IN_FOREST |
2058 netlogon.NETR_TRUST_FLAG_OUTBOUND |
2059 netlogon.NETR_TRUST_FLAG_INBOUND)
2060 except RuntimeError as error:
2061 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
2062 # TODO: we could implement a fallback to lsa.EnumTrustDom()
2063 raise CommandError("LOCAL_DC[%s]: netr_DsrEnumerateDomainTrusts not supported." % (
2064 self.local_server))
2065 raise self.LocalRuntimeError(self, error, "netr_DsrEnumerateDomainTrusts failed")
2067 a = local_netlogon_trusts.array
2068 for t in a:
2069 if t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
2070 continue
2071 self.outf.write("%-14s %-15s %-19s %s\n" % (
2072 "Type[%s]" % self.netr_DomainTrust_to_type(a, t),
2073 "Transitive[%s]" % self.netr_DomainTrust_to_transitive(t),
2074 "Direction[%s]" % self.netr_DomainTrust_to_direction(t),
2075 "Name[%s]" % self.netr_DomainTrust_to_name(t)))
2076 return
2078 class cmd_domain_trust_show(DomainTrustCommand):
2079 """Show trusted domain details."""
2081 synopsis = "%prog NAME [options]"
2083 takes_optiongroups = {
2084 "sambaopts": options.SambaOptions,
2085 "versionopts": options.VersionOptions,
2086 "localdcopts": LocalDCCredentialsOptions,
2089 takes_options = [
2092 takes_args = ["domain"]
2094 def run(self, domain, sambaopts=None, versionopts=None, localdcopts=None):
2096 local_server = self.setup_local_server(sambaopts, localdcopts)
2097 try:
2098 local_lsa = self.new_local_lsa_connection()
2099 except RuntimeError as error:
2100 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2102 try:
2103 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2104 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2105 except RuntimeError as error:
2106 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2108 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2109 local_lsa_info.name.string,
2110 local_lsa_info.dns_domain.string,
2111 local_lsa_info.sid))
2113 lsaString = lsa.String()
2114 lsaString.string = domain
2115 try:
2116 local_tdo_full = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2117 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2118 local_tdo_info = local_tdo_full.info_ex
2119 local_tdo_posix = local_tdo_full.posix_offset
2120 except NTSTATUSError as error:
2121 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2122 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2124 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(FULL_INFO) failed")
2126 try:
2127 local_tdo_enctypes = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2128 lsaString, lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES)
2129 except NTSTATUSError as error:
2130 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_PARAMETER):
2131 error = None
2132 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_INFO_CLASS):
2133 error = None
2135 if error is not None:
2136 raise self.LocalRuntimeError(self, error,
2137 "QueryTrustedDomainInfoByName(SUPPORTED_ENCRYPTION_TYPES) failed")
2139 local_tdo_enctypes = lsa.TrustDomainInfoSupportedEncTypes()
2140 local_tdo_enctypes.enc_types = 0
2142 try:
2143 local_tdo_forest = None
2144 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2145 local_tdo_forest = local_lsa.lsaRQueryForestTrustInformation(local_policy,
2146 lsaString, lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
2147 except RuntimeError as error:
2148 if self.check_runtime_error(error, ntstatus.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE):
2149 error = None
2150 if self.check_runtime_error(error, ntstatus.NT_STATUS_NOT_FOUND):
2151 error = None
2152 if error is not None:
2153 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation failed")
2155 local_tdo_forest = lsa.ForestTrustInformation()
2156 local_tdo_forest.count = 0
2157 local_tdo_forest.entries = []
2159 self.outf.write("TrusteDomain:\n\n");
2160 self.outf.write("NetbiosName: %s\n" % local_tdo_info.netbios_name.string)
2161 if local_tdo_info.netbios_name.string != local_tdo_info.domain_name.string:
2162 self.outf.write("DnsName: %s\n" % local_tdo_info.domain_name.string)
2163 self.outf.write("SID: %s\n" % local_tdo_info.sid)
2164 self.outf.write("Type: %s\n" % self.trustType_string(local_tdo_info.trust_type))
2165 self.outf.write("Direction: %s\n" % self.trustDirection_string(local_tdo_info.trust_direction))
2166 self.outf.write("Attributes: %s\n" % self.trustAttributes_string(local_tdo_info.trust_attributes))
2167 posix_offset_u32 = ctypes.c_uint32(local_tdo_posix.posix_offset).value
2168 posix_offset_i32 = ctypes.c_int32(local_tdo_posix.posix_offset).value
2169 self.outf.write("PosixOffset: 0x%08X (%d)\n" % (posix_offset_u32, posix_offset_i32))
2170 self.outf.write("kerb_EncTypes: %s\n" % self.kerb_EncTypes_string(local_tdo_enctypes.enc_types))
2172 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2173 self.write_forest_trust_info(local_tdo_forest,
2174 tln=local_tdo_info.domain_name.string)
2176 return
2178 class cmd_domain_trust_create(DomainTrustCommand):
2179 """Create a domain or forest trust."""
2181 synopsis = "%prog DOMAIN [options]"
2183 takes_optiongroups = {
2184 "sambaopts": options.SambaOptions,
2185 "versionopts": options.VersionOptions,
2186 "credopts": options.CredentialsOptions,
2187 "localdcopts": LocalDCCredentialsOptions,
2190 takes_options = [
2191 Option("--type", type="choice", metavar="TYPE",
2192 choices=["external", "forest"],
2193 help="The type of the trust: 'external' or 'forest'.",
2194 dest='trust_type',
2195 default="external"),
2196 Option("--direction", type="choice", metavar="DIRECTION",
2197 choices=["incoming", "outgoing", "both"],
2198 help="The trust direction: 'incoming', 'outgoing' or 'both'.",
2199 dest='trust_direction',
2200 default="both"),
2201 Option("--create-location", type="choice", metavar="LOCATION",
2202 choices=["local", "both"],
2203 help="Where to create the trusted domain object: 'local' or 'both'.",
2204 dest='create_location',
2205 default="both"),
2206 Option("--cross-organisation", action="store_true",
2207 help="The related domains does not belong to the same organisation.",
2208 dest='cross_organisation',
2209 default=False),
2210 Option("--quarantined", type="choice", metavar="yes|no",
2211 choices=["yes", "no", None],
2212 help="Special SID filtering rules are applied to the trust. "
2213 "With --type=external the default is yes. "
2214 "With --type=forest the default is no.",
2215 dest='quarantined_arg',
2216 default=None),
2217 Option("--not-transitive", action="store_true",
2218 help="The forest trust is not transitive.",
2219 dest='not_transitive',
2220 default=False),
2221 Option("--treat-as-external", action="store_true",
2222 help="The treat the forest trust as external.",
2223 dest='treat_as_external',
2224 default=False),
2225 Option("--no-aes-keys", action="store_false",
2226 help="The trust uses aes kerberos keys.",
2227 dest='use_aes_keys',
2228 default=True),
2229 Option("--skip-validation", action="store_false",
2230 help="Skip validation of the trust.",
2231 dest='validate',
2232 default=True),
2235 takes_args = ["domain"]
2237 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2238 trust_type=None, trust_direction=None, create_location=None,
2239 cross_organisation=False, quarantined_arg=None,
2240 not_transitive=False, treat_as_external=False,
2241 use_aes_keys=False, validate=True):
2243 lsaString = lsa.String()
2245 quarantined = False
2246 if quarantined_arg is None:
2247 if trust_type == 'external':
2248 quarantined = True
2249 elif quarantined_arg == 'yes':
2250 quarantined = True
2252 if trust_type != 'forest':
2253 if not_transitive:
2254 raise CommandError("--not-transitive requires --type=forest")
2255 if treat_as_external:
2256 raise CommandError("--treat-as-external requires --type=forest")
2258 enc_types = None
2259 if use_aes_keys:
2260 enc_types = lsa.TrustDomainInfoSupportedEncTypes()
2261 enc_types.enc_types = security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96
2262 enc_types.enc_types |= security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96
2264 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2265 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2266 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2268 local_trust_info = lsa.TrustDomainInfoInfoEx()
2269 local_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2270 local_trust_info.trust_direction = 0
2271 if trust_direction == "both":
2272 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2273 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2274 elif trust_direction == "incoming":
2275 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2276 elif trust_direction == "outgoing":
2277 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2278 local_trust_info.trust_attributes = 0
2279 if cross_organisation:
2280 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2281 if quarantined:
2282 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2283 if trust_type == "forest":
2284 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2285 if not_transitive:
2286 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2287 if treat_as_external:
2288 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2290 def get_password(name):
2291 password = None
2292 while True:
2293 if password is not None and password is not '':
2294 return password
2295 password = getpass("New %s Password: " % name)
2296 passwordverify = getpass("Retype %s Password: " % name)
2297 if not password == passwordverify:
2298 password = None
2299 self.outf.write("Sorry, passwords do not match.\n")
2301 incoming_secret = None
2302 outgoing_secret = None
2303 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2304 if create_location == "local":
2305 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2306 incoming_password = get_password("Incoming Trust")
2307 incoming_secret = string_to_byte_array(incoming_password.encode('utf-16-le'))
2308 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2309 outgoing_password = get_password("Outgoing Trust")
2310 outgoing_secret = string_to_byte_array(outgoing_password.encode('utf-16-le'))
2312 remote_trust_info = None
2313 else:
2314 # We use 240 random bytes.
2315 # Windows uses 28 or 240 random bytes. I guess it's
2316 # based on the trust type external vs. forest.
2318 # The initial trust password can be up to 512 bytes
2319 # while the versioned passwords used for periodic updates
2320 # can only be up to 498 bytes, as netr_ServerPasswordSet2()
2321 # needs to pass the NL_PASSWORD_VERSION structure within the
2322 # 512 bytes and a 2 bytes confounder is required.
2324 def random_trust_secret(length):
2325 pw = samba.generate_random_machine_password(length/2, length/2)
2326 return string_to_byte_array(pw.encode('utf-16-le'))
2328 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2329 incoming_secret = random_trust_secret(240)
2330 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2331 outgoing_secret = random_trust_secret(240)
2333 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2334 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2336 remote_trust_info = lsa.TrustDomainInfoInfoEx()
2337 remote_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2338 remote_trust_info.trust_direction = 0
2339 if trust_direction == "both":
2340 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2341 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2342 elif trust_direction == "incoming":
2343 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2344 elif trust_direction == "outgoing":
2345 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2346 remote_trust_info.trust_attributes = 0
2347 if cross_organisation:
2348 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2349 if quarantined:
2350 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2351 if trust_type == "forest":
2352 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2353 if not_transitive:
2354 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2355 if treat_as_external:
2356 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2358 local_server = self.setup_local_server(sambaopts, localdcopts)
2359 try:
2360 local_lsa = self.new_local_lsa_connection()
2361 except RuntimeError as error:
2362 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2364 try:
2365 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2366 except RuntimeError as error:
2367 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2369 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2370 local_lsa_info.name.string,
2371 local_lsa_info.dns_domain.string,
2372 local_lsa_info.sid))
2374 try:
2375 remote_server = self.setup_remote_server(credopts, domain)
2376 except RuntimeError as error:
2377 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2379 try:
2380 remote_lsa = self.new_remote_lsa_connection()
2381 except RuntimeError as error:
2382 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2384 try:
2385 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2386 except RuntimeError as error:
2387 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2389 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2390 remote_lsa_info.name.string,
2391 remote_lsa_info.dns_domain.string,
2392 remote_lsa_info.sid))
2394 local_trust_info.domain_name.string = remote_lsa_info.dns_domain.string
2395 local_trust_info.netbios_name.string = remote_lsa_info.name.string
2396 local_trust_info.sid = remote_lsa_info.sid
2398 if remote_trust_info:
2399 remote_trust_info.domain_name.string = local_lsa_info.dns_domain.string
2400 remote_trust_info.netbios_name.string = local_lsa_info.name.string
2401 remote_trust_info.sid = local_lsa_info.sid
2403 try:
2404 lsaString.string = local_trust_info.domain_name.string
2405 local_old_netbios = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2406 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2407 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2408 except NTSTATUSError as error:
2409 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2410 raise self.LocalRuntimeError(self, error,
2411 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2412 lsaString.string))
2414 try:
2415 lsaString.string = local_trust_info.netbios_name.string
2416 local_old_dns = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2417 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2418 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2419 except NTSTATUSError as error:
2420 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2421 raise self.LocalRuntimeError(self, error,
2422 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2423 lsaString.string))
2425 if remote_trust_info:
2426 try:
2427 lsaString.string = remote_trust_info.domain_name.string
2428 remote_old_netbios = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2429 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2430 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2431 except NTSTATUSError as error:
2432 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2433 raise self.RemoteRuntimeError(self, error,
2434 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2435 lsaString.string))
2437 try:
2438 lsaString.string = remote_trust_info.netbios_name.string
2439 remote_old_dns = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2440 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2441 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2442 except NTSTATUSError as error:
2443 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2444 raise self.RemoteRuntimeError(self, error,
2445 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2446 lsaString.string))
2448 try:
2449 local_netlogon = self.new_local_netlogon_connection()
2450 except RuntimeError as error:
2451 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2453 try:
2454 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
2455 except RuntimeError as error:
2456 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
2458 if remote_trust_info:
2459 try:
2460 remote_netlogon = self.new_remote_netlogon_connection()
2461 except RuntimeError as error:
2462 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2464 try:
2465 remote_netlogon_info = self.get_netlogon_dc_info(remote_netlogon, remote_server)
2466 except RuntimeError as error:
2467 raise self.RemoteRuntimeError(self, error, "failed to get netlogon dc info")
2469 def generate_AuthInOutBlob(secret, update_time):
2470 if secret is None:
2471 blob = drsblobs.trustAuthInOutBlob()
2472 blob.count = 0
2474 return blob
2476 clear = drsblobs.AuthInfoClear()
2477 clear.size = len(secret)
2478 clear.password = secret
2480 info = drsblobs.AuthenticationInformation()
2481 info.LastUpdateTime = samba.unix2nttime(update_time)
2482 info.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
2483 info.AuthInfo = clear
2485 array = drsblobs.AuthenticationInformationArray()
2486 array.count = 1
2487 array.array = [info]
2489 blob = drsblobs.trustAuthInOutBlob()
2490 blob.count = 1
2491 blob.current = array
2493 return blob
2495 def generate_AuthInfoInternal(session_key, incoming=None, outgoing=None):
2496 confounder = [0] * 512
2497 for i in range(len(confounder)):
2498 confounder[i] = random.randint(0, 255)
2500 trustpass = drsblobs.trustDomainPasswords()
2502 trustpass.confounder = confounder
2503 trustpass.outgoing = outgoing
2504 trustpass.incoming = incoming
2506 trustpass_blob = ndr_pack(trustpass)
2508 encrypted_trustpass = arcfour_encrypt(session_key, trustpass_blob)
2510 auth_blob = lsa.DATA_BUF2()
2511 auth_blob.size = len(encrypted_trustpass)
2512 auth_blob.data = string_to_byte_array(encrypted_trustpass)
2514 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
2515 auth_info.auth_blob = auth_blob
2517 return auth_info
2519 update_time = samba.current_unix_time()
2520 incoming_blob = generate_AuthInOutBlob(incoming_secret, update_time)
2521 outgoing_blob = generate_AuthInOutBlob(outgoing_secret, update_time)
2523 local_tdo_handle = None
2524 remote_tdo_handle = None
2526 local_auth_info = generate_AuthInfoInternal(local_lsa.session_key,
2527 incoming=incoming_blob,
2528 outgoing=outgoing_blob)
2529 if remote_trust_info:
2530 remote_auth_info = generate_AuthInfoInternal(remote_lsa.session_key,
2531 incoming=outgoing_blob,
2532 outgoing=incoming_blob)
2534 try:
2535 if remote_trust_info:
2536 self.outf.write("Creating remote TDO.\n")
2537 current_request = { "location": "remote", "name": "CreateTrustedDomainEx2"}
2538 remote_tdo_handle = remote_lsa.CreateTrustedDomainEx2(remote_policy,
2539 remote_trust_info,
2540 remote_auth_info,
2541 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2542 self.outf.write("Remote TDO created.\n")
2543 if enc_types:
2544 self.outf.write("Setting supported encryption types on remote TDO.\n")
2545 current_request = { "location": "remote", "name": "SetInformationTrustedDomain"}
2546 remote_lsa.SetInformationTrustedDomain(remote_tdo_handle,
2547 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2548 enc_types)
2550 self.outf.write("Creating local TDO.\n")
2551 current_request = { "location": "local", "name": "CreateTrustedDomainEx2"}
2552 local_tdo_handle = local_lsa.CreateTrustedDomainEx2(local_policy,
2553 local_trust_info,
2554 local_auth_info,
2555 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2556 self.outf.write("Local TDO created\n")
2557 if enc_types:
2558 self.outf.write("Setting supported encryption types on local TDO.\n")
2559 current_request = { "location": "local", "name": "SetInformationTrustedDomain"}
2560 local_lsa.SetInformationTrustedDomain(local_tdo_handle,
2561 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2562 enc_types)
2563 except RuntimeError as error:
2564 self.outf.write("Error: %s failed %sly - cleaning up\n" % (
2565 current_request['name'], current_request['location']))
2566 if remote_tdo_handle:
2567 self.outf.write("Deleting remote TDO.\n")
2568 remote_lsa.DeleteObject(remote_tdo_handle)
2569 remote_tdo_handle = None
2570 if local_tdo_handle:
2571 self.outf.write("Deleting local TDO.\n")
2572 local_lsa.DeleteObject(local_tdo_handle)
2573 local_tdo_handle = None
2574 if current_request['location'] is "remote":
2575 raise self.RemoteRuntimeError(self, error, "%s" % (
2576 current_request['name']))
2577 raise self.LocalRuntimeError(self, error, "%s" % (
2578 current_request['name']))
2580 if validate:
2581 if local_trust_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2582 self.outf.write("Setup local forest trust information...\n")
2583 try:
2584 # get all information about the remote trust
2585 # this triggers netr_GetForestTrustInformation to the remote domain
2586 # and lsaRSetForestTrustInformation() locally, but new top level
2587 # names are disabled by default.
2588 local_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
2589 remote_lsa_info.dns_domain.string,
2590 netlogon.DS_GFTI_UPDATE_TDO)
2591 except RuntimeError as error:
2592 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2594 try:
2595 # here we try to enable all top level names
2596 local_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
2597 remote_lsa_info.dns_domain,
2598 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2599 local_forest_info,
2601 except RuntimeError as error:
2602 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2604 self.write_forest_trust_info(local_forest_info,
2605 tln=remote_lsa_info.dns_domain.string,
2606 collisions=local_forest_collision)
2608 if remote_trust_info:
2609 self.outf.write("Setup remote forest trust information...\n")
2610 try:
2611 # get all information about the local trust (from the perspective of the remote domain)
2612 # this triggers netr_GetForestTrustInformation to our domain.
2613 # and lsaRSetForestTrustInformation() remotely, but new top level
2614 # names are disabled by default.
2615 remote_forest_info = remote_netlogon.netr_DsRGetForestTrustInformation(remote_netlogon_info.dc_unc,
2616 local_lsa_info.dns_domain.string,
2617 netlogon.DS_GFTI_UPDATE_TDO)
2618 except RuntimeError as error:
2619 raise self.RemoteRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2621 try:
2622 # here we try to enable all top level names
2623 remote_forest_collision = remote_lsa.lsaRSetForestTrustInformation(remote_policy,
2624 local_lsa_info.dns_domain,
2625 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2626 remote_forest_info,
2628 except RuntimeError as error:
2629 raise self.RemoteRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2631 self.write_forest_trust_info(remote_forest_info,
2632 tln=local_lsa_info.dns_domain.string,
2633 collisions=remote_forest_collision)
2635 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2636 self.outf.write("Validating outgoing trust...\n")
2637 try:
2638 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_netlogon_info.dc_unc,
2639 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2641 remote_lsa_info.dns_domain.string)
2642 except RuntimeError as error:
2643 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2645 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2646 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2648 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2649 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2650 local_trust_verify.trusted_dc_name,
2651 local_trust_verify.tc_connection_status[1],
2652 local_trust_verify.pdc_connection_status[1])
2653 else:
2654 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2655 local_trust_verify.trusted_dc_name,
2656 local_trust_verify.tc_connection_status[1],
2657 local_trust_verify.pdc_connection_status[1])
2659 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2660 raise CommandError(local_validation)
2661 else:
2662 self.outf.write("OK: %s\n" % local_validation)
2664 if remote_trust_info:
2665 if remote_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2666 self.outf.write("Validating incoming trust...\n")
2667 try:
2668 remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_netlogon_info.dc_unc,
2669 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2671 local_lsa_info.dns_domain.string)
2672 except RuntimeError as error:
2673 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2675 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2676 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2678 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2679 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2680 remote_trust_verify.trusted_dc_name,
2681 remote_trust_verify.tc_connection_status[1],
2682 remote_trust_verify.pdc_connection_status[1])
2683 else:
2684 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2685 remote_trust_verify.trusted_dc_name,
2686 remote_trust_verify.tc_connection_status[1],
2687 remote_trust_verify.pdc_connection_status[1])
2689 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
2690 raise CommandError(remote_validation)
2691 else:
2692 self.outf.write("OK: %s\n" % remote_validation)
2694 if remote_tdo_handle is not None:
2695 try:
2696 remote_lsa.Close(remote_tdo_handle)
2697 except RuntimeError as error:
2698 pass
2699 remote_tdo_handle = None
2700 if local_tdo_handle is not None:
2701 try:
2702 local_lsa.Close(local_tdo_handle)
2703 except RuntimeError as error:
2704 pass
2705 local_tdo_handle = None
2707 self.outf.write("Success.\n")
2708 return
2710 class cmd_domain_trust_delete(DomainTrustCommand):
2711 """Delete a domain trust."""
2713 synopsis = "%prog DOMAIN [options]"
2715 takes_optiongroups = {
2716 "sambaopts": options.SambaOptions,
2717 "versionopts": options.VersionOptions,
2718 "credopts": options.CredentialsOptions,
2719 "localdcopts": LocalDCCredentialsOptions,
2722 takes_options = [
2723 Option("--delete-location", type="choice", metavar="LOCATION",
2724 choices=["local", "both"],
2725 help="Where to delete the trusted domain object: 'local' or 'both'.",
2726 dest='delete_location',
2727 default="both"),
2730 takes_args = ["domain"]
2732 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2733 delete_location=None):
2735 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2736 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2737 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2739 if delete_location == "local":
2740 remote_policy_access = None
2741 else:
2742 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2743 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2744 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2746 local_server = self.setup_local_server(sambaopts, localdcopts)
2747 try:
2748 local_lsa = self.new_local_lsa_connection()
2749 except RuntimeError as error:
2750 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2752 try:
2753 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2754 except RuntimeError as error:
2755 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2757 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2758 local_lsa_info.name.string,
2759 local_lsa_info.dns_domain.string,
2760 local_lsa_info.sid))
2762 local_tdo_info = None
2763 local_tdo_handle = None
2764 remote_tdo_info = None
2765 remote_tdo_handle = None
2767 lsaString = lsa.String()
2768 try:
2769 lsaString.string = domain
2770 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2771 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2772 except NTSTATUSError as error:
2773 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2774 raise CommandError("Failed to find trust for domain '%s'" % domain)
2775 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2778 if remote_policy_access is not None:
2779 try:
2780 remote_server = self.setup_remote_server(credopts, domain)
2781 except RuntimeError as error:
2782 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2784 try:
2785 remote_lsa = self.new_remote_lsa_connection()
2786 except RuntimeError as error:
2787 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2789 try:
2790 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2791 except RuntimeError as error:
2792 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2794 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2795 remote_lsa_info.name.string,
2796 remote_lsa_info.dns_domain.string,
2797 remote_lsa_info.sid))
2799 if remote_lsa_info.sid != local_tdo_info.sid or \
2800 remote_lsa_info.name.string != local_tdo_info.netbios_name.string or \
2801 remote_lsa_info.dns_domain.string != local_tdo_info.domain_name.string:
2802 raise CommandError("LocalTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2803 local_tdo_info.netbios_name.string,
2804 local_tdo_info.domain_name.string,
2805 local_tdo_info.sid))
2807 try:
2808 lsaString.string = local_lsa_info.dns_domain.string
2809 remote_tdo_info = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2810 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2811 except NTSTATUSError as error:
2812 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2813 raise self.RemoteRuntimeError(self, error, "QueryTrustedDomainInfoByName(%s)" % (
2814 lsaString.string))
2815 pass
2817 if remote_tdo_info is not None:
2818 if local_lsa_info.sid != remote_tdo_info.sid or \
2819 local_lsa_info.name.string != remote_tdo_info.netbios_name.string or \
2820 local_lsa_info.dns_domain.string != remote_tdo_info.domain_name.string:
2821 raise CommandError("RemoteTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2822 remote_tdo_info.netbios_name.string,
2823 remote_tdo_info.domain_name.string,
2824 remote_tdo_info.sid))
2826 if local_tdo_info is not None:
2827 try:
2828 lsaString.string = local_tdo_info.domain_name.string
2829 local_tdo_handle = local_lsa.OpenTrustedDomainByName(local_policy,
2830 lsaString,
2831 security.SEC_STD_DELETE)
2832 except RuntimeError as error:
2833 raise self.LocalRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2834 lsaString.string))
2836 local_lsa.DeleteObject(local_tdo_handle)
2837 local_tdo_handle = None
2839 if remote_tdo_info is not None:
2840 try:
2841 lsaString.string = remote_tdo_info.domain_name.string
2842 remote_tdo_handle = remote_lsa.OpenTrustedDomainByName(remote_policy,
2843 lsaString,
2844 security.SEC_STD_DELETE)
2845 except RuntimeError as error:
2846 raise self.RemoteRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2847 lsaString.string))
2849 if remote_tdo_handle is not None:
2850 try:
2851 remote_lsa.DeleteObject(remote_tdo_handle)
2852 remote_tdo_handle = None
2853 self.outf.write("RemoteTDO deleted.\n")
2854 except RuntimeError as error:
2855 self.outf.write("%s\n" % self.RemoteRuntimeError(self, error, "DeleteObject() failed"))
2857 if local_tdo_handle is not None:
2858 try:
2859 local_lsa.DeleteObject(local_tdo_handle)
2860 local_tdo_handle = None
2861 self.outf.write("LocalTDO deleted.\n")
2862 except RuntimeError as error:
2863 self.outf.write("%s\n" % self.LocalRuntimeError(self, error, "DeleteObject() failed"))
2865 return
2867 class cmd_domain_trust_validate(DomainTrustCommand):
2868 """Validate a domain trust."""
2870 synopsis = "%prog DOMAIN [options]"
2872 takes_optiongroups = {
2873 "sambaopts": options.SambaOptions,
2874 "versionopts": options.VersionOptions,
2875 "credopts": options.CredentialsOptions,
2876 "localdcopts": LocalDCCredentialsOptions,
2879 takes_options = [
2880 Option("--validate-location", type="choice", metavar="LOCATION",
2881 choices=["local", "both"],
2882 help="Where to validate the trusted domain object: 'local' or 'both'.",
2883 dest='validate_location',
2884 default="both"),
2887 takes_args = ["domain"]
2889 def run(self, domain, sambaopts=None, versionopts=None, credopts=None, localdcopts=None,
2890 validate_location=None):
2892 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2894 local_server = self.setup_local_server(sambaopts, localdcopts)
2895 try:
2896 local_lsa = self.new_local_lsa_connection()
2897 except RuntimeError as error:
2898 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2900 try:
2901 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2902 except RuntimeError as error:
2903 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2905 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2906 local_lsa_info.name.string,
2907 local_lsa_info.dns_domain.string,
2908 local_lsa_info.sid))
2910 try:
2911 lsaString = lsa.String()
2912 lsaString.string = domain
2913 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2914 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2915 except NTSTATUSError as error:
2916 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2917 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2919 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
2921 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
2922 local_tdo_info.netbios_name.string,
2923 local_tdo_info.domain_name.string,
2924 local_tdo_info.sid))
2926 try:
2927 local_netlogon = self.new_local_netlogon_connection()
2928 except RuntimeError as error:
2929 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2931 try:
2932 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_server,
2933 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2935 local_tdo_info.domain_name.string)
2936 except RuntimeError as error:
2937 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2939 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2940 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2942 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2943 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2944 local_trust_verify.trusted_dc_name,
2945 local_trust_verify.tc_connection_status[1],
2946 local_trust_verify.pdc_connection_status[1])
2947 else:
2948 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2949 local_trust_verify.trusted_dc_name,
2950 local_trust_verify.tc_connection_status[1],
2951 local_trust_verify.pdc_connection_status[1])
2953 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2954 raise CommandError(local_validation)
2955 else:
2956 self.outf.write("OK: %s\n" % local_validation)
2958 try:
2959 server = local_trust_verify.trusted_dc_name.replace('\\', '')
2960 domain_and_server = "%s\\%s" % (local_tdo_info.domain_name.string, server)
2961 local_trust_rediscover = local_netlogon.netr_LogonControl2Ex(local_server,
2962 netlogon.NETLOGON_CONTROL_REDISCOVER,
2964 domain_and_server)
2965 except RuntimeError as error:
2966 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
2968 local_conn_status = self._uint32(local_trust_rediscover.tc_connection_status[0])
2969 local_rediscover = "LocalRediscover: DC[%s] CONNECTION[%s]" % (
2970 local_trust_rediscover.trusted_dc_name,
2971 local_trust_rediscover.tc_connection_status[1])
2973 if local_conn_status != werror.WERR_SUCCESS:
2974 raise CommandError(local_rediscover)
2975 else:
2976 self.outf.write("OK: %s\n" % local_rediscover)
2978 if validate_location != "local":
2979 try:
2980 remote_server = self.setup_remote_server(credopts, domain, require_pdc=False)
2981 except RuntimeError as error:
2982 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2984 try:
2985 remote_netlogon = self.new_remote_netlogon_connection()
2986 except RuntimeError as error:
2987 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2989 try:
2990 remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_server,
2991 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2993 local_lsa_info.dns_domain.string)
2994 except RuntimeError as error:
2995 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2997 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2998 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
3000 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
3001 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
3002 remote_trust_verify.trusted_dc_name,
3003 remote_trust_verify.tc_connection_status[1],
3004 remote_trust_verify.pdc_connection_status[1])
3005 else:
3006 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
3007 remote_trust_verify.trusted_dc_name,
3008 remote_trust_verify.tc_connection_status[1],
3009 remote_trust_verify.pdc_connection_status[1])
3011 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
3012 raise CommandError(remote_validation)
3013 else:
3014 self.outf.write("OK: %s\n" % remote_validation)
3016 try:
3017 server = remote_trust_verify.trusted_dc_name.replace('\\', '')
3018 domain_and_server = "%s\\%s" % (local_lsa_info.dns_domain.string, server)
3019 remote_trust_rediscover = remote_netlogon.netr_LogonControl2Ex(remote_server,
3020 netlogon.NETLOGON_CONTROL_REDISCOVER,
3022 domain_and_server)
3023 except RuntimeError as error:
3024 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
3026 remote_conn_status = self._uint32(remote_trust_rediscover.tc_connection_status[0])
3028 remote_rediscover = "RemoteRediscover: DC[%s] CONNECTION[%s]" % (
3029 remote_trust_rediscover.trusted_dc_name,
3030 remote_trust_rediscover.tc_connection_status[1])
3032 if remote_conn_status != werror.WERR_SUCCESS:
3033 raise CommandError(remote_rediscover)
3034 else:
3035 self.outf.write("OK: %s\n" % remote_rediscover)
3037 return
3039 class cmd_domain_trust_namespaces(DomainTrustCommand):
3040 """Manage forest trust namespaces."""
3042 synopsis = "%prog [DOMAIN] [options]"
3044 takes_optiongroups = {
3045 "sambaopts": options.SambaOptions,
3046 "versionopts": options.VersionOptions,
3047 "localdcopts": LocalDCCredentialsOptions,
3050 takes_options = [
3051 Option("--refresh", type="choice", metavar="check|store",
3052 choices=["check", "store", None],
3053 help="List and maybe store refreshed forest trust information: 'check' or 'store'.",
3054 dest='refresh',
3055 default=None),
3056 Option("--enable-all", action="store_true",
3057 help="Try to update disabled entries, not allowed with --refresh=check.",
3058 dest='enable_all',
3059 default=False),
3060 Option("--enable-tln", action="append", metavar='DNSDOMAIN',
3061 help="Enable a top level name entry. Can be specified multiple times.",
3062 dest='enable_tln',
3063 default=[]),
3064 Option("--disable-tln", action="append", metavar='DNSDOMAIN',
3065 help="Disable a top level name entry. Can be specified multiple times.",
3066 dest='disable_tln',
3067 default=[]),
3068 Option("--add-tln-ex", action="append", metavar='DNSDOMAIN',
3069 help="Add a top level exclusion entry. Can be specified multiple times.",
3070 dest='add_tln_ex',
3071 default=[]),
3072 Option("--delete-tln-ex", action="append", metavar='DNSDOMAIN',
3073 help="Delete a top level exclusion entry. Can be specified multiple times.",
3074 dest='delete_tln_ex',
3075 default=[]),
3076 Option("--enable-nb", action="append", metavar='NETBIOSDOMAIN',
3077 help="Enable a netbios name in a domain entry. Can be specified multiple times.",
3078 dest='enable_nb',
3079 default=[]),
3080 Option("--disable-nb", action="append", metavar='NETBIOSDOMAIN',
3081 help="Disable a netbios name in a domain entry. Can be specified multiple times.",
3082 dest='disable_nb',
3083 default=[]),
3084 Option("--enable-sid", action="append", metavar='DOMAINSID',
3085 help="Enable a SID in a domain entry. Can be specified multiple times.",
3086 dest='enable_sid_str',
3087 default=[]),
3088 Option("--disable-sid", action="append", metavar='DOMAINSID',
3089 help="Disable a SID in a domain entry. Can be specified multiple times.",
3090 dest='disable_sid_str',
3091 default=[]),
3092 Option("--add-upn-suffix", action="append", metavar='DNSDOMAIN',
3093 help="Add a new uPNSuffixes attribute for the local forest. Can be specified multiple times.",
3094 dest='add_upn',
3095 default=[]),
3096 Option("--delete-upn-suffix", action="append", metavar='DNSDOMAIN',
3097 help="Delete an existing uPNSuffixes attribute of the local forest. Can be specified multiple times.",
3098 dest='delete_upn',
3099 default=[]),
3100 Option("--add-spn-suffix", action="append", metavar='DNSDOMAIN',
3101 help="Add a new msDS-SPNSuffixes attribute for the local forest. Can be specified multiple times.",
3102 dest='add_spn',
3103 default=[]),
3104 Option("--delete-spn-suffix", action="append", metavar='DNSDOMAIN',
3105 help="Delete an existing msDS-SPNSuffixes attribute of the local forest. Can be specified multiple times.",
3106 dest='delete_spn',
3107 default=[]),
3110 takes_args = ["domain?"]
3112 def run(self, domain=None, sambaopts=None, localdcopts=None, versionopts=None,
3113 refresh=None, enable_all=False,
3114 enable_tln=[], disable_tln=[], add_tln_ex=[], delete_tln_ex=[],
3115 enable_sid_str=[], disable_sid_str=[], enable_nb=[], disable_nb=[],
3116 add_upn=[], delete_upn=[], add_spn=[], delete_spn=[]):
3118 require_update = False
3120 if domain is None:
3121 if refresh == "store":
3122 raise CommandError("--refresh=%s not allowed without DOMAIN" % refresh)
3124 if enable_all:
3125 raise CommandError("--enable-all not allowed without DOMAIN")
3127 if len(enable_tln) > 0:
3128 raise CommandError("--enable-tln not allowed without DOMAIN")
3129 if len(disable_tln) > 0:
3130 raise CommandError("--disable-tln not allowed without DOMAIN")
3132 if len(add_tln_ex) > 0:
3133 raise CommandError("--add-tln-ex not allowed without DOMAIN")
3134 if len(delete_tln_ex) > 0:
3135 raise CommandError("--delete-tln-ex not allowed without DOMAIN")
3137 if len(enable_nb) > 0:
3138 raise CommandError("--enable-nb not allowed without DOMAIN")
3139 if len(disable_nb) > 0:
3140 raise CommandError("--disable-nb not allowed without DOMAIN")
3142 if len(enable_sid_str) > 0:
3143 raise CommandError("--enable-sid not allowed without DOMAIN")
3144 if len(disable_sid_str) > 0:
3145 raise CommandError("--disable-sid not allowed without DOMAIN")
3147 if len(add_upn) > 0:
3148 for n in add_upn:
3149 if not n.startswith("*."):
3150 continue
3151 raise CommandError("value[%s] specified for --add-upn-suffix should not include with '*.'" % n)
3152 require_update = True
3153 if len(delete_upn) > 0:
3154 for n in delete_upn:
3155 if not n.startswith("*."):
3156 continue
3157 raise CommandError("value[%s] specified for --delete-upn-suffix should not include with '*.'" % n)
3158 require_update = True
3159 for a in add_upn:
3160 for d in delete_upn:
3161 if a.lower() != d.lower():
3162 continue
3163 raise CommandError("value[%s] specified for --add-upn-suffix and --delete-upn-suffix" % a)
3165 if len(add_spn) > 0:
3166 for n in add_spn:
3167 if not n.startswith("*."):
3168 continue
3169 raise CommandError("value[%s] specified for --add-spn-suffix should not include with '*.'" % n)
3170 require_update = True
3171 if len(delete_spn) > 0:
3172 for n in delete_spn:
3173 if not n.startswith("*."):
3174 continue
3175 raise CommandError("value[%s] specified for --delete-spn-suffix should not include with '*.'" % n)
3176 require_update = True
3177 for a in add_spn:
3178 for d in delete_spn:
3179 if a.lower() != d.lower():
3180 continue
3181 raise CommandError("value[%s] specified for --add-spn-suffix and --delete-spn-suffix" % a)
3182 else:
3183 if len(add_upn) > 0:
3184 raise CommandError("--add-upn-suffix not allowed together with DOMAIN")
3185 if len(delete_upn) > 0:
3186 raise CommandError("--delete-upn-suffix not allowed together with DOMAIN")
3187 if len(add_spn) > 0:
3188 raise CommandError("--add-spn-suffix not allowed together with DOMAIN")
3189 if len(delete_spn) > 0:
3190 raise CommandError("--delete-spn-suffix not allowed together with DOMAIN")
3192 if refresh is not None:
3193 if refresh == "store":
3194 require_update = True
3196 if enable_all and refresh != "store":
3197 raise CommandError("--enable-all not allowed together with --refresh=%s" % refresh)
3199 if len(enable_tln) > 0:
3200 raise CommandError("--enable-tln not allowed together with --refresh")
3201 if len(disable_tln) > 0:
3202 raise CommandError("--disable-tln not allowed together with --refresh")
3204 if len(add_tln_ex) > 0:
3205 raise CommandError("--add-tln-ex not allowed together with --refresh")
3206 if len(delete_tln_ex) > 0:
3207 raise CommandError("--delete-tln-ex not allowed together with --refresh")
3209 if len(enable_nb) > 0:
3210 raise CommandError("--enable-nb not allowed together with --refresh")
3211 if len(disable_nb) > 0:
3212 raise CommandError("--disable-nb not allowed together with --refresh")
3214 if len(enable_sid_str) > 0:
3215 raise CommandError("--enable-sid not allowed together with --refresh")
3216 if len(disable_sid_str) > 0:
3217 raise CommandError("--disable-sid not allowed together with --refresh")
3218 else:
3219 if enable_all:
3220 require_update = True
3222 if len(enable_tln) > 0:
3223 raise CommandError("--enable-tln not allowed together with --enable-all")
3225 if len(enable_nb) > 0:
3226 raise CommandError("--enable-nb not allowed together with --enable-all")
3228 if len(enable_sid_str) > 0:
3229 raise CommandError("--enable-sid not allowed together with --enable-all")
3231 if len(enable_tln) > 0:
3232 require_update = True
3233 if len(disable_tln) > 0:
3234 require_update = True
3235 for e in enable_tln:
3236 for d in disable_tln:
3237 if e.lower() != d.lower():
3238 continue
3239 raise CommandError("value[%s] specified for --enable-tln and --disable-tln" % e)
3241 if len(add_tln_ex) > 0:
3242 for n in add_tln_ex:
3243 if not n.startswith("*."):
3244 continue
3245 raise CommandError("value[%s] specified for --add-tln-ex should not include with '*.'" % n)
3246 require_update = True
3247 if len(delete_tln_ex) > 0:
3248 for n in delete_tln_ex:
3249 if not n.startswith("*."):
3250 continue
3251 raise CommandError("value[%s] specified for --delete-tln-ex should not include with '*.'" % n)
3252 require_update = True
3253 for a in add_tln_ex:
3254 for d in delete_tln_ex:
3255 if a.lower() != d.lower():
3256 continue
3257 raise CommandError("value[%s] specified for --add-tln-ex and --delete-tln-ex" % a)
3259 if len(enable_nb) > 0:
3260 require_update = True
3261 if len(disable_nb) > 0:
3262 require_update = True
3263 for e in enable_nb:
3264 for d in disable_nb:
3265 if e.upper() != d.upper():
3266 continue
3267 raise CommandError("value[%s] specified for --enable-nb and --disable-nb" % e)
3269 enable_sid = []
3270 for s in enable_sid_str:
3271 try:
3272 sid = security.dom_sid(s)
3273 except TypeError as error:
3274 raise CommandError("value[%s] specified for --enable-sid is not a valid SID" % s)
3275 enable_sid.append(sid)
3276 disable_sid = []
3277 for s in disable_sid_str:
3278 try:
3279 sid = security.dom_sid(s)
3280 except TypeError as error:
3281 raise CommandError("value[%s] specified for --disable-sid is not a valid SID" % s)
3282 disable_sid.append(sid)
3283 if len(enable_sid) > 0:
3284 require_update = True
3285 if len(disable_sid) > 0:
3286 require_update = True
3287 for e in enable_sid:
3288 for d in disable_sid:
3289 if e != d:
3290 continue
3291 raise CommandError("value[%s] specified for --enable-sid and --disable-sid" % e)
3293 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
3294 if require_update:
3295 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
3297 local_server = self.setup_local_server(sambaopts, localdcopts)
3298 try:
3299 local_lsa = self.new_local_lsa_connection()
3300 except RuntimeError as error:
3301 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
3303 try:
3304 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
3305 except RuntimeError as error:
3306 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
3308 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
3309 local_lsa_info.name.string,
3310 local_lsa_info.dns_domain.string,
3311 local_lsa_info.sid))
3313 if domain is None:
3314 try:
3315 local_netlogon = self.new_local_netlogon_connection()
3316 except RuntimeError as error:
3317 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3319 try:
3320 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3321 except RuntimeError as error:
3322 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3324 if local_netlogon_info.domain_name != local_netlogon_info.forest_name:
3325 raise CommandError("The local domain [%s] is not the forest root [%s]" % (
3326 local_netlogon_info.domain_name,
3327 local_netlogon_info.forest_name))
3329 try:
3330 # get all information about our own forest
3331 own_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3332 None, 0)
3333 except RuntimeError as error:
3334 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
3335 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3336 self.local_server))
3338 if self.check_runtime_error(error, werror.WERR_INVALID_FUNCTION):
3339 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3340 self.local_server))
3342 if self.check_runtime_error(error, werror.WERR_NERR_ACFNOTLOADED):
3343 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3344 self.local_server))
3346 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3348 self.outf.write("Own forest trust information...\n")
3349 self.write_forest_trust_info(own_forest_info,
3350 tln=local_lsa_info.dns_domain.string)
3352 try:
3353 local_samdb = self.new_local_ldap_connection()
3354 except RuntimeError as error:
3355 raise self.LocalRuntimeError(self, error, "failed to connect to SamDB")
3357 local_partitions_dn = "CN=Partitions,%s" % str(local_samdb.get_config_basedn())
3358 attrs = ['uPNSuffixes', 'msDS-SPNSuffixes']
3359 try:
3360 msgs = local_samdb.search(base=local_partitions_dn,
3361 scope=ldb.SCOPE_BASE,
3362 expression="(objectClass=crossRefContainer)",
3363 attrs=attrs)
3364 stored_msg = msgs[0]
3365 except ldb.LdbError as error:
3366 raise self.LocalLdbError(self, error, "failed to search partition dn")
3368 stored_upn_vals = []
3369 if 'uPNSuffixes' in stored_msg:
3370 stored_upn_vals.extend(stored_msg['uPNSuffixes'])
3372 stored_spn_vals = []
3373 if 'msDS-SPNSuffixes' in stored_msg:
3374 stored_spn_vals.extend(stored_msg['msDS-SPNSuffixes'])
3376 self.outf.write("Stored uPNSuffixes attributes[%d]:\n" % len(stored_upn_vals))
3377 for v in stored_upn_vals:
3378 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3379 self.outf.write("Stored msDS-SPNSuffixes attributes[%d]:\n" % len(stored_spn_vals))
3380 for v in stored_spn_vals:
3381 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3383 if not require_update:
3384 return
3386 replace_upn = False
3387 update_upn_vals = []
3388 update_upn_vals.extend(stored_upn_vals)
3390 replace_spn = False
3391 update_spn_vals = []
3392 update_spn_vals.extend(stored_spn_vals)
3394 for upn in add_upn:
3395 idx = None
3396 for i in xrange(0, len(update_upn_vals)):
3397 v = update_upn_vals[i]
3398 if v.lower() != upn.lower():
3399 continue
3400 idx = i
3401 break
3402 if idx is not None:
3403 raise CommandError("Entry already present for value[%s] specified for --add-upn-suffix" % upn)
3404 update_upn_vals.append(upn)
3405 replace_upn = True
3407 for upn in delete_upn:
3408 idx = None
3409 for i in xrange(0, len(update_upn_vals)):
3410 v = update_upn_vals[i]
3411 if v.lower() != upn.lower():
3412 continue
3413 idx = i
3414 break
3415 if idx is None:
3416 raise CommandError("Entry not found for value[%s] specified for --delete-upn-suffix" % upn)
3418 update_upn_vals.pop(idx)
3419 replace_upn = True
3421 for spn in add_spn:
3422 idx = None
3423 for i in xrange(0, len(update_spn_vals)):
3424 v = update_spn_vals[i]
3425 if v.lower() != spn.lower():
3426 continue
3427 idx = i
3428 break
3429 if idx is not None:
3430 raise CommandError("Entry already present for value[%s] specified for --add-spn-suffix" % spn)
3431 update_spn_vals.append(spn)
3432 replace_spn = True
3434 for spn in delete_spn:
3435 idx = None
3436 for i in xrange(0, len(update_spn_vals)):
3437 v = update_spn_vals[i]
3438 if v.lower() != spn.lower():
3439 continue
3440 idx = i
3441 break
3442 if idx is None:
3443 raise CommandError("Entry not found for value[%s] specified for --delete-spn-suffix" % spn)
3445 update_spn_vals.pop(idx)
3446 replace_spn = True
3448 self.outf.write("Update uPNSuffixes attributes[%d]:\n" % len(update_upn_vals))
3449 for v in update_upn_vals:
3450 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3451 self.outf.write("Update msDS-SPNSuffixes attributes[%d]:\n" % len(update_spn_vals))
3452 for v in update_spn_vals:
3453 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3455 update_msg = ldb.Message()
3456 update_msg.dn = stored_msg.dn
3458 if replace_upn:
3459 update_msg['uPNSuffixes'] = ldb.MessageElement(update_upn_vals,
3460 ldb.FLAG_MOD_REPLACE,
3461 'uPNSuffixes')
3462 if replace_spn:
3463 update_msg['msDS-SPNSuffixes'] = ldb.MessageElement(update_spn_vals,
3464 ldb.FLAG_MOD_REPLACE,
3465 'msDS-SPNSuffixes')
3466 try:
3467 local_samdb.modify(update_msg)
3468 except ldb.LdbError as error:
3469 raise self.LocalLdbError(self, error, "failed to update partition dn")
3471 try:
3472 stored_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3473 None, 0)
3474 except RuntimeError as error:
3475 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3477 self.outf.write("Stored forest trust information...\n")
3478 self.write_forest_trust_info(stored_forest_info,
3479 tln=local_lsa_info.dns_domain.string)
3480 return
3482 try:
3483 lsaString = lsa.String()
3484 lsaString.string = domain
3485 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
3486 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
3487 except NTSTATUSError as error:
3488 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
3489 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
3491 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
3493 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
3494 local_tdo_info.netbios_name.string,
3495 local_tdo_info.domain_name.string,
3496 local_tdo_info.sid))
3498 if not local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
3499 raise CommandError("trusted domain object for domain [%s] is not marked as FOREST_TRANSITIVE." % domain)
3501 if refresh is not None:
3502 try:
3503 local_netlogon = self.new_local_netlogon_connection()
3504 except RuntimeError as error:
3505 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3507 try:
3508 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3509 except RuntimeError as error:
3510 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3512 lsa_update_check = 1
3513 if refresh == "store":
3514 netlogon_update_tdo = netlogon.DS_GFTI_UPDATE_TDO
3515 if enable_all:
3516 lsa_update_check = 0
3517 else:
3518 netlogon_update_tdo = 0
3520 try:
3521 # get all information about the remote trust
3522 # this triggers netr_GetForestTrustInformation to the remote domain
3523 # and lsaRSetForestTrustInformation() locally, but new top level
3524 # names are disabled by default.
3525 fresh_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3526 local_tdo_info.domain_name.string,
3527 netlogon_update_tdo)
3528 except RuntimeError as error:
3529 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3531 try:
3532 fresh_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3533 local_tdo_info.domain_name,
3534 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3535 fresh_forest_info,
3536 lsa_update_check)
3537 except RuntimeError as error:
3538 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3540 self.outf.write("Fresh forest trust information...\n")
3541 self.write_forest_trust_info(fresh_forest_info,
3542 tln=local_tdo_info.domain_name.string,
3543 collisions=fresh_forest_collision)
3545 if refresh == "store":
3546 try:
3547 lsaString = lsa.String()
3548 lsaString.string = local_tdo_info.domain_name.string
3549 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3550 lsaString,
3551 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3552 except RuntimeError as error:
3553 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3555 self.outf.write("Stored forest trust information...\n")
3556 self.write_forest_trust_info(stored_forest_info,
3557 tln=local_tdo_info.domain_name.string)
3559 return
3562 # The none --refresh path
3565 try:
3566 lsaString = lsa.String()
3567 lsaString.string = local_tdo_info.domain_name.string
3568 local_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3569 lsaString,
3570 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3571 except RuntimeError as error:
3572 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3574 self.outf.write("Local forest trust information...\n")
3575 self.write_forest_trust_info(local_forest_info,
3576 tln=local_tdo_info.domain_name.string)
3578 if not require_update:
3579 return
3581 entries = []
3582 entries.extend(local_forest_info.entries)
3583 update_forest_info = lsa.ForestTrustInformation()
3584 update_forest_info.count = len(entries)
3585 update_forest_info.entries = entries
3587 if enable_all:
3588 for i in xrange(0, len(update_forest_info.entries)):
3589 r = update_forest_info.entries[i]
3590 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3591 continue
3592 if update_forest_info.entries[i].flags == 0:
3593 continue
3594 update_forest_info.entries[i].time = 0
3595 update_forest_info.entries[i].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3596 for i in xrange(0, len(update_forest_info.entries)):
3597 r = update_forest_info.entries[i]
3598 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3599 continue
3600 if update_forest_info.entries[i].flags == 0:
3601 continue
3602 update_forest_info.entries[i].time = 0
3603 update_forest_info.entries[i].flags &= ~lsa.LSA_NB_DISABLED_MASK
3604 update_forest_info.entries[i].flags &= ~lsa.LSA_SID_DISABLED_MASK
3606 for tln in enable_tln:
3607 idx = None
3608 for i in xrange(0, len(update_forest_info.entries)):
3609 r = update_forest_info.entries[i]
3610 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3611 continue
3612 if r.forest_trust_data.string.lower() != tln.lower():
3613 continue
3614 idx = i
3615 break
3616 if idx is None:
3617 raise CommandError("Entry not found for value[%s] specified for --enable-tln" % tln)
3618 if not update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_MASK:
3619 raise CommandError("Entry found for value[%s] specified for --enable-tln is already enabled" % tln)
3620 update_forest_info.entries[idx].time = 0
3621 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3623 for tln in disable_tln:
3624 idx = None
3625 for i in xrange(0, len(update_forest_info.entries)):
3626 r = update_forest_info.entries[i]
3627 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3628 continue
3629 if r.forest_trust_data.string.lower() != tln.lower():
3630 continue
3631 idx = i
3632 break
3633 if idx is None:
3634 raise CommandError("Entry not found for value[%s] specified for --disable-tln" % tln)
3635 if update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_ADMIN:
3636 raise CommandError("Entry found for value[%s] specified for --disable-tln is already disabled" % tln)
3637 update_forest_info.entries[idx].time = 0
3638 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3639 update_forest_info.entries[idx].flags |= lsa.LSA_TLN_DISABLED_ADMIN
3641 for tln_ex in add_tln_ex:
3642 idx = None
3643 for i in xrange(0, len(update_forest_info.entries)):
3644 r = update_forest_info.entries[i]
3645 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3646 continue
3647 if r.forest_trust_data.string.lower() != tln_ex.lower():
3648 continue
3649 idx = i
3650 break
3651 if idx is not None:
3652 raise CommandError("Entry already present for value[%s] specified for --add-tln-ex" % tln_ex)
3654 tln_dot = ".%s" % tln_ex.lower()
3655 idx = None
3656 for i in xrange(0, len(update_forest_info.entries)):
3657 r = update_forest_info.entries[i]
3658 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3659 continue
3660 r_dot = ".%s" % r.forest_trust_data.string.lower()
3661 if tln_dot == r_dot:
3662 raise CommandError("TLN entry present for value[%s] specified for --add-tln-ex" % tln_ex)
3663 if not tln_dot.endswith(r_dot):
3664 continue
3665 idx = i
3666 break
3668 if idx is None:
3669 raise CommandError("No TLN parent present for value[%s] specified for --add-tln-ex" % tln_ex)
3671 r = lsa.ForestTrustRecord()
3672 r.type = lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX
3673 r.flags = 0
3674 r.time = 0
3675 r.forest_trust_data.string = tln_ex
3677 entries = []
3678 entries.extend(update_forest_info.entries)
3679 entries.insert(idx + 1, r)
3680 update_forest_info.count = len(entries)
3681 update_forest_info.entries = entries
3683 for tln_ex in delete_tln_ex:
3684 idx = None
3685 for i in xrange(0, len(update_forest_info.entries)):
3686 r = update_forest_info.entries[i]
3687 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3688 continue
3689 if r.forest_trust_data.string.lower() != tln_ex.lower():
3690 continue
3691 idx = i
3692 break
3693 if idx is None:
3694 raise CommandError("Entry not found for value[%s] specified for --delete-tln-ex" % tln_ex)
3696 entries = []
3697 entries.extend(update_forest_info.entries)
3698 entries.pop(idx)
3699 update_forest_info.count = len(entries)
3700 update_forest_info.entries = entries
3702 for nb in enable_nb:
3703 idx = None
3704 for i in xrange(0, len(update_forest_info.entries)):
3705 r = update_forest_info.entries[i]
3706 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3707 continue
3708 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3709 continue
3710 idx = i
3711 break
3712 if idx is None:
3713 raise CommandError("Entry not found for value[%s] specified for --enable-nb" % nb)
3714 if not update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_MASK:
3715 raise CommandError("Entry found for value[%s] specified for --enable-nb is already enabled" % nb)
3716 update_forest_info.entries[idx].time = 0
3717 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3719 for nb in disable_nb:
3720 idx = None
3721 for i in xrange(0, len(update_forest_info.entries)):
3722 r = update_forest_info.entries[i]
3723 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3724 continue
3725 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3726 continue
3727 idx = i
3728 break
3729 if idx is None:
3730 raise CommandError("Entry not found for value[%s] specified for --delete-nb" % nb)
3731 if update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_ADMIN:
3732 raise CommandError("Entry found for value[%s] specified for --disable-nb is already disabled" % nb)
3733 update_forest_info.entries[idx].time = 0
3734 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3735 update_forest_info.entries[idx].flags |= lsa.LSA_NB_DISABLED_ADMIN
3737 for sid in enable_sid:
3738 idx = None
3739 for i in xrange(0, len(update_forest_info.entries)):
3740 r = update_forest_info.entries[i]
3741 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3742 continue
3743 if r.forest_trust_data.domain_sid != sid:
3744 continue
3745 idx = i
3746 break
3747 if idx is None:
3748 raise CommandError("Entry not found for value[%s] specified for --enable-sid" % sid)
3749 if not update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_MASK:
3750 raise CommandError("Entry found for value[%s] specified for --enable-sid is already enabled" % nb)
3751 update_forest_info.entries[idx].time = 0
3752 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3754 for sid in disable_sid:
3755 idx = None
3756 for i in xrange(0, len(update_forest_info.entries)):
3757 r = update_forest_info.entries[i]
3758 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3759 continue
3760 if r.forest_trust_data.domain_sid != sid:
3761 continue
3762 idx = i
3763 break
3764 if idx is None:
3765 raise CommandError("Entry not found for value[%s] specified for --delete-sid" % sid)
3766 if update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_ADMIN:
3767 raise CommandError("Entry found for value[%s] specified for --disable-sid is already disabled" % nb)
3768 update_forest_info.entries[idx].time = 0
3769 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3770 update_forest_info.entries[idx].flags |= lsa.LSA_SID_DISABLED_ADMIN
3772 try:
3773 update_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3774 local_tdo_info.domain_name,
3775 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3776 update_forest_info, 0)
3777 except RuntimeError as error:
3778 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3780 self.outf.write("Updated forest trust information...\n")
3781 self.write_forest_trust_info(update_forest_info,
3782 tln=local_tdo_info.domain_name.string,
3783 collisions=update_forest_collision)
3785 try:
3786 lsaString = lsa.String()
3787 lsaString.string = local_tdo_info.domain_name.string
3788 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3789 lsaString,
3790 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3791 except RuntimeError as error:
3792 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3794 self.outf.write("Stored forest trust information...\n")
3795 self.write_forest_trust_info(stored_forest_info,
3796 tln=local_tdo_info.domain_name.string)
3797 return
3799 class cmd_domain_tombstones_expunge(Command):
3800 """Expunge tombstones from the database.
3802 This command expunges tombstones from the database."""
3803 synopsis = "%prog NC [NC [...]] [options]"
3805 takes_options = [
3806 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3807 metavar="URL", dest="H"),
3808 Option("--current-time",
3809 help="The current time to evaluate the tombstone lifetime from, expressed as YYYY-MM-DD",
3810 type=str),
3811 Option("--tombstone-lifetime", help="Number of days a tombstone should be preserved for", type=int),
3814 takes_args = ["nc*"]
3816 takes_optiongroups = {
3817 "sambaopts": options.SambaOptions,
3818 "credopts": options.CredentialsOptions,
3819 "versionopts": options.VersionOptions,
3822 def run(self, *ncs, **kwargs):
3823 sambaopts = kwargs.get("sambaopts")
3824 credopts = kwargs.get("credopts")
3825 versionpts = kwargs.get("versionopts")
3826 H = kwargs.get("H")
3827 current_time_string = kwargs.get("current_time")
3828 tombstone_lifetime = kwargs.get("tombstone_lifetime")
3829 lp = sambaopts.get_loadparm()
3830 creds = credopts.get_credentials(lp)
3831 samdb = SamDB(url=H, session_info=system_session(),
3832 credentials=creds, lp=lp)
3834 if current_time_string is not None:
3835 current_time_obj = time.strptime(current_time_string, "%Y-%m-%d")
3836 current_time = long(time.mktime(current_time_obj))
3838 else:
3839 current_time = long(time.time())
3841 if len(ncs) == 0:
3842 res = samdb.search(expression="", base="", scope=ldb.SCOPE_BASE,
3843 attrs=["namingContexts"])
3845 ncs = []
3846 for nc in res[0]["namingContexts"]:
3847 ncs.append(str(nc))
3848 else:
3849 ncs = list(ncs)
3851 started_transaction = False
3852 try:
3853 samdb.transaction_start()
3854 started_transaction = True
3855 (removed_objects,
3856 removed_links) = samdb.garbage_collect_tombstones(ncs,
3857 current_time=current_time,
3858 tombstone_lifetime=tombstone_lifetime)
3860 except Exception as err:
3861 if started_transaction:
3862 samdb.transaction_cancel()
3863 raise CommandError("Failed to expunge / garbage collect tombstones", err)
3865 samdb.transaction_commit()
3867 self.outf.write("Removed %d objects and %d links successfully\n"
3868 % (removed_objects, removed_links))
3872 class cmd_domain_trust(SuperCommand):
3873 """Domain and forest trust management."""
3875 subcommands = {}
3876 subcommands["list"] = cmd_domain_trust_list()
3877 subcommands["show"] = cmd_domain_trust_show()
3878 subcommands["create"] = cmd_domain_trust_create()
3879 subcommands["delete"] = cmd_domain_trust_delete()
3880 subcommands["validate"] = cmd_domain_trust_validate()
3881 subcommands["namespaces"] = cmd_domain_trust_namespaces()
3883 class cmd_domain_tombstones(SuperCommand):
3884 """Domain tombstone and recycled object management."""
3886 subcommands = {}
3887 subcommands["expunge"] = cmd_domain_tombstones_expunge()
3889 class ldif_schema_update:
3890 """Helper class for applying LDIF schema updates"""
3892 def __init__(self):
3893 self.is_defunct = False
3894 self.unknown_oid = None
3895 self.dn = None
3896 self.ldif = ""
3898 def _ldap_schemaUpdateNow(self, samdb):
3899 ldif = """
3901 changetype: modify
3902 add: schemaUpdateNow
3903 schemaUpdateNow: 1
3905 samdb.modify_ldif(ldif)
3907 def can_ignore_failure(self, error):
3908 """Checks if we can safely ignore failure to apply an LDIF update"""
3909 (num, errstr) = error.args
3911 # Microsoft has marked objects as defunct that Samba doesn't know about
3912 if num == ldb.ERR_NO_SUCH_OBJECT and self.is_defunct:
3913 print("Defunct object %s doesn't exist, skipping" % self.dn)
3914 return True
3915 elif self.unknown_oid is not None:
3916 print("Skipping unknown OID %s for object %s" %(self.unknown_oid, self.dn))
3917 return True
3919 return False
3921 def apply(self, samdb):
3922 """Applies a single LDIF update to the schema"""
3924 try:
3925 try:
3926 samdb.modify_ldif(self.ldif, controls=['relax:0'])
3927 except ldb.LdbError as e:
3928 if e.args[0] == ldb.ERR_INVALID_ATTRIBUTE_SYNTAX:
3930 # REFRESH after a failed change
3932 # Otherwise the OID-to-attribute mapping in
3933 # _apply_updates_in_file() won't work, because it
3934 # can't lookup the new OID in the schema
3935 self._ldap_schemaUpdateNow(samdb)
3937 samdb.modify_ldif(self.ldif, controls=['relax:0'])
3938 else:
3939 raise
3940 except ldb.LdbError as e:
3941 if self.can_ignore_failure(e):
3942 return 0
3943 else:
3944 print("Exception: %s" % e)
3945 print("Encountered while trying to apply the following LDIF")
3946 print("----------------------------------------------------")
3947 print("%s" % self.ldif)
3949 raise
3951 return 1
3953 class cmd_domain_schema_upgrade(Command):
3954 """Domain schema upgrading"""
3956 synopsis = "%prog [options]"
3958 takes_optiongroups = {
3959 "sambaopts": options.SambaOptions,
3960 "versionopts": options.VersionOptions,
3961 "credopts": options.CredentialsOptions,
3964 takes_options = [
3965 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3966 metavar="URL", dest="H"),
3967 Option("--quiet", help="Be quiet", action="store_true"),
3968 Option("--verbose", help="Be verbose", action="store_true"),
3969 Option("--schema", type="choice", metavar="SCHEMA",
3970 choices=["2012", "2012_R2"],
3971 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
3972 default="2012_R2"),
3973 Option("--ldf-file", type=str, default=None,
3974 help="Just apply the schema updates in the adprep/.LDF file(s) specified"),
3975 Option("--base-dir", type=str, default=None,
3976 help="Location of ldf files Default is ${SETUPDIR}/adprep.")
3979 def _apply_updates_in_file(self, samdb, ldif_file):
3981 Applies a series of updates specified in an .LDIF file. The .LDIF file
3982 is based on the adprep Schema updates provided by Microsoft.
3984 count = 0
3985 ldif_op = ldif_schema_update()
3987 # parse the file line by line and work out each update operation to apply
3988 for line in ldif_file:
3990 line = line.rstrip()
3992 # the operations in the .LDIF file are separated by blank lines. If
3993 # we hit a blank line, try to apply the update we've parsed so far
3994 if line == '':
3996 # keep going if we haven't parsed anything yet
3997 if ldif_op.ldif == '':
3998 continue
4000 # Apply the individual change
4001 count += ldif_op.apply(samdb)
4003 # start storing the next operation from scratch again
4004 ldif_op = ldif_schema_update()
4005 continue
4007 # replace the placeholder domain name in the .ldif file with the real domain
4008 if line.upper().endswith('DC=X'):
4009 line = line[:-len('DC=X')] + str(samdb.get_default_basedn())
4010 elif line.upper().endswith('CN=X'):
4011 line = line[:-len('CN=X')] + str(samdb.get_default_basedn())
4013 values = line.split(':')
4015 if values[0].lower() == 'dn':
4016 ldif_op.dn = values[1].strip()
4018 # replace the Windows-specific operation with the Samba one
4019 if values[0].lower() == 'changetype':
4020 line = line.lower().replace(': ntdsschemaadd',
4021 ': add')
4022 line = line.lower().replace(': ntdsschemamodify',
4023 ': modify')
4025 if values[0].lower() in ['rdnattid', 'subclassof',
4026 'systemposssuperiors',
4027 'systemmaycontain',
4028 'systemauxiliaryclass']:
4029 _, value = values
4031 # The Microsoft updates contain some OIDs we don't recognize.
4032 # Query the DB to see if we can work out the OID this update is
4033 # referring to. If we find a match, then replace the OID with
4034 # the ldapDisplayname
4035 if '.' in value:
4036 res = samdb.search(base=samdb.get_schema_basedn(),
4037 expression="(|(attributeId=%s)(governsId=%s))" %
4038 (value, value),
4039 attrs=['ldapDisplayName'])
4041 if len(res) != 1:
4042 ldif_op.unknown_oid = value
4043 else:
4044 display_name = res[0]['ldapDisplayName'][0]
4045 line = line.replace(value, ' ' + display_name)
4047 # Microsoft has marked objects as defunct that Samba doesn't know about
4048 if values[0].lower() == 'isdefunct' and values[1].strip().lower() == 'true':
4049 ldif_op.is_defunct = True
4051 # Samba has added the showInAdvancedViewOnly attribute to all objects,
4052 # so rather than doing an add, we need to do a replace
4053 if values[0].lower() == 'add' and values[1].strip().lower() == 'showinadvancedviewonly':
4054 line = 'replace: showInAdvancedViewOnly'
4056 # Add the line to the current LDIF operation (including the newline
4057 # we stripped off at the start of the loop)
4058 ldif_op.ldif += line + '\n'
4060 return count
4063 def _apply_update(self, samdb, update_file, base_dir):
4064 """Wrapper function for parsing an LDIF file and applying the updates"""
4066 print("Applying %s updates..." % update_file)
4068 ldif_file = None
4069 try:
4070 ldif_file = open(os.path.join(base_dir, update_file))
4072 count = self._apply_updates_in_file(samdb, ldif_file)
4074 finally:
4075 if ldif_file:
4076 ldif_file.close()
4078 print("%u changes applied" % count)
4080 return count
4082 def run(self, **kwargs):
4083 from samba.ms_schema_markdown import read_ms_markdown
4084 from samba.schema import Schema
4086 updates_allowed_overriden = False
4087 sambaopts = kwargs.get("sambaopts")
4088 credopts = kwargs.get("credopts")
4089 versionpts = kwargs.get("versionopts")
4090 lp = sambaopts.get_loadparm()
4091 creds = credopts.get_credentials(lp)
4092 H = kwargs.get("H")
4093 target_schema = kwargs.get("schema")
4094 ldf_files = kwargs.get("ldf_file")
4095 base_dir = kwargs.get("base_dir")
4097 temp_folder = None
4099 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4101 # we're not going to get far if the config doesn't allow schema updates
4102 if lp.get("dsdb:schema update allowed") is None:
4103 lp.set("dsdb:schema update allowed", "yes")
4104 print("Temporarily overriding 'dsdb:schema update allowed' setting")
4105 updates_allowed_overriden = True
4107 own_dn = ldb.Dn(samdb, samdb.get_dsServiceName())
4108 master = get_fsmo_roleowner(samdb, str(samdb.get_schema_basedn()),
4109 'schema')
4110 if own_dn != master:
4111 raise CommandError("This server is not the schema master.")
4113 # if specific LDIF files were specified, just apply them
4114 if ldf_files:
4115 schema_updates = ldf_files.split(",")
4116 else:
4117 schema_updates = []
4119 # work out the version of the target schema we're upgrading to
4120 end = Schema.get_version(target_schema)
4122 # work out the version of the schema we're currently using
4123 res = samdb.search(base=samdb.get_schema_basedn(),
4124 scope=ldb.SCOPE_BASE, attrs=['objectVersion'])
4126 if len(res) != 1:
4127 raise CommandError('Could not determine current schema version')
4128 start = int(res[0]['objectVersion'][0]) + 1
4130 diff_dir = setup_path("adprep/WindowsServerDocs")
4131 if base_dir is None:
4132 # Read from the Schema-Updates.md file
4133 temp_folder = tempfile.mkdtemp()
4135 update_file = setup_path("adprep/WindowsServerDocs/Schema-Updates.md")
4137 try:
4138 read_ms_markdown(update_file, temp_folder)
4139 except Exception as e:
4140 print("Exception in markdown parsing: %s" % e)
4141 shutil.rmtree(temp_folder)
4142 raise CommandError('Failed to upgrade schema')
4144 base_dir = temp_folder
4146 for version in range(start, end + 1):
4147 update = 'Sch%d.ldf' % version
4148 schema_updates.append(update)
4150 # Apply patches if we parsed the Schema-Updates.md file
4151 diff = os.path.abspath(os.path.join(diff_dir, update + '.diff'))
4152 if temp_folder and os.path.exists(diff):
4153 try:
4154 p = subprocess.Popen(['patch', update, '-i', diff],
4155 stdout=subprocess.PIPE,
4156 stderr=subprocess.PIPE, cwd=temp_folder)
4157 except (OSError, IOError):
4158 shutil.rmtree(temp_folder)
4159 raise CommandError("Failed to upgrade schema. Check if 'patch' is installed.")
4161 stdout, stderr = p.communicate()
4163 if p.returncode:
4164 print("Exception in patch: %s\n%s" % (stdout, stderr))
4165 shutil.rmtree(temp_folder)
4166 raise CommandError('Failed to upgrade schema')
4168 print("Patched %s using %s" % (update, diff))
4170 if base_dir is None:
4171 base_dir = setup_path("adprep")
4173 samdb.transaction_start()
4174 count = 0
4175 error_encountered = False
4177 try:
4178 # Apply the schema updates needed to move to the new schema version
4179 for ldif_file in schema_updates:
4180 count += self._apply_update(samdb, ldif_file, base_dir)
4182 if count > 0:
4183 samdb.transaction_commit()
4184 print("Schema successfully updated")
4185 else:
4186 print("No changes applied to schema")
4187 samdb.transaction_cancel()
4188 except Exception as e:
4189 print("Exception: %s" % e)
4190 print("Error encountered, aborting schema upgrade")
4191 samdb.transaction_cancel()
4192 error_encountered = True
4194 if updates_allowed_overriden:
4195 lp.set("dsdb:schema update allowed", "no")
4197 if temp_folder:
4198 shutil.rmtree(temp_folder)
4200 if error_encountered:
4201 raise CommandError('Failed to upgrade schema')
4203 class cmd_domain_functional_prep(Command):
4204 """Domain functional level preparation"""
4206 synopsis = "%prog [options]"
4208 takes_optiongroups = {
4209 "sambaopts": options.SambaOptions,
4210 "versionopts": options.VersionOptions,
4211 "credopts": options.CredentialsOptions,
4214 takes_options = [
4215 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
4216 metavar="URL", dest="H"),
4217 Option("--quiet", help="Be quiet", action="store_true"),
4218 Option("--verbose", help="Be verbose", action="store_true"),
4219 Option("--function-level", type="choice", metavar="FUNCTION_LEVEL",
4220 choices=["2008_R2", "2012", "2012_R2"],
4221 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
4222 default="2012_R2"),
4223 Option("--forest-prep", action="store_true",
4224 help="Run the forest prep (by default, both the domain and forest prep are run)."),
4225 Option("--domain-prep", action="store_true",
4226 help="Run the domain prep (by default, both the domain and forest prep are run).")
4229 def run(self, **kwargs):
4230 updates_allowed_overriden = False
4231 sambaopts = kwargs.get("sambaopts")
4232 credopts = kwargs.get("credopts")
4233 versionpts = kwargs.get("versionopts")
4234 lp = sambaopts.get_loadparm()
4235 creds = credopts.get_credentials(lp)
4236 H = kwargs.get("H")
4237 target_level = string_version_to_constant[kwargs.get("function_level")]
4238 forest_prep = kwargs.get("forest_prep")
4239 domain_prep = kwargs.get("domain_prep")
4241 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4243 # we're not going to get far if the config doesn't allow schema updates
4244 if lp.get("dsdb:schema update allowed") is None:
4245 lp.set("dsdb:schema update allowed", "yes")
4246 print("Temporarily overriding 'dsdb:schema update allowed' setting")
4247 updates_allowed_overriden = True
4249 if forest_prep is None and domain_prep is None:
4250 forest_prep = True
4251 domain_prep = True
4253 own_dn = ldb.Dn(samdb, samdb.get_dsServiceName())
4254 if forest_prep:
4255 master = get_fsmo_roleowner(samdb, str(samdb.get_schema_basedn()),
4256 'schema')
4257 if own_dn != master:
4258 raise CommandError("This server is not the schema master.")
4260 if domain_prep:
4261 domain_dn = samdb.domain_dn()
4262 infrastructure_dn = "CN=Infrastructure," + domain_dn
4263 master = get_fsmo_roleowner(samdb, infrastructure_dn,
4264 'infrastructure')
4265 if own_dn != master:
4266 raise CommandError("This server is not the infrastructure master.")
4268 if forest_prep:
4269 samdb.transaction_start()
4270 error_encountered = False
4271 try:
4272 from samba.forest_update import ForestUpdate
4273 forest = ForestUpdate(samdb, fix=True)
4275 forest.check_updates_iterator([53, 79, 80, 81, 82, 83])
4276 forest.check_updates_functional_level(target_level,
4277 DS_DOMAIN_FUNCTION_2008_R2,
4278 update_revision=True)
4280 samdb.transaction_commit()
4281 except Exception as e:
4282 print("Exception: %s" % e)
4283 samdb.transaction_cancel()
4284 error_encountered = True
4286 if domain_prep:
4287 samdb.transaction_start()
4288 error_encountered = False
4289 try:
4290 from samba.domain_update import DomainUpdate
4292 domain = DomainUpdate(samdb, fix=True)
4293 domain.check_updates_functional_level(target_level,
4294 DS_DOMAIN_FUNCTION_2008,
4295 update_revision=True)
4297 samdb.transaction_commit()
4298 except Exception as e:
4299 print("Exception: %s" % e)
4300 samdb.transaction_cancel()
4301 error_encountered = True
4303 if updates_allowed_overriden:
4304 lp.set("dsdb:schema update allowed", "no")
4306 if error_encountered:
4307 raise CommandError('Failed to perform functional prep')
4309 class cmd_domain(SuperCommand):
4310 """Domain management."""
4312 subcommands = {}
4313 subcommands["demote"] = cmd_domain_demote()
4314 if cmd_domain_export_keytab is not None:
4315 subcommands["exportkeytab"] = cmd_domain_export_keytab()
4316 subcommands["info"] = cmd_domain_info()
4317 subcommands["provision"] = cmd_domain_provision()
4318 subcommands["join"] = cmd_domain_join()
4319 subcommands["dcpromo"] = cmd_domain_dcpromo()
4320 subcommands["level"] = cmd_domain_level()
4321 subcommands["passwordsettings"] = cmd_domain_passwordsettings()
4322 subcommands["classicupgrade"] = cmd_domain_classicupgrade()
4323 subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()
4324 subcommands["trust"] = cmd_domain_trust()
4325 subcommands["tombstones"] = cmd_domain_tombstones()
4326 subcommands["schemaupgrade"] = cmd_domain_schema_upgrade()
4327 subcommands["functionalprep"] = cmd_domain_functional_prep()