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