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