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