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