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