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