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