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