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