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