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