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