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