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