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