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