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