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