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