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