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