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