markdown: Rename ms_markdown.py -> ms_schema_markdown.py
[Samba.git] / python / samba / netcmd / domain.py
blob6f6ef61c6aaf682710e2a5180738bbafd1d10562
1 # domain management
3 # Copyright Matthias Dieter Wallnoefer 2009
4 # Copyright Andrew Kroeger 2009
5 # Copyright Jelmer Vernooij 2007-2012
6 # Copyright Giampaolo Lauria 2011
7 # Copyright Matthieu Patou <mat@matws.net> 2011
8 # Copyright Andrew Bartlett 2008-2015
9 # Copyright Stefan Metzmacher 2012
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import samba.getopt as options
26 import ldb
27 import string
28 import os
29 import sys
30 import ctypes
31 import random
32 import tempfile
33 import logging
34 import subprocess
35 import time
36 import shutil
37 from samba import ntstatus
38 from samba import NTSTATUSError
39 from samba import werror
40 from getpass import getpass
41 from samba.net import Net, LIBNET_JOIN_AUTOMATIC
42 import samba.ntacls
43 from samba.join import join_RODC, join_DC, join_subdomain
44 from samba.auth import system_session
45 from samba.samdb import SamDB
46 from samba.ndr import ndr_unpack, ndr_pack, ndr_print
47 from samba.dcerpc import drsuapi
48 from samba.dcerpc import drsblobs
49 from samba.dcerpc import lsa
50 from samba.dcerpc import netlogon
51 from samba.dcerpc import security
52 from samba.dcerpc import nbt
53 from samba.dcerpc import misc
54 from samba.dcerpc.samr import DOMAIN_PASSWORD_COMPLEX, DOMAIN_PASSWORD_STORE_CLEARTEXT
55 from samba.netcmd import (
56 Command,
57 CommandError,
58 SuperCommand,
59 Option
61 from samba.netcmd.common import netcmd_get_domain_infos_via_cldap
62 from samba.samba3 import Samba3
63 from samba.samba3 import param as s3param
64 from samba.upgrade import upgrade_from_samba3
65 from samba.drs_utils import (
66 sendDsReplicaSync, drsuapi_connect, drsException,
67 sendRemoveDsServer)
68 from samba import remove_dc, arcfour_encrypt, string_to_byte_array
70 from samba.dsdb import (
71 DS_DOMAIN_FUNCTION_2000,
72 DS_DOMAIN_FUNCTION_2003,
73 DS_DOMAIN_FUNCTION_2003_MIXED,
74 DS_DOMAIN_FUNCTION_2008,
75 DS_DOMAIN_FUNCTION_2008_R2,
76 DS_DOMAIN_FUNCTION_2012,
77 DS_DOMAIN_FUNCTION_2012_R2,
78 DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL,
79 DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL,
80 UF_WORKSTATION_TRUST_ACCOUNT,
81 UF_SERVER_TRUST_ACCOUNT,
82 UF_TRUSTED_FOR_DELEGATION,
83 UF_PARTIAL_SECRETS_ACCOUNT
86 from samba.provision import (
87 provision,
88 ProvisioningError,
89 DEFAULT_MIN_PWD_LENGTH,
90 setup_path
93 from samba.provision.common import (
94 FILL_FULL,
95 FILL_NT4SYNC,
96 FILL_DRS
99 def get_testparm_var(testparm, smbconf, varname):
100 errfile = open(os.devnull, 'w')
101 p = subprocess.Popen([testparm, '-s', '-l',
102 '--parameter-name=%s' % varname, smbconf],
103 stdout=subprocess.PIPE, stderr=errfile)
104 (out,err) = p.communicate()
105 errfile.close()
106 lines = out.split('\n')
107 if lines:
108 return lines[0].strip()
109 return ""
111 try:
112 import samba.dckeytab
113 except ImportError:
114 cmd_domain_export_keytab = None
115 else:
116 class cmd_domain_export_keytab(Command):
117 """Dump Kerberos keys of the domain into a keytab."""
119 synopsis = "%prog <keytab> [options]"
121 takes_optiongroups = {
122 "sambaopts": options.SambaOptions,
123 "credopts": options.CredentialsOptions,
124 "versionopts": options.VersionOptions,
127 takes_options = [
128 Option("--principal", help="extract only this principal", type=str),
131 takes_args = ["keytab"]
133 def run(self, keytab, credopts=None, sambaopts=None, versionopts=None, principal=None):
134 lp = sambaopts.get_loadparm()
135 net = Net(None, lp)
136 net.export_keytab(keytab=keytab, principal=principal)
139 class cmd_domain_info(Command):
140 """Print basic info about a domain and the DC passed as parameter."""
142 synopsis = "%prog <ip_address> [options]"
144 takes_options = [
147 takes_optiongroups = {
148 "sambaopts": options.SambaOptions,
149 "credopts": options.CredentialsOptions,
150 "versionopts": options.VersionOptions,
153 takes_args = ["address"]
155 def run(self, address, credopts=None, sambaopts=None, versionopts=None):
156 lp = sambaopts.get_loadparm()
157 try:
158 res = netcmd_get_domain_infos_via_cldap(lp, None, address)
159 except RuntimeError:
160 raise CommandError("Invalid IP address '" + address + "'!")
161 self.outf.write("Forest : %s\n" % res.forest)
162 self.outf.write("Domain : %s\n" % res.dns_domain)
163 self.outf.write("Netbios domain : %s\n" % res.domain_name)
164 self.outf.write("DC name : %s\n" % res.pdc_dns_name)
165 self.outf.write("DC netbios name : %s\n" % res.pdc_name)
166 self.outf.write("Server site : %s\n" % res.server_site)
167 self.outf.write("Client site : %s\n" % res.client_site)
170 class cmd_domain_provision(Command):
171 """Provision a domain."""
173 synopsis = "%prog [options]"
175 takes_optiongroups = {
176 "sambaopts": options.SambaOptions,
177 "versionopts": options.VersionOptions,
180 takes_options = [
181 Option("--interactive", help="Ask for names", action="store_true"),
182 Option("--domain", type="string", metavar="DOMAIN",
183 help="NetBIOS domain name to use"),
184 Option("--domain-guid", type="string", metavar="GUID",
185 help="set domainguid (otherwise random)"),
186 Option("--domain-sid", type="string", metavar="SID",
187 help="set domainsid (otherwise random)"),
188 Option("--ntds-guid", type="string", metavar="GUID",
189 help="set NTDS object GUID (otherwise random)"),
190 Option("--invocationid", type="string", metavar="GUID",
191 help="set invocationid (otherwise random)"),
192 Option("--host-name", type="string", metavar="HOSTNAME",
193 help="set hostname"),
194 Option("--host-ip", type="string", metavar="IPADDRESS",
195 help="set IPv4 ipaddress"),
196 Option("--host-ip6", type="string", metavar="IP6ADDRESS",
197 help="set IPv6 ipaddress"),
198 Option("--site", type="string", metavar="SITENAME",
199 help="set site name"),
200 Option("--adminpass", type="string", metavar="PASSWORD",
201 help="choose admin password (otherwise random)"),
202 Option("--krbtgtpass", type="string", metavar="PASSWORD",
203 help="choose krbtgt password (otherwise random)"),
204 Option("--machinepass", type="string", metavar="PASSWORD",
205 help="choose machine password (otherwise random)"),
206 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
207 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
208 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
209 "BIND9_FLATFILE uses bind9 text database to store zone information, "
210 "BIND9_DLZ uses samba4 AD to store zone information, "
211 "NONE skips the DNS setup entirely (not recommended)",
212 default="SAMBA_INTERNAL"),
213 Option("--dnspass", type="string", metavar="PASSWORD",
214 help="choose dns password (otherwise random)"),
215 Option("--ldapadminpass", type="string", metavar="PASSWORD",
216 help="choose password to set between Samba and its LDAP backend (otherwise random)"),
217 Option("--root", type="string", metavar="USERNAME",
218 help="choose 'root' unix username"),
219 Option("--nobody", type="string", metavar="USERNAME",
220 help="choose 'nobody' user"),
221 Option("--users", type="string", metavar="GROUPNAME",
222 help="choose 'users' group"),
223 Option("--quiet", help="Be quiet", action="store_true"),
224 Option("--blank", action="store_true",
225 help="do not add users or groups, just the structure"),
226 Option("--ldap-backend-type", type="choice", metavar="LDAP-BACKEND-TYPE",
227 help="Test initialisation support for unsupported LDAP backend type (fedora-ds or openldap) DO NOT USE",
228 choices=["fedora-ds", "openldap"]),
229 Option("--server-role", type="choice", metavar="ROLE",
230 choices=["domain controller", "dc", "member server", "member", "standalone"],
231 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
232 default="domain controller"),
233 Option("--function-level", type="choice", metavar="FOR-FUN-LEVEL",
234 choices=["2000", "2003", "2008", "2008_R2"],
235 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2008_R2 Native.",
236 default="2008_R2"),
237 Option("--base-schema", type="choice", metavar="BASE-SCHEMA",
238 choices=["2008_R2", "2008_R2_old", "2012", "2012_R2"],
239 help="The base schema files to use. Default is (Windows) 2008_R2.",
240 default="2008_R2"),
241 Option("--next-rid", type="int", metavar="NEXTRID", default=1000,
242 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
243 Option("--partitions-only",
244 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"),
245 Option("--targetdir", type="string", metavar="DIR",
246 help="Set target directory"),
247 Option("--ol-mmr-urls", type="string", metavar="LDAPSERVER",
248 help="List of LDAP-URLS [ ldap://<FQHN>:<PORT>/ (where <PORT> has to be different than 389!) ] separated with comma (\",\") for use with OpenLDAP-MMR (Multi-Master-Replication), e.g.: \"ldap://s4dc1:9000,ldap://s4dc2:9000\""),
249 Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
252 openldap_options = [
253 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",
254 action="store_true"),
255 Option("--slapd-path", type="string", metavar="SLAPD-PATH",
256 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."),
257 Option("--ldap-backend-extra-port", type="int", metavar="LDAP-BACKEND-EXTRA-PORT", help="Additional TCP port for LDAP backend server (to use for replication)"),
258 Option("--ldap-backend-forced-uri", type="string", metavar="LDAP-BACKEND-FORCED-URI",
259 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"),
260 Option("--ldap-backend-nosync", help="Configure LDAP backend not to call fsync() (for performance in test environments)", action="store_true"),
263 ntvfs_options = [
264 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
265 Option("--use-xattrs", type="choice", choices=["yes","no","auto"],
266 metavar="[yes|no|auto]",
267 help="Define if we should use the native fs capabilities or a tdb file for "
268 "storing attributes likes ntacl when --use-ntvfs is set. "
269 "auto tries to make an inteligent guess based on the user rights and system capabilities",
270 default="auto")
273 if os.getenv('TEST_LDAP', "no") == "yes":
274 takes_options.extend(openldap_options)
276 if samba.is_ntvfs_fileserver_built():
277 takes_options.extend(ntvfs_options)
279 takes_args = []
281 def run(self, sambaopts=None, versionopts=None,
282 interactive=None,
283 domain=None,
284 domain_guid=None,
285 domain_sid=None,
286 ntds_guid=None,
287 invocationid=None,
288 host_name=None,
289 host_ip=None,
290 host_ip6=None,
291 adminpass=None,
292 site=None,
293 krbtgtpass=None,
294 machinepass=None,
295 dns_backend=None,
296 dns_forwarder=None,
297 dnspass=None,
298 ldapadminpass=None,
299 root=None,
300 nobody=None,
301 users=None,
302 quiet=None,
303 blank=None,
304 ldap_backend_type=None,
305 server_role=None,
306 function_level=None,
307 next_rid=None,
308 partitions_only=None,
309 targetdir=None,
310 ol_mmr_urls=None,
311 use_xattrs="auto",
312 slapd_path=None,
313 use_ntvfs=False,
314 use_rfc2307=None,
315 ldap_backend_nosync=None,
316 ldap_backend_extra_port=None,
317 ldap_backend_forced_uri=None,
318 ldap_dryrun_mode=None,
319 base_schema=None):
321 self.logger = self.get_logger("provision")
322 if quiet:
323 self.logger.setLevel(logging.WARNING)
324 else:
325 self.logger.setLevel(logging.INFO)
327 lp = sambaopts.get_loadparm()
328 smbconf = lp.configfile
330 if dns_forwarder is not None:
331 suggested_forwarder = dns_forwarder
332 else:
333 suggested_forwarder = self._get_nameserver_ip()
334 if suggested_forwarder is None:
335 suggested_forwarder = "none"
337 if len(self.raw_argv) == 1:
338 interactive = True
340 if interactive:
341 from getpass import getpass
342 import socket
344 def ask(prompt, default=None):
345 if default is not None:
346 print "%s [%s]: " % (prompt, default),
347 else:
348 print "%s: " % (prompt,),
349 return sys.stdin.readline().rstrip("\n") or default
351 try:
352 default = socket.getfqdn().split(".", 1)[1].upper()
353 except IndexError:
354 default = None
355 realm = ask("Realm", default)
356 if realm in (None, ""):
357 raise CommandError("No realm set!")
359 try:
360 default = realm.split(".")[0]
361 except IndexError:
362 default = None
363 domain = ask("Domain", default)
364 if domain is None:
365 raise CommandError("No domain set!")
367 server_role = ask("Server Role (dc, member, standalone)", "dc")
369 dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
370 if dns_backend in (None, ''):
371 raise CommandError("No DNS backend set!")
373 if dns_backend == "SAMBA_INTERNAL":
374 dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
375 if dns_forwarder.lower() in (None, 'none'):
376 suggested_forwarder = None
377 dns_forwarder = None
379 while True:
380 adminpassplain = getpass("Administrator password: ")
381 issue = self._adminpass_issue(adminpassplain)
382 if issue:
383 self.errf.write("%s.\n" % issue)
384 else:
385 adminpassverify = getpass("Retype password: ")
386 if not adminpassplain == adminpassverify:
387 self.errf.write("Sorry, passwords do not match.\n")
388 else:
389 adminpass = adminpassplain
390 break
392 else:
393 realm = sambaopts._lp.get('realm')
394 if realm is None:
395 raise CommandError("No realm set!")
396 if domain is None:
397 raise CommandError("No domain set!")
399 if adminpass:
400 issue = self._adminpass_issue(adminpass)
401 if issue:
402 raise CommandError(issue)
403 else:
404 self.logger.info("Administrator password will be set randomly!")
406 if function_level == "2000":
407 dom_for_fun_level = DS_DOMAIN_FUNCTION_2000
408 elif function_level == "2003":
409 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
410 elif function_level == "2008":
411 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
412 elif function_level == "2008_R2":
413 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
415 if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
416 dns_forwarder = suggested_forwarder
418 samdb_fill = FILL_FULL
419 if blank:
420 samdb_fill = FILL_NT4SYNC
421 elif partitions_only:
422 samdb_fill = FILL_DRS
424 if targetdir is not None:
425 if not os.path.isdir(targetdir):
426 os.mkdir(targetdir)
428 eadb = True
430 if use_xattrs == "yes":
431 eadb = False
432 elif use_xattrs == "auto" and use_ntvfs == False:
433 eadb = False
434 elif use_ntvfs == False:
435 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
436 "Please re-run with --use-xattrs omitted.")
437 elif use_xattrs == "auto" and not lp.get("posix:eadb"):
438 if targetdir:
439 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
440 else:
441 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
442 try:
443 try:
444 samba.ntacls.setntacl(lp, file.name,
445 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
446 eadb = False
447 except Exception:
448 self.logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. ")
449 finally:
450 file.close()
452 if eadb:
453 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.")
454 if ldap_backend_type == "existing":
455 if ldap_backend_forced_uri is not None:
456 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)
457 else:
458 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")
459 else:
460 if ldap_backend_forced_uri is not None:
461 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")
463 if domain_sid is not None:
464 domain_sid = security.dom_sid(domain_sid)
466 session = system_session()
467 try:
468 result = provision(self.logger,
469 session, smbconf=smbconf, targetdir=targetdir,
470 samdb_fill=samdb_fill, realm=realm, domain=domain,
471 domainguid=domain_guid, domainsid=domain_sid,
472 hostname=host_name,
473 hostip=host_ip, hostip6=host_ip6,
474 sitename=site, ntdsguid=ntds_guid,
475 invocationid=invocationid, adminpass=adminpass,
476 krbtgtpass=krbtgtpass, machinepass=machinepass,
477 dns_backend=dns_backend, dns_forwarder=dns_forwarder,
478 dnspass=dnspass, root=root, nobody=nobody,
479 users=users,
480 serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
481 backend_type=ldap_backend_type,
482 ldapadminpass=ldapadminpass, ol_mmr_urls=ol_mmr_urls, slapd_path=slapd_path,
483 useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
484 use_rfc2307=use_rfc2307, skip_sysvolacl=False,
485 ldap_backend_extra_port=ldap_backend_extra_port,
486 ldap_backend_forced_uri=ldap_backend_forced_uri,
487 nosync=ldap_backend_nosync, ldap_dryrun_mode=ldap_dryrun_mode,
488 base_schema=base_schema)
490 except ProvisioningError, e:
491 raise CommandError("Provision failed", e)
493 result.report_logger(self.logger)
495 def _get_nameserver_ip(self):
496 """Grab the nameserver IP address from /etc/resolv.conf."""
497 from os import path
498 RESOLV_CONF="/etc/resolv.conf"
500 if not path.isfile(RESOLV_CONF):
501 self.logger.warning("Failed to locate %s" % RESOLV_CONF)
502 return None
504 handle = None
505 try:
506 handle = open(RESOLV_CONF, 'r')
507 for line in handle:
508 if not line.startswith('nameserver'):
509 continue
510 # we want the last non-space continuous string of the line
511 return line.strip().split()[-1]
512 finally:
513 if handle is not None:
514 handle.close()
516 self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
518 def _adminpass_issue(self, adminpass):
519 """Returns error string for a bad administrator password,
520 or None if acceptable"""
522 if len(adminpass.decode('utf-8')) < DEFAULT_MIN_PWD_LENGTH:
523 return "Administrator password does not meet the default minimum" \
524 " password length requirement (%d characters)" \
525 % DEFAULT_MIN_PWD_LENGTH
526 elif not samba.check_password_quality(adminpass):
527 return "Administrator password does not meet the default" \
528 " quality standards"
529 else:
530 return None
533 class cmd_domain_dcpromo(Command):
534 """Promote an existing domain member or NT4 PDC to an AD DC."""
536 synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
538 takes_optiongroups = {
539 "sambaopts": options.SambaOptions,
540 "versionopts": options.VersionOptions,
541 "credopts": options.CredentialsOptions,
544 takes_options = [
545 Option("--server", help="DC to join", type=str),
546 Option("--site", help="site to join", type=str),
547 Option("--targetdir", help="where to store provision", type=str),
548 Option("--domain-critical-only",
549 help="only replicate critical domain objects",
550 action="store_true"),
551 Option("--machinepass", type=str, metavar="PASSWORD",
552 help="choose machine password (otherwise random)"),
553 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
554 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
555 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
556 "BIND9_DLZ uses samba4 AD to store zone information, "
557 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
558 default="SAMBA_INTERNAL"),
559 Option("--quiet", help="Be quiet", action="store_true"),
560 Option("--verbose", help="Be verbose", action="store_true")
563 ntvfs_options = [
564 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
567 if samba.is_ntvfs_fileserver_built():
568 takes_options.extend(ntvfs_options)
571 takes_args = ["domain", "role?"]
573 def run(self, domain, role=None, sambaopts=None, credopts=None,
574 versionopts=None, server=None, site=None, targetdir=None,
575 domain_critical_only=False, parent_domain=None, machinepass=None,
576 use_ntvfs=False, dns_backend=None,
577 quiet=False, verbose=False):
578 lp = sambaopts.get_loadparm()
579 creds = credopts.get_credentials(lp)
580 net = Net(creds, lp, server=credopts.ipaddress)
582 logger = self.get_logger()
583 if verbose:
584 logger.setLevel(logging.DEBUG)
585 elif quiet:
586 logger.setLevel(logging.WARNING)
587 else:
588 logger.setLevel(logging.INFO)
590 netbios_name = lp.get("netbios name")
592 if not role is None:
593 role = role.upper()
595 if role == "DC":
596 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
597 site=site, netbios_name=netbios_name, targetdir=targetdir,
598 domain_critical_only=domain_critical_only,
599 machinepass=machinepass, use_ntvfs=use_ntvfs,
600 dns_backend=dns_backend,
601 promote_existing=True)
602 elif role == "RODC":
603 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
604 site=site, netbios_name=netbios_name, targetdir=targetdir,
605 domain_critical_only=domain_critical_only,
606 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
607 promote_existing=True)
608 else:
609 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
612 class cmd_domain_join(Command):
613 """Join domain as either member or backup domain controller."""
615 synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
617 takes_optiongroups = {
618 "sambaopts": options.SambaOptions,
619 "versionopts": options.VersionOptions,
620 "credopts": options.CredentialsOptions,
623 takes_options = [
624 Option("--server", help="DC to join", type=str),
625 Option("--site", help="site to join", type=str),
626 Option("--targetdir", help="where to store provision", type=str),
627 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
628 Option("--domain-critical-only",
629 help="only replicate critical domain objects",
630 action="store_true"),
631 Option("--machinepass", type=str, metavar="PASSWORD",
632 help="choose machine password (otherwise random)"),
633 Option("--adminpass", type="string", metavar="PASSWORD",
634 help="choose adminstrator password when joining as a subdomain (otherwise random)"),
635 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
636 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
637 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
638 "BIND9_DLZ uses samba4 AD to store zone information, "
639 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
640 default="SAMBA_INTERNAL"),
641 Option("--quiet", help="Be quiet", action="store_true"),
642 Option("--verbose", help="Be verbose", action="store_true")
645 ntvfs_options = [
646 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
647 action="store_true")
649 if samba.is_ntvfs_fileserver_built():
650 takes_options.extend(ntvfs_options)
652 takes_args = ["domain", "role?"]
654 def run(self, domain, role=None, sambaopts=None, credopts=None,
655 versionopts=None, server=None, site=None, targetdir=None,
656 domain_critical_only=False, parent_domain=None, machinepass=None,
657 use_ntvfs=False, dns_backend=None, adminpass=None,
658 quiet=False, verbose=False):
659 lp = sambaopts.get_loadparm()
660 creds = credopts.get_credentials(lp)
661 net = Net(creds, lp, server=credopts.ipaddress)
663 if site is None:
664 site = "Default-First-Site-Name"
666 logger = self.get_logger()
667 if verbose:
668 logger.setLevel(logging.DEBUG)
669 elif quiet:
670 logger.setLevel(logging.WARNING)
671 else:
672 logger.setLevel(logging.INFO)
674 netbios_name = lp.get("netbios name")
676 if not role is None:
677 role = role.upper()
679 if role is None or role == "MEMBER":
680 (join_password, sid, domain_name) = net.join_member(
681 domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
682 machinepass=machinepass)
684 self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
685 elif role == "DC":
686 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
687 site=site, netbios_name=netbios_name, targetdir=targetdir,
688 domain_critical_only=domain_critical_only,
689 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend)
690 elif role == "RODC":
691 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
692 site=site, netbios_name=netbios_name, targetdir=targetdir,
693 domain_critical_only=domain_critical_only,
694 machinepass=machinepass, use_ntvfs=use_ntvfs,
695 dns_backend=dns_backend)
696 elif role == "SUBDOMAIN":
697 if not adminpass:
698 logger.info("Administrator password will be set randomly!")
700 netbios_domain = lp.get("workgroup")
701 if parent_domain is None:
702 parent_domain = ".".join(domain.split(".")[1:])
703 join_subdomain(logger=logger, server=server, creds=creds, lp=lp, dnsdomain=domain,
704 parent_domain=parent_domain, site=site,
705 netbios_name=netbios_name, netbios_domain=netbios_domain,
706 targetdir=targetdir, machinepass=machinepass,
707 use_ntvfs=use_ntvfs, dns_backend=dns_backend,
708 adminpass=adminpass)
709 else:
710 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role)
713 class cmd_domain_demote(Command):
714 """Demote ourselves from the role of Domain Controller."""
716 synopsis = "%prog [options]"
718 takes_options = [
719 Option("--server", help="writable DC to write demotion changes on", type=str),
720 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
721 metavar="URL", dest="H"),
722 Option("--remove-other-dead-server", help="Dead DC (name or NTDS GUID) "
723 "to remove ALL references to (rather than this DC)", type=str),
724 Option("--quiet", help="Be quiet", action="store_true"),
725 Option("--verbose", help="Be verbose", action="store_true"),
728 takes_optiongroups = {
729 "sambaopts": options.SambaOptions,
730 "credopts": options.CredentialsOptions,
731 "versionopts": options.VersionOptions,
734 def run(self, sambaopts=None, credopts=None,
735 versionopts=None, server=None,
736 remove_other_dead_server=None, H=None,
737 verbose=False, quiet=False):
738 lp = sambaopts.get_loadparm()
739 creds = credopts.get_credentials(lp)
740 net = Net(creds, lp, server=credopts.ipaddress)
742 logger = self.get_logger()
743 if verbose:
744 logger.setLevel(logging.DEBUG)
745 elif quiet:
746 logger.setLevel(logging.WARNING)
747 else:
748 logger.setLevel(logging.INFO)
750 if remove_other_dead_server is not None:
751 if server is not None:
752 samdb = SamDB(url="ldap://%s" % server,
753 session_info=system_session(),
754 credentials=creds, lp=lp)
755 else:
756 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
757 try:
758 remove_dc.remove_dc(samdb, logger, remove_other_dead_server)
759 except remove_dc.DemoteException as err:
760 raise CommandError("Demote failed: %s" % err)
761 return
763 netbios_name = lp.get("netbios name")
764 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
765 if not server:
766 res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
767 if (len(res) == 0):
768 raise CommandError("Unable to search for servers")
770 if (len(res) == 1):
771 raise CommandError("You are the latest server in the domain")
773 server = None
774 for e in res:
775 if str(e["name"]).lower() != netbios_name.lower():
776 server = e["dnsHostName"]
777 break
779 ntds_guid = samdb.get_ntds_GUID()
780 msg = samdb.search(base=str(samdb.get_config_basedn()),
781 scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
782 attrs=['options'])
783 if len(msg) == 0 or "options" not in msg[0]:
784 raise CommandError("Failed to find options on %s" % ntds_guid)
786 ntds_dn = msg[0].dn
787 dsa_options = int(str(msg[0]['options']))
789 res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
790 controls=["search_options:1:2"])
792 if len(res) != 0:
793 raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res))
795 self.errf.write("Using %s as partner server for the demotion\n" %
796 server)
797 (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
799 self.errf.write("Deactivating inbound replication\n")
801 nmsg = ldb.Message()
802 nmsg.dn = msg[0].dn
804 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
805 dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
806 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
807 samdb.modify(nmsg)
810 self.errf.write("Asking partner server %s to synchronize from us\n"
811 % server)
812 for part in (samdb.get_schema_basedn(),
813 samdb.get_config_basedn(),
814 samdb.get_root_basedn()):
815 nc = drsuapi.DsReplicaObjectIdentifier()
816 nc.dn = str(part)
818 req1 = drsuapi.DsReplicaSyncRequest1()
819 req1.naming_context = nc;
820 req1.options = drsuapi.DRSUAPI_DRS_WRIT_REP
821 req1.source_dsa_guid = misc.GUID(ntds_guid)
823 try:
824 drsuapiBind.DsReplicaSync(drsuapi_handle, 1, req1)
825 except RuntimeError as (werr, string):
826 if werr == werror.WERR_DS_DRA_NO_REPLICA:
827 pass
828 else:
829 self.errf.write(
830 "Error while replicating out last local changes from '%s' for demotion, "
831 "re-enabling inbound replication\n" % part)
832 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
833 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
834 samdb.modify(nmsg)
835 raise CommandError("Error while sending a DsReplicaSync for partition '%s'" % str(part), string)
836 try:
837 remote_samdb = SamDB(url="ldap://%s" % server,
838 session_info=system_session(),
839 credentials=creds, lp=lp)
841 self.errf.write("Changing userControl and container\n")
842 res = remote_samdb.search(base=str(remote_samdb.domain_dn()),
843 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
844 netbios_name.upper(),
845 attrs=["userAccountControl"])
846 dc_dn = res[0].dn
847 uac = int(str(res[0]["userAccountControl"]))
849 except Exception, e:
850 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
851 self.errf.write(
852 "Error while demoting, re-enabling inbound replication\n")
853 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
854 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
855 samdb.modify(nmsg)
856 raise CommandError("Error while changing account control", e)
858 if (len(res) != 1):
859 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
860 self.errf.write(
861 "Error while demoting, re-enabling inbound replication")
862 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
863 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
864 samdb.modify(nmsg)
865 raise CommandError("Unable to find object with samaccountName = %s$"
866 " in the remote dc" % netbios_name.upper())
868 olduac = uac
870 uac &= ~(UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION|UF_PARTIAL_SECRETS_ACCOUNT)
871 uac |= UF_WORKSTATION_TRUST_ACCOUNT
873 msg = ldb.Message()
874 msg.dn = dc_dn
876 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
877 ldb.FLAG_MOD_REPLACE,
878 "userAccountControl")
879 try:
880 remote_samdb.modify(msg)
881 except Exception, e:
882 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
883 self.errf.write(
884 "Error while demoting, re-enabling inbound replication")
885 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
886 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
887 samdb.modify(nmsg)
889 raise CommandError("Error while changing account control", e)
891 parent = msg.dn.parent()
892 dc_name = res[0].dn.get_rdn_value()
893 rdn = "CN=%s" % dc_name
895 # Let's move to the Computer container
896 i = 0
897 newrdn = str(rdn)
899 computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.domain_dn()))
900 res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
902 if (len(res) != 0):
903 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
904 scope=ldb.SCOPE_ONELEVEL)
905 while(len(res) != 0 and i < 100):
906 i = i + 1
907 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
908 scope=ldb.SCOPE_ONELEVEL)
910 if i == 100:
911 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
912 self.errf.write(
913 "Error while demoting, re-enabling inbound replication\n")
914 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
915 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
916 samdb.modify(nmsg)
918 msg = ldb.Message()
919 msg.dn = dc_dn
921 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
922 ldb.FLAG_MOD_REPLACE,
923 "userAccountControl")
925 remote_samdb.modify(msg)
927 raise CommandError("Unable to find a slot for renaming %s,"
928 " all names from %s-1 to %s-%d seemed used" %
929 (str(dc_dn), rdn, rdn, i - 9))
931 newrdn = "%s-%d" % (rdn, i)
933 try:
934 newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
935 remote_samdb.rename(dc_dn, newdn)
936 except Exception, e:
937 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
938 self.errf.write(
939 "Error while demoting, re-enabling inbound replication\n")
940 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
941 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
942 samdb.modify(nmsg)
944 msg = ldb.Message()
945 msg.dn = dc_dn
947 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
948 ldb.FLAG_MOD_REPLACE,
949 "userAccountControl")
951 remote_samdb.modify(msg)
952 raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
955 server_dsa_dn = samdb.get_serverName()
956 domain = remote_samdb.get_root_basedn()
958 try:
959 req1 = drsuapi.DsRemoveDSServerRequest1()
960 req1.server_dn = str(server_dsa_dn)
961 req1.domain_dn = str(domain)
962 req1.commit = 1
964 drsuapiBind.DsRemoveDSServer(drsuapi_handle, 1, req1)
965 except RuntimeError as (werr, string):
966 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
967 self.errf.write(
968 "Error while demoting, re-enabling inbound replication\n")
969 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
970 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
971 samdb.modify(nmsg)
973 msg = ldb.Message()
974 msg.dn = newdn
976 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
977 ldb.FLAG_MOD_REPLACE,
978 "userAccountControl")
979 remote_samdb.modify(msg)
980 remote_samdb.rename(newdn, dc_dn)
981 if werr == werror.WERR_DS_DRA_NO_REPLICA:
982 raise CommandError("The DC %s is not present on (already removed from) the remote server: " % server_dsa_dn, e)
983 else:
984 raise CommandError("Error while sending a removeDsServer of %s: " % server_dsa_dn, e)
986 remove_dc.remove_sysvol_references(remote_samdb, logger, dc_name)
988 # These are objects under the computer account that should be deleted
989 for s in ("CN=Enterprise,CN=NTFRS Subscriptions",
990 "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
991 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
992 "CN=NTFRS Subscriptions"):
993 try:
994 remote_samdb.delete(ldb.Dn(remote_samdb,
995 "%s,%s" % (s, str(newdn))))
996 except ldb.LdbError, l:
997 pass
999 self.errf.write("Demote successful\n")
1002 class cmd_domain_level(Command):
1003 """Raise domain and forest function levels."""
1005 synopsis = "%prog (show|raise <options>) [options]"
1007 takes_optiongroups = {
1008 "sambaopts": options.SambaOptions,
1009 "credopts": options.CredentialsOptions,
1010 "versionopts": options.VersionOptions,
1013 takes_options = [
1014 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1015 metavar="URL", dest="H"),
1016 Option("--quiet", help="Be quiet", action="store_true"),
1017 Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1018 help="The forest function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)"),
1019 Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1020 help="The domain function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)")
1023 takes_args = ["subcommand"]
1025 def run(self, subcommand, H=None, forest_level=None, domain_level=None,
1026 quiet=False, credopts=None, sambaopts=None, versionopts=None):
1027 lp = sambaopts.get_loadparm()
1028 creds = credopts.get_credentials(lp, fallback_machine=True)
1030 samdb = SamDB(url=H, session_info=system_session(),
1031 credentials=creds, lp=lp)
1033 domain_dn = samdb.domain_dn()
1035 res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
1036 scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
1037 assert len(res_forest) == 1
1039 res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1040 attrs=["msDS-Behavior-Version", "nTMixedDomain"])
1041 assert len(res_domain) == 1
1043 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
1044 scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
1045 attrs=["msDS-Behavior-Version"])
1046 assert len(res_dc_s) >= 1
1048 # default values, since "msDS-Behavior-Version" does not exist on Windows 2000 AD
1049 level_forest = DS_DOMAIN_FUNCTION_2000
1050 level_domain = DS_DOMAIN_FUNCTION_2000
1052 if "msDS-Behavior-Version" in res_forest[0]:
1053 level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
1054 if "msDS-Behavior-Version" in res_domain[0]:
1055 level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
1056 level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
1058 min_level_dc = None
1059 for msg in res_dc_s:
1060 if "msDS-Behavior-Version" in msg:
1061 if min_level_dc is None or int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
1062 min_level_dc = int(msg["msDS-Behavior-Version"][0])
1063 else:
1064 min_level_dc = DS_DOMAIN_FUNCTION_2000
1065 # well, this is the least
1066 break
1068 if level_forest < DS_DOMAIN_FUNCTION_2000 or level_domain < DS_DOMAIN_FUNCTION_2000:
1069 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
1070 if min_level_dc < DS_DOMAIN_FUNCTION_2000:
1071 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
1072 if level_forest > level_domain:
1073 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
1074 if level_domain > min_level_dc:
1075 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
1077 if subcommand == "show":
1078 self.message("Domain and forest function level for domain '%s'" % domain_dn)
1079 if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1080 self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1081 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1082 self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1083 if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1084 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)!")
1086 self.message("")
1088 if level_forest == DS_DOMAIN_FUNCTION_2000:
1089 outstr = "2000"
1090 elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
1091 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1092 elif level_forest == DS_DOMAIN_FUNCTION_2003:
1093 outstr = "2003"
1094 elif level_forest == DS_DOMAIN_FUNCTION_2008:
1095 outstr = "2008"
1096 elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
1097 outstr = "2008 R2"
1098 elif level_forest == DS_DOMAIN_FUNCTION_2012:
1099 outstr = "2012"
1100 elif level_forest == DS_DOMAIN_FUNCTION_2012_R2:
1101 outstr = "2012 R2"
1102 else:
1103 outstr = "higher than 2012 R2"
1104 self.message("Forest function level: (Windows) " + outstr)
1106 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1107 outstr = "2000 mixed (NT4 DC support)"
1108 elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
1109 outstr = "2000"
1110 elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
1111 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1112 elif level_domain == DS_DOMAIN_FUNCTION_2003:
1113 outstr = "2003"
1114 elif level_domain == DS_DOMAIN_FUNCTION_2008:
1115 outstr = "2008"
1116 elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
1117 outstr = "2008 R2"
1118 elif level_domain == DS_DOMAIN_FUNCTION_2012:
1119 outstr = "2012"
1120 elif level_domain == DS_DOMAIN_FUNCTION_2012_R2:
1121 outstr = "2012 R2"
1122 else:
1123 outstr = "higher than 2012 R2"
1124 self.message("Domain function level: (Windows) " + outstr)
1126 if min_level_dc == DS_DOMAIN_FUNCTION_2000:
1127 outstr = "2000"
1128 elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
1129 outstr = "2003"
1130 elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
1131 outstr = "2008"
1132 elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
1133 outstr = "2008 R2"
1134 elif min_level_dc == DS_DOMAIN_FUNCTION_2012:
1135 outstr = "2012"
1136 elif min_level_dc == DS_DOMAIN_FUNCTION_2012_R2:
1137 outstr = "2012 R2"
1138 else:
1139 outstr = "higher than 2012 R2"
1140 self.message("Lowest function level of a DC: (Windows) " + outstr)
1142 elif subcommand == "raise":
1143 msgs = []
1145 if domain_level is not None:
1146 if domain_level == "2003":
1147 new_level_domain = DS_DOMAIN_FUNCTION_2003
1148 elif domain_level == "2008":
1149 new_level_domain = DS_DOMAIN_FUNCTION_2008
1150 elif domain_level == "2008_R2":
1151 new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
1152 elif domain_level == "2012":
1153 new_level_domain = DS_DOMAIN_FUNCTION_2012
1154 elif domain_level == "2012_R2":
1155 new_level_domain = DS_DOMAIN_FUNCTION_2012_R2
1157 if new_level_domain <= level_domain and level_domain_mixed == 0:
1158 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
1159 if new_level_domain > min_level_dc:
1160 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
1162 # Deactivate mixed/interim domain support
1163 if level_domain_mixed != 0:
1164 # Directly on the base DN
1165 m = ldb.Message()
1166 m.dn = ldb.Dn(samdb, domain_dn)
1167 m["nTMixedDomain"] = ldb.MessageElement("0",
1168 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1169 samdb.modify(m)
1170 # Under partitions
1171 m = ldb.Message()
1172 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
1173 m["nTMixedDomain"] = ldb.MessageElement("0",
1174 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1175 try:
1176 samdb.modify(m)
1177 except ldb.LdbError, (enum, emsg):
1178 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1179 raise
1181 # Directly on the base DN
1182 m = ldb.Message()
1183 m.dn = ldb.Dn(samdb, domain_dn)
1184 m["msDS-Behavior-Version"]= ldb.MessageElement(
1185 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1186 "msDS-Behavior-Version")
1187 samdb.modify(m)
1188 # Under partitions
1189 m = ldb.Message()
1190 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
1191 + ",CN=Partitions,%s" % samdb.get_config_basedn())
1192 m["msDS-Behavior-Version"]= ldb.MessageElement(
1193 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1194 "msDS-Behavior-Version")
1195 try:
1196 samdb.modify(m)
1197 except ldb.LdbError, (enum, emsg):
1198 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1199 raise
1201 level_domain = new_level_domain
1202 msgs.append("Domain function level changed!")
1204 if forest_level is not None:
1205 if forest_level == "2003":
1206 new_level_forest = DS_DOMAIN_FUNCTION_2003
1207 elif forest_level == "2008":
1208 new_level_forest = DS_DOMAIN_FUNCTION_2008
1209 elif forest_level == "2008_R2":
1210 new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
1211 elif forest_level == "2012":
1212 new_level_forest = DS_DOMAIN_FUNCTION_2012
1213 elif forest_level == "2012_R2":
1214 new_level_forest = DS_DOMAIN_FUNCTION_2012_R2
1216 if new_level_forest <= level_forest:
1217 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1218 if new_level_forest > level_domain:
1219 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1221 m = ldb.Message()
1222 m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
1223 m["msDS-Behavior-Version"]= ldb.MessageElement(
1224 str(new_level_forest), ldb.FLAG_MOD_REPLACE,
1225 "msDS-Behavior-Version")
1226 samdb.modify(m)
1227 msgs.append("Forest function level changed!")
1228 msgs.append("All changes applied successfully!")
1229 self.message("\n".join(msgs))
1230 else:
1231 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
1234 class cmd_domain_passwordsettings(Command):
1235 """Set password settings.
1237 Password complexity, password lockout policy, history length,
1238 minimum password length, the minimum and maximum password age) on
1239 a Samba AD DC server.
1241 Use against a Windows DC is possible, but group policy will override it.
1244 synopsis = "%prog (show|set <options>) [options]"
1246 takes_optiongroups = {
1247 "sambaopts": options.SambaOptions,
1248 "versionopts": options.VersionOptions,
1249 "credopts": options.CredentialsOptions,
1252 takes_options = [
1253 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1254 metavar="URL", dest="H"),
1255 Option("--quiet", help="Be quiet", action="store_true"),
1256 Option("--complexity", type="choice", choices=["on","off","default"],
1257 help="The password complexity (on | off | default). Default is 'on'"),
1258 Option("--store-plaintext", type="choice", choices=["on","off","default"],
1259 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1260 Option("--history-length",
1261 help="The password history length (<integer> | default). Default is 24.", type=str),
1262 Option("--min-pwd-length",
1263 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1264 Option("--min-pwd-age",
1265 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1266 Option("--max-pwd-age",
1267 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1268 Option("--account-lockout-duration",
1269 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),
1270 Option("--account-lockout-threshold",
1271 help="The number of bad password attempts allowed before locking out the account (<integer> | default). Default is 0 (never lock out).", type=str),
1272 Option("--reset-account-lockout-after",
1273 help="After this time is elapsed, the recorded number of attempts restarts from zero (<integer> | default). Default is 30.", type=str),
1276 takes_args = ["subcommand"]
1278 def run(self, subcommand, H=None, min_pwd_age=None, max_pwd_age=None,
1279 quiet=False, complexity=None, store_plaintext=None, history_length=None,
1280 min_pwd_length=None, account_lockout_duration=None, account_lockout_threshold=None,
1281 reset_account_lockout_after=None, credopts=None, sambaopts=None,
1282 versionopts=None):
1283 lp = sambaopts.get_loadparm()
1284 creds = credopts.get_credentials(lp)
1286 samdb = SamDB(url=H, session_info=system_session(),
1287 credentials=creds, lp=lp)
1289 domain_dn = samdb.domain_dn()
1290 res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1291 attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1292 "minPwdAge", "maxPwdAge", "lockoutDuration", "lockoutThreshold",
1293 "lockOutObservationWindow"])
1294 assert(len(res) == 1)
1295 try:
1296 pwd_props = int(res[0]["pwdProperties"][0])
1297 pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
1298 cur_min_pwd_len = int(res[0]["minPwdLength"][0])
1299 # ticks -> days
1300 cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1301 if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000:
1302 cur_max_pwd_age = 0
1303 else:
1304 cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1305 cur_account_lockout_threshold = int(res[0]["lockoutThreshold"][0])
1306 # ticks -> mins
1307 if int(res[0]["lockoutDuration"][0]) == -0x8000000000000000:
1308 cur_account_lockout_duration = 0
1309 else:
1310 cur_account_lockout_duration = abs(int(res[0]["lockoutDuration"][0])) / (1e7 * 60)
1311 cur_reset_account_lockout_after = abs(int(res[0]["lockOutObservationWindow"][0])) / (1e7 * 60)
1312 except Exception, e:
1313 raise CommandError("Could not retrieve password properties!", e)
1315 if subcommand == "show":
1316 self.message("Password informations for domain '%s'" % domain_dn)
1317 self.message("")
1318 if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
1319 self.message("Password complexity: on")
1320 else:
1321 self.message("Password complexity: off")
1322 if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
1323 self.message("Store plaintext passwords: on")
1324 else:
1325 self.message("Store plaintext passwords: off")
1326 self.message("Password history length: %d" % pwd_hist_len)
1327 self.message("Minimum password length: %d" % cur_min_pwd_len)
1328 self.message("Minimum password age (days): %d" % cur_min_pwd_age)
1329 self.message("Maximum password age (days): %d" % cur_max_pwd_age)
1330 self.message("Account lockout duration (mins): %d" % cur_account_lockout_duration)
1331 self.message("Account lockout threshold (attempts): %d" % cur_account_lockout_threshold)
1332 self.message("Reset account lockout after (mins): %d" % cur_reset_account_lockout_after)
1333 elif subcommand == "set":
1334 msgs = []
1335 m = ldb.Message()
1336 m.dn = ldb.Dn(samdb, domain_dn)
1338 if complexity is not None:
1339 if complexity == "on" or complexity == "default":
1340 pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
1341 msgs.append("Password complexity activated!")
1342 elif complexity == "off":
1343 pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
1344 msgs.append("Password complexity deactivated!")
1346 if store_plaintext is not None:
1347 if store_plaintext == "on" or store_plaintext == "default":
1348 pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1349 msgs.append("Plaintext password storage for changed passwords activated!")
1350 elif store_plaintext == "off":
1351 pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
1352 msgs.append("Plaintext password storage for changed passwords deactivated!")
1354 if complexity is not None or store_plaintext is not None:
1355 m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
1356 ldb.FLAG_MOD_REPLACE, "pwdProperties")
1358 if history_length is not None:
1359 if history_length == "default":
1360 pwd_hist_len = 24
1361 else:
1362 pwd_hist_len = int(history_length)
1364 if pwd_hist_len < 0 or pwd_hist_len > 24:
1365 raise CommandError("Password history length must be in the range of 0 to 24!")
1367 m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
1368 ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
1369 msgs.append("Password history length changed!")
1371 if min_pwd_length is not None:
1372 if min_pwd_length == "default":
1373 min_pwd_len = 7
1374 else:
1375 min_pwd_len = int(min_pwd_length)
1377 if min_pwd_len < 0 or min_pwd_len > 14:
1378 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1380 m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
1381 ldb.FLAG_MOD_REPLACE, "minPwdLength")
1382 msgs.append("Minimum password length changed!")
1384 if min_pwd_age is not None:
1385 if min_pwd_age == "default":
1386 min_pwd_age = 1
1387 else:
1388 min_pwd_age = int(min_pwd_age)
1390 if min_pwd_age < 0 or min_pwd_age > 998:
1391 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1393 # days -> ticks
1394 min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
1396 m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
1397 ldb.FLAG_MOD_REPLACE, "minPwdAge")
1398 msgs.append("Minimum password age changed!")
1400 if max_pwd_age is not None:
1401 if max_pwd_age == "default":
1402 max_pwd_age = 43
1403 else:
1404 max_pwd_age = int(max_pwd_age)
1406 if max_pwd_age < 0 or max_pwd_age > 999:
1407 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1409 # days -> ticks
1410 if max_pwd_age == 0:
1411 max_pwd_age_ticks = -0x8000000000000000
1412 else:
1413 max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
1415 m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
1416 ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1417 msgs.append("Maximum password age changed!")
1419 if account_lockout_duration is not None:
1420 if account_lockout_duration == "default":
1421 account_lockout_duration = 30
1422 else:
1423 account_lockout_duration = int(account_lockout_duration)
1425 if account_lockout_duration < 0 or account_lockout_duration > 99999:
1426 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1428 # days -> ticks
1429 if account_lockout_duration == 0:
1430 account_lockout_duration_ticks = -0x8000000000000000
1431 else:
1432 account_lockout_duration_ticks = -int(account_lockout_duration * (60 * 1e7))
1434 m["lockoutDuration"] = ldb.MessageElement(str(account_lockout_duration_ticks),
1435 ldb.FLAG_MOD_REPLACE, "lockoutDuration")
1436 msgs.append("Account lockout duration changed!")
1438 if account_lockout_threshold is not None:
1439 if account_lockout_threshold == "default":
1440 account_lockout_threshold = 0
1441 else:
1442 account_lockout_threshold = int(account_lockout_threshold)
1444 m["lockoutThreshold"] = ldb.MessageElement(str(account_lockout_threshold),
1445 ldb.FLAG_MOD_REPLACE, "lockoutThreshold")
1446 msgs.append("Account lockout threshold changed!")
1448 if reset_account_lockout_after is not None:
1449 if reset_account_lockout_after == "default":
1450 reset_account_lockout_after = 30
1451 else:
1452 reset_account_lockout_after = int(reset_account_lockout_after)
1454 if reset_account_lockout_after < 0 or reset_account_lockout_after > 99999:
1455 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1457 # days -> ticks
1458 if reset_account_lockout_after == 0:
1459 reset_account_lockout_after_ticks = -0x8000000000000000
1460 else:
1461 reset_account_lockout_after_ticks = -int(reset_account_lockout_after * (60 * 1e7))
1463 m["lockOutObservationWindow"] = ldb.MessageElement(str(reset_account_lockout_after_ticks),
1464 ldb.FLAG_MOD_REPLACE, "lockOutObservationWindow")
1465 msgs.append("Duration to reset account lockout after changed!")
1467 if max_pwd_age > 0 and min_pwd_age >= max_pwd_age:
1468 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
1470 if len(m) == 0:
1471 raise CommandError("You must specify at least one option to set. Try --help")
1472 samdb.modify(m)
1473 msgs.append("All changes applied successfully!")
1474 self.message("\n".join(msgs))
1475 else:
1476 raise CommandError("Wrong argument '%s'!" % subcommand)
1479 class cmd_domain_classicupgrade(Command):
1480 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1482 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1483 the testparm utility from your classic installation (with --testparm).
1486 synopsis = "%prog [options] <classic_smb_conf>"
1488 takes_optiongroups = {
1489 "sambaopts": options.SambaOptions,
1490 "versionopts": options.VersionOptions
1493 takes_options = [
1494 Option("--dbdir", type="string", metavar="DIR",
1495 help="Path to samba classic DC database directory"),
1496 Option("--testparm", type="string", metavar="PATH",
1497 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1498 Option("--targetdir", type="string", metavar="DIR",
1499 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1500 Option("--quiet", help="Be quiet", action="store_true"),
1501 Option("--verbose", help="Be verbose", action="store_true"),
1502 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
1503 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1504 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1505 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1506 "BIND9_DLZ uses samba4 AD to store zone information, "
1507 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1508 default="SAMBA_INTERNAL")
1511 ntvfs_options = [
1512 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
1513 action="store_true"),
1514 Option("--use-xattrs", type="choice", choices=["yes","no","auto"],
1515 metavar="[yes|no|auto]",
1516 help="Define if we should use the native fs capabilities or a tdb file for "
1517 "storing attributes likes ntacl when --use-ntvfs is set. "
1518 "auto tries to make an inteligent guess based on the user rights and system capabilities",
1519 default="auto")
1521 if samba.is_ntvfs_fileserver_built():
1522 takes_options.extend(ntvfs_options)
1524 takes_args = ["smbconf"]
1526 def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
1527 quiet=False, verbose=False, use_xattrs="auto", sambaopts=None, versionopts=None,
1528 dns_backend=None, use_ntvfs=False):
1530 if not os.path.exists(smbconf):
1531 raise CommandError("File %s does not exist" % smbconf)
1533 if testparm and not os.path.exists(testparm):
1534 raise CommandError("Testparm utility %s does not exist" % testparm)
1536 if dbdir and not os.path.exists(dbdir):
1537 raise CommandError("Directory %s does not exist" % dbdir)
1539 if not dbdir and not testparm:
1540 raise CommandError("Please specify either dbdir or testparm")
1542 logger = self.get_logger()
1543 if verbose:
1544 logger.setLevel(logging.DEBUG)
1545 elif quiet:
1546 logger.setLevel(logging.WARNING)
1547 else:
1548 logger.setLevel(logging.INFO)
1550 if dbdir and testparm:
1551 logger.warning("both dbdir and testparm specified, ignoring dbdir.")
1552 dbdir = None
1554 lp = sambaopts.get_loadparm()
1556 s3conf = s3param.get_context()
1558 if sambaopts.realm:
1559 s3conf.set("realm", sambaopts.realm)
1561 if targetdir is not None:
1562 if not os.path.isdir(targetdir):
1563 os.mkdir(targetdir)
1565 eadb = True
1566 if use_xattrs == "yes":
1567 eadb = False
1568 elif use_xattrs == "auto" and use_ntvfs == False:
1569 eadb = False
1570 elif use_ntvfs == False:
1571 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
1572 "Please re-run with --use-xattrs omitted.")
1573 elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
1574 if targetdir:
1575 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
1576 else:
1577 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
1578 try:
1579 try:
1580 samba.ntacls.setntacl(lp, tmpfile.name,
1581 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1582 eadb = False
1583 except Exception:
1584 # FIXME: Don't catch all exceptions here
1585 logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. "
1586 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1587 finally:
1588 tmpfile.close()
1590 # Set correct default values from dbdir or testparm
1591 paths = {}
1592 if dbdir:
1593 paths["state directory"] = dbdir
1594 paths["private dir"] = dbdir
1595 paths["lock directory"] = dbdir
1596 paths["smb passwd file"] = dbdir + "/smbpasswd"
1597 else:
1598 paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
1599 paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
1600 paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
1601 paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
1602 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1603 # "state directory", instead make use of "lock directory"
1604 if len(paths["state directory"]) == 0:
1605 paths["state directory"] = paths["lock directory"]
1607 for p in paths:
1608 s3conf.set(p, paths[p])
1610 # load smb.conf parameters
1611 logger.info("Reading smb.conf")
1612 s3conf.load(smbconf)
1613 samba3 = Samba3(smbconf, s3conf)
1615 logger.info("Provisioning")
1616 upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
1617 useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
1620 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
1621 __doc__ = cmd_domain_classicupgrade.__doc__
1623 # This command is present for backwards compatibility only,
1624 # and should not be shown.
1626 hidden = True
1628 class LocalDCCredentialsOptions(options.CredentialsOptions):
1629 def __init__(self, parser):
1630 options.CredentialsOptions.__init__(self, parser, special_name="local-dc")
1632 class DomainTrustCommand(Command):
1633 """List domain trusts."""
1635 def __init__(self):
1636 Command.__init__(self)
1637 self.local_lp = None
1639 self.local_server = None
1640 self.local_binding_string = None
1641 self.local_creds = None
1643 self.remote_server = None
1644 self.remote_binding_string = None
1645 self.remote_creds = None
1647 def _uint32(self, v):
1648 return ctypes.c_uint32(v).value
1650 def check_runtime_error(self, runtime, val):
1651 if runtime is None:
1652 return False
1654 err32 = self._uint32(runtime[0])
1655 if err32 == val:
1656 return True
1658 return False
1660 class LocalRuntimeError(CommandError):
1661 def __init__(exception_self, self, runtime, message):
1662 err32 = self._uint32(runtime[0])
1663 errstr = runtime[1]
1664 msg = "LOCAL_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1665 self.local_server, message, err32, errstr)
1666 CommandError.__init__(exception_self, msg)
1668 class RemoteRuntimeError(CommandError):
1669 def __init__(exception_self, self, runtime, message):
1670 err32 = self._uint32(runtime[0])
1671 errstr = runtime[1]
1672 msg = "REMOTE_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1673 self.remote_server, message, err32, errstr)
1674 CommandError.__init__(exception_self, msg)
1676 class LocalLdbError(CommandError):
1677 def __init__(exception_self, self, ldb_error, message):
1678 errval = ldb_error[0]
1679 errstr = ldb_error[1]
1680 msg = "LOCAL_DC[%s]: %s - ERROR(%d) - %s" % (
1681 self.local_server, message, errval, errstr)
1682 CommandError.__init__(exception_self, msg)
1684 def setup_local_server(self, sambaopts, localdcopts):
1685 if self.local_server is not None:
1686 return self.local_server
1688 lp = sambaopts.get_loadparm()
1690 local_server = localdcopts.ipaddress
1691 if local_server is None:
1692 server_role = lp.server_role()
1693 if server_role != "ROLE_ACTIVE_DIRECTORY_DC":
1694 raise CommandError("Invalid server_role %s" % (server_role))
1695 local_server = lp.get('netbios name')
1696 local_transport = "ncalrpc"
1697 local_binding_options = ""
1698 local_binding_options += ",auth_type=ncalrpc_as_system"
1699 local_ldap_url = None
1700 local_creds = None
1701 else:
1702 local_transport = "ncacn_np"
1703 local_binding_options = ""
1704 local_ldap_url = "ldap://%s" % local_server
1705 local_creds = localdcopts.get_credentials(lp)
1707 self.local_lp = lp
1709 self.local_server = local_server
1710 self.local_binding_string = "%s:%s[%s]" % (local_transport, local_server, local_binding_options)
1711 self.local_ldap_url = local_ldap_url
1712 self.local_creds = local_creds
1713 return self.local_server
1715 def new_local_lsa_connection(self):
1716 return lsa.lsarpc(self.local_binding_string, self.local_lp, self.local_creds)
1718 def new_local_netlogon_connection(self):
1719 return netlogon.netlogon(self.local_binding_string, self.local_lp, self.local_creds)
1721 def new_local_ldap_connection(self):
1722 return SamDB(url=self.local_ldap_url,
1723 session_info=system_session(),
1724 credentials=self.local_creds,
1725 lp=self.local_lp)
1727 def setup_remote_server(self, credopts, domain,
1728 require_pdc=True,
1729 require_writable=True):
1731 if require_pdc:
1732 assert require_writable
1734 if self.remote_server is not None:
1735 return self.remote_server
1737 self.remote_server = "__unknown__remote_server__.%s" % domain
1738 assert self.local_server is not None
1740 remote_creds = credopts.get_credentials(self.local_lp)
1741 remote_server = credopts.ipaddress
1742 remote_binding_options = ""
1744 # TODO: we should also support NT4 domains
1745 # we could use local_netlogon.netr_DsRGetDCNameEx2() with the remote domain name
1746 # and delegate NBT or CLDAP to the local netlogon server
1747 try:
1748 remote_net = Net(remote_creds, self.local_lp, server=remote_server)
1749 remote_flags = nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS
1750 if require_writable:
1751 remote_flags |= nbt.NBT_SERVER_WRITABLE
1752 if require_pdc:
1753 remote_flags |= nbt.NBT_SERVER_PDC
1754 remote_info = remote_net.finddc(flags=remote_flags, domain=domain, address=remote_server)
1755 except Exception:
1756 raise CommandError("Failed to find a writeable DC for domain '%s'" % domain)
1757 flag_map = {
1758 nbt.NBT_SERVER_PDC: "PDC",
1759 nbt.NBT_SERVER_GC: "GC",
1760 nbt.NBT_SERVER_LDAP: "LDAP",
1761 nbt.NBT_SERVER_DS: "DS",
1762 nbt.NBT_SERVER_KDC: "KDC",
1763 nbt.NBT_SERVER_TIMESERV: "TIMESERV",
1764 nbt.NBT_SERVER_CLOSEST: "CLOSEST",
1765 nbt.NBT_SERVER_WRITABLE: "WRITABLE",
1766 nbt.NBT_SERVER_GOOD_TIMESERV: "GOOD_TIMESERV",
1767 nbt.NBT_SERVER_NDNC: "NDNC",
1768 nbt.NBT_SERVER_SELECT_SECRET_DOMAIN_6: "SELECT_SECRET_DOMAIN_6",
1769 nbt.NBT_SERVER_FULL_SECRET_DOMAIN_6: "FULL_SECRET_DOMAIN_6",
1770 nbt.NBT_SERVER_ADS_WEB_SERVICE: "ADS_WEB_SERVICE",
1771 nbt.NBT_SERVER_DS_8: "DS_8",
1772 nbt.NBT_SERVER_HAS_DNS_NAME: "HAS_DNS_NAME",
1773 nbt.NBT_SERVER_IS_DEFAULT_NC: "IS_DEFAULT_NC",
1774 nbt.NBT_SERVER_FOREST_ROOT: "FOREST_ROOT",
1776 server_type_string = self.generic_bitmap_to_string(flag_map,
1777 remote_info.server_type, names_only=True)
1778 self.outf.write("RemoteDC Netbios[%s] DNS[%s] ServerType[%s]\n" % (
1779 remote_info.pdc_name,
1780 remote_info.pdc_dns_name,
1781 server_type_string))
1783 self.remote_server = remote_info.pdc_dns_name
1784 self.remote_binding_string="ncacn_np:%s[%s]" % (self.remote_server, remote_binding_options)
1785 self.remote_creds = remote_creds
1786 return self.remote_server
1788 def new_remote_lsa_connection(self):
1789 return lsa.lsarpc(self.remote_binding_string, self.local_lp, self.remote_creds)
1791 def new_remote_netlogon_connection(self):
1792 return netlogon.netlogon(self.remote_binding_string, self.local_lp, self.remote_creds)
1794 def get_lsa_info(self, conn, policy_access):
1795 objectAttr = lsa.ObjectAttribute()
1796 objectAttr.sec_qos = lsa.QosInfo()
1798 policy = conn.OpenPolicy2(''.decode('utf-8'),
1799 objectAttr, policy_access)
1801 info = conn.QueryInfoPolicy2(policy, lsa.LSA_POLICY_INFO_DNS)
1803 return (policy, info)
1805 def get_netlogon_dc_info(self, conn, server):
1806 info = conn.netr_DsRGetDCNameEx2(server,
1807 None, 0, None, None, None,
1808 netlogon.DS_RETURN_DNS_NAME)
1809 return info
1811 def netr_DomainTrust_to_name(self, t):
1812 if t.trust_type == lsa.LSA_TRUST_TYPE_DOWNLEVEL:
1813 return t.netbios_name
1815 return t.dns_name
1817 def netr_DomainTrust_to_type(self, a, t):
1818 primary = None
1819 primary_parent = None
1820 for _t in a:
1821 if _t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
1822 primary = _t
1823 if not _t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1824 primary_parent = a[_t.parent_index]
1825 break
1827 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1828 if t is primary_parent:
1829 return "Parent"
1831 if t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1832 return "TreeRoot"
1834 parent = a[t.parent_index]
1835 if parent is primary:
1836 return "Child"
1838 return "Shortcut"
1840 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1841 return "Forest"
1843 return "External"
1845 def netr_DomainTrust_to_transitive(self, t):
1846 if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1847 return "Yes"
1849 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE:
1850 return "No"
1852 if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1853 return "Yes"
1855 return "No"
1857 def netr_DomainTrust_to_direction(self, t):
1858 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND and \
1859 t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1860 return "BOTH"
1862 if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND:
1863 return "INCOMING"
1865 if t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1866 return "OUTGOING"
1868 return "INVALID"
1870 def generic_enum_to_string(self, e_dict, v, names_only=False):
1871 try:
1872 w = e_dict[v]
1873 except KeyError:
1874 v32 = self._uint32(v)
1875 w = "__unknown__%08X__" % v32
1877 r = "0x%x (%s)" % (v, w)
1878 return r;
1880 def generic_bitmap_to_string(self, b_dict, v, names_only=False):
1882 s = []
1884 c = v
1885 for b in sorted(b_dict.keys()):
1886 if not (c & b):
1887 continue
1888 c &= ~b
1889 s += [b_dict[b]]
1891 if c != 0:
1892 c32 = self._uint32(c)
1893 s += ["__unknown_%08X__" % c32]
1895 w = ",".join(s)
1896 if names_only:
1897 return w
1898 r = "0x%x (%s)" % (v, w)
1899 return r;
1901 def trustType_string(self, v):
1902 types = {
1903 lsa.LSA_TRUST_TYPE_DOWNLEVEL : "DOWNLEVEL",
1904 lsa.LSA_TRUST_TYPE_UPLEVEL : "UPLEVEL",
1905 lsa.LSA_TRUST_TYPE_MIT : "MIT",
1906 lsa.LSA_TRUST_TYPE_DCE : "DCE",
1908 return self.generic_enum_to_string(types, v)
1910 def trustDirection_string(self, v):
1911 directions = {
1912 lsa.LSA_TRUST_DIRECTION_INBOUND |
1913 lsa.LSA_TRUST_DIRECTION_OUTBOUND : "BOTH",
1914 lsa.LSA_TRUST_DIRECTION_INBOUND : "INBOUND",
1915 lsa.LSA_TRUST_DIRECTION_OUTBOUND : "OUTBOUND",
1917 return self.generic_enum_to_string(directions, v)
1919 def trustAttributes_string(self, v):
1920 attributes = {
1921 lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE : "NON_TRANSITIVE",
1922 lsa.LSA_TRUST_ATTRIBUTE_UPLEVEL_ONLY : "UPLEVEL_ONLY",
1923 lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN : "QUARANTINED_DOMAIN",
1924 lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE : "FOREST_TRANSITIVE",
1925 lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION : "CROSS_ORGANIZATION",
1926 lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST : "WITHIN_FOREST",
1927 lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL : "TREAT_AS_EXTERNAL",
1928 lsa.LSA_TRUST_ATTRIBUTE_USES_RC4_ENCRYPTION : "USES_RC4_ENCRYPTION",
1930 return self.generic_bitmap_to_string(attributes, v)
1932 def kerb_EncTypes_string(self, v):
1933 enctypes = {
1934 security.KERB_ENCTYPE_DES_CBC_CRC : "DES_CBC_CRC",
1935 security.KERB_ENCTYPE_DES_CBC_MD5 : "DES_CBC_MD5",
1936 security.KERB_ENCTYPE_RC4_HMAC_MD5 : "RC4_HMAC_MD5",
1937 security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96 : "AES128_CTS_HMAC_SHA1_96",
1938 security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96 : "AES256_CTS_HMAC_SHA1_96",
1939 security.KERB_ENCTYPE_FAST_SUPPORTED : "FAST_SUPPORTED",
1940 security.KERB_ENCTYPE_COMPOUND_IDENTITY_SUPPORTED : "COMPOUND_IDENTITY_SUPPORTED",
1941 security.KERB_ENCTYPE_CLAIMS_SUPPORTED : "CLAIMS_SUPPORTED",
1942 security.KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED : "RESOURCE_SID_COMPRESSION_DISABLED",
1944 return self.generic_bitmap_to_string(enctypes, v)
1946 def entry_tln_status(self, e_flags, ):
1947 if e_flags == 0:
1948 return "Status[Enabled]"
1950 flags = {
1951 lsa.LSA_TLN_DISABLED_NEW : "Disabled-New",
1952 lsa.LSA_TLN_DISABLED_ADMIN : "Disabled",
1953 lsa.LSA_TLN_DISABLED_CONFLICT : "Disabled-Conflicting",
1955 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
1957 def entry_dom_status(self, e_flags):
1958 if e_flags == 0:
1959 return "Status[Enabled]"
1961 flags = {
1962 lsa.LSA_SID_DISABLED_ADMIN : "Disabled-SID",
1963 lsa.LSA_SID_DISABLED_CONFLICT : "Disabled-SID-Conflicting",
1964 lsa.LSA_NB_DISABLED_ADMIN : "Disabled-NB",
1965 lsa.LSA_NB_DISABLED_CONFLICT : "Disabled-NB-Conflicting",
1967 return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
1969 def write_forest_trust_info(self, fti, tln=None, collisions=None):
1970 if tln is not None:
1971 tln_string = " TDO[%s]" % tln
1972 else:
1973 tln_string = ""
1975 self.outf.write("Namespaces[%d]%s:\n" % (
1976 len(fti.entries), tln_string))
1978 for i in xrange(0, len(fti.entries)):
1979 e = fti.entries[i]
1981 flags = e.flags
1982 collision_string = ""
1984 if collisions is not None:
1985 for c in collisions.entries:
1986 if c.index != i:
1987 continue
1988 flags = c.flags
1989 collision_string = " Collision[%s]" % (c.name.string)
1991 d = e.forest_trust_data
1992 if e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
1993 self.outf.write("TLN: %-32s DNS[*.%s]%s\n" % (
1994 self.entry_tln_status(flags),
1995 d.string, collision_string))
1996 elif e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
1997 self.outf.write("TLN_EX: %-29s DNS[*.%s]\n" % (
1998 "", d.string))
1999 elif e.type == lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
2000 self.outf.write("DOM: %-32s DNS[%s] Netbios[%s] SID[%s]%s\n" % (
2001 self.entry_dom_status(flags),
2002 d.dns_domain_name.string,
2003 d.netbios_domain_name.string,
2004 d.domain_sid, collision_string))
2005 return
2007 class cmd_domain_trust_list(DomainTrustCommand):
2008 """List domain trusts."""
2010 synopsis = "%prog [options]"
2012 takes_optiongroups = {
2013 "sambaopts": options.SambaOptions,
2014 "versionopts": options.VersionOptions,
2015 "localdcopts": LocalDCCredentialsOptions,
2018 takes_options = [
2021 def run(self, sambaopts=None, versionopts=None, localdcopts=None):
2023 local_server = self.setup_local_server(sambaopts, localdcopts)
2024 try:
2025 local_netlogon = self.new_local_netlogon_connection()
2026 except RuntimeError as error:
2027 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2029 try:
2030 local_netlogon_trusts = local_netlogon.netr_DsrEnumerateDomainTrusts(local_server,
2031 netlogon.NETR_TRUST_FLAG_IN_FOREST |
2032 netlogon.NETR_TRUST_FLAG_OUTBOUND |
2033 netlogon.NETR_TRUST_FLAG_INBOUND)
2034 except RuntimeError as error:
2035 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
2036 # TODO: we could implement a fallback to lsa.EnumTrustDom()
2037 raise CommandError("LOCAL_DC[%s]: netr_DsrEnumerateDomainTrusts not supported." % (
2038 self.local_server))
2039 raise self.LocalRuntimeError(self, error, "netr_DsrEnumerateDomainTrusts failed")
2041 a = local_netlogon_trusts.array
2042 for t in a:
2043 if t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
2044 continue
2045 self.outf.write("%-14s %-15s %-19s %s\n" % (
2046 "Type[%s]" % self.netr_DomainTrust_to_type(a, t),
2047 "Transitive[%s]" % self.netr_DomainTrust_to_transitive(t),
2048 "Direction[%s]" % self.netr_DomainTrust_to_direction(t),
2049 "Name[%s]" % self.netr_DomainTrust_to_name(t)))
2050 return
2052 class cmd_domain_trust_show(DomainTrustCommand):
2053 """Show trusted domain details."""
2055 synopsis = "%prog NAME [options]"
2057 takes_optiongroups = {
2058 "sambaopts": options.SambaOptions,
2059 "versionopts": options.VersionOptions,
2060 "localdcopts": LocalDCCredentialsOptions,
2063 takes_options = [
2066 takes_args = ["domain"]
2068 def run(self, domain, sambaopts=None, versionopts=None, localdcopts=None):
2070 local_server = self.setup_local_server(sambaopts, localdcopts)
2071 try:
2072 local_lsa = self.new_local_lsa_connection()
2073 except RuntimeError as error:
2074 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2076 try:
2077 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2078 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2079 except RuntimeError as error:
2080 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2082 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2083 local_lsa_info.name.string,
2084 local_lsa_info.dns_domain.string,
2085 local_lsa_info.sid))
2087 lsaString = lsa.String()
2088 lsaString.string = domain
2089 try:
2090 local_tdo_full = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2091 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2092 local_tdo_info = local_tdo_full.info_ex
2093 local_tdo_posix = local_tdo_full.posix_offset
2094 except NTSTATUSError as error:
2095 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2096 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2098 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(FULL_INFO) failed")
2100 try:
2101 local_tdo_enctypes = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2102 lsaString, lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES)
2103 except NTSTATUSError as error:
2104 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_PARAMETER):
2105 error = None
2106 if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_INFO_CLASS):
2107 error = None
2109 if error is not None:
2110 raise self.LocalRuntimeError(self, error,
2111 "QueryTrustedDomainInfoByName(SUPPORTED_ENCRYPTION_TYPES) failed")
2113 local_tdo_enctypes = lsa.TrustDomainInfoSupportedEncTypes()
2114 local_tdo_enctypes.enc_types = 0
2116 try:
2117 local_tdo_forest = None
2118 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2119 local_tdo_forest = local_lsa.lsaRQueryForestTrustInformation(local_policy,
2120 lsaString, lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
2121 except RuntimeError as error:
2122 if self.check_runtime_error(error, ntstatus.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE):
2123 error = None
2124 if self.check_runtime_error(error, ntstatus.NT_STATUS_NOT_FOUND):
2125 error = None
2126 if error is not None:
2127 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation failed")
2129 local_tdo_forest = lsa.ForestTrustInformation()
2130 local_tdo_forest.count = 0
2131 local_tdo_forest.entries = []
2133 self.outf.write("TrusteDomain:\n\n");
2134 self.outf.write("NetbiosName: %s\n" % local_tdo_info.netbios_name.string)
2135 if local_tdo_info.netbios_name.string != local_tdo_info.domain_name.string:
2136 self.outf.write("DnsName: %s\n" % local_tdo_info.domain_name.string)
2137 self.outf.write("SID: %s\n" % local_tdo_info.sid)
2138 self.outf.write("Type: %s\n" % self.trustType_string(local_tdo_info.trust_type))
2139 self.outf.write("Direction: %s\n" % self.trustDirection_string(local_tdo_info.trust_direction))
2140 self.outf.write("Attributes: %s\n" % self.trustAttributes_string(local_tdo_info.trust_attributes))
2141 posix_offset_u32 = ctypes.c_uint32(local_tdo_posix.posix_offset).value
2142 posix_offset_i32 = ctypes.c_int32(local_tdo_posix.posix_offset).value
2143 self.outf.write("PosixOffset: 0x%08X (%d)\n" % (posix_offset_u32, posix_offset_i32))
2144 self.outf.write("kerb_EncTypes: %s\n" % self.kerb_EncTypes_string(local_tdo_enctypes.enc_types))
2146 if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2147 self.write_forest_trust_info(local_tdo_forest,
2148 tln=local_tdo_info.domain_name.string)
2150 return
2152 class cmd_domain_trust_create(DomainTrustCommand):
2153 """Create a domain or forest trust."""
2155 synopsis = "%prog DOMAIN [options]"
2157 takes_optiongroups = {
2158 "sambaopts": options.SambaOptions,
2159 "versionopts": options.VersionOptions,
2160 "credopts": options.CredentialsOptions,
2161 "localdcopts": LocalDCCredentialsOptions,
2164 takes_options = [
2165 Option("--type", type="choice", metavar="TYPE",
2166 choices=["external", "forest"],
2167 help="The type of the trust: 'external' or 'forest'.",
2168 dest='trust_type',
2169 default="external"),
2170 Option("--direction", type="choice", metavar="DIRECTION",
2171 choices=["incoming", "outgoing", "both"],
2172 help="The trust direction: 'incoming', 'outgoing' or 'both'.",
2173 dest='trust_direction',
2174 default="both"),
2175 Option("--create-location", type="choice", metavar="LOCATION",
2176 choices=["local", "both"],
2177 help="Where to create the trusted domain object: 'local' or 'both'.",
2178 dest='create_location',
2179 default="both"),
2180 Option("--cross-organisation", action="store_true",
2181 help="The related domains does not belong to the same organisation.",
2182 dest='cross_organisation',
2183 default=False),
2184 Option("--quarantined", type="choice", metavar="yes|no",
2185 choices=["yes", "no", None],
2186 help="Special SID filtering rules are applied to the trust. "
2187 "With --type=external the default is yes. "
2188 "With --type=forest the default is no.",
2189 dest='quarantined_arg',
2190 default=None),
2191 Option("--not-transitive", action="store_true",
2192 help="The forest trust is not transitive.",
2193 dest='not_transitive',
2194 default=False),
2195 Option("--treat-as-external", action="store_true",
2196 help="The treat the forest trust as external.",
2197 dest='treat_as_external',
2198 default=False),
2199 Option("--no-aes-keys", action="store_false",
2200 help="The trust uses aes kerberos keys.",
2201 dest='use_aes_keys',
2202 default=True),
2203 Option("--skip-validation", action="store_false",
2204 help="Skip validation of the trust.",
2205 dest='validate',
2206 default=True),
2209 takes_args = ["domain"]
2211 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2212 trust_type=None, trust_direction=None, create_location=None,
2213 cross_organisation=False, quarantined_arg=None,
2214 not_transitive=False, treat_as_external=False,
2215 use_aes_keys=False, validate=True):
2217 lsaString = lsa.String()
2219 quarantined = False
2220 if quarantined_arg is None:
2221 if trust_type == 'external':
2222 quarantined = True
2223 elif quarantined_arg == 'yes':
2224 quarantined = True
2226 if trust_type != 'forest':
2227 if not_transitive:
2228 raise CommandError("--not-transitive requires --type=forest")
2229 if treat_as_external:
2230 raise CommandError("--treat-as-external requires --type=forest")
2232 enc_types = None
2233 if use_aes_keys:
2234 enc_types = lsa.TrustDomainInfoSupportedEncTypes()
2235 enc_types.enc_types = security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96
2236 enc_types.enc_types |= security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96
2238 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2239 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2240 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2242 local_trust_info = lsa.TrustDomainInfoInfoEx()
2243 local_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2244 local_trust_info.trust_direction = 0
2245 if trust_direction == "both":
2246 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2247 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2248 elif trust_direction == "incoming":
2249 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2250 elif trust_direction == "outgoing":
2251 local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2252 local_trust_info.trust_attributes = 0
2253 if cross_organisation:
2254 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2255 if quarantined:
2256 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2257 if trust_type == "forest":
2258 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2259 if not_transitive:
2260 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2261 if treat_as_external:
2262 local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2264 def get_password(name):
2265 password = None
2266 while True:
2267 if password is not None and password is not '':
2268 return password
2269 password = getpass("New %s Password: " % name)
2270 passwordverify = getpass("Retype %s Password: " % name)
2271 if not password == passwordverify:
2272 password = None
2273 self.outf.write("Sorry, passwords do not match.\n")
2275 incoming_secret = None
2276 outgoing_secret = None
2277 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2278 if create_location == "local":
2279 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2280 incoming_password = get_password("Incoming Trust")
2281 incoming_secret = string_to_byte_array(incoming_password.encode('utf-16-le'))
2282 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2283 outgoing_password = get_password("Outgoing Trust")
2284 outgoing_secret = string_to_byte_array(outgoing_password.encode('utf-16-le'))
2286 remote_trust_info = None
2287 else:
2288 # We use 240 random bytes.
2289 # Windows uses 28 or 240 random bytes. I guess it's
2290 # based on the trust type external vs. forest.
2292 # The initial trust password can be up to 512 bytes
2293 # while the versioned passwords used for periodic updates
2294 # can only be up to 498 bytes, as netr_ServerPasswordSet2()
2295 # needs to pass the NL_PASSWORD_VERSION structure within the
2296 # 512 bytes and a 2 bytes confounder is required.
2298 def random_trust_secret(length):
2299 pw = samba.generate_random_machine_password(length/2, length/2)
2300 return string_to_byte_array(pw.encode('utf-16-le'))
2302 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2303 incoming_secret = random_trust_secret(240)
2304 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2305 outgoing_secret = random_trust_secret(240)
2307 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2308 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2310 remote_trust_info = lsa.TrustDomainInfoInfoEx()
2311 remote_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2312 remote_trust_info.trust_direction = 0
2313 if trust_direction == "both":
2314 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2315 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2316 elif trust_direction == "incoming":
2317 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2318 elif trust_direction == "outgoing":
2319 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2320 remote_trust_info.trust_attributes = 0
2321 if cross_organisation:
2322 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2323 if quarantined:
2324 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2325 if trust_type == "forest":
2326 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2327 if not_transitive:
2328 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2329 if treat_as_external:
2330 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2332 local_server = self.setup_local_server(sambaopts, localdcopts)
2333 try:
2334 local_lsa = self.new_local_lsa_connection()
2335 except RuntimeError as error:
2336 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2338 try:
2339 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2340 except RuntimeError as error:
2341 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2343 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2344 local_lsa_info.name.string,
2345 local_lsa_info.dns_domain.string,
2346 local_lsa_info.sid))
2348 try:
2349 remote_server = self.setup_remote_server(credopts, domain)
2350 except RuntimeError as error:
2351 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2353 try:
2354 remote_lsa = self.new_remote_lsa_connection()
2355 except RuntimeError as error:
2356 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2358 try:
2359 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2360 except RuntimeError as error:
2361 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2363 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2364 remote_lsa_info.name.string,
2365 remote_lsa_info.dns_domain.string,
2366 remote_lsa_info.sid))
2368 local_trust_info.domain_name.string = remote_lsa_info.dns_domain.string
2369 local_trust_info.netbios_name.string = remote_lsa_info.name.string
2370 local_trust_info.sid = remote_lsa_info.sid
2372 if remote_trust_info:
2373 remote_trust_info.domain_name.string = local_lsa_info.dns_domain.string
2374 remote_trust_info.netbios_name.string = local_lsa_info.name.string
2375 remote_trust_info.sid = local_lsa_info.sid
2377 try:
2378 lsaString.string = local_trust_info.domain_name.string
2379 local_old_netbios = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2380 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2381 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2382 except NTSTATUSError as error:
2383 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2384 raise self.LocalRuntimeError(self, error,
2385 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2386 lsaString.string))
2388 try:
2389 lsaString.string = local_trust_info.netbios_name.string
2390 local_old_dns = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2391 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2392 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2393 except NTSTATUSError as error:
2394 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2395 raise self.LocalRuntimeError(self, error,
2396 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2397 lsaString.string))
2399 if remote_trust_info:
2400 try:
2401 lsaString.string = remote_trust_info.domain_name.string
2402 remote_old_netbios = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2403 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2404 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2405 except NTSTATUSError as error:
2406 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2407 raise self.RemoteRuntimeError(self, error,
2408 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2409 lsaString.string))
2411 try:
2412 lsaString.string = remote_trust_info.netbios_name.string
2413 remote_old_dns = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2414 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2415 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2416 except NTSTATUSError as error:
2417 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2418 raise self.RemoteRuntimeError(self, error,
2419 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2420 lsaString.string))
2422 try:
2423 local_netlogon = self.new_local_netlogon_connection()
2424 except RuntimeError as error:
2425 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2427 try:
2428 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
2429 except RuntimeError as error:
2430 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
2432 if remote_trust_info:
2433 try:
2434 remote_netlogon = self.new_remote_netlogon_connection()
2435 except RuntimeError as error:
2436 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2438 try:
2439 remote_netlogon_info = self.get_netlogon_dc_info(remote_netlogon, remote_server)
2440 except RuntimeError as error:
2441 raise self.RemoteRuntimeError(self, error, "failed to get netlogon dc info")
2443 def generate_AuthInOutBlob(secret, update_time):
2444 if secret is None:
2445 blob = drsblobs.trustAuthInOutBlob()
2446 blob.count = 0
2448 return blob
2450 clear = drsblobs.AuthInfoClear()
2451 clear.size = len(secret)
2452 clear.password = secret
2454 info = drsblobs.AuthenticationInformation()
2455 info.LastUpdateTime = samba.unix2nttime(update_time)
2456 info.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
2457 info.AuthInfo = clear
2459 array = drsblobs.AuthenticationInformationArray()
2460 array.count = 1
2461 array.array = [info]
2463 blob = drsblobs.trustAuthInOutBlob()
2464 blob.count = 1
2465 blob.current = array
2467 return blob
2469 def generate_AuthInfoInternal(session_key, incoming=None, outgoing=None):
2470 confounder = [0] * 512
2471 for i in range(len(confounder)):
2472 confounder[i] = random.randint(0, 255)
2474 trustpass = drsblobs.trustDomainPasswords()
2476 trustpass.confounder = confounder
2477 trustpass.outgoing = outgoing
2478 trustpass.incoming = incoming
2480 trustpass_blob = ndr_pack(trustpass)
2482 encrypted_trustpass = arcfour_encrypt(session_key, trustpass_blob)
2484 auth_blob = lsa.DATA_BUF2()
2485 auth_blob.size = len(encrypted_trustpass)
2486 auth_blob.data = string_to_byte_array(encrypted_trustpass)
2488 auth_info = lsa.TrustDomainInfoAuthInfoInternal()
2489 auth_info.auth_blob = auth_blob
2491 return auth_info
2493 update_time = samba.current_unix_time()
2494 incoming_blob = generate_AuthInOutBlob(incoming_secret, update_time)
2495 outgoing_blob = generate_AuthInOutBlob(outgoing_secret, update_time)
2497 local_tdo_handle = None
2498 remote_tdo_handle = None
2500 local_auth_info = generate_AuthInfoInternal(local_lsa.session_key,
2501 incoming=incoming_blob,
2502 outgoing=outgoing_blob)
2503 if remote_trust_info:
2504 remote_auth_info = generate_AuthInfoInternal(remote_lsa.session_key,
2505 incoming=outgoing_blob,
2506 outgoing=incoming_blob)
2508 try:
2509 if remote_trust_info:
2510 self.outf.write("Creating remote TDO.\n")
2511 current_request = { "location": "remote", "name": "CreateTrustedDomainEx2"}
2512 remote_tdo_handle = remote_lsa.CreateTrustedDomainEx2(remote_policy,
2513 remote_trust_info,
2514 remote_auth_info,
2515 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2516 self.outf.write("Remote TDO created.\n")
2517 if enc_types:
2518 self.outf.write("Setting supported encryption types on remote TDO.\n")
2519 current_request = { "location": "remote", "name": "SetInformationTrustedDomain"}
2520 remote_lsa.SetInformationTrustedDomain(remote_tdo_handle,
2521 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2522 enc_types)
2524 self.outf.write("Creating local TDO.\n")
2525 current_request = { "location": "local", "name": "CreateTrustedDomainEx2"}
2526 local_tdo_handle = local_lsa.CreateTrustedDomainEx2(local_policy,
2527 local_trust_info,
2528 local_auth_info,
2529 lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2530 self.outf.write("Local TDO created\n")
2531 if enc_types:
2532 self.outf.write("Setting supported encryption types on local TDO.\n")
2533 current_request = { "location": "local", "name": "SetInformationTrustedDomain"}
2534 local_lsa.SetInformationTrustedDomain(local_tdo_handle,
2535 lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2536 enc_types)
2537 except RuntimeError as error:
2538 self.outf.write("Error: %s failed %sly - cleaning up\n" % (
2539 current_request['name'], current_request['location']))
2540 if remote_tdo_handle:
2541 self.outf.write("Deleting remote TDO.\n")
2542 remote_lsa.DeleteObject(remote_tdo_handle)
2543 remote_tdo_handle = None
2544 if local_tdo_handle:
2545 self.outf.write("Deleting local TDO.\n")
2546 local_lsa.DeleteObject(local_tdo_handle)
2547 local_tdo_handle = None
2548 if current_request['location'] is "remote":
2549 raise self.RemoteRuntimeError(self, error, "%s" % (
2550 current_request['name']))
2551 raise self.LocalRuntimeError(self, error, "%s" % (
2552 current_request['name']))
2554 if validate:
2555 if local_trust_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2556 self.outf.write("Setup local forest trust information...\n")
2557 try:
2558 # get all information about the remote trust
2559 # this triggers netr_GetForestTrustInformation to the remote domain
2560 # and lsaRSetForestTrustInformation() locally, but new top level
2561 # names are disabled by default.
2562 local_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
2563 remote_lsa_info.dns_domain.string,
2564 netlogon.DS_GFTI_UPDATE_TDO)
2565 except RuntimeError as error:
2566 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2568 try:
2569 # here we try to enable all top level names
2570 local_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
2571 remote_lsa_info.dns_domain,
2572 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2573 local_forest_info,
2575 except RuntimeError as error:
2576 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2578 self.write_forest_trust_info(local_forest_info,
2579 tln=remote_lsa_info.dns_domain.string,
2580 collisions=local_forest_collision)
2582 if remote_trust_info:
2583 self.outf.write("Setup remote forest trust information...\n")
2584 try:
2585 # get all information about the local trust (from the perspective of the remote domain)
2586 # this triggers netr_GetForestTrustInformation to our domain.
2587 # and lsaRSetForestTrustInformation() remotely, but new top level
2588 # names are disabled by default.
2589 remote_forest_info = remote_netlogon.netr_DsRGetForestTrustInformation(remote_netlogon_info.dc_unc,
2590 local_lsa_info.dns_domain.string,
2591 netlogon.DS_GFTI_UPDATE_TDO)
2592 except RuntimeError as error:
2593 raise self.RemoteRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2595 try:
2596 # here we try to enable all top level names
2597 remote_forest_collision = remote_lsa.lsaRSetForestTrustInformation(remote_policy,
2598 local_lsa_info.dns_domain,
2599 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2600 remote_forest_info,
2602 except RuntimeError as error:
2603 raise self.RemoteRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2605 self.write_forest_trust_info(remote_forest_info,
2606 tln=local_lsa_info.dns_domain.string,
2607 collisions=remote_forest_collision)
2609 if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2610 self.outf.write("Validating outgoing trust...\n")
2611 try:
2612 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_netlogon_info.dc_unc,
2613 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2615 remote_lsa_info.dns_domain.string)
2616 except RuntimeError as error:
2617 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2619 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2620 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2622 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2623 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2624 local_trust_verify.trusted_dc_name,
2625 local_trust_verify.tc_connection_status[1],
2626 local_trust_verify.pdc_connection_status[1])
2627 else:
2628 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2629 local_trust_verify.trusted_dc_name,
2630 local_trust_verify.tc_connection_status[1],
2631 local_trust_verify.pdc_connection_status[1])
2633 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2634 raise CommandError(local_validation)
2635 else:
2636 self.outf.write("OK: %s\n" % local_validation)
2638 if remote_trust_info:
2639 if remote_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2640 self.outf.write("Validating incoming trust...\n")
2641 try:
2642 remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_netlogon_info.dc_unc,
2643 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2645 local_lsa_info.dns_domain.string)
2646 except RuntimeError as error:
2647 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2649 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2650 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2652 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2653 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2654 remote_trust_verify.trusted_dc_name,
2655 remote_trust_verify.tc_connection_status[1],
2656 remote_trust_verify.pdc_connection_status[1])
2657 else:
2658 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2659 remote_trust_verify.trusted_dc_name,
2660 remote_trust_verify.tc_connection_status[1],
2661 remote_trust_verify.pdc_connection_status[1])
2663 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
2664 raise CommandError(remote_validation)
2665 else:
2666 self.outf.write("OK: %s\n" % remote_validation)
2668 if remote_tdo_handle is not None:
2669 try:
2670 remote_lsa.Close(remote_tdo_handle)
2671 except RuntimeError as error:
2672 pass
2673 remote_tdo_handle = None
2674 if local_tdo_handle is not None:
2675 try:
2676 local_lsa.Close(local_tdo_handle)
2677 except RuntimeError as error:
2678 pass
2679 local_tdo_handle = None
2681 self.outf.write("Success.\n")
2682 return
2684 class cmd_domain_trust_delete(DomainTrustCommand):
2685 """Delete a domain trust."""
2687 synopsis = "%prog DOMAIN [options]"
2689 takes_optiongroups = {
2690 "sambaopts": options.SambaOptions,
2691 "versionopts": options.VersionOptions,
2692 "credopts": options.CredentialsOptions,
2693 "localdcopts": LocalDCCredentialsOptions,
2696 takes_options = [
2697 Option("--delete-location", type="choice", metavar="LOCATION",
2698 choices=["local", "both"],
2699 help="Where to delete the trusted domain object: 'local' or 'both'.",
2700 dest='delete_location',
2701 default="both"),
2704 takes_args = ["domain"]
2706 def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2707 delete_location=None):
2709 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2710 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2711 local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2713 if delete_location == "local":
2714 remote_policy_access = None
2715 else:
2716 remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2717 remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2718 remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2720 local_server = self.setup_local_server(sambaopts, localdcopts)
2721 try:
2722 local_lsa = self.new_local_lsa_connection()
2723 except RuntimeError as error:
2724 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2726 try:
2727 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2728 except RuntimeError as error:
2729 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2731 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2732 local_lsa_info.name.string,
2733 local_lsa_info.dns_domain.string,
2734 local_lsa_info.sid))
2736 local_tdo_info = None
2737 local_tdo_handle = None
2738 remote_tdo_info = None
2739 remote_tdo_handle = None
2741 lsaString = lsa.String()
2742 try:
2743 lsaString.string = domain
2744 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2745 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2746 except NTSTATUSError as error:
2747 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2748 raise CommandError("Failed to find trust for domain '%s'" % domain)
2749 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2752 if remote_policy_access is not None:
2753 try:
2754 remote_server = self.setup_remote_server(credopts, domain)
2755 except RuntimeError as error:
2756 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2758 try:
2759 remote_lsa = self.new_remote_lsa_connection()
2760 except RuntimeError as error:
2761 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2763 try:
2764 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2765 except RuntimeError as error:
2766 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2768 self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2769 remote_lsa_info.name.string,
2770 remote_lsa_info.dns_domain.string,
2771 remote_lsa_info.sid))
2773 if remote_lsa_info.sid != local_tdo_info.sid or \
2774 remote_lsa_info.name.string != local_tdo_info.netbios_name.string or \
2775 remote_lsa_info.dns_domain.string != local_tdo_info.domain_name.string:
2776 raise CommandError("LocalTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2777 local_tdo_info.netbios_name.string,
2778 local_tdo_info.domain_name.string,
2779 local_tdo_info.sid))
2781 try:
2782 lsaString.string = local_lsa_info.dns_domain.string
2783 remote_tdo_info = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2784 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2785 except NTSTATUSError as error:
2786 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2787 raise self.RemoteRuntimeError(self, error, "QueryTrustedDomainInfoByName(%s)" % (
2788 lsaString.string))
2789 pass
2791 if remote_tdo_info is not None:
2792 if local_lsa_info.sid != remote_tdo_info.sid or \
2793 local_lsa_info.name.string != remote_tdo_info.netbios_name.string or \
2794 local_lsa_info.dns_domain.string != remote_tdo_info.domain_name.string:
2795 raise CommandError("RemoteTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2796 remote_tdo_info.netbios_name.string,
2797 remote_tdo_info.domain_name.string,
2798 remote_tdo_info.sid))
2800 if local_tdo_info is not None:
2801 try:
2802 lsaString.string = local_tdo_info.domain_name.string
2803 local_tdo_handle = local_lsa.OpenTrustedDomainByName(local_policy,
2804 lsaString,
2805 security.SEC_STD_DELETE)
2806 except RuntimeError as error:
2807 raise self.LocalRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2808 lsaString.string))
2810 local_lsa.DeleteObject(local_tdo_handle)
2811 local_tdo_handle = None
2813 if remote_tdo_info is not None:
2814 try:
2815 lsaString.string = remote_tdo_info.domain_name.string
2816 remote_tdo_handle = remote_lsa.OpenTrustedDomainByName(remote_policy,
2817 lsaString,
2818 security.SEC_STD_DELETE)
2819 except RuntimeError as error:
2820 raise self.RemoteRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2821 lsaString.string))
2823 if remote_tdo_handle is not None:
2824 try:
2825 remote_lsa.DeleteObject(remote_tdo_handle)
2826 remote_tdo_handle = None
2827 self.outf.write("RemoteTDO deleted.\n")
2828 except RuntimeError as error:
2829 self.outf.write("%s\n" % self.RemoteRuntimeError(self, error, "DeleteObject() failed"))
2831 if local_tdo_handle is not None:
2832 try:
2833 local_lsa.DeleteObject(local_tdo_handle)
2834 local_tdo_handle = None
2835 self.outf.write("LocalTDO deleted.\n")
2836 except RuntimeError as error:
2837 self.outf.write("%s\n" % self.LocalRuntimeError(self, error, "DeleteObject() failed"))
2839 return
2841 class cmd_domain_trust_validate(DomainTrustCommand):
2842 """Validate a domain trust."""
2844 synopsis = "%prog DOMAIN [options]"
2846 takes_optiongroups = {
2847 "sambaopts": options.SambaOptions,
2848 "versionopts": options.VersionOptions,
2849 "credopts": options.CredentialsOptions,
2850 "localdcopts": LocalDCCredentialsOptions,
2853 takes_options = [
2854 Option("--validate-location", type="choice", metavar="LOCATION",
2855 choices=["local", "both"],
2856 help="Where to validate the trusted domain object: 'local' or 'both'.",
2857 dest='validate_location',
2858 default="both"),
2861 takes_args = ["domain"]
2863 def run(self, domain, sambaopts=None, versionopts=None, credopts=None, localdcopts=None,
2864 validate_location=None):
2866 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2868 local_server = self.setup_local_server(sambaopts, localdcopts)
2869 try:
2870 local_lsa = self.new_local_lsa_connection()
2871 except RuntimeError as error:
2872 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2874 try:
2875 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2876 except RuntimeError as error:
2877 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2879 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2880 local_lsa_info.name.string,
2881 local_lsa_info.dns_domain.string,
2882 local_lsa_info.sid))
2884 try:
2885 lsaString = lsa.String()
2886 lsaString.string = domain
2887 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2888 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2889 except NTSTATUSError as error:
2890 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2891 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2893 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
2895 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
2896 local_tdo_info.netbios_name.string,
2897 local_tdo_info.domain_name.string,
2898 local_tdo_info.sid))
2900 try:
2901 local_netlogon = self.new_local_netlogon_connection()
2902 except RuntimeError as error:
2903 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2905 try:
2906 local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_server,
2907 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2909 local_tdo_info.domain_name.string)
2910 except RuntimeError as error:
2911 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2913 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2914 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2916 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2917 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2918 local_trust_verify.trusted_dc_name,
2919 local_trust_verify.tc_connection_status[1],
2920 local_trust_verify.pdc_connection_status[1])
2921 else:
2922 local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2923 local_trust_verify.trusted_dc_name,
2924 local_trust_verify.tc_connection_status[1],
2925 local_trust_verify.pdc_connection_status[1])
2927 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2928 raise CommandError(local_validation)
2929 else:
2930 self.outf.write("OK: %s\n" % local_validation)
2932 try:
2933 server = local_trust_verify.trusted_dc_name.replace('\\', '')
2934 domain_and_server = "%s\\%s" % (local_tdo_info.domain_name.string, server)
2935 local_trust_rediscover = local_netlogon.netr_LogonControl2Ex(local_server,
2936 netlogon.NETLOGON_CONTROL_REDISCOVER,
2938 domain_and_server)
2939 except RuntimeError as error:
2940 raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
2942 local_conn_status = self._uint32(local_trust_rediscover.tc_connection_status[0])
2943 local_rediscover = "LocalRediscover: DC[%s] CONNECTION[%s]" % (
2944 local_trust_rediscover.trusted_dc_name,
2945 local_trust_rediscover.tc_connection_status[1])
2947 if local_conn_status != werror.WERR_SUCCESS:
2948 raise CommandError(local_rediscover)
2949 else:
2950 self.outf.write("OK: %s\n" % local_rediscover)
2952 if validate_location != "local":
2953 try:
2954 remote_server = self.setup_remote_server(credopts, domain, require_pdc=False)
2955 except RuntimeError as error:
2956 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2958 try:
2959 remote_netlogon = self.new_remote_netlogon_connection()
2960 except RuntimeError as error:
2961 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2963 try:
2964 remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_server,
2965 netlogon.NETLOGON_CONTROL_TC_VERIFY,
2967 local_lsa_info.dns_domain.string)
2968 except RuntimeError as error:
2969 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2971 remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2972 remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2974 if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2975 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2976 remote_trust_verify.trusted_dc_name,
2977 remote_trust_verify.tc_connection_status[1],
2978 remote_trust_verify.pdc_connection_status[1])
2979 else:
2980 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2981 remote_trust_verify.trusted_dc_name,
2982 remote_trust_verify.tc_connection_status[1],
2983 remote_trust_verify.pdc_connection_status[1])
2985 if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
2986 raise CommandError(remote_validation)
2987 else:
2988 self.outf.write("OK: %s\n" % remote_validation)
2990 try:
2991 server = remote_trust_verify.trusted_dc_name.replace('\\', '')
2992 domain_and_server = "%s\\%s" % (local_lsa_info.dns_domain.string, server)
2993 remote_trust_rediscover = remote_netlogon.netr_LogonControl2Ex(remote_server,
2994 netlogon.NETLOGON_CONTROL_REDISCOVER,
2996 domain_and_server)
2997 except RuntimeError as error:
2998 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
3000 remote_conn_status = self._uint32(remote_trust_rediscover.tc_connection_status[0])
3002 remote_rediscover = "RemoteRediscover: DC[%s] CONNECTION[%s]" % (
3003 remote_trust_rediscover.trusted_dc_name,
3004 remote_trust_rediscover.tc_connection_status[1])
3006 if remote_conn_status != werror.WERR_SUCCESS:
3007 raise CommandError(remote_rediscover)
3008 else:
3009 self.outf.write("OK: %s\n" % remote_rediscover)
3011 return
3013 class cmd_domain_trust_namespaces(DomainTrustCommand):
3014 """Manage forest trust namespaces."""
3016 synopsis = "%prog [DOMAIN] [options]"
3018 takes_optiongroups = {
3019 "sambaopts": options.SambaOptions,
3020 "versionopts": options.VersionOptions,
3021 "localdcopts": LocalDCCredentialsOptions,
3024 takes_options = [
3025 Option("--refresh", type="choice", metavar="check|store",
3026 choices=["check", "store", None],
3027 help="List and maybe store refreshed forest trust information: 'check' or 'store'.",
3028 dest='refresh',
3029 default=None),
3030 Option("--enable-all", action="store_true",
3031 help="Try to update disabled entries, not allowed with --refresh=check.",
3032 dest='enable_all',
3033 default=False),
3034 Option("--enable-tln", action="append", metavar='DNSDOMAIN',
3035 help="Enable a top level name entry. Can be specified multiple times.",
3036 dest='enable_tln',
3037 default=[]),
3038 Option("--disable-tln", action="append", metavar='DNSDOMAIN',
3039 help="Disable a top level name entry. Can be specified multiple times.",
3040 dest='disable_tln',
3041 default=[]),
3042 Option("--add-tln-ex", action="append", metavar='DNSDOMAIN',
3043 help="Add a top level exclusion entry. Can be specified multiple times.",
3044 dest='add_tln_ex',
3045 default=[]),
3046 Option("--delete-tln-ex", action="append", metavar='DNSDOMAIN',
3047 help="Delete a top level exclusion entry. Can be specified multiple times.",
3048 dest='delete_tln_ex',
3049 default=[]),
3050 Option("--enable-nb", action="append", metavar='NETBIOSDOMAIN',
3051 help="Enable a netbios name in a domain entry. Can be specified multiple times.",
3052 dest='enable_nb',
3053 default=[]),
3054 Option("--disable-nb", action="append", metavar='NETBIOSDOMAIN',
3055 help="Disable a netbios name in a domain entry. Can be specified multiple times.",
3056 dest='disable_nb',
3057 default=[]),
3058 Option("--enable-sid", action="append", metavar='DOMAINSID',
3059 help="Enable a SID in a domain entry. Can be specified multiple times.",
3060 dest='enable_sid_str',
3061 default=[]),
3062 Option("--disable-sid", action="append", metavar='DOMAINSID',
3063 help="Disable a SID in a domain entry. Can be specified multiple times.",
3064 dest='disable_sid_str',
3065 default=[]),
3066 Option("--add-upn-suffix", action="append", metavar='DNSDOMAIN',
3067 help="Add a new uPNSuffixes attribute for the local forest. Can be specified multiple times.",
3068 dest='add_upn',
3069 default=[]),
3070 Option("--delete-upn-suffix", action="append", metavar='DNSDOMAIN',
3071 help="Delete an existing uPNSuffixes attribute of the local forest. Can be specified multiple times.",
3072 dest='delete_upn',
3073 default=[]),
3074 Option("--add-spn-suffix", action="append", metavar='DNSDOMAIN',
3075 help="Add a new msDS-SPNSuffixes attribute for the local forest. Can be specified multiple times.",
3076 dest='add_spn',
3077 default=[]),
3078 Option("--delete-spn-suffix", action="append", metavar='DNSDOMAIN',
3079 help="Delete an existing msDS-SPNSuffixes attribute of the local forest. Can be specified multiple times.",
3080 dest='delete_spn',
3081 default=[]),
3084 takes_args = ["domain?"]
3086 def run(self, domain=None, sambaopts=None, localdcopts=None, versionopts=None,
3087 refresh=None, enable_all=False,
3088 enable_tln=[], disable_tln=[], add_tln_ex=[], delete_tln_ex=[],
3089 enable_sid_str=[], disable_sid_str=[], enable_nb=[], disable_nb=[],
3090 add_upn=[], delete_upn=[], add_spn=[], delete_spn=[]):
3092 require_update = False
3094 if domain is None:
3095 if refresh == "store":
3096 raise CommandError("--refresh=%s not allowed without DOMAIN" % refresh)
3098 if enable_all:
3099 raise CommandError("--enable-all not allowed without DOMAIN")
3101 if len(enable_tln) > 0:
3102 raise CommandError("--enable-tln not allowed without DOMAIN")
3103 if len(disable_tln) > 0:
3104 raise CommandError("--disable-tln not allowed without DOMAIN")
3106 if len(add_tln_ex) > 0:
3107 raise CommandError("--add-tln-ex not allowed without DOMAIN")
3108 if len(delete_tln_ex) > 0:
3109 raise CommandError("--delete-tln-ex not allowed without DOMAIN")
3111 if len(enable_nb) > 0:
3112 raise CommandError("--enable-nb not allowed without DOMAIN")
3113 if len(disable_nb) > 0:
3114 raise CommandError("--disable-nb not allowed without DOMAIN")
3116 if len(enable_sid_str) > 0:
3117 raise CommandError("--enable-sid not allowed without DOMAIN")
3118 if len(disable_sid_str) > 0:
3119 raise CommandError("--disable-sid not allowed without DOMAIN")
3121 if len(add_upn) > 0:
3122 for n in add_upn:
3123 if not n.startswith("*."):
3124 continue
3125 raise CommandError("value[%s] specified for --add-upn-suffix should not include with '*.'" % n)
3126 require_update = True
3127 if len(delete_upn) > 0:
3128 for n in delete_upn:
3129 if not n.startswith("*."):
3130 continue
3131 raise CommandError("value[%s] specified for --delete-upn-suffix should not include with '*.'" % n)
3132 require_update = True
3133 for a in add_upn:
3134 for d in delete_upn:
3135 if a.lower() != d.lower():
3136 continue
3137 raise CommandError("value[%s] specified for --add-upn-suffix and --delete-upn-suffix" % a)
3139 if len(add_spn) > 0:
3140 for n in add_spn:
3141 if not n.startswith("*."):
3142 continue
3143 raise CommandError("value[%s] specified for --add-spn-suffix should not include with '*.'" % n)
3144 require_update = True
3145 if len(delete_spn) > 0:
3146 for n in delete_spn:
3147 if not n.startswith("*."):
3148 continue
3149 raise CommandError("value[%s] specified for --delete-spn-suffix should not include with '*.'" % n)
3150 require_update = True
3151 for a in add_spn:
3152 for d in delete_spn:
3153 if a.lower() != d.lower():
3154 continue
3155 raise CommandError("value[%s] specified for --add-spn-suffix and --delete-spn-suffix" % a)
3156 else:
3157 if len(add_upn) > 0:
3158 raise CommandError("--add-upn-suffix not allowed together with DOMAIN")
3159 if len(delete_upn) > 0:
3160 raise CommandError("--delete-upn-suffix not allowed together with DOMAIN")
3161 if len(add_spn) > 0:
3162 raise CommandError("--add-spn-suffix not allowed together with DOMAIN")
3163 if len(delete_spn) > 0:
3164 raise CommandError("--delete-spn-suffix not allowed together with DOMAIN")
3166 if refresh is not None:
3167 if refresh == "store":
3168 require_update = True
3170 if enable_all and refresh != "store":
3171 raise CommandError("--enable-all not allowed together with --refresh=%s" % refresh)
3173 if len(enable_tln) > 0:
3174 raise CommandError("--enable-tln not allowed together with --refresh")
3175 if len(disable_tln) > 0:
3176 raise CommandError("--disable-tln not allowed together with --refresh")
3178 if len(add_tln_ex) > 0:
3179 raise CommandError("--add-tln-ex not allowed together with --refresh")
3180 if len(delete_tln_ex) > 0:
3181 raise CommandError("--delete-tln-ex not allowed together with --refresh")
3183 if len(enable_nb) > 0:
3184 raise CommandError("--enable-nb not allowed together with --refresh")
3185 if len(disable_nb) > 0:
3186 raise CommandError("--disable-nb not allowed together with --refresh")
3188 if len(enable_sid_str) > 0:
3189 raise CommandError("--enable-sid not allowed together with --refresh")
3190 if len(disable_sid_str) > 0:
3191 raise CommandError("--disable-sid not allowed together with --refresh")
3192 else:
3193 if enable_all:
3194 require_update = True
3196 if len(enable_tln) > 0:
3197 raise CommandError("--enable-tln not allowed together with --enable-all")
3199 if len(enable_nb) > 0:
3200 raise CommandError("--enable-nb not allowed together with --enable-all")
3202 if len(enable_sid_str) > 0:
3203 raise CommandError("--enable-sid not allowed together with --enable-all")
3205 if len(enable_tln) > 0:
3206 require_update = True
3207 if len(disable_tln) > 0:
3208 require_update = True
3209 for e in enable_tln:
3210 for d in disable_tln:
3211 if e.lower() != d.lower():
3212 continue
3213 raise CommandError("value[%s] specified for --enable-tln and --disable-tln" % e)
3215 if len(add_tln_ex) > 0:
3216 for n in add_tln_ex:
3217 if not n.startswith("*."):
3218 continue
3219 raise CommandError("value[%s] specified for --add-tln-ex should not include with '*.'" % n)
3220 require_update = True
3221 if len(delete_tln_ex) > 0:
3222 for n in delete_tln_ex:
3223 if not n.startswith("*."):
3224 continue
3225 raise CommandError("value[%s] specified for --delete-tln-ex should not include with '*.'" % n)
3226 require_update = True
3227 for a in add_tln_ex:
3228 for d in delete_tln_ex:
3229 if a.lower() != d.lower():
3230 continue
3231 raise CommandError("value[%s] specified for --add-tln-ex and --delete-tln-ex" % a)
3233 if len(enable_nb) > 0:
3234 require_update = True
3235 if len(disable_nb) > 0:
3236 require_update = True
3237 for e in enable_nb:
3238 for d in disable_nb:
3239 if e.upper() != d.upper():
3240 continue
3241 raise CommandError("value[%s] specified for --enable-nb and --disable-nb" % e)
3243 enable_sid = []
3244 for s in enable_sid_str:
3245 try:
3246 sid = security.dom_sid(s)
3247 except TypeError as error:
3248 raise CommandError("value[%s] specified for --enable-sid is not a valid SID" % s)
3249 enable_sid.append(sid)
3250 disable_sid = []
3251 for s in disable_sid_str:
3252 try:
3253 sid = security.dom_sid(s)
3254 except TypeError as error:
3255 raise CommandError("value[%s] specified for --disable-sid is not a valid SID" % s)
3256 disable_sid.append(sid)
3257 if len(enable_sid) > 0:
3258 require_update = True
3259 if len(disable_sid) > 0:
3260 require_update = True
3261 for e in enable_sid:
3262 for d in disable_sid:
3263 if e != d:
3264 continue
3265 raise CommandError("value[%s] specified for --enable-sid and --disable-sid" % e)
3267 local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
3268 if require_update:
3269 local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
3271 local_server = self.setup_local_server(sambaopts, localdcopts)
3272 try:
3273 local_lsa = self.new_local_lsa_connection()
3274 except RuntimeError as error:
3275 raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
3277 try:
3278 (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
3279 except RuntimeError as error:
3280 raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
3282 self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
3283 local_lsa_info.name.string,
3284 local_lsa_info.dns_domain.string,
3285 local_lsa_info.sid))
3287 if domain is None:
3288 try:
3289 local_netlogon = self.new_local_netlogon_connection()
3290 except RuntimeError as error:
3291 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3293 try:
3294 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3295 except RuntimeError as error:
3296 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3298 if local_netlogon_info.domain_name != local_netlogon_info.forest_name:
3299 raise CommandError("The local domain [%s] is not the forest root [%s]" % (
3300 local_netlogon_info.domain_name,
3301 local_netlogon_info.forest_name))
3303 try:
3304 # get all information about our own forest
3305 own_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3306 None, 0)
3307 except RuntimeError as error:
3308 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
3309 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3310 self.local_server))
3312 if self.check_runtime_error(error, werror.WERR_INVALID_FUNCTION):
3313 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3314 self.local_server))
3316 if self.check_runtime_error(error, werror.WERR_NERR_ACFNOTLOADED):
3317 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3318 self.local_server))
3320 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3322 self.outf.write("Own forest trust information...\n")
3323 self.write_forest_trust_info(own_forest_info,
3324 tln=local_lsa_info.dns_domain.string)
3326 try:
3327 local_samdb = self.new_local_ldap_connection()
3328 except RuntimeError as error:
3329 raise self.LocalRuntimeError(self, error, "failed to connect to SamDB")
3331 local_partitions_dn = "CN=Partitions,%s" % str(local_samdb.get_config_basedn())
3332 attrs = ['uPNSuffixes', 'msDS-SPNSuffixes']
3333 try:
3334 msgs = local_samdb.search(base=local_partitions_dn,
3335 scope=ldb.SCOPE_BASE,
3336 expression="(objectClass=crossRefContainer)",
3337 attrs=attrs)
3338 stored_msg = msgs[0]
3339 except ldb.LdbError as error:
3340 raise self.LocalLdbError(self, error, "failed to search partition dn")
3342 stored_upn_vals = []
3343 if 'uPNSuffixes' in stored_msg:
3344 stored_upn_vals.extend(stored_msg['uPNSuffixes'])
3346 stored_spn_vals = []
3347 if 'msDS-SPNSuffixes' in stored_msg:
3348 stored_spn_vals.extend(stored_msg['msDS-SPNSuffixes'])
3350 self.outf.write("Stored uPNSuffixes attributes[%d]:\n" % len(stored_upn_vals))
3351 for v in stored_upn_vals:
3352 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3353 self.outf.write("Stored msDS-SPNSuffixes attributes[%d]:\n" % len(stored_spn_vals))
3354 for v in stored_spn_vals:
3355 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3357 if not require_update:
3358 return
3360 replace_upn = False
3361 update_upn_vals = []
3362 update_upn_vals.extend(stored_upn_vals)
3364 replace_spn = False
3365 update_spn_vals = []
3366 update_spn_vals.extend(stored_spn_vals)
3368 for upn in add_upn:
3369 idx = None
3370 for i in xrange(0, len(update_upn_vals)):
3371 v = update_upn_vals[i]
3372 if v.lower() != upn.lower():
3373 continue
3374 idx = i
3375 break
3376 if idx is not None:
3377 raise CommandError("Entry already present for value[%s] specified for --add-upn-suffix" % upn)
3378 update_upn_vals.append(upn)
3379 replace_upn = True
3381 for upn in delete_upn:
3382 idx = None
3383 for i in xrange(0, len(update_upn_vals)):
3384 v = update_upn_vals[i]
3385 if v.lower() != upn.lower():
3386 continue
3387 idx = i
3388 break
3389 if idx is None:
3390 raise CommandError("Entry not found for value[%s] specified for --delete-upn-suffix" % upn)
3392 update_upn_vals.pop(idx)
3393 replace_upn = True
3395 for spn in add_spn:
3396 idx = None
3397 for i in xrange(0, len(update_spn_vals)):
3398 v = update_spn_vals[i]
3399 if v.lower() != spn.lower():
3400 continue
3401 idx = i
3402 break
3403 if idx is not None:
3404 raise CommandError("Entry already present for value[%s] specified for --add-spn-suffix" % spn)
3405 update_spn_vals.append(spn)
3406 replace_spn = True
3408 for spn in delete_spn:
3409 idx = None
3410 for i in xrange(0, len(update_spn_vals)):
3411 v = update_spn_vals[i]
3412 if v.lower() != spn.lower():
3413 continue
3414 idx = i
3415 break
3416 if idx is None:
3417 raise CommandError("Entry not found for value[%s] specified for --delete-spn-suffix" % spn)
3419 update_spn_vals.pop(idx)
3420 replace_spn = True
3422 self.outf.write("Update uPNSuffixes attributes[%d]:\n" % len(update_upn_vals))
3423 for v in update_upn_vals:
3424 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3425 self.outf.write("Update msDS-SPNSuffixes attributes[%d]:\n" % len(update_spn_vals))
3426 for v in update_spn_vals:
3427 self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3429 update_msg = ldb.Message()
3430 update_msg.dn = stored_msg.dn
3432 if replace_upn:
3433 update_msg['uPNSuffixes'] = ldb.MessageElement(update_upn_vals,
3434 ldb.FLAG_MOD_REPLACE,
3435 'uPNSuffixes')
3436 if replace_spn:
3437 update_msg['msDS-SPNSuffixes'] = ldb.MessageElement(update_spn_vals,
3438 ldb.FLAG_MOD_REPLACE,
3439 'msDS-SPNSuffixes')
3440 try:
3441 local_samdb.modify(update_msg)
3442 except ldb.LdbError as error:
3443 raise self.LocalLdbError(self, error, "failed to update partition dn")
3445 try:
3446 stored_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3447 None, 0)
3448 except RuntimeError as error:
3449 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3451 self.outf.write("Stored forest trust information...\n")
3452 self.write_forest_trust_info(stored_forest_info,
3453 tln=local_lsa_info.dns_domain.string)
3454 return
3456 try:
3457 lsaString = lsa.String()
3458 lsaString.string = domain
3459 local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
3460 lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
3461 except NTSTATUSError as error:
3462 if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
3463 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
3465 raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
3467 self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
3468 local_tdo_info.netbios_name.string,
3469 local_tdo_info.domain_name.string,
3470 local_tdo_info.sid))
3472 if not local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
3473 raise CommandError("trusted domain object for domain [%s] is not marked as FOREST_TRANSITIVE." % domain)
3475 if refresh is not None:
3476 try:
3477 local_netlogon = self.new_local_netlogon_connection()
3478 except RuntimeError as error:
3479 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3481 try:
3482 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3483 except RuntimeError as error:
3484 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3486 lsa_update_check = 1
3487 if refresh == "store":
3488 netlogon_update_tdo = netlogon.DS_GFTI_UPDATE_TDO
3489 if enable_all:
3490 lsa_update_check = 0
3491 else:
3492 netlogon_update_tdo = 0
3494 try:
3495 # get all information about the remote trust
3496 # this triggers netr_GetForestTrustInformation to the remote domain
3497 # and lsaRSetForestTrustInformation() locally, but new top level
3498 # names are disabled by default.
3499 fresh_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3500 local_tdo_info.domain_name.string,
3501 netlogon_update_tdo)
3502 except RuntimeError as error:
3503 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3505 try:
3506 fresh_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3507 local_tdo_info.domain_name,
3508 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3509 fresh_forest_info,
3510 lsa_update_check)
3511 except RuntimeError as error:
3512 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3514 self.outf.write("Fresh forest trust information...\n")
3515 self.write_forest_trust_info(fresh_forest_info,
3516 tln=local_tdo_info.domain_name.string,
3517 collisions=fresh_forest_collision)
3519 if refresh == "store":
3520 try:
3521 lsaString = lsa.String()
3522 lsaString.string = local_tdo_info.domain_name.string
3523 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3524 lsaString,
3525 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3526 except RuntimeError as error:
3527 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3529 self.outf.write("Stored forest trust information...\n")
3530 self.write_forest_trust_info(stored_forest_info,
3531 tln=local_tdo_info.domain_name.string)
3533 return
3536 # The none --refresh path
3539 try:
3540 lsaString = lsa.String()
3541 lsaString.string = local_tdo_info.domain_name.string
3542 local_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3543 lsaString,
3544 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3545 except RuntimeError as error:
3546 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3548 self.outf.write("Local forest trust information...\n")
3549 self.write_forest_trust_info(local_forest_info,
3550 tln=local_tdo_info.domain_name.string)
3552 if not require_update:
3553 return
3555 entries = []
3556 entries.extend(local_forest_info.entries)
3557 update_forest_info = lsa.ForestTrustInformation()
3558 update_forest_info.count = len(entries)
3559 update_forest_info.entries = entries
3561 if enable_all:
3562 for i in xrange(0, len(update_forest_info.entries)):
3563 r = update_forest_info.entries[i]
3564 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3565 continue
3566 if update_forest_info.entries[i].flags == 0:
3567 continue
3568 update_forest_info.entries[i].time = 0
3569 update_forest_info.entries[i].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3570 for i in xrange(0, len(update_forest_info.entries)):
3571 r = update_forest_info.entries[i]
3572 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3573 continue
3574 if update_forest_info.entries[i].flags == 0:
3575 continue
3576 update_forest_info.entries[i].time = 0
3577 update_forest_info.entries[i].flags &= ~lsa.LSA_NB_DISABLED_MASK
3578 update_forest_info.entries[i].flags &= ~lsa.LSA_SID_DISABLED_MASK
3580 for tln in enable_tln:
3581 idx = None
3582 for i in xrange(0, len(update_forest_info.entries)):
3583 r = update_forest_info.entries[i]
3584 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3585 continue
3586 if r.forest_trust_data.string.lower() != tln.lower():
3587 continue
3588 idx = i
3589 break
3590 if idx is None:
3591 raise CommandError("Entry not found for value[%s] specified for --enable-tln" % tln)
3592 if not update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_MASK:
3593 raise CommandError("Entry found for value[%s] specified for --enable-tln is already enabled" % tln)
3594 update_forest_info.entries[idx].time = 0
3595 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3597 for tln in disable_tln:
3598 idx = None
3599 for i in xrange(0, len(update_forest_info.entries)):
3600 r = update_forest_info.entries[i]
3601 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3602 continue
3603 if r.forest_trust_data.string.lower() != tln.lower():
3604 continue
3605 idx = i
3606 break
3607 if idx is None:
3608 raise CommandError("Entry not found for value[%s] specified for --disable-tln" % tln)
3609 if update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_ADMIN:
3610 raise CommandError("Entry found for value[%s] specified for --disable-tln is already disabled" % tln)
3611 update_forest_info.entries[idx].time = 0
3612 update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3613 update_forest_info.entries[idx].flags |= lsa.LSA_TLN_DISABLED_ADMIN
3615 for tln_ex in add_tln_ex:
3616 idx = None
3617 for i in xrange(0, len(update_forest_info.entries)):
3618 r = update_forest_info.entries[i]
3619 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3620 continue
3621 if r.forest_trust_data.string.lower() != tln_ex.lower():
3622 continue
3623 idx = i
3624 break
3625 if idx is not None:
3626 raise CommandError("Entry already present for value[%s] specified for --add-tln-ex" % tln_ex)
3628 tln_dot = ".%s" % tln_ex.lower()
3629 idx = None
3630 for i in xrange(0, len(update_forest_info.entries)):
3631 r = update_forest_info.entries[i]
3632 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3633 continue
3634 r_dot = ".%s" % r.forest_trust_data.string.lower()
3635 if tln_dot == r_dot:
3636 raise CommandError("TLN entry present for value[%s] specified for --add-tln-ex" % tln_ex)
3637 if not tln_dot.endswith(r_dot):
3638 continue
3639 idx = i
3640 break
3642 if idx is None:
3643 raise CommandError("No TLN parent present for value[%s] specified for --add-tln-ex" % tln_ex)
3645 r = lsa.ForestTrustRecord()
3646 r.type = lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX
3647 r.flags = 0
3648 r.time = 0
3649 r.forest_trust_data.string = tln_ex
3651 entries = []
3652 entries.extend(update_forest_info.entries)
3653 entries.insert(idx + 1, r)
3654 update_forest_info.count = len(entries)
3655 update_forest_info.entries = entries
3657 for tln_ex in delete_tln_ex:
3658 idx = None
3659 for i in xrange(0, len(update_forest_info.entries)):
3660 r = update_forest_info.entries[i]
3661 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3662 continue
3663 if r.forest_trust_data.string.lower() != tln_ex.lower():
3664 continue
3665 idx = i
3666 break
3667 if idx is None:
3668 raise CommandError("Entry not found for value[%s] specified for --delete-tln-ex" % tln_ex)
3670 entries = []
3671 entries.extend(update_forest_info.entries)
3672 entries.pop(idx)
3673 update_forest_info.count = len(entries)
3674 update_forest_info.entries = entries
3676 for nb in enable_nb:
3677 idx = None
3678 for i in xrange(0, len(update_forest_info.entries)):
3679 r = update_forest_info.entries[i]
3680 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3681 continue
3682 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3683 continue
3684 idx = i
3685 break
3686 if idx is None:
3687 raise CommandError("Entry not found for value[%s] specified for --enable-nb" % nb)
3688 if not update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_MASK:
3689 raise CommandError("Entry found for value[%s] specified for --enable-nb is already enabled" % nb)
3690 update_forest_info.entries[idx].time = 0
3691 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3693 for nb in disable_nb:
3694 idx = None
3695 for i in xrange(0, len(update_forest_info.entries)):
3696 r = update_forest_info.entries[i]
3697 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3698 continue
3699 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3700 continue
3701 idx = i
3702 break
3703 if idx is None:
3704 raise CommandError("Entry not found for value[%s] specified for --delete-nb" % nb)
3705 if update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_ADMIN:
3706 raise CommandError("Entry found for value[%s] specified for --disable-nb is already disabled" % nb)
3707 update_forest_info.entries[idx].time = 0
3708 update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3709 update_forest_info.entries[idx].flags |= lsa.LSA_NB_DISABLED_ADMIN
3711 for sid in enable_sid:
3712 idx = None
3713 for i in xrange(0, len(update_forest_info.entries)):
3714 r = update_forest_info.entries[i]
3715 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3716 continue
3717 if r.forest_trust_data.domain_sid != sid:
3718 continue
3719 idx = i
3720 break
3721 if idx is None:
3722 raise CommandError("Entry not found for value[%s] specified for --enable-sid" % sid)
3723 if not update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_MASK:
3724 raise CommandError("Entry found for value[%s] specified for --enable-sid is already enabled" % nb)
3725 update_forest_info.entries[idx].time = 0
3726 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3728 for sid in disable_sid:
3729 idx = None
3730 for i in xrange(0, len(update_forest_info.entries)):
3731 r = update_forest_info.entries[i]
3732 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3733 continue
3734 if r.forest_trust_data.domain_sid != sid:
3735 continue
3736 idx = i
3737 break
3738 if idx is None:
3739 raise CommandError("Entry not found for value[%s] specified for --delete-sid" % sid)
3740 if update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_ADMIN:
3741 raise CommandError("Entry found for value[%s] specified for --disable-sid is already disabled" % nb)
3742 update_forest_info.entries[idx].time = 0
3743 update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3744 update_forest_info.entries[idx].flags |= lsa.LSA_SID_DISABLED_ADMIN
3746 try:
3747 update_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3748 local_tdo_info.domain_name,
3749 lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3750 update_forest_info, 0)
3751 except RuntimeError as error:
3752 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3754 self.outf.write("Updated forest trust information...\n")
3755 self.write_forest_trust_info(update_forest_info,
3756 tln=local_tdo_info.domain_name.string,
3757 collisions=update_forest_collision)
3759 try:
3760 lsaString = lsa.String()
3761 lsaString.string = local_tdo_info.domain_name.string
3762 stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3763 lsaString,
3764 lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3765 except RuntimeError as error:
3766 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3768 self.outf.write("Stored forest trust information...\n")
3769 self.write_forest_trust_info(stored_forest_info,
3770 tln=local_tdo_info.domain_name.string)
3771 return
3773 class cmd_domain_tombstones_expunge(Command):
3774 """Expunge tombstones from the database.
3776 This command expunges tombstones from the database."""
3777 synopsis = "%prog NC [NC [...]] [options]"
3779 takes_options = [
3780 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3781 metavar="URL", dest="H"),
3782 Option("--current-time",
3783 help="The current time to evaluate the tombstone lifetime from, expressed as YYYY-MM-DD",
3784 type=str),
3785 Option("--tombstone-lifetime", help="Number of days a tombstone should be preserved for", type=int),
3788 takes_args = ["nc*"]
3790 takes_optiongroups = {
3791 "sambaopts": options.SambaOptions,
3792 "credopts": options.CredentialsOptions,
3793 "versionopts": options.VersionOptions,
3796 def run(self, *ncs, **kwargs):
3797 sambaopts = kwargs.get("sambaopts")
3798 credopts = kwargs.get("credopts")
3799 versionpts = kwargs.get("versionopts")
3800 H = kwargs.get("H")
3801 current_time_string = kwargs.get("current_time")
3802 tombstone_lifetime = kwargs.get("tombstone_lifetime")
3803 lp = sambaopts.get_loadparm()
3804 creds = credopts.get_credentials(lp)
3805 samdb = SamDB(url=H, session_info=system_session(),
3806 credentials=creds, lp=lp)
3808 if current_time_string is not None:
3809 current_time_obj = time.strptime(current_time_string, "%Y-%m-%d")
3810 current_time = long(time.mktime(current_time_obj))
3812 else:
3813 current_time = long(time.time())
3815 if len(ncs) == 0:
3816 res = samdb.search(expression="", base="", scope=ldb.SCOPE_BASE,
3817 attrs=["namingContexts"])
3819 ncs = []
3820 for nc in res[0]["namingContexts"]:
3821 ncs.append(str(nc))
3822 else:
3823 ncs = list(ncs)
3825 started_transaction = False
3826 try:
3827 samdb.transaction_start()
3828 started_transaction = True
3829 (removed_objects,
3830 removed_links) = samdb.garbage_collect_tombstones(ncs,
3831 current_time=current_time,
3832 tombstone_lifetime=tombstone_lifetime)
3834 except Exception, err:
3835 if started_transaction:
3836 samdb.transaction_cancel()
3837 raise CommandError("Failed to expunge / garbage collect tombstones", err)
3839 samdb.transaction_commit()
3841 self.outf.write("Removed %d objects and %d links successfully\n"
3842 % (removed_objects, removed_links))
3846 class cmd_domain_trust(SuperCommand):
3847 """Domain and forest trust management."""
3849 subcommands = {}
3850 subcommands["list"] = cmd_domain_trust_list()
3851 subcommands["show"] = cmd_domain_trust_show()
3852 subcommands["create"] = cmd_domain_trust_create()
3853 subcommands["delete"] = cmd_domain_trust_delete()
3854 subcommands["validate"] = cmd_domain_trust_validate()
3855 subcommands["namespaces"] = cmd_domain_trust_namespaces()
3857 class cmd_domain_tombstones(SuperCommand):
3858 """Domain tombstone and recycled object management."""
3860 subcommands = {}
3861 subcommands["expunge"] = cmd_domain_tombstones_expunge()
3863 class ldif_schema_update:
3864 """Helper class for applying LDIF schema updates"""
3866 def __init__(self):
3867 self.is_defunct = False
3868 self.unknown_oid = None
3869 self.dn = None
3870 self.ldif = ""
3872 def _ldap_schemaUpdateNow(self, samdb):
3873 ldif = """
3875 changetype: modify
3876 add: schemaUpdateNow
3877 schemaUpdateNow: 1
3879 samdb.modify_ldif(ldif)
3881 def can_ignore_failure(self, error):
3882 """Checks if we can safely ignore failure to apply an LDIF update"""
3883 (num, errstr) = error.args
3885 # Microsoft has marked objects as defunct that Samba doesn't know about
3886 if num == ldb.ERR_NO_SUCH_OBJECT and self.is_defunct:
3887 print("Defunct object %s doesn't exist, skipping" % self.dn)
3888 return True
3889 elif self.unknown_oid is not None:
3890 print("Skipping unknown OID %s for object %s" %(self.unknown_oid, self.dn))
3891 return True
3893 return False
3895 def apply(self, samdb):
3896 """Applies a single LDIF update to the schema"""
3898 try:
3899 samdb.modify_ldif(self.ldif, controls=['relax:0'])
3900 except ldb.LdbError as e:
3901 if self.can_ignore_failure(e):
3902 return 0
3903 else:
3904 print("Exception: %s" % e)
3905 print("Encountered while trying to apply the following LDIF")
3906 print("----------------------------------------------------")
3907 print("%s" % self.ldif)
3909 raise
3911 # REFRESH AFTER EVERY CHANGE
3912 # Otherwise the OID-to-attribute mapping in _apply_updates_in_file()
3913 # won't work, because it can't lookup the new OID in the schema
3914 self._ldap_schemaUpdateNow(samdb)
3916 return 1
3918 class cmd_domain_schema_upgrade(Command):
3919 """Domain schema upgrading"""
3921 synopsis = "%prog [options]"
3923 takes_optiongroups = {
3924 "sambaopts": options.SambaOptions,
3925 "versionopts": options.VersionOptions,
3926 "credopts": options.CredentialsOptions,
3929 takes_options = [
3930 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3931 metavar="URL", dest="H"),
3932 Option("--quiet", help="Be quiet", action="store_true"),
3933 Option("--verbose", help="Be verbose", action="store_true"),
3934 Option("--schema", type="choice", metavar="SCHEMA",
3935 choices=["2012", "2012_R2"],
3936 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
3937 default="2012_R2"),
3938 Option("--ldf-file", type=str, default=None,
3939 help="Just apply the schema updates in the adprep/.LDF file(s) specified"),
3940 Option("--base-dir", type=str, default=None,
3941 help="Location of ldf files Default is ${SETUPDIR}/adprep.")
3944 def _apply_updates_in_file(self, samdb, ldif_file):
3946 Applies a series of updates specified in an .LDIF file. The .LDIF file
3947 is based on the adprep Schema updates provided by Microsoft.
3949 count = 0
3950 ldif_op = ldif_schema_update()
3952 # parse the file line by line and work out each update operation to apply
3953 for line in ldif_file:
3955 line = line.rstrip()
3957 # the operations in the .LDIF file are separated by blank lines. If
3958 # we hit a blank line, try to apply the update we've parsed so far
3959 if line == '':
3961 # keep going if we haven't parsed anything yet
3962 if ldif_op.ldif == '':
3963 continue
3965 # Apply the individual change
3966 count += ldif_op.apply(samdb)
3968 # start storing the next operation from scratch again
3969 ldif_op = ldif_schema_update()
3970 continue
3972 # replace the placeholder domain name in the .ldif file with the real domain
3973 if line.upper().endswith('DC=X'):
3974 line = line[:-len('DC=X')] + str(samdb.get_default_basedn())
3975 elif line.upper().endswith('CN=X'):
3976 line = line[:-len('CN=X')] + str(samdb.get_default_basedn())
3978 values = line.split(':')
3980 if values[0].lower() == 'dn':
3981 ldif_op.dn = values[1].strip()
3983 # replace the Windows-specific operation with the Samba one
3984 if values[0].lower() == 'changetype':
3985 line = line.lower().replace(': ntdsschemaadd',
3986 ': add')
3987 line = line.lower().replace(': ntdsschemamodify',
3988 ': modify')
3990 if values[0].lower() in ['rdnattid', 'subclassof',
3991 'systemposssuperiors',
3992 'systemmaycontain',
3993 'systemauxiliaryclass']:
3994 _, value = values
3996 # The Microsoft updates contain some OIDs we don't recognize.
3997 # Query the DB to see if we can work out the OID this update is
3998 # referring to. If we find a match, then replace the OID with
3999 # the ldapDisplayname
4000 if '.' in value:
4001 res = samdb.search(base=samdb.get_schema_basedn(),
4002 expression="(|(attributeId=%s)(governsId=%s))" %
4003 (value, value),
4004 attrs=['ldapDisplayName'])
4006 if len(res) != 1:
4007 ldif_op.unknown_oid = value
4008 else:
4009 display_name = res[0]['ldapDisplayName'][0]
4010 line = line.replace(value, ' ' + display_name)
4012 # Microsoft has marked objects as defunct that Samba doesn't know about
4013 if values[0].lower() == 'isdefunct' and values[1].strip().lower() == 'true':
4014 ldif_op.is_defunct = True
4016 # Samba has added the showInAdvancedViewOnly attribute to all objects,
4017 # so rather than doing an add, we need to do a replace
4018 if values[0].lower() == 'add' and values[1].strip().lower() == 'showinadvancedviewonly':
4019 line = 'replace: showInAdvancedViewOnly'
4021 # Add the line to the current LDIF operation (including the newline
4022 # we stripped off at the start of the loop)
4023 ldif_op.ldif += line + '\n'
4025 return count
4028 def _apply_update(self, samdb, update_file, base_dir):
4029 """Wrapper function for parsing an LDIF file and applying the updates"""
4031 print("Applying %s updates..." % update_file)
4033 ldif_file = None
4034 try:
4035 ldif_file = open(os.path.join(base_dir, update_file))
4037 count = self._apply_updates_in_file(samdb, ldif_file)
4039 finally:
4040 if ldif_file:
4041 ldif_file.close()
4043 print("%u changes applied" % count)
4045 return count
4047 def run(self, **kwargs):
4048 from samba.ms_schema_markdown import read_ms_markdown
4049 from samba.schema import Schema
4051 updates_allowed_overriden = False
4052 sambaopts = kwargs.get("sambaopts")
4053 credopts = kwargs.get("credopts")
4054 versionpts = kwargs.get("versionopts")
4055 lp = sambaopts.get_loadparm()
4056 creds = credopts.get_credentials(lp)
4057 H = kwargs.get("H")
4058 target_schema = kwargs.get("schema")
4059 ldf_files = kwargs.get("ldf_file")
4060 base_dir = kwargs.get("base_dir")
4062 temp_folder = None
4064 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4066 # we're not going to get far if the config doesn't allow schema updates
4067 if lp.get("dsdb:schema update allowed") is None:
4068 lp.set("dsdb:schema update allowed", "yes")
4069 print("Temporarily overriding 'dsdb:schema update allowed' setting")
4070 updates_allowed_overriden = True
4072 # if specific LDIF files were specified, just apply them
4073 if ldf_files:
4074 schema_updates = ldf_files.split(",")
4075 else:
4076 schema_updates = []
4078 # work out the version of the target schema we're upgrading to
4079 end = Schema.get_version(target_schema)
4081 # work out the version of the schema we're currently using
4082 res = samdb.search(base=samdb.get_schema_basedn(),
4083 scope=ldb.SCOPE_BASE, attrs=['objectVersion'])
4085 if len(res) != 1:
4086 raise CommandError('Could not determine current schema version')
4087 start = int(res[0]['objectVersion'][0]) + 1
4089 diff_dir = setup_path("adprep/WindowsServerDocs")
4090 if base_dir is None:
4091 # Read from the Schema-Updates.md file
4092 temp_folder = tempfile.mkdtemp()
4094 update_file = setup_path("adprep/WindowsServerDocs/Schema-Updates.md")
4096 try:
4097 read_ms_markdown(update_file, temp_folder)
4098 except Exception as e:
4099 print("Exception in markdown parsing: %s" % e)
4100 shutil.rmtree(temp_folder)
4101 raise CommandError('Failed to upgrade schema')
4103 base_dir = temp_folder
4105 for version in range(start, end + 1):
4106 update = 'Sch%d.ldf' % version
4107 schema_updates.append(update)
4109 # Apply patches if we parsed the Schema-Updates.md file
4110 diff = os.path.abspath(os.path.join(diff_dir, update + '.diff'))
4111 if temp_folder and os.path.exists(diff):
4112 p = subprocess.Popen(['patch', update, '-i', diff],
4113 stdout=subprocess.PIPE,
4114 stderr=subprocess.PIPE, cwd=temp_folder)
4115 stdout, stderr = p.communicate()
4117 if p.returncode:
4118 print("Exception in patch: %s\n%s" % (stdout, stderr))
4119 shutil.rmtree(temp_folder)
4120 raise CommandError('Failed to upgrade schema')
4122 print("Patched %s using %s" % (update, diff))
4124 if base_dir is None:
4125 base_dir = setup_path("adprep")
4127 samdb.transaction_start()
4128 count = 0
4129 error_encountered = False
4131 try:
4132 # Apply the schema updates needed to move to the new schema version
4133 for ldif_file in schema_updates:
4134 count += self._apply_update(samdb, ldif_file, base_dir)
4136 if count > 0:
4137 samdb.transaction_commit()
4138 print("Schema successfully updated")
4139 else:
4140 print("No changes applied to schema")
4141 samdb.transaction_cancel()
4142 except Exception as e:
4143 print("Exception: %s" % e)
4144 print("Error encountered, aborting schema upgrade")
4145 samdb.transaction_cancel()
4146 error_encountered = True
4148 if updates_allowed_overriden:
4149 lp.set("dsdb:schema update allowed", "no")
4151 if temp_folder:
4152 shutil.rmtree(temp_folder)
4154 if error_encountered:
4155 raise CommandError('Failed to upgrade schema')
4157 class cmd_domain(SuperCommand):
4158 """Domain management."""
4160 subcommands = {}
4161 subcommands["demote"] = cmd_domain_demote()
4162 if cmd_domain_export_keytab is not None:
4163 subcommands["exportkeytab"] = cmd_domain_export_keytab()
4164 subcommands["info"] = cmd_domain_info()
4165 subcommands["provision"] = cmd_domain_provision()
4166 subcommands["join"] = cmd_domain_join()
4167 subcommands["dcpromo"] = cmd_domain_dcpromo()
4168 subcommands["level"] = cmd_domain_level()
4169 subcommands["passwordsettings"] = cmd_domain_passwordsettings()
4170 subcommands["classicupgrade"] = cmd_domain_classicupgrade()
4171 subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()
4172 subcommands["trust"] = cmd_domain_trust()
4173 subcommands["tombstones"] = cmd_domain_tombstones()
4174 subcommands["schemaupgrade"] = cmd_domain_schema_upgrade()