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