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