s4:samba-tool domain raise tool - make it aware of newer domain function levels
[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
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 demoting, "
793                         "re-enabling inbound replication\n")
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 partion %s" % str(part), e)
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, 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         def string_to_array(string):
2234             blob = [0] * len(string)
2235
2236             for i in range(len(string)):
2237                 blob[i] = ord(string[i])
2238
2239             return blob
2240
2241         incoming_secret = None
2242         outgoing_secret = None
2243         remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2244         if create_location == "local":
2245             if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2246                 incoming_password = get_password("Incoming Trust")
2247                 incoming_secret = string_to_array(incoming_password.encode('utf-16-le'))
2248             if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2249                 outgoing_password = get_password("Outgoing Trust")
2250                 outgoing_secret = string_to_array(outgoing_password.encode('utf-16-le'))
2251
2252             remote_trust_info = None
2253         else:
2254             # We use 240 random bytes.
2255             # Windows uses 28 or 240 random bytes. I guess it's
2256             # based on the trust type external vs. forest.
2257             #
2258             # The initial trust password can be up to 512 bytes
2259             # while the versioned passwords used for periodic updates
2260             # can only be up to 498 bytes, as netr_ServerPasswordSet2()
2261             # needs to pass the NL_PASSWORD_VERSION structure within the
2262             # 512 bytes and a 2 bytes confounder is required.
2263             #
2264             def random_trust_secret(length, use_aes_keys=True):
2265                 secret = [0] * length
2266
2267                 pw1 = samba.generate_random_password(length/2, length/2)
2268                 if not use_aes_keys:
2269                     # With arcfour-hmac-md5 we have to use valid utf16
2270                     # in order to generate the correct pre-auth key
2271                     # based on a utf8 password.
2272                     #
2273                     # We can remove this once our client libraries
2274                     # support using the correct NTHASH.
2275                     return string_to_array(pw1.encode('utf-16-le'))
2276
2277                 # We mix characters from generate_random_password
2278                 # with random numbers from random.randint()
2279                 for i in range(len(secret)):
2280                     if len(pw1) > i:
2281                         secret[i] = ord(pw1[i])
2282                     else:
2283                         secret[i] = random.randint(0, 255)
2284
2285                 return secret
2286
2287             if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2288                 incoming_secret = random_trust_secret(240, use_aes_keys=use_aes_keys)
2289             if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2290                 outgoing_secret = random_trust_secret(240, use_aes_keys=use_aes_keys)
2291
2292             remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2293             remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2294
2295             remote_trust_info = lsa.TrustDomainInfoInfoEx()
2296             remote_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2297             remote_trust_info.trust_direction = 0
2298             if trust_direction == "both":
2299                 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2300                 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2301             elif trust_direction == "incoming":
2302                 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2303             elif trust_direction == "outgoing":
2304                 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2305             remote_trust_info.trust_attributes = 0
2306             if cross_organisation:
2307                 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2308             if quarantined:
2309                 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2310             if trust_type == "forest":
2311                 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2312             if not_transitive:
2313                 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2314             if treat_as_external:
2315                 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2316
2317         local_server = self.setup_local_server(sambaopts, localdcopts)
2318         try:
2319             local_lsa = self.new_local_lsa_connection()
2320         except RuntimeError as error:
2321             raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2322
2323         try:
2324             (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2325         except RuntimeError as error:
2326             raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2327
2328         self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2329                         local_lsa_info.name.string,
2330                         local_lsa_info.dns_domain.string,
2331                         local_lsa_info.sid))
2332
2333         try:
2334             remote_server = self.setup_remote_server(credopts, domain)
2335         except RuntimeError as error:
2336             raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2337
2338         try:
2339             remote_lsa = self.new_remote_lsa_connection()
2340         except RuntimeError as error:
2341             raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2342
2343         try:
2344             (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2345         except RuntimeError as error:
2346             raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2347
2348         self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2349                         remote_lsa_info.name.string,
2350                         remote_lsa_info.dns_domain.string,
2351                         remote_lsa_info.sid))
2352
2353         local_trust_info.domain_name.string = remote_lsa_info.dns_domain.string
2354         local_trust_info.netbios_name.string = remote_lsa_info.name.string
2355         local_trust_info.sid = remote_lsa_info.sid
2356
2357         if remote_trust_info:
2358             remote_trust_info.domain_name.string = local_lsa_info.dns_domain.string
2359             remote_trust_info.netbios_name.string = local_lsa_info.name.string
2360             remote_trust_info.sid = local_lsa_info.sid
2361
2362         try:
2363             lsaString.string = local_trust_info.domain_name.string
2364             local_old_netbios = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2365                                         lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2366             raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2367         except RuntimeError as error:
2368             if not self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2369                 raise self.LocalRuntimeError(self, error,
2370                                 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2371                                 lsaString.string))
2372
2373         try:
2374             lsaString.string = local_trust_info.netbios_name.string
2375             local_old_dns = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2376                                         lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2377             raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2378         except RuntimeError as error:
2379             if not self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2380                 raise self.LocalRuntimeError(self, error,
2381                                 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2382                                 lsaString.string))
2383
2384         if remote_trust_info:
2385             try:
2386                 lsaString.string = remote_trust_info.domain_name.string
2387                 remote_old_netbios = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2388                                             lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2389                 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2390             except RuntimeError as error:
2391                 if not self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2392                     raise self.RemoteRuntimeError(self, error,
2393                                     "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2394                                     lsaString.string))
2395
2396             try:
2397                 lsaString.string = remote_trust_info.netbios_name.string
2398                 remote_old_dns = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2399                                             lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2400                 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2401             except RuntimeError as error:
2402                 if not self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2403                     raise self.RemoteRuntimeError(self, error,
2404                                     "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2405                                     lsaString.string))
2406
2407         try:
2408             local_netlogon = self.new_local_netlogon_connection()
2409         except RuntimeError as error:
2410             raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2411
2412         try:
2413             local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
2414         except RuntimeError as error:
2415             raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
2416
2417         if remote_trust_info:
2418             try:
2419                 remote_netlogon = self.new_remote_netlogon_connection()
2420             except RuntimeError as error:
2421                 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2422
2423             try:
2424                 remote_netlogon_info = self.get_netlogon_dc_info(remote_netlogon, remote_server)
2425             except RuntimeError as error:
2426                 raise self.RemoteRuntimeError(self, error, "failed to get netlogon dc info")
2427
2428         def arcfour_encrypt(key, data):
2429             from Crypto.Cipher import ARC4
2430             c = ARC4.new(key)
2431             return c.encrypt(data)
2432
2433         def generate_AuthInOutBlob(secret, update_time):
2434             if secret is None:
2435                 blob = drsblobs.trustAuthInOutBlob()
2436                 blob.count = 0
2437
2438                 return blob
2439
2440             clear = drsblobs.AuthInfoClear()
2441             clear.size = len(secret)
2442             clear.password = secret
2443
2444             info = drsblobs.AuthenticationInformation()
2445             info.LastUpdateTime = samba.unix2nttime(update_time)
2446             info.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
2447             info.AuthInfo = clear
2448
2449             array = drsblobs.AuthenticationInformationArray()
2450             array.count = 1
2451             array.array = [info]
2452
2453             blob = drsblobs.trustAuthInOutBlob()
2454             blob.count = 1
2455             blob.current = array
2456
2457             return blob
2458
2459         def generate_AuthInfoInternal(session_key, incoming=None, outgoing=None):
2460             confounder = [0] * 512
2461             for i in range(len(confounder)):
2462                 confounder[i] = random.randint(0, 255)
2463
2464             trustpass = drsblobs.trustDomainPasswords()
2465
2466             trustpass.confounder = confounder
2467             trustpass.outgoing = outgoing
2468             trustpass.incoming = incoming
2469
2470             trustpass_blob = ndr_pack(trustpass)
2471
2472             encrypted_trustpass = arcfour_encrypt(session_key, trustpass_blob)
2473
2474             auth_blob = lsa.DATA_BUF2()
2475             auth_blob.size = len(encrypted_trustpass)
2476             auth_blob.data = string_to_array(encrypted_trustpass)
2477
2478             auth_info = lsa.TrustDomainInfoAuthInfoInternal()
2479             auth_info.auth_blob = auth_blob
2480
2481             return auth_info
2482
2483         update_time = samba.current_unix_time()
2484         incoming_blob = generate_AuthInOutBlob(incoming_secret, update_time)
2485         outgoing_blob = generate_AuthInOutBlob(outgoing_secret, update_time)
2486
2487         local_tdo_handle = None
2488         remote_tdo_handle = None
2489
2490         local_auth_info = generate_AuthInfoInternal(local_lsa.session_key,
2491                                                     incoming=incoming_blob,
2492                                                     outgoing=outgoing_blob)
2493         if remote_trust_info:
2494             remote_auth_info = generate_AuthInfoInternal(remote_lsa.session_key,
2495                                                          incoming=outgoing_blob,
2496                                                          outgoing=incoming_blob)
2497
2498         try:
2499             if remote_trust_info:
2500                 self.outf.write("Creating remote TDO.\n")
2501                 current_request = { "location": "remote", "name": "CreateTrustedDomainEx2"}
2502                 remote_tdo_handle = remote_lsa.CreateTrustedDomainEx2(remote_policy,
2503                                                                       remote_trust_info,
2504                                                                       remote_auth_info,
2505                                                                       lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2506                 self.outf.write("Remote TDO created.\n")
2507                 if enc_types:
2508                     self.outf.write("Setting supported encryption types on remote TDO.\n")
2509                     current_request = { "location": "remote", "name": "SetInformationTrustedDomain"}
2510                     remote_lsa.SetInformationTrustedDomain(remote_tdo_handle,
2511                                                            lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2512                                                            enc_types)
2513
2514             self.outf.write("Creating local TDO.\n")
2515             current_request = { "location": "local", "name": "CreateTrustedDomainEx2"}
2516             local_tdo_handle = local_lsa.CreateTrustedDomainEx2(local_policy,
2517                                                                   local_trust_info,
2518                                                                   local_auth_info,
2519                                                                   lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2520             self.outf.write("Local TDO created\n")
2521             if enc_types:
2522                 self.outf.write("Setting supported encryption types on local TDO.\n")
2523                 current_request = { "location": "local", "name": "SetInformationTrustedDomain"}
2524                 local_lsa.SetInformationTrustedDomain(local_tdo_handle,
2525                                                       lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2526                                                       enc_types)
2527         except RuntimeError as error:
2528             self.outf.write("Error: %s failed %sly - cleaning up\n" % (
2529                             current_request['name'], current_request['location']))
2530             if remote_tdo_handle:
2531                 self.outf.write("Deleting remote TDO.\n")
2532                 remote_lsa.DeleteObject(remote_tdo_handle)
2533                 remote_tdo_handle = None
2534             if local_tdo_handle:
2535                 self.outf.write("Deleting local TDO.\n")
2536                 local_lsa.DeleteObject(local_tdo_handle)
2537                 local_tdo_handle = None
2538             if current_request['location'] is "remote":
2539                 raise self.RemoteRuntimeError(self, error, "%s" % (
2540                                               current_request['name']))
2541             raise self.LocalRuntimeError(self, error, "%s" % (
2542                                          current_request['name']))
2543
2544         if validate:
2545             if local_trust_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2546                 self.outf.write("Setup local forest trust information...\n")
2547                 try:
2548                     # get all information about the remote trust
2549                     # this triggers netr_GetForestTrustInformation to the remote domain
2550                     # and lsaRSetForestTrustInformation() locally, but new top level
2551                     # names are disabled by default.
2552                     local_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
2553                                                                   remote_lsa_info.dns_domain.string,
2554                                                                   netlogon.DS_GFTI_UPDATE_TDO)
2555                 except RuntimeError as error:
2556                     raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2557
2558                 try:
2559                     # here we try to enable all top level names
2560                     local_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
2561                                                                   remote_lsa_info.dns_domain,
2562                                                                   lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2563                                                                   local_forest_info,
2564                                                                   0)
2565                 except RuntimeError as error:
2566                     raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2567
2568                 self.write_forest_trust_info(local_forest_info,
2569                                              tln=remote_lsa_info.dns_domain.string,
2570                                              collisions=local_forest_collision)
2571
2572                 if remote_trust_info:
2573                     self.outf.write("Setup remote forest trust information...\n")
2574                     try:
2575                         # get all information about the local trust (from the perspective of the remote domain)
2576                         # this triggers netr_GetForestTrustInformation to our domain.
2577                         # and lsaRSetForestTrustInformation() remotely, but new top level
2578                         # names are disabled by default.
2579                         remote_forest_info = remote_netlogon.netr_DsRGetForestTrustInformation(remote_netlogon_info.dc_unc,
2580                                                                       local_lsa_info.dns_domain.string,
2581                                                                       netlogon.DS_GFTI_UPDATE_TDO)
2582                     except RuntimeError as error:
2583                         raise self.RemoteRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2584
2585                     try:
2586                         # here we try to enable all top level names
2587                         remote_forest_collision = remote_lsa.lsaRSetForestTrustInformation(remote_policy,
2588                                                                       local_lsa_info.dns_domain,
2589                                                                       lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2590                                                                       remote_forest_info,
2591                                                                       0)
2592                     except RuntimeError as error:
2593                         raise self.RemoteRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2594
2595                     self.write_forest_trust_info(remote_forest_info,
2596                                                  tln=local_lsa_info.dns_domain.string,
2597                                                  collisions=remote_forest_collision)
2598
2599             if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2600                 self.outf.write("Validating outgoing trust...\n")
2601                 try:
2602                     local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_netlogon_info.dc_unc,
2603                                                                       netlogon.NETLOGON_CONTROL_TC_VERIFY,
2604                                                                       2,
2605                                                                       remote_lsa_info.dns_domain.string)
2606                 except RuntimeError as error:
2607                     raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2608
2609                 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2610                 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2611
2612                 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2613                     local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2614                                        local_trust_verify.trusted_dc_name,
2615                                        local_trust_verify.tc_connection_status[1],
2616                                        local_trust_verify.pdc_connection_status[1])
2617                 else:
2618                     local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2619                                        local_trust_verify.trusted_dc_name,
2620                                        local_trust_verify.tc_connection_status[1],
2621                                        local_trust_verify.pdc_connection_status[1])
2622
2623                 if local_trust_status != self.WERR_OK or local_conn_status != self.WERR_OK:
2624                     raise CommandError(local_validation)
2625                 else:
2626                     self.outf.write("OK: %s\n" % local_validation)
2627
2628             if remote_trust_info:
2629                 if remote_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2630                     self.outf.write("Validating incoming trust...\n")
2631                     try:
2632                         remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_netlogon_info.dc_unc,
2633                                                                       netlogon.NETLOGON_CONTROL_TC_VERIFY,
2634                                                                       2,
2635                                                                       local_lsa_info.dns_domain.string)
2636                     except RuntimeError as error:
2637                         raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2638
2639                     remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2640                     remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2641
2642                     if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2643                         remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2644                                            remote_trust_verify.trusted_dc_name,
2645                                            remote_trust_verify.tc_connection_status[1],
2646                                            remote_trust_verify.pdc_connection_status[1])
2647                     else:
2648                         remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2649                                            remote_trust_verify.trusted_dc_name,
2650                                            remote_trust_verify.tc_connection_status[1],
2651                                            remote_trust_verify.pdc_connection_status[1])
2652
2653                     if remote_trust_status != self.WERR_OK or remote_conn_status != self.WERR_OK:
2654                         raise CommandError(remote_validation)
2655                     else:
2656                         self.outf.write("OK: %s\n" % remote_validation)
2657
2658         if remote_tdo_handle is not None:
2659             try:
2660                 remote_lsa.Close(remote_tdo_handle)
2661             except RuntimeError as error:
2662                 pass
2663             remote_tdo_handle = None
2664         if local_tdo_handle is not None:
2665             try:
2666                 local_lsa.Close(local_tdo_handle)
2667             except RuntimeError as error:
2668                 pass
2669             local_tdo_handle = None
2670
2671         self.outf.write("Success.\n")
2672         return
2673
2674 class cmd_domain_trust_delete(DomainTrustCommand):
2675     """Delete a domain trust."""
2676
2677     synopsis = "%prog DOMAIN [options]"
2678
2679     takes_optiongroups = {
2680         "sambaopts": options.SambaOptions,
2681         "versionopts": options.VersionOptions,
2682         "credopts": options.CredentialsOptions,
2683         "localdcopts": LocalDCCredentialsOptions,
2684     }
2685
2686     takes_options = [
2687         Option("--delete-location", type="choice", metavar="LOCATION",
2688                choices=["local", "both"],
2689                help="Where to delete the trusted domain object: 'local' or 'both'.",
2690                dest='delete_location',
2691                default="both"),
2692        ]
2693
2694     takes_args = ["domain"]
2695
2696     def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2697             delete_location=None):
2698
2699         local_policy_access =  lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2700         local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2701         local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2702
2703         if delete_location == "local":
2704             remote_policy_access = None
2705         else:
2706             remote_policy_access =  lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2707             remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2708             remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2709
2710         local_server = self.setup_local_server(sambaopts, localdcopts)
2711         try:
2712             local_lsa = self.new_local_lsa_connection()
2713         except RuntimeError as error:
2714             raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2715
2716         try:
2717             (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2718         except RuntimeError as error:
2719             raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2720
2721         self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2722                         local_lsa_info.name.string,
2723                         local_lsa_info.dns_domain.string,
2724                         local_lsa_info.sid))
2725
2726         local_tdo_info = None
2727         local_tdo_handle = None
2728         remote_tdo_info = None
2729         remote_tdo_handle = None
2730
2731         lsaString = lsa.String()
2732         try:
2733             lsaString.string = domain
2734             local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2735                                         lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2736         except RuntimeError as error:
2737             if self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2738                 raise CommandError("Failed to find trust for domain '%s'" % domain)
2739             raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2740
2741
2742         if remote_policy_access is not None:
2743             try:
2744                 remote_server = self.setup_remote_server(credopts, domain)
2745             except RuntimeError as error:
2746                 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2747
2748             try:
2749                 remote_lsa = self.new_remote_lsa_connection()
2750             except RuntimeError as error:
2751                 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2752
2753             try:
2754                 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2755             except RuntimeError as error:
2756                 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2757
2758             self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2759                             remote_lsa_info.name.string,
2760                             remote_lsa_info.dns_domain.string,
2761                             remote_lsa_info.sid))
2762
2763             if remote_lsa_info.sid != local_tdo_info.sid or \
2764                remote_lsa_info.name.string != local_tdo_info.netbios_name.string or \
2765                remote_lsa_info.dns_domain.string != local_tdo_info.domain_name.string:
2766                 raise CommandError("LocalTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2767                                    local_tdo_info.netbios_name.string,
2768                                    local_tdo_info.domain_name.string,
2769                                    local_tdo_info.sid))
2770
2771             try:
2772                 lsaString.string = local_lsa_info.dns_domain.string
2773                 remote_tdo_info = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2774                                             lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2775             except RuntimeError as error:
2776                 if not self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2777                     raise self.RemoteRuntimeError(self, error, "QueryTrustedDomainInfoByName(%s)" % (
2778                                                   lsaString.string))
2779                 pass
2780
2781             if remote_tdo_info is not None:
2782                 if local_lsa_info.sid != remote_tdo_info.sid or \
2783                    local_lsa_info.name.string != remote_tdo_info.netbios_name.string or \
2784                    local_lsa_info.dns_domain.string != remote_tdo_info.domain_name.string:
2785                     raise CommandError("RemoteTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2786                                        remote_tdo_info.netbios_name.string,
2787                                        remote_tdo_info.domain_name.string,
2788                                        remote_tdo_info.sid))
2789
2790         if local_tdo_info is not None:
2791             try:
2792                 lsaString.string = local_tdo_info.domain_name.string
2793                 local_tdo_handle = local_lsa.OpenTrustedDomainByName(local_policy,
2794                                                                      lsaString,
2795                                                                      security.SEC_STD_DELETE)
2796             except RuntimeError as error:
2797                 raise self.LocalRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2798                                              lsaString.string))
2799
2800             local_lsa.DeleteObject(local_tdo_handle)
2801             local_tdo_handle = None
2802
2803         if remote_tdo_info is not None:
2804             try:
2805                 lsaString.string = remote_tdo_info.domain_name.string
2806                 remote_tdo_handle = remote_lsa.OpenTrustedDomainByName(remote_policy,
2807                                                                        lsaString,
2808                                                                        security.SEC_STD_DELETE)
2809             except RuntimeError as error:
2810                 raise self.RemoteRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2811                                               lsaString.string))
2812
2813         if remote_tdo_handle is not None:
2814             try:
2815                 remote_lsa.DeleteObject(remote_tdo_handle)
2816                 remote_tdo_handle = None
2817                 self.outf.write("RemoteTDO deleted.\n")
2818             except RuntimeError as error:
2819                 self.outf.write("%s\n" % self.RemoteRuntimeError(self, error, "DeleteObject() failed"))
2820
2821         if local_tdo_handle is not None:
2822             try:
2823                 local_lsa.DeleteObject(local_tdo_handle)
2824                 local_tdo_handle = None
2825                 self.outf.write("LocalTDO deleted.\n")
2826             except RuntimeError as error:
2827                 self.outf.write("%s\n" % self.LocalRuntimeError(self, error, "DeleteObject() failed"))
2828
2829         return
2830
2831 class cmd_domain_trust_validate(DomainTrustCommand):
2832     """Validate a domain trust."""
2833
2834     synopsis = "%prog DOMAIN [options]"
2835
2836     takes_optiongroups = {
2837         "sambaopts": options.SambaOptions,
2838         "versionopts": options.VersionOptions,
2839         "credopts": options.CredentialsOptions,
2840         "localdcopts": LocalDCCredentialsOptions,
2841     }
2842
2843     takes_options = [
2844         Option("--validate-location", type="choice", metavar="LOCATION",
2845                choices=["local", "both"],
2846                help="Where to validate the trusted domain object: 'local' or 'both'.",
2847                dest='validate_location',
2848                default="both"),
2849        ]
2850
2851     takes_args = ["domain"]
2852
2853     def run(self, domain, sambaopts=None, versionopts=None, credopts=None, localdcopts=None,
2854             validate_location=None):
2855
2856         local_policy_access =  lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2857
2858         local_server = self.setup_local_server(sambaopts, localdcopts)
2859         try:
2860             local_lsa = self.new_local_lsa_connection()
2861         except RuntimeError as error:
2862             raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2863
2864         try:
2865             (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2866         except RuntimeError as error:
2867             raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2868
2869         self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2870                         local_lsa_info.name.string,
2871                         local_lsa_info.dns_domain.string,
2872                         local_lsa_info.sid))
2873
2874         try:
2875             lsaString = lsa.String()
2876             lsaString.string = domain
2877             local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2878                                         lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2879         except RuntimeError as error:
2880             if self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2881                 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2882
2883             raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
2884
2885         self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
2886                         local_tdo_info.netbios_name.string,
2887                         local_tdo_info.domain_name.string,
2888                         local_tdo_info.sid))
2889
2890         try:
2891             local_netlogon = self.new_local_netlogon_connection()
2892         except RuntimeError as error:
2893             raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2894
2895         try:
2896             local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_server,
2897                                                                  netlogon.NETLOGON_CONTROL_TC_VERIFY,
2898                                                                  2,
2899                                                                  local_tdo_info.domain_name.string)
2900         except RuntimeError as error:
2901             raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2902
2903         local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2904         local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2905
2906         if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2907             local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2908                                local_trust_verify.trusted_dc_name,
2909                                local_trust_verify.tc_connection_status[1],
2910                                local_trust_verify.pdc_connection_status[1])
2911         else:
2912             local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2913                                local_trust_verify.trusted_dc_name,
2914                                local_trust_verify.tc_connection_status[1],
2915                                local_trust_verify.pdc_connection_status[1])
2916
2917         if local_trust_status != self.WERR_OK or local_conn_status != self.WERR_OK:
2918             raise CommandError(local_validation)
2919         else:
2920             self.outf.write("OK: %s\n" % local_validation)
2921
2922         try:
2923             server = local_trust_verify.trusted_dc_name.replace('\\', '')
2924             domain_and_server = "%s\\%s" % (local_tdo_info.domain_name.string, server)
2925             local_trust_rediscover = local_netlogon.netr_LogonControl2Ex(local_server,
2926                                                                  netlogon.NETLOGON_CONTROL_REDISCOVER,
2927                                                                  2,
2928                                                                  domain_and_server)
2929         except RuntimeError as error:
2930             raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
2931
2932         local_conn_status = self._uint32(local_trust_rediscover.tc_connection_status[0])
2933         local_rediscover = "LocalRediscover: DC[%s] CONNECTION[%s]" % (
2934                                local_trust_rediscover.trusted_dc_name,
2935                                local_trust_rediscover.tc_connection_status[1])
2936
2937         if local_conn_status != self.WERR_OK:
2938             raise CommandError(local_rediscover)
2939         else:
2940             self.outf.write("OK: %s\n" % local_rediscover)
2941
2942         if validate_location != "local":
2943             try:
2944                 remote_server = self.setup_remote_server(credopts, domain, require_pdc=False)
2945             except RuntimeError as error:
2946                 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2947
2948             try:
2949                 remote_netlogon = self.new_remote_netlogon_connection()
2950             except RuntimeError as error:
2951                 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2952
2953             try:
2954                 remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_server,
2955                                                                   netlogon.NETLOGON_CONTROL_TC_VERIFY,
2956                                                                   2,
2957                                                                   local_lsa_info.dns_domain.string)
2958             except RuntimeError as error:
2959                 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2960
2961             remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2962             remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2963
2964             if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2965                 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2966                                    remote_trust_verify.trusted_dc_name,
2967                                    remote_trust_verify.tc_connection_status[1],
2968                                    remote_trust_verify.pdc_connection_status[1])
2969             else:
2970                 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2971                                    remote_trust_verify.trusted_dc_name,
2972                                    remote_trust_verify.tc_connection_status[1],
2973                                    remote_trust_verify.pdc_connection_status[1])
2974
2975             if remote_trust_status != self.WERR_OK or remote_conn_status != self.WERR_OK:
2976                 raise CommandError(remote_validation)
2977             else:
2978                 self.outf.write("OK: %s\n" % remote_validation)
2979
2980             try:
2981                 server = remote_trust_verify.trusted_dc_name.replace('\\', '')
2982                 domain_and_server = "%s\\%s" % (local_lsa_info.dns_domain.string, server)
2983                 remote_trust_rediscover = remote_netlogon.netr_LogonControl2Ex(remote_server,
2984                                                                      netlogon.NETLOGON_CONTROL_REDISCOVER,
2985                                                                      2,
2986                                                                      domain_and_server)
2987             except RuntimeError as error:
2988                 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
2989
2990             remote_conn_status = self._uint32(remote_trust_rediscover.tc_connection_status[0])
2991
2992             remote_rediscover = "RemoteRediscover: DC[%s] CONNECTION[%s]" % (
2993                                    remote_trust_rediscover.trusted_dc_name,
2994                                    remote_trust_rediscover.tc_connection_status[1])
2995
2996             if remote_conn_status != self.WERR_OK:
2997                 raise CommandError(remote_rediscover)
2998             else:
2999                 self.outf.write("OK: %s\n" % remote_rediscover)
3000
3001         return
3002
3003 class cmd_domain_trust_namespaces(DomainTrustCommand):
3004     """Manage forest trust namespaces."""
3005
3006     synopsis = "%prog [DOMAIN] [options]"
3007
3008     takes_optiongroups = {
3009         "sambaopts": options.SambaOptions,
3010         "versionopts": options.VersionOptions,
3011         "localdcopts": LocalDCCredentialsOptions,
3012     }
3013
3014     takes_options = [
3015         Option("--refresh", type="choice", metavar="check|store",
3016                choices=["check", "store", None],
3017                help="List and maybe store refreshed forest trust information: 'check' or 'store'.",
3018                dest='refresh',
3019                default=None),
3020         Option("--enable-all", action="store_true",
3021                help="Try to update disabled entries, not allowed with --refresh=check.",
3022                dest='enable_all',
3023                default=False),
3024         Option("--enable-tln", action="append", metavar='DNSDOMAIN',
3025                help="Enable a top level name entry. Can be specified multiple times.",
3026                dest='enable_tln',
3027                default=[]),
3028         Option("--disable-tln", action="append", metavar='DNSDOMAIN',
3029                help="Disable a top level name entry. Can be specified multiple times.",
3030                dest='disable_tln',
3031                default=[]),
3032         Option("--add-tln-ex", action="append", metavar='DNSDOMAIN',
3033                help="Add a top level exclusion entry. Can be specified multiple times.",
3034                dest='add_tln_ex',
3035                default=[]),
3036         Option("--delete-tln-ex", action="append", metavar='DNSDOMAIN',
3037                help="Delete a top level exclusion entry. Can be specified multiple times.",
3038                dest='delete_tln_ex',
3039                default=[]),
3040         Option("--enable-nb", action="append", metavar='NETBIOSDOMAIN',
3041                help="Enable a netbios name in a domain entry. Can be specified multiple times.",
3042                dest='enable_nb',
3043                default=[]),
3044         Option("--disable-nb", action="append", metavar='NETBIOSDOMAIN',
3045                help="Disable a netbios name in a domain entry. Can be specified multiple times.",
3046                dest='disable_nb',
3047                default=[]),
3048         Option("--enable-sid", action="append", metavar='DOMAINSID',
3049                help="Enable a SID in a domain entry. Can be specified multiple times.",
3050                dest='enable_sid_str',
3051                default=[]),
3052         Option("--disable-sid", action="append", metavar='DOMAINSID',
3053                help="Disable a SID in a domain entry. Can be specified multiple times.",
3054                dest='disable_sid_str',
3055                default=[]),
3056         Option("--add-upn-suffix", action="append", metavar='DNSDOMAIN',
3057                help="Add a new uPNSuffixes attribute for the local forest. Can be specified multiple times.",
3058                dest='add_upn',
3059                default=[]),
3060         Option("--delete-upn-suffix", action="append", metavar='DNSDOMAIN',
3061                help="Delete an existing uPNSuffixes attribute of the local forest. Can be specified multiple times.",
3062                dest='delete_upn',
3063                default=[]),
3064         Option("--add-spn-suffix", action="append", metavar='DNSDOMAIN',
3065                help="Add a new msDS-SPNSuffixes attribute for the local forest. Can be specified multiple times.",
3066                dest='add_spn',
3067                default=[]),
3068         Option("--delete-spn-suffix", action="append", metavar='DNSDOMAIN',
3069                help="Delete an existing msDS-SPNSuffixes attribute of the local forest. Can be specified multiple times.",
3070                dest='delete_spn',
3071                default=[]),
3072        ]
3073
3074     takes_args = ["domain?"]
3075
3076     def run(self, domain=None, sambaopts=None, localdcopts=None, versionopts=None,
3077             refresh=None, enable_all=False,
3078             enable_tln=[], disable_tln=[], add_tln_ex=[], delete_tln_ex=[],
3079             enable_sid_str=[], disable_sid_str=[], enable_nb=[], disable_nb=[],
3080             add_upn=[], delete_upn=[], add_spn=[], delete_spn=[]):
3081
3082         require_update = False
3083
3084         if domain is None:
3085             if refresh == "store":
3086                 raise CommandError("--refresh=%s not allowed without DOMAIN" % refresh)
3087
3088             if enable_all:
3089                 raise CommandError("--enable-all not allowed without DOMAIN")
3090
3091             if len(enable_tln) > 0:
3092                 raise CommandError("--enable-tln not allowed without DOMAIN")
3093             if len(disable_tln) > 0:
3094                 raise CommandError("--disable-tln not allowed without DOMAIN")
3095
3096             if len(add_tln_ex) > 0:
3097                 raise CommandError("--add-tln-ex not allowed without DOMAIN")
3098             if len(delete_tln_ex) > 0:
3099                 raise CommandError("--delete-tln-ex not allowed without DOMAIN")
3100
3101             if len(enable_nb) > 0:
3102                 raise CommandError("--enable-nb not allowed without DOMAIN")
3103             if len(disable_nb) > 0:
3104                 raise CommandError("--disable-nb not allowed without DOMAIN")
3105
3106             if len(enable_sid_str) > 0:
3107                 raise CommandError("--enable-sid not allowed without DOMAIN")
3108             if len(disable_sid_str) > 0:
3109                 raise CommandError("--disable-sid not allowed without DOMAIN")
3110
3111             if len(add_upn) > 0:
3112                 for n in add_upn:
3113                     if not n.startswith("*."):
3114                         continue
3115                     raise CommandError("value[%s] specified for --add-upn-suffix should not include with '*.'" % n)
3116                 require_update = True
3117             if len(delete_upn) > 0:
3118                 for n in delete_upn:
3119                     if not n.startswith("*."):
3120                         continue
3121                     raise CommandError("value[%s] specified for --delete-upn-suffix should not include with '*.'" % n)
3122                 require_update = True
3123             for a in add_upn:
3124                 for d in delete_upn:
3125                     if a.lower() != d.lower():
3126                         continue
3127                     raise CommandError("value[%s] specified for --add-upn-suffix and --delete-upn-suffix" % a)
3128
3129             if len(add_spn) > 0:
3130                 for n in add_spn:
3131                     if not n.startswith("*."):
3132                         continue
3133                     raise CommandError("value[%s] specified for --add-spn-suffix should not include with '*.'" % n)
3134                 require_update = True
3135             if len(delete_spn) > 0:
3136                 for n in delete_spn:
3137                     if not n.startswith("*."):
3138                         continue
3139                     raise CommandError("value[%s] specified for --delete-spn-suffix should not include with '*.'" % n)
3140                 require_update = True
3141             for a in add_spn:
3142                 for d in delete_spn:
3143                     if a.lower() != d.lower():
3144                         continue
3145                     raise CommandError("value[%s] specified for --add-spn-suffix and --delete-spn-suffix" % a)
3146         else:
3147             if len(add_upn) > 0:
3148                 raise CommandError("--add-upn-suffix not allowed together with DOMAIN")
3149             if len(delete_upn) > 0:
3150                 raise CommandError("--delete-upn-suffix not allowed together with DOMAIN")
3151             if len(add_spn) > 0:
3152                 raise CommandError("--add-spn-suffix not allowed together with DOMAIN")
3153             if len(delete_spn) > 0:
3154                 raise CommandError("--delete-spn-suffix not allowed together with DOMAIN")
3155
3156         if refresh is not None:
3157             if refresh == "store":
3158                 require_update = True
3159
3160             if enable_all and refresh != "store":
3161                 raise CommandError("--enable-all not allowed together with --refresh=%s" % refresh)
3162
3163             if len(enable_tln) > 0:
3164                 raise CommandError("--enable-tln not allowed together with --refresh")
3165             if len(disable_tln) > 0:
3166                 raise CommandError("--disable-tln not allowed together with --refresh")
3167
3168             if len(add_tln_ex) > 0:
3169                 raise CommandError("--add-tln-ex not allowed together with --refresh")
3170             if len(delete_tln_ex) > 0:
3171                 raise CommandError("--delete-tln-ex not allowed together with --refresh")
3172
3173             if len(enable_nb) > 0:
3174                 raise CommandError("--enable-nb not allowed together with --refresh")
3175             if len(disable_nb) > 0:
3176                 raise CommandError("--disable-nb not allowed together with --refresh")
3177
3178             if len(enable_sid_str) > 0:
3179                 raise CommandError("--enable-sid not allowed together with --refresh")
3180             if len(disable_sid_str) > 0:
3181                 raise CommandError("--disable-sid not allowed together with --refresh")
3182         else:
3183             if enable_all:
3184                 require_update = True
3185
3186                 if len(enable_tln) > 0:
3187                     raise CommandError("--enable-tln not allowed together with --enable-all")
3188
3189                 if len(enable_nb) > 0:
3190                     raise CommandError("--enable-nb not allowed together with --enable-all")
3191
3192                 if len(enable_sid) > 0:
3193                     raise CommandError("--enable-sid not allowed together with --enable-all")
3194
3195             if len(enable_tln) > 0:
3196                 require_update = True
3197             if len(disable_tln) > 0:
3198                 require_update = True
3199             for e in enable_tln:
3200                 for d in disable_tln:
3201                     if e.lower() != d.lower():
3202                         continue
3203                     raise CommandError("value[%s] specified for --enable-tln and --disable-tln" % e)
3204
3205             if len(add_tln_ex) > 0:
3206                 for n in add_tln_ex:
3207                     if not n.startswith("*."):
3208                         continue
3209                     raise CommandError("value[%s] specified for --add-tln-ex should not include with '*.'" % n)
3210                 require_update = True
3211             if len(delete_tln_ex) > 0:
3212                 for n in delete_tln_ex:
3213                     if not n.startswith("*."):
3214                         continue
3215                     raise CommandError("value[%s] specified for --delete-tln-ex should not include with '*.'" % n)
3216                 require_update = True
3217             for a in add_tln_ex:
3218                 for d in delete_tln_ex:
3219                     if a.lower() != d.lower():
3220                         continue
3221                     raise CommandError("value[%s] specified for --add-tln-ex and --delete-tln-ex" % a)
3222
3223             if len(enable_nb) > 0:
3224                 require_update = True
3225             if len(disable_nb) > 0:
3226                 require_update = True
3227             for e in enable_nb:
3228                 for d in disable_nb:
3229                     if e.upper() != d.upper():
3230                         continue
3231                     raise CommandError("value[%s] specified for --enable-nb and --disable-nb" % e)
3232
3233             enable_sid = []
3234             for s in enable_sid_str:
3235                 try:
3236                     sid = security.dom_sid(s)
3237                 except TypeError as error:
3238                     raise CommandError("value[%s] specified for --enable-sid is not a valid SID" % s)
3239                 enable_sid.append(sid)
3240             disable_sid = []
3241             for s in disable_sid_str:
3242                 try:
3243                     sid = security.dom_sid(s)
3244                 except TypeError as error:
3245                     raise CommandError("value[%s] specified for --disable-sid is not a valid SID" % s)
3246                 disable_sid.append(sid)
3247             if len(enable_sid) > 0:
3248                 require_update = True
3249             if len(disable_sid) > 0:
3250                 require_update = True
3251             for e in enable_sid:
3252                 for d in disable_sid:
3253                     if e != d:
3254                         continue
3255                     raise CommandError("value[%s] specified for --enable-sid and --disable-sid" % e)
3256
3257         local_policy_access =  lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
3258         if require_update:
3259             local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
3260
3261         local_server = self.setup_local_server(sambaopts, localdcopts)
3262         try:
3263             local_lsa = self.new_local_lsa_connection()
3264         except RuntimeError as error:
3265             raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
3266
3267         try:
3268             (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
3269         except RuntimeError as error:
3270             raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
3271
3272         self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
3273                         local_lsa_info.name.string,
3274                         local_lsa_info.dns_domain.string,
3275                         local_lsa_info.sid))
3276
3277         if domain is None:
3278             try:
3279                 local_netlogon = self.new_local_netlogon_connection()
3280             except RuntimeError as error:
3281                 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3282
3283             try:
3284                 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3285             except RuntimeError as error:
3286                 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3287
3288             if local_netlogon_info.domain_name != local_netlogon_info.forest_name:
3289                 raise CommandError("The local domain [%s] is not the forest root [%s]" % (
3290                                    local_netlogon_info.domain_name,
3291                                    local_netlogon_info.forest_name))
3292
3293             try:
3294                 # get all information about our own forest
3295                 own_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3296                                                                                    None, 0)
3297             except RuntimeError as error:
3298                 if self.check_runtime_error(error, self.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE):
3299                     raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3300                                        self.local_server))
3301
3302                 if self.check_runtime_error(error, self.WERR_INVALID_FUNCTION):
3303                     raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3304                                        self.local_server))
3305
3306                 if self.check_runtime_error(error, self.WERR_NERR_ACFNOTLOADED):
3307                     raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3308                                        self.local_server))
3309
3310                 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3311
3312             self.outf.write("Own forest trust information...\n")
3313             self.write_forest_trust_info(own_forest_info,
3314                                          tln=local_lsa_info.dns_domain.string)
3315
3316             try:
3317                 local_samdb = self.new_local_ldap_connection()
3318             except RuntimeError as error:
3319                 raise self.LocalRuntimeError(self, error, "failed to connect to SamDB")
3320
3321             local_partitions_dn = "CN=Partitions,%s" % str(local_samdb.get_config_basedn())
3322             attrs = ['uPNSuffixes', 'msDS-SPNSuffixes']
3323             try:
3324                 msgs = local_samdb.search(base=local_partitions_dn,
3325                                           scope=ldb.SCOPE_BASE,
3326                                           expression="(objectClass=crossRefContainer)",
3327                                           attrs=attrs)
3328                 stored_msg = msgs[0]
3329             except ldb.LdbError as error:
3330                 raise self.LocalLdbError(self, error, "failed to search partition dn")
3331
3332             stored_upn_vals = []
3333             if 'uPNSuffixes' in stored_msg:
3334                 stored_upn_vals.extend(stored_msg['uPNSuffixes'])
3335
3336             stored_spn_vals = []
3337             if 'msDS-SPNSuffixes' in stored_msg:
3338                 stored_spn_vals.extend(stored_msg['msDS-SPNSuffixes'])
3339
3340             self.outf.write("Stored uPNSuffixes attributes[%d]:\n" % len(stored_upn_vals))
3341             for v in stored_upn_vals:
3342                   self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3343             self.outf.write("Stored msDS-SPNSuffixes attributes[%d]:\n" % len(stored_spn_vals))
3344             for v in stored_spn_vals:
3345                   self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3346
3347             if not require_update:
3348                 return
3349
3350             replace_upn = False
3351             update_upn_vals = []
3352             update_upn_vals.extend(stored_upn_vals)
3353
3354             replace_spn = False
3355             update_spn_vals = []
3356             update_spn_vals.extend(stored_spn_vals)
3357
3358             for upn in add_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 not None:
3367                     raise CommandError("Entry already present for value[%s] specified for --add-upn-suffix" % upn)
3368                 update_upn_vals.append(upn)
3369                 replace_upn = True
3370
3371             for upn in delete_upn:
3372                 idx = None
3373                 for i in xrange(0, len(update_upn_vals)):
3374                     v = update_upn_vals[i]
3375                     if v.lower() != upn.lower():
3376                         continue
3377                     idx = i
3378                     break
3379                 if idx is None:
3380                     raise CommandError("Entry not found for value[%s] specified for --delete-upn-suffix" % upn)
3381
3382                 update_upn_vals.pop(idx)
3383                 replace_upn = True
3384
3385             for spn in add_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 not None:
3394                     raise CommandError("Entry already present for value[%s] specified for --add-spn-suffix" % spn)
3395                 update_spn_vals.append(spn)
3396                 replace_spn = True
3397
3398             for spn in delete_spn:
3399                 idx = None
3400                 for i in xrange(0, len(update_spn_vals)):
3401                     v = update_spn_vals[i]
3402                     if v.lower() != spn.lower():
3403                         continue
3404                     idx = i
3405                     break
3406                 if idx is None:
3407                     raise CommandError("Entry not found for value[%s] specified for --delete-spn-suffix" % spn)
3408
3409                 update_spn_vals.pop(idx)
3410                 replace_spn = True
3411
3412             self.outf.write("Update uPNSuffixes attributes[%d]:\n" % len(update_upn_vals))
3413             for v in update_upn_vals:
3414                   self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3415             self.outf.write("Update msDS-SPNSuffixes attributes[%d]:\n" % len(update_spn_vals))
3416             for v in update_spn_vals:
3417                   self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3418
3419             update_msg = ldb.Message()
3420             update_msg.dn = stored_msg.dn
3421
3422             if replace_upn:
3423                 update_msg['uPNSuffixes'] = ldb.MessageElement(update_upn_vals,
3424                                                                     ldb.FLAG_MOD_REPLACE,
3425                                                                     'uPNSuffixes')
3426             if replace_spn:
3427                 update_msg['msDS-SPNSuffixes'] = ldb.MessageElement(update_spn_vals,
3428                                                                     ldb.FLAG_MOD_REPLACE,
3429                                                                     'msDS-SPNSuffixes')
3430             try:
3431                 local_samdb.modify(update_msg)
3432             except ldb.LdbError as error:
3433                 raise self.LocalLdbError(self, error, "failed to update partition dn")
3434
3435             try:
3436                 stored_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3437                                                                                        None, 0)
3438             except RuntimeError as error:
3439                 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3440
3441             self.outf.write("Stored forest trust information...\n")
3442             self.write_forest_trust_info(stored_forest_info,
3443                                          tln=local_lsa_info.dns_domain.string)
3444             return
3445
3446         try:
3447             lsaString = lsa.String()
3448             lsaString.string = domain
3449             local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
3450                                         lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
3451         except RuntimeError as error:
3452             if self.check_runtime_error(error, self.NT_STATUS_OBJECT_NAME_NOT_FOUND):
3453                 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
3454
3455             raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
3456
3457         self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
3458                         local_tdo_info.netbios_name.string,
3459                         local_tdo_info.domain_name.string,
3460                         local_tdo_info.sid))
3461
3462         if not local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
3463             raise CommandError("trusted domain object for domain [%s] is not marked as FOREST_TRANSITIVE." % domain)
3464
3465         if refresh is not None:
3466             try:
3467                 local_netlogon = self.new_local_netlogon_connection()
3468             except RuntimeError as error:
3469                 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3470
3471             try:
3472                 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3473             except RuntimeError as error:
3474                 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3475
3476             lsa_update_check = 1
3477             if refresh == "store":
3478                 netlogon_update_tdo = netlogon.DS_GFTI_UPDATE_TDO
3479                 if enable_all:
3480                     lsa_update_check = 0
3481             else:
3482                 netlogon_update_tdo = 0
3483
3484             try:
3485                 # get all information about the remote trust
3486                 # this triggers netr_GetForestTrustInformation to the remote domain
3487                 # and lsaRSetForestTrustInformation() locally, but new top level
3488                 # names are disabled by default.
3489                 fresh_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3490                                                               local_tdo_info.domain_name.string,
3491                                                               netlogon_update_tdo)
3492             except RuntimeError as error:
3493                 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3494
3495             try:
3496                 fresh_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3497                                                               local_tdo_info.domain_name,
3498                                                               lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3499                                                               fresh_forest_info,
3500                                                               lsa_update_check)
3501             except RuntimeError as error:
3502                 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3503
3504             self.outf.write("Fresh forest trust information...\n")
3505             self.write_forest_trust_info(fresh_forest_info,
3506                                          tln=local_tdo_info.domain_name.string,
3507                                          collisions=fresh_forest_collision)
3508
3509             if refresh == "store":
3510                 try:
3511                     lsaString = lsa.String()
3512                     lsaString.string = local_tdo_info.domain_name.string
3513                     stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3514                                                                   lsaString,
3515                                                                   lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3516                 except RuntimeError as error:
3517                     raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3518
3519                 self.outf.write("Stored forest trust information...\n")
3520                 self.write_forest_trust_info(stored_forest_info,
3521                                              tln=local_tdo_info.domain_name.string)
3522
3523             return
3524
3525         #
3526         # The none --refresh path
3527         #
3528
3529         try:
3530             lsaString = lsa.String()
3531             lsaString.string = local_tdo_info.domain_name.string
3532             local_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3533                                                       lsaString,
3534                                                       lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3535         except RuntimeError as error:
3536             raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3537
3538         self.outf.write("Local forest trust information...\n")
3539         self.write_forest_trust_info(local_forest_info,
3540                                      tln=local_tdo_info.domain_name.string)
3541
3542         if not require_update:
3543             return
3544
3545         entries = []
3546         entries.extend(local_forest_info.entries)
3547         update_forest_info = lsa.ForestTrustInformation()
3548         update_forest_info.count = len(entries)
3549         update_forest_info.entries = entries
3550
3551         if enable_all:
3552             for i in xrange(0, len(update_forest_info.entries)):
3553                 r = update_forest_info.entries[i]
3554                 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3555                     continue
3556                 if update_forest_info.entries[i].flags == 0:
3557                     continue
3558                 update_forest_info.entries[i].time = 0
3559                 update_forest_info.entries[i].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3560             for i in xrange(0, len(update_forest_info.entries)):
3561                 r = update_forest_info.entries[i]
3562                 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3563                     continue
3564                 if update_forest_info.entries[i].flags == 0:
3565                     continue
3566                 update_forest_info.entries[i].time = 0
3567                 update_forest_info.entries[i].flags &= ~lsa.LSA_NB_DISABLED_MASK
3568                 update_forest_info.entries[i].flags &= ~lsa.LSA_SID_DISABLED_MASK
3569
3570         for tln in enable_tln:
3571             idx = None
3572             for i in xrange(0, len(update_forest_info.entries)):
3573                 r = update_forest_info.entries[i]
3574                 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3575                     continue
3576                 if r.forest_trust_data.string.lower() != tln.lower():
3577                     continue
3578                 idx = i
3579                 break
3580             if idx is None:
3581                 raise CommandError("Entry not found for value[%s] specified for --enable-tln" % tln)
3582             if not update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_MASK:
3583                 raise CommandError("Entry found for value[%s] specified for --enable-tln is already enabled" % tln)
3584             update_forest_info.entries[idx].time = 0
3585             update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3586
3587         for tln in disable_tln:
3588             idx = None
3589             for i in xrange(0, len(update_forest_info.entries)):
3590                 r = update_forest_info.entries[i]
3591                 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3592                     continue
3593                 if r.forest_trust_data.string.lower() != tln.lower():
3594                     continue
3595                 idx = i
3596                 break
3597             if idx is None:
3598                 raise CommandError("Entry not found for value[%s] specified for --disable-tln" % tln)
3599             if update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_ADMIN:
3600                 raise CommandError("Entry found for value[%s] specified for --disable-tln is already disabled" % tln)
3601             update_forest_info.entries[idx].time = 0
3602             update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3603             update_forest_info.entries[idx].flags |= lsa.LSA_TLN_DISABLED_ADMIN
3604
3605         for tln_ex in add_tln_ex:
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_EX:
3610                     continue
3611                 if r.forest_trust_data.string.lower() != tln_ex.lower():
3612                     continue
3613                 idx = i
3614                 break
3615             if idx is not None:
3616                 raise CommandError("Entry already present for value[%s] specified for --add-tln-ex" % tln_ex)
3617
3618             tln_dot = ".%s" % tln_ex.lower()
3619             idx = None
3620             for i in xrange(0, len(update_forest_info.entries)):
3621                 r = update_forest_info.entries[i]
3622                 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3623                     continue
3624                 r_dot = ".%s" % r.forest_trust_data.string.lower()
3625                 if tln_dot == r_dot:
3626                     raise CommandError("TLN entry present for value[%s] specified for --add-tln-ex" % tln_ex)
3627                 if not tln_dot.endswith(r_dot):
3628                     continue
3629                 idx = i
3630                 break
3631
3632             if idx is None:
3633                 raise CommandError("No TLN parent present for value[%s] specified for --add-tln-ex" % tln_ex)
3634
3635             r = lsa.ForestTrustRecord()
3636             r.type = lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX
3637             r.flags = 0
3638             r.time = 0
3639             r.forest_trust_data.string = tln_ex
3640
3641             entries = []
3642             entries.extend(update_forest_info.entries)
3643             entries.insert(idx + 1, r)
3644             update_forest_info.count = len(entries)
3645             update_forest_info.entries = entries
3646
3647         for tln_ex in delete_tln_ex:
3648             idx = None
3649             for i in xrange(0, len(update_forest_info.entries)):
3650                 r = update_forest_info.entries[i]
3651                 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3652                     continue
3653                 if r.forest_trust_data.string.lower() != tln_ex.lower():
3654                     continue
3655                 idx = i
3656                 break
3657             if idx is None:
3658                 raise CommandError("Entry not found for value[%s] specified for --delete-tln-ex" % tln_ex)
3659
3660             entries = []
3661             entries.extend(update_forest_info.entries)
3662             entries.pop(idx)
3663             update_forest_info.count = len(entries)
3664             update_forest_info.entries = entries
3665
3666         for nb in enable_nb:
3667             idx = None
3668             for i in xrange(0, len(update_forest_info.entries)):
3669                 r = update_forest_info.entries[i]
3670                 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3671                     continue
3672                 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3673                     continue
3674                 idx = i
3675                 break
3676             if idx is None:
3677                 raise CommandError("Entry not found for value[%s] specified for --enable-nb" % nb)
3678             if not update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_MASK:
3679                 raise CommandError("Entry found for value[%s] specified for --enable-nb is already enabled" % nb)
3680             update_forest_info.entries[idx].time = 0
3681             update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3682
3683         for nb in disable_nb:
3684             idx = None
3685             for i in xrange(0, len(update_forest_info.entries)):
3686                 r = update_forest_info.entries[i]
3687                 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3688                     continue
3689                 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3690                     continue
3691                 idx = i
3692                 break
3693             if idx is None:
3694                 raise CommandError("Entry not found for value[%s] specified for --delete-nb" % nb)
3695             if update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_ADMIN:
3696                 raise CommandError("Entry found for value[%s] specified for --disable-nb is already disabled" % nb)
3697             update_forest_info.entries[idx].time = 0
3698             update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3699             update_forest_info.entries[idx].flags |= lsa.LSA_NB_DISABLED_ADMIN
3700
3701         for sid in enable_sid:
3702             idx = None
3703             for i in xrange(0, len(update_forest_info.entries)):
3704                 r = update_forest_info.entries[i]
3705                 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3706                     continue
3707                 if r.forest_trust_data.domain_sid != sid:
3708                     continue
3709                 idx = i
3710                 break
3711             if idx is None:
3712                 raise CommandError("Entry not found for value[%s] specified for --enable-sid" % sid)
3713             if not update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_MASK:
3714                 raise CommandError("Entry found for value[%s] specified for --enable-sid is already enabled" % nb)
3715             update_forest_info.entries[idx].time = 0
3716             update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3717
3718         for sid in disable_sid:
3719             idx = None
3720             for i in xrange(0, len(update_forest_info.entries)):
3721                 r = update_forest_info.entries[i]
3722                 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3723                     continue
3724                 if r.forest_trust_data.domain_sid != sid:
3725                     continue
3726                 idx = i
3727                 break
3728             if idx is None:
3729                 raise CommandError("Entry not found for value[%s] specified for --delete-sid" % sid)
3730             if update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_ADMIN:
3731                 raise CommandError("Entry found for value[%s] specified for --disable-sid is already disabled" % nb)
3732             update_forest_info.entries[idx].time = 0
3733             update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3734             update_forest_info.entries[idx].flags |= lsa.LSA_SID_DISABLED_ADMIN
3735
3736         try:
3737             update_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3738                                                           local_tdo_info.domain_name,
3739                                                           lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3740                                                           update_forest_info, 0)
3741         except RuntimeError as error:
3742             raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3743
3744         self.outf.write("Updated forest trust information...\n")
3745         self.write_forest_trust_info(update_forest_info,
3746                                      tln=local_tdo_info.domain_name.string,
3747                                      collisions=update_forest_collision)
3748
3749         try:
3750             lsaString = lsa.String()
3751             lsaString.string = local_tdo_info.domain_name.string
3752             stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3753                                                           lsaString,
3754                                                           lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3755         except RuntimeError as error:
3756             raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3757
3758         self.outf.write("Stored forest trust information...\n")
3759         self.write_forest_trust_info(stored_forest_info,
3760                                      tln=local_tdo_info.domain_name.string)
3761         return
3762
3763 class cmd_domain_trust(SuperCommand):
3764     """Domain and forest trust management."""
3765
3766     subcommands = {}
3767     subcommands["list"] = cmd_domain_trust_list()
3768     subcommands["show"] = cmd_domain_trust_show()
3769     subcommands["create"] = cmd_domain_trust_create()
3770     subcommands["delete"] = cmd_domain_trust_delete()
3771     subcommands["validate"] = cmd_domain_trust_validate()
3772     subcommands["namespaces"] = cmd_domain_trust_namespaces()
3773
3774 class cmd_domain(SuperCommand):
3775     """Domain management."""
3776
3777     subcommands = {}
3778     subcommands["demote"] = cmd_domain_demote()
3779     if cmd_domain_export_keytab is not None:
3780         subcommands["exportkeytab"] = cmd_domain_export_keytab()
3781     subcommands["info"] = cmd_domain_info()
3782     subcommands["provision"] = cmd_domain_provision()
3783     subcommands["join"] = cmd_domain_join()
3784     subcommands["dcpromo"] = cmd_domain_dcpromo()
3785     subcommands["level"] = cmd_domain_level()
3786     subcommands["passwordsettings"] = cmd_domain_passwordsettings()
3787     subcommands["classicupgrade"] = cmd_domain_classicupgrade()
3788     subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()
3789     subcommands["trust"] = cmd_domain_trust()