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