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