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