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