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