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