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