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