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