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