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