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