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