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