PEP8: fix E203: whitespace before ':'
[Samba.git] / python / samba / netcmd / domain.py
blobbcf5d4ddb0833b2836a618fae87dd08ea3742537
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 from __future__ import print_function
26 from __future__ import division
27 import samba.getopt as options
28 import ldb
29 import string
30 import os
31 import sys
32 import ctypes
33 import random
34 import tempfile
35 import logging
36 import subprocess
37 import time
38 import shutil
39 from samba import ntstatus
40 from samba import NTSTATUSError
41 from samba import werror
42 from getpass import getpass
43 from samba.net import Net, LIBNET_JOIN_AUTOMATIC
44 import samba.ntacls
45 from samba.join import join_RODC, join_DC, join_subdomain
46 from samba.auth import system_session
47 from samba.samdb import SamDB, get_default_backend_store
48 from samba.ndr import ndr_unpack, ndr_pack, ndr_print
49 from samba.dcerpc import drsuapi
50 from samba.dcerpc import drsblobs
51 from samba.dcerpc import lsa
52 from samba.dcerpc import netlogon
53 from samba.dcerpc import security
54 from samba.dcerpc import nbt
55 from samba.dcerpc import misc
56 from samba.dcerpc.samr import DOMAIN_PASSWORD_COMPLEX, DOMAIN_PASSWORD_STORE_CLEARTEXT
57 from samba.netcmd import (
58 Command,
59 CommandError,
60 SuperCommand,
61 Option
63 from samba.netcmd.fsmo import get_fsmo_roleowner
64 from samba.netcmd.common import netcmd_get_domain_infos_via_cldap
65 from samba.samba3 import Samba3
66 from samba.samba3 import param as s3param
67 from samba.upgrade import upgrade_from_samba3
68 from samba.drs_utils import (
69 sendDsReplicaSync, drsuapi_connect, drsException,
70 sendRemoveDsServer)
71 from samba import remove_dc, arcfour_encrypt, string_to_byte_array
73 from samba.dsdb import (
74 DS_DOMAIN_FUNCTION_2000,
75 DS_DOMAIN_FUNCTION_2003,
76 DS_DOMAIN_FUNCTION_2003_MIXED,
77 DS_DOMAIN_FUNCTION_2008,
78 DS_DOMAIN_FUNCTION_2008_R2,
79 DS_DOMAIN_FUNCTION_2012,
80 DS_DOMAIN_FUNCTION_2012_R2,
81 DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL,
82 DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL,
83 UF_WORKSTATION_TRUST_ACCOUNT,
84 UF_SERVER_TRUST_ACCOUNT,
85 UF_TRUSTED_FOR_DELEGATION,
86 UF_PARTIAL_SECRETS_ACCOUNT
89 from samba.provision import (
90 provision,
91 ProvisioningError,
92 DEFAULT_MIN_PWD_LENGTH,
93 setup_path
96 from samba.provision.common import (
97 FILL_FULL,
98 FILL_NT4SYNC,
99 FILL_DRS
102 from samba.netcmd.pso import cmd_domain_passwordsettings_pso
103 from samba.netcmd.domain_backup import cmd_domain_backup
105 string_version_to_constant = {
106 "2008_R2": DS_DOMAIN_FUNCTION_2008_R2,
107 "2012": DS_DOMAIN_FUNCTION_2012,
108 "2012_R2": DS_DOMAIN_FUNCTION_2012_R2,
111 common_provision_join_options = [
112 Option("--machinepass", type="string", metavar="PASSWORD",
113 help="choose machine password (otherwise random)"),
114 Option("--plaintext-secrets", action="store_true",
115 help="Store secret/sensitive values as plain text on disk" +
116 "(default is to encrypt secret/ensitive values)"),
117 Option("--backend-store", type="choice", metavar="BACKENDSTORE",
118 choices=["tdb", "mdb"],
119 help="Specify the database backend to be used "
120 "(default is %s)" % get_default_backend_store()),
121 Option("--targetdir", metavar="DIR",
122 help="Set target directory (where to store provision)", type=str),
123 Option("-q", "--quiet", help="Be quiet", action="store_true"),
126 common_join_options = [
127 Option("--server", help="DC to join", type=str),
128 Option("--site", help="site to join", type=str),
129 Option("--domain-critical-only",
130 help="only replicate critical domain objects",
131 action="store_true"),
132 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
133 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
134 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
135 "BIND9_DLZ uses samba4 AD to store zone information, "
136 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
137 default="SAMBA_INTERNAL"),
138 Option("-v", "--verbose", help="Be verbose", action="store_true")
141 common_ntvfs_options = [
142 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
143 action="store_true")
146 def get_testparm_var(testparm, smbconf, varname):
147 errfile = open(os.devnull, 'w')
148 p = subprocess.Popen([testparm, '-s', '-l',
149 '--parameter-name=%s' % varname, smbconf],
150 stdout=subprocess.PIPE, stderr=errfile)
151 (out,err) = p.communicate()
152 errfile.close()
153 lines = out.split('\n')
154 if lines:
155 return lines[0].strip()
156 return ""
158 try:
159 import samba.dckeytab
160 except ImportError:
161 cmd_domain_export_keytab = None
162 else:
163 class cmd_domain_export_keytab(Command):
164 """Dump Kerberos keys of the domain into a keytab."""
166 synopsis = "%prog <keytab> [options]"
168 takes_optiongroups = {
169 "sambaopts": options.SambaOptions,
170 "credopts": options.CredentialsOptions,
171 "versionopts": options.VersionOptions,
174 takes_options = [
175 Option("--principal", help="extract only this principal", type=str),
178 takes_args = ["keytab"]
180 def run(self, keytab, credopts=None, sambaopts=None, versionopts=None, principal=None):
181 lp = sambaopts.get_loadparm()
182 net = Net(None, lp)
183 net.export_keytab(keytab=keytab, principal=principal)
186 class cmd_domain_info(Command):
187 """Print basic info about a domain and the DC passed as parameter."""
189 synopsis = "%prog <ip_address> [options]"
191 takes_options = [
194 takes_optiongroups = {
195 "sambaopts": options.SambaOptions,
196 "credopts": options.CredentialsOptions,
197 "versionopts": options.VersionOptions,
200 takes_args = ["address"]
202 def run(self, address, credopts=None, sambaopts=None, versionopts=None):
203 lp = sambaopts.get_loadparm()
204 try:
205 res = netcmd_get_domain_infos_via_cldap(lp, None, address)
206 except RuntimeError:
207 raise CommandError("Invalid IP address '" + address + "'!")
208 self.outf.write("Forest : %s\n" % res.forest)
209 self.outf.write("Domain : %s\n" % res.dns_domain)
210 self.outf.write("Netbios domain : %s\n" % res.domain_name)
211 self.outf.write("DC name : %s\n" % res.pdc_dns_name)
212 self.outf.write("DC netbios name : %s\n" % res.pdc_name)
213 self.outf.write("Server site : %s\n" % res.server_site)
214 self.outf.write("Client site : %s\n" % res.client_site)
217 class cmd_domain_provision(Command):
218 """Provision a domain."""
220 synopsis = "%prog [options]"
222 takes_optiongroups = {
223 "sambaopts": options.SambaOptions,
224 "versionopts": options.VersionOptions,
227 takes_options = [
228 Option("--interactive", help="Ask for names", action="store_true"),
229 Option("--domain", type="string", metavar="DOMAIN",
230 help="NetBIOS domain name to use"),
231 Option("--domain-guid", type="string", metavar="GUID",
232 help="set domainguid (otherwise random)"),
233 Option("--domain-sid", type="string", metavar="SID",
234 help="set domainsid (otherwise random)"),
235 Option("--ntds-guid", type="string", metavar="GUID",
236 help="set NTDS object GUID (otherwise random)"),
237 Option("--invocationid", type="string", metavar="GUID",
238 help="set invocationid (otherwise random)"),
239 Option("--host-name", type="string", metavar="HOSTNAME",
240 help="set hostname"),
241 Option("--host-ip", type="string", metavar="IPADDRESS",
242 help="set IPv4 ipaddress"),
243 Option("--host-ip6", type="string", metavar="IP6ADDRESS",
244 help="set IPv6 ipaddress"),
245 Option("--site", type="string", metavar="SITENAME",
246 help="set site name"),
247 Option("--adminpass", type="string", metavar="PASSWORD",
248 help="choose admin password (otherwise random)"),
249 Option("--krbtgtpass", type="string", metavar="PASSWORD",
250 help="choose krbtgt password (otherwise random)"),
251 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
252 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
253 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
254 "BIND9_FLATFILE uses bind9 text database to store zone information, "
255 "BIND9_DLZ uses samba4 AD to store zone information, "
256 "NONE skips the DNS setup entirely (not recommended)",
257 default="SAMBA_INTERNAL"),
258 Option("--dnspass", type="string", metavar="PASSWORD",
259 help="choose dns password (otherwise random)"),
260 Option("--root", type="string", metavar="USERNAME",
261 help="choose 'root' unix username"),
262 Option("--nobody", type="string", metavar="USERNAME",
263 help="choose 'nobody' user"),
264 Option("--users", type="string", metavar="GROUPNAME",
265 help="choose 'users' group"),
266 Option("--blank", action="store_true",
267 help="do not add users or groups, just the structure"),
268 Option("--server-role", type="choice", metavar="ROLE",
269 choices=["domain controller", "dc", "member server", "member", "standalone"],
270 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
271 default="domain controller"),
272 Option("--function-level", type="choice", metavar="FOR-FUN-LEVEL",
273 choices=["2000", "2003", "2008", "2008_R2"],
274 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2008_R2 Native.",
275 default="2008_R2"),
276 Option("--base-schema", type="choice", metavar="BASE-SCHEMA",
277 choices=["2008_R2", "2008_R2_old", "2012", "2012_R2"],
278 help="The base schema files to use. Default is (Windows) 2008_R2.",
279 default="2008_R2"),
280 Option("--next-rid", type="int", metavar="NEXTRID", default=1000,
281 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
282 Option("--partitions-only",
283 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"),
284 Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
287 openldap_options = [
288 Option("--ldapadminpass", type="string", metavar="PASSWORD",
289 help="choose password to set between Samba and its LDAP backend (otherwise random)"),
290 Option("--ldap-backend-type", type="choice", metavar="LDAP-BACKEND-TYPE",
291 help="Test initialisation support for unsupported LDAP backend type (fedora-ds or openldap) DO NOT USE",
292 choices=["fedora-ds", "openldap"]),
293 Option("--ol-mmr-urls", type="string", metavar="LDAPSERVER",
294 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\""),
295 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",
296 action="store_true"),
297 Option("--slapd-path", type="string", metavar="SLAPD-PATH",
298 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."),
299 Option("--ldap-backend-extra-port", type="int", metavar="LDAP-BACKEND-EXTRA-PORT", help="Additional TCP port for LDAP backend server (to use for replication)"),
300 Option("--ldap-backend-forced-uri", type="string", metavar="LDAP-BACKEND-FORCED-URI",
301 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"),
302 Option("--ldap-backend-nosync", help="Configure LDAP backend not to call fsync() (for performance in test environments)", action="store_true"),
305 ntvfs_options = [
306 Option("--use-xattrs", type="choice", choices=["yes","no","auto"],
307 metavar="[yes|no|auto]",
308 help="Define if we should use the native fs capabilities or a tdb file for "
309 "storing attributes likes ntacl when --use-ntvfs is set. "
310 "auto tries to make an inteligent guess based on the user rights and system capabilities",
311 default="auto")
314 takes_options.extend(common_provision_join_options)
316 if os.getenv('TEST_LDAP', "no") == "yes":
317 takes_options.extend(openldap_options)
319 if samba.is_ntvfs_fileserver_built():
320 takes_options.extend(common_ntvfs_options)
321 takes_options.extend(ntvfs_options)
323 takes_args = []
325 def run(self, sambaopts=None, versionopts=None,
326 interactive=None,
327 domain=None,
328 domain_guid=None,
329 domain_sid=None,
330 ntds_guid=None,
331 invocationid=None,
332 host_name=None,
333 host_ip=None,
334 host_ip6=None,
335 adminpass=None,
336 site=None,
337 krbtgtpass=None,
338 machinepass=None,
339 dns_backend=None,
340 dns_forwarder=None,
341 dnspass=None,
342 ldapadminpass=None,
343 root=None,
344 nobody=None,
345 users=None,
346 quiet=None,
347 blank=None,
348 ldap_backend_type=None,
349 server_role=None,
350 function_level=None,
351 next_rid=None,
352 partitions_only=None,
353 targetdir=None,
354 ol_mmr_urls=None,
355 use_xattrs="auto",
356 slapd_path=None,
357 use_ntvfs=False,
358 use_rfc2307=None,
359 ldap_backend_nosync=None,
360 ldap_backend_extra_port=None,
361 ldap_backend_forced_uri=None,
362 ldap_dryrun_mode=None,
363 base_schema=None,
364 plaintext_secrets=False,
365 backend_store=None):
367 self.logger = self.get_logger("provision")
368 if quiet:
369 self.logger.setLevel(logging.WARNING)
370 else:
371 self.logger.setLevel(logging.INFO)
373 lp = sambaopts.get_loadparm()
374 smbconf = lp.configfile
376 if dns_forwarder is not None:
377 suggested_forwarder = dns_forwarder
378 else:
379 suggested_forwarder = self._get_nameserver_ip()
380 if suggested_forwarder is None:
381 suggested_forwarder = "none"
383 if len(self.raw_argv) == 1:
384 interactive = True
386 if interactive:
387 from getpass import getpass
388 import socket
390 def ask(prompt, default=None):
391 if default is not None:
392 print("%s [%s]: " % (prompt, default), end=' ')
393 else:
394 print("%s: " % (prompt,), end=' ')
395 return sys.stdin.readline().rstrip("\n") or default
397 try:
398 default = socket.getfqdn().split(".", 1)[1].upper()
399 except IndexError:
400 default = None
401 realm = ask("Realm", default)
402 if realm in (None, ""):
403 raise CommandError("No realm set!")
405 try:
406 default = realm.split(".")[0]
407 except IndexError:
408 default = None
409 domain = ask("Domain", default)
410 if domain is None:
411 raise CommandError("No domain set!")
413 server_role = ask("Server Role (dc, member, standalone)", "dc")
415 dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
416 if dns_backend in (None, ''):
417 raise CommandError("No DNS backend set!")
419 if dns_backend == "SAMBA_INTERNAL":
420 dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
421 if dns_forwarder.lower() in (None, 'none'):
422 suggested_forwarder = None
423 dns_forwarder = None
425 while True:
426 adminpassplain = getpass("Administrator password: ")
427 issue = self._adminpass_issue(adminpassplain)
428 if issue:
429 self.errf.write("%s.\n" % issue)
430 else:
431 adminpassverify = getpass("Retype password: ")
432 if not adminpassplain == adminpassverify:
433 self.errf.write("Sorry, passwords do not match.\n")
434 else:
435 adminpass = adminpassplain
436 break
438 else:
439 realm = sambaopts._lp.get('realm')
440 if realm is None:
441 raise CommandError("No realm set!")
442 if domain is None:
443 raise CommandError("No domain set!")
445 if adminpass:
446 issue = self._adminpass_issue(adminpass)
447 if issue:
448 raise CommandError(issue)
449 else:
450 self.logger.info("Administrator password will be set randomly!")
452 if function_level == "2000":
453 dom_for_fun_level = DS_DOMAIN_FUNCTION_2000
454 elif function_level == "2003":
455 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
456 elif function_level == "2008":
457 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
458 elif function_level == "2008_R2":
459 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
461 if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
462 dns_forwarder = suggested_forwarder
464 samdb_fill = FILL_FULL
465 if blank:
466 samdb_fill = FILL_NT4SYNC
467 elif partitions_only:
468 samdb_fill = FILL_DRS
470 if targetdir is not None:
471 if not os.path.isdir(targetdir):
472 os.mkdir(targetdir)
474 eadb = True
476 if use_xattrs == "yes":
477 eadb = False
478 elif use_xattrs == "auto" and use_ntvfs == False:
479 eadb = False
480 elif use_ntvfs == False:
481 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
482 "Please re-run with --use-xattrs omitted.")
483 elif use_xattrs == "auto" and not lp.get("posix:eadb"):
484 if targetdir:
485 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
486 else:
487 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
488 try:
489 try:
490 samba.ntacls.setntacl(lp, file.name,
491 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
492 eadb = False
493 except Exception:
494 self.logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. ")
495 finally:
496 file.close()
498 if eadb:
499 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.")
500 if ldap_backend_type == "existing":
501 if ldap_backend_forced_uri is not None:
502 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)
503 else:
504 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")
505 else:
506 if ldap_backend_forced_uri is not None:
507 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")
509 if domain_sid is not None:
510 domain_sid = security.dom_sid(domain_sid)
512 session = system_session()
513 if backend_store is None:
514 backend_store = get_default_backend_store()
515 try:
516 result = provision(self.logger,
517 session, smbconf=smbconf, targetdir=targetdir,
518 samdb_fill=samdb_fill, realm=realm, domain=domain,
519 domainguid=domain_guid, domainsid=domain_sid,
520 hostname=host_name,
521 hostip=host_ip, hostip6=host_ip6,
522 sitename=site, ntdsguid=ntds_guid,
523 invocationid=invocationid, adminpass=adminpass,
524 krbtgtpass=krbtgtpass, machinepass=machinepass,
525 dns_backend=dns_backend, dns_forwarder=dns_forwarder,
526 dnspass=dnspass, root=root, nobody=nobody,
527 users=users,
528 serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
529 backend_type=ldap_backend_type,
530 ldapadminpass=ldapadminpass, ol_mmr_urls=ol_mmr_urls, slapd_path=slapd_path,
531 useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
532 use_rfc2307=use_rfc2307, skip_sysvolacl=False,
533 ldap_backend_extra_port=ldap_backend_extra_port,
534 ldap_backend_forced_uri=ldap_backend_forced_uri,
535 nosync=ldap_backend_nosync, ldap_dryrun_mode=ldap_dryrun_mode,
536 base_schema=base_schema,
537 plaintext_secrets=plaintext_secrets,
538 backend_store=backend_store)
540 except ProvisioningError as e:
541 raise CommandError("Provision failed", e)
543 result.report_logger(self.logger)
545 def _get_nameserver_ip(self):
546 """Grab the nameserver IP address from /etc/resolv.conf."""
547 from os import path
548 RESOLV_CONF="/etc/resolv.conf"
550 if not path.isfile(RESOLV_CONF):
551 self.logger.warning("Failed to locate %s" % RESOLV_CONF)
552 return None
554 handle = None
555 try:
556 handle = open(RESOLV_CONF, 'r')
557 for line in handle:
558 if not line.startswith('nameserver'):
559 continue
560 # we want the last non-space continuous string of the line
561 return line.strip().split()[-1]
562 finally:
563 if handle is not None:
564 handle.close()
566 self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
568 def _adminpass_issue(self, adminpass):
569 """Returns error string for a bad administrator password,
570 or None if acceptable"""
572 if len(adminpass.decode('utf-8')) < DEFAULT_MIN_PWD_LENGTH:
573 return "Administrator password does not meet the default minimum" \
574 " password length requirement (%d characters)" \
575 % DEFAULT_MIN_PWD_LENGTH
576 elif not samba.check_password_quality(adminpass):
577 return "Administrator password does not meet the default" \
578 " quality standards"
579 else:
580 return None
583 class cmd_domain_dcpromo(Command):
584 """Promote an existing domain member or NT4 PDC to an AD DC."""
586 synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
588 takes_optiongroups = {
589 "sambaopts": options.SambaOptions,
590 "versionopts": options.VersionOptions,
591 "credopts": options.CredentialsOptions,
594 takes_options = []
595 takes_options.extend(common_join_options)
597 takes_options.extend(common_provision_join_options)
599 if samba.is_ntvfs_fileserver_built():
600 takes_options.extend(common_ntvfs_options)
603 takes_args = ["domain", "role?"]
605 def run(self, domain, role=None, sambaopts=None, credopts=None,
606 versionopts=None, server=None, site=None, targetdir=None,
607 domain_critical_only=False, parent_domain=None, machinepass=None,
608 use_ntvfs=False, dns_backend=None,
609 quiet=False, verbose=False, plaintext_secrets=False,
610 backend_store=None):
611 lp = sambaopts.get_loadparm()
612 creds = credopts.get_credentials(lp)
613 net = Net(creds, lp, server=credopts.ipaddress)
615 logger = self.get_logger()
616 if verbose:
617 logger.setLevel(logging.DEBUG)
618 elif quiet:
619 logger.setLevel(logging.WARNING)
620 else:
621 logger.setLevel(logging.INFO)
623 netbios_name = lp.get("netbios name")
625 if role is not None:
626 role = role.upper()
628 if role == "DC":
629 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
630 site=site, netbios_name=netbios_name, targetdir=targetdir,
631 domain_critical_only=domain_critical_only,
632 machinepass=machinepass, use_ntvfs=use_ntvfs,
633 dns_backend=dns_backend,
634 promote_existing=True, plaintext_secrets=plaintext_secrets,
635 backend_store=backend_store)
636 elif role == "RODC":
637 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
638 site=site, netbios_name=netbios_name, targetdir=targetdir,
639 domain_critical_only=domain_critical_only,
640 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
641 promote_existing=True, plaintext_secrets=plaintext_secrets,
642 backend_store=backend_store)
643 else:
644 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
647 class cmd_domain_join(Command):
648 """Join domain as either member or backup domain controller."""
650 synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
652 takes_optiongroups = {
653 "sambaopts": options.SambaOptions,
654 "versionopts": options.VersionOptions,
655 "credopts": options.CredentialsOptions,
658 takes_options = [
659 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
660 Option("--adminpass", type="string", metavar="PASSWORD",
661 help="choose adminstrator password when joining as a subdomain (otherwise random)"),
664 ntvfs_options = [
665 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
666 action="store_true")
668 takes_options.extend(common_join_options)
669 takes_options.extend(common_provision_join_options)
671 if samba.is_ntvfs_fileserver_built():
672 takes_options.extend(ntvfs_options)
674 takes_args = ["domain", "role?"]
676 def run(self, domain, role=None, sambaopts=None, credopts=None,
677 versionopts=None, server=None, site=None, targetdir=None,
678 domain_critical_only=False, parent_domain=None, machinepass=None,
679 use_ntvfs=False, dns_backend=None, adminpass=None,
680 quiet=False, verbose=False,
681 plaintext_secrets=False,
682 backend_store=None):
683 lp = sambaopts.get_loadparm()
684 creds = credopts.get_credentials(lp)
685 net = Net(creds, lp, server=credopts.ipaddress)
687 if site is None:
688 site = "Default-First-Site-Name"
690 logger = self.get_logger()
691 if verbose:
692 logger.setLevel(logging.DEBUG)
693 elif quiet:
694 logger.setLevel(logging.WARNING)
695 else:
696 logger.setLevel(logging.INFO)
698 netbios_name = lp.get("netbios name")
700 if role is not None:
701 role = role.upper()
703 if role is None or role == "MEMBER":
704 (join_password, sid, domain_name) = net.join_member(
705 domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
706 machinepass=machinepass)
708 self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
709 elif role == "DC":
710 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
711 site=site, netbios_name=netbios_name, targetdir=targetdir,
712 domain_critical_only=domain_critical_only,
713 machinepass=machinepass, use_ntvfs=use_ntvfs,
714 dns_backend=dns_backend,
715 plaintext_secrets=plaintext_secrets,
716 backend_store=backend_store)
717 elif role == "RODC":
718 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
719 site=site, netbios_name=netbios_name, targetdir=targetdir,
720 domain_critical_only=domain_critical_only,
721 machinepass=machinepass, use_ntvfs=use_ntvfs,
722 dns_backend=dns_backend,
723 plaintext_secrets=plaintext_secrets,
724 backend_store=backend_store)
725 elif role == "SUBDOMAIN":
726 if not adminpass:
727 logger.info("Administrator password will be set randomly!")
729 netbios_domain = lp.get("workgroup")
730 if parent_domain is None:
731 parent_domain = ".".join(domain.split(".")[1:])
732 join_subdomain(logger=logger, server=server, creds=creds, lp=lp, dnsdomain=domain,
733 parent_domain=parent_domain, site=site,
734 netbios_name=netbios_name, netbios_domain=netbios_domain,
735 targetdir=targetdir, machinepass=machinepass,
736 use_ntvfs=use_ntvfs, dns_backend=dns_backend,
737 adminpass=adminpass,
738 plaintext_secrets=plaintext_secrets,
739 backend_store=backend_store)
740 else:
741 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role)
744 class cmd_domain_demote(Command):
745 """Demote ourselves from the role of Domain Controller."""
747 synopsis = "%prog [options]"
749 takes_options = [
750 Option("--server", help="writable DC to write demotion changes on", type=str),
751 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
752 metavar="URL", dest="H"),
753 Option("--remove-other-dead-server", help="Dead DC (name or NTDS GUID) "
754 "to remove ALL references to (rather than this DC)", type=str),
755 Option("-q", "--quiet", help="Be quiet", action="store_true"),
756 Option("-v", "--verbose", help="Be verbose", action="store_true"),
759 takes_optiongroups = {
760 "sambaopts": options.SambaOptions,
761 "credopts": options.CredentialsOptions,
762 "versionopts": options.VersionOptions,
765 def run(self, sambaopts=None, credopts=None,
766 versionopts=None, server=None,
767 remove_other_dead_server=None, H=None,
768 verbose=False, quiet=False):
769 lp = sambaopts.get_loadparm()
770 creds = credopts.get_credentials(lp)
771 net = Net(creds, lp, server=credopts.ipaddress)
773 logger = self.get_logger()
774 if verbose:
775 logger.setLevel(logging.DEBUG)
776 elif quiet:
777 logger.setLevel(logging.WARNING)
778 else:
779 logger.setLevel(logging.INFO)
781 if remove_other_dead_server is not None:
782 if server is not None:
783 samdb = SamDB(url="ldap://%s" % server,
784 session_info=system_session(),
785 credentials=creds, lp=lp)
786 else:
787 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
788 try:
789 remove_dc.remove_dc(samdb, logger, remove_other_dead_server)
790 except remove_dc.DemoteException as err:
791 raise CommandError("Demote failed: %s" % err)
792 return
794 netbios_name = lp.get("netbios name")
795 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
796 if not server:
797 res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
798 if (len(res) == 0):
799 raise CommandError("Unable to search for servers")
801 if (len(res) == 1):
802 raise CommandError("You are the last server in the domain")
804 server = None
805 for e in res:
806 if str(e["name"]).lower() != netbios_name.lower():
807 server = e["dnsHostName"]
808 break
810 ntds_guid = samdb.get_ntds_GUID()
811 msg = samdb.search(base=str(samdb.get_config_basedn()),
812 scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
813 attrs=['options'])
814 if len(msg) == 0 or "options" not in msg[0]:
815 raise CommandError("Failed to find options on %s" % ntds_guid)
817 ntds_dn = msg[0].dn
818 dsa_options = int(str(msg[0]['options']))
820 res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
821 controls=["search_options:1:2"])
823 if len(res) != 0:
824 raise CommandError("Current DC is still the owner of %d role(s), "
825 "use the role command to transfer roles to "
826 "another DC" %
827 len(res))
829 self.errf.write("Using %s as partner server for the demotion\n" %
830 server)
831 (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
833 self.errf.write("Deactivating inbound replication\n")
835 nmsg = ldb.Message()
836 nmsg.dn = msg[0].dn
838 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
839 dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
840 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
841 samdb.modify(nmsg)
844 self.errf.write("Asking partner server %s to synchronize from us\n"
845 % server)
846 for part in (samdb.get_schema_basedn(),
847 samdb.get_config_basedn(),
848 samdb.get_root_basedn()):
849 nc = drsuapi.DsReplicaObjectIdentifier()
850 nc.dn = str(part)
852 req1 = drsuapi.DsReplicaSyncRequest1()
853 req1.naming_context = nc;
854 req1.options = drsuapi.DRSUAPI_DRS_WRIT_REP
855 req1.source_dsa_guid = misc.GUID(ntds_guid)
857 try:
858 drsuapiBind.DsReplicaSync(drsuapi_handle, 1, req1)
859 except RuntimeError as e1:
860 (werr, string) = e1.args
861 if werr == werror.WERR_DS_DRA_NO_REPLICA:
862 pass
863 else:
864 self.errf.write(
865 "Error while replicating out last local changes from '%s' for demotion, "
866 "re-enabling inbound replication\n" % part)
867 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
868 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
869 samdb.modify(nmsg)
870 raise CommandError("Error while sending a DsReplicaSync for partition '%s'" % str(part), string)
871 try:
872 remote_samdb = SamDB(url="ldap://%s" % server,
873 session_info=system_session(),
874 credentials=creds, lp=lp)
876 self.errf.write("Changing userControl and container\n")
877 res = remote_samdb.search(base=str(remote_samdb.domain_dn()),
878 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
879 netbios_name.upper(),
880 attrs=["userAccountControl"])
881 dc_dn = res[0].dn
882 uac = int(str(res[0]["userAccountControl"]))
884 except Exception as e:
885 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
886 self.errf.write(
887 "Error while demoting, re-enabling inbound replication\n")
888 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
889 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
890 samdb.modify(nmsg)
891 raise CommandError("Error while changing account control", e)
893 if (len(res) != 1):
894 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
895 self.errf.write(
896 "Error while demoting, re-enabling inbound replication")
897 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
898 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
899 samdb.modify(nmsg)
900 raise CommandError("Unable to find object with samaccountName = %s$"
901 " in the remote dc" % netbios_name.upper())
903 olduac = uac
905 uac &= ~(UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION|UF_PARTIAL_SECRETS_ACCOUNT)
906 uac |= UF_WORKSTATION_TRUST_ACCOUNT
908 msg = ldb.Message()
909 msg.dn = dc_dn
911 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
912 ldb.FLAG_MOD_REPLACE,
913 "userAccountControl")
914 try:
915 remote_samdb.modify(msg)
916 except Exception as e:
917 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
918 self.errf.write(
919 "Error while demoting, re-enabling inbound replication")
920 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
921 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
922 samdb.modify(nmsg)
924 raise CommandError("Error while changing account control", e)
926 parent = msg.dn.parent()
927 dc_name = res[0].dn.get_rdn_value()
928 rdn = "CN=%s" % dc_name
930 # Let's move to the Computer container
931 i = 0
932 newrdn = str(rdn)
934 computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.domain_dn()))
935 res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
937 if (len(res) != 0):
938 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
939 scope=ldb.SCOPE_ONELEVEL)
940 while(len(res) != 0 and i < 100):
941 i = i + 1
942 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
943 scope=ldb.SCOPE_ONELEVEL)
945 if i == 100:
946 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
947 self.errf.write(
948 "Error while demoting, re-enabling inbound replication\n")
949 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
950 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
951 samdb.modify(nmsg)
953 msg = ldb.Message()
954 msg.dn = dc_dn
956 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
957 ldb.FLAG_MOD_REPLACE,
958 "userAccountControl")
960 remote_samdb.modify(msg)
962 raise CommandError("Unable to find a slot for renaming %s,"
963 " all names from %s-1 to %s-%d seemed used" %
964 (str(dc_dn), rdn, rdn, i - 9))
966 newrdn = "%s-%d" % (rdn, i)
968 try:
969 newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
970 remote_samdb.rename(dc_dn, newdn)
971 except Exception as e:
972 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
973 self.errf.write(
974 "Error while demoting, re-enabling inbound replication\n")
975 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
976 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
977 samdb.modify(nmsg)
979 msg = ldb.Message()
980 msg.dn = dc_dn
982 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
983 ldb.FLAG_MOD_REPLACE,
984 "userAccountControl")
986 remote_samdb.modify(msg)
987 raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
990 server_dsa_dn = samdb.get_serverName()
991 domain = remote_samdb.get_root_basedn()
993 try:
994 req1 = drsuapi.DsRemoveDSServerRequest1()
995 req1.server_dn = str(server_dsa_dn)
996 req1.domain_dn = str(domain)
997 req1.commit = 1
999 drsuapiBind.DsRemoveDSServer(drsuapi_handle, 1, req1)
1000 except RuntimeError as e3:
1001 (werr, string) = e3.args
1002 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
1003 self.errf.write(
1004 "Error while demoting, re-enabling inbound replication\n")
1005 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
1006 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
1007 samdb.modify(nmsg)
1009 msg = ldb.Message()
1010 msg.dn = newdn
1012 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
1013 ldb.FLAG_MOD_REPLACE,
1014 "userAccountControl")
1015 remote_samdb.modify(msg)
1016 remote_samdb.rename(newdn, dc_dn)
1017 if werr == werror.WERR_DS_DRA_NO_REPLICA:
1018 raise CommandError("The DC %s is not present on (already "
1019 "removed from) the remote server: %s" %
1020 (server_dsa_dn, e3))
1021 else:
1022 raise CommandError("Error while sending a removeDsServer "
1023 "of %s: %s" %
1024 (server_dsa_dn, e3))
1026 remove_dc.remove_sysvol_references(remote_samdb, logger, dc_name)
1028 # These are objects under the computer account that should be deleted
1029 for s in ("CN=Enterprise,CN=NTFRS Subscriptions",
1030 "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
1031 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
1032 "CN=NTFRS Subscriptions"):
1033 try:
1034 remote_samdb.delete(ldb.Dn(remote_samdb,
1035 "%s,%s" % (s, str(newdn))))
1036 except ldb.LdbError as l:
1037 pass
1039 # get dns host name for target server to demote, remove dns references
1040 remove_dc.remove_dns_references(remote_samdb, logger, samdb.host_dns_name(),
1041 ignore_no_name=True)
1043 self.errf.write("Demote successful\n")
1046 class cmd_domain_level(Command):
1047 """Raise domain and forest function levels."""
1049 synopsis = "%prog (show|raise <options>) [options]"
1051 takes_optiongroups = {
1052 "sambaopts": options.SambaOptions,
1053 "credopts": options.CredentialsOptions,
1054 "versionopts": options.VersionOptions,
1057 takes_options = [
1058 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1059 metavar="URL", dest="H"),
1060 Option("-q", "--quiet", help="Be quiet", action="store_true"), # unused
1061 Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1062 help="The forest function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)"),
1063 Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1064 help="The domain function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)")
1067 takes_args = ["subcommand"]
1069 def run(self, subcommand, H=None, forest_level=None, domain_level=None,
1070 quiet=False, credopts=None, sambaopts=None, versionopts=None):
1071 lp = sambaopts.get_loadparm()
1072 creds = credopts.get_credentials(lp, fallback_machine=True)
1074 samdb = SamDB(url=H, session_info=system_session(),
1075 credentials=creds, lp=lp)
1077 domain_dn = samdb.domain_dn()
1079 res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
1080 scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
1081 assert len(res_forest) == 1
1083 res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1084 attrs=["msDS-Behavior-Version", "nTMixedDomain"])
1085 assert len(res_domain) == 1
1087 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
1088 scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
1089 attrs=["msDS-Behavior-Version"])
1090 assert len(res_dc_s) >= 1
1092 # default values, since "msDS-Behavior-Version" does not exist on Windows 2000 AD
1093 level_forest = DS_DOMAIN_FUNCTION_2000
1094 level_domain = DS_DOMAIN_FUNCTION_2000
1096 if "msDS-Behavior-Version" in res_forest[0]:
1097 level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
1098 if "msDS-Behavior-Version" in res_domain[0]:
1099 level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
1100 level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
1102 min_level_dc = None
1103 for msg in res_dc_s:
1104 if "msDS-Behavior-Version" in msg:
1105 if min_level_dc is None or int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
1106 min_level_dc = int(msg["msDS-Behavior-Version"][0])
1107 else:
1108 min_level_dc = DS_DOMAIN_FUNCTION_2000
1109 # well, this is the least
1110 break
1112 if level_forest < DS_DOMAIN_FUNCTION_2000 or level_domain < DS_DOMAIN_FUNCTION_2000:
1113 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
1114 if min_level_dc < DS_DOMAIN_FUNCTION_2000:
1115 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
1116 if level_forest > level_domain:
1117 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
1118 if level_domain > min_level_dc:
1119 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
1121 if subcommand == "show":
1122 self.message("Domain and forest function level for domain '%s'" % domain_dn)
1123 if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1124 self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1125 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1126 self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1127 if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1128 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)!")
1130 self.message("")
1132 if level_forest == DS_DOMAIN_FUNCTION_2000:
1133 outstr = "2000"
1134 elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
1135 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1136 elif level_forest == DS_DOMAIN_FUNCTION_2003:
1137 outstr = "2003"
1138 elif level_forest == DS_DOMAIN_FUNCTION_2008:
1139 outstr = "2008"
1140 elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
1141 outstr = "2008 R2"
1142 elif level_forest == DS_DOMAIN_FUNCTION_2012:
1143 outstr = "2012"
1144 elif level_forest == DS_DOMAIN_FUNCTION_2012_R2:
1145 outstr = "2012 R2"
1146 else:
1147 outstr = "higher than 2012 R2"
1148 self.message("Forest function level: (Windows) " + outstr)
1150 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1151 outstr = "2000 mixed (NT4 DC support)"
1152 elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
1153 outstr = "2000"
1154 elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
1155 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1156 elif level_domain == DS_DOMAIN_FUNCTION_2003:
1157 outstr = "2003"
1158 elif level_domain == DS_DOMAIN_FUNCTION_2008:
1159 outstr = "2008"
1160 elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
1161 outstr = "2008 R2"
1162 elif level_domain == DS_DOMAIN_FUNCTION_2012:
1163 outstr = "2012"
1164 elif level_domain == DS_DOMAIN_FUNCTION_2012_R2:
1165 outstr = "2012 R2"
1166 else:
1167 outstr = "higher than 2012 R2"
1168 self.message("Domain function level: (Windows) " + outstr)
1170 if min_level_dc == DS_DOMAIN_FUNCTION_2000:
1171 outstr = "2000"
1172 elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
1173 outstr = "2003"
1174 elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
1175 outstr = "2008"
1176 elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
1177 outstr = "2008 R2"
1178 elif min_level_dc == DS_DOMAIN_FUNCTION_2012:
1179 outstr = "2012"
1180 elif min_level_dc == DS_DOMAIN_FUNCTION_2012_R2:
1181 outstr = "2012 R2"
1182 else:
1183 outstr = "higher than 2012 R2"
1184 self.message("Lowest function level of a DC: (Windows) " + outstr)
1186 elif subcommand == "raise":
1187 msgs = []
1189 if domain_level is not None:
1190 if domain_level == "2003":
1191 new_level_domain = DS_DOMAIN_FUNCTION_2003
1192 elif domain_level == "2008":
1193 new_level_domain = DS_DOMAIN_FUNCTION_2008
1194 elif domain_level == "2008_R2":
1195 new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
1196 elif domain_level == "2012":
1197 new_level_domain = DS_DOMAIN_FUNCTION_2012
1198 elif domain_level == "2012_R2":
1199 new_level_domain = DS_DOMAIN_FUNCTION_2012_R2
1201 if new_level_domain <= level_domain and level_domain_mixed == 0:
1202 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
1203 if new_level_domain > min_level_dc:
1204 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
1206 # Deactivate mixed/interim domain support
1207 if level_domain_mixed != 0:
1208 # Directly on the base DN
1209 m = ldb.Message()
1210 m.dn = ldb.Dn(samdb, domain_dn)
1211 m["nTMixedDomain"] = ldb.MessageElement("0",
1212 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1213 samdb.modify(m)
1214 # Under partitions
1215 m = ldb.Message()
1216 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
1217 m["nTMixedDomain"] = ldb.MessageElement("0",
1218 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1219 try:
1220 samdb.modify(m)
1221 except ldb.LdbError as e:
1222 (enum, emsg) = e.args
1223 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1224 raise
1226 # Directly on the base DN
1227 m = ldb.Message()
1228 m.dn = ldb.Dn(samdb, domain_dn)
1229 m["msDS-Behavior-Version"]= ldb.MessageElement(
1230 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1231 "msDS-Behavior-Version")
1232 samdb.modify(m)
1233 # Under partitions
1234 m = ldb.Message()
1235 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
1236 + ",CN=Partitions,%s" % samdb.get_config_basedn())
1237 m["msDS-Behavior-Version"]= ldb.MessageElement(
1238 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1239 "msDS-Behavior-Version")
1240 try:
1241 samdb.modify(m)
1242 except ldb.LdbError as e2:
1243 (enum, emsg) = e2.args
1244 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1245 raise
1247 level_domain = new_level_domain
1248 msgs.append("Domain function level changed!")
1250 if forest_level is not None:
1251 if forest_level == "2003":
1252 new_level_forest = DS_DOMAIN_FUNCTION_2003
1253 elif forest_level == "2008":
1254 new_level_forest = DS_DOMAIN_FUNCTION_2008
1255 elif forest_level == "2008_R2":
1256 new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
1257 elif forest_level == "2012":
1258 new_level_forest = DS_DOMAIN_FUNCTION_2012
1259 elif forest_level == "2012_R2":
1260 new_level_forest = DS_DOMAIN_FUNCTION_2012_R2
1262 if new_level_forest <= level_forest:
1263 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1264 if new_level_forest > level_domain:
1265 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1267 m = ldb.Message()
1268 m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
1269 m["msDS-Behavior-Version"]= ldb.MessageElement(
1270 str(new_level_forest), ldb.FLAG_MOD_REPLACE,
1271 "msDS-Behavior-Version")
1272 samdb.modify(m)
1273 msgs.append("Forest function level changed!")
1274 msgs.append("All changes applied successfully!")
1275 self.message("\n".join(msgs))
1276 else:
1277 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
1279 class cmd_domain_passwordsettings_show(Command):
1280 """Display current password settings for the domain."""
1282 synopsis = "%prog [options]"
1284 takes_optiongroups = {
1285 "sambaopts": options.SambaOptions,
1286 "versionopts": options.VersionOptions,
1287 "credopts": options.CredentialsOptions,
1290 takes_options = [
1291 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1292 metavar="URL", dest="H"),
1295 def run(self, H=None, credopts=None, sambaopts=None, versionopts=None):
1296 lp = sambaopts.get_loadparm()
1297 creds = credopts.get_credentials(lp)
1299 samdb = SamDB(url=H, session_info=system_session(),
1300 credentials=creds, lp=lp)
1302 domain_dn = samdb.domain_dn()
1303 res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1304 attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1305 "minPwdAge", "maxPwdAge", "lockoutDuration", "lockoutThreshold",
1306 "lockOutObservationWindow"])
1307 assert(len(res) == 1)
1308 try:
1309 pwd_props = int(res[0]["pwdProperties"][0])
1310 pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
1311 cur_min_pwd_len = int(res[0]["minPwdLength"][0])
1312 # ticks -> days
1313 cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1314 if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000:
1315 cur_max_pwd_age = 0
1316 else:
1317 cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1318 cur_account_lockout_threshold = int(res[0]["lockoutThreshold"][0])
1319 # ticks -> mins
1320 if int(res[0]["lockoutDuration"][0]) == -0x8000000000000000:
1321 cur_account_lockout_duration = 0
1322 else:
1323 cur_account_lockout_duration = abs(int(res[0]["lockoutDuration"][0])) / (1e7 * 60)
1324 cur_reset_account_lockout_after = abs(int(res[0]["lockOutObservationWindow"][0])) / (1e7 * 60)
1325 except Exception as e:
1326 raise CommandError("Could not retrieve password properties!", e)
1328 self.message("Password informations for domain '%s'" % domain_dn)
1329 self.message("")
1330 if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
1331 self.message("Password complexity: on")
1332 else:
1333 self.message("Password complexity: off")
1334 if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
1335 self.message("Store plaintext passwords: on")
1336 else:
1337 self.message("Store plaintext passwords: off")
1338 self.message("Password history length: %d" % pwd_hist_len)
1339 self.message("Minimum password length: %d" % cur_min_pwd_len)
1340 self.message("Minimum password age (days): %d" % cur_min_pwd_age)
1341 self.message("Maximum password age (days): %d" % cur_max_pwd_age)
1342 self.message("Account lockout duration (mins): %d" % cur_account_lockout_duration)
1343 self.message("Account lockout threshold (attempts): %d" % cur_account_lockout_threshold)
1344 self.message("Reset account lockout after (mins): %d" % cur_reset_account_lockout_after)
1346 class cmd_domain_passwordsettings_set(Command):
1347 """Set password settings.
1349 Password complexity, password lockout policy, history length,
1350 minimum password length, the minimum and maximum password age) on
1351 a Samba AD DC server.
1353 Use against a Windows DC is possible, but group policy will override it.
1356 synopsis = "%prog <options> [options]"
1358 takes_optiongroups = {
1359 "sambaopts": options.SambaOptions,
1360 "versionopts": options.VersionOptions,
1361 "credopts": options.CredentialsOptions,
1364 takes_options = [
1365 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1366 metavar="URL", dest="H"),
1367 Option("-q", "--quiet", help="Be quiet", action="store_true"), # unused
1368 Option("--complexity", type="choice", choices=["on","off","default"],
1369 help="The password complexity (on | off | default). Default is 'on'"),
1370 Option("--store-plaintext", type="choice", choices=["on","off","default"],
1371 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1372 Option("--history-length",
1373 help="The password history length (<integer> | default). Default is 24.", type=str),
1374 Option("--min-pwd-length",
1375 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1376 Option("--min-pwd-age",
1377 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1378 Option("--max-pwd-age",
1379 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1380 Option("--account-lockout-duration",
1381 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),
1382 Option("--account-lockout-threshold",
1383 help="The number of bad password attempts allowed before locking out the account (<integer> | default). Default is 0 (never lock out).", type=str),
1384 Option("--reset-account-lockout-after",
1385 help="After this time is elapsed, the recorded number of attempts restarts from zero (<integer> | default). Default is 30.", type=str),
1388 def run(self, H=None, min_pwd_age=None, max_pwd_age=None,
1389 quiet=False, complexity=None, store_plaintext=None, history_length=None,
1390 min_pwd_length=None, account_lockout_duration=None, account_lockout_threshold=None,
1391 reset_account_lockout_after=None, credopts=None, sambaopts=None,
1392 versionopts=None):
1393 lp = sambaopts.get_loadparm()
1394 creds = credopts.get_credentials(lp)
1396 samdb = SamDB(url=H, session_info=system_session(),
1397 credentials=creds, lp=lp)
1399 domain_dn = samdb.domain_dn()
1400 msgs = []
1401 m = ldb.Message()
1402 m.dn = ldb.Dn(samdb, domain_dn)
1403 pwd_props = int(samdb.get_pwdProperties())
1405 if complexity is not None:
1406 if complexity == "on" or complexity == "default":
1407 pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
1408 msgs.append("Password complexity activated!")
1409 elif complexity == "off":
1410 pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
1411 msgs.append("Password complexity deactivated!")
1413 if store_plaintext is not None:
1414 if store_plaintext == "on" or store_plaintext == "default":
1415 pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1416 msgs.append("Plaintext password storage for changed passwords activated!")
1417 elif store_plaintext == "off":
1418 pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
1419 msgs.append("Plaintext password storage for changed passwords deactivated!")
1421 if complexity is not None or store_plaintext is not None:
1422 m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
1423 ldb.FLAG_MOD_REPLACE, "pwdProperties")
1425 if history_length is not None:
1426 if history_length == "default":
1427 pwd_hist_len = 24
1428 else:
1429 pwd_hist_len = int(history_length)
1431 if pwd_hist_len < 0 or pwd_hist_len > 24:
1432 raise CommandError("Password history length must be in the range of 0 to 24!")
1434 m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
1435 ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
1436 msgs.append("Password history length changed!")
1438 if min_pwd_length is not None:
1439 if min_pwd_length == "default":
1440 min_pwd_len = 7
1441 else:
1442 min_pwd_len = int(min_pwd_length)
1444 if min_pwd_len < 0 or min_pwd_len > 14:
1445 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1447 m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
1448 ldb.FLAG_MOD_REPLACE, "minPwdLength")
1449 msgs.append("Minimum password length changed!")
1451 if min_pwd_age is not None:
1452 if min_pwd_age == "default":
1453 min_pwd_age = 1
1454 else:
1455 min_pwd_age = int(min_pwd_age)
1457 if min_pwd_age < 0 or min_pwd_age > 998:
1458 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1460 # days -> ticks
1461 min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
1463 m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
1464 ldb.FLAG_MOD_REPLACE, "minPwdAge")
1465 msgs.append("Minimum password age changed!")
1467 if max_pwd_age is not None:
1468 if max_pwd_age == "default":
1469 max_pwd_age = 43
1470 else:
1471 max_pwd_age = int(max_pwd_age)
1473 if max_pwd_age < 0 or max_pwd_age > 999:
1474 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1476 # days -> ticks
1477 if max_pwd_age == 0:
1478 max_pwd_age_ticks = -0x8000000000000000
1479 else:
1480 max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
1482 m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
1483 ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1484 msgs.append("Maximum password age changed!")
1486 if account_lockout_duration is not None:
1487 if account_lockout_duration == "default":
1488 account_lockout_duration = 30
1489 else:
1490 account_lockout_duration = int(account_lockout_duration)
1492 if account_lockout_duration < 0 or account_lockout_duration > 99999:
1493 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1495 # minutes -> ticks
1496 if account_lockout_duration == 0:
1497 account_lockout_duration_ticks = -0x8000000000000000
1498 else:
1499 account_lockout_duration_ticks = -int(account_lockout_duration * (60 * 1e7))
1501 m["lockoutDuration"] = ldb.MessageElement(str(account_lockout_duration_ticks),
1502 ldb.FLAG_MOD_REPLACE, "lockoutDuration")
1503 msgs.append("Account lockout duration changed!")
1505 if account_lockout_threshold is not None:
1506 if account_lockout_threshold == "default":
1507 account_lockout_threshold = 0
1508 else:
1509 account_lockout_threshold = int(account_lockout_threshold)
1511 m["lockoutThreshold"] = ldb.MessageElement(str(account_lockout_threshold),
1512 ldb.FLAG_MOD_REPLACE, "lockoutThreshold")
1513 msgs.append("Account lockout threshold changed!")
1515 if reset_account_lockout_after is not None:
1516 if reset_account_lockout_after == "default":
1517 reset_account_lockout_after = 30
1518 else:
1519 reset_account_lockout_after = int(reset_account_lockout_after)
1521 if reset_account_lockout_after < 0 or reset_account_lockout_after > 99999:
1522 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1524 # minutes -> ticks
1525 if reset_account_lockout_after == 0:
1526 reset_account_lockout_after_ticks = -0x8000000000000000
1527 else:
1528 reset_account_lockout_after_ticks = -int(reset_account_lockout_after * (60 * 1e7))
1530 m["lockOutObservationWindow"] = ldb.MessageElement(str(reset_account_lockout_after_ticks),
1531 ldb.FLAG_MOD_REPLACE, "lockOutObservationWindow")
1532 msgs.append("Duration to reset account lockout after changed!")
1534 if max_pwd_age and max_pwd_age > 0 and min_pwd_age >= max_pwd_age:
1535 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
1537 if len(m) == 0:
1538 raise CommandError("You must specify at least one option to set. Try --help")
1539 samdb.modify(m)
1540 msgs.append("All changes applied successfully!")
1541 self.message("\n".join(msgs))
1543 class cmd_domain_passwordsettings(SuperCommand):
1544 """Manage password policy settings."""
1546 subcommands = {}
1547 subcommands["pso"] = cmd_domain_passwordsettings_pso()
1548 subcommands["show"] = cmd_domain_passwordsettings_show()
1549 subcommands["set"] = cmd_domain_passwordsettings_set()
1551 class cmd_domain_classicupgrade(Command):
1552 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1554 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1555 the testparm utility from your classic installation (with --testparm).
1558 synopsis = "%prog [options] <classic_smb_conf>"
1560 takes_optiongroups = {
1561 "sambaopts": options.SambaOptions,
1562 "versionopts": options.VersionOptions
1565 takes_options = [
1566 Option("--dbdir", type="string", metavar="DIR",
1567 help="Path to samba classic DC database directory"),
1568 Option("--testparm", type="string", metavar="PATH",
1569 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1570 Option("--targetdir", type="string", metavar="DIR",
1571 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1572 Option("-q", "--quiet", help="Be quiet", action="store_true"),
1573 Option("-v", "--verbose", help="Be verbose", action="store_true"),
1574 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
1575 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1576 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1577 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1578 "BIND9_DLZ uses samba4 AD to store zone information, "
1579 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1580 default="SAMBA_INTERNAL")
1583 ntvfs_options = [
1584 Option("--use-xattrs", type="choice", choices=["yes","no","auto"],
1585 metavar="[yes|no|auto]",
1586 help="Define if we should use the native fs capabilities or a tdb file for "
1587 "storing attributes likes ntacl when --use-ntvfs is set. "
1588 "auto tries to make an inteligent guess based on the user rights and system capabilities",
1589 default="auto")
1591 if samba.is_ntvfs_fileserver_built():
1592 takes_options.extend(common_ntvfs_options)
1593 takes_options.extend(ntvfs_options)
1595 takes_args = ["smbconf"]
1597 def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
1598 quiet=False, verbose=False, use_xattrs="auto", sambaopts=None, versionopts=None,
1599 dns_backend=None, use_ntvfs=False):
1601 if not os.path.exists(smbconf):
1602 raise CommandError("File %s does not exist" % smbconf)
1604 if testparm and not os.path.exists(testparm):
1605 raise CommandError("Testparm utility %s does not exist" % testparm)
1607 if dbdir and not os.path.exists(dbdir):
1608 raise CommandError("Directory %s does not exist" % dbdir)
1610 if not dbdir and not testparm:
1611 raise CommandError("Please specify either dbdir or testparm")
1613 logger = self.get_logger()
1614 if verbose:
1615 logger.setLevel(logging.DEBUG)
1616 elif quiet:
1617 logger.setLevel(logging.WARNING)
1618 else:
1619 logger.setLevel(logging.INFO)
1621 if dbdir and testparm:
1622 logger.warning("both dbdir and testparm specified, ignoring dbdir.")
1623 dbdir = None
1625 lp = sambaopts.get_loadparm()
1627 s3conf = s3param.get_context()
1629 if sambaopts.realm:
1630 s3conf.set("realm", sambaopts.realm)
1632 if targetdir is not None:
1633 if not os.path.isdir(targetdir):
1634 os.mkdir(targetdir)
1636 eadb = True
1637 if use_xattrs == "yes":
1638 eadb = False
1639 elif use_xattrs == "auto" and use_ntvfs == False:
1640 eadb = False
1641 elif use_ntvfs == False:
1642 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
1643 "Please re-run with --use-xattrs omitted.")
1644 elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
1645 if targetdir:
1646 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
1647 else:
1648 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
1649 try:
1650 try:
1651 samba.ntacls.setntacl(lp, tmpfile.name,
1652 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1653 eadb = False
1654 except Exception:
1655 # FIXME: Don't catch all exceptions here
1656 logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. "
1657 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1658 finally:
1659 tmpfile.close()
1661 # Set correct default values from dbdir or testparm
1662 paths = {}
1663 if dbdir:
1664 paths["state directory"] = dbdir
1665 paths["private dir"] = dbdir
1666 paths["lock directory"] = dbdir
1667 paths["smb passwd file"] = dbdir + "/smbpasswd"
1668 else:
1669 paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
1670 paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
1671 paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
1672 paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
1673 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1674 # "state directory", instead make use of "lock directory"
1675 if len(paths["state directory"]) == 0:
1676 paths["state directory"] = paths["lock directory"]
1678 for p in paths:
1679 s3conf.set(p, paths[p])
1681 # load smb.conf parameters
1682 logger.info("Reading smb.conf")
1683 s3conf.load(smbconf)
1684 samba3 = Samba3(smbconf, s3conf)
1686 logger.info("Provisioning")
1687 upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
1688 useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
1691 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
1692 __doc__ = cmd_domain_classicupgrade.__doc__
1694 # This command is present for backwards compatibility only,
1695 # and should not be shown.
1697 hidden = True
1699 class LocalDCCredentialsOptions(options.CredentialsOptions):
1700 def __init__(self, parser):
1701 options.CredentialsOptions.__init__(self, parser, special_name="local-dc")
1703 class DomainTrustCommand(Command):
1704 """List domain trusts."""
1706 def __init__(self):
1707 Command.__init__(self)
1708 self.local_lp = None
1710 self.local_server = None
1711 self.local_binding_string = None
1712 self.local_creds = None
1714 self.remote_server = None
1715 self.remote_binding_string = None
1716 self.remote_creds = None
1718 def _uint32(self, v):
1719 return ctypes.c_uint32(v).value
1721 def check_runtime_error(self, runtime, val):
1722 if runtime is None:
1723 return False
1725 err32 = self._uint32(runtime.args[0])
1726 if err32 == val:
1727 return True
1729 return False
1731 class LocalRuntimeError(CommandError):
1732 def __init__(exception_self, self, runtime, message):
1733 err32 = self._uint32(runtime.args[0])
1734 errstr = runtime.args[1]
1735 msg = "LOCAL_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1736 self.local_server, message, err32, errstr)
1737 CommandError.__init__(exception_self, msg)
1739 class RemoteRuntimeError(CommandError):
1740 def __init__(exception_self, self, runtime, message):
1741 err32 = self._uint32(runtime.args[0])
1742 errstr = runtime.args[1]
1743 msg = "REMOTE_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1744 self.remote_server, message, err32, errstr)
1745 CommandError.__init__(exception_self, msg)
1747 class LocalLdbError(CommandError):
1748 def __init__(exception_self, self, ldb_error, message):
1749 errval = ldb_error.args[0]
1750 errstr = ldb_error.args[1]
1751 msg = "LOCAL_DC[%s]: %s - ERROR(%d) - %s" % (
1752 self.local_server, message, errval, errstr)
1753 CommandError.__init__(exception_self, msg)
1755 def setup_local_server(self, sambaopts, localdcopts):
1756 if self.local_server is not None:
1757 return self.local_server
1759 lp = sambaopts.get_loadparm()
1761 local_server = localdcopts.ipaddress
1762 if local_server is None:
1763 server_role = lp.server_role()
1764 if server_role != "ROLE_ACTIVE_DIRECTORY_DC":
1765 raise CommandError("Invalid server_role %s" % (server_role))
1766 local_server = lp.get('netbios name')
1767 local_transport = "ncalrpc"
1768 local_binding_options = ""
1769 local_binding_options += ",auth_type=ncalrpc_as_system"
1770 local_ldap_url = None
1771 local_creds = None
1772 else:
1773 local_transport = "ncacn_np"
1774 local_binding_options = ""
1775 local_ldap_url = "ldap://%s" % local_server
1776 local_creds = localdcopts.get_credentials(lp)
1778 self.local_lp = lp
1780 self.local_server = local_server
1781 self.local_binding_string = "%s:%s[%s]" % (local_transport, local_server, local_binding_options)
1782 self.local_ldap_url = local_ldap_url
1783 self.local_creds = local_creds
1784 return self.local_server
1786 def new_local_lsa_connection(self):
1787 return lsa.lsarpc(self.local_binding_string, self.local_lp, self.local_creds)
1789 def new_local_netlogon_connection(self):
1790 return netlogon.netlogon(self.local_binding_string, self.local_lp, self.local_creds)
1792 def new_local_ldap_connection(self):
1793 return SamDB(url=self.local_ldap_url,
1794 session_info=system_session(),
1795 credentials=self.local_creds,
1796 lp=self.local_lp)
1798 def setup_remote_server(self, credopts, domain,
1799 require_pdc=True,
1800 require_writable=True):
1802 if require_pdc:
1803 assert require_writable
1805 if self.remote_server is not None:
1806 return self.remote_server
1808 self.remote_server = "__unknown__remote_server__.%s" % domain
1809 assert self.local_server is not None
1811 remote_creds = credopts.get_credentials(self.local_lp)
1812 remote_server = credopts.ipaddress
1813 remote_binding_options = ""
1815 # TODO: we should also support NT4 domains
1816 # we could use local_netlogon.netr_DsRGetDCNameEx2() with the remote domain name
1817 # and delegate NBT or CLDAP to the local netlogon server
1818 try:
1819 remote_net = Net(remote_creds, self.local_lp, server=remote_server)
1820 remote_flags = nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS
1821 if require_writable:
1822 remote_flags |= nbt.NBT_SERVER_WRITABLE
1823 if require_pdc:
1824 remote_flags |= nbt.NBT_SERVER_PDC
1825 remote_info = remote_net.finddc(flags=remote_flags, domain=domain, address=remote_server)
1826 except NTSTATUSError as error:
1827 raise CommandError("Failed to find a writeable DC for domain '%s': %s" %
1828 (domain, error[1]))
1829 except Exception:
1830 raise CommandError("Failed to find a writeable DC for domain '%s'" % domain)
1831 flag_map = {
1832 nbt.NBT_SERVER_PDC: "PDC",
1833 nbt.NBT_SERVER_GC: "GC",
1834 nbt.NBT_SERVER_LDAP: "LDAP",
1835 nbt.NBT_SERVER_DS: "DS",
1836 nbt.NBT_SERVER_KDC: "KDC",
1837 nbt.NBT_SERVER_TIMESERV: "TIMESERV",
1838 nbt.NBT_SERVER_CLOSEST: "CLOSEST",
1839 nbt.NBT_SERVER_WRITABLE: "WRITABLE",
1840 nbt.NBT_SERVER_GOOD_TIMESERV: "GOOD_TIMESERV",
1841 nbt.NBT_SERVER_NDNC: "NDNC",
1842 nbt.NBT_SERVER_SELECT_SECRET_DOMAIN_6: "SELECT_SECRET_DOMAIN_6",
1843 nbt.NBT_SERVER_FULL_SECRET_DOMAIN_6: "FULL_SECRET_DOMAIN_6",
1844 nbt.NBT_SERVER_ADS_WEB_SERVICE: "ADS_WEB_SERVICE",
1845 nbt.NBT_SERVER_DS_8: "DS_8",
1846 nbt.NBT_SERVER_HAS_DNS_NAME: "HAS_DNS_NAME",
1847 nbt.NBT_SERVER_IS_DEFAULT_NC: "IS_DEFAULT_NC",
1848 nbt.NBT_SERVER_FOREST_ROOT: "FOREST_ROOT",
1850 server_type_string = self.generic_bitmap_to_string(flag_map,
1851 remote_info.server_type, names_only=True)
1852 self.outf.write("RemoteDC Netbios[%s] DNS[%s] ServerType[%s]\n" % (
1853 remote_info.pdc_name,
1854 remote_info.pdc_dns_name,
1855 server_type_string))
1857 self.remote_server = remote_info.pdc_dns_name
1858 self.remote_binding_string="ncacn_np:%s[%s]" % (self.remote_server, remote_binding_options)
1859 self.remote_creds = remote_creds
1860 return self.remote_server
1862 def new_remote_lsa_connection(self):
1863 return lsa.lsarpc(self.remote_binding_string, self.local_lp, self.remote_creds)
1865 def new_remote_netlogon_connection(self):
1866 return netlogon.netlogon(self.remote_binding_string, self.local_lp, self.remote_creds)
1868 def get_lsa_info(self, conn, policy_access):
1869 objectAttr = lsa.ObjectAttribute()
1870 objectAttr.sec_qos = lsa.QosInfo()
1872 policy = conn.OpenPolicy2(''.decode('utf-8'),
1873 objectAttr, policy_access)
1875 info = conn.QueryInfoPolicy2(policy, lsa.LSA_POLICY_INFO_DNS)
1877 return (policy, info)
1879 def get_netlogon_dc_unc(self, conn, server, domain):
1880 try:
1881 info = conn.netr_DsRGetDCNameEx2(server,
1882 None, 0, None, None, None,
1883 netlogon.DS_RETURN_DNS_NAME)
1884 return info.dc_unc
1885 except RuntimeError:
1886 return conn.netr_GetDcName(server, domain)
1888 def get_netlogon_dc_info(self, conn, server):
1889 info = conn.netr_DsRGetDCNameEx2(server,
1890 None, 0, None, None, None,
1891 netlogon.DS_RETURN_DNS_NAME)
1892 return info
1894 def netr_DomainTrust_to_name(self, t):
1895 if t.trust_type == lsa.LSA_TRUST_TYPE_DOWNLEVEL:
1896 return t.netbios_name
1898 return t.dns_name
1900 def netr_DomainTrust_to_type(self, a, t):
1901 primary = None
1902 primary_parent = None
1903 for _t in a:
1904 if _t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
1905 primary = _t
1906 if not _t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1907 primary_parent = a[_t.parent_index]
1908 break
1910 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1911 if t is primary_parent:
1912 return "Parent"
1914 if t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1915 return "TreeRoot"
1917 parent = a[t.parent_index]
1918 if parent is primary:
1919 return "Child"
1921 return "Shortcut"
1923 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1924 return "Forest"
1926 return "External"
1928 def netr_DomainTrust_to_transitive(self, t):
1929 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1930 return "Yes"
1932 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE:
1933 return "No"
1935 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1936 return "Yes"
1938 return "No"
1940 def netr_DomainTrust_to_direction(self, t):
1941 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND and \
1942 t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1943 return "BOTH"
1945 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND:
1946 return "INCOMING"
1948 if t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1949 return "OUTGOING"
1951 return "INVALID"
1953 def generic_enum_to_string(self, e_dict, v, names_only=False):
1954 try:
1955 w = e_dict[v]
1956 except KeyError:
1957 v32 = self._uint32(v)
1958 w = "__unknown__%08X__" % v32
1960 r = "0x%x (%s)" % (v, w)
1961 return r;
1963 def generic_bitmap_to_string(self, b_dict, v, names_only=False):
1965 s = []
1967 c = v
1968 for b in sorted(b_dict.keys()):
1969 if not (c & b):
1970 continue
1971 c &= ~b
1972 s += [b_dict[b]]
1974 if c != 0:
1975 c32 = self._uint32(c)
1976 s += ["__unknown_%08X__" % c32]
1978 w = ",".join(s)
1979 if names_only:
1980 return w
1981 r = "0x%x (%s)" % (v, w)
1982 return r;
1984 def trustType_string(self, v):
1985 types = {
1986 lsa.LSA_TRUST_TYPE_DOWNLEVEL: "DOWNLEVEL",
1987 lsa.LSA_TRUST_TYPE_UPLEVEL: "UPLEVEL",
1988 lsa.LSA_TRUST_TYPE_MIT: "MIT",
1989 lsa.LSA_TRUST_TYPE_DCE: "DCE",
1991 return self.generic_enum_to_string(types, v)
1993 def trustDirection_string(self, v):
1994 directions = {
1995 lsa.LSA_TRUST_DIRECTION_INBOUND |
1996 lsa.LSA_TRUST_DIRECTION_OUTBOUND: "BOTH",
1997 lsa.LSA_TRUST_DIRECTION_INBOUND: "INBOUND",
1998 lsa.LSA_TRUST_DIRECTION_OUTBOUND: "OUTBOUND",
2000 return self.generic_enum_to_string(directions, v)
2002 def trustAttributes_string(self, v):
2003 attributes = {
2004 lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE: "NON_TRANSITIVE",
2005 lsa.LSA_TRUST_ATTRIBUTE_UPLEVEL_ONLY: "UPLEVEL_ONLY",
2006 lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN: "QUARANTINED_DOMAIN",
2007 lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE: "FOREST_TRANSITIVE",
2008 lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION: "CROSS_ORGANIZATION",
2009 lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST: "WITHIN_FOREST",
2010 lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL: "TREAT_AS_EXTERNAL",
2011 lsa.LSA_TRUST_ATTRIBUTE_USES_RC4_ENCRYPTION: "USES_RC4_ENCRYPTION",
2013 return self.generic_bitmap_to_string(attributes, v)
2015 def kerb_EncTypes_string(self, v):
2016 enctypes = {
2017 security.KERB_ENCTYPE_DES_CBC_CRC: "DES_CBC_CRC",
2018 security.KERB_ENCTYPE_DES_CBC_MD5: "DES_CBC_MD5",
2019 security.KERB_ENCTYPE_RC4_HMAC_MD5: "RC4_HMAC_MD5",
2020 security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96: "AES128_CTS_HMAC_SHA1_96",
2021 security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96: "AES256_CTS_HMAC_SHA1_96",
2022 security.KERB_ENCTYPE_FAST_SUPPORTED: "FAST_SUPPORTED",
2023 security.KERB_ENCTYPE_COMPOUND_IDENTITY_SUPPORTED: "COMPOUND_IDENTITY_SUPPORTED",
2024 security.KERB_ENCTYPE_CLAIMS_SUPPORTED: "CLAIMS_SUPPORTED",
2025 security.KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED: "RESOURCE_SID_COMPRESSION_DISABLED",
2027 return self.generic_bitmap_to_string(enctypes, v)
2029 def entry_tln_status(self, e_flags, ):
2030 if e_flags == 0:
2031 return "Status[Enabled]"
2033 flags = {
2034 lsa.LSA_TLN_DISABLED_NEW: "Disabled-New",
2035 lsa.LSA_TLN_DISABLED_ADMIN: "Disabled",
2036 lsa.LSA_TLN_DISABLED_CONFLICT: "Disabled-Conflicting",
2038 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
2040 def entry_dom_status(self, e_flags):
2041 if e_flags == 0:
2042 return "Status[Enabled]"
2044 flags = {
2045 lsa.LSA_SID_DISABLED_ADMIN: "Disabled-SID",
2046 lsa.LSA_SID_DISABLED_CONFLICT: "Disabled-SID-Conflicting",
2047 lsa.LSA_NB_DISABLED_ADMIN: "Disabled-NB",
2048 lsa.LSA_NB_DISABLED_CONFLICT: "Disabled-NB-Conflicting",
2050 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
2052 def write_forest_trust_info(self, fti, tln=None, collisions=None):
2053 if tln is not None:
2054 tln_string = " TDO[%s]" % tln
2055 else:
2056 tln_string = ""
2058 self.outf.write("Namespaces[%d]%s:\n" % (
2059 len(fti.entries), tln_string))
2061 for i, e in enumerate(fti.entries):
2063 flags = e.flags
2064 collision_string = ""
2066 if collisions is not None:
2067 for c in collisions.entries:
2068 if c.index != i:
2069 continue
2070 flags = c.flags
2071 collision_string = " Collision[%s]" % (c.name.string)
2073 d = e.forest_trust_data
2074 if e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
2075 self.outf.write("TLN: %-32s DNS[*.%s]%s\n" % (
2076 self.entry_tln_status(flags),
2077 d.string, collision_string))
2078 elif e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
2079 self.outf.write("TLN_EX: %-29s DNS[*.%s]\n" % (
2080 "", d.string))
2081 elif e.type == lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
2082 self.outf.write("DOM: %-32s DNS[%s] Netbios[%s] SID[%s]%s\n" % (
2083 self.entry_dom_status(flags),
2084 d.dns_domain_name.string,
2085 d.netbios_domain_name.string,
2086 d.domain_sid, collision_string))
2087 return
2089 class cmd_domain_trust_list(DomainTrustCommand):
2090 """List domain trusts."""
2092 synopsis = "%prog [options]"
2094 takes_optiongroups = {
2095 "sambaopts": options.SambaOptions,
2096 "versionopts": options.VersionOptions,
2097 "localdcopts": LocalDCCredentialsOptions,
2100 takes_options = [
2103 def run(self, sambaopts=None, versionopts=None, localdcopts=None):
2105 local_server = self.setup_local_server(sambaopts, localdcopts)
2106 try:
2107 local_netlogon = self.new_local_netlogon_connection()
2108 except RuntimeError as error:
2109 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2111 try:
2112 local_netlogon_trusts = \
2113 local_netlogon.netr_DsrEnumerateDomainTrusts(local_server,
2114 netlogon.NETR_TRUST_FLAG_IN_FOREST |
2115 netlogon.NETR_TRUST_FLAG_OUTBOUND |
2116 netlogon.NETR_TRUST_FLAG_INBOUND)
2117 except RuntimeError as error:
2118 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
2119 # TODO: we could implement a fallback to lsa.EnumTrustDom()
2120 raise CommandError("LOCAL_DC[%s]: netr_DsrEnumerateDomainTrusts not supported." % (
2121 self.local_server))
2122 raise self.LocalRuntimeError(self, error, "netr_DsrEnumerateDomainTrusts failed")
2124 a = local_netlogon_trusts.array
2125 for t in a:
2126 if t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
2127 continue
2128 self.outf.write("%-14s %-15s %-19s %s\n" % (
2129 "Type[%s]" % self.netr_DomainTrust_to_type(a, t),
2130 "Transitive[%s]" % self.netr_DomainTrust_to_transitive(t),
2131 "Direction[%s]" % self.netr_DomainTrust_to_direction(t),
2132 "Name[%s]" % self.netr_DomainTrust_to_name(t)))
2133 return
2135 class cmd_domain_trust_show(DomainTrustCommand):
2136 """Show trusted domain details."""
2138 synopsis = "%prog NAME [options]"
2140 takes_optiongroups = {
2141 "sambaopts": options.SambaOptions,
2142 "versionopts": options.VersionOptions,
2143 "localdcopts": LocalDCCredentialsOptions,
2146 takes_options = [
2149 takes_args = ["domain"]
2151 def run(self, domain, sambaopts=None, versionopts=None, localdcopts=None):
2153 local_server = self.setup_local_server(sambaopts, localdcopts)
2154 try:
2155 local_lsa = self.new_local_lsa_connection()
2156 except RuntimeError as error:
2157 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2159 try:
2160 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2161 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2162 except RuntimeError as error:
2163 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2165 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2166 local_lsa_info.name.string,
2167 local_lsa_info.dns_domain.string,
2168 local_lsa_info.sid))
2170 lsaString = lsa.String()
2171 lsaString.string = domain
2172 try:
2173 local_tdo_full = \
2174 local_lsa.QueryTrustedDomainInfoByName(local_policy,
2175 lsaString,
2176 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2177 local_tdo_info = local_tdo_full.info_ex
2178 local_tdo_posix = local_tdo_full.posix_offset
2179 except NTSTATUSError as error:
2180 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2181 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2183 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(FULL_INFO) failed")
2185 try:
2186 local_tdo_enctypes = \
2187 local_lsa.QueryTrustedDomainInfoByName(local_policy,
2188 lsaString,
2189 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES)
2190 except NTSTATUSError as error:
2191 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_PARAMETER):
2192 error = None
2193 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_INFO_CLASS):
2194 error = None
2196 if error is not None:
2197 raise self.LocalRuntimeError(self, error,
2198 "QueryTrustedDomainInfoByName(SUPPORTED_ENCRYPTION_TYPES) failed")
2200 local_tdo_enctypes = lsa.TrustDomainInfoSupportedEncTypes()
2201 local_tdo_enctypes.enc_types = 0
2203 try:
2204 local_tdo_forest = None
2205 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2206 local_tdo_forest = \
2207 local_lsa.lsaRQueryForestTrustInformation(local_policy,
2208 lsaString,
2209 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
2210 except RuntimeError as error:
2211 if self.check_runtime_error(error, ntstatus.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE):
2212 error = None
2213 if self.check_runtime_error(error, ntstatus.NT_STATUS_NOT_FOUND):
2214 error = None
2215 if error is not None:
2216 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation failed")
2218 local_tdo_forest = lsa.ForestTrustInformation()
2219 local_tdo_forest.count = 0
2220 local_tdo_forest.entries = []
2222 self.outf.write("TrustedDomain:\n\n");
2223 self.outf.write("NetbiosName: %s\n" % local_tdo_info.netbios_name.string)
2224 if local_tdo_info.netbios_name.string != local_tdo_info.domain_name.string:
2225 self.outf.write("DnsName: %s\n" % local_tdo_info.domain_name.string)
2226 self.outf.write("SID: %s\n" % local_tdo_info.sid)
2227 self.outf.write("Type: %s\n" % self.trustType_string(local_tdo_info.trust_type))
2228 self.outf.write("Direction: %s\n" % self.trustDirection_string(local_tdo_info.trust_direction))
2229 self.outf.write("Attributes: %s\n" % self.trustAttributes_string(local_tdo_info.trust_attributes))
2230 posix_offset_u32 = ctypes.c_uint32(local_tdo_posix.posix_offset).value
2231 posix_offset_i32 = ctypes.c_int32(local_tdo_posix.posix_offset).value
2232 self.outf.write("PosixOffset: 0x%08X (%d)\n" % (posix_offset_u32, posix_offset_i32))
2233 self.outf.write("kerb_EncTypes: %s\n" % self.kerb_EncTypes_string(local_tdo_enctypes.enc_types))
2235 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2236 self.write_forest_trust_info(local_tdo_forest,
2237 tln=local_tdo_info.domain_name.string)
2239 return
2241 class cmd_domain_trust_create(DomainTrustCommand):
2242 """Create a domain or forest trust."""
2244 synopsis = "%prog DOMAIN [options]"
2246 takes_optiongroups = {
2247 "sambaopts": options.SambaOptions,
2248 "versionopts": options.VersionOptions,
2249 "credopts": options.CredentialsOptions,
2250 "localdcopts": LocalDCCredentialsOptions,
2253 takes_options = [
2254 Option("--type", type="choice", metavar="TYPE",
2255 choices=["external", "forest"],
2256 help="The type of the trust: 'external' or 'forest'.",
2257 dest='trust_type',
2258 default="external"),
2259 Option("--direction", type="choice", metavar="DIRECTION",
2260 choices=["incoming", "outgoing", "both"],
2261 help="The trust direction: 'incoming', 'outgoing' or 'both'.",
2262 dest='trust_direction',
2263 default="both"),
2264 Option("--create-location", type="choice", metavar="LOCATION",
2265 choices=["local", "both"],
2266 help="Where to create the trusted domain object: 'local' or 'both'.",
2267 dest='create_location',
2268 default="both"),
2269 Option("--cross-organisation", action="store_true",
2270 help="The related domains does not belong to the same organisation.",
2271 dest='cross_organisation',
2272 default=False),
2273 Option("--quarantined", type="choice", metavar="yes|no",
2274 choices=["yes", "no", None],
2275 help="Special SID filtering rules are applied to the trust. "
2276 "With --type=external the default is yes. "
2277 "With --type=forest the default is no.",
2278 dest='quarantined_arg',
2279 default=None),
2280 Option("--not-transitive", action="store_true",
2281 help="The forest trust is not transitive.",
2282 dest='not_transitive',
2283 default=False),
2284 Option("--treat-as-external", action="store_true",
2285 help="The treat the forest trust as external.",
2286 dest='treat_as_external',
2287 default=False),
2288 Option("--no-aes-keys", action="store_false",
2289 help="The trust uses aes kerberos keys.",
2290 dest='use_aes_keys',
2291 default=True),
2292 Option("--skip-validation", action="store_false",
2293 help="Skip validation of the trust.",
2294 dest='validate',
2295 default=True),
2298 takes_args = ["domain"]
2300 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2301 trust_type=None, trust_direction=None, create_location=None,
2302 cross_organisation=False, quarantined_arg=None,
2303 not_transitive=False, treat_as_external=False,
2304 use_aes_keys=False, validate=True):
2306 lsaString = lsa.String()
2308 quarantined = False
2309 if quarantined_arg is None:
2310 if trust_type == 'external':
2311 quarantined = True
2312 elif quarantined_arg == 'yes':
2313 quarantined = True
2315 if trust_type != 'forest':
2316 if not_transitive:
2317 raise CommandError("--not-transitive requires --type=forest")
2318 if treat_as_external:
2319 raise CommandError("--treat-as-external requires --type=forest")
2321 enc_types = None
2322 if use_aes_keys:
2323 enc_types = lsa.TrustDomainInfoSupportedEncTypes()
2324 enc_types.enc_types = security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96
2325 enc_types.enc_types |= security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96
2327 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2328 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2329 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2331 local_trust_info = lsa.TrustDomainInfoInfoEx()
2332 local_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2333 local_trust_info.trust_direction = 0
2334 if trust_direction == "both":
2335 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2336 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2337 elif trust_direction == "incoming":
2338 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2339 elif trust_direction == "outgoing":
2340 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2341 local_trust_info.trust_attributes = 0
2342 if cross_organisation:
2343 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2344 if quarantined:
2345 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2346 if trust_type == "forest":
2347 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2348 if not_transitive:
2349 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2350 if treat_as_external:
2351 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2353 def get_password(name):
2354 password = None
2355 while True:
2356 if password is not None and password is not '':
2357 return password
2358 password = getpass("New %s Password: " % name)
2359 passwordverify = getpass("Retype %s Password: " % name)
2360 if not password == passwordverify:
2361 password = None
2362 self.outf.write("Sorry, passwords do not match.\n")
2364 incoming_secret = None
2365 outgoing_secret = None
2366 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2367 if create_location == "local":
2368 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2369 incoming_password = get_password("Incoming Trust")
2370 incoming_secret = string_to_byte_array(incoming_password.encode('utf-16-le'))
2371 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2372 outgoing_password = get_password("Outgoing Trust")
2373 outgoing_secret = string_to_byte_array(outgoing_password.encode('utf-16-le'))
2375 remote_trust_info = None
2376 else:
2377 # We use 240 random bytes.
2378 # Windows uses 28 or 240 random bytes. I guess it's
2379 # based on the trust type external vs. forest.
2381 # The initial trust password can be up to 512 bytes
2382 # while the versioned passwords used for periodic updates
2383 # can only be up to 498 bytes, as netr_ServerPasswordSet2()
2384 # needs to pass the NL_PASSWORD_VERSION structure within the
2385 # 512 bytes and a 2 bytes confounder is required.
2387 def random_trust_secret(length):
2388 pw = samba.generate_random_machine_password(length//2, length//2)
2389 return string_to_byte_array(pw.encode('utf-16-le'))
2391 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2392 incoming_secret = random_trust_secret(240)
2393 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2394 outgoing_secret = random_trust_secret(240)
2396 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2397 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2399 remote_trust_info = lsa.TrustDomainInfoInfoEx()
2400 remote_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2401 remote_trust_info.trust_direction = 0
2402 if trust_direction == "both":
2403 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2404 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2405 elif trust_direction == "incoming":
2406 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2407 elif trust_direction == "outgoing":
2408 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2409 remote_trust_info.trust_attributes = 0
2410 if cross_organisation:
2411 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2412 if quarantined:
2413 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2414 if trust_type == "forest":
2415 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2416 if not_transitive:
2417 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2418 if treat_as_external:
2419 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2421 local_server = self.setup_local_server(sambaopts, localdcopts)
2422 try:
2423 local_lsa = self.new_local_lsa_connection()
2424 except RuntimeError as error:
2425 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2427 try:
2428 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2429 except RuntimeError as error:
2430 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2432 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2433 local_lsa_info.name.string,
2434 local_lsa_info.dns_domain.string,
2435 local_lsa_info.sid))
2437 try:
2438 remote_server = self.setup_remote_server(credopts, domain)
2439 except RuntimeError as error:
2440 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2442 try:
2443 remote_lsa = self.new_remote_lsa_connection()
2444 except RuntimeError as error:
2445 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2447 try:
2448 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2449 except RuntimeError as error:
2450 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2452 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2453 remote_lsa_info.name.string,
2454 remote_lsa_info.dns_domain.string,
2455 remote_lsa_info.sid))
2457 local_trust_info.domain_name.string = remote_lsa_info.dns_domain.string
2458 local_trust_info.netbios_name.string = remote_lsa_info.name.string
2459 local_trust_info.sid = remote_lsa_info.sid
2461 if remote_trust_info:
2462 remote_trust_info.domain_name.string = local_lsa_info.dns_domain.string
2463 remote_trust_info.netbios_name.string = local_lsa_info.name.string
2464 remote_trust_info.sid = local_lsa_info.sid
2466 try:
2467 lsaString.string = local_trust_info.domain_name.string
2468 local_old_netbios = \
2469 local_lsa.QueryTrustedDomainInfoByName(local_policy,
2470 lsaString,
2471 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2472 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2473 except NTSTATUSError as error:
2474 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2475 raise self.LocalRuntimeError(self, error,
2476 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2477 lsaString.string))
2479 try:
2480 lsaString.string = local_trust_info.netbios_name.string
2481 local_old_dns = \
2482 local_lsa.QueryTrustedDomainInfoByName(local_policy,
2483 lsaString,
2484 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2485 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2486 except NTSTATUSError as error:
2487 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2488 raise self.LocalRuntimeError(self, error,
2489 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2490 lsaString.string))
2492 if remote_trust_info:
2493 try:
2494 lsaString.string = remote_trust_info.domain_name.string
2495 remote_old_netbios = \
2496 remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2497 lsaString,
2498 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2499 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2500 except NTSTATUSError as error:
2501 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2502 raise self.RemoteRuntimeError(self, error,
2503 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2504 lsaString.string))
2506 try:
2507 lsaString.string = remote_trust_info.netbios_name.string
2508 remote_old_dns = \
2509 remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2510 lsaString,
2511 lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2512 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2513 except NTSTATUSError as error:
2514 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2515 raise self.RemoteRuntimeError(self, error,
2516 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2517 lsaString.string))
2519 try:
2520 local_netlogon = self.new_local_netlogon_connection()
2521 except RuntimeError as error:
2522 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2524 try:
2525 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
2526 except RuntimeError as error:
2527 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
2529 if remote_trust_info:
2530 try:
2531 remote_netlogon = self.new_remote_netlogon_connection()
2532 except RuntimeError as error:
2533 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2535 try:
2536 remote_netlogon_dc_unc = self.get_netlogon_dc_unc(remote_netlogon,
2537 remote_server, domain)
2538 except RuntimeError as error:
2539 raise self.RemoteRuntimeError(self, error, "failed to get netlogon dc info")
2541 def generate_AuthInOutBlob(secret, update_time):
2542 if secret is None:
2543 blob = drsblobs.trustAuthInOutBlob()
2544 blob.count = 0
2546 return blob
2548 clear = drsblobs.AuthInfoClear()
2549 clear.size = len(secret)
2550 clear.password = secret
2552 info = drsblobs.AuthenticationInformation()
2553 info.LastUpdateTime = samba.unix2nttime(update_time)
2554 info.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
2555 info.AuthInfo = clear
2557 array = drsblobs.AuthenticationInformationArray()
2558 array.count = 1
2559 array.array = [info]
2561 blob = drsblobs.trustAuthInOutBlob()
2562 blob.count = 1
2563 blob.current = array
2565 return blob
2567 def generate_AuthInfoInternal(session_key, incoming=None, outgoing=None):
2568 confounder = [0] * 512
2569 for i in range(len(confounder)):
2570 confounder[i] = random.randint(0, 255)
2572 trustpass = drsblobs.trustDomainPasswords()
2574 trustpass.confounder = confounder
2575 trustpass.outgoing = outgoing
2576 trustpass.incoming = incoming
2578 trustpass_blob = ndr_pack(trustpass)
2580 encrypted_trustpass = arcfour_encrypt(session_key, trustpass_blob)
2582 auth_blob = lsa.DATA_BUF2()
2583 auth_blob.size = len(encrypted_trustpass)
2584 auth_blob.data = string_to_byte_array(encrypted_trustpass)
2586 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
2587 auth_info.auth_blob = auth_blob
2589 return auth_info
2591 update_time = samba.current_unix_time()
2592 incoming_blob = generate_AuthInOutBlob(incoming_secret, update_time)
2593 outgoing_blob = generate_AuthInOutBlob(outgoing_secret, update_time)
2595 local_tdo_handle = None
2596 remote_tdo_handle = None
2598 local_auth_info = generate_AuthInfoInternal(local_lsa.session_key,
2599 incoming=incoming_blob,
2600 outgoing=outgoing_blob)
2601 if remote_trust_info:
2602 remote_auth_info = generate_AuthInfoInternal(remote_lsa.session_key,
2603 incoming=outgoing_blob,
2604 outgoing=incoming_blob)
2606 try:
2607 if remote_trust_info:
2608 self.outf.write("Creating remote TDO.\n")
2609 current_request = {"location": "remote", "name": "CreateTrustedDomainEx2"}
2610 remote_tdo_handle = \
2611 remote_lsa.CreateTrustedDomainEx2(remote_policy,
2612 remote_trust_info,
2613 remote_auth_info,
2614 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2615 self.outf.write("Remote TDO created.\n")
2616 if enc_types:
2617 self.outf.write("Setting supported encryption types on remote TDO.\n")
2618 current_request = {"location": "remote", "name": "SetInformationTrustedDomain"}
2619 remote_lsa.SetInformationTrustedDomain(remote_tdo_handle,
2620 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2621 enc_types)
2623 self.outf.write("Creating local TDO.\n")
2624 current_request = {"location": "local", "name": "CreateTrustedDomainEx2"}
2625 local_tdo_handle = local_lsa.CreateTrustedDomainEx2(local_policy,
2626 local_trust_info,
2627 local_auth_info,
2628 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2629 self.outf.write("Local TDO created\n")
2630 if enc_types:
2631 self.outf.write("Setting supported encryption types on local TDO.\n")
2632 current_request = {"location": "local", "name": "SetInformationTrustedDomain"}
2633 local_lsa.SetInformationTrustedDomain(local_tdo_handle,
2634 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2635 enc_types)
2636 except RuntimeError as error:
2637 self.outf.write("Error: %s failed %sly - cleaning up\n" % (
2638 current_request['name'], current_request['location']))
2639 if remote_tdo_handle:
2640 self.outf.write("Deleting remote TDO.\n")
2641 remote_lsa.DeleteObject(remote_tdo_handle)
2642 remote_tdo_handle = None
2643 if local_tdo_handle:
2644 self.outf.write("Deleting local TDO.\n")
2645 local_lsa.DeleteObject(local_tdo_handle)
2646 local_tdo_handle = None
2647 if current_request['location'] is "remote":
2648 raise self.RemoteRuntimeError(self, error, "%s" % (
2649 current_request['name']))
2650 raise self.LocalRuntimeError(self, error, "%s" % (
2651 current_request['name']))
2653 if validate:
2654 if local_trust_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2655 self.outf.write("Setup local forest trust information...\n")
2656 try:
2657 # get all information about the remote trust
2658 # this triggers netr_GetForestTrustInformation to the remote domain
2659 # and lsaRSetForestTrustInformation() locally, but new top level
2660 # names are disabled by default.
2661 local_forest_info = \
2662 local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
2663 remote_lsa_info.dns_domain.string,
2664 netlogon.DS_GFTI_UPDATE_TDO)
2665 except RuntimeError as error:
2666 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2668 try:
2669 # here we try to enable all top level names
2670 local_forest_collision = \
2671 local_lsa.lsaRSetForestTrustInformation(local_policy,
2672 remote_lsa_info.dns_domain,
2673 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2674 local_forest_info,
2676 except RuntimeError as error:
2677 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2679 self.write_forest_trust_info(local_forest_info,
2680 tln=remote_lsa_info.dns_domain.string,
2681 collisions=local_forest_collision)
2683 if remote_trust_info:
2684 self.outf.write("Setup remote forest trust information...\n")
2685 try:
2686 # get all information about the local trust (from the perspective of the remote domain)
2687 # this triggers netr_GetForestTrustInformation to our domain.
2688 # and lsaRSetForestTrustInformation() remotely, but new top level
2689 # names are disabled by default.
2690 remote_forest_info = \
2691 remote_netlogon.netr_DsRGetForestTrustInformation(remote_netlogon_dc_unc,
2692 local_lsa_info.dns_domain.string,
2693 netlogon.DS_GFTI_UPDATE_TDO)
2694 except RuntimeError as error:
2695 raise self.RemoteRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2697 try:
2698 # here we try to enable all top level names
2699 remote_forest_collision = \
2700 remote_lsa.lsaRSetForestTrustInformation(remote_policy,
2701 local_lsa_info.dns_domain,
2702 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2703 remote_forest_info,
2705 except RuntimeError as error:
2706 raise self.RemoteRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2708 self.write_forest_trust_info(remote_forest_info,
2709 tln=local_lsa_info.dns_domain.string,
2710 collisions=remote_forest_collision)
2712 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2713 self.outf.write("Validating outgoing trust...\n")
2714 try:
2715 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_netlogon_info.dc_unc,
2716 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2718 remote_lsa_info.dns_domain.string)
2719 except RuntimeError as error:
2720 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2722 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2723 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2725 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2726 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2727 local_trust_verify.trusted_dc_name,
2728 local_trust_verify.tc_connection_status[1],
2729 local_trust_verify.pdc_connection_status[1])
2730 else:
2731 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2732 local_trust_verify.trusted_dc_name,
2733 local_trust_verify.tc_connection_status[1],
2734 local_trust_verify.pdc_connection_status[1])
2736 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2737 raise CommandError(local_validation)
2738 else:
2739 self.outf.write("OK: %s\n" % local_validation)
2741 if remote_trust_info:
2742 if remote_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2743 self.outf.write("Validating incoming trust...\n")
2744 try:
2745 remote_trust_verify = \
2746 remote_netlogon.netr_LogonControl2Ex(remote_netlogon_dc_unc,
2747 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2749 local_lsa_info.dns_domain.string)
2750 except RuntimeError as error:
2751 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2753 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2754 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2756 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2757 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2758 remote_trust_verify.trusted_dc_name,
2759 remote_trust_verify.tc_connection_status[1],
2760 remote_trust_verify.pdc_connection_status[1])
2761 else:
2762 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2763 remote_trust_verify.trusted_dc_name,
2764 remote_trust_verify.tc_connection_status[1],
2765 remote_trust_verify.pdc_connection_status[1])
2767 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
2768 raise CommandError(remote_validation)
2769 else:
2770 self.outf.write("OK: %s\n" % remote_validation)
2772 if remote_tdo_handle is not None:
2773 try:
2774 remote_lsa.Close(remote_tdo_handle)
2775 except RuntimeError as error:
2776 pass
2777 remote_tdo_handle = None
2778 if local_tdo_handle is not None:
2779 try:
2780 local_lsa.Close(local_tdo_handle)
2781 except RuntimeError as error:
2782 pass
2783 local_tdo_handle = None
2785 self.outf.write("Success.\n")
2786 return
2788 class cmd_domain_trust_delete(DomainTrustCommand):
2789 """Delete a domain trust."""
2791 synopsis = "%prog DOMAIN [options]"
2793 takes_optiongroups = {
2794 "sambaopts": options.SambaOptions,
2795 "versionopts": options.VersionOptions,
2796 "credopts": options.CredentialsOptions,
2797 "localdcopts": LocalDCCredentialsOptions,
2800 takes_options = [
2801 Option("--delete-location", type="choice", metavar="LOCATION",
2802 choices=["local", "both"],
2803 help="Where to delete the trusted domain object: 'local' or 'both'.",
2804 dest='delete_location',
2805 default="both"),
2808 takes_args = ["domain"]
2810 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2811 delete_location=None):
2813 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2814 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2815 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2817 if delete_location == "local":
2818 remote_policy_access = None
2819 else:
2820 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2821 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2822 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2824 local_server = self.setup_local_server(sambaopts, localdcopts)
2825 try:
2826 local_lsa = self.new_local_lsa_connection()
2827 except RuntimeError as error:
2828 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2830 try:
2831 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2832 except RuntimeError as error:
2833 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2835 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2836 local_lsa_info.name.string,
2837 local_lsa_info.dns_domain.string,
2838 local_lsa_info.sid))
2840 local_tdo_info = None
2841 local_tdo_handle = None
2842 remote_tdo_info = None
2843 remote_tdo_handle = None
2845 lsaString = lsa.String()
2846 try:
2847 lsaString.string = domain
2848 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2849 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2850 except NTSTATUSError as error:
2851 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2852 raise CommandError("Failed to find trust for domain '%s'" % domain)
2853 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2856 if remote_policy_access is not None:
2857 try:
2858 remote_server = self.setup_remote_server(credopts, domain)
2859 except RuntimeError as error:
2860 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2862 try:
2863 remote_lsa = self.new_remote_lsa_connection()
2864 except RuntimeError as error:
2865 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2867 try:
2868 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2869 except RuntimeError as error:
2870 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2872 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2873 remote_lsa_info.name.string,
2874 remote_lsa_info.dns_domain.string,
2875 remote_lsa_info.sid))
2877 if remote_lsa_info.sid != local_tdo_info.sid or \
2878 remote_lsa_info.name.string != local_tdo_info.netbios_name.string or \
2879 remote_lsa_info.dns_domain.string != local_tdo_info.domain_name.string:
2880 raise CommandError("LocalTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2881 local_tdo_info.netbios_name.string,
2882 local_tdo_info.domain_name.string,
2883 local_tdo_info.sid))
2885 try:
2886 lsaString.string = local_lsa_info.dns_domain.string
2887 remote_tdo_info = \
2888 remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2889 lsaString,
2890 lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2891 except NTSTATUSError as error:
2892 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2893 raise self.RemoteRuntimeError(self, error, "QueryTrustedDomainInfoByName(%s)" % (
2894 lsaString.string))
2895 pass
2897 if remote_tdo_info is not None:
2898 if local_lsa_info.sid != remote_tdo_info.sid or \
2899 local_lsa_info.name.string != remote_tdo_info.netbios_name.string or \
2900 local_lsa_info.dns_domain.string != remote_tdo_info.domain_name.string:
2901 raise CommandError("RemoteTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2902 remote_tdo_info.netbios_name.string,
2903 remote_tdo_info.domain_name.string,
2904 remote_tdo_info.sid))
2906 if local_tdo_info is not None:
2907 try:
2908 lsaString.string = local_tdo_info.domain_name.string
2909 local_tdo_handle = \
2910 local_lsa.OpenTrustedDomainByName(local_policy,
2911 lsaString,
2912 security.SEC_STD_DELETE)
2913 except RuntimeError as error:
2914 raise self.LocalRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2915 lsaString.string))
2917 local_lsa.DeleteObject(local_tdo_handle)
2918 local_tdo_handle = None
2920 if remote_tdo_info is not None:
2921 try:
2922 lsaString.string = remote_tdo_info.domain_name.string
2923 remote_tdo_handle = \
2924 remote_lsa.OpenTrustedDomainByName(remote_policy,
2925 lsaString,
2926 security.SEC_STD_DELETE)
2927 except RuntimeError as error:
2928 raise self.RemoteRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2929 lsaString.string))
2931 if remote_tdo_handle is not None:
2932 try:
2933 remote_lsa.DeleteObject(remote_tdo_handle)
2934 remote_tdo_handle = None
2935 self.outf.write("RemoteTDO deleted.\n")
2936 except RuntimeError as error:
2937 self.outf.write("%s\n" % self.RemoteRuntimeError(self, error, "DeleteObject() failed"))
2939 if local_tdo_handle is not None:
2940 try:
2941 local_lsa.DeleteObject(local_tdo_handle)
2942 local_tdo_handle = None
2943 self.outf.write("LocalTDO deleted.\n")
2944 except RuntimeError as error:
2945 self.outf.write("%s\n" % self.LocalRuntimeError(self, error, "DeleteObject() failed"))
2947 return
2949 class cmd_domain_trust_validate(DomainTrustCommand):
2950 """Validate a domain trust."""
2952 synopsis = "%prog DOMAIN [options]"
2954 takes_optiongroups = {
2955 "sambaopts": options.SambaOptions,
2956 "versionopts": options.VersionOptions,
2957 "credopts": options.CredentialsOptions,
2958 "localdcopts": LocalDCCredentialsOptions,
2961 takes_options = [
2962 Option("--validate-location", type="choice", metavar="LOCATION",
2963 choices=["local", "both"],
2964 help="Where to validate the trusted domain object: 'local' or 'both'.",
2965 dest='validate_location',
2966 default="both"),
2969 takes_args = ["domain"]
2971 def run(self, domain, sambaopts=None, versionopts=None, credopts=None, localdcopts=None,
2972 validate_location=None):
2974 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2976 local_server = self.setup_local_server(sambaopts, localdcopts)
2977 try:
2978 local_lsa = self.new_local_lsa_connection()
2979 except RuntimeError as error:
2980 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2982 try:
2983 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2984 except RuntimeError as error:
2985 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2987 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2988 local_lsa_info.name.string,
2989 local_lsa_info.dns_domain.string,
2990 local_lsa_info.sid))
2992 try:
2993 lsaString = lsa.String()
2994 lsaString.string = domain
2995 local_tdo_info = \
2996 local_lsa.QueryTrustedDomainInfoByName(local_policy,
2997 lsaString,
2998 lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2999 except NTSTATUSError as error:
3000 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
3001 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
3003 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
3005 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
3006 local_tdo_info.netbios_name.string,
3007 local_tdo_info.domain_name.string,
3008 local_tdo_info.sid))
3010 try:
3011 local_netlogon = self.new_local_netlogon_connection()
3012 except RuntimeError as error:
3013 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3015 try:
3016 local_trust_verify = \
3017 local_netlogon.netr_LogonControl2Ex(local_server,
3018 netlogon.NETLOGON_CONTROL_TC_VERIFY,
3020 local_tdo_info.domain_name.string)
3021 except RuntimeError as error:
3022 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
3024 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
3025 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
3027 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
3028 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
3029 local_trust_verify.trusted_dc_name,
3030 local_trust_verify.tc_connection_status[1],
3031 local_trust_verify.pdc_connection_status[1])
3032 else:
3033 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
3034 local_trust_verify.trusted_dc_name,
3035 local_trust_verify.tc_connection_status[1],
3036 local_trust_verify.pdc_connection_status[1])
3038 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
3039 raise CommandError(local_validation)
3040 else:
3041 self.outf.write("OK: %s\n" % local_validation)
3043 try:
3044 server = local_trust_verify.trusted_dc_name.replace('\\', '')
3045 domain_and_server = "%s\\%s" % (local_tdo_info.domain_name.string, server)
3046 local_trust_rediscover = \
3047 local_netlogon.netr_LogonControl2Ex(local_server,
3048 netlogon.NETLOGON_CONTROL_REDISCOVER,
3050 domain_and_server)
3051 except RuntimeError as error:
3052 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
3054 local_conn_status = self._uint32(local_trust_rediscover.tc_connection_status[0])
3055 local_rediscover = "LocalRediscover: DC[%s] CONNECTION[%s]" % (
3056 local_trust_rediscover.trusted_dc_name,
3057 local_trust_rediscover.tc_connection_status[1])
3059 if local_conn_status != werror.WERR_SUCCESS:
3060 raise CommandError(local_rediscover)
3061 else:
3062 self.outf.write("OK: %s\n" % local_rediscover)
3064 if validate_location != "local":
3065 try:
3066 remote_server = self.setup_remote_server(credopts, domain, require_pdc=False)
3067 except RuntimeError as error:
3068 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
3070 try:
3071 remote_netlogon = self.new_remote_netlogon_connection()
3072 except RuntimeError as error:
3073 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
3075 try:
3076 remote_trust_verify = \
3077 remote_netlogon.netr_LogonControl2Ex(remote_server,
3078 netlogon.NETLOGON_CONTROL_TC_VERIFY,
3080 local_lsa_info.dns_domain.string)
3081 except RuntimeError as error:
3082 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
3084 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
3085 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
3087 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
3088 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
3089 remote_trust_verify.trusted_dc_name,
3090 remote_trust_verify.tc_connection_status[1],
3091 remote_trust_verify.pdc_connection_status[1])
3092 else:
3093 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
3094 remote_trust_verify.trusted_dc_name,
3095 remote_trust_verify.tc_connection_status[1],
3096 remote_trust_verify.pdc_connection_status[1])
3098 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
3099 raise CommandError(remote_validation)
3100 else:
3101 self.outf.write("OK: %s\n" % remote_validation)
3103 try:
3104 server = remote_trust_verify.trusted_dc_name.replace('\\', '')
3105 domain_and_server = "%s\\%s" % (local_lsa_info.dns_domain.string, server)
3106 remote_trust_rediscover = \
3107 remote_netlogon.netr_LogonControl2Ex(remote_server,
3108 netlogon.NETLOGON_CONTROL_REDISCOVER,
3110 domain_and_server)
3111 except RuntimeError as error:
3112 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
3114 remote_conn_status = self._uint32(remote_trust_rediscover.tc_connection_status[0])
3116 remote_rediscover = "RemoteRediscover: DC[%s] CONNECTION[%s]" % (
3117 remote_trust_rediscover.trusted_dc_name,
3118 remote_trust_rediscover.tc_connection_status[1])
3120 if remote_conn_status != werror.WERR_SUCCESS:
3121 raise CommandError(remote_rediscover)
3122 else:
3123 self.outf.write("OK: %s\n" % remote_rediscover)
3125 return
3127 class cmd_domain_trust_namespaces(DomainTrustCommand):
3128 """Manage forest trust namespaces."""
3130 synopsis = "%prog [DOMAIN] [options]"
3132 takes_optiongroups = {
3133 "sambaopts": options.SambaOptions,
3134 "versionopts": options.VersionOptions,
3135 "localdcopts": LocalDCCredentialsOptions,
3138 takes_options = [
3139 Option("--refresh", type="choice", metavar="check|store",
3140 choices=["check", "store", None],
3141 help="List and maybe store refreshed forest trust information: 'check' or 'store'.",
3142 dest='refresh',
3143 default=None),
3144 Option("--enable-all", action="store_true",
3145 help="Try to update disabled entries, not allowed with --refresh=check.",
3146 dest='enable_all',
3147 default=False),
3148 Option("--enable-tln", action="append", metavar='DNSDOMAIN',
3149 help="Enable a top level name entry. Can be specified multiple times.",
3150 dest='enable_tln',
3151 default=[]),
3152 Option("--disable-tln", action="append", metavar='DNSDOMAIN',
3153 help="Disable a top level name entry. Can be specified multiple times.",
3154 dest='disable_tln',
3155 default=[]),
3156 Option("--add-tln-ex", action="append", metavar='DNSDOMAIN',
3157 help="Add a top level exclusion entry. Can be specified multiple times.",
3158 dest='add_tln_ex',
3159 default=[]),
3160 Option("--delete-tln-ex", action="append", metavar='DNSDOMAIN',
3161 help="Delete a top level exclusion entry. Can be specified multiple times.",
3162 dest='delete_tln_ex',
3163 default=[]),
3164 Option("--enable-nb", action="append", metavar='NETBIOSDOMAIN',
3165 help="Enable a netbios name in a domain entry. Can be specified multiple times.",
3166 dest='enable_nb',
3167 default=[]),
3168 Option("--disable-nb", action="append", metavar='NETBIOSDOMAIN',
3169 help="Disable a netbios name in a domain entry. Can be specified multiple times.",
3170 dest='disable_nb',
3171 default=[]),
3172 Option("--enable-sid", action="append", metavar='DOMAINSID',
3173 help="Enable a SID in a domain entry. Can be specified multiple times.",
3174 dest='enable_sid_str',
3175 default=[]),
3176 Option("--disable-sid", action="append", metavar='DOMAINSID',
3177 help="Disable a SID in a domain entry. Can be specified multiple times.",
3178 dest='disable_sid_str',
3179 default=[]),
3180 Option("--add-upn-suffix", action="append", metavar='DNSDOMAIN',
3181 help="Add a new uPNSuffixes attribute for the local forest. Can be specified multiple times.",
3182 dest='add_upn',
3183 default=[]),
3184 Option("--delete-upn-suffix", action="append", metavar='DNSDOMAIN',
3185 help="Delete an existing uPNSuffixes attribute of the local forest. Can be specified multiple times.",
3186 dest='delete_upn',
3187 default=[]),
3188 Option("--add-spn-suffix", action="append", metavar='DNSDOMAIN',
3189 help="Add a new msDS-SPNSuffixes attribute for the local forest. Can be specified multiple times.",
3190 dest='add_spn',
3191 default=[]),
3192 Option("--delete-spn-suffix", action="append", metavar='DNSDOMAIN',
3193 help="Delete an existing msDS-SPNSuffixes attribute of the local forest. Can be specified multiple times.",
3194 dest='delete_spn',
3195 default=[]),
3198 takes_args = ["domain?"]
3200 def run(self, domain=None, sambaopts=None, localdcopts=None, versionopts=None,
3201 refresh=None, enable_all=False,
3202 enable_tln=[], disable_tln=[], add_tln_ex=[], delete_tln_ex=[],
3203 enable_sid_str=[], disable_sid_str=[], enable_nb=[], disable_nb=[],
3204 add_upn=[], delete_upn=[], add_spn=[], delete_spn=[]):
3206 require_update = False
3208 if domain is None:
3209 if refresh == "store":
3210 raise CommandError("--refresh=%s not allowed without DOMAIN" % refresh)
3212 if enable_all:
3213 raise CommandError("--enable-all not allowed without DOMAIN")
3215 if len(enable_tln) > 0:
3216 raise CommandError("--enable-tln not allowed without DOMAIN")
3217 if len(disable_tln) > 0:
3218 raise CommandError("--disable-tln not allowed without DOMAIN")
3220 if len(add_tln_ex) > 0:
3221 raise CommandError("--add-tln-ex not allowed without DOMAIN")
3222 if len(delete_tln_ex) > 0:
3223 raise CommandError("--delete-tln-ex not allowed without DOMAIN")
3225 if len(enable_nb) > 0:
3226 raise CommandError("--enable-nb not allowed without DOMAIN")
3227 if len(disable_nb) > 0:
3228 raise CommandError("--disable-nb not allowed without DOMAIN")
3230 if len(enable_sid_str) > 0:
3231 raise CommandError("--enable-sid not allowed without DOMAIN")
3232 if len(disable_sid_str) > 0:
3233 raise CommandError("--disable-sid not allowed without DOMAIN")
3235 if len(add_upn) > 0:
3236 for n in add_upn:
3237 if not n.startswith("*."):
3238 continue
3239 raise CommandError("value[%s] specified for --add-upn-suffix should not include with '*.'" % n)
3240 require_update = True
3241 if len(delete_upn) > 0:
3242 for n in delete_upn:
3243 if not n.startswith("*."):
3244 continue
3245 raise CommandError("value[%s] specified for --delete-upn-suffix should not include with '*.'" % n)
3246 require_update = True
3247 for a in add_upn:
3248 for d in delete_upn:
3249 if a.lower() != d.lower():
3250 continue
3251 raise CommandError("value[%s] specified for --add-upn-suffix and --delete-upn-suffix" % a)
3253 if len(add_spn) > 0:
3254 for n in add_spn:
3255 if not n.startswith("*."):
3256 continue
3257 raise CommandError("value[%s] specified for --add-spn-suffix should not include with '*.'" % n)
3258 require_update = True
3259 if len(delete_spn) > 0:
3260 for n in delete_spn:
3261 if not n.startswith("*."):
3262 continue
3263 raise CommandError("value[%s] specified for --delete-spn-suffix should not include with '*.'" % n)
3264 require_update = True
3265 for a in add_spn:
3266 for d in delete_spn:
3267 if a.lower() != d.lower():
3268 continue
3269 raise CommandError("value[%s] specified for --add-spn-suffix and --delete-spn-suffix" % a)
3270 else:
3271 if len(add_upn) > 0:
3272 raise CommandError("--add-upn-suffix not allowed together with DOMAIN")
3273 if len(delete_upn) > 0:
3274 raise CommandError("--delete-upn-suffix not allowed together with DOMAIN")
3275 if len(add_spn) > 0:
3276 raise CommandError("--add-spn-suffix not allowed together with DOMAIN")
3277 if len(delete_spn) > 0:
3278 raise CommandError("--delete-spn-suffix not allowed together with DOMAIN")
3280 if refresh is not None:
3281 if refresh == "store":
3282 require_update = True
3284 if enable_all and refresh != "store":
3285 raise CommandError("--enable-all not allowed together with --refresh=%s" % refresh)
3287 if len(enable_tln) > 0:
3288 raise CommandError("--enable-tln not allowed together with --refresh")
3289 if len(disable_tln) > 0:
3290 raise CommandError("--disable-tln not allowed together with --refresh")
3292 if len(add_tln_ex) > 0:
3293 raise CommandError("--add-tln-ex not allowed together with --refresh")
3294 if len(delete_tln_ex) > 0:
3295 raise CommandError("--delete-tln-ex not allowed together with --refresh")
3297 if len(enable_nb) > 0:
3298 raise CommandError("--enable-nb not allowed together with --refresh")
3299 if len(disable_nb) > 0:
3300 raise CommandError("--disable-nb not allowed together with --refresh")
3302 if len(enable_sid_str) > 0:
3303 raise CommandError("--enable-sid not allowed together with --refresh")
3304 if len(disable_sid_str) > 0:
3305 raise CommandError("--disable-sid not allowed together with --refresh")
3306 else:
3307 if enable_all:
3308 require_update = True
3310 if len(enable_tln) > 0:
3311 raise CommandError("--enable-tln not allowed together with --enable-all")
3313 if len(enable_nb) > 0:
3314 raise CommandError("--enable-nb not allowed together with --enable-all")
3316 if len(enable_sid_str) > 0:
3317 raise CommandError("--enable-sid not allowed together with --enable-all")
3319 if len(enable_tln) > 0:
3320 require_update = True
3321 if len(disable_tln) > 0:
3322 require_update = True
3323 for e in enable_tln:
3324 for d in disable_tln:
3325 if e.lower() != d.lower():
3326 continue
3327 raise CommandError("value[%s] specified for --enable-tln and --disable-tln" % e)
3329 if len(add_tln_ex) > 0:
3330 for n in add_tln_ex:
3331 if not n.startswith("*."):
3332 continue
3333 raise CommandError("value[%s] specified for --add-tln-ex should not include with '*.'" % n)
3334 require_update = True
3335 if len(delete_tln_ex) > 0:
3336 for n in delete_tln_ex:
3337 if not n.startswith("*."):
3338 continue
3339 raise CommandError("value[%s] specified for --delete-tln-ex should not include with '*.'" % n)
3340 require_update = True
3341 for a in add_tln_ex:
3342 for d in delete_tln_ex:
3343 if a.lower() != d.lower():
3344 continue
3345 raise CommandError("value[%s] specified for --add-tln-ex and --delete-tln-ex" % a)
3347 if len(enable_nb) > 0:
3348 require_update = True
3349 if len(disable_nb) > 0:
3350 require_update = True
3351 for e in enable_nb:
3352 for d in disable_nb:
3353 if e.upper() != d.upper():
3354 continue
3355 raise CommandError("value[%s] specified for --enable-nb and --disable-nb" % e)
3357 enable_sid = []
3358 for s in enable_sid_str:
3359 try:
3360 sid = security.dom_sid(s)
3361 except TypeError as error:
3362 raise CommandError("value[%s] specified for --enable-sid is not a valid SID" % s)
3363 enable_sid.append(sid)
3364 disable_sid = []
3365 for s in disable_sid_str:
3366 try:
3367 sid = security.dom_sid(s)
3368 except TypeError as error:
3369 raise CommandError("value[%s] specified for --disable-sid is not a valid SID" % s)
3370 disable_sid.append(sid)
3371 if len(enable_sid) > 0:
3372 require_update = True
3373 if len(disable_sid) > 0:
3374 require_update = True
3375 for e in enable_sid:
3376 for d in disable_sid:
3377 if e != d:
3378 continue
3379 raise CommandError("value[%s] specified for --enable-sid and --disable-sid" % e)
3381 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
3382 if require_update:
3383 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
3385 local_server = self.setup_local_server(sambaopts, localdcopts)
3386 try:
3387 local_lsa = self.new_local_lsa_connection()
3388 except RuntimeError as error:
3389 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
3391 try:
3392 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
3393 except RuntimeError as error:
3394 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
3396 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
3397 local_lsa_info.name.string,
3398 local_lsa_info.dns_domain.string,
3399 local_lsa_info.sid))
3401 if domain is None:
3402 try:
3403 local_netlogon = self.new_local_netlogon_connection()
3404 except RuntimeError as error:
3405 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3407 try:
3408 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3409 except RuntimeError as error:
3410 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3412 if local_netlogon_info.domain_name != local_netlogon_info.forest_name:
3413 raise CommandError("The local domain [%s] is not the forest root [%s]" % (
3414 local_netlogon_info.domain_name,
3415 local_netlogon_info.forest_name))
3417 try:
3418 # get all information about our own forest
3419 own_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3420 None, 0)
3421 except RuntimeError as error:
3422 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
3423 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3424 self.local_server))
3426 if self.check_runtime_error(error, werror.WERR_INVALID_FUNCTION):
3427 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3428 self.local_server))
3430 if self.check_runtime_error(error, werror.WERR_NERR_ACFNOTLOADED):
3431 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3432 self.local_server))
3434 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3436 self.outf.write("Own forest trust information...\n")
3437 self.write_forest_trust_info(own_forest_info,
3438 tln=local_lsa_info.dns_domain.string)
3440 try:
3441 local_samdb = self.new_local_ldap_connection()
3442 except RuntimeError as error:
3443 raise self.LocalRuntimeError(self, error, "failed to connect to SamDB")
3445 local_partitions_dn = "CN=Partitions,%s" % str(local_samdb.get_config_basedn())
3446 attrs = ['uPNSuffixes', 'msDS-SPNSuffixes']
3447 try:
3448 msgs = local_samdb.search(base=local_partitions_dn,
3449 scope=ldb.SCOPE_BASE,
3450 expression="(objectClass=crossRefContainer)",
3451 attrs=attrs)
3452 stored_msg = msgs[0]
3453 except ldb.LdbError as error:
3454 raise self.LocalLdbError(self, error, "failed to search partition dn")
3456 stored_upn_vals = []
3457 if 'uPNSuffixes' in stored_msg:
3458 stored_upn_vals.extend(stored_msg['uPNSuffixes'])
3460 stored_spn_vals = []
3461 if 'msDS-SPNSuffixes' in stored_msg:
3462 stored_spn_vals.extend(stored_msg['msDS-SPNSuffixes'])
3464 self.outf.write("Stored uPNSuffixes attributes[%d]:\n" % len(stored_upn_vals))
3465 for v in stored_upn_vals:
3466 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3467 self.outf.write("Stored msDS-SPNSuffixes attributes[%d]:\n" % len(stored_spn_vals))
3468 for v in stored_spn_vals:
3469 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3471 if not require_update:
3472 return
3474 replace_upn = False
3475 update_upn_vals = []
3476 update_upn_vals.extend(stored_upn_vals)
3478 replace_spn = False
3479 update_spn_vals = []
3480 update_spn_vals.extend(stored_spn_vals)
3482 for upn in add_upn:
3483 for i, v in enumerate(update_upn_vals):
3484 if v.lower() == upn.lower():
3485 raise CommandError("Entry already present for "
3486 "value[%s] specified for "
3487 "--add-upn-suffix" % upn)
3488 update_upn_vals.append(upn)
3489 replace_upn = True
3491 for upn in delete_upn:
3492 idx = None
3493 for i, v in enumerate(update_upn_vals):
3494 if v.lower() != upn.lower():
3495 continue
3496 idx = i
3497 break
3498 if idx is None:
3499 raise CommandError("Entry not found for value[%s] specified for --delete-upn-suffix" % upn)
3501 update_upn_vals.pop(idx)
3502 replace_upn = True
3504 for spn in add_spn:
3505 for i, v in enumerate(update_spn_vals):
3506 if v.lower() == spn.lower():
3507 raise CommandError("Entry already present for "
3508 "value[%s] specified for "
3509 "--add-spn-suffix" % spn)
3510 update_spn_vals.append(spn)
3511 replace_spn = True
3513 for spn in delete_spn:
3514 idx = None
3515 for i, v in enumerate(update_spn_vals):
3516 if v.lower() != spn.lower():
3517 continue
3518 idx = i
3519 break
3520 if idx is None:
3521 raise CommandError("Entry not found for value[%s] specified for --delete-spn-suffix" % spn)
3523 update_spn_vals.pop(idx)
3524 replace_spn = True
3526 self.outf.write("Update uPNSuffixes attributes[%d]:\n" % len(update_upn_vals))
3527 for v in update_upn_vals:
3528 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3529 self.outf.write("Update msDS-SPNSuffixes attributes[%d]:\n" % len(update_spn_vals))
3530 for v in update_spn_vals:
3531 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3533 update_msg = ldb.Message()
3534 update_msg.dn = stored_msg.dn
3536 if replace_upn:
3537 update_msg['uPNSuffixes'] = ldb.MessageElement(update_upn_vals,
3538 ldb.FLAG_MOD_REPLACE,
3539 'uPNSuffixes')
3540 if replace_spn:
3541 update_msg['msDS-SPNSuffixes'] = ldb.MessageElement(update_spn_vals,
3542 ldb.FLAG_MOD_REPLACE,
3543 'msDS-SPNSuffixes')
3544 try:
3545 local_samdb.modify(update_msg)
3546 except ldb.LdbError as error:
3547 raise self.LocalLdbError(self, error, "failed to update partition dn")
3549 try:
3550 stored_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3551 None, 0)
3552 except RuntimeError as error:
3553 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3555 self.outf.write("Stored forest trust information...\n")
3556 self.write_forest_trust_info(stored_forest_info,
3557 tln=local_lsa_info.dns_domain.string)
3558 return
3560 try:
3561 lsaString = lsa.String()
3562 lsaString.string = domain
3563 local_tdo_info = \
3564 local_lsa.QueryTrustedDomainInfoByName(local_policy,
3565 lsaString,
3566 lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
3567 except NTSTATUSError as error:
3568 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
3569 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
3571 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
3573 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
3574 local_tdo_info.netbios_name.string,
3575 local_tdo_info.domain_name.string,
3576 local_tdo_info.sid))
3578 if not local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
3579 raise CommandError("trusted domain object for domain [%s] is not marked as FOREST_TRANSITIVE." % domain)
3581 if refresh is not None:
3582 try:
3583 local_netlogon = self.new_local_netlogon_connection()
3584 except RuntimeError as error:
3585 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3587 try:
3588 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3589 except RuntimeError as error:
3590 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3592 lsa_update_check = 1
3593 if refresh == "store":
3594 netlogon_update_tdo = netlogon.DS_GFTI_UPDATE_TDO
3595 if enable_all:
3596 lsa_update_check = 0
3597 else:
3598 netlogon_update_tdo = 0
3600 try:
3601 # get all information about the remote trust
3602 # this triggers netr_GetForestTrustInformation to the remote domain
3603 # and lsaRSetForestTrustInformation() locally, but new top level
3604 # names are disabled by default.
3605 fresh_forest_info = \
3606 local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3607 local_tdo_info.domain_name.string,
3608 netlogon_update_tdo)
3609 except RuntimeError as error:
3610 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3612 try:
3613 fresh_forest_collision = \
3614 local_lsa.lsaRSetForestTrustInformation(local_policy,
3615 local_tdo_info.domain_name,
3616 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3617 fresh_forest_info,
3618 lsa_update_check)
3619 except RuntimeError as error:
3620 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3622 self.outf.write("Fresh forest trust information...\n")
3623 self.write_forest_trust_info(fresh_forest_info,
3624 tln=local_tdo_info.domain_name.string,
3625 collisions=fresh_forest_collision)
3627 if refresh == "store":
3628 try:
3629 lsaString = lsa.String()
3630 lsaString.string = local_tdo_info.domain_name.string
3631 stored_forest_info = \
3632 local_lsa.lsaRQueryForestTrustInformation(local_policy,
3633 lsaString,
3634 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3635 except RuntimeError as error:
3636 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3638 self.outf.write("Stored forest trust information...\n")
3639 self.write_forest_trust_info(stored_forest_info,
3640 tln=local_tdo_info.domain_name.string)
3642 return
3645 # The none --refresh path
3648 try:
3649 lsaString = lsa.String()
3650 lsaString.string = local_tdo_info.domain_name.string
3651 local_forest_info = \
3652 local_lsa.lsaRQueryForestTrustInformation(local_policy,
3653 lsaString,
3654 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3655 except RuntimeError as error:
3656 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3658 self.outf.write("Local forest trust information...\n")
3659 self.write_forest_trust_info(local_forest_info,
3660 tln=local_tdo_info.domain_name.string)
3662 if not require_update:
3663 return
3665 entries = []
3666 entries.extend(local_forest_info.entries)
3667 update_forest_info = lsa.ForestTrustInformation()
3668 update_forest_info.count = len(entries)
3669 update_forest_info.entries = entries
3671 if enable_all:
3672 for i, r in enumerate(update_forest_info.entries):
3673 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3674 continue
3675 if update_forest_info.entries[i].flags == 0:
3676 continue
3677 update_forest_info.entries[i].time = 0
3678 update_forest_info.entries[i].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3679 for i, r in enumerate(update_forest_info.entries):
3680 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3681 continue
3682 if update_forest_info.entries[i].flags == 0:
3683 continue
3684 update_forest_info.entries[i].time = 0
3685 update_forest_info.entries[i].flags &= ~lsa.LSA_NB_DISABLED_MASK
3686 update_forest_info.entries[i].flags &= ~lsa.LSA_SID_DISABLED_MASK
3688 for tln in enable_tln:
3689 idx = None
3690 for i, r in enumerate(update_forest_info.entries):
3691 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3692 continue
3693 if r.forest_trust_data.string.lower() != tln.lower():
3694 continue
3695 idx = i
3696 break
3697 if idx is None:
3698 raise CommandError("Entry not found for value[%s] specified for --enable-tln" % tln)
3699 if not update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_MASK:
3700 raise CommandError("Entry found for value[%s] specified for --enable-tln is already enabled" % tln)
3701 update_forest_info.entries[idx].time = 0
3702 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3704 for tln in disable_tln:
3705 idx = None
3706 for i, r in enumerate(update_forest_info.entries):
3707 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3708 continue
3709 if r.forest_trust_data.string.lower() != tln.lower():
3710 continue
3711 idx = i
3712 break
3713 if idx is None:
3714 raise CommandError("Entry not found for value[%s] specified for --disable-tln" % tln)
3715 if update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_ADMIN:
3716 raise CommandError("Entry found for value[%s] specified for --disable-tln is already disabled" % tln)
3717 update_forest_info.entries[idx].time = 0
3718 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3719 update_forest_info.entries[idx].flags |= lsa.LSA_TLN_DISABLED_ADMIN
3721 for tln_ex in add_tln_ex:
3722 idx = None
3723 for i, r in enumerate(update_forest_info.entries):
3724 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3725 continue
3726 if r.forest_trust_data.string.lower() != tln_ex.lower():
3727 continue
3728 idx = i
3729 break
3730 if idx is not None:
3731 raise CommandError("Entry already present for value[%s] specified for --add-tln-ex" % tln_ex)
3733 tln_dot = ".%s" % tln_ex.lower()
3734 idx = None
3735 for i, r in enumerate(update_forest_info.entries):
3736 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3737 continue
3738 r_dot = ".%s" % r.forest_trust_data.string.lower()
3739 if tln_dot == r_dot:
3740 raise CommandError("TLN entry present for value[%s] specified for --add-tln-ex" % tln_ex)
3741 if not tln_dot.endswith(r_dot):
3742 continue
3743 idx = i
3744 break
3746 if idx is None:
3747 raise CommandError("No TLN parent present for value[%s] specified for --add-tln-ex" % tln_ex)
3749 r = lsa.ForestTrustRecord()
3750 r.type = lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX
3751 r.flags = 0
3752 r.time = 0
3753 r.forest_trust_data.string = tln_ex
3755 entries = []
3756 entries.extend(update_forest_info.entries)
3757 entries.insert(idx + 1, r)
3758 update_forest_info.count = len(entries)
3759 update_forest_info.entries = entries
3761 for tln_ex in delete_tln_ex:
3762 idx = None
3763 for i, r in enumerate(update_forest_info.entries):
3764 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3765 continue
3766 if r.forest_trust_data.string.lower() != tln_ex.lower():
3767 continue
3768 idx = i
3769 break
3770 if idx is None:
3771 raise CommandError("Entry not found for value[%s] specified for --delete-tln-ex" % tln_ex)
3773 entries = []
3774 entries.extend(update_forest_info.entries)
3775 entries.pop(idx)
3776 update_forest_info.count = len(entries)
3777 update_forest_info.entries = entries
3779 for nb in enable_nb:
3780 idx = None
3781 for i, r in enumerate(update_forest_info.entries):
3782 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3783 continue
3784 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3785 continue
3786 idx = i
3787 break
3788 if idx is None:
3789 raise CommandError("Entry not found for value[%s] specified for --enable-nb" % nb)
3790 if not update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_MASK:
3791 raise CommandError("Entry found for value[%s] specified for --enable-nb is already enabled" % nb)
3792 update_forest_info.entries[idx].time = 0
3793 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3795 for nb in disable_nb:
3796 idx = None
3797 for i, r in enumerate(update_forest_info.entries):
3798 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3799 continue
3800 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3801 continue
3802 idx = i
3803 break
3804 if idx is None:
3805 raise CommandError("Entry not found for value[%s] specified for --delete-nb" % nb)
3806 if update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_ADMIN:
3807 raise CommandError("Entry found for value[%s] specified for --disable-nb is already disabled" % nb)
3808 update_forest_info.entries[idx].time = 0
3809 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3810 update_forest_info.entries[idx].flags |= lsa.LSA_NB_DISABLED_ADMIN
3812 for sid in enable_sid:
3813 idx = None
3814 for i, r in enumerate(update_forest_info.entries):
3815 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3816 continue
3817 if r.forest_trust_data.domain_sid != sid:
3818 continue
3819 idx = i
3820 break
3821 if idx is None:
3822 raise CommandError("Entry not found for value[%s] specified for --enable-sid" % sid)
3823 if not update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_MASK:
3824 raise CommandError("Entry found for value[%s] specified for --enable-sid is already enabled" % nb)
3825 update_forest_info.entries[idx].time = 0
3826 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3828 for sid in disable_sid:
3829 idx = None
3830 for i, r in enumerate(update_forest_info.entries):
3831 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3832 continue
3833 if r.forest_trust_data.domain_sid != sid:
3834 continue
3835 idx = i
3836 break
3837 if idx is None:
3838 raise CommandError("Entry not found for value[%s] specified for --delete-sid" % sid)
3839 if update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_ADMIN:
3840 raise CommandError("Entry found for value[%s] specified for --disable-sid is already disabled" % nb)
3841 update_forest_info.entries[idx].time = 0
3842 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3843 update_forest_info.entries[idx].flags |= lsa.LSA_SID_DISABLED_ADMIN
3845 try:
3846 update_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3847 local_tdo_info.domain_name,
3848 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3849 update_forest_info, 0)
3850 except RuntimeError as error:
3851 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3853 self.outf.write("Updated forest trust information...\n")
3854 self.write_forest_trust_info(update_forest_info,
3855 tln=local_tdo_info.domain_name.string,
3856 collisions=update_forest_collision)
3858 try:
3859 lsaString = lsa.String()
3860 lsaString.string = local_tdo_info.domain_name.string
3861 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3862 lsaString,
3863 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3864 except RuntimeError as error:
3865 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3867 self.outf.write("Stored forest trust information...\n")
3868 self.write_forest_trust_info(stored_forest_info,
3869 tln=local_tdo_info.domain_name.string)
3870 return
3872 class cmd_domain_tombstones_expunge(Command):
3873 """Expunge tombstones from the database.
3875 This command expunges tombstones from the database."""
3876 synopsis = "%prog NC [NC [...]] [options]"
3878 takes_options = [
3879 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3880 metavar="URL", dest="H"),
3881 Option("--current-time",
3882 help="The current time to evaluate the tombstone lifetime from, expressed as YYYY-MM-DD",
3883 type=str),
3884 Option("--tombstone-lifetime", help="Number of days a tombstone should be preserved for", type=int),
3887 takes_args = ["nc*"]
3889 takes_optiongroups = {
3890 "sambaopts": options.SambaOptions,
3891 "credopts": options.CredentialsOptions,
3892 "versionopts": options.VersionOptions,
3895 def run(self, *ncs, **kwargs):
3896 sambaopts = kwargs.get("sambaopts")
3897 credopts = kwargs.get("credopts")
3898 versionpts = kwargs.get("versionopts")
3899 H = kwargs.get("H")
3900 current_time_string = kwargs.get("current_time")
3901 tombstone_lifetime = kwargs.get("tombstone_lifetime")
3902 lp = sambaopts.get_loadparm()
3903 creds = credopts.get_credentials(lp)
3904 samdb = SamDB(url=H, session_info=system_session(),
3905 credentials=creds, lp=lp)
3907 if current_time_string is not None:
3908 current_time_obj = time.strptime(current_time_string, "%Y-%m-%d")
3909 current_time = long(time.mktime(current_time_obj))
3911 else:
3912 current_time = long(time.time())
3914 if len(ncs) == 0:
3915 res = samdb.search(expression="", base="", scope=ldb.SCOPE_BASE,
3916 attrs=["namingContexts"])
3918 ncs = []
3919 for nc in res[0]["namingContexts"]:
3920 ncs.append(str(nc))
3921 else:
3922 ncs = list(ncs)
3924 started_transaction = False
3925 try:
3926 samdb.transaction_start()
3927 started_transaction = True
3928 (removed_objects,
3929 removed_links) = samdb.garbage_collect_tombstones(ncs,
3930 current_time=current_time,
3931 tombstone_lifetime=tombstone_lifetime)
3933 except Exception as err:
3934 if started_transaction:
3935 samdb.transaction_cancel()
3936 raise CommandError("Failed to expunge / garbage collect tombstones", err)
3938 samdb.transaction_commit()
3940 self.outf.write("Removed %d objects and %d links successfully\n"
3941 % (removed_objects, removed_links))
3945 class cmd_domain_trust(SuperCommand):
3946 """Domain and forest trust management."""
3948 subcommands = {}
3949 subcommands["list"] = cmd_domain_trust_list()
3950 subcommands["show"] = cmd_domain_trust_show()
3951 subcommands["create"] = cmd_domain_trust_create()
3952 subcommands["delete"] = cmd_domain_trust_delete()
3953 subcommands["validate"] = cmd_domain_trust_validate()
3954 subcommands["namespaces"] = cmd_domain_trust_namespaces()
3956 class cmd_domain_tombstones(SuperCommand):
3957 """Domain tombstone and recycled object management."""
3959 subcommands = {}
3960 subcommands["expunge"] = cmd_domain_tombstones_expunge()
3962 class ldif_schema_update:
3963 """Helper class for applying LDIF schema updates"""
3965 def __init__(self):
3966 self.is_defunct = False
3967 self.unknown_oid = None
3968 self.dn = None
3969 self.ldif = ""
3971 def can_ignore_failure(self, error):
3972 """Checks if we can safely ignore failure to apply an LDIF update"""
3973 (num, errstr) = error.args
3975 # Microsoft has marked objects as defunct that Samba doesn't know about
3976 if num == ldb.ERR_NO_SUCH_OBJECT and self.is_defunct:
3977 print("Defunct object %s doesn't exist, skipping" % self.dn)
3978 return True
3979 elif self.unknown_oid is not None:
3980 print("Skipping unknown OID %s for object %s" %(self.unknown_oid, self.dn))
3981 return True
3983 return False
3985 def apply(self, samdb):
3986 """Applies a single LDIF update to the schema"""
3988 try:
3989 try:
3990 samdb.modify_ldif(self.ldif, controls=['relax:0'])
3991 except ldb.LdbError as e:
3992 if e.args[0] == ldb.ERR_INVALID_ATTRIBUTE_SYNTAX:
3994 # REFRESH after a failed change
3996 # Otherwise the OID-to-attribute mapping in
3997 # _apply_updates_in_file() won't work, because it
3998 # can't lookup the new OID in the schema
3999 samdb.set_schema_update_now()
4001 samdb.modify_ldif(self.ldif, controls=['relax:0'])
4002 else:
4003 raise
4004 except ldb.LdbError as e:
4005 if self.can_ignore_failure(e):
4006 return 0
4007 else:
4008 print("Exception: %s" % e)
4009 print("Encountered while trying to apply the following LDIF")
4010 print("----------------------------------------------------")
4011 print("%s" % self.ldif)
4013 raise
4015 return 1
4017 class cmd_domain_schema_upgrade(Command):
4018 """Domain schema upgrading"""
4020 synopsis = "%prog [options]"
4022 takes_optiongroups = {
4023 "sambaopts": options.SambaOptions,
4024 "versionopts": options.VersionOptions,
4025 "credopts": options.CredentialsOptions,
4028 takes_options = [
4029 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
4030 metavar="URL", dest="H"),
4031 Option("-q", "--quiet", help="Be quiet", action="store_true"), #unused
4032 Option("-v", "--verbose", help="Be verbose", action="store_true"),
4033 Option("--schema", type="choice", metavar="SCHEMA",
4034 choices=["2012", "2012_R2"],
4035 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
4036 default="2012_R2"),
4037 Option("--ldf-file", type=str, default=None,
4038 help="Just apply the schema updates in the adprep/.LDF file(s) specified"),
4039 Option("--base-dir", type=str, default=None,
4040 help="Location of ldf files Default is ${SETUPDIR}/adprep.")
4043 def _apply_updates_in_file(self, samdb, ldif_file):
4045 Applies a series of updates specified in an .LDIF file. The .LDIF file
4046 is based on the adprep Schema updates provided by Microsoft.
4048 count = 0
4049 ldif_op = ldif_schema_update()
4051 # parse the file line by line and work out each update operation to apply
4052 for line in ldif_file:
4054 line = line.rstrip()
4056 # the operations in the .LDIF file are separated by blank lines. If
4057 # we hit a blank line, try to apply the update we've parsed so far
4058 if line == '':
4060 # keep going if we haven't parsed anything yet
4061 if ldif_op.ldif == '':
4062 continue
4064 # Apply the individual change
4065 count += ldif_op.apply(samdb)
4067 # start storing the next operation from scratch again
4068 ldif_op = ldif_schema_update()
4069 continue
4071 # replace the placeholder domain name in the .ldif file with the real domain
4072 if line.upper().endswith('DC=X'):
4073 line = line[:-len('DC=X')] + str(samdb.get_default_basedn())
4074 elif line.upper().endswith('CN=X'):
4075 line = line[:-len('CN=X')] + str(samdb.get_default_basedn())
4077 values = line.split(':')
4079 if values[0].lower() == 'dn':
4080 ldif_op.dn = values[1].strip()
4082 # replace the Windows-specific operation with the Samba one
4083 if values[0].lower() == 'changetype':
4084 line = line.lower().replace(': ntdsschemaadd',
4085 ': add')
4086 line = line.lower().replace(': ntdsschemamodify',
4087 ': modify')
4089 if values[0].lower() in ['rdnattid', 'subclassof',
4090 'systemposssuperiors',
4091 'systemmaycontain',
4092 'systemauxiliaryclass']:
4093 _, value = values
4095 # The Microsoft updates contain some OIDs we don't recognize.
4096 # Query the DB to see if we can work out the OID this update is
4097 # referring to. If we find a match, then replace the OID with
4098 # the ldapDisplayname
4099 if '.' in value:
4100 res = samdb.search(base=samdb.get_schema_basedn(),
4101 expression="(|(attributeId=%s)(governsId=%s))" %
4102 (value, value),
4103 attrs=['ldapDisplayName'])
4105 if len(res) != 1:
4106 ldif_op.unknown_oid = value
4107 else:
4108 display_name = res[0]['ldapDisplayName'][0]
4109 line = line.replace(value, ' ' + display_name)
4111 # Microsoft has marked objects as defunct that Samba doesn't know about
4112 if values[0].lower() == 'isdefunct' and values[1].strip().lower() == 'true':
4113 ldif_op.is_defunct = True
4115 # Samba has added the showInAdvancedViewOnly attribute to all objects,
4116 # so rather than doing an add, we need to do a replace
4117 if values[0].lower() == 'add' and values[1].strip().lower() == 'showinadvancedviewonly':
4118 line = 'replace: showInAdvancedViewOnly'
4120 # Add the line to the current LDIF operation (including the newline
4121 # we stripped off at the start of the loop)
4122 ldif_op.ldif += line + '\n'
4124 return count
4127 def _apply_update(self, samdb, update_file, base_dir):
4128 """Wrapper function for parsing an LDIF file and applying the updates"""
4130 print("Applying %s updates..." % update_file)
4132 ldif_file = None
4133 try:
4134 ldif_file = open(os.path.join(base_dir, update_file))
4136 count = self._apply_updates_in_file(samdb, ldif_file)
4138 finally:
4139 if ldif_file:
4140 ldif_file.close()
4142 print("%u changes applied" % count)
4144 return count
4146 def run(self, **kwargs):
4147 from samba.ms_schema_markdown import read_ms_markdown
4148 from samba.schema import Schema
4150 updates_allowed_overriden = False
4151 sambaopts = kwargs.get("sambaopts")
4152 credopts = kwargs.get("credopts")
4153 versionpts = kwargs.get("versionopts")
4154 lp = sambaopts.get_loadparm()
4155 creds = credopts.get_credentials(lp)
4156 H = kwargs.get("H")
4157 target_schema = kwargs.get("schema")
4158 ldf_files = kwargs.get("ldf_file")
4159 base_dir = kwargs.get("base_dir")
4161 temp_folder = None
4163 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4165 # we're not going to get far if the config doesn't allow schema updates
4166 if lp.get("dsdb:schema update allowed") is None:
4167 lp.set("dsdb:schema update allowed", "yes")
4168 print("Temporarily overriding 'dsdb:schema update allowed' setting")
4169 updates_allowed_overriden = True
4171 own_dn = ldb.Dn(samdb, samdb.get_dsServiceName())
4172 master = get_fsmo_roleowner(samdb, str(samdb.get_schema_basedn()),
4173 'schema')
4174 if own_dn != master:
4175 raise CommandError("This server is not the schema master.")
4177 # if specific LDIF files were specified, just apply them
4178 if ldf_files:
4179 schema_updates = ldf_files.split(",")
4180 else:
4181 schema_updates = []
4183 # work out the version of the target schema we're upgrading to
4184 end = Schema.get_version(target_schema)
4186 # work out the version of the schema we're currently using
4187 res = samdb.search(base=samdb.get_schema_basedn(),
4188 scope=ldb.SCOPE_BASE, attrs=['objectVersion'])
4190 if len(res) != 1:
4191 raise CommandError('Could not determine current schema version')
4192 start = int(res[0]['objectVersion'][0]) + 1
4194 diff_dir = setup_path("adprep/WindowsServerDocs")
4195 if base_dir is None:
4196 # Read from the Schema-Updates.md file
4197 temp_folder = tempfile.mkdtemp()
4199 update_file = setup_path("adprep/WindowsServerDocs/Schema-Updates.md")
4201 try:
4202 read_ms_markdown(update_file, temp_folder)
4203 except Exception as e:
4204 print("Exception in markdown parsing: %s" % e)
4205 shutil.rmtree(temp_folder)
4206 raise CommandError('Failed to upgrade schema')
4208 base_dir = temp_folder
4210 for version in range(start, end + 1):
4211 update = 'Sch%d.ldf' % version
4212 schema_updates.append(update)
4214 # Apply patches if we parsed the Schema-Updates.md file
4215 diff = os.path.abspath(os.path.join(diff_dir, update + '.diff'))
4216 if temp_folder and os.path.exists(diff):
4217 try:
4218 p = subprocess.Popen(['patch', update, '-i', diff],
4219 stdout=subprocess.PIPE,
4220 stderr=subprocess.PIPE, cwd=temp_folder)
4221 except (OSError, IOError):
4222 shutil.rmtree(temp_folder)
4223 raise CommandError("Failed to upgrade schema. Check if 'patch' is installed.")
4225 stdout, stderr = p.communicate()
4227 if p.returncode:
4228 print("Exception in patch: %s\n%s" % (stdout, stderr))
4229 shutil.rmtree(temp_folder)
4230 raise CommandError('Failed to upgrade schema')
4232 print("Patched %s using %s" % (update, diff))
4234 if base_dir is None:
4235 base_dir = setup_path("adprep")
4237 samdb.transaction_start()
4238 count = 0
4239 error_encountered = False
4241 try:
4242 # Apply the schema updates needed to move to the new schema version
4243 for ldif_file in schema_updates:
4244 count += self._apply_update(samdb, ldif_file, base_dir)
4246 if count > 0:
4247 samdb.transaction_commit()
4248 print("Schema successfully updated")
4249 else:
4250 print("No changes applied to schema")
4251 samdb.transaction_cancel()
4252 except Exception as e:
4253 print("Exception: %s" % e)
4254 print("Error encountered, aborting schema upgrade")
4255 samdb.transaction_cancel()
4256 error_encountered = True
4258 if updates_allowed_overriden:
4259 lp.set("dsdb:schema update allowed", "no")
4261 if temp_folder:
4262 shutil.rmtree(temp_folder)
4264 if error_encountered:
4265 raise CommandError('Failed to upgrade schema')
4267 class cmd_domain_functional_prep(Command):
4268 """Domain functional level preparation"""
4270 synopsis = "%prog [options]"
4272 takes_optiongroups = {
4273 "sambaopts": options.SambaOptions,
4274 "versionopts": options.VersionOptions,
4275 "credopts": options.CredentialsOptions,
4278 takes_options = [
4279 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
4280 metavar="URL", dest="H"),
4281 Option("-q", "--quiet", help="Be quiet", action="store_true"),
4282 Option("-v", "--verbose", help="Be verbose", action="store_true"),
4283 Option("--function-level", type="choice", metavar="FUNCTION_LEVEL",
4284 choices=["2008_R2", "2012", "2012_R2"],
4285 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
4286 default="2012_R2"),
4287 Option("--forest-prep", action="store_true",
4288 help="Run the forest prep (by default, both the domain and forest prep are run)."),
4289 Option("--domain-prep", action="store_true",
4290 help="Run the domain prep (by default, both the domain and forest prep are run).")
4293 def run(self, **kwargs):
4294 updates_allowed_overriden = False
4295 sambaopts = kwargs.get("sambaopts")
4296 credopts = kwargs.get("credopts")
4297 versionpts = kwargs.get("versionopts")
4298 lp = sambaopts.get_loadparm()
4299 creds = credopts.get_credentials(lp)
4300 H = kwargs.get("H")
4301 target_level = string_version_to_constant[kwargs.get("function_level")]
4302 forest_prep = kwargs.get("forest_prep")
4303 domain_prep = kwargs.get("domain_prep")
4305 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4307 # we're not going to get far if the config doesn't allow schema updates
4308 if lp.get("dsdb:schema update allowed") is None:
4309 lp.set("dsdb:schema update allowed", "yes")
4310 print("Temporarily overriding 'dsdb:schema update allowed' setting")
4311 updates_allowed_overriden = True
4313 if forest_prep is None and domain_prep is None:
4314 forest_prep = True
4315 domain_prep = True
4317 own_dn = ldb.Dn(samdb, samdb.get_dsServiceName())
4318 if forest_prep:
4319 master = get_fsmo_roleowner(samdb, str(samdb.get_schema_basedn()),
4320 'schema')
4321 if own_dn != master:
4322 raise CommandError("This server is not the schema master.")
4324 if domain_prep:
4325 domain_dn = samdb.domain_dn()
4326 infrastructure_dn = "CN=Infrastructure," + domain_dn
4327 master = get_fsmo_roleowner(samdb, infrastructure_dn,
4328 'infrastructure')
4329 if own_dn != master:
4330 raise CommandError("This server is not the infrastructure master.")
4332 if forest_prep:
4333 samdb.transaction_start()
4334 error_encountered = False
4335 try:
4336 from samba.forest_update import ForestUpdate
4337 forest = ForestUpdate(samdb, fix=True)
4339 forest.check_updates_iterator([53, 79, 80, 81, 82, 83])
4340 forest.check_updates_functional_level(target_level,
4341 DS_DOMAIN_FUNCTION_2008_R2,
4342 update_revision=True)
4344 samdb.transaction_commit()
4345 except Exception as e:
4346 print("Exception: %s" % e)
4347 samdb.transaction_cancel()
4348 error_encountered = True
4350 if domain_prep:
4351 samdb.transaction_start()
4352 error_encountered = False
4353 try:
4354 from samba.domain_update import DomainUpdate
4356 domain = DomainUpdate(samdb, fix=True)
4357 domain.check_updates_functional_level(target_level,
4358 DS_DOMAIN_FUNCTION_2008,
4359 update_revision=True)
4361 samdb.transaction_commit()
4362 except Exception as e:
4363 print("Exception: %s" % e)
4364 samdb.transaction_cancel()
4365 error_encountered = True
4367 if updates_allowed_overriden:
4368 lp.set("dsdb:schema update allowed", "no")
4370 if error_encountered:
4371 raise CommandError('Failed to perform functional prep')
4373 class cmd_domain(SuperCommand):
4374 """Domain management."""
4376 subcommands = {}
4377 subcommands["demote"] = cmd_domain_demote()
4378 if cmd_domain_export_keytab is not None:
4379 subcommands["exportkeytab"] = cmd_domain_export_keytab()
4380 subcommands["info"] = cmd_domain_info()
4381 subcommands["provision"] = cmd_domain_provision()
4382 subcommands["join"] = cmd_domain_join()
4383 subcommands["dcpromo"] = cmd_domain_dcpromo()
4384 subcommands["level"] = cmd_domain_level()
4385 subcommands["passwordsettings"] = cmd_domain_passwordsettings()
4386 subcommands["classicupgrade"] = cmd_domain_classicupgrade()
4387 subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()
4388 subcommands["trust"] = cmd_domain_trust()
4389 subcommands["tombstones"] = cmd_domain_tombstones()
4390 subcommands["schemaupgrade"] = cmd_domain_schema_upgrade()
4391 subcommands["functionalprep"] = cmd_domain_functional_prep()
4392 subcommands["backup"] = cmd_domain_backup()