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