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