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