provision: allow provisioning of a different database backend
[nivanova/samba-autobuild/.git] / python / samba / netcmd / domain.py
1 # domain management
2 #
3 # Copyright Matthias Dieter Wallnoefer 2009
4 # Copyright Andrew Kroeger 2009
5 # Copyright Jelmer Vernooij 2007-2012
6 # Copyright Giampaolo Lauria 2011
7 # Copyright Matthieu Patou <mat@matws.net> 2011
8 # Copyright Andrew Bartlett 2008-2015
9 # Copyright Stefan Metzmacher 2012
10 #
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
15 #
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 # GNU General Public License for more details.
20 #
21 # You should have received a copy of the GNU General Public License
22 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
23 #
24
25 from __future__ import print_function
26 import samba.getopt as options
27 import ldb
28 import string
29 import os
30 import sys
31 import ctypes
32 import random
33 import tempfile
34 import logging
35 import subprocess
36 import time
37 import shutil
38 from samba import ntstatus
39 from samba import NTSTATUSError
40 from samba import werror
41 from getpass import getpass
42 from samba.net import Net, LIBNET_JOIN_AUTOMATIC
43 import samba.ntacls
44 from samba.join import join_RODC, join_DC, join_subdomain
45 from samba.auth import system_session
46 from samba.samdb import SamDB, get_default_backend_store
47 from samba.ndr import ndr_unpack, ndr_pack, ndr_print
48 from samba.dcerpc import drsuapi
49 from samba.dcerpc import drsblobs
50 from samba.dcerpc import lsa
51 from samba.dcerpc import netlogon
52 from samba.dcerpc import security
53 from samba.dcerpc import nbt
54 from samba.dcerpc import misc
55 from samba.dcerpc.samr import DOMAIN_PASSWORD_COMPLEX, DOMAIN_PASSWORD_STORE_CLEARTEXT
56 from samba.netcmd import (
57     Command,
58     CommandError,
59     SuperCommand,
60     Option
61     )
62 from samba.netcmd.fsmo import get_fsmo_roleowner
63 from samba.netcmd.common import netcmd_get_domain_infos_via_cldap
64 from samba.samba3 import Samba3
65 from samba.samba3 import param as s3param
66 from samba.upgrade import upgrade_from_samba3
67 from samba.drs_utils import (
68                             sendDsReplicaSync, drsuapi_connect, drsException,
69                             sendRemoveDsServer)
70 from samba import remove_dc, arcfour_encrypt, string_to_byte_array
71
72 from samba.dsdb import (
73     DS_DOMAIN_FUNCTION_2000,
74     DS_DOMAIN_FUNCTION_2003,
75     DS_DOMAIN_FUNCTION_2003_MIXED,
76     DS_DOMAIN_FUNCTION_2008,
77     DS_DOMAIN_FUNCTION_2008_R2,
78     DS_DOMAIN_FUNCTION_2012,
79     DS_DOMAIN_FUNCTION_2012_R2,
80     DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL,
81     DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL,
82     UF_WORKSTATION_TRUST_ACCOUNT,
83     UF_SERVER_TRUST_ACCOUNT,
84     UF_TRUSTED_FOR_DELEGATION,
85     UF_PARTIAL_SECRETS_ACCOUNT
86     )
87
88 from samba.provision import (
89     provision,
90     ProvisioningError,
91     DEFAULT_MIN_PWD_LENGTH,
92     setup_path
93     )
94
95 from samba.provision.common import (
96     FILL_FULL,
97     FILL_NT4SYNC,
98     FILL_DRS
99 )
100
101 string_version_to_constant = {
102     "2008_R2" : DS_DOMAIN_FUNCTION_2008_R2,
103     "2012": DS_DOMAIN_FUNCTION_2012,
104     "2012_R2": DS_DOMAIN_FUNCTION_2012_R2,
105 }
106
107 def get_testparm_var(testparm, smbconf, varname):
108     errfile = open(os.devnull, 'w')
109     p = subprocess.Popen([testparm, '-s', '-l',
110                           '--parameter-name=%s' % varname, smbconf],
111                          stdout=subprocess.PIPE, stderr=errfile)
112     (out,err) = p.communicate()
113     errfile.close()
114     lines = out.split('\n')
115     if lines:
116         return lines[0].strip()
117     return ""
118
119 try:
120    import samba.dckeytab
121 except ImportError:
122    cmd_domain_export_keytab = None
123 else:
124    class cmd_domain_export_keytab(Command):
125        """Dump Kerberos keys of the domain into a keytab."""
126
127        synopsis = "%prog <keytab> [options]"
128
129        takes_optiongroups = {
130            "sambaopts": options.SambaOptions,
131            "credopts": options.CredentialsOptions,
132            "versionopts": options.VersionOptions,
133            }
134
135        takes_options = [
136            Option("--principal", help="extract only this principal", type=str),
137            ]
138
139        takes_args = ["keytab"]
140
141        def run(self, keytab, credopts=None, sambaopts=None, versionopts=None, principal=None):
142            lp = sambaopts.get_loadparm()
143            net = Net(None, lp)
144            net.export_keytab(keytab=keytab, principal=principal)
145
146
147 class cmd_domain_info(Command):
148     """Print basic info about a domain and the DC passed as parameter."""
149
150     synopsis = "%prog <ip_address> [options]"
151
152     takes_options = [
153         ]
154
155     takes_optiongroups = {
156         "sambaopts": options.SambaOptions,
157         "credopts": options.CredentialsOptions,
158         "versionopts": options.VersionOptions,
159         }
160
161     takes_args = ["address"]
162
163     def run(self, address, credopts=None, sambaopts=None, versionopts=None):
164         lp = sambaopts.get_loadparm()
165         try:
166             res = netcmd_get_domain_infos_via_cldap(lp, None, address)
167         except RuntimeError:
168             raise CommandError("Invalid IP address '" + address + "'!")
169         self.outf.write("Forest           : %s\n" % res.forest)
170         self.outf.write("Domain           : %s\n" % res.dns_domain)
171         self.outf.write("Netbios domain   : %s\n" % res.domain_name)
172         self.outf.write("DC name          : %s\n" % res.pdc_dns_name)
173         self.outf.write("DC netbios name  : %s\n" % res.pdc_name)
174         self.outf.write("Server site      : %s\n" % res.server_site)
175         self.outf.write("Client site      : %s\n" % res.client_site)
176
177
178 class cmd_domain_provision(Command):
179     """Provision a domain."""
180
181     synopsis = "%prog [options]"
182
183     takes_optiongroups = {
184         "sambaopts": options.SambaOptions,
185         "versionopts": options.VersionOptions,
186     }
187
188     takes_options = [
189          Option("--interactive", help="Ask for names", action="store_true"),
190          Option("--domain", type="string", metavar="DOMAIN",
191                 help="NetBIOS domain name to use"),
192          Option("--domain-guid", type="string", metavar="GUID",
193                 help="set domainguid (otherwise random)"),
194          Option("--domain-sid", type="string", metavar="SID",
195                 help="set domainsid (otherwise random)"),
196          Option("--ntds-guid", type="string", metavar="GUID",
197                 help="set NTDS object GUID (otherwise random)"),
198          Option("--invocationid", type="string", metavar="GUID",
199                 help="set invocationid (otherwise random)"),
200          Option("--host-name", type="string", metavar="HOSTNAME",
201                 help="set hostname"),
202          Option("--host-ip", type="string", metavar="IPADDRESS",
203                 help="set IPv4 ipaddress"),
204          Option("--host-ip6", type="string", metavar="IP6ADDRESS",
205                 help="set IPv6 ipaddress"),
206          Option("--site", type="string", metavar="SITENAME",
207                 help="set site name"),
208          Option("--adminpass", type="string", metavar="PASSWORD",
209                 help="choose admin password (otherwise random)"),
210          Option("--krbtgtpass", type="string", metavar="PASSWORD",
211                 help="choose krbtgt password (otherwise random)"),
212          Option("--machinepass", type="string", metavar="PASSWORD",
213                 help="choose machine password (otherwise random)"),
214          Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
215                 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
216                 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
217                      "BIND9_FLATFILE uses bind9 text database to store zone information, "
218                      "BIND9_DLZ uses samba4 AD to store zone information, "
219                      "NONE skips the DNS setup entirely (not recommended)",
220                 default="SAMBA_INTERNAL"),
221          Option("--dnspass", type="string", metavar="PASSWORD",
222                 help="choose dns password (otherwise random)"),
223          Option("--ldapadminpass", type="string", metavar="PASSWORD",
224                 help="choose password to set between Samba and its LDAP backend (otherwise random)"),
225          Option("--root", type="string", metavar="USERNAME",
226                 help="choose 'root' unix username"),
227          Option("--nobody", type="string", metavar="USERNAME",
228                 help="choose 'nobody' user"),
229          Option("--users", type="string", metavar="GROUPNAME",
230                 help="choose 'users' group"),
231          Option("--quiet", help="Be quiet", action="store_true"),
232          Option("--blank", action="store_true",
233                 help="do not add users or groups, just the structure"),
234          Option("--ldap-backend-type", type="choice", metavar="LDAP-BACKEND-TYPE",
235                 help="Test initialisation support for unsupported LDAP backend type (fedora-ds or openldap) DO NOT USE",
236                 choices=["fedora-ds", "openldap"]),
237          Option("--server-role", type="choice", metavar="ROLE",
238                 choices=["domain controller", "dc", "member server", "member", "standalone"],
239                 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
240                 default="domain controller"),
241          Option("--function-level", type="choice", metavar="FOR-FUN-LEVEL",
242                 choices=["2000", "2003", "2008", "2008_R2"],
243                 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2008_R2 Native.",
244                 default="2008_R2"),
245          Option("--base-schema", type="choice", metavar="BASE-SCHEMA",
246                 choices=["2008_R2", "2008_R2_old", "2012", "2012_R2"],
247                 help="The base schema files to use. Default is (Windows) 2008_R2.",
248                 default="2008_R2"),
249          Option("--next-rid", type="int", metavar="NEXTRID", default=1000,
250                 help="The initial nextRid value (only needed for upgrades).  Default is 1000."),
251          Option("--partitions-only",
252                 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"),
253          Option("--targetdir", type="string", metavar="DIR",
254                 help="Set target directory"),
255          Option("--ol-mmr-urls", type="string", metavar="LDAPSERVER",
256                 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\""),
257          Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
258          Option("--plaintext-secrets", action="store_true",
259                 help="Store secret/sensitive values as plain text on disk" +
260                      "(default is to encrypt secret/ensitive values)"),
261          Option("--backend-store", type="choice", metavar="BACKENDSTORE",
262                 choices=["tdb", "mdb"],
263                 help="Specify the database backend to be used "
264                      "(default is %s)" % get_default_backend_store()),
265         ]
266
267     openldap_options = [
268         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",
269                action="store_true"),
270         Option("--slapd-path", type="string", metavar="SLAPD-PATH",
271                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."),
272         Option("--ldap-backend-extra-port", type="int", metavar="LDAP-BACKEND-EXTRA-PORT", help="Additional TCP port for LDAP backend server (to use for replication)"),
273         Option("--ldap-backend-forced-uri", type="string", metavar="LDAP-BACKEND-FORCED-URI",
274                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"),
275         Option("--ldap-backend-nosync", help="Configure LDAP backend not to call fsync() (for performance in test environments)", action="store_true"),
276         ]
277
278     ntvfs_options = [
279         Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
280         Option("--use-xattrs", type="choice", choices=["yes","no","auto"],
281                metavar="[yes|no|auto]",
282                help="Define if we should use the native fs capabilities or a tdb file for "
283                "storing attributes likes ntacl when --use-ntvfs is set. "
284                "auto tries to make an inteligent guess based on the user rights and system capabilities",
285                default="auto")
286     ]
287
288     if os.getenv('TEST_LDAP', "no") == "yes":
289         takes_options.extend(openldap_options)
290
291     if samba.is_ntvfs_fileserver_built():
292          takes_options.extend(ntvfs_options)
293
294     takes_args = []
295
296     def run(self, sambaopts=None, versionopts=None,
297             interactive=None,
298             domain=None,
299             domain_guid=None,
300             domain_sid=None,
301             ntds_guid=None,
302             invocationid=None,
303             host_name=None,
304             host_ip=None,
305             host_ip6=None,
306             adminpass=None,
307             site=None,
308             krbtgtpass=None,
309             machinepass=None,
310             dns_backend=None,
311             dns_forwarder=None,
312             dnspass=None,
313             ldapadminpass=None,
314             root=None,
315             nobody=None,
316             users=None,
317             quiet=None,
318             blank=None,
319             ldap_backend_type=None,
320             server_role=None,
321             function_level=None,
322             next_rid=None,
323             partitions_only=None,
324             targetdir=None,
325             ol_mmr_urls=None,
326             use_xattrs="auto",
327             slapd_path=None,
328             use_ntvfs=False,
329             use_rfc2307=None,
330             ldap_backend_nosync=None,
331             ldap_backend_extra_port=None,
332             ldap_backend_forced_uri=None,
333             ldap_dryrun_mode=None,
334             base_schema=None,
335             plaintext_secrets=False,
336             backend_store=None):
337
338         self.logger = self.get_logger("provision")
339         if quiet:
340             self.logger.setLevel(logging.WARNING)
341         else:
342             self.logger.setLevel(logging.INFO)
343
344         lp = sambaopts.get_loadparm()
345         smbconf = lp.configfile
346
347         if dns_forwarder is not None:
348             suggested_forwarder = dns_forwarder
349         else:
350             suggested_forwarder = self._get_nameserver_ip()
351             if suggested_forwarder is None:
352                 suggested_forwarder = "none"
353
354         if len(self.raw_argv) == 1:
355             interactive = True
356
357         if interactive:
358             from getpass import getpass
359             import socket
360
361             def ask(prompt, default=None):
362                 if default is not None:
363                     print("%s [%s]: " % (prompt, default), end=' ')
364                 else:
365                     print("%s: " % (prompt,), end=' ')
366                 return sys.stdin.readline().rstrip("\n") or default
367
368             try:
369                 default = socket.getfqdn().split(".", 1)[1].upper()
370             except IndexError:
371                 default = None
372             realm = ask("Realm", default)
373             if realm in (None, ""):
374                 raise CommandError("No realm set!")
375
376             try:
377                 default = realm.split(".")[0]
378             except IndexError:
379                 default = None
380             domain = ask("Domain", default)
381             if domain is None:
382                 raise CommandError("No domain set!")
383
384             server_role = ask("Server Role (dc, member, standalone)", "dc")
385
386             dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
387             if dns_backend in (None, ''):
388                 raise CommandError("No DNS backend set!")
389
390             if dns_backend == "SAMBA_INTERNAL":
391                 dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
392                 if dns_forwarder.lower() in (None, 'none'):
393                     suggested_forwarder = None
394                     dns_forwarder = None
395
396             while True:
397                 adminpassplain = getpass("Administrator password: ")
398                 issue = self._adminpass_issue(adminpassplain)
399                 if issue:
400                     self.errf.write("%s.\n" % issue)
401                 else:
402                     adminpassverify = getpass("Retype password: ")
403                     if not adminpassplain == adminpassverify:
404                         self.errf.write("Sorry, passwords do not match.\n")
405                     else:
406                         adminpass = adminpassplain
407                         break
408
409         else:
410             realm = sambaopts._lp.get('realm')
411             if realm is None:
412                 raise CommandError("No realm set!")
413             if domain is None:
414                 raise CommandError("No domain set!")
415
416         if adminpass:
417             issue = self._adminpass_issue(adminpass)
418             if issue:
419                 raise CommandError(issue)
420         else:
421             self.logger.info("Administrator password will be set randomly!")
422
423         if function_level == "2000":
424             dom_for_fun_level = DS_DOMAIN_FUNCTION_2000
425         elif function_level == "2003":
426             dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
427         elif function_level == "2008":
428             dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
429         elif function_level == "2008_R2":
430             dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
431
432         if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
433             dns_forwarder = suggested_forwarder
434
435         samdb_fill = FILL_FULL
436         if blank:
437             samdb_fill = FILL_NT4SYNC
438         elif partitions_only:
439             samdb_fill = FILL_DRS
440
441         if targetdir is not None:
442             if not os.path.isdir(targetdir):
443                 os.mkdir(targetdir)
444
445         eadb = True
446
447         if use_xattrs == "yes":
448             eadb = False
449         elif use_xattrs == "auto" and use_ntvfs == False:
450             eadb = False
451         elif use_ntvfs == False:
452             raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use).  "
453                                "Please re-run with --use-xattrs omitted.")
454         elif use_xattrs == "auto" and not lp.get("posix:eadb"):
455             if targetdir:
456                 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
457             else:
458                 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
459             try:
460                 try:
461                     samba.ntacls.setntacl(lp, file.name,
462                                           "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
463                     eadb = False
464                 except Exception:
465                     self.logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. ")
466             finally:
467                 file.close()
468
469         if eadb:
470             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.")
471         if ldap_backend_type == "existing":
472             if ldap_backend_forced_uri is not None:
473                 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)
474             else:
475                 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")
476         else:
477             if ldap_backend_forced_uri is not None:
478                 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")
479
480         if domain_sid is not None:
481             domain_sid = security.dom_sid(domain_sid)
482
483         session = system_session()
484         if backend_store is None:
485             backend_store = get_default_backend_store()
486         try:
487             result = provision(self.logger,
488                   session, smbconf=smbconf, targetdir=targetdir,
489                   samdb_fill=samdb_fill, realm=realm, domain=domain,
490                   domainguid=domain_guid, domainsid=domain_sid,
491                   hostname=host_name,
492                   hostip=host_ip, hostip6=host_ip6,
493                   sitename=site, ntdsguid=ntds_guid,
494                   invocationid=invocationid, adminpass=adminpass,
495                   krbtgtpass=krbtgtpass, machinepass=machinepass,
496                   dns_backend=dns_backend, dns_forwarder=dns_forwarder,
497                   dnspass=dnspass, root=root, nobody=nobody,
498                   users=users,
499                   serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
500                   backend_type=ldap_backend_type,
501                   ldapadminpass=ldapadminpass, ol_mmr_urls=ol_mmr_urls, slapd_path=slapd_path,
502                   useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
503                   use_rfc2307=use_rfc2307, skip_sysvolacl=False,
504                   ldap_backend_extra_port=ldap_backend_extra_port,
505                   ldap_backend_forced_uri=ldap_backend_forced_uri,
506                   nosync=ldap_backend_nosync, ldap_dryrun_mode=ldap_dryrun_mode,
507                   base_schema=base_schema,
508                   plaintext_secrets=plaintext_secrets,
509                   backend_store=backend_store)
510
511         except ProvisioningError as e:
512             raise CommandError("Provision failed", e)
513
514         result.report_logger(self.logger)
515
516     def _get_nameserver_ip(self):
517         """Grab the nameserver IP address from /etc/resolv.conf."""
518         from os import path
519         RESOLV_CONF="/etc/resolv.conf"
520
521         if not path.isfile(RESOLV_CONF):
522             self.logger.warning("Failed to locate %s" % RESOLV_CONF)
523             return None
524
525         handle = None
526         try:
527             handle = open(RESOLV_CONF, 'r')
528             for line in handle:
529                 if not line.startswith('nameserver'):
530                     continue
531                 # we want the last non-space continuous string of the line
532                 return line.strip().split()[-1]
533         finally:
534             if handle is not None:
535                 handle.close()
536
537         self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
538
539     def _adminpass_issue(self, adminpass):
540         """Returns error string for a bad administrator password,
541         or None if acceptable"""
542
543         if len(adminpass.decode('utf-8')) < DEFAULT_MIN_PWD_LENGTH:
544             return "Administrator password does not meet the default minimum" \
545                 " password length requirement (%d characters)" \
546                 % DEFAULT_MIN_PWD_LENGTH
547         elif not samba.check_password_quality(adminpass):
548             return "Administrator password does not meet the default" \
549                 " quality standards"
550         else:
551             return None
552
553
554 class cmd_domain_dcpromo(Command):
555     """Promote an existing domain member or NT4 PDC to an AD DC."""
556
557     synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
558
559     takes_optiongroups = {
560         "sambaopts": options.SambaOptions,
561         "versionopts": options.VersionOptions,
562         "credopts": options.CredentialsOptions,
563     }
564
565     takes_options = [
566         Option("--server", help="DC to join", type=str),
567         Option("--site", help="site to join", type=str),
568         Option("--targetdir", help="where to store provision", type=str),
569         Option("--domain-critical-only",
570                help="only replicate critical domain objects",
571                action="store_true"),
572         Option("--machinepass", type=str, metavar="PASSWORD",
573                help="choose machine password (otherwise random)"),
574         Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
575                choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
576                help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
577                    "BIND9_DLZ uses samba4 AD to store zone information, "
578                    "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
579                default="SAMBA_INTERNAL"),
580         Option("--quiet", help="Be quiet", action="store_true"),
581         Option("--verbose", help="Be verbose", action="store_true")
582         ]
583
584     ntvfs_options = [
585          Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
586     ]
587
588     if samba.is_ntvfs_fileserver_built():
589          takes_options.extend(ntvfs_options)
590
591
592     takes_args = ["domain", "role?"]
593
594     def run(self, domain, role=None, sambaopts=None, credopts=None,
595             versionopts=None, server=None, site=None, targetdir=None,
596             domain_critical_only=False, parent_domain=None, machinepass=None,
597             use_ntvfs=False, dns_backend=None,
598             quiet=False, verbose=False):
599         lp = sambaopts.get_loadparm()
600         creds = credopts.get_credentials(lp)
601         net = Net(creds, lp, server=credopts.ipaddress)
602
603         logger = self.get_logger()
604         if verbose:
605             logger.setLevel(logging.DEBUG)
606         elif quiet:
607             logger.setLevel(logging.WARNING)
608         else:
609             logger.setLevel(logging.INFO)
610
611         netbios_name = lp.get("netbios name")
612
613         if not role is None:
614             role = role.upper()
615
616         if role == "DC":
617             join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
618                     site=site, netbios_name=netbios_name, targetdir=targetdir,
619                     domain_critical_only=domain_critical_only,
620                     machinepass=machinepass, use_ntvfs=use_ntvfs,
621                     dns_backend=dns_backend,
622                     promote_existing=True)
623         elif role == "RODC":
624             join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
625                       site=site, netbios_name=netbios_name, targetdir=targetdir,
626                       domain_critical_only=domain_critical_only,
627                       machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
628                       promote_existing=True)
629         else:
630             raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
631
632
633 class cmd_domain_join(Command):
634     """Join domain as either member or backup domain controller."""
635
636     synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
637
638     takes_optiongroups = {
639         "sambaopts": options.SambaOptions,
640         "versionopts": options.VersionOptions,
641         "credopts": options.CredentialsOptions,
642     }
643
644     takes_options = [
645         Option("--server", help="DC to join", type=str),
646         Option("--site", help="site to join", type=str),
647         Option("--targetdir", help="where to store provision", type=str),
648         Option("--parent-domain", help="parent domain to create subdomain under", type=str),
649         Option("--domain-critical-only",
650                help="only replicate critical domain objects",
651                action="store_true"),
652         Option("--machinepass", type=str, metavar="PASSWORD",
653                help="choose machine password (otherwise random)"),
654         Option("--adminpass", type="string", metavar="PASSWORD",
655                help="choose adminstrator password when joining as a subdomain (otherwise random)"),
656         Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
657                choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
658                help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
659                    "BIND9_DLZ uses samba4 AD to store zone information, "
660                    "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
661                default="SAMBA_INTERNAL"),
662         Option("--plaintext-secrets", action="store_true",
663                help="Store secret/sensitive values as plain text on disk" +
664                     "(default is to encrypt secret/ensitive values)"),
665         Option("--quiet", help="Be quiet", action="store_true"),
666         Option("--verbose", help="Be verbose", action="store_true")
667        ]
668
669     ntvfs_options = [
670         Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
671                action="store_true")
672     ]
673     if samba.is_ntvfs_fileserver_built():
674         takes_options.extend(ntvfs_options)
675
676     takes_args = ["domain", "role?"]
677
678     def run(self, domain, role=None, sambaopts=None, credopts=None,
679             versionopts=None, server=None, site=None, targetdir=None,
680             domain_critical_only=False, parent_domain=None, machinepass=None,
681             use_ntvfs=False, dns_backend=None, adminpass=None,
682             quiet=False, verbose=False, plaintext_secrets=False):
683         lp = sambaopts.get_loadparm()
684         creds = credopts.get_credentials(lp)
685         net = Net(creds, lp, server=credopts.ipaddress)
686
687         if site is None:
688             site = "Default-First-Site-Name"
689
690         logger = self.get_logger()
691         if verbose:
692             logger.setLevel(logging.DEBUG)
693         elif quiet:
694             logger.setLevel(logging.WARNING)
695         else:
696             logger.setLevel(logging.INFO)
697
698         netbios_name = lp.get("netbios name")
699
700         if not role is None:
701             role = role.upper()
702
703         if role is None or role == "MEMBER":
704             (join_password, sid, domain_name) = net.join_member(
705                 domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
706                 machinepass=machinepass)
707
708             self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
709         elif role == "DC":
710             join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
711                     site=site, netbios_name=netbios_name, targetdir=targetdir,
712                     domain_critical_only=domain_critical_only,
713                     machinepass=machinepass, use_ntvfs=use_ntvfs,
714                     dns_backend=dns_backend,
715                     plaintext_secrets=plaintext_secrets)
716         elif role == "RODC":
717             join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
718                       site=site, netbios_name=netbios_name, targetdir=targetdir,
719                       domain_critical_only=domain_critical_only,
720                       machinepass=machinepass, use_ntvfs=use_ntvfs,
721                       dns_backend=dns_backend,
722                       plaintext_secrets=plaintext_secrets)
723         elif role == "SUBDOMAIN":
724             if not adminpass:
725                 logger.info("Administrator password will be set randomly!")
726
727             netbios_domain = lp.get("workgroup")
728             if parent_domain is None:
729                 parent_domain = ".".join(domain.split(".")[1:])
730             join_subdomain(logger=logger, server=server, creds=creds, lp=lp, dnsdomain=domain,
731                            parent_domain=parent_domain, site=site,
732                            netbios_name=netbios_name, netbios_domain=netbios_domain,
733                            targetdir=targetdir, machinepass=machinepass,
734                            use_ntvfs=use_ntvfs, dns_backend=dns_backend,
735                            adminpass=adminpass,
736                            plaintext_secrets=plaintext_secrets)
737         else:
738             raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role)
739
740
741 class cmd_domain_demote(Command):
742     """Demote ourselves from the role of Domain Controller."""
743
744     synopsis = "%prog [options]"
745
746     takes_options = [
747         Option("--server", help="writable DC to write demotion changes on", type=str),
748         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
749                metavar="URL", dest="H"),
750         Option("--remove-other-dead-server", help="Dead DC (name or NTDS GUID) "
751                "to remove ALL references to (rather than this DC)", type=str),
752         Option("--quiet", help="Be quiet", action="store_true"),
753         Option("--verbose", help="Be verbose", action="store_true"),
754         ]
755
756     takes_optiongroups = {
757         "sambaopts": options.SambaOptions,
758         "credopts": options.CredentialsOptions,
759         "versionopts": options.VersionOptions,
760         }
761
762     def run(self, sambaopts=None, credopts=None,
763             versionopts=None, server=None,
764             remove_other_dead_server=None, H=None,
765             verbose=False, quiet=False):
766         lp = sambaopts.get_loadparm()
767         creds = credopts.get_credentials(lp)
768         net = Net(creds, lp, server=credopts.ipaddress)
769
770         logger = self.get_logger()
771         if verbose:
772             logger.setLevel(logging.DEBUG)
773         elif quiet:
774             logger.setLevel(logging.WARNING)
775         else:
776             logger.setLevel(logging.INFO)
777
778         if remove_other_dead_server is not None:
779             if server is not None:
780                 samdb = SamDB(url="ldap://%s" % server,
781                               session_info=system_session(),
782                               credentials=creds, lp=lp)
783             else:
784                 samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
785             try:
786                 remove_dc.remove_dc(samdb, logger, remove_other_dead_server)
787             except remove_dc.DemoteException as err:
788                 raise CommandError("Demote failed: %s" % err)
789             return
790
791         netbios_name = lp.get("netbios name")
792         samdb = SamDB(url=H, session_info=system_session(), credentials=creds, lp=lp)
793         if not server:
794             res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
795             if (len(res) == 0):
796                 raise CommandError("Unable to search for servers")
797
798             if (len(res) == 1):
799                 raise CommandError("You are the latest server in the domain")
800
801             server = None
802             for e in res:
803                 if str(e["name"]).lower() != netbios_name.lower():
804                     server = e["dnsHostName"]
805                     break
806
807         ntds_guid = samdb.get_ntds_GUID()
808         msg = samdb.search(base=str(samdb.get_config_basedn()),
809             scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
810             attrs=['options'])
811         if len(msg) == 0 or "options" not in msg[0]:
812             raise CommandError("Failed to find options on %s" % ntds_guid)
813
814         ntds_dn = msg[0].dn
815         dsa_options = int(str(msg[0]['options']))
816
817         res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
818                             controls=["search_options:1:2"])
819
820         if len(res) != 0:
821             raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res))
822
823         self.errf.write("Using %s as partner server for the demotion\n" %
824                         server)
825         (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
826
827         self.errf.write("Deactivating inbound replication\n")
828
829         nmsg = ldb.Message()
830         nmsg.dn = msg[0].dn
831
832         if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
833             dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
834             nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
835             samdb.modify(nmsg)
836
837
838             self.errf.write("Asking partner server %s to synchronize from us\n"
839                             % server)
840             for part in (samdb.get_schema_basedn(),
841                             samdb.get_config_basedn(),
842                             samdb.get_root_basedn()):
843                 nc = drsuapi.DsReplicaObjectIdentifier()
844                 nc.dn = str(part)
845
846                 req1 = drsuapi.DsReplicaSyncRequest1()
847                 req1.naming_context = nc;
848                 req1.options = drsuapi.DRSUAPI_DRS_WRIT_REP
849                 req1.source_dsa_guid = misc.GUID(ntds_guid)
850
851                 try:
852                     drsuapiBind.DsReplicaSync(drsuapi_handle, 1, req1)
853                 except RuntimeError as e1:
854                     (werr, string) = e1.args
855                     if werr == werror.WERR_DS_DRA_NO_REPLICA:
856                         pass
857                     else:
858                         self.errf.write(
859                             "Error while replicating out last local changes from '%s' for demotion, "
860                             "re-enabling inbound replication\n" % part)
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("Error while sending a DsReplicaSync for partition '%s'" % str(part), string)
865         try:
866             remote_samdb = SamDB(url="ldap://%s" % server,
867                                 session_info=system_session(),
868                                 credentials=creds, lp=lp)
869
870             self.errf.write("Changing userControl and container\n")
871             res = remote_samdb.search(base=str(remote_samdb.domain_dn()),
872                                 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
873                                             netbios_name.upper(),
874                                 attrs=["userAccountControl"])
875             dc_dn = res[0].dn
876             uac = int(str(res[0]["userAccountControl"]))
877
878         except Exception as e:
879             if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
880                 self.errf.write(
881                     "Error while demoting, re-enabling inbound replication\n")
882                 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
883                 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
884                 samdb.modify(nmsg)
885             raise CommandError("Error while changing account control", e)
886
887         if (len(res) != 1):
888             if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
889                 self.errf.write(
890                     "Error while demoting, re-enabling inbound replication")
891                 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
892                 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
893                 samdb.modify(nmsg)
894             raise CommandError("Unable to find object with samaccountName = %s$"
895                                " in the remote dc" % netbios_name.upper())
896
897         olduac = uac
898
899         uac &= ~(UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION|UF_PARTIAL_SECRETS_ACCOUNT)
900         uac |= UF_WORKSTATION_TRUST_ACCOUNT
901
902         msg = ldb.Message()
903         msg.dn = dc_dn
904
905         msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
906                                                         ldb.FLAG_MOD_REPLACE,
907                                                         "userAccountControl")
908         try:
909             remote_samdb.modify(msg)
910         except Exception as e:
911             if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
912                 self.errf.write(
913                     "Error while demoting, re-enabling inbound replication")
914                 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
915                 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
916                 samdb.modify(nmsg)
917
918             raise CommandError("Error while changing account control", e)
919
920         parent = msg.dn.parent()
921         dc_name = res[0].dn.get_rdn_value()
922         rdn = "CN=%s" % dc_name
923
924         # Let's move to the Computer container
925         i = 0
926         newrdn = str(rdn)
927
928         computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.domain_dn()))
929         res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
930
931         if (len(res) != 0):
932             res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
933                                         scope=ldb.SCOPE_ONELEVEL)
934             while(len(res) != 0 and i < 100):
935                 i = i + 1
936                 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
937                                             scope=ldb.SCOPE_ONELEVEL)
938
939             if i == 100:
940                 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
941                     self.errf.write(
942                         "Error while demoting, re-enabling inbound replication\n")
943                     dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
944                     nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
945                     samdb.modify(nmsg)
946
947                 msg = ldb.Message()
948                 msg.dn = dc_dn
949
950                 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
951                                                         ldb.FLAG_MOD_REPLACE,
952                                                         "userAccountControl")
953
954                 remote_samdb.modify(msg)
955
956                 raise CommandError("Unable to find a slot for renaming %s,"
957                                     " all names from %s-1 to %s-%d seemed used" %
958                                     (str(dc_dn), rdn, rdn, i - 9))
959
960             newrdn = "%s-%d" % (rdn, i)
961
962         try:
963             newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
964             remote_samdb.rename(dc_dn, newdn)
965         except Exception as e:
966             if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
967                 self.errf.write(
968                     "Error while demoting, re-enabling inbound replication\n")
969                 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
970                 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
971                 samdb.modify(nmsg)
972
973             msg = ldb.Message()
974             msg.dn = dc_dn
975
976             msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
977                                                     ldb.FLAG_MOD_REPLACE,
978                                                     "userAccountControl")
979
980             remote_samdb.modify(msg)
981             raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
982
983
984         server_dsa_dn = samdb.get_serverName()
985         domain = remote_samdb.get_root_basedn()
986
987         try:
988             req1 = drsuapi.DsRemoveDSServerRequest1()
989             req1.server_dn = str(server_dsa_dn)
990             req1.domain_dn = str(domain)
991             req1.commit = 1
992
993             drsuapiBind.DsRemoveDSServer(drsuapi_handle, 1, req1)
994         except RuntimeError as e3:
995             (werr, string) = e3.args
996             if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
997                 self.errf.write(
998                     "Error while demoting, re-enabling inbound replication\n")
999                 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
1000                 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
1001                 samdb.modify(nmsg)
1002
1003             msg = ldb.Message()
1004             msg.dn = newdn
1005
1006             msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
1007                                                            ldb.FLAG_MOD_REPLACE,
1008                                                            "userAccountControl")
1009             remote_samdb.modify(msg)
1010             remote_samdb.rename(newdn, dc_dn)
1011             if werr == werror.WERR_DS_DRA_NO_REPLICA:
1012                 raise CommandError("The DC %s is not present on (already removed from) the remote server: " % server_dsa_dn, e)
1013             else:
1014                 raise CommandError("Error while sending a removeDsServer of %s: " % server_dsa_dn, e)
1015
1016         remove_dc.remove_sysvol_references(remote_samdb, logger, dc_name)
1017
1018         # These are objects under the computer account that should be deleted
1019         for s in ("CN=Enterprise,CN=NTFRS Subscriptions",
1020                   "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
1021                   "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
1022                   "CN=NTFRS Subscriptions"):
1023             try:
1024                 remote_samdb.delete(ldb.Dn(remote_samdb,
1025                                     "%s,%s" % (s, str(newdn))))
1026             except ldb.LdbError as l:
1027                 pass
1028
1029         self.errf.write("Demote successful\n")
1030
1031
1032 class cmd_domain_level(Command):
1033     """Raise domain and forest function levels."""
1034
1035     synopsis = "%prog (show|raise <options>) [options]"
1036
1037     takes_optiongroups = {
1038         "sambaopts": options.SambaOptions,
1039         "credopts": options.CredentialsOptions,
1040         "versionopts": options.VersionOptions,
1041         }
1042
1043     takes_options = [
1044         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1045                metavar="URL", dest="H"),
1046         Option("--quiet", help="Be quiet", action="store_true"),
1047         Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1048             help="The forest function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)"),
1049         Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1050             help="The domain function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)")
1051             ]
1052
1053     takes_args = ["subcommand"]
1054
1055     def run(self, subcommand, H=None, forest_level=None, domain_level=None,
1056             quiet=False, credopts=None, sambaopts=None, versionopts=None):
1057         lp = sambaopts.get_loadparm()
1058         creds = credopts.get_credentials(lp, fallback_machine=True)
1059
1060         samdb = SamDB(url=H, session_info=system_session(),
1061             credentials=creds, lp=lp)
1062
1063         domain_dn = samdb.domain_dn()
1064
1065         res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
1066           scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
1067         assert len(res_forest) == 1
1068
1069         res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1070           attrs=["msDS-Behavior-Version", "nTMixedDomain"])
1071         assert len(res_domain) == 1
1072
1073         res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
1074           scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
1075           attrs=["msDS-Behavior-Version"])
1076         assert len(res_dc_s) >= 1
1077
1078         # default values, since "msDS-Behavior-Version" does not exist on Windows 2000 AD
1079         level_forest = DS_DOMAIN_FUNCTION_2000
1080         level_domain = DS_DOMAIN_FUNCTION_2000
1081
1082         if "msDS-Behavior-Version" in res_forest[0]:
1083             level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
1084         if "msDS-Behavior-Version" in res_domain[0]:
1085             level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
1086         level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
1087
1088         min_level_dc = None
1089         for msg in res_dc_s:
1090             if "msDS-Behavior-Version" in msg:
1091                 if min_level_dc is None or int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
1092                     min_level_dc = int(msg["msDS-Behavior-Version"][0])
1093             else:
1094                 min_level_dc = DS_DOMAIN_FUNCTION_2000
1095                 # well, this is the least
1096                 break
1097
1098         if level_forest < DS_DOMAIN_FUNCTION_2000 or level_domain < DS_DOMAIN_FUNCTION_2000:
1099             raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
1100         if min_level_dc < DS_DOMAIN_FUNCTION_2000:
1101             raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
1102         if level_forest > level_domain:
1103             raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
1104         if level_domain > min_level_dc:
1105             raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
1106
1107         if subcommand == "show":
1108             self.message("Domain and forest function level for domain '%s'" % domain_dn)
1109             if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1110                 self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1111             if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1112                 self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1113             if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1114                 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)!")
1115
1116             self.message("")
1117
1118             if level_forest == DS_DOMAIN_FUNCTION_2000:
1119                 outstr = "2000"
1120             elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
1121                 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1122             elif level_forest == DS_DOMAIN_FUNCTION_2003:
1123                 outstr = "2003"
1124             elif level_forest == DS_DOMAIN_FUNCTION_2008:
1125                 outstr = "2008"
1126             elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
1127                 outstr = "2008 R2"
1128             elif level_forest == DS_DOMAIN_FUNCTION_2012:
1129                 outstr = "2012"
1130             elif level_forest == DS_DOMAIN_FUNCTION_2012_R2:
1131                 outstr = "2012 R2"
1132             else:
1133                 outstr = "higher than 2012 R2"
1134             self.message("Forest function level: (Windows) " + outstr)
1135
1136             if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
1137                 outstr = "2000 mixed (NT4 DC support)"
1138             elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
1139                 outstr = "2000"
1140             elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
1141                 outstr = "2003 with mixed domains/interim (NT4 DC support)"
1142             elif level_domain == DS_DOMAIN_FUNCTION_2003:
1143                 outstr = "2003"
1144             elif level_domain == DS_DOMAIN_FUNCTION_2008:
1145                 outstr = "2008"
1146             elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
1147                 outstr = "2008 R2"
1148             elif level_domain == DS_DOMAIN_FUNCTION_2012:
1149                 outstr = "2012"
1150             elif level_domain == DS_DOMAIN_FUNCTION_2012_R2:
1151                 outstr = "2012 R2"
1152             else:
1153                 outstr = "higher than 2012 R2"
1154             self.message("Domain function level: (Windows) " + outstr)
1155
1156             if min_level_dc == DS_DOMAIN_FUNCTION_2000:
1157                 outstr = "2000"
1158             elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
1159                 outstr = "2003"
1160             elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
1161                 outstr = "2008"
1162             elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
1163                 outstr = "2008 R2"
1164             elif min_level_dc == DS_DOMAIN_FUNCTION_2012:
1165                 outstr = "2012"
1166             elif min_level_dc == DS_DOMAIN_FUNCTION_2012_R2:
1167                 outstr = "2012 R2"
1168             else:
1169                 outstr = "higher than 2012 R2"
1170             self.message("Lowest function level of a DC: (Windows) " + outstr)
1171
1172         elif subcommand == "raise":
1173             msgs = []
1174
1175             if domain_level is not None:
1176                 if domain_level == "2003":
1177                     new_level_domain = DS_DOMAIN_FUNCTION_2003
1178                 elif domain_level == "2008":
1179                     new_level_domain = DS_DOMAIN_FUNCTION_2008
1180                 elif domain_level == "2008_R2":
1181                     new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
1182                 elif domain_level == "2012":
1183                     new_level_domain = DS_DOMAIN_FUNCTION_2012
1184                 elif domain_level == "2012_R2":
1185                     new_level_domain = DS_DOMAIN_FUNCTION_2012_R2
1186
1187                 if new_level_domain <= level_domain and level_domain_mixed == 0:
1188                     raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
1189                 if new_level_domain > min_level_dc:
1190                     raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
1191
1192                 # Deactivate mixed/interim domain support
1193                 if level_domain_mixed != 0:
1194                     # Directly on the base DN
1195                     m = ldb.Message()
1196                     m.dn = ldb.Dn(samdb, domain_dn)
1197                     m["nTMixedDomain"] = ldb.MessageElement("0",
1198                       ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1199                     samdb.modify(m)
1200                     # Under partitions
1201                     m = ldb.Message()
1202                     m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
1203                     m["nTMixedDomain"] = ldb.MessageElement("0",
1204                       ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1205                     try:
1206                         samdb.modify(m)
1207                     except ldb.LdbError as e:
1208                         (enum, emsg) = e.args
1209                         if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1210                             raise
1211
1212                 # Directly on the base DN
1213                 m = ldb.Message()
1214                 m.dn = ldb.Dn(samdb, domain_dn)
1215                 m["msDS-Behavior-Version"]= ldb.MessageElement(
1216                   str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1217                             "msDS-Behavior-Version")
1218                 samdb.modify(m)
1219                 # Under partitions
1220                 m = ldb.Message()
1221                 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
1222                   + ",CN=Partitions,%s" % samdb.get_config_basedn())
1223                 m["msDS-Behavior-Version"]= ldb.MessageElement(
1224                   str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1225                           "msDS-Behavior-Version")
1226                 try:
1227                     samdb.modify(m)
1228                 except ldb.LdbError as e2:
1229                     (enum, emsg) = e2.args
1230                     if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1231                         raise
1232
1233                 level_domain = new_level_domain
1234                 msgs.append("Domain function level changed!")
1235
1236             if forest_level is not None:
1237                 if forest_level == "2003":
1238                     new_level_forest = DS_DOMAIN_FUNCTION_2003
1239                 elif forest_level == "2008":
1240                     new_level_forest = DS_DOMAIN_FUNCTION_2008
1241                 elif forest_level == "2008_R2":
1242                     new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
1243                 elif forest_level == "2012":
1244                     new_level_forest = DS_DOMAIN_FUNCTION_2012
1245                 elif forest_level == "2012_R2":
1246                     new_level_forest = DS_DOMAIN_FUNCTION_2012_R2
1247
1248                 if new_level_forest <= level_forest:
1249                     raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1250                 if new_level_forest > level_domain:
1251                     raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1252
1253                 m = ldb.Message()
1254                 m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
1255                 m["msDS-Behavior-Version"]= ldb.MessageElement(
1256                   str(new_level_forest), ldb.FLAG_MOD_REPLACE,
1257                           "msDS-Behavior-Version")
1258                 samdb.modify(m)
1259                 msgs.append("Forest function level changed!")
1260             msgs.append("All changes applied successfully!")
1261             self.message("\n".join(msgs))
1262         else:
1263             raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
1264
1265
1266 class cmd_domain_passwordsettings(Command):
1267     """Set password settings.
1268
1269     Password complexity, password lockout policy, history length,
1270     minimum password length, the minimum and maximum password age) on
1271     a Samba AD DC server.
1272
1273     Use against a Windows DC is possible, but group policy will override it.
1274     """
1275
1276     synopsis = "%prog (show|set <options>) [options]"
1277
1278     takes_optiongroups = {
1279         "sambaopts": options.SambaOptions,
1280         "versionopts": options.VersionOptions,
1281         "credopts": options.CredentialsOptions,
1282         }
1283
1284     takes_options = [
1285         Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1286                metavar="URL", dest="H"),
1287         Option("--quiet", help="Be quiet", action="store_true"),
1288         Option("--complexity", type="choice", choices=["on","off","default"],
1289           help="The password complexity (on | off | default). Default is 'on'"),
1290         Option("--store-plaintext", type="choice", choices=["on","off","default"],
1291           help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1292         Option("--history-length",
1293           help="The password history length (<integer> | default).  Default is 24.", type=str),
1294         Option("--min-pwd-length",
1295           help="The minimum password length (<integer> | default).  Default is 7.", type=str),
1296         Option("--min-pwd-age",
1297           help="The minimum password age (<integer in days> | default).  Default is 1.", type=str),
1298         Option("--max-pwd-age",
1299           help="The maximum password age (<integer in days> | default).  Default is 43.", type=str),
1300         Option("--account-lockout-duration",
1301           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),
1302         Option("--account-lockout-threshold",
1303           help="The number of bad password attempts allowed before locking out the account (<integer> | default).  Default is 0 (never lock out).", type=str),
1304         Option("--reset-account-lockout-after",
1305           help="After this time is elapsed, the recorded number of attempts restarts from zero (<integer> | default).  Default is 30.", type=str),
1306           ]
1307
1308     takes_args = ["subcommand"]
1309
1310     def run(self, subcommand, H=None, min_pwd_age=None, max_pwd_age=None,
1311             quiet=False, complexity=None, store_plaintext=None, history_length=None,
1312             min_pwd_length=None, account_lockout_duration=None, account_lockout_threshold=None,
1313             reset_account_lockout_after=None, credopts=None, sambaopts=None,
1314             versionopts=None):
1315         lp = sambaopts.get_loadparm()
1316         creds = credopts.get_credentials(lp)
1317
1318         samdb = SamDB(url=H, session_info=system_session(),
1319             credentials=creds, lp=lp)
1320
1321         domain_dn = samdb.domain_dn()
1322         res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1323           attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1324                  "minPwdAge", "maxPwdAge", "lockoutDuration", "lockoutThreshold",
1325                  "lockOutObservationWindow"])
1326         assert(len(res) == 1)
1327         try:
1328             pwd_props = int(res[0]["pwdProperties"][0])
1329             pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
1330             cur_min_pwd_len = int(res[0]["minPwdLength"][0])
1331             # ticks -> days
1332             cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1333             if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000:
1334                 cur_max_pwd_age = 0
1335             else:
1336                 cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1337             cur_account_lockout_threshold = int(res[0]["lockoutThreshold"][0])
1338             # ticks -> mins
1339             if int(res[0]["lockoutDuration"][0]) == -0x8000000000000000:
1340                 cur_account_lockout_duration = 0
1341             else:
1342                 cur_account_lockout_duration = abs(int(res[0]["lockoutDuration"][0])) / (1e7 * 60)
1343             cur_reset_account_lockout_after = abs(int(res[0]["lockOutObservationWindow"][0])) / (1e7 * 60)
1344         except Exception as e:
1345             raise CommandError("Could not retrieve password properties!", e)
1346
1347         if subcommand == "show":
1348             self.message("Password informations for domain '%s'" % domain_dn)
1349             self.message("")
1350             if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
1351                 self.message("Password complexity: on")
1352             else:
1353                 self.message("Password complexity: off")
1354             if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
1355                 self.message("Store plaintext passwords: on")
1356             else:
1357                 self.message("Store plaintext passwords: off")
1358             self.message("Password history length: %d" % pwd_hist_len)
1359             self.message("Minimum password length: %d" % cur_min_pwd_len)
1360             self.message("Minimum password age (days): %d" % cur_min_pwd_age)
1361             self.message("Maximum password age (days): %d" % cur_max_pwd_age)
1362             self.message("Account lockout duration (mins): %d" % cur_account_lockout_duration)
1363             self.message("Account lockout threshold (attempts): %d" % cur_account_lockout_threshold)
1364             self.message("Reset account lockout after (mins): %d" % cur_reset_account_lockout_after)
1365         elif subcommand == "set":
1366             msgs = []
1367             m = ldb.Message()
1368             m.dn = ldb.Dn(samdb, domain_dn)
1369
1370             if complexity is not None:
1371                 if complexity == "on" or complexity == "default":
1372                     pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
1373                     msgs.append("Password complexity activated!")
1374                 elif complexity == "off":
1375                     pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
1376                     msgs.append("Password complexity deactivated!")
1377
1378             if store_plaintext is not None:
1379                 if store_plaintext == "on" or store_plaintext == "default":
1380                     pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1381                     msgs.append("Plaintext password storage for changed passwords activated!")
1382                 elif store_plaintext == "off":
1383                     pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
1384                     msgs.append("Plaintext password storage for changed passwords deactivated!")
1385
1386             if complexity is not None or store_plaintext is not None:
1387                 m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
1388                   ldb.FLAG_MOD_REPLACE, "pwdProperties")
1389
1390             if history_length is not None:
1391                 if history_length == "default":
1392                     pwd_hist_len = 24
1393                 else:
1394                     pwd_hist_len = int(history_length)
1395
1396                 if pwd_hist_len < 0 or pwd_hist_len > 24:
1397                     raise CommandError("Password history length must be in the range of 0 to 24!")
1398
1399                 m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
1400                   ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
1401                 msgs.append("Password history length changed!")
1402
1403             if min_pwd_length is not None:
1404                 if min_pwd_length == "default":
1405                     min_pwd_len = 7
1406                 else:
1407                     min_pwd_len = int(min_pwd_length)
1408
1409                 if min_pwd_len < 0 or min_pwd_len > 14:
1410                     raise CommandError("Minimum password length must be in the range of 0 to 14!")
1411
1412                 m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
1413                   ldb.FLAG_MOD_REPLACE, "minPwdLength")
1414                 msgs.append("Minimum password length changed!")
1415
1416             if min_pwd_age is not None:
1417                 if min_pwd_age == "default":
1418                     min_pwd_age = 1
1419                 else:
1420                     min_pwd_age = int(min_pwd_age)
1421
1422                 if min_pwd_age < 0 or min_pwd_age > 998:
1423                     raise CommandError("Minimum password age must be in the range of 0 to 998!")
1424
1425                 # days -> ticks
1426                 min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
1427
1428                 m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
1429                   ldb.FLAG_MOD_REPLACE, "minPwdAge")
1430                 msgs.append("Minimum password age changed!")
1431
1432             if max_pwd_age is not None:
1433                 if max_pwd_age == "default":
1434                     max_pwd_age = 43
1435                 else:
1436                     max_pwd_age = int(max_pwd_age)
1437
1438                 if max_pwd_age < 0 or max_pwd_age > 999:
1439                     raise CommandError("Maximum password age must be in the range of 0 to 999!")
1440
1441                 # days -> ticks
1442                 if max_pwd_age == 0:
1443                     max_pwd_age_ticks = -0x8000000000000000
1444                 else:
1445                     max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
1446
1447                 m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
1448                   ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1449                 msgs.append("Maximum password age changed!")
1450
1451             if account_lockout_duration is not None:
1452                 if account_lockout_duration == "default":
1453                     account_lockout_duration = 30
1454                 else:
1455                     account_lockout_duration = int(account_lockout_duration)
1456
1457                 if account_lockout_duration < 0 or account_lockout_duration > 99999:
1458                     raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1459
1460                 # days -> ticks
1461                 if account_lockout_duration == 0:
1462                     account_lockout_duration_ticks = -0x8000000000000000
1463                 else:
1464                     account_lockout_duration_ticks = -int(account_lockout_duration * (60 * 1e7))
1465
1466                 m["lockoutDuration"] = ldb.MessageElement(str(account_lockout_duration_ticks),
1467                   ldb.FLAG_MOD_REPLACE, "lockoutDuration")
1468                 msgs.append("Account lockout duration changed!")
1469
1470             if account_lockout_threshold is not None:
1471                 if account_lockout_threshold == "default":
1472                     account_lockout_threshold = 0
1473                 else:
1474                     account_lockout_threshold = int(account_lockout_threshold)
1475
1476                 m["lockoutThreshold"] = ldb.MessageElement(str(account_lockout_threshold),
1477                   ldb.FLAG_MOD_REPLACE, "lockoutThreshold")
1478                 msgs.append("Account lockout threshold changed!")
1479
1480             if reset_account_lockout_after is not None:
1481                 if reset_account_lockout_after == "default":
1482                     reset_account_lockout_after = 30
1483                 else:
1484                     reset_account_lockout_after = int(reset_account_lockout_after)
1485
1486                 if reset_account_lockout_after < 0 or reset_account_lockout_after > 99999:
1487                     raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1488
1489                 # days -> ticks
1490                 if reset_account_lockout_after == 0:
1491                     reset_account_lockout_after_ticks = -0x8000000000000000
1492                 else:
1493                     reset_account_lockout_after_ticks = -int(reset_account_lockout_after * (60 * 1e7))
1494
1495                 m["lockOutObservationWindow"] = ldb.MessageElement(str(reset_account_lockout_after_ticks),
1496                   ldb.FLAG_MOD_REPLACE, "lockOutObservationWindow")
1497                 msgs.append("Duration to reset account lockout after changed!")
1498
1499             if max_pwd_age > 0 and min_pwd_age >= max_pwd_age:
1500                 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
1501
1502             if len(m) == 0:
1503                 raise CommandError("You must specify at least one option to set. Try --help")
1504             samdb.modify(m)
1505             msgs.append("All changes applied successfully!")
1506             self.message("\n".join(msgs))
1507         else:
1508             raise CommandError("Wrong argument '%s'!" % subcommand)
1509
1510
1511 class cmd_domain_classicupgrade(Command):
1512     """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1513
1514     Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1515     the testparm utility from your classic installation (with --testparm).
1516     """
1517
1518     synopsis = "%prog [options] <classic_smb_conf>"
1519
1520     takes_optiongroups = {
1521         "sambaopts": options.SambaOptions,
1522         "versionopts": options.VersionOptions
1523     }
1524
1525     takes_options = [
1526         Option("--dbdir", type="string", metavar="DIR",
1527                   help="Path to samba classic DC database directory"),
1528         Option("--testparm", type="string", metavar="PATH",
1529                   help="Path to samba classic DC testparm utility from the previous installation.  This allows the default paths of the previous installation to be followed"),
1530         Option("--targetdir", type="string", metavar="DIR",
1531                   help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1532         Option("--quiet", help="Be quiet", action="store_true"),
1533         Option("--verbose", help="Be verbose", action="store_true"),
1534         Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
1535                choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1536                help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1537                    "BIND9_FLATFILE uses bind9 text database to store zone information, "
1538                    "BIND9_DLZ uses samba4 AD to store zone information, "
1539                    "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1540                default="SAMBA_INTERNAL")
1541     ]
1542
1543     ntvfs_options = [
1544         Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
1545                action="store_true"),
1546         Option("--use-xattrs", type="choice", choices=["yes","no","auto"],
1547                metavar="[yes|no|auto]",
1548                help="Define if we should use the native fs capabilities or a tdb file for "
1549                "storing attributes likes ntacl when --use-ntvfs is set. "
1550                "auto tries to make an inteligent guess based on the user rights and system capabilities",
1551                default="auto")
1552     ]
1553     if samba.is_ntvfs_fileserver_built():
1554         takes_options.extend(ntvfs_options)
1555
1556     takes_args = ["smbconf"]
1557
1558     def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
1559             quiet=False, verbose=False, use_xattrs="auto", sambaopts=None, versionopts=None,
1560             dns_backend=None, use_ntvfs=False):
1561
1562         if not os.path.exists(smbconf):
1563             raise CommandError("File %s does not exist" % smbconf)
1564
1565         if testparm and not os.path.exists(testparm):
1566             raise CommandError("Testparm utility %s does not exist" % testparm)
1567
1568         if dbdir and not os.path.exists(dbdir):
1569             raise CommandError("Directory %s does not exist" % dbdir)
1570
1571         if not dbdir and not testparm:
1572             raise CommandError("Please specify either dbdir or testparm")
1573
1574         logger = self.get_logger()
1575         if verbose:
1576             logger.setLevel(logging.DEBUG)
1577         elif quiet:
1578             logger.setLevel(logging.WARNING)
1579         else:
1580             logger.setLevel(logging.INFO)
1581
1582         if dbdir and testparm:
1583             logger.warning("both dbdir and testparm specified, ignoring dbdir.")
1584             dbdir = None
1585
1586         lp = sambaopts.get_loadparm()
1587
1588         s3conf = s3param.get_context()
1589
1590         if sambaopts.realm:
1591             s3conf.set("realm", sambaopts.realm)
1592
1593         if targetdir is not None:
1594             if not os.path.isdir(targetdir):
1595                 os.mkdir(targetdir)
1596
1597         eadb = True
1598         if use_xattrs == "yes":
1599             eadb = False
1600         elif use_xattrs == "auto" and use_ntvfs == False:
1601             eadb = False
1602         elif use_ntvfs == False:
1603             raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use).  "
1604                                "Please re-run with --use-xattrs omitted.")
1605         elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
1606             if targetdir:
1607                 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
1608             else:
1609                 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
1610             try:
1611                 try:
1612                     samba.ntacls.setntacl(lp, tmpfile.name,
1613                                 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1614                     eadb = False
1615                 except Exception:
1616                     # FIXME: Don't catch all exceptions here
1617                     logger.info("You are not root or your system does not support xattr, using tdb backend for attributes. "
1618                                 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1619             finally:
1620                 tmpfile.close()
1621
1622         # Set correct default values from dbdir or testparm
1623         paths = {}
1624         if dbdir:
1625             paths["state directory"] = dbdir
1626             paths["private dir"] = dbdir
1627             paths["lock directory"] = dbdir
1628             paths["smb passwd file"] = dbdir + "/smbpasswd"
1629         else:
1630             paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
1631             paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
1632             paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
1633             paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
1634             # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1635             # "state directory", instead make use of "lock directory"
1636             if len(paths["state directory"]) == 0:
1637                 paths["state directory"] = paths["lock directory"]
1638
1639         for p in paths:
1640             s3conf.set(p, paths[p])
1641
1642         # load smb.conf parameters
1643         logger.info("Reading smb.conf")
1644         s3conf.load(smbconf)
1645         samba3 = Samba3(smbconf, s3conf)
1646
1647         logger.info("Provisioning")
1648         upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
1649                             useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
1650
1651
1652 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
1653     __doc__ = cmd_domain_classicupgrade.__doc__
1654
1655     # This command is present for backwards compatibility only,
1656     # and should not be shown.
1657
1658     hidden = True
1659
1660 class LocalDCCredentialsOptions(options.CredentialsOptions):
1661     def __init__(self, parser):
1662         options.CredentialsOptions.__init__(self, parser, special_name="local-dc")
1663
1664 class DomainTrustCommand(Command):
1665     """List domain trusts."""
1666
1667     def __init__(self):
1668         Command.__init__(self)
1669         self.local_lp = None
1670
1671         self.local_server = None
1672         self.local_binding_string = None
1673         self.local_creds = None
1674
1675         self.remote_server = None
1676         self.remote_binding_string = None
1677         self.remote_creds = None
1678
1679     def _uint32(self, v):
1680         return ctypes.c_uint32(v).value
1681
1682     def check_runtime_error(self, runtime, val):
1683         if runtime is None:
1684             return False
1685
1686         err32 = self._uint32(runtime[0])
1687         if err32 == val:
1688             return True
1689
1690         return False
1691
1692     class LocalRuntimeError(CommandError):
1693         def __init__(exception_self, self, runtime, message):
1694             err32 = self._uint32(runtime[0])
1695             errstr = runtime[1]
1696             msg = "LOCAL_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1697                   self.local_server, message, err32, errstr)
1698             CommandError.__init__(exception_self, msg)
1699
1700     class RemoteRuntimeError(CommandError):
1701         def __init__(exception_self, self, runtime, message):
1702             err32 = self._uint32(runtime[0])
1703             errstr = runtime[1]
1704             msg = "REMOTE_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1705                   self.remote_server, message, err32, errstr)
1706             CommandError.__init__(exception_self, msg)
1707
1708     class LocalLdbError(CommandError):
1709         def __init__(exception_self, self, ldb_error, message):
1710             errval = ldb_error[0]
1711             errstr = ldb_error[1]
1712             msg = "LOCAL_DC[%s]: %s - ERROR(%d) - %s" % (
1713                   self.local_server, message, errval, errstr)
1714             CommandError.__init__(exception_self, msg)
1715
1716     def setup_local_server(self, sambaopts, localdcopts):
1717         if self.local_server is not None:
1718             return self.local_server
1719
1720         lp = sambaopts.get_loadparm()
1721
1722         local_server = localdcopts.ipaddress
1723         if local_server is None:
1724             server_role = lp.server_role()
1725             if server_role != "ROLE_ACTIVE_DIRECTORY_DC":
1726                 raise CommandError("Invalid server_role %s" % (server_role))
1727             local_server = lp.get('netbios name')
1728             local_transport = "ncalrpc"
1729             local_binding_options = ""
1730             local_binding_options += ",auth_type=ncalrpc_as_system"
1731             local_ldap_url = None
1732             local_creds = None
1733         else:
1734             local_transport = "ncacn_np"
1735             local_binding_options = ""
1736             local_ldap_url = "ldap://%s" % local_server
1737             local_creds = localdcopts.get_credentials(lp)
1738
1739         self.local_lp = lp
1740
1741         self.local_server = local_server
1742         self.local_binding_string = "%s:%s[%s]" % (local_transport, local_server, local_binding_options)
1743         self.local_ldap_url = local_ldap_url
1744         self.local_creds = local_creds
1745         return self.local_server
1746
1747     def new_local_lsa_connection(self):
1748         return lsa.lsarpc(self.local_binding_string, self.local_lp, self.local_creds)
1749
1750     def new_local_netlogon_connection(self):
1751         return netlogon.netlogon(self.local_binding_string, self.local_lp, self.local_creds)
1752
1753     def new_local_ldap_connection(self):
1754         return SamDB(url=self.local_ldap_url,
1755                      session_info=system_session(),
1756                      credentials=self.local_creds,
1757                      lp=self.local_lp)
1758
1759     def setup_remote_server(self, credopts, domain,
1760                             require_pdc=True,
1761                             require_writable=True):
1762
1763         if require_pdc:
1764             assert require_writable
1765
1766         if self.remote_server is not None:
1767             return self.remote_server
1768
1769         self.remote_server = "__unknown__remote_server__.%s" % domain
1770         assert self.local_server is not None
1771
1772         remote_creds = credopts.get_credentials(self.local_lp)
1773         remote_server = credopts.ipaddress
1774         remote_binding_options = ""
1775
1776         # TODO: we should also support NT4 domains
1777         # we could use local_netlogon.netr_DsRGetDCNameEx2() with the remote domain name
1778         # and delegate NBT or CLDAP to the local netlogon server
1779         try:
1780             remote_net = Net(remote_creds, self.local_lp, server=remote_server)
1781             remote_flags = nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS
1782             if require_writable:
1783                 remote_flags |= nbt.NBT_SERVER_WRITABLE
1784             if require_pdc:
1785                 remote_flags |= nbt.NBT_SERVER_PDC
1786             remote_info = remote_net.finddc(flags=remote_flags, domain=domain, address=remote_server)
1787         except NTSTATUSError as error:
1788             raise CommandError("Failed to find a writeable DC for domain '%s': %s" %
1789                                (domain, error[1]))
1790         except Exception:
1791             raise CommandError("Failed to find a writeable DC for domain '%s'" % domain)
1792         flag_map = {
1793             nbt.NBT_SERVER_PDC: "PDC",
1794             nbt.NBT_SERVER_GC: "GC",
1795             nbt.NBT_SERVER_LDAP: "LDAP",
1796             nbt.NBT_SERVER_DS: "DS",
1797             nbt.NBT_SERVER_KDC: "KDC",
1798             nbt.NBT_SERVER_TIMESERV: "TIMESERV",
1799             nbt.NBT_SERVER_CLOSEST: "CLOSEST",
1800             nbt.NBT_SERVER_WRITABLE: "WRITABLE",
1801             nbt.NBT_SERVER_GOOD_TIMESERV: "GOOD_TIMESERV",
1802             nbt.NBT_SERVER_NDNC: "NDNC",
1803             nbt.NBT_SERVER_SELECT_SECRET_DOMAIN_6: "SELECT_SECRET_DOMAIN_6",
1804             nbt.NBT_SERVER_FULL_SECRET_DOMAIN_6: "FULL_SECRET_DOMAIN_6",
1805             nbt.NBT_SERVER_ADS_WEB_SERVICE: "ADS_WEB_SERVICE",
1806             nbt.NBT_SERVER_DS_8: "DS_8",
1807             nbt.NBT_SERVER_HAS_DNS_NAME: "HAS_DNS_NAME",
1808             nbt.NBT_SERVER_IS_DEFAULT_NC: "IS_DEFAULT_NC",
1809             nbt.NBT_SERVER_FOREST_ROOT: "FOREST_ROOT",
1810         }
1811         server_type_string = self.generic_bitmap_to_string(flag_map,
1812                                 remote_info.server_type, names_only=True)
1813         self.outf.write("RemoteDC Netbios[%s] DNS[%s] ServerType[%s]\n" % (
1814                         remote_info.pdc_name,
1815                         remote_info.pdc_dns_name,
1816                         server_type_string))
1817
1818         self.remote_server = remote_info.pdc_dns_name
1819         self.remote_binding_string="ncacn_np:%s[%s]" % (self.remote_server, remote_binding_options)
1820         self.remote_creds = remote_creds
1821         return self.remote_server
1822
1823     def new_remote_lsa_connection(self):
1824         return lsa.lsarpc(self.remote_binding_string, self.local_lp, self.remote_creds)
1825
1826     def new_remote_netlogon_connection(self):
1827         return netlogon.netlogon(self.remote_binding_string, self.local_lp, self.remote_creds)
1828
1829     def get_lsa_info(self, conn, policy_access):
1830         objectAttr = lsa.ObjectAttribute()
1831         objectAttr.sec_qos = lsa.QosInfo()
1832
1833         policy = conn.OpenPolicy2(''.decode('utf-8'),
1834                                   objectAttr, policy_access)
1835
1836         info = conn.QueryInfoPolicy2(policy, lsa.LSA_POLICY_INFO_DNS)
1837
1838         return (policy, info)
1839
1840     def get_netlogon_dc_info(self, conn, server):
1841         info = conn.netr_DsRGetDCNameEx2(server,
1842                                          None, 0, None, None, None,
1843                                          netlogon.DS_RETURN_DNS_NAME)
1844         return info
1845
1846     def netr_DomainTrust_to_name(self, t):
1847         if t.trust_type == lsa.LSA_TRUST_TYPE_DOWNLEVEL:
1848              return t.netbios_name
1849
1850         return t.dns_name
1851
1852     def netr_DomainTrust_to_type(self, a, t):
1853         primary = None
1854         primary_parent = None
1855         for _t in a:
1856              if _t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
1857                   primary = _t
1858                   if not _t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1859                       primary_parent = a[_t.parent_index]
1860                   break
1861
1862         if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1863             if t is primary_parent:
1864                 return "Parent"
1865
1866             if t.trust_flags & netlogon.NETR_TRUST_FLAG_TREEROOT:
1867                 return "TreeRoot"
1868
1869             parent = a[t.parent_index]
1870             if parent is primary:
1871                 return "Child"
1872
1873             return "Shortcut"
1874
1875         if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1876             return "Forest"
1877
1878         return "External"
1879
1880     def netr_DomainTrust_to_transitive(self, t):
1881         if t.trust_flags & netlogon.NETR_TRUST_FLAG_IN_FOREST:
1882             return "Yes"
1883
1884         if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE:
1885             return "No"
1886
1887         if t.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
1888             return "Yes"
1889
1890         return "No"
1891
1892     def netr_DomainTrust_to_direction(self, t):
1893         if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND and \
1894            t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1895             return "BOTH"
1896
1897         if t.trust_flags & netlogon.NETR_TRUST_FLAG_INBOUND:
1898             return "INCOMING"
1899
1900         if t.trust_flags & netlogon.NETR_TRUST_FLAG_OUTBOUND:
1901             return "OUTGOING"
1902
1903         return "INVALID"
1904
1905     def generic_enum_to_string(self, e_dict, v, names_only=False):
1906         try:
1907             w = e_dict[v]
1908         except KeyError:
1909             v32 = self._uint32(v)
1910             w = "__unknown__%08X__" % v32
1911
1912         r = "0x%x (%s)" % (v, w)
1913         return r;
1914
1915     def generic_bitmap_to_string(self, b_dict, v, names_only=False):
1916
1917         s = []
1918
1919         c = v
1920         for b in sorted(b_dict.keys()):
1921             if not (c & b):
1922                 continue
1923             c &= ~b
1924             s += [b_dict[b]]
1925
1926         if c != 0:
1927             c32 = self._uint32(c)
1928             s += ["__unknown_%08X__" % c32]
1929
1930         w = ",".join(s)
1931         if names_only:
1932             return w
1933         r = "0x%x (%s)" % (v, w)
1934         return r;
1935
1936     def trustType_string(self, v):
1937         types = {
1938             lsa.LSA_TRUST_TYPE_DOWNLEVEL : "DOWNLEVEL",
1939             lsa.LSA_TRUST_TYPE_UPLEVEL : "UPLEVEL",
1940             lsa.LSA_TRUST_TYPE_MIT : "MIT",
1941             lsa.LSA_TRUST_TYPE_DCE : "DCE",
1942         }
1943         return self.generic_enum_to_string(types, v)
1944
1945     def trustDirection_string(self, v):
1946         directions = {
1947             lsa.LSA_TRUST_DIRECTION_INBOUND |
1948             lsa.LSA_TRUST_DIRECTION_OUTBOUND : "BOTH",
1949             lsa.LSA_TRUST_DIRECTION_INBOUND : "INBOUND",
1950             lsa.LSA_TRUST_DIRECTION_OUTBOUND : "OUTBOUND",
1951         }
1952         return self.generic_enum_to_string(directions, v)
1953
1954     def trustAttributes_string(self, v):
1955         attributes = {
1956             lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE : "NON_TRANSITIVE",
1957             lsa.LSA_TRUST_ATTRIBUTE_UPLEVEL_ONLY : "UPLEVEL_ONLY",
1958             lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN : "QUARANTINED_DOMAIN",
1959             lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE : "FOREST_TRANSITIVE",
1960             lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION : "CROSS_ORGANIZATION",
1961             lsa.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST : "WITHIN_FOREST",
1962             lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL : "TREAT_AS_EXTERNAL",
1963             lsa.LSA_TRUST_ATTRIBUTE_USES_RC4_ENCRYPTION : "USES_RC4_ENCRYPTION",
1964         }
1965         return self.generic_bitmap_to_string(attributes, v)
1966
1967     def kerb_EncTypes_string(self, v):
1968         enctypes = {
1969             security.KERB_ENCTYPE_DES_CBC_CRC : "DES_CBC_CRC",
1970             security.KERB_ENCTYPE_DES_CBC_MD5 : "DES_CBC_MD5",
1971             security.KERB_ENCTYPE_RC4_HMAC_MD5 : "RC4_HMAC_MD5",
1972             security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96 : "AES128_CTS_HMAC_SHA1_96",
1973             security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96 : "AES256_CTS_HMAC_SHA1_96",
1974             security.KERB_ENCTYPE_FAST_SUPPORTED : "FAST_SUPPORTED",
1975             security.KERB_ENCTYPE_COMPOUND_IDENTITY_SUPPORTED : "COMPOUND_IDENTITY_SUPPORTED",
1976             security.KERB_ENCTYPE_CLAIMS_SUPPORTED : "CLAIMS_SUPPORTED",
1977             security.KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED : "RESOURCE_SID_COMPRESSION_DISABLED",
1978         }
1979         return self.generic_bitmap_to_string(enctypes, v)
1980
1981     def entry_tln_status(self, e_flags, ):
1982         if e_flags == 0:
1983             return "Status[Enabled]"
1984
1985         flags = {
1986             lsa.LSA_TLN_DISABLED_NEW : "Disabled-New",
1987             lsa.LSA_TLN_DISABLED_ADMIN : "Disabled",
1988             lsa.LSA_TLN_DISABLED_CONFLICT : "Disabled-Conflicting",
1989         }
1990         return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
1991
1992     def entry_dom_status(self, e_flags):
1993         if e_flags == 0:
1994             return "Status[Enabled]"
1995
1996         flags = {
1997             lsa.LSA_SID_DISABLED_ADMIN : "Disabled-SID",
1998             lsa.LSA_SID_DISABLED_CONFLICT : "Disabled-SID-Conflicting",
1999             lsa.LSA_NB_DISABLED_ADMIN : "Disabled-NB",
2000             lsa.LSA_NB_DISABLED_CONFLICT : "Disabled-NB-Conflicting",
2001         }
2002         return "Status[%s]" % self.generic_bitmap_to_string(flags, e_flags, names_only=True)
2003
2004     def write_forest_trust_info(self, fti, tln=None, collisions=None):
2005         if tln is not None:
2006             tln_string = " TDO[%s]" % tln
2007         else:
2008             tln_string = ""
2009
2010         self.outf.write("Namespaces[%d]%s:\n" % (
2011                         len(fti.entries), tln_string))
2012
2013         for i in xrange(0, len(fti.entries)):
2014             e = fti.entries[i]
2015
2016             flags = e.flags
2017             collision_string = ""
2018
2019             if collisions is not None:
2020                 for c in collisions.entries:
2021                     if c.index != i:
2022                         continue
2023                     flags = c.flags
2024                     collision_string = " Collision[%s]" % (c.name.string)
2025
2026             d = e.forest_trust_data
2027             if e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME:
2028                 self.outf.write("TLN: %-32s DNS[*.%s]%s\n" % (
2029                                 self.entry_tln_status(flags),
2030                                 d.string, collision_string))
2031             elif e.type == lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
2032                 self.outf.write("TLN_EX: %-29s DNS[*.%s]\n" % (
2033                                 "", d.string))
2034             elif e.type == lsa.LSA_FOREST_TRUST_DOMAIN_INFO:
2035                 self.outf.write("DOM: %-32s DNS[%s] Netbios[%s] SID[%s]%s\n" % (
2036                                 self.entry_dom_status(flags),
2037                                 d.dns_domain_name.string,
2038                                 d.netbios_domain_name.string,
2039                                 d.domain_sid, collision_string))
2040         return
2041
2042 class cmd_domain_trust_list(DomainTrustCommand):
2043     """List domain trusts."""
2044
2045     synopsis = "%prog [options]"
2046
2047     takes_optiongroups = {
2048         "sambaopts": options.SambaOptions,
2049         "versionopts": options.VersionOptions,
2050         "localdcopts": LocalDCCredentialsOptions,
2051     }
2052
2053     takes_options = [
2054        ]
2055
2056     def run(self, sambaopts=None, versionopts=None, localdcopts=None):
2057
2058         local_server = self.setup_local_server(sambaopts, localdcopts)
2059         try:
2060             local_netlogon = self.new_local_netlogon_connection()
2061         except RuntimeError as error:
2062             raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2063
2064         try:
2065             local_netlogon_trusts = local_netlogon.netr_DsrEnumerateDomainTrusts(local_server,
2066                                     netlogon.NETR_TRUST_FLAG_IN_FOREST |
2067                                     netlogon.NETR_TRUST_FLAG_OUTBOUND |
2068                                     netlogon.NETR_TRUST_FLAG_INBOUND)
2069         except RuntimeError as error:
2070             if self.check_runtime_error(error, werror.WERR_RPC_S_PROCNUM_OUT_OF_RANGE):
2071                 # TODO: we could implement a fallback to lsa.EnumTrustDom()
2072                 raise CommandError("LOCAL_DC[%s]: netr_DsrEnumerateDomainTrusts not supported." % (
2073                                    self.local_server))
2074             raise self.LocalRuntimeError(self, error, "netr_DsrEnumerateDomainTrusts failed")
2075
2076         a = local_netlogon_trusts.array
2077         for t in a:
2078             if t.trust_flags & netlogon.NETR_TRUST_FLAG_PRIMARY:
2079                 continue
2080             self.outf.write("%-14s %-15s %-19s %s\n" % (
2081                             "Type[%s]" % self.netr_DomainTrust_to_type(a, t),
2082                             "Transitive[%s]" % self.netr_DomainTrust_to_transitive(t),
2083                             "Direction[%s]" % self.netr_DomainTrust_to_direction(t),
2084                             "Name[%s]" % self.netr_DomainTrust_to_name(t)))
2085         return
2086
2087 class cmd_domain_trust_show(DomainTrustCommand):
2088     """Show trusted domain details."""
2089
2090     synopsis = "%prog NAME [options]"
2091
2092     takes_optiongroups = {
2093         "sambaopts": options.SambaOptions,
2094         "versionopts": options.VersionOptions,
2095         "localdcopts": LocalDCCredentialsOptions,
2096     }
2097
2098     takes_options = [
2099        ]
2100
2101     takes_args = ["domain"]
2102
2103     def run(self, domain, sambaopts=None, versionopts=None, localdcopts=None):
2104
2105         local_server = self.setup_local_server(sambaopts, localdcopts)
2106         try:
2107             local_lsa = self.new_local_lsa_connection()
2108         except RuntimeError as error:
2109             raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2110
2111         try:
2112             local_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2113             (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2114         except RuntimeError as error:
2115             raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2116
2117         self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2118                         local_lsa_info.name.string,
2119                         local_lsa_info.dns_domain.string,
2120                         local_lsa_info.sid))
2121
2122         lsaString = lsa.String()
2123         lsaString.string = domain
2124         try:
2125             local_tdo_full = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2126                                         lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2127             local_tdo_info = local_tdo_full.info_ex
2128             local_tdo_posix = local_tdo_full.posix_offset
2129         except NTSTATUSError as error:
2130             if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2131                 raise CommandError("trusted domain object does not exist for domain [%s]" % domain)
2132
2133             raise self.LocalRuntimeError(self, error, "QueryTrustedDomainInfoByName(FULL_INFO) failed")
2134
2135         try:
2136             local_tdo_enctypes = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2137                                         lsaString, lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES)
2138         except NTSTATUSError as error:
2139             if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_PARAMETER):
2140                 error = None
2141             if self.check_runtime_error(error, ntstatus.NT_STATUS_INVALID_INFO_CLASS):
2142                 error = None
2143
2144             if error is not None:
2145                 raise self.LocalRuntimeError(self, error,
2146                            "QueryTrustedDomainInfoByName(SUPPORTED_ENCRYPTION_TYPES) failed")
2147
2148             local_tdo_enctypes = lsa.TrustDomainInfoSupportedEncTypes()
2149             local_tdo_enctypes.enc_types = 0
2150
2151         try:
2152             local_tdo_forest = None
2153             if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2154                 local_tdo_forest = local_lsa.lsaRQueryForestTrustInformation(local_policy,
2155                                         lsaString, lsa.LSA_FOREST_TRUST_DOMAIN_INFO)
2156         except RuntimeError as error:
2157             if self.check_runtime_error(error, ntstatus.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE):
2158                 error = None
2159             if self.check_runtime_error(error, ntstatus.NT_STATUS_NOT_FOUND):
2160                 error = None
2161             if error is not None:
2162                 raise self.LocalRuntimeError(self, error, "lsaRQueryForestTrustInformation failed")
2163
2164             local_tdo_forest = lsa.ForestTrustInformation()
2165             local_tdo_forest.count = 0
2166             local_tdo_forest.entries = []
2167
2168         self.outf.write("TrusteDomain:\n\n");
2169         self.outf.write("NetbiosName:    %s\n" % local_tdo_info.netbios_name.string)
2170         if local_tdo_info.netbios_name.string != local_tdo_info.domain_name.string:
2171             self.outf.write("DnsName:        %s\n" % local_tdo_info.domain_name.string)
2172         self.outf.write("SID:            %s\n" % local_tdo_info.sid)
2173         self.outf.write("Type:           %s\n" % self.trustType_string(local_tdo_info.trust_type))
2174         self.outf.write("Direction:      %s\n" % self.trustDirection_string(local_tdo_info.trust_direction))
2175         self.outf.write("Attributes:     %s\n" % self.trustAttributes_string(local_tdo_info.trust_attributes))
2176         posix_offset_u32 = ctypes.c_uint32(local_tdo_posix.posix_offset).value
2177         posix_offset_i32 = ctypes.c_int32(local_tdo_posix.posix_offset).value
2178         self.outf.write("PosixOffset:    0x%08X (%d)\n" % (posix_offset_u32, posix_offset_i32))
2179         self.outf.write("kerb_EncTypes:  %s\n" % self.kerb_EncTypes_string(local_tdo_enctypes.enc_types))
2180
2181         if local_tdo_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2182             self.write_forest_trust_info(local_tdo_forest,
2183                                          tln=local_tdo_info.domain_name.string)
2184
2185         return
2186
2187 class cmd_domain_trust_create(DomainTrustCommand):
2188     """Create a domain or forest trust."""
2189
2190     synopsis = "%prog DOMAIN [options]"
2191
2192     takes_optiongroups = {
2193         "sambaopts": options.SambaOptions,
2194         "versionopts": options.VersionOptions,
2195         "credopts": options.CredentialsOptions,
2196         "localdcopts": LocalDCCredentialsOptions,
2197     }
2198
2199     takes_options = [
2200         Option("--type", type="choice", metavar="TYPE",
2201                choices=["external", "forest"],
2202                help="The type of the trust: 'external' or 'forest'.",
2203                dest='trust_type',
2204                default="external"),
2205         Option("--direction", type="choice", metavar="DIRECTION",
2206                choices=["incoming", "outgoing", "both"],
2207                help="The trust direction: 'incoming', 'outgoing' or 'both'.",
2208                dest='trust_direction',
2209                default="both"),
2210         Option("--create-location", type="choice", metavar="LOCATION",
2211                choices=["local", "both"],
2212                help="Where to create the trusted domain object: 'local' or 'both'.",
2213                dest='create_location',
2214                default="both"),
2215         Option("--cross-organisation", action="store_true",
2216                help="The related domains does not belong to the same organisation.",
2217                dest='cross_organisation',
2218                default=False),
2219         Option("--quarantined", type="choice", metavar="yes|no",
2220                choices=["yes", "no", None],
2221                help="Special SID filtering rules are applied to the trust. "
2222                     "With --type=external the default is yes. "
2223                     "With --type=forest the default is no.",
2224                dest='quarantined_arg',
2225                default=None),
2226         Option("--not-transitive", action="store_true",
2227                help="The forest trust is not transitive.",
2228                dest='not_transitive',
2229                default=False),
2230         Option("--treat-as-external", action="store_true",
2231                help="The treat the forest trust as external.",
2232                dest='treat_as_external',
2233                default=False),
2234         Option("--no-aes-keys", action="store_false",
2235                help="The trust uses aes kerberos keys.",
2236                dest='use_aes_keys',
2237                default=True),
2238         Option("--skip-validation", action="store_false",
2239                help="Skip validation of the trust.",
2240                dest='validate',
2241                default=True),
2242        ]
2243
2244     takes_args = ["domain"]
2245
2246     def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2247             trust_type=None, trust_direction=None, create_location=None,
2248             cross_organisation=False, quarantined_arg=None,
2249             not_transitive=False, treat_as_external=False,
2250             use_aes_keys=False, validate=True):
2251
2252         lsaString = lsa.String()
2253
2254         quarantined = False
2255         if quarantined_arg is None:
2256             if trust_type == 'external':
2257                 quarantined = True
2258         elif quarantined_arg == 'yes':
2259             quarantined = True
2260
2261         if trust_type != 'forest':
2262             if not_transitive:
2263                 raise CommandError("--not-transitive requires --type=forest")
2264             if treat_as_external:
2265                 raise CommandError("--treat-as-external requires --type=forest")
2266
2267         enc_types = None
2268         if use_aes_keys:
2269             enc_types = lsa.TrustDomainInfoSupportedEncTypes()
2270             enc_types.enc_types = security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96
2271             enc_types.enc_types |= security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96
2272
2273         local_policy_access =  lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2274         local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2275         local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2276
2277         local_trust_info = lsa.TrustDomainInfoInfoEx()
2278         local_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2279         local_trust_info.trust_direction = 0
2280         if trust_direction == "both":
2281             local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2282             local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2283         elif trust_direction == "incoming":
2284             local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2285         elif trust_direction == "outgoing":
2286             local_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2287         local_trust_info.trust_attributes = 0
2288         if cross_organisation:
2289             local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2290         if quarantined:
2291             local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2292         if trust_type == "forest":
2293             local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2294         if not_transitive:
2295             local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2296         if treat_as_external:
2297             local_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2298
2299         def get_password(name):
2300             password = None
2301             while True:
2302                 if password is not None and password is not '':
2303                     return password
2304                 password = getpass("New %s Password: " % name)
2305                 passwordverify = getpass("Retype %s Password: " % name)
2306                 if not password == passwordverify:
2307                     password = None
2308                     self.outf.write("Sorry, passwords do not match.\n")
2309
2310         incoming_secret = None
2311         outgoing_secret = None
2312         remote_policy_access = lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2313         if create_location == "local":
2314             if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2315                 incoming_password = get_password("Incoming Trust")
2316                 incoming_secret = string_to_byte_array(incoming_password.encode('utf-16-le'))
2317             if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2318                 outgoing_password = get_password("Outgoing Trust")
2319                 outgoing_secret = string_to_byte_array(outgoing_password.encode('utf-16-le'))
2320
2321             remote_trust_info = None
2322         else:
2323             # We use 240 random bytes.
2324             # Windows uses 28 or 240 random bytes. I guess it's
2325             # based on the trust type external vs. forest.
2326             #
2327             # The initial trust password can be up to 512 bytes
2328             # while the versioned passwords used for periodic updates
2329             # can only be up to 498 bytes, as netr_ServerPasswordSet2()
2330             # needs to pass the NL_PASSWORD_VERSION structure within the
2331             # 512 bytes and a 2 bytes confounder is required.
2332             #
2333             def random_trust_secret(length):
2334                 pw = samba.generate_random_machine_password(length/2, length/2)
2335                 return string_to_byte_array(pw.encode('utf-16-le'))
2336
2337             if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_INBOUND:
2338                 incoming_secret = random_trust_secret(240)
2339             if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2340                 outgoing_secret = random_trust_secret(240)
2341
2342             remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2343             remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2344
2345             remote_trust_info = lsa.TrustDomainInfoInfoEx()
2346             remote_trust_info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL
2347             remote_trust_info.trust_direction = 0
2348             if trust_direction == "both":
2349                 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2350                 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2351             elif trust_direction == "incoming":
2352                 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND
2353             elif trust_direction == "outgoing":
2354                 remote_trust_info.trust_direction |= lsa.LSA_TRUST_DIRECTION_INBOUND
2355             remote_trust_info.trust_attributes = 0
2356             if cross_organisation:
2357                 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2358             if quarantined:
2359                 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2360             if trust_type == "forest":
2361                 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2362             if not_transitive:
2363                 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2364             if treat_as_external:
2365                 remote_trust_info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2366
2367         local_server = self.setup_local_server(sambaopts, localdcopts)
2368         try:
2369             local_lsa = self.new_local_lsa_connection()
2370         except RuntimeError as error:
2371             raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2372
2373         try:
2374             (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2375         except RuntimeError as error:
2376             raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2377
2378         self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2379                         local_lsa_info.name.string,
2380                         local_lsa_info.dns_domain.string,
2381                         local_lsa_info.sid))
2382
2383         try:
2384             remote_server = self.setup_remote_server(credopts, domain)
2385         except RuntimeError as error:
2386             raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2387
2388         try:
2389             remote_lsa = self.new_remote_lsa_connection()
2390         except RuntimeError as error:
2391             raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2392
2393         try:
2394             (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2395         except RuntimeError as error:
2396             raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2397
2398         self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2399                         remote_lsa_info.name.string,
2400                         remote_lsa_info.dns_domain.string,
2401                         remote_lsa_info.sid))
2402
2403         local_trust_info.domain_name.string = remote_lsa_info.dns_domain.string
2404         local_trust_info.netbios_name.string = remote_lsa_info.name.string
2405         local_trust_info.sid = remote_lsa_info.sid
2406
2407         if remote_trust_info:
2408             remote_trust_info.domain_name.string = local_lsa_info.dns_domain.string
2409             remote_trust_info.netbios_name.string = local_lsa_info.name.string
2410             remote_trust_info.sid = local_lsa_info.sid
2411
2412         try:
2413             lsaString.string = local_trust_info.domain_name.string
2414             local_old_netbios = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2415                                         lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2416             raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2417         except NTSTATUSError as error:
2418             if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2419                 raise self.LocalRuntimeError(self, error,
2420                                 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2421                                 lsaString.string))
2422
2423         try:
2424             lsaString.string = local_trust_info.netbios_name.string
2425             local_old_dns = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2426                                         lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2427             raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2428         except NTSTATUSError as error:
2429             if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2430                 raise self.LocalRuntimeError(self, error,
2431                                 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2432                                 lsaString.string))
2433
2434         if remote_trust_info:
2435             try:
2436                 lsaString.string = remote_trust_info.domain_name.string
2437                 remote_old_netbios = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2438                                             lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2439                 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2440             except NTSTATUSError as error:
2441                 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2442                     raise self.RemoteRuntimeError(self, error,
2443                                     "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2444                                     lsaString.string))
2445
2446             try:
2447                 lsaString.string = remote_trust_info.netbios_name.string
2448                 remote_old_dns = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2449                                             lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
2450                 raise CommandError("TrustedDomain %s already exist'" % lsaString.string)
2451             except NTSTATUSError as error:
2452                 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2453                     raise self.RemoteRuntimeError(self, error,
2454                                     "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2455                                     lsaString.string))
2456
2457         try:
2458             local_netlogon = self.new_local_netlogon_connection()
2459         except RuntimeError as error:
2460             raise self.LocalRuntimeError(self, error, "failed to connect netlogon server")
2461
2462         try:
2463             local_netlogon_info = self.get_netlogon_dc_info(local_netlogon, local_server)
2464         except RuntimeError as error:
2465             raise self.LocalRuntimeError(self, error, "failed to get netlogon dc info")
2466
2467         if remote_trust_info:
2468             try:
2469                 remote_netlogon = self.new_remote_netlogon_connection()
2470             except RuntimeError as error:
2471                 raise self.RemoteRuntimeError(self, error, "failed to connect netlogon server")
2472
2473             try:
2474                 remote_netlogon_info = self.get_netlogon_dc_info(remote_netlogon, remote_server)
2475             except RuntimeError as error:
2476                 raise self.RemoteRuntimeError(self, error, "failed to get netlogon dc info")
2477
2478         def generate_AuthInOutBlob(secret, update_time):
2479             if secret is None:
2480                 blob = drsblobs.trustAuthInOutBlob()
2481                 blob.count = 0
2482
2483                 return blob
2484
2485             clear = drsblobs.AuthInfoClear()
2486             clear.size = len(secret)
2487             clear.password = secret
2488
2489             info = drsblobs.AuthenticationInformation()
2490             info.LastUpdateTime = samba.unix2nttime(update_time)
2491             info.AuthType = lsa.TRUST_AUTH_TYPE_CLEAR
2492             info.AuthInfo = clear
2493
2494             array = drsblobs.AuthenticationInformationArray()
2495             array.count = 1
2496             array.array = [info]
2497
2498             blob = drsblobs.trustAuthInOutBlob()
2499             blob.count = 1
2500             blob.current = array
2501
2502             return blob
2503
2504         def generate_AuthInfoInternal(session_key, incoming=None, outgoing=None):
2505             confounder = [0] * 512
2506             for i in range(len(confounder)):
2507                 confounder[i] = random.randint(0, 255)
2508
2509             trustpass = drsblobs.trustDomainPasswords()
2510
2511             trustpass.confounder = confounder
2512             trustpass.outgoing = outgoing
2513             trustpass.incoming = incoming
2514
2515             trustpass_blob = ndr_pack(trustpass)
2516
2517             encrypted_trustpass = arcfour_encrypt(session_key, trustpass_blob)
2518
2519             auth_blob = lsa.DATA_BUF2()
2520             auth_blob.size = len(encrypted_trustpass)
2521             auth_blob.data = string_to_byte_array(encrypted_trustpass)
2522
2523             auth_info = lsa.TrustDomainInfoAuthInfoInternal()
2524             auth_info.auth_blob = auth_blob
2525
2526             return auth_info
2527
2528         update_time = samba.current_unix_time()
2529         incoming_blob = generate_AuthInOutBlob(incoming_secret, update_time)
2530         outgoing_blob = generate_AuthInOutBlob(outgoing_secret, update_time)
2531
2532         local_tdo_handle = None
2533         remote_tdo_handle = None
2534
2535         local_auth_info = generate_AuthInfoInternal(local_lsa.session_key,
2536                                                     incoming=incoming_blob,
2537                                                     outgoing=outgoing_blob)
2538         if remote_trust_info:
2539             remote_auth_info = generate_AuthInfoInternal(remote_lsa.session_key,
2540                                                          incoming=outgoing_blob,
2541                                                          outgoing=incoming_blob)
2542
2543         try:
2544             if remote_trust_info:
2545                 self.outf.write("Creating remote TDO.\n")
2546                 current_request = { "location": "remote", "name": "CreateTrustedDomainEx2"}
2547                 remote_tdo_handle = remote_lsa.CreateTrustedDomainEx2(remote_policy,
2548                                                                       remote_trust_info,
2549                                                                       remote_auth_info,
2550                                                                       lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2551                 self.outf.write("Remote TDO created.\n")
2552                 if enc_types:
2553                     self.outf.write("Setting supported encryption types on remote TDO.\n")
2554                     current_request = { "location": "remote", "name": "SetInformationTrustedDomain"}
2555                     remote_lsa.SetInformationTrustedDomain(remote_tdo_handle,
2556                                                            lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2557                                                            enc_types)
2558
2559             self.outf.write("Creating local TDO.\n")
2560             current_request = { "location": "local", "name": "CreateTrustedDomainEx2"}
2561             local_tdo_handle = local_lsa.CreateTrustedDomainEx2(local_policy,
2562                                                                   local_trust_info,
2563                                                                   local_auth_info,
2564                                                                   lsa.LSA_TRUSTED_DOMAIN_ALL_ACCESS)
2565             self.outf.write("Local TDO created\n")
2566             if enc_types:
2567                 self.outf.write("Setting supported encryption types on local TDO.\n")
2568                 current_request = { "location": "local", "name": "SetInformationTrustedDomain"}
2569                 local_lsa.SetInformationTrustedDomain(local_tdo_handle,
2570                                                       lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
2571                                                       enc_types)
2572         except RuntimeError as error:
2573             self.outf.write("Error: %s failed %sly - cleaning up\n" % (
2574                             current_request['name'], current_request['location']))
2575             if remote_tdo_handle:
2576                 self.outf.write("Deleting remote TDO.\n")
2577                 remote_lsa.DeleteObject(remote_tdo_handle)
2578                 remote_tdo_handle = None
2579             if local_tdo_handle:
2580                 self.outf.write("Deleting local TDO.\n")
2581                 local_lsa.DeleteObject(local_tdo_handle)
2582                 local_tdo_handle = None
2583             if current_request['location'] is "remote":
2584                 raise self.RemoteRuntimeError(self, error, "%s" % (
2585                                               current_request['name']))
2586             raise self.LocalRuntimeError(self, error, "%s" % (
2587                                          current_request['name']))
2588
2589         if validate:
2590             if local_trust_info.trust_attributes & lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE:
2591                 self.outf.write("Setup local forest trust information...\n")
2592                 try:
2593                     # get all information about the remote trust
2594                     # this triggers netr_GetForestTrustInformation to the remote domain
2595                     # and lsaRSetForestTrustInformation() locally, but new top level
2596                     # names are disabled by default.
2597                     local_forest_info = local_netlogon.netr_DsRGetForestTrustInformation(local_netlogon_info.dc_unc,
2598                                                                   remote_lsa_info.dns_domain.string,
2599                                                                   netlogon.DS_GFTI_UPDATE_TDO)
2600                 except RuntimeError as error:
2601                     raise self.LocalRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2602
2603                 try:
2604                     # here we try to enable all top level names
2605                     local_forest_collision = local_lsa.lsaRSetForestTrustInformation(local_policy,
2606                                                                   remote_lsa_info.dns_domain,
2607                                                                   lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2608                                                                   local_forest_info,
2609                                                                   0)
2610                 except RuntimeError as error:
2611                     raise self.LocalRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2612
2613                 self.write_forest_trust_info(local_forest_info,
2614                                              tln=remote_lsa_info.dns_domain.string,
2615                                              collisions=local_forest_collision)
2616
2617                 if remote_trust_info:
2618                     self.outf.write("Setup remote forest trust information...\n")
2619                     try:
2620                         # get all information about the local trust (from the perspective of the remote domain)
2621                         # this triggers netr_GetForestTrustInformation to our domain.
2622                         # and lsaRSetForestTrustInformation() remotely, but new top level
2623                         # names are disabled by default.
2624                         remote_forest_info = remote_netlogon.netr_DsRGetForestTrustInformation(remote_netlogon_info.dc_unc,
2625                                                                       local_lsa_info.dns_domain.string,
2626                                                                       netlogon.DS_GFTI_UPDATE_TDO)
2627                     except RuntimeError as error:
2628                         raise self.RemoteRuntimeError(self, error, "netr_DsRGetForestTrustInformation() failed")
2629
2630                     try:
2631                         # here we try to enable all top level names
2632                         remote_forest_collision = remote_lsa.lsaRSetForestTrustInformation(remote_policy,
2633                                                                       local_lsa_info.dns_domain,
2634                                                                       lsa.LSA_FOREST_TRUST_DOMAIN_INFO,
2635                                                                       remote_forest_info,
2636                                                                       0)
2637                     except RuntimeError as error:
2638                         raise self.RemoteRuntimeError(self, error, "lsaRSetForestTrustInformation() failed")
2639
2640                     self.write_forest_trust_info(remote_forest_info,
2641                                                  tln=local_lsa_info.dns_domain.string,
2642                                                  collisions=remote_forest_collision)
2643
2644             if local_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2645                 self.outf.write("Validating outgoing trust...\n")
2646                 try:
2647                     local_trust_verify = local_netlogon.netr_LogonControl2Ex(local_netlogon_info.dc_unc,
2648                                                                       netlogon.NETLOGON_CONTROL_TC_VERIFY,
2649                                                                       2,
2650                                                                       remote_lsa_info.dns_domain.string)
2651                 except RuntimeError as error:
2652                     raise self.LocalRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2653
2654                 local_trust_status = self._uint32(local_trust_verify.pdc_connection_status[0])
2655                 local_conn_status = self._uint32(local_trust_verify.tc_connection_status[0])
2656
2657                 if local_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2658                     local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2659                                        local_trust_verify.trusted_dc_name,
2660                                        local_trust_verify.tc_connection_status[1],
2661                                        local_trust_verify.pdc_connection_status[1])
2662                 else:
2663                     local_validation = "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2664                                        local_trust_verify.trusted_dc_name,
2665                                        local_trust_verify.tc_connection_status[1],
2666                                        local_trust_verify.pdc_connection_status[1])
2667
2668                 if local_trust_status != werror.WERR_SUCCESS or local_conn_status != werror.WERR_SUCCESS:
2669                     raise CommandError(local_validation)
2670                 else:
2671                     self.outf.write("OK: %s\n" % local_validation)
2672
2673             if remote_trust_info:
2674                 if remote_trust_info.trust_direction & lsa.LSA_TRUST_DIRECTION_OUTBOUND:
2675                     self.outf.write("Validating incoming trust...\n")
2676                     try:
2677                         remote_trust_verify = remote_netlogon.netr_LogonControl2Ex(remote_netlogon_info.dc_unc,
2678                                                                       netlogon.NETLOGON_CONTROL_TC_VERIFY,
2679                                                                       2,
2680                                                                       local_lsa_info.dns_domain.string)
2681                     except RuntimeError as error:
2682                         raise self.RemoteRuntimeError(self, error, "NETLOGON_CONTROL_TC_VERIFY failed")
2683
2684                     remote_trust_status = self._uint32(remote_trust_verify.pdc_connection_status[0])
2685                     remote_conn_status = self._uint32(remote_trust_verify.tc_connection_status[0])
2686
2687                     if remote_trust_verify.flags & netlogon.NETLOGON_VERIFY_STATUS_RETURNED:
2688                         remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2689                                            remote_trust_verify.trusted_dc_name,
2690                                            remote_trust_verify.tc_connection_status[1],
2691                                            remote_trust_verify.pdc_connection_status[1])
2692                     else:
2693                         remote_validation = "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2694                                            remote_trust_verify.trusted_dc_name,
2695                                            remote_trust_verify.tc_connection_status[1],
2696                                            remote_trust_verify.pdc_connection_status[1])
2697
2698                     if remote_trust_status != werror.WERR_SUCCESS or remote_conn_status != werror.WERR_SUCCESS:
2699                         raise CommandError(remote_validation)
2700                     else:
2701                         self.outf.write("OK: %s\n" % remote_validation)
2702
2703         if remote_tdo_handle is not None:
2704             try:
2705                 remote_lsa.Close(remote_tdo_handle)
2706             except RuntimeError as error:
2707                 pass
2708             remote_tdo_handle = None
2709         if local_tdo_handle is not None:
2710             try:
2711                 local_lsa.Close(local_tdo_handle)
2712             except RuntimeError as error:
2713                 pass
2714             local_tdo_handle = None
2715
2716         self.outf.write("Success.\n")
2717         return
2718
2719 class cmd_domain_trust_delete(DomainTrustCommand):
2720     """Delete a domain trust."""
2721
2722     synopsis = "%prog DOMAIN [options]"
2723
2724     takes_optiongroups = {
2725         "sambaopts": options.SambaOptions,
2726         "versionopts": options.VersionOptions,
2727         "credopts": options.CredentialsOptions,
2728         "localdcopts": LocalDCCredentialsOptions,
2729     }
2730
2731     takes_options = [
2732         Option("--delete-location", type="choice", metavar="LOCATION",
2733                choices=["local", "both"],
2734                help="Where to delete the trusted domain object: 'local' or 'both'.",
2735                dest='delete_location',
2736                default="both"),
2737        ]
2738
2739     takes_args = ["domain"]
2740
2741     def run(self, domain, sambaopts=None, localdcopts=None, credopts=None, versionopts=None,
2742             delete_location=None):
2743
2744         local_policy_access =  lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2745         local_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2746         local_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2747
2748         if delete_location == "local":
2749             remote_policy_access = None
2750         else:
2751             remote_policy_access =  lsa.LSA_POLICY_VIEW_LOCAL_INFORMATION
2752             remote_policy_access |= lsa.LSA_POLICY_TRUST_ADMIN
2753             remote_policy_access |= lsa.LSA_POLICY_CREATE_SECRET
2754
2755         local_server = self.setup_local_server(sambaopts, localdcopts)
2756         try:
2757             local_lsa = self.new_local_lsa_connection()
2758         except RuntimeError as error:
2759             raise self.LocalRuntimeError(self, error, "failed to connect lsa server")
2760
2761         try:
2762             (local_policy, local_lsa_info) = self.get_lsa_info(local_lsa, local_policy_access)
2763         except RuntimeError as error:
2764             raise self.LocalRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2765
2766         self.outf.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2767                         local_lsa_info.name.string,
2768                         local_lsa_info.dns_domain.string,
2769                         local_lsa_info.sid))
2770
2771         local_tdo_info = None
2772         local_tdo_handle = None
2773         remote_tdo_info = None
2774         remote_tdo_handle = None
2775
2776         lsaString = lsa.String()
2777         try:
2778             lsaString.string = domain
2779             local_tdo_info = local_lsa.QueryTrustedDomainInfoByName(local_policy,
2780                                         lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2781         except NTSTATUSError as error:
2782             if self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2783                 raise CommandError("Failed to find trust for domain '%s'" % domain)
2784             raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2785
2786
2787         if remote_policy_access is not None:
2788             try:
2789                 remote_server = self.setup_remote_server(credopts, domain)
2790             except RuntimeError as error:
2791                 raise self.RemoteRuntimeError(self, error, "failed to locate remote server")
2792
2793             try:
2794                 remote_lsa = self.new_remote_lsa_connection()
2795             except RuntimeError as error:
2796                 raise self.RemoteRuntimeError(self, error, "failed to connect lsa server")
2797
2798             try:
2799                 (remote_policy, remote_lsa_info) = self.get_lsa_info(remote_lsa, remote_policy_access)
2800             except RuntimeError as error:
2801                 raise self.RemoteRuntimeError(self, error, "failed to query LSA_POLICY_INFO_DNS")
2802
2803             self.outf.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2804                             remote_lsa_info.name.string,
2805                             remote_lsa_info.dns_domain.string,
2806                             remote_lsa_info.sid))
2807
2808             if remote_lsa_info.sid != local_tdo_info.sid or \
2809                remote_lsa_info.name.string != local_tdo_info.netbios_name.string or \
2810                remote_lsa_info.dns_domain.string != local_tdo_info.domain_name.string:
2811                 raise CommandError("LocalTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2812                                    local_tdo_info.netbios_name.string,
2813                                    local_tdo_info.domain_name.string,
2814                                    local_tdo_info.sid))
2815
2816             try:
2817                 lsaString.string = local_lsa_info.dns_domain.string
2818                 remote_tdo_info = remote_lsa.QueryTrustedDomainInfoByName(remote_policy,
2819                                             lsaString, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX)
2820             except NTSTATUSError as error:
2821                 if not self.check_runtime_error(error, ntstatus.NT_STATUS_OBJECT_NAME_NOT_FOUND):
2822                     raise self.RemoteRuntimeError(self, error, "QueryTrustedDomainInfoByName(%s)" % (
2823                                                   lsaString.string))
2824                 pass
2825
2826             if remote_tdo_info is not None:
2827                 if local_lsa_info.sid != remote_tdo_info.sid or \
2828                    local_lsa_info.name.string != remote_tdo_info.netbios_name.string or \
2829                    local_lsa_info.dns_domain.string != remote_tdo_info.domain_name.string:
2830                     raise CommandError("RemoteTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2831                                        remote_tdo_info.netbios_name.string,
2832                                        remote_tdo_info.domain_name.string,
2833                                        remote_tdo_info.sid))
2834
2835         if local_tdo_info is not None:
2836             try:
2837                 lsaString.string = local_tdo_info.domain_name.string
2838                 local_tdo_handle = local_lsa.OpenTrustedDomainByName(local_policy,
2839                                                                      lsaString,
2840                                                                      security.SEC_STD_DELETE)
2841             except RuntimeError as error:
2842                 raise self.LocalRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2843                                              lsaString.string))
2844
2845             local_lsa.DeleteObject(local_tdo_handle)
2846             local_tdo_handle = None
2847
2848         if remote_tdo_info is not None:
2849             try:
2850                 lsaString.string = remote_tdo_info.domain_name.string
2851                 remote_tdo_handle = remote_lsa.OpenTrustedDomainByName(remote_policy,
2852                                                                        lsaString,
2853                                                                        security.SEC_STD_DELETE)
2854             except RuntimeError as error:
2855                 raise self.RemoteRuntimeError(self, error, "OpenTrustedDomainByName(%s)" % (
2856                                               lsaString.string))
2857
2858         if remote_tdo_handle is not None:
2859             try:
2860                 remote_lsa.DeleteObject(remote_tdo_handle)
2861                 remote_tdo_handle = None
2862                 self.outf.write("RemoteTDO deleted.\n")
2863             except RuntimeError as error:
2864                 self.outf.write("%s\n" % self.RemoteRuntimeError(self, error, "DeleteObject() failed"))
2865
2866         if local_tdo_handle is not None:
2867             try:
2868                 local_lsa.DeleteObject(local_tdo_handle)
2869       &n