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