samba-tool domain schemaupgrade: Avoid reindex after every hunk
[metze/samba/wip.git] / python / samba / netcmd / domain.py
1 # domain management
2 #
3 # Copyright Matthias Dieter Wallnoefer 2009
4 # Copyright Andrew Kroeger 2009
5 # Copyright Jelmer Vernooij 2007-2012
6 # Copyright Giampaolo Lauria 2011
7 # Copyright Matthieu Patou <mat@matws.net> 2011
8 # Copyright Andrew Bartlett 2008-2015
9 # Copyright Stefan Metzmacher 2012
10 #
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
15 #
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 # GNU General Public License for more details.
20 #
21 # You should have received a copy of the GNU General Public License
22 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
23 #
24
25 import samba.getopt as options
26 import ldb
27 import string
28 import os
29 import sys
30 import ctypes
31 import random
32 import tempfile
33 import logging
34 import subprocess
35 import time
36 import shutil
37 from samba import ntstatus
38 from samba import NTSTATUSError
39 from samba import werror
40 from getpass import getpass
41 from samba.net import Net, LIBNET_JOIN_AUTOMATIC
42 import samba.ntacls
43 from samba.join import join_RODC, join_DC, join_subdomain
44 from samba.auth import system_session
45 from samba.samdb import SamDB
46 from samba.ndr import ndr_unpack, ndr_pack, ndr_print
47 from samba.dcerpc import drsuapi
48 from samba.dcerpc import drsblobs
49 from samba.dcerpc import lsa
50 from samba.dcerpc import netlogon
51 from samba.dcerpc import security
52 from samba.dcerpc import nbt
53 from samba.dcerpc import misc
54 from samba.dcerpc.samr import DOMAIN_PASSWORD_COMPLEX, DOMAIN_PASSWORD_STORE_CLEARTEXT
55 from samba.netcmd import (
56     Command,
57     CommandError,
58     SuperCommand,
59     Option
60     )
61 from samba.netcmd.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 Exception:
1775             raise CommandError("Failed to find a writeable DC for domain '%s'" % domain)
1776         flag_map = {
1777             nbt.NBT_SERVER_PDC: "PDC",
1778             nbt.NBT_SERVER_GC: "GC",
1779             nbt.NBT_SERVER_LDAP: "LDAP",
1780             nbt.NBT_SERVER_DS: "DS",
1781             nbt.NBT_SERVER_KDC: "KDC",
1782             nbt.NBT_SERVER_TIMESERV: "TIMESERV",
1783             nbt.NBT_SERVER_CLOSEST: "CLOSEST",
1784             nbt.NBT_SERVER_WRITABLE: "WRITABLE",
1785             nbt.NBT_SERVER_GOOD_TIMESERV: "GOOD_TIMESERV",
1786             nbt.NBT_SERVER_NDNC: "NDNC",
1787             nbt.NBT_SERVER_SELECT_SECRET_DOMAIN_6: "SELECT_SECRET_DOMAIN_6",
1788             nbt.NBT_SERVER_FULL_SECRET_DOMAIN_6: "FULL_SECRET_DOMAIN_6",
1789             nbt.NBT_SERVER_ADS_WEB_SERVICE: "ADS_WEB_SERVICE",
1790             nbt.NBT_SERVER_DS_8: "DS_8",
1791             nbt.NBT_SERVER_HAS_DNS_NAME: "HAS_DNS_NAME",
1792             nbt.NBT_SERVER_IS_DEFAULT_NC: "IS_DEFAULT_NC",
1793             nbt.NBT_SERVER_FOREST_ROOT: "FOREST_ROOT",
1794         }
1795         server_type_string = self.generic_bitmap_to_string(flag_map,
1796                                 remote_info.server_type, names_only=True)
1797         self.outf.write("RemoteDC Netbios[%s] DNS[%s] ServerType[%s]\n" % (
1798                         remote_info.pdc_name,
1799                         remote_info.pdc_dns_name,
1800                         server_type_string))
1801
1802         self.remote_server = remote_info.pdc_dns_name
1803         self.remote_binding_string="ncacn_np:%s[%s]" % (self.remote_server, remote_binding_options)
1804         self.remote_creds = remote_creds
1805         return self.remote_server
1806
1807     def new_remote_lsa_connection(self):
1808         return lsa.lsarpc(self.remote_binding_string, self.local_lp, self.remote_creds)
1809
1810     def new_remote_netlogon_connection(self):
1811         return netlogon.netlogon(self.remote_binding_string, self.local_lp, self.remote_creds)
1812
1813     def get_lsa_info(self, conn, policy_access):
1814         objectAttr = lsa.ObjectAttribute()
1815         objectAttr.sec_qos = lsa.QosInfo()
1816
1817         policy = conn.OpenPolicy2(''.decode('utf-8'),
1818                                   objectAttr, policy_access)
1819
1820         info = conn.QueryInfoPolicy2(policy, lsa.LSA_POLICY_INFO_DNS)
1821
1822         return (policy, info)
1823
1824     def get_netlogon_dc_info(self, conn, server):
1825         info = conn.netr_DsRGetDCNameEx2(server,
1826                                          None, 0, None, None, None,
1827                                          netlogon.DS_RETURN_DNS_NAME)
1828         return info
1829
1830     def netr_DomainTrust_to_name(self, t):
1831         if t.trust_type == lsa.LSA_TRUST_TYPE_DOWNLEVEL:
1832              return t.netbios_name
1833
1834         return t.dns_name
1835
1836     def netr_DomainTrust_to_type(self, a, t):
1837         primary = None
1838         primary_parent = None
1839         for _t in a:
1840              if _t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
1841                   primary = _t
1842                   if not _t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1843                       primary_parent = a[_t.parent_index]
1844                   break
1845
1846         if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1847             if t is primary_parent:
1848                 return "Parent"
1849
1850             if t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1851                 return "TreeRoot"
1852
1853             parent = a[t.parent_index]
1854             if parent is primary:
1855                 return "Child"
1856
1857             return "Shortcut"
1858
1859         if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1860             return "Forest"
1861
1862         return "External"
1863
1864     def netr_DomainTrust_to_transitive(self, t):
1865         if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1866             return "Yes"
1867
1868         if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE:
1869             return "No"
1870
1871         if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1872             return "Yes"
1873
1874         return "No"
1875
1876     def netr_DomainTrust_to_direction(self, t):
1877         if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND and \
1878            t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1879             return "BOTH"
1880
1881         if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND:
1882             return "INCOMING"
1883
1884         if t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1885             return "OUTGOING"
1886
1887         return "INVALID"
1888
1889     def generic_enum_to_string(self, e_dict, v, names_only=False):
1890         try:
1891             w = e_dict[v]
1892         except KeyError:
1893             v32 = self._uint32(v)
1894             w = "__unknown__%08X__" % v32
1895
1896         r = "0x%x (%s)" % (v, w)
1897         return r;
1898
1899     def generic_bitmap_to_string(self, b_dict, v, names_only=False):
1900
1901         s = []
1902
1903         c = v
1904         for b in sorted(b_dict.keys()):
1905             if not (c & b):
1906                 continue
1907             c &= ~b
1908             s += [b_dict[b]]
1909
1910         if c != 0:
1911             c32 = self._uint32(c)
1912             s += ["__unknown_%08X__" % c32]
1913
1914         w = ",".join(s)
1915         if names_only:
1916             return w
1917         r = "0x%x (%s)" % (v, w)
1918         return r;
1919
1920     def trustType_string(self, v):
1921         types = {
1922             lsa.LSA_TRUST_TYPE_DOWNLEVEL : "DOWNLEVEL",
1923             lsa.LSA_TRUST_TYPE_UPLEVEL : "UPLEVEL",
1924             lsa.LSA_TRUST_TYPE_MIT : "MIT",
1925             lsa.LSA_TRUST_TYPE_DCE : "DCE",
1926         }
1927         return self.generic_enum_to_string(types, v)
1928
1929     def trustDirection_string(self, v):
1930         directions = {
1931             lsa.LSA_TRUST_DIRECTION_INBOUND |
1932             lsa.LSA_TRUST_DIRECTION_OUTBOUND : "BOTH",
1933             lsa.LSA_TRUST_DIRECTION_INBOUND : "INBOUND",
1934             lsa.LSA_TRUST_DIRECTION_OUTBOUND : "OUTBOUND",
1935         }
1936         return self.generic_enum_to_string(directions, v)
1937
1938     def trustAttributes_string(self, v):
1939         attributes = {
1940             lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE : "NON_TRANSITIVE",
1941             lsa.LSA_TRUST_ATTRIBUTE_UPLEVEL_ONLY : "UPLEVEL_ONLY",
1942             lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN : "QUARANTINED_DOMAIN",
1943             lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE : "FOREST_TRANSITIVE",
1944             lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION : "CROSS_ORGANIZATION",
1945             lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST : "WITHIN_FOREST",
1946             lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL : "TREAT_AS_EXTERNAL",
1947             lsa.LSA_TRUST_ATTRIBUTE_USES_RC4_ENCRYPTION : "USES_RC4_ENCRYPTION",
1948         }
1949         return self.generic_bitmap_to_string(attributes, v)
1950
1951     def kerb_EncTypes_string(self, v):
1952         enctypes = {
1953             security.KERB_ENCTYPE_DES_CBC_CRC : "DES_CBC_CRC",
1954             security.KERB_ENCTYPE_DES_CBC_MD5 : "DES_CBC_MD5",
1955             security.KERB_ENCTYPE_RC4_HMAC_MD5 : "RC4_HMAC_MD5",
1956             security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96 : "AES128_CTS_HMAC_SHA1_96",
1957             security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96 : "AES256_CTS_HMAC_SHA1_96",
1958             security.KERB_ENCTYPE_FAST_SUPPORTED : "FAST_SUPPORTED",
1959             security.KERB_ENCTYPE_COMPOUND_IDENTITY_SUPPORTED : "COMPOUND_IDENTITY_SUPPORTED",
1960             security.KERB_ENCTYPE_CLAIMS_SUPPORTED : "CLAIMS_SUPPORTED",
1961             security.KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED : "RESOURCE_SID_COMPRESSION_DISABLED",
1962         }
1963         return self.generic_bitmap_to_string(enctypes, v)
1964
1965     def entry_tln_status(self, e_flags, ):
1966         if e_flags == 0:
1967             return "Status[Enabled]"
1968
1969         flags = {
1970             lsa.LSA_TLN_DISABLED_NEW : "Disabled-New",
1971             lsa.LSA_TLN_DISABLED_ADMIN : "Disabled",
1972             lsa.LSA_TLN_DISABLED_CONFLICT : "Disabled-Conflicting",
1973         }
1974         return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
1975
1976     def entry_dom_status(self, e_flags):
1977         if e_flags == 0:
1978             return "Status[Enabled]"
1979
1980         flags = {
1981             lsa.LSA_SID_DISABLED_ADMIN : "Disabled-SID",
1982             lsa.LSA_SID_DISABLED_CONFLICT : "Disabled-SID-Conflicting",
1983             lsa.LSA_NB_DISABLED_ADMIN : "Disabled-NB",
1984             lsa.LSA_NB_DISABLED_CONFLICT : "Disabled-NB-Conflicting",
1985         }
1986         return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
1987
1988     def write_forest_trust_info(self, fti, tln=None, collisions=None):
1989         if tln is not None:
1990             tln_string = " TDO[%s]" % tln
1991         else:
1992             tln_string = ""
1993
1994         self.outf.write("Namespaces[%d]%s:\n" % (
1995                         len(fti.entries), tln_string))
1996
1997         for i in xrange(0, len(fti.entries)):
1998             e = fti.entries[i]
1999
2000             flags = e.flags
2001             collision_string = ""
2002
2003             if collisions is not None:
2004                 for c in collisions.entries:
2005                     if c.index != i:
2006                         continue
2007                     flags = c.flags
2008                     collision_string = " Collision[%s]" % (c.name.string)
2009
2010             d = e.forest_trust_data
2011             if e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
2012                 self.outf.write("TLN: %-32s DNS[*.%s]%s\n" % (
2013                                 self.entry_tln_status(flags),
2014                                 d.string, collision_string))
2015             elif e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
2016                 self.outf.write("TLN_EX: %-29s DNS[*.%s]\n" % (
2017                                 "", d.string))
2018             elif e.type == lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
2019                 self.outf.write("DOM: %-32s DNS[%s] Netbios[%s] SID[%s]%s\n" % (
2020                                 self.entry_dom_status(flags),
2021                                 d.dns_domain_name.string,
2022                                 d.netbios_domain_name.string,
2023                                 d.domain_sid, collision_string))
2024         return
2025
2026 class cmd_domain_trust_list(DomainTrustCommand):
2027     """List domain trusts."""
2028
2029     synopsis = "%prog [options]"
2030
2031     takes_optiongroups = {
2032         "sambaopts": options.SambaOptions,
2033         "versionopts": options.VersionOptions,
2034         "localdcopts": LocalDCCredentialsOptions,
2035     }
2036
2037     takes_options = [
2038        ]
2039
2040     def run(self, sambaopts=None, versionopts=None, localdcopts=None):
2041
2042         local_server = self.setup_local_server(sambaopts, localdcopts)
2043         try:
2044             local_netlogon = self.new_local_netlogon_connection()
2045         except RuntimeError as error:
2046             raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2047
2048         try:
2049             local_netlogon_trusts = local_netlogon.netr_DsrEnumerateDomainTrusts(local_server,
2050                                     netlogon.NETR_TRUST_FLAG_IN_FOREST |
2051                                     netlogon.NETR_TRUST_FLAG_OUTBOUND |
2052                                     netlogon.NETR_TRUST_FLAG_INBOUND)
2053         except RuntimeError as error:
2054             if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
2055                 # TODO: we could implement a fallback to lsa.EnumTrustDom()
2056                 raise CommandError("LOCAL_DC[%s]: netr_DsrEnumerateDomainTrusts not supported." % (
2057                                    self.local_server))
2058             raise self.LocalRuntimeError(self, error, "netr_DsrEnumerateDomainTrusts failed")
2059
2060         a = local_netlogon_trusts.array
2061         for t in a:
2062             if t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
2063                 continue
2064             self.outf.write("%-14s %-15s %-19s %s\n" % (
2065                             "Type[%s]" % self.netr_DomainTrust_to_type(a, t),
2066                             "Transitive[%s]" % self.netr_DomainTrust_to_transitive(t),
2067                             "Direction[%s]" % self.netr_DomainTrust_to_direction(t),
2068                             "Name[%s]" % self.netr_DomainTrust_to_name(t)))
2069         return
2070
2071 class cmd_domain_trust_show(DomainTrustCommand):
2072     """Show trusted domain details."""
2073
2074     synopsis = "%prog NAME [options]"
2075
2076     takes_optiongroups = {
2077         "sambaopts": options.SambaOptions,
2078         "versionopts": options.VersionOptions,
2079         "localdcopts": LocalDCCredentialsOptions,
2080     }
2081
2082     takes_options = [
2083        ]
2084
2085     takes_args = ["domain"]
2086
2087     def run(self, domain, sambaopts=None, versionopts=None, localdcopts=None):
2088
2089         local_server = self.setup_local_server(sambaopts, localdcopts)
2090         try:
2091             local_lsa = self.new_local_lsa_connection()
2092         except RuntimeError as error:
2093             raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2094
2095         try:
2096             local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2097             (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2098         except RuntimeError as error:
2099             raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2100
2101         self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2102                         local_lsa_info.name.string,
2103                         local_lsa_info.dns_domain.string,
2104                         local_lsa_info.sid))
2105
2106         lsaString = lsa.String()
2107         lsaString.string = domain
2108         try:
2109             local_tdo_full = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2110                                         lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2111             local_tdo_info = local_tdo_full.info_ex
2112             local_tdo_posix = local_tdo_full.posix_offset
2113         except NTSTATUSError as error:
2114             if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2115                 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2116
2117             raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(FULL_INFO) failed")
2118
2119         try:
2120             local_tdo_enctypes = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2121                                         lsaString, lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES)
2122         except NTSTATUSError as error:
2123             if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_PARAMETER):
2124                 error = None
2125             if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_INFO_CLASS):
2126                 error = None
2127
2128             if error is not None:
2129                 raise self.LocalRuntimeError(self, error,
2130                            "QueryTrustedDomainInfoByName(SUPPORTED_ENCRYPTION_TYPES) failed")
2131
2132             local_tdo_enctypes = lsa.TrustDomainInfoSupportedEncTypes()
2133             local_tdo_enctypes.enc_types = 0
2134
2135         try:
2136             local_tdo_forest = None
2137             if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2138                 local_tdo_forest = local_lsa.lsaRQueryForestTrustInformation(local_policy,
2139                                         lsaString, lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
2140         except RuntimeError as error:
2141             if self.check_runtime_error(error, ntstatus.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE):
2142                 error = None
2143             if self.check_runtime_error(error, ntstatus.NT_STATUS_NOT_FOUND):
2144                 error = None
2145             if error is not None:
2146                 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation failed")
2147
2148             local_tdo_forest = lsa.ForestTrustInformation()
2149             local_tdo_forest.count = 0
2150             local_tdo_forest.entries = []
2151
2152         self.outf.write("TrusteDomain:\n\n");
2153         self.outf.write("NetbiosName:    %s\n" % local_tdo_info.netbios_name.string)
2154         if local_tdo_info.netbios_name.string != local_tdo_info.domain_name.string:
2155             self.outf.write("DnsName:        %s\n" % local_tdo_info.domain_name.string)
2156         self.outf.write("SID:            %s\n" % local_tdo_info.sid)
2157         self.outf.write("Type:           %s\n" % self.trustType_string(local_tdo_info.trust_type))
2158         self.outf.write("Direction:      %s\n" % self.trustDirection_string(local_tdo_info.trust_direction))
2159         self.outf.write("Attributes:     %s\n" % self.trustAttributes_string(local_tdo_info.trust_attributes))
2160         posix_offset_u32 = ctypes.c_uint32(local_tdo_posix.posix_offset).value
2161         posix_offset_i32 = ctypes.c_int32(local_tdo_posix.posix_offset).value
2162         self.outf.write("PosixOffset:    0x%08X (%d)\n" % (posix_offset_u32, posix_offset_i32))
2163         self.outf.write("kerb_EncTypes:  %s\n" % self.kerb_EncTypes_string(local_tdo_enctypes.enc_types))
2164
2165         if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2166             self.write_forest_trust_info(local_tdo_forest,
2167                                          tln=local_tdo_info.domain_name.string)
2168
2169         return
2170
2171 class cmd_domain_trust_create(DomainTrustCommand):
2172     """Create a domain or forest trust."""
2173
2174     synopsis = "%prog DOMAIN [options]"
2175
2176     takes_optiongroups = {
2177         "sambaopts": options.SambaOptions,
2178         "versionopts": options.VersionOptions,
2179         "credopts": options.CredentialsOptions,
2180         "localdcopts": LocalDCCredentialsOptions,
2181     }
2182
2183     takes_options = [
2184         Option("--type", type="choice", metavar="TYPE",
2185                choices=["external", "forest"],
2186                help="The type of the trust: 'external' or 'forest'.",
2187                dest='trust_type',
2188                default="external"),
2189         Option("--direction", type="choice", metavar="DIRECTION",
2190                choices=["incoming", "outgoing", "both"],
2191                help="The trust direction: 'incoming', 'outgoing' or 'both'.",
2192                dest='trust_direction',
2193                default="both"),
2194         Option("--create-location", type="choice", metavar="LOCATION",
2195                choices=["local", "both"],
2196                help="Where to create the trusted domain object: 'local' or 'both'.",
2197                dest='create_location',
2198                default="both"),
2199         Option("--cross-organisation", action="store_true",
2200                help="The related domains does not belong to the same organisation.",
2201                dest='cross_organisation',
2202                default=False),
2203         Option("--quarantined", type="choice", metavar="yes|no",
2204                choices=["yes", "no", None],
2205                help="Special SID filtering rules are applied to the trust. "
2206                     "With --type=external the default is yes. "
2207                     "With --type=forest the default is no.",
2208                dest='quarantined_arg',
2209                default=None),
2210         Option("--not-transitive", action="store_true",
2211                help="The forest trust is not transitive.",
2212                dest='not_transitive',
2213                default=False),
2214         Option("--treat-as-external", action="store_true",
2215                help="The treat the forest trust as external.",
2216                dest='treat_as_external',
2217                default=False),
2218         Option("--no-aes-keys", action="store_false",
2219                help="The trust uses aes kerberos keys.",
2220                dest='use_aes_keys',
2221                default=True),
2222         Option("--skip-validation", action="store_false",
2223                help="Skip validation of the trust.",
2224                dest='validate',
2225                default=True),
2226        ]
2227
2228     takes_args = ["domain"]
2229
2230     def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2231             trust_type=None, trust_direction=None, create_location=None,
2232             cross_organisation=False, quarantined_arg=None,
2233             not_transitive=False, treat_as_external=False,
2234             use_aes_keys=False, validate=True):
2235
2236         lsaString = lsa.String()
2237
2238         quarantined = False
2239         if quarantined_arg is None:
2240             if trust_type == 'external':
2241                 quarantined = True
2242         elif quarantined_arg == 'yes':
2243             quarantined = True
2244
2245         if trust_type != 'forest':
2246             if not_transitive:
2247                 raise CommandError("--not-transitive requires --type=forest")
2248             if treat_as_external:
2249                 raise CommandError("--treat-as-external requires --type=forest")
2250
2251         enc_types = None
2252         if use_aes_keys:
2253             enc_types = lsa.TrustDomainInfoSupportedEncTypes()
2254             enc_types.enc_types = security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96
2255             enc_types.enc_types |= security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96
2256
2257         local_policy_access =  lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2258         local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2259         local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2260
2261         local_trust_info = lsa.TrustDomainInfoInfoEx()
2262         local_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2263         local_trust_info.trust_direction = 0
2264         if trust_direction == "both":
2265             local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2266             local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2267         elif trust_direction == "incoming":
2268             local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2269         elif trust_direction == "outgoing":
2270             local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2271         local_trust_info.trust_attributes = 0
2272         if cross_organisation:
2273             local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2274         if quarantined:
2275             local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2276         if trust_type == "forest":
2277             local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2278         if not_transitive:
2279             local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2280         if treat_as_external:
2281             local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2282
2283         def get_password(name):
2284             password = None
2285             while True:
2286                 if password is not None and password is not '':
2287                     return password
2288                 password = getpass("New %s Password: " % name)
2289                 passwordverify = getpass("Retype %s Password: " % name)
2290                 if not password == passwordverify:
2291                     password = None
2292                     self.outf.write("Sorry, passwords do not match.\n")
2293
2294         incoming_secret = None
2295         outgoing_secret = None
2296         remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2297         if create_location == "local":
2298             if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2299                 incoming_password = get_password("Incoming Trust")
2300                 incoming_secret = string_to_byte_array(incoming_password.encode('utf-16-le'))
2301             if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2302                 outgoing_password = get_password("Outgoing Trust")
2303                 outgoing_secret = string_to_byte_array(outgoing_password.encode('utf-16-le'))
2304
2305             remote_trust_info = None
2306         else:
2307             # We use 240 random bytes.
2308             # Windows uses 28 or 240 random bytes. I guess it's
2309             # based on the trust type external vs. forest.
2310             #
2311             # The initial trust password can be up to 512 bytes
2312             # while the versioned passwords used for periodic updates
2313             # can only be up to 498 bytes, as netr_ServerPasswordSet2()
2314             # needs to pass the NL_PASSWORD_VERSION structure within the
2315             # 512 bytes and a 2 bytes confounder is required.
2316             #
2317             def random_trust_secret(length):
2318                 pw = samba.generate_random_machine_password(length/2, length/2)
2319                 return string_to_byte_array(pw.encode('utf-16-le'))
2320
2321             if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2322                 incoming_secret = random_trust_secret(240)
2323             if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2324                 outgoing_secret = random_trust_secret(240)
2325
2326             remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2327             remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2328
2329             remote_trust_info = lsa.TrustDomainInfoInfoEx()
2330             remote_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2331             remote_trust_info.trust_direction = 0
2332             if trust_direction == "both":
2333                 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2334                 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2335             elif trust_direction == "incoming":
2336                 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2337             elif trust_direction == "outgoing":
2338                 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2339             remote_trust_info.trust_attributes = 0
2340             if cross_organisation:
2341                 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2342             if quarantined:
2343                 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2344             if trust_type == "forest":
2345                 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2346             if not_transitive:
2347                 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2348             if treat_as_external:
2349                 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2350
2351         local_server = self.setup_local_server(sambaopts, localdcopts)
2352         try:
2353             local_lsa = self.new_local_lsa_connection()
2354         except RuntimeError as error:
2355             raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2356
2357         try:
2358             (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2359         except RuntimeError as error:
2360             raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2361
2362         self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2363                         local_lsa_info.name.string,
2364                         local_lsa_info.dns_domain.string,
2365                         local_lsa_info.sid))
2366
2367         try:
2368             remote_server = self.setup_remote_server(credopts, domain)
2369         except RuntimeError as error:
2370             raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2371
2372         try:
2373             remote_lsa = self.new_remote_lsa_connection()
2374         except RuntimeError as error:
2375             raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2376
2377         try:
2378             (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2379         except RuntimeError as error:
2380             raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2381
2382         self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2383                         remote_lsa_info.name.string,
2384                         remote_lsa_info.dns_domain.string,
2385                         remote_lsa_info.sid))
2386
2387         local_trust_info.domain_name.string = remote_lsa_info.dns_domain.string
2388         local_trust_info.netbios_name.string = remote_lsa_info.name.string
2389         local_trust_info.sid = remote_lsa_info.sid
2390
2391         if remote_trust_info:
2392             remote_trust_info.domain_name.string = local_lsa_info.dns_domain.string
2393             remote_trust_info.netbios_name.string = local_lsa_info.name.string
2394             remote_trust_info.sid = local_lsa_info.sid
2395
2396         try:
2397             lsaString.string = local_trust_info.domain_name.string
2398             local_old_netbios = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2399                                         lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2400             raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2401         except NTSTATUSError as error:
2402             if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2403                 raise self.LocalRuntimeError(self, error,
2404                                 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2405                                 lsaString.string))
2406
2407         try:
2408             lsaString.string = local_trust_info.netbios_name.string
2409             local_old_dns = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2410                                         lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2411             raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2412         except NTSTATUSError as error:
2413             if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2414                 raise self.LocalRuntimeError(self, error,
2415                                 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2416                                 lsaString.string))
2417
2418         if remote_trust_info:
2419             try:
2420                 lsaString.string = remote_trust_info.domain_name.string
2421                 remote_old_netbios = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2422                                             lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2423                 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2424             except NTSTATUSError as error:
2425                 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2426                     raise self.RemoteRuntimeError(self, error,
2427                                     "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2428                                     lsaString.string))
2429
2430             try:
2431                 lsaString.string = remote_trust_info.netbios_name.string
2432                 remote_old_dns = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2433                                             lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2434                 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2435             except NTSTATUSError as error:
2436                 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2437                     raise self.RemoteRuntimeError(self, error,
2438                                     "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2439                                     lsaString.string))
2440
2441         try:
2442             local_netlogon = self.new_local_netlogon_connection()
2443         except RuntimeError as error:
2444             raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2445
2446         try:
2447             local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
2448         except RuntimeError as error:
2449             raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
2450
2451         if remote_trust_info:
2452             try:
2453                 remote_netlogon = self.new_remote_netlogon_connection()
2454             except RuntimeError as error:
2455                 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2456
2457             try:
2458                 remote_netlogon_info = self.get_netlogon_dc_info(remote_netlogon, remote_server)
2459             except RuntimeError as error:
2460                 raise self.RemoteRuntimeError(self, error, "failed to get netlogon dc info")
2461
2462         def generate_AuthInOutBlob(secret, update_time):
2463             if secret is None:
2464                 blob = drsblobs.trustAuthInOutBlob()
2465                 blob.count = 0
2466
2467                 return blob
2468
2469             clear = drsblobs.AuthInfoClear()
2470             clear.size = len(secret)
2471             clear.password = secret
2472
2473             info = drsblobs.AuthenticationInformation()
2474             info.LastUpdateTime = samba.unix2nttime(update_time)
2475             info.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
2476             info.AuthInfo = clear
2477
2478             array = drsblobs.AuthenticationInformationArray()
2479             array.count = 1
2480             array.array = [info]
2481
2482             blob = drsblobs.trustAuthInOutBlob()
2483             blob.count = 1
2484             blob.current = array
2485
2486             return blob
2487
2488         def generate_AuthInfoInternal(session_key, incoming=None, outgoing=None):
2489             confounder = [0] * 512
2490             for i in range(len(confounder)):
2491                 confounder[i] = random.randint(0, 255)
2492
2493             trustpass = drsblobs.trustDomainPasswords()
2494
2495             trustpass.confounder = confounder
2496             trustpass.outgoing = outgoing
2497             trustpass.incoming = incoming
2498
2499             trustpass_blob = ndr_pack(trustpass)
2500
2501             encrypted_trustpass = arcfour_encrypt(session_key, trustpass_blob)
2502
2503             auth_blob = lsa.DATA_BUF2()
2504             auth_blob.size = len(encrypted_trustpass)
2505             auth_blob.data = string_to_byte_array(encrypted_trustpass)
2506
2507             auth_info = lsa.TrustDomainInfoAuthInfoInternal()
2508             auth_info.auth_blob = auth_blob
2509
2510             return auth_info
2511
2512         update_time = samba.current_unix_time()
2513         incoming_blob = generate_AuthInOutBlob(incoming_secret, update_time)
2514         outgoing_blob = generate_AuthInOutBlob(outgoing_secret, update_time)
2515
2516         local_tdo_handle = None
2517         remote_tdo_handle = None
2518
2519         local_auth_info = generate_AuthInfoInternal(local_lsa.session_key,
2520                                                     incoming=incoming_blob,
2521                                                     outgoing=outgoing_blob)
2522         if remote_trust_info:
2523             remote_auth_info = generate_AuthInfoInternal(remote_lsa.session_key,
2524                                                          incoming=outgoing_blob,
2525                                                          outgoing=incoming_blob)
2526
2527         try:
2528             if remote_trust_info:
2529                 self.outf.write("Creating remote TDO.\n")
2530                 current_request = { "location": "remote", "name": "CreateTrustedDomainEx2"}
2531                 remote_tdo_handle = remote_lsa.CreateTrustedDomainEx2(remote_policy,
2532                                                                       remote_trust_info,
2533                                                                       remote_auth_info,
2534                                                                       lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2535                 self.outf.write("Remote TDO created.\n")
2536                 if enc_types:
2537                     self.outf.write("Setting supported encryption types on remote TDO.\n")
2538                     current_request = { "location": "remote", "name": "SetInformationTrustedDomain"}
2539                     remote_lsa.SetInformationTrustedDomain(remote_tdo_handle,
2540                                                            lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2541                                                            enc_types)
2542
2543             self.outf.write("Creating local TDO.\n")
2544             current_request = { "location": "local", "name": "CreateTrustedDomainEx2"}
2545             local_tdo_handle = local_lsa.CreateTrustedDomainEx2(local_policy,
2546                                                                   local_trust_info,
2547                                                                   local_auth_info,
2548                                                                   lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2549             self.outf.write("Local TDO created\n")
2550             if enc_types:
2551                 self.outf.write("Setting supported encryption types on local TDO.\n")
2552                 current_request = { "location": "local", "name": "SetInformationTrustedDomain"}
2553                 local_lsa.SetInformationTrustedDomain(local_tdo_handle,
2554                                                       lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2555                                                       enc_types)
2556         except RuntimeError as error:
2557             self.outf.write("Error: %s failed %sly - cleaning up\n" % (
2558                             current_request['name'], current_request['location']))
2559             if remote_tdo_handle:
2560                 self.outf.write("Deleting remote TDO.\n")
2561                 remote_lsa.DeleteObject(remote_tdo_handle)
2562                 remote_tdo_handle = None
2563             if local_tdo_handle:
2564                 self.outf.write("Deleting local TDO.\n")
2565                 local_lsa.DeleteObject(local_tdo_handle)
2566                 local_tdo_handle = None
2567             if current_request['location'] is "remote":
2568                 raise self.RemoteRuntimeError(self, error, "%s" % (
2569                                               current_request['name']))
2570             raise self.LocalRuntimeError(self, error, "%s" % (
2571                                          current_request['name']))
2572
2573         if validate:
2574             if local_trust_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2575                 self.outf.write("Setup local forest trust information...\n")
2576                 try:
2577                     # get all information about the remote trust
2578                     # this triggers netr_GetForestTrustInformation to the remote domain
2579                     # and lsaRSetForestTrustInformation() locally, but new top level
2580                     # names are disabled by default.
2581                     local_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
2582                                                                   remote_lsa_info.dns_domain.string,
2583                                                                   netlogon.DS_GFTI_UPDATE_TDO)
2584                 except RuntimeError as error:
2585                     raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2586
2587                 try:
2588                     # here we try to enable all top level names
2589                     local_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
2590                                                                   remote_lsa_info.dns_domain,
2591                                                                   lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2592                                                                   local_forest_info,
2593                                                                   0)
2594                 except RuntimeError as error:
2595                     raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2596
2597                 self.write_forest_trust_info(local_forest_info,
2598                                              tln=remote_lsa_info.dns_domain.string,
2599                                              collisions=local_forest_collision)
2600
2601                 if remote_trust_info:
2602                     self.outf.write("Setup remote forest trust information...\n")
2603                     try:
2604                         # get all information about the local trust (from the perspective of the remote domain)
2605                         # this triggers netr_GetForestTrustInformation to our domain.
2606                         # and lsaRSetForestTrustInformation() remotely, but new top level
2607                         # names are disabled by default.
2608                         remote_forest_info = remote_netlogon.netr_DsRGetForestTrustInformation(remote_netlogon_info.dc_unc,
2609                                                                       local_lsa_info.dns_domain.string,
2610                                                                       netlogon.DS_GFTI_UPDATE_TDO)
2611                     except RuntimeError as error:
2612                         raise self.RemoteRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2613
2614                     try:
2615                         # here we try to enable all top level names
2616                         remote_forest_collision = remote_lsa.lsaRSetForestTrustInformation(remote_policy,
2617                                                                       local_lsa_info.dns_domain,
2618                                                                       lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2619                                                                       remote_forest_info,
2620                                                                       0)
2621                     except RuntimeError as error:
2622                         raise self.RemoteRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2623
2624                     self.write_forest_trust_info(remote_forest_info,
2625                                                  tln=local_lsa_info.dns_domain.string,
2626                                                  collisions=remote_forest_collision)
2627
2628             if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2629                 self.outf.write("Validating outgoing trust...\n")
2630                 try:
2631                     local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_netlogon_info.dc_unc,
2632                                                                       netlogon.NETLOGON_CONTROL_TC_VERIFY,
2633                                                                       2,
2634                                                                       remote_lsa_info.dns_domain.string)
2635                 except RuntimeError as error:
2636                     raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2637
2638                 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2639                 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2640
2641                 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2642                     local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2643                                        local_trust_verify.trusted_dc_name,
2644                                        local_trust_verify.tc_connection_status[1],
2645                                        local_trust_verify.pdc_connection_status[1])
2646                 else:
2647                     local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2648                                        local_trust_verify.trusted_dc_name,
2649                                        local_trust_verify.tc_connection_status[1],
2650                                        local_trust_verify.pdc_connection_status[1])
2651
2652                 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2653                     raise CommandError(local_validation)
2654                 else:
2655                     self.outf.write("OK: %s\n" % local_validation)
2656
2657             if remote_trust_info:
2658                 if remote_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2659                     self.outf.write("Validating incoming trust...\n")
2660                     try:
2661                         remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_netlogon_info.dc_unc,
2662                                                                       netlogon.NETLOGON_CONTROL_TC_VERIFY,
2663                                                                       2,
2664                                                                       local_lsa_info.dns_domain.string)
2665                     except RuntimeError as error:
2666                         raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2667
2668                     remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2669                     remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2670
2671                     if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2672                         remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2673                                            remote_trust_verify.trusted_dc_name,
2674                                            remote_trust_verify.tc_connection_status[1],
2675                                            remote_trust_verify.pdc_connection_status[1])
2676                     else:
2677                         remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2678                                            remote_trust_verify.trusted_dc_name,
2679                                            remote_trust_verify.tc_connection_status[1],
2680                                            remote_trust_verify.pdc_connection_status[1])
2681
2682                     if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
2683                         raise CommandError(remote_validation)
2684                     else:
2685                         self.outf.write("OK: %s\n" % remote_validation)
2686
2687         if remote_tdo_handle is not None:
2688             try:
2689                 remote_lsa.Close(remote_tdo_handle)
2690             except RuntimeError as error:
2691                 pass
2692             remote_tdo_handle = None
2693         if local_tdo_handle is not None:
2694             try:
2695                 local_lsa.Close(local_tdo_handle)
2696             except RuntimeError as error:
2697                 pass
2698             local_tdo_handle = None
2699
2700         self.outf.write("Success.\n")
2701         return
2702
2703 class cmd_domain_trust_delete(DomainTrustCommand):
2704     """Delete a domain trust."""
2705
2706     synopsis = "%prog DOMAIN [options]"
2707
2708     takes_optiongroups = {
2709         "sambaopts": options.SambaOptions,
2710         "versionopts": options.VersionOptions,
2711         "credopts": options.CredentialsOptions,
2712         "localdcopts": LocalDCCredentialsOptions,
2713     }
2714
2715     takes_options = [
2716         Option("--delete-location", type="choice", metavar="LOCATION",
2717                choices=["local", "both"],
2718                help="Where to delete the trusted domain object: 'local' or 'both'.",
2719                dest='delete_location',
2720                default="both"),
2721        ]
2722
2723     takes_args = ["domain"]
2724
2725     def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2726             delete_location=None):
2727
2728         local_policy_access =  lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2729         local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2730         local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2731
2732         if delete_location == "local":
2733             remote_policy_access = None
2734         else:
2735             remote_policy_access =  lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2736             remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2737             remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2738
2739         local_server = self.setup_local_server(sambaopts, localdcopts)
2740         try:
2741             local_lsa = self.new_local_lsa_connection()
2742         except RuntimeError as error:
2743             raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2744
2745         try:
2746             (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2747         except RuntimeError as error:
2748             raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2749
2750         self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2751                         local_lsa_info.name.string,
2752                         local_lsa_info.dns_domain.string,
2753                         local_lsa_info.sid))
2754
2755         local_tdo_info = None
2756         local_tdo_handle = None
2757         remote_tdo_info = None
2758         remote_tdo_handle = None
2759
2760         lsaString = lsa.String()
2761         try:
2762             lsaString.string = domain
2763             local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2764                                         lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2765         except NTSTATUSError as error:
2766             if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2767                 raise CommandError("Failed to find trust for domain '%s'" % domain)
2768             raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2769
2770
2771         if remote_policy_access is not None:
2772             try:
2773                 remote_server = self.setup_remote_server(credopts, domain)
2774             except RuntimeError as error:
2775                 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2776
2777             try:
2778                 remote_lsa = self.new_remote_lsa_connection()
2779             except RuntimeError as error:
2780                 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2781
2782             try:
2783                 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2784             except RuntimeError as error:
2785                 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2786
2787             self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2788                             remote_lsa_info.name.string,
2789                             remote_lsa_info.dns_domain.string,
2790                             remote_lsa_info.sid))
2791
2792             if remote_lsa_info.sid != local_tdo_info.sid or \
2793                remote_lsa_info.name.string != local_tdo_info.netbios_name.string or \
2794                remote_lsa_info.dns_domain.string != local_tdo_info.domain_name.string:
2795                 raise CommandError("LocalTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2796                                    local_tdo_info.netbios_name.string,
2797                                    local_tdo_info.domain_name.string,
2798                                    local_tdo_info.sid))
2799
2800             try:
2801                 lsaString.string = local_lsa_info.dns_domain.string
2802                 remote_tdo_info = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2803                                             lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2804             except NTSTATUSError as error:
2805                 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2806                     raise self.RemoteRuntimeError(self, error, "QueryTrustedDomainInfoByName(%s)" % (
2807                                                   lsaString.string))
2808                 pass
2809
2810             if remote_tdo_info is not None:
2811                 if local_lsa_info.sid != remote_tdo_info.sid or \
2812                    local_lsa_info.name.string != remote_tdo_info.netbios_name.string or \
2813                    local_lsa_info.dns_domain.string != remote_tdo_info.domain_name.string:
2814                     raise CommandError("RemoteTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2815                                        remote_tdo_info.netbios_name.string,
2816                                        remote_tdo_info.domain_name.string,
2817                                        remote_tdo_info.sid))
2818
2819         if local_tdo_info is not None:
2820             try:
2821                 lsaString.string = local_tdo_info.domain_name.string
2822                 local_tdo_handle = local_lsa.OpenTrustedDomainByName(local_policy,
2823                                                                      lsaString,
2824                                                                      security.SEC_STD_DELETE)
2825             except RuntimeError as error:
2826                 raise self.LocalRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2827                                              lsaString.string))
2828
2829             local_lsa.DeleteObject(local_tdo_handle)
2830             local_tdo_handle = None
2831
2832         if remote_tdo_info is not None:
2833             try:
2834                 lsaString.string = remote_tdo_info.domain_name.string
2835                 remote_tdo_handle = remote_lsa.OpenTrustedDomainByName(remote_policy,
2836                                                                        lsaString,
2837                                                                        security.SEC_STD_DELETE)
2838             except RuntimeError as error:
2839                 raise self.RemoteRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2840                                               lsaString.string))
2841
2842         if remote_tdo_handle is not None:
2843             try:
2844                 remote_lsa.DeleteObject(remote_tdo_handle)
2845                 remote_tdo_handle = None
2846                 self.outf.write("RemoteTDO deleted.\n")
2847             except RuntimeError as error:
2848                 self.outf.write("%s\n" % self.RemoteRuntimeError(self, error, "DeleteObject() failed"))
2849
2850         if local_tdo_handle is not None:
2851             try:
2852                 local_lsa.DeleteObject(local_tdo_handle)
2853                 local_tdo_handle = None
2854                 self.outf.write("LocalTDO deleted.\n")
2855             except RuntimeError as error:
2856                 self.outf.write("%s\n" % self.LocalRuntimeError(self, error, "DeleteObject() failed"))
2857
2858         return
2859
2860 class cmd_domain_trust_validate(DomainTrustCommand):
2861     """Validate a domain trust."""
2862
2863     synopsis = "%prog DOMAIN [options]"
2864
2865     takes_optiongroups = {
2866         "sambaopts": options.SambaOptions,
2867         "versionopts": options.VersionOptions,
2868         "credopts": options.CredentialsOptions,
2869         "localdcopts": LocalDCCredentialsOptions,
2870     }
2871
2872     takes_options = [
2873         Option("--validate-location", type="choice", metavar="LOCATION",
2874                choices=["local", "both"],
2875                help="Where to validate the trusted domain object: 'local' or 'both'.",
2876                dest='validate_location',
2877                default="both"),
2878        ]
2879
2880     takes_args = ["domain"]
2881
2882     def run(self, domain, sambaopts=None, versionopts=None, credopts=None, localdcopts=None,
2883             validate_location=None):
2884
2885         local_policy_access =  lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2886
2887         local_server = self.setup_local_server(sambaopts, localdcopts)
2888         try:
2889             local_lsa = self.new_local_lsa_connection()
2890         except RuntimeError as error:
2891             raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2892
2893         try:
2894             (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2895         except RuntimeError as error:
2896             raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2897
2898         self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2899                         local_lsa_info.name.string,
2900                         local_lsa_info.dns_domain.string,
2901                         local_lsa_info.sid))
2902
2903         try:
2904             lsaString = lsa.String()
2905             lsaString.string = domain
2906             local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2907                                         lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2908         except NTSTATUSError as error:
2909             if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2910                 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2911
2912             raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
2913
2914         self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
2915                         local_tdo_info.netbios_name.string,
2916                         local_tdo_info.domain_name.string,
2917                         local_tdo_info.sid))
2918
2919         try:
2920             local_netlogon = self.new_local_netlogon_connection()
2921         except RuntimeError as error:
2922             raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2923
2924         try:
2925             local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_server,
2926                                                                  netlogon.NETLOGON_CONTROL_TC_VERIFY,
2927                                                                  2,
2928                                                                  local_tdo_info.domain_name.string)
2929         except RuntimeError as error:
2930             raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2931
2932         local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2933         local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2934
2935         if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2936             local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2937                                local_trust_verify.trusted_dc_name,
2938                                local_trust_verify.tc_connection_status[1],
2939                                local_trust_verify.pdc_connection_status[1])
2940         else:
2941             local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2942                                local_trust_verify.trusted_dc_name,
2943                                local_trust_verify.tc_connection_status[1],
2944                                local_trust_verify.pdc_connection_status[1])
2945
2946         if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2947             raise CommandError(local_validation)
2948         else:
2949             self.outf.write("OK: %s\n" % local_validation)
2950
2951         try:
2952             server = local_trust_verify.trusted_dc_name.replace('\\', '')
2953             domain_and_server = "%s\\%s" % (local_tdo_info.domain_name.string, server)
2954             local_trust_rediscover = local_netlogon.netr_LogonControl2Ex(local_server,
2955                                                                  netlogon.NETLOGON_CONTROL_REDISCOVER,
2956                                                                  2,
2957                                                                  domain_and_server)
2958         except RuntimeError as error:
2959             raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
2960
2961         local_conn_status = self._uint32(local_trust_rediscover.tc_connection_status[0])
2962         local_rediscover = "LocalRediscover: DC[%s] CONNECTION[%s]" % (
2963                                local_trust_rediscover.trusted_dc_name,
2964                                local_trust_rediscover.tc_connection_status[1])
2965
2966         if local_conn_status != werror.WERR_SUCCESS:
2967             raise CommandError(local_rediscover)
2968         else:
2969             self.outf.write("OK: %s\n" % local_rediscover)
2970
2971         if validate_location != "local":
2972             try:
2973                 remote_server = self.setup_remote_server(credopts, domain, require_pdc=False)
2974             except RuntimeError as error:
2975                 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2976
2977             try:
2978                 remote_netlogon = self.new_remote_netlogon_connection()
2979             except RuntimeError as error:
2980                 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2981
2982             try:
2983                 remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_server,
2984                                                                   netlogon.NETLOGON_CONTROL_TC_VERIFY,
2985                                                                   2,
2986                                                                   local_lsa_info.dns_domain.string)
2987             except RuntimeError as error:
2988                 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2989
2990             remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2991             remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2992
2993             if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2994                 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2995                                    remote_trust_verify.trusted_dc_name,
2996                                    remote_trust_verify.tc_connection_status[1],
2997                                    remote_trust_verify.pdc_connection_status[1])
2998             else:
2999                 remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
3000                                    remote_trust_verify.trusted_dc_name,
3001                                    remote_trust_verify.tc_connection_status[1],
3002                                    remote_trust_verify.pdc_connection_status[1])
3003
3004             if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
3005                 raise CommandError(remote_validation)
3006             else:
3007                 self.outf.write("OK: %s\n" % remote_validation)
3008
3009             try:
3010                 server = remote_trust_verify.trusted_dc_name.replace('\\', '')
3011                 domain_and_server = "%s\\%s" % (local_lsa_info.dns_domain.string, server)
3012                 remote_trust_rediscover = remote_netlogon.netr_LogonControl2Ex(remote_server,
3013                                                                      netlogon.NETLOGON_CONTROL_REDISCOVER,
3014                                                                      2,
3015                                                                      domain_and_server)
3016             except RuntimeError as error:
3017                 raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_REDISCOVER failed")
3018
3019             remote_conn_status = self._uint32(remote_trust_rediscover.tc_connection_status[0])
3020
3021             remote_rediscover = "RemoteRediscover: DC[%s] CONNECTION[%s]" % (
3022                                    remote_trust_rediscover.trusted_dc_name,
3023                                    remote_trust_rediscover.tc_connection_status[1])
3024
3025             if remote_conn_status != werror.WERR_SUCCESS:
3026                 raise CommandError(remote_rediscover)
3027             else:
3028                 self.outf.write("OK: %s\n" % remote_rediscover)
3029
3030         return
3031
3032 class cmd_domain_trust_namespaces(DomainTrustCommand):
3033     """Manage forest trust namespaces."""
3034
3035     synopsis = "%prog [DOMAIN] [options]"
3036
3037     takes_optiongroups = {
3038         "sambaopts": options.SambaOptions,
3039         "versionopts": options.VersionOptions,
3040         "localdcopts": LocalDCCredentialsOptions,
3041     }
3042
3043     takes_options = [
3044         Option("--refresh", type="choice", metavar="check|store",
3045                choices=["check", "store", None],
3046                help="List and maybe store refreshed forest trust information: 'check' or 'store'.",
3047                dest='refresh',
3048                default=None),
3049         Option("--enable-all", action="store_true",
3050                help="Try to update disabled entries, not allowed with --refresh=check.",
3051                dest='enable_all',
3052                default=False),
3053         Option("--enable-tln", action="append", metavar='DNSDOMAIN',
3054                help="Enable a top level name entry. Can be specified multiple times.",
3055                dest='enable_tln',
3056                default=[]),
3057         Option("--disable-tln", action="append", metavar='DNSDOMAIN',
3058                help="Disable a top level name entry. Can be specified multiple times.",
3059                dest='disable_tln',
3060                default=[]),
3061         Option("--add-tln-ex", action="append", metavar='DNSDOMAIN',
3062                help="Add a top level exclusion entry. Can be specified multiple times.",
3063                dest='add_tln_ex',
3064                default=[]),
3065         Option("--delete-tln-ex", action="append", metavar='DNSDOMAIN',
3066                help="Delete a top level exclusion entry. Can be specified multiple times.",
3067                dest='delete_tln_ex',
3068                default=[]),
3069         Option("--enable-nb", action="append", metavar='NETBIOSDOMAIN',
3070                help="Enable a netbios name in a domain entry. Can be specified multiple times.",
3071                dest='enable_nb',
3072                default=[]),
3073         Option("--disable-nb", action="append", metavar='NETBIOSDOMAIN',
3074                help="Disable a netbios name in a domain entry. Can be specified multiple times.",
3075                dest='disable_nb',
3076                default=[]),
3077         Option("--enable-sid", action="append", metavar='DOMAINSID',
3078                help="Enable a SID in a domain entry. Can be specified multiple times.",
3079                dest='enable_sid_str',
3080                default=[]),
3081         Option("--disable-sid", action="append", metavar='DOMAINSID',
3082                help="Disable a SID in a domain entry. Can be specified multiple times.",
3083                dest='disable_sid_str',
3084                default=[]),
3085         Option("--add-upn-suffix", action="append", metavar='DNSDOMAIN',
3086                help="Add a new uPNSuffixes attribute for the local forest. Can be specified multiple times.",
3087                dest='add_upn',
3088                default=[]),
3089         Option("--delete-upn-suffix", action="append", metavar='DNSDOMAIN',
3090                help="Delete an existing uPNSuffixes attribute of the local forest. Can be specified multiple times.",
3091                dest='delete_upn',
3092                default=[]),
3093         Option("--add-spn-suffix", action="append", metavar='DNSDOMAIN',
3094                help="Add a new msDS-SPNSuffixes attribute for the local forest. Can be specified multiple times.",
3095                dest='add_spn',
3096                default=[]),
3097         Option("--delete-spn-suffix", action="append", metavar='DNSDOMAIN',
3098                help="Delete an existing msDS-SPNSuffixes attribute of the local forest. Can be specified multiple times.",
3099                dest='delete_spn',
3100                default=[]),
3101        ]
3102
3103     takes_args = ["domain?"]
3104
3105     def run(self, domain=None, sambaopts=None, localdcopts=None, versionopts=None,
3106             refresh=None, enable_all=False,
3107             enable_tln=[], disable_tln=[], add_tln_ex=[], delete_tln_ex=[],
3108             enable_sid_str=[], disable_sid_str=[], enable_nb=[], disable_nb=[],
3109             add_upn=[], delete_upn=[], add_spn=[], delete_spn=[]):
3110
3111         require_update = False
3112
3113         if domain is None:
3114             if refresh == "store":
3115                 raise CommandError("--refresh=%s not allowed without DOMAIN" % refresh)
3116
3117             if enable_all:
3118                 raise CommandError("--enable-all not allowed without DOMAIN")
3119
3120             if len(enable_tln) > 0:
3121                 raise CommandError("--enable-tln not allowed without DOMAIN")
3122             if len(disable_tln) > 0:
3123                 raise CommandError("--disable-tln not allowed without DOMAIN")
3124
3125             if len(add_tln_ex) > 0:
3126                 raise CommandError("--add-tln-ex not allowed without DOMAIN")
3127             if len(delete_tln_ex) > 0:
3128                 raise CommandError("--delete-tln-ex not allowed without DOMAIN")
3129
3130             if len(enable_nb) > 0:
3131                 raise CommandError("--enable-nb not allowed without DOMAIN")
3132             if len(disable_nb) > 0:
3133                 raise CommandError("--disable-nb not allowed without DOMAIN")
3134
3135             if len(enable_sid_str) > 0:
3136                 raise CommandError("--enable-sid not allowed without DOMAIN")
3137             if len(disable_sid_str) > 0:
3138                 raise CommandError("--disable-sid not allowed without DOMAIN")
3139
3140             if len(add_upn) > 0:
3141                 for n in add_upn:
3142                     if not n.startswith("*."):
3143                         continue
3144                     raise CommandError("value[%s] specified for --add-upn-suffix should not include with '*.'" % n)
3145                 require_update = True
3146             if len(delete_upn) > 0:
3147                 for n in delete_upn:
3148                     if not n.startswith("*."):
3149                         continue
3150                     raise CommandError("value[%s] specified for --delete-upn-suffix should not include with '*.'" % n)
3151                 require_update = True
3152             for a in add_upn:
3153                 for d in delete_upn:
3154                     if a.lower() != d.lower():
3155                         continue
3156                     raise CommandError("value[%s] specified for --add-upn-suffix and --delete-upn-suffix" % a)
3157
3158             if len(add_spn) > 0:
3159                 for n in add_spn:
3160                     if not n.startswith("*."):
3161                         continue
3162                     raise CommandError("value[%s] specified for --add-spn-suffix should not include with '*.'" % n)
3163                 require_update = True
3164             if len(delete_spn) > 0:
3165                 for n in delete_spn:
3166                     if not n.startswith("*."):
3167                         continue
3168                     raise CommandError("value[%s] specified for --delete-spn-suffix should not include with '*.'" % n)
3169                 require_update = True
3170             for a in add_spn:
3171                 for d in delete_spn:
3172                     if a.lower() != d.lower():
3173                         continue
3174                     raise CommandError("value[%s] specified for --add-spn-suffix and --delete-spn-suffix" % a)
3175         else:
3176             if len(add_upn) > 0:
3177                 raise CommandError("--add-upn-suffix not allowed together with DOMAIN")
3178             if len(delete_upn) > 0:
3179                 raise CommandError("--delete-upn-suffix not allowed together with DOMAIN")
3180             if len(add_spn) > 0:
3181                 raise CommandError("--add-spn-suffix not allowed together with DOMAIN")
3182             if len(delete_spn) > 0:
3183                 raise CommandError("--delete-spn-suffix not allowed together with DOMAIN")
3184
3185         if refresh is not None:
3186             if refresh == "store":
3187                 require_update = True
3188
3189             if enable_all and refresh != "store":
3190                 raise CommandError("--enable-all not allowed together with --refresh=%s" % refresh)
3191
3192             if len(enable_tln) > 0:
3193                 raise CommandError("--enable-tln not allowed together with --refresh")
3194             if len(disable_tln) > 0:
3195                 raise CommandError("--disable-tln not allowed together with --refresh")
3196
3197             if len(add_tln_ex) > 0:
3198                 raise CommandError("--add-tln-ex not allowed together with --refresh")
3199             if len(delete_tln_ex) > 0:
3200                 raise CommandError("--delete-tln-ex not allowed together with --refresh")
3201
3202             if len(enable_nb) > 0:
3203                 raise CommandError("--enable-nb not allowed together with --refresh")
3204             if len(disable_nb) > 0:
3205                 raise CommandError("--disable-nb not allowed together with --refresh")
3206
3207             if len(enable_sid_str) > 0:
3208                 raise CommandError("--enable-sid not allowed together with --refresh")
3209             if len(disable_sid_str) > 0:
3210                 raise CommandError("--disable-sid not allowed together with --refresh")
3211         else:
3212             if enable_all:
3213                 require_update = True
3214
3215                 if len(enable_tln) > 0:
3216                     raise CommandError("--enable-tln not allowed together with --enable-all")
3217
3218                 if len(enable_nb) > 0:
3219                     raise CommandError("--enable-nb not allowed together with --enable-all")
3220
3221                 if len(enable_sid_str) > 0:
3222                     raise CommandError("--enable-sid not allowed together with --enable-all")
3223
3224             if len(enable_tln) > 0:
3225                 require_update = True
3226             if len(disable_tln) > 0:
3227                 require_update = True
3228             for e in enable_tln:
3229                 for d in disable_tln:
3230                     if e.lower() != d.lower():
3231                         continue
3232                     raise CommandError("value[%s] specified for --enable-tln and --disable-tln" % e)
3233
3234             if len(add_tln_ex) > 0:
3235                 for n in add_tln_ex:
3236                     if not n.startswith("*."):
3237                         continue
3238                     raise CommandError("value[%s] specified for --add-tln-ex should not include with '*.'" % n)
3239                 require_update = True
3240             if len(delete_tln_ex) > 0:
3241                 for n in delete_tln_ex:
3242                     if not n.startswith("*."):
3243                         continue
3244                     raise CommandError("value[%s] specified for --delete-tln-ex should not include with '*.'" % n)
3245                 require_update = True
3246             for a in add_tln_ex:
3247                 for d in delete_tln_ex:
3248                     if a.lower() != d.lower():
3249                         continue
3250                     raise CommandError("value[%s] specified for --add-tln-ex and --delete-tln-ex" % a)
3251
3252             if len(enable_nb) > 0:
3253                 require_update = True
3254             if len(disable_nb) > 0:
3255                 require_update = True
3256             for e in enable_nb:
3257                 for d in disable_nb:
3258                     if e.upper() != d.upper():
3259                         continue
3260                     raise CommandError("value[%s] specified for --enable-nb and --disable-nb" % e)
3261
3262             enable_sid = []
3263             for s in enable_sid_str:
3264                 try:
3265                     sid = security.dom_sid(s)
3266                 except TypeError as error:
3267                     raise CommandError("value[%s] specified for --enable-sid is not a valid SID" % s)
3268                 enable_sid.append(sid)
3269             disable_sid = []
3270             for s in disable_sid_str:
3271                 try:
3272                     sid = security.dom_sid(s)
3273                 except TypeError as error:
3274                     raise CommandError("value[%s] specified for --disable-sid is not a valid SID" % s)
3275                 disable_sid.append(sid)
3276             if len(enable_sid) > 0:
3277                 require_update = True
3278             if len(disable_sid) > 0:
3279                 require_update = True
3280             for e in enable_sid:
3281                 for d in disable_sid:
3282                     if e != d:
3283                         continue
3284                     raise CommandError("value[%s] specified for --enable-sid and --disable-sid" % e)
3285
3286         local_policy_access =  lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
3287         if require_update:
3288             local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
3289
3290         local_server = self.setup_local_server(sambaopts, localdcopts)
3291         try:
3292             local_lsa = self.new_local_lsa_connection()
3293         except RuntimeError as error:
3294             raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
3295
3296         try:
3297             (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
3298         except RuntimeError as error:
3299             raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
3300
3301         self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
3302                         local_lsa_info.name.string,
3303                         local_lsa_info.dns_domain.string,
3304                         local_lsa_info.sid))
3305
3306         if domain is None:
3307             try:
3308                 local_netlogon = self.new_local_netlogon_connection()
3309             except RuntimeError as error:
3310                 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3311
3312             try:
3313                 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3314             except RuntimeError as error:
3315                 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3316
3317             if local_netlogon_info.domain_name != local_netlogon_info.forest_name:
3318                 raise CommandError("The local domain [%s] is not the forest root [%s]" % (
3319                                    local_netlogon_info.domain_name,
3320                                    local_netlogon_info.forest_name))
3321
3322             try:
3323                 # get all information about our own forest
3324                 own_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3325                                                                                    None, 0)
3326             except RuntimeError as error:
3327                 if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
3328                     raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3329                                        self.local_server))
3330
3331                 if self.check_runtime_error(error, werror.WERR_INVALID_FUNCTION):
3332                     raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3333                                        self.local_server))
3334
3335                 if self.check_runtime_error(error, werror.WERR_NERR_ACFNOTLOADED):
3336                     raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3337                                        self.local_server))
3338
3339                 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3340
3341             self.outf.write("Own forest trust information...\n")
3342             self.write_forest_trust_info(own_forest_info,
3343                                          tln=local_lsa_info.dns_domain.string)
3344
3345             try:
3346                 local_samdb = self.new_local_ldap_connection()
3347             except RuntimeError as error:
3348                 raise self.LocalRuntimeError(self, error, "failed to connect to SamDB")
3349
3350             local_partitions_dn = "CN=Partitions,%s" % str(local_samdb.get_config_basedn())
3351             attrs = ['uPNSuffixes', 'msDS-SPNSuffixes']
3352             try:
3353                 msgs = local_samdb.search(base=local_partitions_dn,
3354                                           scope=ldb.SCOPE_BASE,
3355                                           expression="(objectClass=crossRefContainer)",
3356                                           attrs=attrs)
3357                 stored_msg = msgs[0]
3358             except ldb.LdbError as error:
3359                 raise self.LocalLdbError(self, error, "failed to search partition dn")
3360
3361             stored_upn_vals = []
3362             if 'uPNSuffixes' in stored_msg:
3363                 stored_upn_vals.extend(stored_msg['uPNSuffixes'])
3364
3365             stored_spn_vals = []
3366             if 'msDS-SPNSuffixes' in stored_msg:
3367                 stored_spn_vals.extend(stored_msg['msDS-SPNSuffixes'])
3368
3369             self.outf.write("Stored uPNSuffixes attributes[%d]:\n" % len(stored_upn_vals))
3370             for v in stored_upn_vals:
3371                   self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3372             self.outf.write("Stored msDS-SPNSuffixes attributes[%d]:\n" % len(stored_spn_vals))
3373             for v in stored_spn_vals:
3374                   self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3375
3376             if not require_update:
3377                 return
3378
3379             replace_upn = False
3380             update_upn_vals = []
3381             update_upn_vals.extend(stored_upn_vals)
3382
3383             replace_spn = False
3384             update_spn_vals = []
3385             update_spn_vals.extend(stored_spn_vals)
3386
3387             for upn in add_upn:
3388                 idx = None
3389                 for i in xrange(0, len(update_upn_vals)):
3390                     v = update_upn_vals[i]
3391                     if v.lower() != upn.lower():
3392                         continue
3393                     idx = i
3394                     break
3395                 if idx is not None:
3396                     raise CommandError("Entry already present for value[%s] specified for --add-upn-suffix" % upn)
3397                 update_upn_vals.append(upn)
3398                 replace_upn = True
3399
3400             for upn in delete_upn:
3401                 idx = None
3402                 for i in xrange(0, len(update_upn_vals)):
3403                     v = update_upn_vals[i]
3404                     if v.lower() != upn.lower():
3405                         continue
3406                     idx = i
3407                     break
3408                 if idx is None:
3409                     raise CommandError("Entry not found for value[%s] specified for --delete-upn-suffix" % upn)
3410
3411                 update_upn_vals.pop(idx)
3412                 replace_upn = True
3413
3414             for spn in add_spn:
3415                 idx = None
3416                 for i in xrange(0, len(update_spn_vals)):
3417                     v = update_spn_vals[i]
3418                     if v.lower() != spn.lower():
3419                         continue
3420                     idx = i
3421                     break
3422                 if idx is not None:
3423                     raise CommandError("Entry already present for value[%s] specified for --add-spn-suffix" % spn)
3424                 update_spn_vals.append(spn)
3425                 replace_spn = True
3426
3427             for spn in delete_spn:
3428                 idx = None
3429                 for i in xrange(0, len(update_spn_vals)):
3430                     v = update_spn_vals[i]
3431                     if v.lower() != spn.lower():
3432                         continue
3433                     idx = i
3434                     break
3435                 if idx is None:
3436                     raise CommandError("Entry not found for value[%s] specified for --delete-spn-suffix" % spn)
3437
3438                 update_spn_vals.pop(idx)
3439                 replace_spn = True
3440
3441             self.outf.write("Update uPNSuffixes attributes[%d]:\n" % len(update_upn_vals))
3442             for v in update_upn_vals:
3443                   self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3444             self.outf.write("Update msDS-SPNSuffixes attributes[%d]:\n" % len(update_spn_vals))
3445             for v in update_spn_vals:
3446                   self.outf.write("TLN: %-32s DNS[*.%s]\n" % ("", v))
3447
3448             update_msg = ldb.Message()
3449             update_msg.dn = stored_msg.dn
3450
3451             if replace_upn:
3452                 update_msg['uPNSuffixes'] = ldb.MessageElement(update_upn_vals,
3453                                                                     ldb.FLAG_MOD_REPLACE,
3454                                                                     'uPNSuffixes')
3455             if replace_spn:
3456                 update_msg['msDS-SPNSuffixes'] = ldb.MessageElement(update_spn_vals,
3457                                                                     ldb.FLAG_MOD_REPLACE,
3458                                                                     'msDS-SPNSuffixes')
3459             try:
3460                 local_samdb.modify(update_msg)
3461             except ldb.LdbError as error:
3462                 raise self.LocalLdbError(self, error, "failed to update partition dn")
3463
3464             try:
3465                 stored_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3466                                                                                        None, 0)
3467             except RuntimeError as error:
3468                 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3469
3470             self.outf.write("Stored forest trust information...\n")
3471             self.write_forest_trust_info(stored_forest_info,
3472                                          tln=local_lsa_info.dns_domain.string)
3473             return
3474
3475         try:
3476             lsaString = lsa.String()
3477             lsaString.string = domain
3478             local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
3479                                         lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
3480         except NTSTATUSError as error:
3481             if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
3482                 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
3483
3484             raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(INFO_EX) failed")
3485
3486         self.outf.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
3487                         local_tdo_info.netbios_name.string,
3488                         local_tdo_info.domain_name.string,
3489                         local_tdo_info.sid))
3490
3491         if not local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
3492             raise CommandError("trusted domain object for domain [%s] is not marked as FOREST_TRANSITIVE." % domain)
3493
3494         if refresh is not None:
3495             try:
3496                 local_netlogon = self.new_local_netlogon_connection()
3497             except RuntimeError as error:
3498                 raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
3499
3500             try:
3501                 local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
3502             except RuntimeError as error:
3503                 raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
3504
3505             lsa_update_check = 1
3506             if refresh == "store":
3507                 netlogon_update_tdo = netlogon.DS_GFTI_UPDATE_TDO
3508                 if enable_all:
3509                     lsa_update_check = 0
3510             else:
3511                 netlogon_update_tdo = 0
3512
3513             try:
3514                 # get all information about the remote trust
3515                 # this triggers netr_GetForestTrustInformation to the remote domain
3516                 # and lsaRSetForestTrustInformation() locally, but new top level
3517                 # names are disabled by default.
3518                 fresh_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
3519                                                               local_tdo_info.domain_name.string,
3520                                                               netlogon_update_tdo)
3521             except RuntimeError as error:
3522                 raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
3523
3524             try:
3525                 fresh_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3526                                                               local_tdo_info.domain_name,
3527                                                               lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3528                                                               fresh_forest_info,
3529                                                               lsa_update_check)
3530             except RuntimeError as error:
3531                 raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3532
3533             self.outf.write("Fresh forest trust information...\n")
3534             self.write_forest_trust_info(fresh_forest_info,
3535                                          tln=local_tdo_info.domain_name.string,
3536                                          collisions=fresh_forest_collision)
3537
3538             if refresh == "store":
3539                 try:
3540                     lsaString = lsa.String()
3541                     lsaString.string = local_tdo_info.domain_name.string
3542                     stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3543                                                                   lsaString,
3544                                                                   lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3545                 except RuntimeError as error:
3546                     raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3547
3548                 self.outf.write("Stored forest trust information...\n")
3549                 self.write_forest_trust_info(stored_forest_info,
3550                                              tln=local_tdo_info.domain_name.string)
3551
3552             return
3553
3554         #
3555         # The none --refresh path
3556         #
3557
3558         try:
3559             lsaString = lsa.String()
3560             lsaString.string = local_tdo_info.domain_name.string
3561             local_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3562                                                       lsaString,
3563                                                       lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3564         except RuntimeError as error:
3565             raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3566
3567         self.outf.write("Local forest trust information...\n")
3568         self.write_forest_trust_info(local_forest_info,
3569                                      tln=local_tdo_info.domain_name.string)
3570
3571         if not require_update:
3572             return
3573
3574         entries = []
3575         entries.extend(local_forest_info.entries)
3576         update_forest_info = lsa.ForestTrustInformation()
3577         update_forest_info.count = len(entries)
3578         update_forest_info.entries = entries
3579
3580         if enable_all:
3581             for i in xrange(0, len(update_forest_info.entries)):
3582                 r = update_forest_info.entries[i]
3583                 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3584                     continue
3585                 if update_forest_info.entries[i].flags == 0:
3586                     continue
3587                 update_forest_info.entries[i].time = 0
3588                 update_forest_info.entries[i].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3589             for i in xrange(0, len(update_forest_info.entries)):
3590                 r = update_forest_info.entries[i]
3591                 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3592                     continue
3593                 if update_forest_info.entries[i].flags == 0:
3594                     continue
3595                 update_forest_info.entries[i].time = 0
3596                 update_forest_info.entries[i].flags &= ~lsa.LSA_NB_DISABLED_MASK
3597                 update_forest_info.entries[i].flags &= ~lsa.LSA_SID_DISABLED_MASK
3598
3599         for tln in enable_tln:
3600             idx = None
3601             for i in xrange(0, len(update_forest_info.entries)):
3602                 r = update_forest_info.entries[i]
3603                 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3604                     continue
3605                 if r.forest_trust_data.string.lower() != tln.lower():
3606                     continue
3607                 idx = i
3608                 break
3609             if idx is None:
3610                 raise CommandError("Entry not found for value[%s] specified for --enable-tln" % tln)
3611             if not update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_MASK:
3612                 raise CommandError("Entry found for value[%s] specified for --enable-tln is already enabled" % tln)
3613             update_forest_info.entries[idx].time = 0
3614             update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3615
3616         for tln in disable_tln:
3617             idx = None
3618             for i in xrange(0, len(update_forest_info.entries)):
3619                 r = update_forest_info.entries[i]
3620                 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3621                     continue
3622                 if r.forest_trust_data.string.lower() != tln.lower():
3623                     continue
3624                 idx = i
3625                 break
3626             if idx is None:
3627                 raise CommandError("Entry not found for value[%s] specified for --disable-tln" % tln)
3628             if update_forest_info.entries[idx].flags & lsa.LSA_TLN_DISABLED_ADMIN:
3629                 raise CommandError("Entry found for value[%s] specified for --disable-tln is already disabled" % tln)
3630             update_forest_info.entries[idx].time = 0
3631             update_forest_info.entries[idx].flags &= ~lsa.LSA_TLN_DISABLED_MASK
3632             update_forest_info.entries[idx].flags |= lsa.LSA_TLN_DISABLED_ADMIN
3633
3634         for tln_ex in add_tln_ex:
3635             idx = None
3636             for i in xrange(0, len(update_forest_info.entries)):
3637                 r = update_forest_info.entries[i]
3638                 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3639                     continue
3640                 if r.forest_trust_data.string.lower() != tln_ex.lower():
3641                     continue
3642                 idx = i
3643                 break
3644             if idx is not None:
3645                 raise CommandError("Entry already present for value[%s] specified for --add-tln-ex" % tln_ex)
3646
3647             tln_dot = ".%s" % tln_ex.lower()
3648             idx = None
3649             for i in xrange(0, len(update_forest_info.entries)):
3650                 r = update_forest_info.entries[i]
3651                 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
3652                     continue
3653                 r_dot = ".%s" % r.forest_trust_data.string.lower()
3654                 if tln_dot == r_dot:
3655                     raise CommandError("TLN entry present for value[%s] specified for --add-tln-ex" % tln_ex)
3656                 if not tln_dot.endswith(r_dot):
3657                     continue
3658                 idx = i
3659                 break
3660
3661             if idx is None:
3662                 raise CommandError("No TLN parent present for value[%s] specified for --add-tln-ex" % tln_ex)
3663
3664             r = lsa.ForestTrustRecord()
3665             r.type = lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX
3666             r.flags = 0
3667             r.time = 0
3668             r.forest_trust_data.string = tln_ex
3669
3670             entries = []
3671             entries.extend(update_forest_info.entries)
3672             entries.insert(idx + 1, r)
3673             update_forest_info.count = len(entries)
3674             update_forest_info.entries = entries
3675
3676         for tln_ex in delete_tln_ex:
3677             idx = None
3678             for i in xrange(0, len(update_forest_info.entries)):
3679                 r = update_forest_info.entries[i]
3680                 if r.type != lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
3681                     continue
3682                 if r.forest_trust_data.string.lower() != tln_ex.lower():
3683                     continue
3684                 idx = i
3685                 break
3686             if idx is None:
3687                 raise CommandError("Entry not found for value[%s] specified for --delete-tln-ex" % tln_ex)
3688
3689             entries = []
3690             entries.extend(update_forest_info.entries)
3691             entries.pop(idx)
3692             update_forest_info.count = len(entries)
3693             update_forest_info.entries = entries
3694
3695         for nb in enable_nb:
3696             idx = None
3697             for i in xrange(0, len(update_forest_info.entries)):
3698                 r = update_forest_info.entries[i]
3699                 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3700                     continue
3701                 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3702                     continue
3703                 idx = i
3704                 break
3705             if idx is None:
3706                 raise CommandError("Entry not found for value[%s] specified for --enable-nb" % nb)
3707             if not update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_MASK:
3708                 raise CommandError("Entry found for value[%s] specified for --enable-nb is already enabled" % nb)
3709             update_forest_info.entries[idx].time = 0
3710             update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3711
3712         for nb in disable_nb:
3713             idx = None
3714             for i in xrange(0, len(update_forest_info.entries)):
3715                 r = update_forest_info.entries[i]
3716                 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3717                     continue
3718                 if r.forest_trust_data.netbios_domain_name.string.upper() != nb.upper():
3719                     continue
3720                 idx = i
3721                 break
3722             if idx is None:
3723                 raise CommandError("Entry not found for value[%s] specified for --delete-nb" % nb)
3724             if update_forest_info.entries[idx].flags & lsa.LSA_NB_DISABLED_ADMIN:
3725                 raise CommandError("Entry found for value[%s] specified for --disable-nb is already disabled" % nb)
3726             update_forest_info.entries[idx].time = 0
3727             update_forest_info.entries[idx].flags &= ~lsa.LSA_NB_DISABLED_MASK
3728             update_forest_info.entries[idx].flags |= lsa.LSA_NB_DISABLED_ADMIN
3729
3730         for sid in enable_sid:
3731             idx = None
3732             for i in xrange(0, len(update_forest_info.entries)):
3733                 r = update_forest_info.entries[i]
3734                 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3735                     continue
3736                 if r.forest_trust_data.domain_sid != sid:
3737                     continue
3738                 idx = i
3739                 break
3740             if idx is None:
3741                 raise CommandError("Entry not found for value[%s] specified for --enable-sid" % sid)
3742             if not update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_MASK:
3743                 raise CommandError("Entry found for value[%s] specified for --enable-sid is already enabled" % nb)
3744             update_forest_info.entries[idx].time = 0
3745             update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3746
3747         for sid in disable_sid:
3748             idx = None
3749             for i in xrange(0, len(update_forest_info.entries)):
3750                 r = update_forest_info.entries[i]
3751                 if r.type != lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
3752                     continue
3753                 if r.forest_trust_data.domain_sid != sid:
3754                     continue
3755                 idx = i
3756                 break
3757             if idx is None:
3758                 raise CommandError("Entry not found for value[%s] specified for --delete-sid" % sid)
3759             if update_forest_info.entries[idx].flags & lsa.LSA_SID_DISABLED_ADMIN:
3760                 raise CommandError("Entry found for value[%s] specified for --disable-sid is already disabled" % nb)
3761             update_forest_info.entries[idx].time = 0
3762             update_forest_info.entries[idx].flags &= ~lsa.LSA_SID_DISABLED_MASK
3763             update_forest_info.entries[idx].flags |= lsa.LSA_SID_DISABLED_ADMIN
3764
3765         try:
3766             update_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
3767                                                           local_tdo_info.domain_name,
3768                                                           lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
3769                                                           update_forest_info, 0)
3770         except RuntimeError as error:
3771             raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
3772
3773         self.outf.write("Updated forest trust information...\n")
3774         self.write_forest_trust_info(update_forest_info,
3775                                      tln=local_tdo_info.domain_name.string,
3776                                      collisions=update_forest_collision)
3777
3778         try:
3779             lsaString = lsa.String()
3780             lsaString.string = local_tdo_info.domain_name.string
3781             stored_forest_info = local_lsa.lsaRQueryForestTrustInformation(local_policy,
3782                                                           lsaString,
3783                                                           lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
3784         except RuntimeError as error:
3785             raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation() failed")
3786
3787         self.outf.write("Stored forest trust information...\n")
3788         self.write_forest_trust_info(stored_forest_info,
3789                                      tln=local_tdo_info.domain_name.string)
3790         return
3791
3792 class cmd_domain_tombstones_expunge(Command):
3793     """Expunge tombstones from the database.
3794
3795 This command expunges tombstones from the database."""
3796     synopsis = "%prog NC [NC [...]] [options]"
3797
3798     takes_options = [
3799         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3800                 metavar="URL", dest="H"),
3801         Option("--current-time",
3802                 help="The current time to evaluate the tombstone lifetime from, expressed as YYYY-MM-DD",
3803                 type=str),
3804         Option("--tombstone-lifetime", help="Number of days a tombstone should be preserved for", type=int),
3805     ]
3806
3807     takes_args = ["nc*"]
3808
3809     takes_optiongroups = {
3810         "sambaopts": options.SambaOptions,
3811         "credopts": options.CredentialsOptions,
3812         "versionopts": options.VersionOptions,
3813         }
3814
3815     def run(self, *ncs, **kwargs):
3816         sambaopts = kwargs.get("sambaopts")
3817         credopts = kwargs.get("credopts")
3818         versionpts = kwargs.get("versionopts")
3819         H = kwargs.get("H")
3820         current_time_string = kwargs.get("current_time")
3821         tombstone_lifetime = kwargs.get("tombstone_lifetime")
3822         lp = sambaopts.get_loadparm()
3823         creds = credopts.get_credentials(lp)
3824         samdb = SamDB(url=H, session_info=system_session(),
3825                       credentials=creds, lp=lp)
3826
3827         if current_time_string is not None:
3828             current_time_obj = time.strptime(current_time_string, "%Y-%m-%d")
3829             current_time = long(time.mktime(current_time_obj))
3830
3831         else:
3832             current_time = long(time.time())
3833
3834         if len(ncs) == 0:
3835             res = samdb.search(expression="", base="", scope=ldb.SCOPE_BASE,
3836                          attrs=["namingContexts"])
3837
3838             ncs = []
3839             for nc in res[0]["namingContexts"]:
3840                 ncs.append(str(nc))
3841         else:
3842             ncs = list(ncs)
3843
3844         started_transaction = False
3845         try:
3846             samdb.transaction_start()
3847             started_transaction = True
3848             (removed_objects,
3849              removed_links) = samdb.garbage_collect_tombstones(ncs,
3850                                                                current_time=current_time,
3851                                                                tombstone_lifetime=tombstone_lifetime)
3852
3853         except Exception, err:
3854             if started_transaction:
3855                 samdb.transaction_cancel()
3856             raise CommandError("Failed to expunge / garbage collect tombstones", err)
3857
3858         samdb.transaction_commit()
3859
3860         self.outf.write("Removed %d objects and %d links successfully\n"
3861                         % (removed_objects, removed_links))
3862
3863
3864
3865 class cmd_domain_trust(SuperCommand):
3866     """Domain and forest trust management."""
3867
3868     subcommands = {}
3869     subcommands["list"] = cmd_domain_trust_list()
3870     subcommands["show"] = cmd_domain_trust_show()
3871     subcommands["create"] = cmd_domain_trust_create()
3872     subcommands["delete"] = cmd_domain_trust_delete()
3873     subcommands["validate"] = cmd_domain_trust_validate()
3874     subcommands["namespaces"] = cmd_domain_trust_namespaces()
3875
3876 class cmd_domain_tombstones(SuperCommand):
3877     """Domain tombstone and recycled object management."""
3878
3879     subcommands = {}
3880     subcommands["expunge"] = cmd_domain_tombstones_expunge()
3881
3882 class ldif_schema_update:
3883     """Helper class for applying LDIF schema updates"""
3884
3885     def __init__(self):
3886         self.is_defunct = False
3887         self.unknown_oid = None
3888         self.dn = None
3889         self.ldif = ""
3890
3891     def _ldap_schemaUpdateNow(self, samdb):
3892         ldif = """
3893 dn:
3894 changetype: modify
3895 add: schemaUpdateNow
3896 schemaUpdateNow: 1
3897 """
3898         samdb.modify_ldif(ldif)
3899
3900     def can_ignore_failure(self, error):
3901         """Checks if we can safely ignore failure to apply an LDIF update"""
3902         (num, errstr) = error.args
3903
3904         # Microsoft has marked objects as defunct that Samba doesn't know about
3905         if num == ldb.ERR_NO_SUCH_OBJECT and self.is_defunct:
3906             print("Defunct object %s doesn't exist, skipping" % self.dn)
3907             return True
3908         elif self.unknown_oid is not None:
3909             print("Skipping unknown OID %s for object %s" %(self.unknown_oid, self.dn))
3910             return True
3911
3912         return False
3913
3914     def apply(self, samdb):
3915         """Applies a single LDIF update to the schema"""
3916
3917         try:
3918             try:
3919                 samdb.modify_ldif(self.ldif, controls=['relax:0'])
3920             except ldb.LdbError as e:
3921                 if e.args[0] == ldb.ERR_INVALID_ATTRIBUTE_SYNTAX:
3922
3923                     # REFRESH after a failed change
3924
3925                     # Otherwise the OID-to-attribute mapping in
3926                     # _apply_updates_in_file() won't work, because it
3927                     # can't lookup the new OID in the schema
3928                     self._ldap_schemaUpdateNow(samdb)
3929
3930                     samdb.modify_ldif(self.ldif, controls=['relax:0'])
3931                 else:
3932                     raise
3933         except ldb.LdbError as e:
3934             if self.can_ignore_failure(e):
3935                 return 0
3936             else:
3937                 print("Exception: %s" % e)
3938                 print("Encountered while trying to apply the following LDIF")
3939                 print("----------------------------------------------------")
3940                 print("%s" % self.ldif)
3941
3942                 raise
3943
3944         return 1
3945
3946 class cmd_domain_schema_upgrade(Command):
3947     """Domain schema upgrading"""
3948
3949     synopsis = "%prog [options]"
3950
3951     takes_optiongroups = {
3952         "sambaopts": options.SambaOptions,
3953         "versionopts": options.VersionOptions,
3954         "credopts": options.CredentialsOptions,
3955     }
3956
3957     takes_options = [
3958         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3959                metavar="URL", dest="H"),
3960         Option("--quiet", help="Be quiet", action="store_true"),
3961         Option("--verbose", help="Be verbose", action="store_true"),
3962         Option("--schema", type="choice", metavar="SCHEMA",
3963                choices=["2012", "2012_R2"],
3964                help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
3965                default="2012_R2"),
3966         Option("--ldf-file", type=str, default=None,
3967                 help="Just apply the schema updates in the adprep/.LDF file(s) specified"),
3968         Option("--base-dir", type=str, default=None,
3969                help="Location of ldf files Default is ${SETUPDIR}/adprep.")
3970     ]
3971
3972     def _apply_updates_in_file(self, samdb, ldif_file):
3973         """
3974         Applies a series of updates specified in an .LDIF file. The .LDIF file
3975         is based on the adprep Schema updates provided by Microsoft.
3976         """
3977         count = 0
3978         ldif_op = ldif_schema_update()
3979
3980         # parse the file line by line and work out each update operation to apply
3981         for line in ldif_file:
3982
3983             line = line.rstrip()
3984
3985             # the operations in the .LDIF file are separated by blank lines. If
3986             # we hit a blank line, try to apply the update we've parsed so far
3987             if line == '':
3988
3989                 # keep going if we haven't parsed anything yet
3990                 if ldif_op.ldif == '':
3991                     continue
3992
3993                 # Apply the individual change
3994                 count += ldif_op.apply(samdb)
3995
3996                 # start storing the next operation from scratch again
3997                 ldif_op = ldif_schema_update()
3998                 continue
3999
4000             # replace the placeholder domain name in the .ldif file with the real domain
4001             if line.upper().endswith('DC=X'):
4002                 line = line[:-len('DC=X')] + str(samdb.get_default_basedn())
4003             elif line.upper().endswith('CN=X'):
4004                 line = line[:-len('CN=X')] + str(samdb.get_default_basedn())
4005
4006             values = line.split(':')
4007
4008             if values[0].lower() == 'dn':
4009                 ldif_op.dn = values[1].strip()
4010
4011             # replace the Windows-specific operation with the Samba one
4012             if values[0].lower() == 'changetype':
4013                 line = line.lower().replace(': ntdsschemaadd',
4014                                             ': add')
4015                 line = line.lower().replace(': ntdsschemamodify',
4016                                             ': modify')
4017
4018             if values[0].lower() in ['rdnattid', 'subclassof',
4019                                      'systemposssuperiors',
4020                                      'systemmaycontain',
4021                                      'systemauxiliaryclass']:
4022                 _, value = values
4023
4024                 # The Microsoft updates contain some OIDs we don't recognize.
4025                 # Query the DB to see if we can work out the OID this update is
4026                 # referring to. If we find a match, then replace the OID with
4027                 # the ldapDisplayname
4028                 if '.' in value:
4029                     res = samdb.search(base=samdb.get_schema_basedn(),
4030                                        expression="(|(attributeId=%s)(governsId=%s))" %
4031                                        (value, value),
4032                                        attrs=['ldapDisplayName'])
4033
4034                     if len(res) != 1:
4035                         ldif_op.unknown_oid = value
4036                     else:
4037                         display_name = res[0]['ldapDisplayName'][0]
4038                         line = line.replace(value, ' ' + display_name)
4039
4040             # Microsoft has marked objects as defunct that Samba doesn't know about
4041             if values[0].lower() == 'isdefunct' and values[1].strip().lower() == 'true':
4042                 ldif_op.is_defunct = True
4043
4044             # Samba has added the showInAdvancedViewOnly attribute to all objects,
4045             # so rather than doing an add, we need to do a replace
4046             if values[0].lower() == 'add' and values[1].strip().lower() == 'showinadvancedviewonly':
4047                 line = 'replace: showInAdvancedViewOnly'
4048
4049             # Add the line to the current LDIF operation (including the newline
4050             # we stripped off at the start of the loop)
4051             ldif_op.ldif += line + '\n'
4052
4053         return count
4054
4055
4056     def _apply_update(self, samdb, update_file, base_dir):
4057         """Wrapper function for parsing an LDIF file and applying the updates"""
4058
4059         print("Applying %s updates..." % update_file)
4060
4061         ldif_file = None
4062         try:
4063             ldif_file = open(os.path.join(base_dir, update_file))
4064
4065             count = self._apply_updates_in_file(samdb, ldif_file)
4066
4067         finally:
4068             if ldif_file:
4069                 ldif_file.close()
4070
4071         print("%u changes applied" % count)
4072
4073         return count
4074
4075     def run(self, **kwargs):
4076         from samba.ms_schema_markdown import read_ms_markdown
4077         from samba.schema import Schema
4078
4079         updates_allowed_overriden = False
4080         sambaopts = kwargs.get("sambaopts")
4081         credopts = kwargs.get("credopts")
4082         versionpts = kwargs.get("versionopts")
4083         lp = sambaopts.get_loadparm()
4084         creds = credopts.get_credentials(lp)
4085         H = kwargs.get("H")
4086         target_schema = kwargs.get("schema")
4087         ldf_files = kwargs.get("ldf_file")
4088         base_dir = kwargs.get("base_dir")
4089
4090         temp_folder = None
4091
4092         samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4093
4094         # we're not going to get far if the config doesn't allow schema updates
4095         if lp.get("dsdb:schema update allowed") is None:
4096             lp.set("dsdb:schema update allowed", "yes")
4097             print("Temporarily overriding 'dsdb:schema update allowed' setting")
4098             updates_allowed_overriden = True
4099
4100         own_dn = ldb.Dn(samdb, samdb.get_dsServiceName())
4101         master = get_fsmo_roleowner(samdb, str(samdb.get_schema_basedn()),
4102                                     'schema')
4103         if own_dn != master:
4104             raise CommandError("This server is not the schema master.")
4105
4106         # if specific LDIF files were specified, just apply them
4107         if ldf_files:
4108             schema_updates = ldf_files.split(",")
4109         else:
4110             schema_updates = []
4111
4112             # work out the version of the target schema we're upgrading to
4113             end = Schema.get_version(target_schema)
4114
4115             # work out the version of the schema we're currently using
4116             res = samdb.search(base=samdb.get_schema_basedn(),
4117                                scope=ldb.SCOPE_BASE, attrs=['objectVersion'])
4118
4119             if len(res) != 1:
4120                 raise CommandError('Could not determine current schema version')
4121             start = int(res[0]['objectVersion'][0]) + 1
4122
4123             diff_dir = setup_path("adprep/WindowsServerDocs")
4124             if base_dir is None:
4125                 # Read from the Schema-Updates.md file
4126                 temp_folder = tempfile.mkdtemp()
4127
4128                 update_file = setup_path("adprep/WindowsServerDocs/Schema-Updates.md")
4129
4130                 try:
4131                     read_ms_markdown(update_file, temp_folder)
4132                 except Exception as e:
4133                     print("Exception in markdown parsing: %s" % e)
4134                     shutil.rmtree(temp_folder)
4135                     raise CommandError('Failed to upgrade schema')
4136
4137                 base_dir = temp_folder
4138
4139             for version in range(start, end + 1):
4140                 update = 'Sch%d.ldf' % version
4141                 schema_updates.append(update)
4142
4143                 # Apply patches if we parsed the Schema-Updates.md file
4144                 diff = os.path.abspath(os.path.join(diff_dir, update + '.diff'))
4145                 if temp_folder and os.path.exists(diff):
4146                     p = subprocess.Popen(['patch', update, '-i', diff],
4147                                          stdout=subprocess.PIPE,
4148                                          stderr=subprocess.PIPE, cwd=temp_folder)
4149                     stdout, stderr = p.communicate()
4150
4151                     if p.returncode:
4152                         print("Exception in patch: %s\n%s" % (stdout, stderr))
4153                         shutil.rmtree(temp_folder)
4154                         raise CommandError('Failed to upgrade schema')
4155
4156                     print("Patched %s using %s" % (update, diff))
4157
4158         if base_dir is None:
4159             base_dir = setup_path("adprep")
4160
4161         samdb.transaction_start()
4162         count = 0
4163         error_encountered = False
4164
4165         try:
4166             # Apply the schema updates needed to move to the new schema version
4167             for ldif_file in schema_updates:
4168                 count += self._apply_update(samdb, ldif_file, base_dir)
4169
4170             if count > 0:
4171                 samdb.transaction_commit()
4172                 print("Schema successfully updated")
4173             else:
4174                 print("No changes applied to schema")
4175                 samdb.transaction_cancel()
4176         except Exception as e:
4177             print("Exception: %s" % e)
4178             print("Error encountered, aborting schema upgrade")
4179             samdb.transaction_cancel()
4180             error_encountered = True
4181
4182         if updates_allowed_overriden:
4183             lp.set("dsdb:schema update allowed", "no")
4184
4185         if temp_folder:
4186             shutil.rmtree(temp_folder)
4187
4188         if error_encountered:
4189             raise CommandError('Failed to upgrade schema')
4190
4191 class cmd_domain_functional_prep(Command):
4192     """Domain functional level preparation"""
4193
4194     synopsis = "%prog [options]"
4195
4196     takes_optiongroups = {
4197         "sambaopts": options.SambaOptions,
4198         "versionopts": options.VersionOptions,
4199         "credopts": options.CredentialsOptions,
4200     }
4201
4202     takes_options = [
4203         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
4204                metavar="URL", dest="H"),
4205         Option("--quiet", help="Be quiet", action="store_true"),
4206         Option("--verbose", help="Be verbose", action="store_true"),
4207         Option("--function-level", type="choice", metavar="FUNCTION_LEVEL",
4208                choices=["2008_R2", "2012", "2012_R2"],
4209                help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
4210                default="2012_R2"),
4211         Option("--forest-prep", action="store_true",
4212                help="Run the forest prep (by default, both the domain and forest prep are run)."),
4213         Option("--domain-prep", action="store_true",
4214                help="Run the domain prep (by default, both the domain and forest prep are run).")
4215     ]
4216
4217     def run(self, **kwargs):
4218         updates_allowed_overriden = False
4219         sambaopts = kwargs.get("sambaopts")
4220         credopts = kwargs.get("credopts")
4221         versionpts = kwargs.get("versionopts")
4222         lp = sambaopts.get_loadparm()
4223         creds = credopts.get_credentials(lp)
4224         H = kwargs.get("H")
4225         target_level = string_version_to_constant[kwargs.get("function_level")]
4226         forest_prep = kwargs.get("forest_prep")
4227         domain_prep = kwargs.get("domain_prep")
4228
4229         samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
4230
4231         # we're not going to get far if the config doesn't allow schema updates
4232         if lp.get("dsdb:schema update allowed") is None:
4233             lp.set("dsdb:schema update allowed", "yes")
4234             print("Temporarily overriding 'dsdb:schema update allowed' setting")
4235             updates_allowed_overriden = True
4236
4237         if forest_prep is None and domain_prep is None:
4238             forest_prep = True
4239             domain_prep = True
4240
4241         own_dn = ldb.Dn(samdb, samdb.get_dsServiceName())
4242         if forest_prep:
4243             master = get_fsmo_roleowner(samdb, str(samdb.get_schema_basedn()),
4244                                         'schema')
4245             if own_dn != master:
4246                 raise CommandError("This server is not the schema master.")
4247
4248         if domain_prep:
4249             domain_dn = samdb.domain_dn()
4250             infrastructure_dn = "CN=Infrastructure," + domain_dn
4251             master = get_fsmo_roleowner(samdb, infrastructure_dn,
4252                                        'infrastructure')
4253             if own_dn != master:
4254                 raise CommandError("This server is not the infrastructure master.")
4255
4256         if forest_prep:
4257             samdb.transaction_start()
4258             error_encountered = False
4259             try:
4260                 from samba.forest_update import ForestUpdate
4261                 forest = ForestUpdate(samdb, fix=True)
4262
4263                 forest.check_updates_iterator([53, 79, 80, 81, 82, 83])
4264                 forest.check_updates_functional_level(target_level,
4265                                                       DS_DOMAIN_FUNCTION_2008_R2,
4266                                                       update_revision=True)
4267
4268                 samdb.transaction_commit()
4269             except Exception as e:
4270                 print("Exception: %s" % e)
4271                 samdb.transaction_cancel()
4272                 error_encountered = True
4273
4274         if domain_prep:
4275             samdb.transaction_start()
4276             error_encountered = False
4277             try:
4278                 from samba.domain_update import DomainUpdate
4279
4280                 domain = DomainUpdate(samdb, fix=True)
4281                 domain.check_updates_functional_level(target_level,
4282                                                       DS_DOMAIN_FUNCTION_2008,
4283                                                       update_revision=True)
4284
4285                 samdb.transaction_commit()
4286             except Exception as e:
4287                 print("Exception: %s" % e)
4288                 samdb.transaction_cancel()
4289                 error_encountered = True
4290
4291         if updates_allowed_overriden:
4292             lp.set("dsdb:schema update allowed", "no")
4293
4294         if error_encountered:
4295             raise CommandError('Failed to perform functional prep')
4296
4297 class cmd_domain(SuperCommand):
4298     """Domain management."""
4299
4300     subcommands = {}
4301     subcommands["demote"] = cmd_domain_demote()
4302     if cmd_domain_export_keytab is not None:
4303         subcommands["exportkeytab"] = cmd_domain_export_keytab()
4304     subcommands["info"] = cmd_domain_info()
4305     subcommands["provision"] = cmd_domain_provision()
4306     subcommands["join"] = cmd_domain_join()
4307     subcommands["dcpromo"] = cmd_domain_dcpromo()
4308     subcommands["level"] = cmd_domain_level()
4309     subcommands["passwordsettings"] = cmd_domain_passwordsettings()
4310     subcommands["classicupgrade"] = cmd_domain_classicupgrade()
4311     subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()
4312     subcommands["trust"] = cmd_domain_trust()
4313     subcommands["tombstones"] = cmd_domain_tombstones()
4314     subcommands["schemaupgrade"] = cmd_domain_schema_upgrade()
4315     subcommands["functionalprep"] = cmd_domain_functional_prep()