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