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