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