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