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