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