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
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.
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.
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/>.
25 import samba
.getopt
as options
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
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 (
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
,
69 from samba
import remove_dc
, arcfour_encrypt
, string_to_byte_array
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
87 from samba
.provision
import (
90 DEFAULT_MIN_PWD_LENGTH
,
94 from samba
.provision
.common
import (
100 string_version_to_constant
= {
101 "2008_R2" : DS_DOMAIN_FUNCTION_2008_R2
,
102 "2012": DS_DOMAIN_FUNCTION_2012
,
103 "2012_R2": DS_DOMAIN_FUNCTION_2012_R2
,
106 def get_testparm_var(testparm
, smbconf
, varname
):
107 errfile
= open(os
.devnull
, 'w')
108 p
= subprocess
.Popen([testparm
, '-s', '-l',
109 '--parameter-name=%s' % varname
, smbconf
],
110 stdout
=subprocess
.PIPE
, stderr
=errfile
)
111 (out
,err
) = p
.communicate()
113 lines
= out
.split('\n')
115 return lines
[0].strip()
119 import samba
.dckeytab
121 cmd_domain_export_keytab
= None
123 class cmd_domain_export_keytab(Command
):
124 """Dump Kerberos keys of the domain into a keytab."""
126 synopsis
= "%prog <keytab> [options]"
128 takes_optiongroups
= {
129 "sambaopts": options
.SambaOptions
,
130 "credopts": options
.CredentialsOptions
,
131 "versionopts": options
.VersionOptions
,
135 Option("--principal", help="extract only this principal", type=str),
138 takes_args
= ["keytab"]
140 def run(self
, keytab
, credopts
=None, sambaopts
=None, versionopts
=None, principal
=None):
141 lp
= sambaopts
.get_loadparm()
143 net
.export_keytab(keytab
=keytab
, principal
=principal
)
146 class cmd_domain_info(Command
):
147 """Print basic info about a domain and the DC passed as parameter."""
149 synopsis
= "%prog <ip_address> [options]"
154 takes_optiongroups
= {
155 "sambaopts": options
.SambaOptions
,
156 "credopts": options
.CredentialsOptions
,
157 "versionopts": options
.VersionOptions
,
160 takes_args
= ["address"]
162 def run(self
, address
, credopts
=None, sambaopts
=None, versionopts
=None):
163 lp
= sambaopts
.get_loadparm()
165 res
= netcmd_get_domain_infos_via_cldap(lp
, None, address
)
167 raise CommandError("Invalid IP address '" + address
+ "'!")
168 self
.outf
.write("Forest : %s\n" % res
.forest
)
169 self
.outf
.write("Domain : %s\n" % res
.dns_domain
)
170 self
.outf
.write("Netbios domain : %s\n" % res
.domain_name
)
171 self
.outf
.write("DC name : %s\n" % res
.pdc_dns_name
)
172 self
.outf
.write("DC netbios name : %s\n" % res
.pdc_name
)
173 self
.outf
.write("Server site : %s\n" % res
.server_site
)
174 self
.outf
.write("Client site : %s\n" % res
.client_site
)
177 class cmd_domain_provision(Command
):
178 """Provision a domain."""
180 synopsis
= "%prog [options]"
182 takes_optiongroups
= {
183 "sambaopts": options
.SambaOptions
,
184 "versionopts": options
.VersionOptions
,
188 Option("--interactive", help="Ask for names", action
="store_true"),
189 Option("--domain", type="string", metavar
="DOMAIN",
190 help="NetBIOS domain name to use"),
191 Option("--domain-guid", type="string", metavar
="GUID",
192 help="set domainguid (otherwise random)"),
193 Option("--domain-sid", type="string", metavar
="SID",
194 help="set domainsid (otherwise random)"),
195 Option("--ntds-guid", type="string", metavar
="GUID",
196 help="set NTDS object GUID (otherwise random)"),
197 Option("--invocationid", type="string", metavar
="GUID",
198 help="set invocationid (otherwise random)"),
199 Option("--host-name", type="string", metavar
="HOSTNAME",
200 help="set hostname"),
201 Option("--host-ip", type="string", metavar
="IPADDRESS",
202 help="set IPv4 ipaddress"),
203 Option("--host-ip6", type="string", metavar
="IP6ADDRESS",
204 help="set IPv6 ipaddress"),
205 Option("--site", type="string", metavar
="SITENAME",
206 help="set site name"),
207 Option("--adminpass", type="string", metavar
="PASSWORD",
208 help="choose admin password (otherwise random)"),
209 Option("--krbtgtpass", type="string", metavar
="PASSWORD",
210 help="choose krbtgt password (otherwise random)"),
211 Option("--machinepass", type="string", metavar
="PASSWORD",
212 help="choose machine password (otherwise random)"),
213 Option("--dns-backend", type="choice", metavar
="NAMESERVER-BACKEND",
214 choices
=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
215 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
216 "BIND9_FLATFILE uses bind9 text database to store zone information, "
217 "BIND9_DLZ uses samba4 AD to store zone information, "
218 "NONE skips the DNS setup entirely (not recommended)",
219 default
="SAMBA_INTERNAL"),
220 Option("--dnspass", type="string", metavar
="PASSWORD",
221 help="choose dns password (otherwise random)"),
222 Option("--ldapadminpass", type="string", metavar
="PASSWORD",
223 help="choose password to set between Samba and its LDAP backend (otherwise random)"),
224 Option("--root", type="string", metavar
="USERNAME",
225 help="choose 'root' unix username"),
226 Option("--nobody", type="string", metavar
="USERNAME",
227 help="choose 'nobody' user"),
228 Option("--users", type="string", metavar
="GROUPNAME",
229 help="choose 'users' group"),
230 Option("--quiet", help="Be quiet", action
="store_true"),
231 Option("--blank", action
="store_true",
232 help="do not add users or groups, just the structure"),
233 Option("--ldap-backend-type", type="choice", metavar
="LDAP-BACKEND-TYPE",
234 help="Test initialisation support for unsupported LDAP backend type (fedora-ds or openldap) DO NOT USE",
235 choices
=["fedora-ds", "openldap"]),
236 Option("--server-role", type="choice", metavar
="ROLE",
237 choices
=["domain controller", "dc", "member server", "member", "standalone"],
238 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
239 default
="domain controller"),
240 Option("--function-level", type="choice", metavar
="FOR-FUN-LEVEL",
241 choices
=["2000", "2003", "2008", "2008_R2"],
242 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2008_R2 Native.",
244 Option("--base-schema", type="choice", metavar
="BASE-SCHEMA",
245 choices
=["2008_R2", "2008_R2_old", "2012", "2012_R2"],
246 help="The base schema files to use. Default is (Windows) 2008_R2.",
248 Option("--next-rid", type="int", metavar
="NEXTRID", default
=1000,
249 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
250 Option("--partitions-only",
251 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action
="store_true"),
252 Option("--targetdir", type="string", metavar
="DIR",
253 help="Set target directory"),
254 Option("--ol-mmr-urls", type="string", metavar
="LDAPSERVER",
255 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\""),
256 Option("--use-rfc2307", action
="store_true", help="Use AD to store posix attributes (default = no)"),
257 Option("--plaintext-secrets", action
="store_true",
258 help="Store secret/sensitive values as plain text on disk" +
259 "(default is to encrypt secret/ensitive values)"),
263 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",
264 action
="store_true"),
265 Option("--slapd-path", type="string", metavar
="SLAPD-PATH",
266 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."),
267 Option("--ldap-backend-extra-port", type="int", metavar
="LDAP-BACKEND-EXTRA-PORT", help="Additional TCP port for LDAP backend server (to use for replication)"),
268 Option("--ldap-backend-forced-uri", type="string", metavar
="LDAP-BACKEND-FORCED-URI",
269 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"),
270 Option("--ldap-backend-nosync", help="Configure LDAP backend not to call fsync() (for performance in test environments)", action
="store_true"),
274 Option("--use-ntvfs", action
="store_true", help="Use NTVFS for the fileserver (default = no)"),
275 Option("--use-xattrs", type="choice", choices
=["yes","no","auto"],
276 metavar
="[yes|no|auto]",
277 help="Define if we should use the native fs capabilities or a tdb file for "
278 "storing attributes likes ntacl when --use-ntvfs is set. "
279 "auto tries to make an inteligent guess based on the user rights and system capabilities",
283 if os
.getenv('TEST_LDAP', "no") == "yes":
284 takes_options
.extend(openldap_options
)
286 if samba
.is_ntvfs_fileserver_built():
287 takes_options
.extend(ntvfs_options
)
291 def run(self
, sambaopts
=None, versionopts
=None,
314 ldap_backend_type
=None,
318 partitions_only
=None,
325 ldap_backend_nosync
=None,
326 ldap_backend_extra_port
=None,
327 ldap_backend_forced_uri
=None,
328 ldap_dryrun_mode
=None,
330 plaintext_secrets
=False):
332 self
.logger
= self
.get_logger("provision")
334 self
.logger
.setLevel(logging
.WARNING
)
336 self
.logger
.setLevel(logging
.INFO
)
338 lp
= sambaopts
.get_loadparm()
339 smbconf
= lp
.configfile
341 if dns_forwarder
is not None:
342 suggested_forwarder
= dns_forwarder
344 suggested_forwarder
= self
._get
_nameserver
_ip
()
345 if suggested_forwarder
is None:
346 suggested_forwarder
= "none"
348 if len(self
.raw_argv
) == 1:
352 from getpass
import getpass
355 def ask(prompt
, default
=None):
356 if default
is not None:
357 print "%s [%s]: " % (prompt
, default
),
359 print "%s: " % (prompt
,),
360 return sys
.stdin
.readline().rstrip("\n") or default
363 default
= socket
.getfqdn().split(".", 1)[1].upper()
366 realm
= ask("Realm", default
)
367 if realm
in (None, ""):
368 raise CommandError("No realm set!")
371 default
= realm
.split(".")[0]
374 domain
= ask("Domain", default
)
376 raise CommandError("No domain set!")
378 server_role
= ask("Server Role (dc, member, standalone)", "dc")
380 dns_backend
= ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
381 if dns_backend
in (None, ''):
382 raise CommandError("No DNS backend set!")
384 if dns_backend
== "SAMBA_INTERNAL":
385 dns_forwarder
= ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder
)
386 if dns_forwarder
.lower() in (None, 'none'):
387 suggested_forwarder
= None
391 adminpassplain
= getpass("Administrator password: ")
392 issue
= self
._adminpass
_issue
(adminpassplain
)
394 self
.errf
.write("%s.\n" % issue
)
396 adminpassverify
= getpass("Retype password: ")
397 if not adminpassplain
== adminpassverify
:
398 self
.errf
.write("Sorry, passwords do not match.\n")
400 adminpass
= adminpassplain
404 realm
= sambaopts
._lp
.get('realm')
406 raise CommandError("No realm set!")
408 raise CommandError("No domain set!")
411 issue
= self
._adminpass
_issue
(adminpass
)
413 raise CommandError(issue
)
415 self
.logger
.info("Administrator password will be set randomly!")
417 if function_level
== "2000":
418 dom_for_fun_level
= DS_DOMAIN_FUNCTION_2000
419 elif function_level
== "2003":
420 dom_for_fun_level
= DS_DOMAIN_FUNCTION_2003
421 elif function_level
== "2008":
422 dom_for_fun_level
= DS_DOMAIN_FUNCTION_2008
423 elif function_level
== "2008_R2":
424 dom_for_fun_level
= DS_DOMAIN_FUNCTION_2008_R2
426 if dns_backend
== "SAMBA_INTERNAL" and dns_forwarder
is None:
427 dns_forwarder
= suggested_forwarder
429 samdb_fill
= FILL_FULL
431 samdb_fill
= FILL_NT4SYNC
432 elif partitions_only
:
433 samdb_fill
= FILL_DRS
435 if targetdir
is not None:
436 if not os
.path
.isdir(targetdir
):
441 if use_xattrs
== "yes":
443 elif use_xattrs
== "auto" and use_ntvfs
== False:
445 elif use_ntvfs
== False:
446 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
447 "Please re-run with --use-xattrs omitted.")
448 elif use_xattrs
== "auto" and not lp
.get("posix:eadb"):
450 file = tempfile
.NamedTemporaryFile(dir=os
.path
.abspath(targetdir
))
452 file = tempfile
.NamedTemporaryFile(dir=os
.path
.abspath(os
.path
.dirname(lp
.get("private dir"))))
455 samba
.ntacls
.setntacl(lp
, file.name
,
456 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
459 self
.logger
.info("You are not root or your system does not support xattr, using tdb backend for attributes. ")
464 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.")
465 if ldap_backend_type
== "existing":
466 if ldap_backend_forced_uri
is not None:
467 self
.logger
.warn("You have specified to use an existing LDAP server as the backend, please make sure an LDAP server is running at %s" % ldap_backend_forced_uri
)
469 self
.logger
.info("You have specified to use an existing LDAP server as the backend, please make sure an LDAP server is running at the default location")
471 if ldap_backend_forced_uri
is not None:
472 self
.logger
.warn("You have specified to use an fixed URI %s for connecting to your LDAP server backend. This is NOT RECOMMENDED, as our default communiation over ldapi:// is more secure and much less")
474 if domain_sid
is not None:
475 domain_sid
= security
.dom_sid(domain_sid
)
477 session
= system_session()
479 result
= provision(self
.logger
,
480 session
, smbconf
=smbconf
, targetdir
=targetdir
,
481 samdb_fill
=samdb_fill
, realm
=realm
, domain
=domain
,
482 domainguid
=domain_guid
, domainsid
=domain_sid
,
484 hostip
=host_ip
, hostip6
=host_ip6
,
485 sitename
=site
, ntdsguid
=ntds_guid
,
486 invocationid
=invocationid
, adminpass
=adminpass
,
487 krbtgtpass
=krbtgtpass
, machinepass
=machinepass
,
488 dns_backend
=dns_backend
, dns_forwarder
=dns_forwarder
,
489 dnspass
=dnspass
, root
=root
, nobody
=nobody
,
491 serverrole
=server_role
, dom_for_fun_level
=dom_for_fun_level
,
492 backend_type
=ldap_backend_type
,
493 ldapadminpass
=ldapadminpass
, ol_mmr_urls
=ol_mmr_urls
, slapd_path
=slapd_path
,
494 useeadb
=eadb
, next_rid
=next_rid
, lp
=lp
, use_ntvfs
=use_ntvfs
,
495 use_rfc2307
=use_rfc2307
, skip_sysvolacl
=False,
496 ldap_backend_extra_port
=ldap_backend_extra_port
,
497 ldap_backend_forced_uri
=ldap_backend_forced_uri
,
498 nosync
=ldap_backend_nosync
, ldap_dryrun_mode
=ldap_dryrun_mode
,
499 base_schema
=base_schema
,
500 plaintext_secrets
=plaintext_secrets
)
502 except ProvisioningError
as e
:
503 raise CommandError("Provision failed", e
)
505 result
.report_logger(self
.logger
)
507 def _get_nameserver_ip(self
):
508 """Grab the nameserver IP address from /etc/resolv.conf."""
510 RESOLV_CONF
="/etc/resolv.conf"
512 if not path
.isfile(RESOLV_CONF
):
513 self
.logger
.warning("Failed to locate %s" % RESOLV_CONF
)
518 handle
= open(RESOLV_CONF
, 'r')
520 if not line
.startswith('nameserver'):
522 # we want the last non-space continuous string of the line
523 return line
.strip().split()[-1]
525 if handle
is not None:
528 self
.logger
.warning("No nameserver found in %s" % RESOLV_CONF
)
530 def _adminpass_issue(self
, adminpass
):
531 """Returns error string for a bad administrator password,
532 or None if acceptable"""
534 if len(adminpass
.decode('utf-8')) < DEFAULT_MIN_PWD_LENGTH
:
535 return "Administrator password does not meet the default minimum" \
536 " password length requirement (%d characters)" \
537 % DEFAULT_MIN_PWD_LENGTH
538 elif not samba
.check_password_quality(adminpass
):
539 return "Administrator password does not meet the default" \
545 class cmd_domain_dcpromo(Command
):
546 """Promote an existing domain member or NT4 PDC to an AD DC."""
548 synopsis
= "%prog <dnsdomain> [DC|RODC] [options]"
550 takes_optiongroups
= {
551 "sambaopts": options
.SambaOptions
,
552 "versionopts": options
.VersionOptions
,
553 "credopts": options
.CredentialsOptions
,
557 Option("--server", help="DC to join", type=str),
558 Option("--site", help="site to join", type=str),
559 Option("--targetdir", help="where to store provision", type=str),
560 Option("--domain-critical-only",
561 help="only replicate critical domain objects",
562 action
="store_true"),
563 Option("--machinepass", type=str, metavar
="PASSWORD",
564 help="choose machine password (otherwise random)"),
565 Option("--dns-backend", type="choice", metavar
="NAMESERVER-BACKEND",
566 choices
=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
567 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
568 "BIND9_DLZ uses samba4 AD to store zone information, "
569 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
570 default
="SAMBA_INTERNAL"),
571 Option("--quiet", help="Be quiet", action
="store_true"),
572 Option("--verbose", help="Be verbose", action
="store_true")
576 Option("--use-ntvfs", action
="store_true", help="Use NTVFS for the fileserver (default = no)"),
579 if samba
.is_ntvfs_fileserver_built():
580 takes_options
.extend(ntvfs_options
)
583 takes_args
= ["domain", "role?"]
585 def run(self
, domain
, role
=None, sambaopts
=None, credopts
=None,
586 versionopts
=None, server
=None, site
=None, targetdir
=None,
587 domain_critical_only
=False, parent_domain
=None, machinepass
=None,
588 use_ntvfs
=False, dns_backend
=None,
589 quiet
=False, verbose
=False):
590 lp
= sambaopts
.get_loadparm()
591 creds
= credopts
.get_credentials(lp
)
592 net
= Net(creds
, lp
, server
=credopts
.ipaddress
)
594 logger
= self
.get_logger()
596 logger
.setLevel(logging
.DEBUG
)
598 logger
.setLevel(logging
.WARNING
)
600 logger
.setLevel(logging
.INFO
)
602 netbios_name
= lp
.get("netbios name")
608 join_DC(logger
=logger
, server
=server
, creds
=creds
, lp
=lp
, domain
=domain
,
609 site
=site
, netbios_name
=netbios_name
, targetdir
=targetdir
,
610 domain_critical_only
=domain_critical_only
,
611 machinepass
=machinepass
, use_ntvfs
=use_ntvfs
,
612 dns_backend
=dns_backend
,
613 promote_existing
=True)
615 join_RODC(logger
=logger
, server
=server
, creds
=creds
, lp
=lp
, domain
=domain
,
616 site
=site
, netbios_name
=netbios_name
, targetdir
=targetdir
,
617 domain_critical_only
=domain_critical_only
,
618 machinepass
=machinepass
, use_ntvfs
=use_ntvfs
, dns_backend
=dns_backend
,
619 promote_existing
=True)
621 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role
)
624 class cmd_domain_join(Command
):
625 """Join domain as either member or backup domain controller."""
627 synopsis
= "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
629 takes_optiongroups
= {
630 "sambaopts": options
.SambaOptions
,
631 "versionopts": options
.VersionOptions
,
632 "credopts": options
.CredentialsOptions
,
636 Option("--server", help="DC to join", type=str),
637 Option("--site", help="site to join", type=str),
638 Option("--targetdir", help="where to store provision", type=str),
639 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
640 Option("--domain-critical-only",
641 help="only replicate critical domain objects",
642 action
="store_true"),
643 Option("--machinepass", type=str, metavar
="PASSWORD",
644 help="choose machine password (otherwise random)"),
645 Option("--adminpass", type="string", metavar
="PASSWORD",
646 help="choose adminstrator password when joining as a subdomain (otherwise random)"),
647 Option("--dns-backend", type="choice", metavar
="NAMESERVER-BACKEND",
648 choices
=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
649 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
650 "BIND9_DLZ uses samba4 AD to store zone information, "
651 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
652 default
="SAMBA_INTERNAL"),
653 Option("--plaintext-secrets", action
="store_true",
654 help="Store secret/sensitive values as plain text on disk" +
655 "(default is to encrypt secret/ensitive values)"),
656 Option("--quiet", help="Be quiet", action
="store_true"),
657 Option("--verbose", help="Be verbose", action
="store_true")
661 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
664 if samba
.is_ntvfs_fileserver_built():
665 takes_options
.extend(ntvfs_options
)
667 takes_args
= ["domain", "role?"]
669 def run(self
, domain
, role
=None, sambaopts
=None, credopts
=None,
670 versionopts
=None, server
=None, site
=None, targetdir
=None,
671 domain_critical_only
=False, parent_domain
=None, machinepass
=None,
672 use_ntvfs
=False, dns_backend
=None, adminpass
=None,
673 quiet
=False, verbose
=False, plaintext_secrets
=False):
674 lp
= sambaopts
.get_loadparm()
675 creds
= credopts
.get_credentials(lp
)
676 net
= Net(creds
, lp
, server
=credopts
.ipaddress
)
679 site
= "Default-First-Site-Name"
681 logger
= self
.get_logger()
683 logger
.setLevel(logging
.DEBUG
)
685 logger
.setLevel(logging
.WARNING
)
687 logger
.setLevel(logging
.INFO
)
689 netbios_name
= lp
.get("netbios name")
694 if role
is None or role
== "MEMBER":
695 (join_password
, sid
, domain_name
) = net
.join_member(
696 domain
, netbios_name
, LIBNET_JOIN_AUTOMATIC
,
697 machinepass
=machinepass
)
699 self
.errf
.write("Joined domain %s (%s)\n" % (domain_name
, sid
))
701 join_DC(logger
=logger
, server
=server
, creds
=creds
, lp
=lp
, domain
=domain
,
702 site
=site
, netbios_name
=netbios_name
, targetdir
=targetdir
,
703 domain_critical_only
=domain_critical_only
,
704 machinepass
=machinepass
, use_ntvfs
=use_ntvfs
,
705 dns_backend
=dns_backend
,
706 plaintext_secrets
=plaintext_secrets
)
708 join_RODC(logger
=logger
, server
=server
, creds
=creds
, lp
=lp
, domain
=domain
,
709 site
=site
, netbios_name
=netbios_name
, targetdir
=targetdir
,
710 domain_critical_only
=domain_critical_only
,
711 machinepass
=machinepass
, use_ntvfs
=use_ntvfs
,
712 dns_backend
=dns_backend
,
713 plaintext_secrets
=plaintext_secrets
)
714 elif role
== "SUBDOMAIN":
716 logger
.info("Administrator password will be set randomly!")
718 netbios_domain
= lp
.get("workgroup")
719 if parent_domain
is None:
720 parent_domain
= ".".join(domain
.split(".")[1:])
721 join_subdomain(logger
=logger
, server
=server
, creds
=creds
, lp
=lp
, dnsdomain
=domain
,
722 parent_domain
=parent_domain
, site
=site
,
723 netbios_name
=netbios_name
, netbios_domain
=netbios_domain
,
724 targetdir
=targetdir
, machinepass
=machinepass
,
725 use_ntvfs
=use_ntvfs
, dns_backend
=dns_backend
,
727 plaintext_secrets
=plaintext_secrets
)
729 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role
)
732 class cmd_domain_demote(Command
):
733 """Demote ourselves from the role of Domain Controller."""
735 synopsis
= "%prog [options]"
738 Option("--server", help="writable DC to write demotion changes on", type=str),
739 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
740 metavar
="URL", dest
="H"),
741 Option("--remove-other-dead-server", help="Dead DC (name or NTDS GUID) "
742 "to remove ALL references to (rather than this DC)", type=str),
743 Option("--quiet", help="Be quiet", action
="store_true"),
744 Option("--verbose", help="Be verbose", action
="store_true"),
747 takes_optiongroups
= {
748 "sambaopts": options
.SambaOptions
,
749 "credopts": options
.CredentialsOptions
,
750 "versionopts": options
.VersionOptions
,
753 def run(self
, sambaopts
=None, credopts
=None,
754 versionopts
=None, server
=None,
755 remove_other_dead_server
=None, H
=None,
756 verbose
=False, quiet
=False):
757 lp
= sambaopts
.get_loadparm()
758 creds
= credopts
.get_credentials(lp
)
759 net
= Net(creds
, lp
, server
=credopts
.ipaddress
)
761 logger
= self
.get_logger()
763 logger
.setLevel(logging
.DEBUG
)
765 logger
.setLevel(logging
.WARNING
)
767 logger
.setLevel(logging
.INFO
)
769 if remove_other_dead_server
is not None:
770 if server
is not None:
771 samdb
= SamDB(url
="ldap://%s" % server
,
772 session_info
=system_session(),
773 credentials
=creds
, lp
=lp
)
775 samdb
= SamDB(url
=H
, session_info
=system_session(), credentials
=creds
, lp
=lp
)
777 remove_dc
.remove_dc(samdb
, logger
, remove_other_dead_server
)
778 except remove_dc
.DemoteException
as err
:
779 raise CommandError("Demote failed: %s" % err
)
782 netbios_name
= lp
.get("netbios name")
783 samdb
= SamDB(url
=H
, session_info
=system_session(), credentials
=creds
, lp
=lp
)
785 res
= samdb
.search(expression
='(&(objectClass=computer)(serverReferenceBL=*))', attrs
=["dnsHostName", "name"])
787 raise CommandError("Unable to search for servers")
790 raise CommandError("You are the latest server in the domain")
794 if str(e
["name"]).lower() != netbios_name
.lower():
795 server
= e
["dnsHostName"]
798 ntds_guid
= samdb
.get_ntds_GUID()
799 msg
= samdb
.search(base
=str(samdb
.get_config_basedn()),
800 scope
=ldb
.SCOPE_SUBTREE
, expression
="(objectGUID=%s)" % ntds_guid
,
802 if len(msg
) == 0 or "options" not in msg
[0]:
803 raise CommandError("Failed to find options on %s" % ntds_guid
)
806 dsa_options
= int(str(msg
[0]['options']))
808 res
= samdb
.search(expression
="(fSMORoleOwner=%s)" % str(ntds_dn
),
809 controls
=["search_options:1:2"])
812 raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res
))
814 self
.errf
.write("Using %s as partner server for the demotion\n" %
816 (drsuapiBind
, drsuapi_handle
, supportedExtensions
) = drsuapi_connect(server
, lp
, creds
)
818 self
.errf
.write("Deactivating inbound replication\n")
823 if not (dsa_options
& DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL
) and not samdb
.am_rodc():
824 dsa_options |
= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
825 nmsg
["options"] = ldb
.MessageElement(str(dsa_options
), ldb
.FLAG_MOD_REPLACE
, "options")
829 self
.errf
.write("Asking partner server %s to synchronize from us\n"
831 for part
in (samdb
.get_schema_basedn(),
832 samdb
.get_config_basedn(),
833 samdb
.get_root_basedn()):
834 nc
= drsuapi
.DsReplicaObjectIdentifier()
837 req1
= drsuapi
.DsReplicaSyncRequest1()
838 req1
.naming_context
= nc
;
839 req1
.options
= drsuapi
.DRSUAPI_DRS_WRIT_REP
840 req1
.source_dsa_guid
= misc
.GUID(ntds_guid
)
843 drsuapiBind
.DsReplicaSync(drsuapi_handle
, 1, req1
)
844 except RuntimeError as e1
:
845 (werr
, string
) = e1
.args
846 if werr
== werror
.WERR_DS_DRA_NO_REPLICA
:
850 "Error while replicating out last local changes from '%s' for demotion, "
851 "re-enabling inbound replication\n" % part
)
852 dsa_options ^
= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
853 nmsg
["options"] = ldb
.MessageElement(str(dsa_options
), ldb
.FLAG_MOD_REPLACE
, "options")
855 raise CommandError("Error while sending a DsReplicaSync for partition '%s'" % str(part
), string
)
857 remote_samdb
= SamDB(url
="ldap://%s" % server
,
858 session_info
=system_session(),
859 credentials
=creds
, lp
=lp
)
861 self
.errf
.write("Changing userControl and container\n")
862 res
= remote_samdb
.search(base
=str(remote_samdb
.domain_dn()),
863 expression
="(&(objectClass=user)(sAMAccountName=%s$))" %
864 netbios_name
.upper(),
865 attrs
=["userAccountControl"])
867 uac
= int(str(res
[0]["userAccountControl"]))
869 except Exception as e
:
870 if not (dsa_options
& DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL
) and not samdb
.am_rodc():
872 "Error while demoting, re-enabling inbound replication\n")
873 dsa_options ^
= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
874 nmsg
["options"] = ldb
.MessageElement(str(dsa_options
), ldb
.FLAG_MOD_REPLACE
, "options")
876 raise CommandError("Error while changing account control", e
)
879 if not (dsa_options
& DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL
) and not samdb
.am_rodc():
881 "Error while demoting, re-enabling inbound replication")
882 dsa_options ^
= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
883 nmsg
["options"] = ldb
.MessageElement(str(dsa_options
), ldb
.FLAG_MOD_REPLACE
, "options")
885 raise CommandError("Unable to find object with samaccountName = %s$"
886 " in the remote dc" % netbios_name
.upper())
890 uac
&= ~
(UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION|UF_PARTIAL_SECRETS_ACCOUNT
)
891 uac |
= UF_WORKSTATION_TRUST_ACCOUNT
896 msg
["userAccountControl"] = ldb
.MessageElement("%d" % uac
,
897 ldb
.FLAG_MOD_REPLACE
,
898 "userAccountControl")
900 remote_samdb
.modify(msg
)
901 except Exception as e
:
902 if not (dsa_options
& DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL
) and not samdb
.am_rodc():
904 "Error while demoting, re-enabling inbound replication")
905 dsa_options ^
= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
906 nmsg
["options"] = ldb
.MessageElement(str(dsa_options
), ldb
.FLAG_MOD_REPLACE
, "options")
909 raise CommandError("Error while changing account control", e
)
911 parent
= msg
.dn
.parent()
912 dc_name
= res
[0].dn
.get_rdn_value()
913 rdn
= "CN=%s" % dc_name
915 # Let's move to the Computer container
919 computer_dn
= ldb
.Dn(remote_samdb
, "CN=Computers,%s" % str(remote_samdb
.domain_dn()))
920 res
= remote_samdb
.search(base
=computer_dn
, expression
=rdn
, scope
=ldb
.SCOPE_ONELEVEL
)
923 res
= remote_samdb
.search(base
=computer_dn
, expression
="%s-%d" % (rdn
, i
),
924 scope
=ldb
.SCOPE_ONELEVEL
)
925 while(len(res
) != 0 and i
< 100):
927 res
= remote_samdb
.search(base
=computer_dn
, expression
="%s-%d" % (rdn
, i
),
928 scope
=ldb
.SCOPE_ONELEVEL
)
931 if not (dsa_options
& DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL
) and not samdb
.am_rodc():
933 "Error while demoting, re-enabling inbound replication\n")
934 dsa_options ^
= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
935 nmsg
["options"] = ldb
.MessageElement(str(dsa_options
), ldb
.FLAG_MOD_REPLACE
, "options")
941 msg
["userAccountControl"] = ldb
.MessageElement("%d" % uac
,
942 ldb
.FLAG_MOD_REPLACE
,
943 "userAccountControl")
945 remote_samdb
.modify(msg
)
947 raise CommandError("Unable to find a slot for renaming %s,"
948 " all names from %s-1 to %s-%d seemed used" %
949 (str(dc_dn
), rdn
, rdn
, i
- 9))
951 newrdn
= "%s-%d" % (rdn
, i
)
954 newdn
= ldb
.Dn(remote_samdb
, "%s,%s" % (newrdn
, str(computer_dn
)))
955 remote_samdb
.rename(dc_dn
, newdn
)
956 except Exception as e
:
957 if not (dsa_options
& DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL
) and not samdb
.am_rodc():
959 "Error while demoting, re-enabling inbound replication\n")
960 dsa_options ^
= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
961 nmsg
["options"] = ldb
.MessageElement(str(dsa_options
), ldb
.FLAG_MOD_REPLACE
, "options")
967 msg
["userAccountControl"] = ldb
.MessageElement("%d" % uac
,
968 ldb
.FLAG_MOD_REPLACE
,
969 "userAccountControl")
971 remote_samdb
.modify(msg
)
972 raise CommandError("Error while renaming %s to %s" % (str(dc_dn
), str(newdn
)), e
)
975 server_dsa_dn
= samdb
.get_serverName()
976 domain
= remote_samdb
.get_root_basedn()
979 req1
= drsuapi
.DsRemoveDSServerRequest1()
980 req1
.server_dn
= str(server_dsa_dn
)
981 req1
.domain_dn
= str(domain
)
984 drsuapiBind
.DsRemoveDSServer(drsuapi_handle
, 1, req1
)
985 except RuntimeError as e3
:
986 (werr
, string
) = e3
.args
987 if not (dsa_options
& DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL
) and not samdb
.am_rodc():
989 "Error while demoting, re-enabling inbound replication\n")
990 dsa_options ^
= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
991 nmsg
["options"] = ldb
.MessageElement(str(dsa_options
), ldb
.FLAG_MOD_REPLACE
, "options")
997 msg
["userAccountControl"] = ldb
.MessageElement("%d" % uac
,
998 ldb
.FLAG_MOD_REPLACE
,
999 "userAccountControl")
1000 remote_samdb
.modify(msg
)
1001 remote_samdb
.rename(newdn
, dc_dn
)
1002 if werr
== werror
.WERR_DS_DRA_NO_REPLICA
:
1003 raise CommandError("The DC %s is not present on (already removed from) the remote server: " % server_dsa_dn
, e
)
1005 raise CommandError("Error while sending a removeDsServer of %s: " % server_dsa_dn
, e
)
1007 remove_dc
.remove_sysvol_references(remote_samdb
, logger
, dc_name
)
1009 # These are objects under the computer account that should be deleted
1010 for s
in ("CN=Enterprise,CN=NTFRS Subscriptions",
1011 "CN=%s, CN=NTFRS Subscriptions" % lp
.get("realm"),
1012 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
1013 "CN=NTFRS Subscriptions"):
1015 remote_samdb
.delete(ldb
.Dn(remote_samdb
,
1016 "%s,%s" % (s
, str(newdn
))))
1017 except ldb
.LdbError
as l
:
1020 self
.errf
.write("Demote successful\n")
1023 class cmd_domain_level(Command
):
1024 """Raise domain and forest function levels."""
1026 synopsis
= "%prog (show|raise <options>) [options]"
1028 takes_optiongroups
= {
1029 "sambaopts": options
.SambaOptions
,
1030 "credopts": options
.CredentialsOptions
,
1031 "versionopts": options
.VersionOptions
,
1035 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1036 metavar
="URL", dest
="H"),
1037 Option("--quiet", help="Be quiet", action
="store_true"),
1038 Option("--forest-level", type="choice", choices
=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1039 help="The forest function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)"),
1040 Option("--domain-level", type="choice", choices
=["2003", "2008", "2008_R2", "2012", "2012_R2"],
1041 help="The domain function level (2003 | 2008 | 2008_R2 | 2012 | 2012_R2)")
1044 takes_args
= ["subcommand"]
1046 def run(self
, subcommand
, H
=None, forest_level
=None, domain_level
=None,
1047 quiet
=False, credopts
=None, sambaopts
=None, versionopts
=None):
1048 lp
= sambaopts
.get_loadparm()
1049 creds
= credopts
.get_credentials(lp
, fallback_machine
=True)
1051 samdb
= SamDB(url
=H
, session_info
=system_session(),
1052 credentials
=creds
, lp
=lp
)
1054 domain_dn
= samdb
.domain_dn()
1056 res_forest
= samdb
.search("CN=Partitions,%s" % samdb
.get_config_basedn(),
1057 scope
=ldb
.SCOPE_BASE
, attrs
=["msDS-Behavior-Version"])
1058 assert len(res_forest
) == 1
1060 res_domain
= samdb
.search(domain_dn
, scope
=ldb
.SCOPE_BASE
,
1061 attrs
=["msDS-Behavior-Version", "nTMixedDomain"])
1062 assert len(res_domain
) == 1
1064 res_dc_s
= samdb
.search("CN=Sites,%s" % samdb
.get_config_basedn(),
1065 scope
=ldb
.SCOPE_SUBTREE
, expression
="(objectClass=nTDSDSA)",
1066 attrs
=["msDS-Behavior-Version"])
1067 assert len(res_dc_s
) >= 1
1069 # default values, since "msDS-Behavior-Version" does not exist on Windows 2000 AD
1070 level_forest
= DS_DOMAIN_FUNCTION_2000
1071 level_domain
= DS_DOMAIN_FUNCTION_2000
1073 if "msDS-Behavior-Version" in res_forest
[0]:
1074 level_forest
= int(res_forest
[0]["msDS-Behavior-Version"][0])
1075 if "msDS-Behavior-Version" in res_domain
[0]:
1076 level_domain
= int(res_domain
[0]["msDS-Behavior-Version"][0])
1077 level_domain_mixed
= int(res_domain
[0]["nTMixedDomain"][0])
1080 for msg
in res_dc_s
:
1081 if "msDS-Behavior-Version" in msg
:
1082 if min_level_dc
is None or int(msg
["msDS-Behavior-Version"][0]) < min_level_dc
:
1083 min_level_dc
= int(msg
["msDS-Behavior-Version"][0])
1085 min_level_dc
= DS_DOMAIN_FUNCTION_2000
1086 # well, this is the least
1089 if level_forest
< DS_DOMAIN_FUNCTION_2000
or level_domain
< DS_DOMAIN_FUNCTION_2000
:
1090 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
1091 if min_level_dc
< DS_DOMAIN_FUNCTION_2000
:
1092 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
1093 if level_forest
> level_domain
:
1094 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
1095 if level_domain
> min_level_dc
:
1096 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
1098 if subcommand
== "show":
1099 self
.message("Domain and forest function level for domain '%s'" % domain_dn
)
1100 if level_forest
== DS_DOMAIN_FUNCTION_2000
and level_domain_mixed
!= 0:
1101 self
.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1102 if level_domain
== DS_DOMAIN_FUNCTION_2000
and level_domain_mixed
!= 0:
1103 self
.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
1104 if min_level_dc
== DS_DOMAIN_FUNCTION_2000
and level_domain_mixed
!= 0:
1105 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)!")
1109 if level_forest
== DS_DOMAIN_FUNCTION_2000
:
1111 elif level_forest
== DS_DOMAIN_FUNCTION_2003_MIXED
:
1112 outstr
= "2003 with mixed domains/interim (NT4 DC support)"
1113 elif level_forest
== DS_DOMAIN_FUNCTION_2003
:
1115 elif level_forest
== DS_DOMAIN_FUNCTION_2008
:
1117 elif level_forest
== DS_DOMAIN_FUNCTION_2008_R2
:
1119 elif level_forest
== DS_DOMAIN_FUNCTION_2012
:
1121 elif level_forest
== DS_DOMAIN_FUNCTION_2012_R2
:
1124 outstr
= "higher than 2012 R2"
1125 self
.message("Forest function level: (Windows) " + outstr
)
1127 if level_domain
== DS_DOMAIN_FUNCTION_2000
and level_domain_mixed
!= 0:
1128 outstr
= "2000 mixed (NT4 DC support)"
1129 elif level_domain
== DS_DOMAIN_FUNCTION_2000
and level_domain_mixed
== 0:
1131 elif level_domain
== DS_DOMAIN_FUNCTION_2003_MIXED
:
1132 outstr
= "2003 with mixed domains/interim (NT4 DC support)"
1133 elif level_domain
== DS_DOMAIN_FUNCTION_2003
:
1135 elif level_domain
== DS_DOMAIN_FUNCTION_2008
:
1137 elif level_domain
== DS_DOMAIN_FUNCTION_2008_R2
:
1139 elif level_domain
== DS_DOMAIN_FUNCTION_2012
:
1141 elif level_domain
== DS_DOMAIN_FUNCTION_2012_R2
:
1144 outstr
= "higher than 2012 R2"
1145 self
.message("Domain function level: (Windows) " + outstr
)
1147 if min_level_dc
== DS_DOMAIN_FUNCTION_2000
:
1149 elif min_level_dc
== DS_DOMAIN_FUNCTION_2003
:
1151 elif min_level_dc
== DS_DOMAIN_FUNCTION_2008
:
1153 elif min_level_dc
== DS_DOMAIN_FUNCTION_2008_R2
:
1155 elif min_level_dc
== DS_DOMAIN_FUNCTION_2012
:
1157 elif min_level_dc
== DS_DOMAIN_FUNCTION_2012_R2
:
1160 outstr
= "higher than 2012 R2"
1161 self
.message("Lowest function level of a DC: (Windows) " + outstr
)
1163 elif subcommand
== "raise":
1166 if domain_level
is not None:
1167 if domain_level
== "2003":
1168 new_level_domain
= DS_DOMAIN_FUNCTION_2003
1169 elif domain_level
== "2008":
1170 new_level_domain
= DS_DOMAIN_FUNCTION_2008
1171 elif domain_level
== "2008_R2":
1172 new_level_domain
= DS_DOMAIN_FUNCTION_2008_R2
1173 elif domain_level
== "2012":
1174 new_level_domain
= DS_DOMAIN_FUNCTION_2012
1175 elif domain_level
== "2012_R2":
1176 new_level_domain
= DS_DOMAIN_FUNCTION_2012_R2
1178 if new_level_domain
<= level_domain
and level_domain_mixed
== 0:
1179 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
1180 if new_level_domain
> min_level_dc
:
1181 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
1183 # Deactivate mixed/interim domain support
1184 if level_domain_mixed
!= 0:
1185 # Directly on the base DN
1187 m
.dn
= ldb
.Dn(samdb
, domain_dn
)
1188 m
["nTMixedDomain"] = ldb
.MessageElement("0",
1189 ldb
.FLAG_MOD_REPLACE
, "nTMixedDomain")
1193 m
.dn
= ldb
.Dn(samdb
, "CN=" + lp
.get("workgroup") + ",CN=Partitions,%s" % samdb
.get_config_basedn())
1194 m
["nTMixedDomain"] = ldb
.MessageElement("0",
1195 ldb
.FLAG_MOD_REPLACE
, "nTMixedDomain")
1198 except ldb
.LdbError
as e
:
1199 (enum
, emsg
) = e
.args
1200 if enum
!= ldb
.ERR_UNWILLING_TO_PERFORM
:
1203 # Directly on the base DN
1205 m
.dn
= ldb
.Dn(samdb
, domain_dn
)
1206 m
["msDS-Behavior-Version"]= ldb
.MessageElement(
1207 str(new_level_domain
), ldb
.FLAG_MOD_REPLACE
,
1208 "msDS-Behavior-Version")
1212 m
.dn
= ldb
.Dn(samdb
, "CN=" + lp
.get("workgroup")
1213 + ",CN=Partitions,%s" % samdb
.get_config_basedn())
1214 m
["msDS-Behavior-Version"]= ldb
.MessageElement(
1215 str(new_level_domain
), ldb
.FLAG_MOD_REPLACE
,
1216 "msDS-Behavior-Version")
1219 except ldb
.LdbError
as e2
:
1220 (enum
, emsg
) = e2
.args
1221 if enum
!= ldb
.ERR_UNWILLING_TO_PERFORM
:
1224 level_domain
= new_level_domain
1225 msgs
.append("Domain function level changed!")
1227 if forest_level
is not None:
1228 if forest_level
== "2003":
1229 new_level_forest
= DS_DOMAIN_FUNCTION_2003
1230 elif forest_level
== "2008":
1231 new_level_forest
= DS_DOMAIN_FUNCTION_2008
1232 elif forest_level
== "2008_R2":
1233 new_level_forest
= DS_DOMAIN_FUNCTION_2008_R2
1234 elif forest_level
== "2012":
1235 new_level_forest
= DS_DOMAIN_FUNCTION_2012
1236 elif forest_level
== "2012_R2":
1237 new_level_forest
= DS_DOMAIN_FUNCTION_2012_R2
1239 if new_level_forest
<= level_forest
:
1240 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1241 if new_level_forest
> level_domain
:
1242 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1245 m
.dn
= ldb
.Dn(samdb
, "CN=Partitions,%s" % samdb
.get_config_basedn())
1246 m
["msDS-Behavior-Version"]= ldb
.MessageElement(
1247 str(new_level_forest
), ldb
.FLAG_MOD_REPLACE
,
1248 "msDS-Behavior-Version")
1250 msgs
.append("Forest function level changed!")
1251 msgs
.append("All changes applied successfully!")
1252 self
.message("\n".join(msgs
))
1254 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand
)
1257 class cmd_domain_passwordsettings(Command
):
1258 """Set password settings.
1260 Password complexity, password lockout policy, history length,
1261 minimum password length, the minimum and maximum password age) on
1262 a Samba AD DC server.
1264 Use against a Windows DC is possible, but group policy will override it.
1267 synopsis
= "%prog (show|set <options>) [options]"
1269 takes_optiongroups
= {
1270 "sambaopts": options
.SambaOptions
,
1271 "versionopts": options
.VersionOptions
,
1272 "credopts": options
.CredentialsOptions
,
1276 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1277 metavar
="URL", dest
="H"),
1278 Option("--quiet", help="Be quiet", action
="store_true"),
1279 Option("--complexity", type="choice", choices
=["on","off","default"],
1280 help="The password complexity (on | off | default). Default is 'on'"),
1281 Option("--store-plaintext", type="choice", choices
=["on","off","default"],
1282 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1283 Option("--history-length",
1284 help="The password history length (<integer> | default). Default is 24.", type=str),
1285 Option("--min-pwd-length",
1286 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1287 Option("--min-pwd-age",
1288 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1289 Option("--max-pwd-age",
1290 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1291 Option("--account-lockout-duration",
1292 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),
1293 Option("--account-lockout-threshold",
1294 help="The number of bad password attempts allowed before locking out the account (<integer> | default). Default is 0 (never lock out).", type=str),
1295 Option("--reset-account-lockout-after",
1296 help="After this time is elapsed, the recorded number of attempts restarts from zero (<integer> | default). Default is 30.", type=str),
1299 takes_args
= ["subcommand"]
1301 def run(self
, subcommand
, H
=None, min_pwd_age
=None, max_pwd_age
=None,
1302 quiet
=False, complexity
=None, store_plaintext
=None, history_length
=None,
1303 min_pwd_length
=None, account_lockout_duration
=None, account_lockout_threshold
=None,
1304 reset_account_lockout_after
=None, credopts
=None, sambaopts
=None,
1306 lp
= sambaopts
.get_loadparm()
1307 creds
= credopts
.get_credentials(lp
)
1309 samdb
= SamDB(url
=H
, session_info
=system_session(),
1310 credentials
=creds
, lp
=lp
)
1312 domain_dn
= samdb
.domain_dn()
1313 res
= samdb
.search(domain_dn
, scope
=ldb
.SCOPE_BASE
,
1314 attrs
=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1315 "minPwdAge", "maxPwdAge", "lockoutDuration", "lockoutThreshold",
1316 "lockOutObservationWindow"])
1317 assert(len(res
) == 1)
1319 pwd_props
= int(res
[0]["pwdProperties"][0])
1320 pwd_hist_len
= int(res
[0]["pwdHistoryLength"][0])
1321 cur_min_pwd_len
= int(res
[0]["minPwdLength"][0])
1323 cur_min_pwd_age
= int(abs(int(res
[0]["minPwdAge"][0])) / (1e7
* 60 * 60 * 24))
1324 if int(res
[0]["maxPwdAge"][0]) == -0x8000000000000000:
1327 cur_max_pwd_age
= int(abs(int(res
[0]["maxPwdAge"][0])) / (1e7
* 60 * 60 * 24))
1328 cur_account_lockout_threshold
= int(res
[0]["lockoutThreshold"][0])
1330 if int(res
[0]["lockoutDuration"][0]) == -0x8000000000000000:
1331 cur_account_lockout_duration
= 0
1333 cur_account_lockout_duration
= abs(int(res
[0]["lockoutDuration"][0])) / (1e7
* 60)
1334 cur_reset_account_lockout_after
= abs(int(res
[0]["lockOutObservationWindow"][0])) / (1e7
* 60)
1335 except Exception as e
:
1336 raise CommandError("Could not retrieve password properties!", e
)
1338 if subcommand
== "show":
1339 self
.message("Password informations for domain '%s'" % domain_dn
)
1341 if pwd_props
& DOMAIN_PASSWORD_COMPLEX
!= 0:
1342 self
.message("Password complexity: on")
1344 self
.message("Password complexity: off")
1345 if pwd_props
& DOMAIN_PASSWORD_STORE_CLEARTEXT
!= 0:
1346 self
.message("Store plaintext passwords: on")
1348 self
.message("Store plaintext passwords: off")
1349 self
.message("Password history length: %d" % pwd_hist_len
)
1350 self
.message("Minimum password length: %d" % cur_min_pwd_len
)
1351 self
.message("Minimum password age (days): %d" % cur_min_pwd_age
)
1352 self
.message("Maximum password age (days): %d" % cur_max_pwd_age
)
1353 self
.message("Account lockout duration (mins): %d" % cur_account_lockout_duration
)
1354 self
.message("Account lockout threshold (attempts): %d" % cur_account_lockout_threshold
)
1355 self
.message("Reset account lockout after (mins): %d" % cur_reset_account_lockout_after
)
1356 elif subcommand
== "set":
1359 m
.dn
= ldb
.Dn(samdb
, domain_dn
)
1361 if complexity
is not None:
1362 if complexity
== "on" or complexity
== "default":
1363 pwd_props
= pwd_props | DOMAIN_PASSWORD_COMPLEX
1364 msgs
.append("Password complexity activated!")
1365 elif complexity
== "off":
1366 pwd_props
= pwd_props
& (~DOMAIN_PASSWORD_COMPLEX
)
1367 msgs
.append("Password complexity deactivated!")
1369 if store_plaintext
is not None:
1370 if store_plaintext
== "on" or store_plaintext
== "default":
1371 pwd_props
= pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1372 msgs
.append("Plaintext password storage for changed passwords activated!")
1373 elif store_plaintext
== "off":
1374 pwd_props
= pwd_props
& (~DOMAIN_PASSWORD_STORE_CLEARTEXT
)
1375 msgs
.append("Plaintext password storage for changed passwords deactivated!")
1377 if complexity
is not None or store_plaintext
is not None:
1378 m
["pwdProperties"] = ldb
.MessageElement(str(pwd_props
),
1379 ldb
.FLAG_MOD_REPLACE
, "pwdProperties")
1381 if history_length
is not None:
1382 if history_length
== "default":
1385 pwd_hist_len
= int(history_length
)
1387 if pwd_hist_len
< 0 or pwd_hist_len
> 24:
1388 raise CommandError("Password history length must be in the range of 0 to 24!")
1390 m
["pwdHistoryLength"] = ldb
.MessageElement(str(pwd_hist_len
),
1391 ldb
.FLAG_MOD_REPLACE
, "pwdHistoryLength")
1392 msgs
.append("Password history length changed!")
1394 if min_pwd_length
is not None:
1395 if min_pwd_length
== "default":
1398 min_pwd_len
= int(min_pwd_length
)
1400 if min_pwd_len
< 0 or min_pwd_len
> 14:
1401 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1403 m
["minPwdLength"] = ldb
.MessageElement(str(min_pwd_len
),
1404 ldb
.FLAG_MOD_REPLACE
, "minPwdLength")
1405 msgs
.append("Minimum password length changed!")
1407 if min_pwd_age
is not None:
1408 if min_pwd_age
== "default":
1411 min_pwd_age
= int(min_pwd_age
)
1413 if min_pwd_age
< 0 or min_pwd_age
> 998:
1414 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1417 min_pwd_age_ticks
= -int(min_pwd_age
* (24 * 60 * 60 * 1e7
))
1419 m
["minPwdAge"] = ldb
.MessageElement(str(min_pwd_age_ticks
),
1420 ldb
.FLAG_MOD_REPLACE
, "minPwdAge")
1421 msgs
.append("Minimum password age changed!")
1423 if max_pwd_age
is not None:
1424 if max_pwd_age
== "default":
1427 max_pwd_age
= int(max_pwd_age
)
1429 if max_pwd_age
< 0 or max_pwd_age
> 999:
1430 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1433 if max_pwd_age
== 0:
1434 max_pwd_age_ticks
= -0x8000000000000000
1436 max_pwd_age_ticks
= -int(max_pwd_age
* (24 * 60 * 60 * 1e7
))
1438 m
["maxPwdAge"] = ldb
.MessageElement(str(max_pwd_age_ticks
),
1439 ldb
.FLAG_MOD_REPLACE
, "maxPwdAge")
1440 msgs
.append("Maximum password age changed!")
1442 if account_lockout_duration
is not None:
1443 if account_lockout_duration
== "default":
1444 account_lockout_duration
= 30
1446 account_lockout_duration
= int(account_lockout_duration
)
1448 if account_lockout_duration
< 0 or account_lockout_duration
> 99999:
1449 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1452 if account_lockout_duration
== 0:
1453 account_lockout_duration_ticks
= -0x8000000000000000
1455 account_lockout_duration_ticks
= -int(account_lockout_duration
* (60 * 1e7
))
1457 m
["lockoutDuration"] = ldb
.MessageElement(str(account_lockout_duration_ticks
),
1458 ldb
.FLAG_MOD_REPLACE
, "lockoutDuration")
1459 msgs
.append("Account lockout duration changed!")
1461 if account_lockout_threshold
is not None:
1462 if account_lockout_threshold
== "default":
1463 account_lockout_threshold
= 0
1465 account_lockout_threshold
= int(account_lockout_threshold
)
1467 m
["lockoutThreshold"] = ldb
.MessageElement(str(account_lockout_threshold
),
1468 ldb
.FLAG_MOD_REPLACE
, "lockoutThreshold")
1469 msgs
.append("Account lockout threshold changed!")
1471 if reset_account_lockout_after
is not None:
1472 if reset_account_lockout_after
== "default":
1473 reset_account_lockout_after
= 30
1475 reset_account_lockout_after
= int(reset_account_lockout_after
)
1477 if reset_account_lockout_after
< 0 or reset_account_lockout_after
> 99999:
1478 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1481 if reset_account_lockout_after
== 0:
1482 reset_account_lockout_after_ticks
= -0x8000000000000000
1484 reset_account_lockout_after_ticks
= -int(reset_account_lockout_after
* (60 * 1e7
))
1486 m
["lockOutObservationWindow"] = ldb
.MessageElement(str(reset_account_lockout_after_ticks
),
1487 ldb
.FLAG_MOD_REPLACE
, "lockOutObservationWindow")
1488 msgs
.append("Duration to reset account lockout after changed!")
1490 if max_pwd_age
> 0 and min_pwd_age
>= max_pwd_age
:
1491 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age
, min_pwd_age
))
1494 raise CommandError("You must specify at least one option to set. Try --help")
1496 msgs
.append("All changes applied successfully!")
1497 self
.message("\n".join(msgs
))
1499 raise CommandError("Wrong argument '%s'!" % subcommand
)
1502 class cmd_domain_classicupgrade(Command
):
1503 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1505 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1506 the testparm utility from your classic installation (with --testparm).
1509 synopsis
= "%prog [options] <classic_smb_conf>"
1511 takes_optiongroups
= {
1512 "sambaopts": options
.SambaOptions
,
1513 "versionopts": options
.VersionOptions
1517 Option("--dbdir", type="string", metavar
="DIR",
1518 help="Path to samba classic DC database directory"),
1519 Option("--testparm", type="string", metavar
="PATH",
1520 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1521 Option("--targetdir", type="string", metavar
="DIR",
1522 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1523 Option("--quiet", help="Be quiet", action
="store_true"),
1524 Option("--verbose", help="Be verbose", action
="store_true"),
1525 Option("--dns-backend", type="choice", metavar
="NAMESERVER-BACKEND",
1526 choices
=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1527 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1528 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1529 "BIND9_DLZ uses samba4 AD to store zone information, "
1530 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1531 default
="SAMBA_INTERNAL")
1535 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
1536 action
="store_true"),
1537 Option("--use-xattrs", type="choice", choices
=["yes","no","auto"],
1538 metavar
="[yes|no|auto]",
1539 help="Define if we should use the native fs capabilities or a tdb file for "
1540 "storing attributes likes ntacl when --use-ntvfs is set. "
1541 "auto tries to make an inteligent guess based on the user rights and system capabilities",
1544 if samba
.is_ntvfs_fileserver_built():
1545 takes_options
.extend(ntvfs_options
)
1547 takes_args
= ["smbconf"]
1549 def run(self
, smbconf
=None, targetdir
=None, dbdir
=None, testparm
=None,
1550 quiet
=False, verbose
=False, use_xattrs
="auto", sambaopts
=None, versionopts
=None,
1551 dns_backend
=None, use_ntvfs
=False):
1553 if not os
.path
.exists(smbconf
):
1554 raise CommandError("File %s does not exist" % smbconf
)
1556 if testparm
and not os
.path
.exists(testparm
):
1557 raise CommandError("Testparm utility %s does not exist" % testparm
)
1559 if dbdir
and not os
.path
.exists(dbdir
):
1560 raise CommandError("Directory %s does not exist" % dbdir
)
1562 if not dbdir
and not testparm
:
1563 raise CommandError("Please specify either dbdir or testparm")
1565 logger
= self
.get_logger()
1567 logger
.setLevel(logging
.DEBUG
)
1569 logger
.setLevel(logging
.WARNING
)
1571 logger
.setLevel(logging
.INFO
)
1573 if dbdir
and testparm
:
1574 logger
.warning("both dbdir and testparm specified, ignoring dbdir.")
1577 lp
= sambaopts
.get_loadparm()
1579 s3conf
= s3param
.get_context()
1582 s3conf
.set("realm", sambaopts
.realm
)
1584 if targetdir
is not None:
1585 if not os
.path
.isdir(targetdir
):
1589 if use_xattrs
== "yes":
1591 elif use_xattrs
== "auto" and use_ntvfs
== False:
1593 elif use_ntvfs
== False:
1594 raise CommandError("--use-xattrs=no requires --use-ntvfs (not supported for production use). "
1595 "Please re-run with --use-xattrs omitted.")
1596 elif use_xattrs
== "auto" and not s3conf
.get("posix:eadb"):
1598 tmpfile
= tempfile
.NamedTemporaryFile(dir=os
.path
.abspath(targetdir
))
1600 tmpfile
= tempfile
.NamedTemporaryFile(dir=os
.path
.abspath(os
.path
.dirname(lp
.get("private dir"))))
1603 samba
.ntacls
.setntacl(lp
, tmpfile
.name
,
1604 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1607 # FIXME: Don't catch all exceptions here
1608 logger
.info("You are not root or your system does not support xattr, using tdb backend for attributes. "
1609 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1613 # Set correct default values from dbdir or testparm
1616 paths
["state directory"] = dbdir
1617 paths
["private dir"] = dbdir
1618 paths
["lock directory"] = dbdir
1619 paths
["smb passwd file"] = dbdir
+ "/smbpasswd"
1621 paths
["state directory"] = get_testparm_var(testparm
, smbconf
, "state directory")
1622 paths
["private dir"] = get_testparm_var(testparm
, smbconf
, "private dir")
1623 paths
["smb passwd file"] = get_testparm_var(testparm
, smbconf
, "smb passwd file")
1624 paths
["lock directory"] = get_testparm_var(testparm
, smbconf
, "lock directory")
1625 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1626 # "state directory", instead make use of "lock directory"
1627 if len(paths
["state directory"]) == 0:
1628 paths
["state directory"] = paths
["lock directory"]
1631 s3conf
.set(p
, paths
[p
])
1633 # load smb.conf parameters
1634 logger
.info("Reading smb.conf")
1635 s3conf
.load(smbconf
)
1636 samba3
= Samba3(smbconf
, s3conf
)
1638 logger
.info("Provisioning")
1639 upgrade_from_samba3(samba3
, logger
, targetdir
, session_info
=system_session(),
1640 useeadb
=eadb
, dns_backend
=dns_backend
, use_ntvfs
=use_ntvfs
)
1643 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade
):
1644 __doc__
= cmd_domain_classicupgrade
.__doc
__
1646 # This command is present for backwards compatibility only,
1647 # and should not be shown.
1651 class LocalDCCredentialsOptions(options
.CredentialsOptions
):
1652 def __init__(self
, parser
):
1653 options
.CredentialsOptions
.__init
__(self
, parser
, special_name
="local-dc")
1655 class DomainTrustCommand(Command
):
1656 """List domain trusts."""
1659 Command
.__init
__(self
)
1660 self
.local_lp
= None
1662 self
.local_server
= None
1663 self
.local_binding_string
= None
1664 self
.local_creds
= None
1666 self
.remote_server
= None
1667 self
.remote_binding_string
= None
1668 self
.remote_creds
= None
1670 def _uint32(self
, v
):
1671 return ctypes
.c_uint32(v
).value
1673 def check_runtime_error(self
, runtime
, val
):
1677 err32
= self
._uint
32(runtime
[0])
1683 class LocalRuntimeError(CommandError
):
1684 def __init__(exception_self
, self
, runtime
, message
):
1685 err32
= self
._uint
32(runtime
[0])
1687 msg
= "LOCAL_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1688 self
.local_server
, message
, err32
, errstr
)
1689 CommandError
.__init
__(exception_self
, msg
)
1691 class RemoteRuntimeError(CommandError
):
1692 def __init__(exception_self
, self
, runtime
, message
):
1693 err32
= self
._uint
32(runtime
[0])
1695 msg
= "REMOTE_DC[%s]: %s - ERROR(0x%08X) - %s" % (
1696 self
.remote_server
, message
, err32
, errstr
)
1697 CommandError
.__init
__(exception_self
, msg
)
1699 class LocalLdbError(CommandError
):
1700 def __init__(exception_self
, self
, ldb_error
, message
):
1701 errval
= ldb_error
[0]
1702 errstr
= ldb_error
[1]
1703 msg
= "LOCAL_DC[%s]: %s - ERROR(%d) - %s" % (
1704 self
.local_server
, message
, errval
, errstr
)
1705 CommandError
.__init
__(exception_self
, msg
)
1707 def setup_local_server(self
, sambaopts
, localdcopts
):
1708 if self
.local_server
is not None:
1709 return self
.local_server
1711 lp
= sambaopts
.get_loadparm()
1713 local_server
= localdcopts
.ipaddress
1714 if local_server
is None:
1715 server_role
= lp
.server_role()
1716 if server_role
!= "ROLE_ACTIVE_DIRECTORY_DC":
1717 raise CommandError("Invalid server_role %s" % (server_role
))
1718 local_server
= lp
.get('netbios name')
1719 local_transport
= "ncalrpc"
1720 local_binding_options
= ""
1721 local_binding_options
+= ",auth_type=ncalrpc_as_system"
1722 local_ldap_url
= None
1725 local_transport
= "ncacn_np"
1726 local_binding_options
= ""
1727 local_ldap_url
= "ldap://%s" % local_server
1728 local_creds
= localdcopts
.get_credentials(lp
)
1732 self
.local_server
= local_server
1733 self
.local_binding_string
= "%s:%s[%s]" % (local_transport
, local_server
, local_binding_options
)
1734 self
.local_ldap_url
= local_ldap_url
1735 self
.local_creds
= local_creds
1736 return self
.local_server
1738 def new_local_lsa_connection(self
):
1739 return lsa
.lsarpc(self
.local_binding_string
, self
.local_lp
, self
.local_creds
)
1741 def new_local_netlogon_connection(self
):
1742 return netlogon
.netlogon(self
.local_binding_string
, self
.local_lp
, self
.local_creds
)
1744 def new_local_ldap_connection(self
):
1745 return SamDB(url
=self
.local_ldap_url
,
1746 session_info
=system_session(),
1747 credentials
=self
.local_creds
,
1750 def setup_remote_server(self
, credopts
, domain
,
1752 require_writable
=True):
1755 assert require_writable
1757 if self
.remote_server
is not None:
1758 return self
.remote_server
1760 self
.remote_server
= "__unknown__remote_server__.%s" % domain
1761 assert self
.local_server
is not None
1763 remote_creds
= credopts
.get_credentials(self
.local_lp
)
1764 remote_server
= credopts
.ipaddress
1765 remote_binding_options
= ""
1767 # TODO: we should also support NT4 domains
1768 # we could use local_netlogon.netr_DsRGetDCNameEx2() with the remote domain name
1769 # and delegate NBT or CLDAP to the local netlogon server
1771 remote_net
= Net(remote_creds
, self
.local_lp
, server
=remote_server
)
1772 remote_flags
= nbt
.NBT_SERVER_LDAP | nbt
.NBT_SERVER_DS
1773 if require_writable
:
1774 remote_flags |
= nbt
.NBT_SERVER_WRITABLE
1776 remote_flags |
= nbt
.NBT_SERVER_PDC
1777 remote_info
= remote_net
.finddc(flags
=remote_flags
, domain
=domain
, address
=remote_server
)
1778 except NTSTATUSError
as error
:
1779 raise CommandError("Failed to find a writeable DC for domain '%s': %s" %
1782 raise CommandError("Failed to find a writeable DC for domain '%s'" % domain
)
1784 nbt
.NBT_SERVER_PDC
: "PDC",
1785 nbt
.NBT_SERVER_GC
: "GC",
1786 nbt
.NBT_SERVER_LDAP
: "LDAP",
1787 nbt
.NBT_SERVER_DS
: "DS",
1788 nbt
.NBT_SERVER_KDC
: "KDC",
1789 nbt
.NBT_SERVER_TIMESERV
: "TIMESERV",
1790 nbt
.NBT_SERVER_CLOSEST
: "CLOSEST",
1791 nbt
.NBT_SERVER_WRITABLE
: "WRITABLE",
1792 nbt
.NBT_SERVER_GOOD_TIMESERV
: "GOOD_TIMESERV",
1793 nbt
.NBT_SERVER_NDNC
: "NDNC",
1794 nbt
.NBT_SERVER_SELECT_SECRET_DOMAIN_6
: "SELECT_SECRET_DOMAIN_6",
1795 nbt
.NBT_SERVER_FULL_SECRET_DOMAIN_6
: "FULL_SECRET_DOMAIN_6",
1796 nbt
.NBT_SERVER_ADS_WEB_SERVICE
: "ADS_WEB_SERVICE",
1797 nbt
.NBT_SERVER_DS_8
: "DS_8",
1798 nbt
.NBT_SERVER_HAS_DNS_NAME
: "HAS_DNS_NAME",
1799 nbt
.NBT_SERVER_IS_DEFAULT_NC
: "IS_DEFAULT_NC",
1800 nbt
.NBT_SERVER_FOREST_ROOT
: "FOREST_ROOT",
1802 server_type_string
= self
.generic_bitmap_to_string(flag_map
,
1803 remote_info
.server_type
, names_only
=True)
1804 self
.outf
.write("RemoteDC Netbios[%s] DNS[%s] ServerType[%s]\n" % (
1805 remote_info
.pdc_name
,
1806 remote_info
.pdc_dns_name
,
1807 server_type_string
))
1809 self
.remote_server
= remote_info
.pdc_dns_name
1810 self
.remote_binding_string
="ncacn_np:%s[%s]" % (self
.remote_server
, remote_binding_options
)
1811 self
.remote_creds
= remote_creds
1812 return self
.remote_server
1814 def new_remote_lsa_connection(self
):
1815 return lsa
.lsarpc(self
.remote_binding_string
, self
.local_lp
, self
.remote_creds
)
1817 def new_remote_netlogon_connection(self
):
1818 return netlogon
.netlogon(self
.remote_binding_string
, self
.local_lp
, self
.remote_creds
)
1820 def get_lsa_info(self
, conn
, policy_access
):
1821 objectAttr
= lsa
.ObjectAttribute()
1822 objectAttr
.sec_qos
= lsa
.QosInfo()
1824 policy
= conn
.OpenPolicy2(''.decode('utf-8'),
1825 objectAttr
, policy_access
)
1827 info
= conn
.QueryInfoPolicy2(policy
, lsa
.LSA_POLICY_INFO_DNS
)
1829 return (policy
, info
)
1831 def get_netlogon_dc_info(self
, conn
, server
):
1832 info
= conn
.netr_DsRGetDCNameEx2(server
,
1833 None, 0, None, None, None,
1834 netlogon
.DS_RETURN_DNS_NAME
)
1837 def netr_DomainTrust_to_name(self
, t
):
1838 if t
.trust_type
== lsa
.LSA_TRUST_TYPE_DOWNLEVEL
:
1839 return t
.netbios_name
1843 def netr_DomainTrust_to_type(self
, a
, t
):
1845 primary_parent
= None
1847 if _t
.trust_flags
& netlogon
.NETR_TRUST_FLAG_PRIMARY
:
1849 if not _t
.trust_flags
& netlogon
.NETR_TRUST_FLAG_TREEROOT
:
1850 primary_parent
= a
[_t
.parent_index
]
1853 if t
.trust_flags
& netlogon
.NETR_TRUST_FLAG_IN_FOREST
:
1854 if t
is primary_parent
:
1857 if t
.trust_flags
& netlogon
.NETR_TRUST_FLAG_TREEROOT
:
1860 parent
= a
[t
.parent_index
]
1861 if parent
is primary
:
1866 if t
.trust_attributes
& lsa
.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
:
1871 def netr_DomainTrust_to_transitive(self
, t
):
1872 if t
.trust_flags
& netlogon
.NETR_TRUST_FLAG_IN_FOREST
:
1875 if t
.trust_attributes
& lsa
.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
:
1878 if t
.trust_attributes
& lsa
.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
:
1883 def netr_DomainTrust_to_direction(self
, t
):
1884 if t
.trust_flags
& netlogon
.NETR_TRUST_FLAG_INBOUND
and \
1885 t
.trust_flags
& netlogon
.NETR_TRUST_FLAG_OUTBOUND
:
1888 if t
.trust_flags
& netlogon
.NETR_TRUST_FLAG_INBOUND
:
1891 if t
.trust_flags
& netlogon
.NETR_TRUST_FLAG_OUTBOUND
:
1896 def generic_enum_to_string(self
, e_dict
, v
, names_only
=False):
1900 v32
= self
._uint
32(v
)
1901 w
= "__unknown__%08X__" % v32
1903 r
= "0x%x (%s)" % (v
, w
)
1906 def generic_bitmap_to_string(self
, b_dict
, v
, names_only
=False):
1911 for b
in sorted(b_dict
.keys()):
1918 c32
= self
._uint
32(c
)
1919 s
+= ["__unknown_%08X__" % c32
]
1924 r
= "0x%x (%s)" % (v
, w
)
1927 def trustType_string(self
, v
):
1929 lsa
.LSA_TRUST_TYPE_DOWNLEVEL
: "DOWNLEVEL",
1930 lsa
.LSA_TRUST_TYPE_UPLEVEL
: "UPLEVEL",
1931 lsa
.LSA_TRUST_TYPE_MIT
: "MIT",
1932 lsa
.LSA_TRUST_TYPE_DCE
: "DCE",
1934 return self
.generic_enum_to_string(types
, v
)
1936 def trustDirection_string(self
, v
):
1938 lsa
.LSA_TRUST_DIRECTION_INBOUND |
1939 lsa
.LSA_TRUST_DIRECTION_OUTBOUND
: "BOTH",
1940 lsa
.LSA_TRUST_DIRECTION_INBOUND
: "INBOUND",
1941 lsa
.LSA_TRUST_DIRECTION_OUTBOUND
: "OUTBOUND",
1943 return self
.generic_enum_to_string(directions
, v
)
1945 def trustAttributes_string(self
, v
):
1947 lsa
.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
: "NON_TRANSITIVE",
1948 lsa
.LSA_TRUST_ATTRIBUTE_UPLEVEL_ONLY
: "UPLEVEL_ONLY",
1949 lsa
.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
: "QUARANTINED_DOMAIN",
1950 lsa
.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
: "FOREST_TRANSITIVE",
1951 lsa
.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
: "CROSS_ORGANIZATION",
1952 lsa
.LSA_TRUST_ATTRIBUTE_WITHIN_FOREST
: "WITHIN_FOREST",
1953 lsa
.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
: "TREAT_AS_EXTERNAL",
1954 lsa
.LSA_TRUST_ATTRIBUTE_USES_RC4_ENCRYPTION
: "USES_RC4_ENCRYPTION",
1956 return self
.generic_bitmap_to_string(attributes
, v
)
1958 def kerb_EncTypes_string(self
, v
):
1960 security
.KERB_ENCTYPE_DES_CBC_CRC
: "DES_CBC_CRC",
1961 security
.KERB_ENCTYPE_DES_CBC_MD5
: "DES_CBC_MD5",
1962 security
.KERB_ENCTYPE_RC4_HMAC_MD5
: "RC4_HMAC_MD5",
1963 security
.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96
: "AES128_CTS_HMAC_SHA1_96",
1964 security
.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96
: "AES256_CTS_HMAC_SHA1_96",
1965 security
.KERB_ENCTYPE_FAST_SUPPORTED
: "FAST_SUPPORTED",
1966 security
.KERB_ENCTYPE_COMPOUND_IDENTITY_SUPPORTED
: "COMPOUND_IDENTITY_SUPPORTED",
1967 security
.KERB_ENCTYPE_CLAIMS_SUPPORTED
: "CLAIMS_SUPPORTED",
1968 security
.KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED
: "RESOURCE_SID_COMPRESSION_DISABLED",
1970 return self
.generic_bitmap_to_string(enctypes
, v
)
1972 def entry_tln_status(self
, e_flags
, ):
1974 return "Status[Enabled]"
1977 lsa
.LSA_TLN_DISABLED_NEW
: "Disabled-New",
1978 lsa
.LSA_TLN_DISABLED_ADMIN
: "Disabled",
1979 lsa
.LSA_TLN_DISABLED_CONFLICT
: "Disabled-Conflicting",
1981 return "Status[%s]" % self
.generic_bitmap_to_string(flags
, e_flags
, names_only
=True)
1983 def entry_dom_status(self
, e_flags
):
1985 return "Status[Enabled]"
1988 lsa
.LSA_SID_DISABLED_ADMIN
: "Disabled-SID",
1989 lsa
.LSA_SID_DISABLED_CONFLICT
: "Disabled-SID-Conflicting",
1990 lsa
.LSA_NB_DISABLED_ADMIN
: "Disabled-NB",
1991 lsa
.LSA_NB_DISABLED_CONFLICT
: "Disabled-NB-Conflicting",
1993 return "Status[%s]" % self
.generic_bitmap_to_string(flags
, e_flags
, names_only
=True)
1995 def write_forest_trust_info(self
, fti
, tln
=None, collisions
=None):
1997 tln_string
= " TDO[%s]" % tln
2001 self
.outf
.write("Namespaces[%d]%s:\n" % (
2002 len(fti
.entries
), tln_string
))
2004 for i
in xrange(0, len(fti
.entries
)):
2008 collision_string
= ""
2010 if collisions
is not None:
2011 for c
in collisions
.entries
:
2015 collision_string
= " Collision[%s]" % (c
.name
.string
)
2017 d
= e
.forest_trust_data
2018 if e
.type == lsa
.LSA_FOREST_TRUST_TOP_LEVEL_NAME
:
2019 self
.outf
.write("TLN: %-32s DNS[*.%s]%s\n" % (
2020 self
.entry_tln_status(flags
),
2021 d
.string
, collision_string
))
2022 elif e
.type == lsa
.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX
:
2023 self
.outf
.write("TLN_EX: %-29s DNS[*.%s]\n" % (
2025 elif e
.type == lsa
.LSA_FOREST_TRUST_DOMAIN_INFO
:
2026 self
.outf
.write("DOM: %-32s DNS[%s] Netbios[%s] SID[%s]%s\n" % (
2027 self
.entry_dom_status(flags
),
2028 d
.dns_domain_name
.string
,
2029 d
.netbios_domain_name
.string
,
2030 d
.domain_sid
, collision_string
))
2033 class cmd_domain_trust_list(DomainTrustCommand
):
2034 """List domain trusts."""
2036 synopsis
= "%prog [options]"
2038 takes_optiongroups
= {
2039 "sambaopts": options
.SambaOptions
,
2040 "versionopts": options
.VersionOptions
,
2041 "localdcopts": LocalDCCredentialsOptions
,
2047 def run(self
, sambaopts
=None, versionopts
=None, localdcopts
=None):
2049 local_server
= self
.setup_local_server(sambaopts
, localdcopts
)
2051 local_netlogon
= self
.new_local_netlogon_connection()
2052 except RuntimeError as error
:
2053 raise self
.LocalRuntimeError(self
, error
, "failed to connect netlogon server")
2056 local_netlogon_trusts
= local_netlogon
.netr_DsrEnumerateDomainTrusts(local_server
,
2057 netlogon
.NETR_TRUST_FLAG_IN_FOREST |
2058 netlogon
.NETR_TRUST_FLAG_OUTBOUND |
2059 netlogon
.NETR_TRUST_FLAG_INBOUND
)
2060 except RuntimeError as error
:
2061 if self
.check_runtime_error(error
, werror
.WERR_RPC_S_PROCNUM_OUT_OF_RANGE
):
2062 # TODO: we could implement a fallback to lsa.EnumTrustDom()
2063 raise CommandError("LOCAL_DC[%s]: netr_DsrEnumerateDomainTrusts not supported." % (
2065 raise self
.LocalRuntimeError(self
, error
, "netr_DsrEnumerateDomainTrusts failed")
2067 a
= local_netlogon_trusts
.array
2069 if t
.trust_flags
& netlogon
.NETR_TRUST_FLAG_PRIMARY
:
2071 self
.outf
.write("%-14s %-15s %-19s %s\n" % (
2072 "Type[%s]" % self
.netr_DomainTrust_to_type(a
, t
),
2073 "Transitive[%s]" % self
.netr_DomainTrust_to_transitive(t
),
2074 "Direction[%s]" % self
.netr_DomainTrust_to_direction(t
),
2075 "Name[%s]" % self
.netr_DomainTrust_to_name(t
)))
2078 class cmd_domain_trust_show(DomainTrustCommand
):
2079 """Show trusted domain details."""
2081 synopsis
= "%prog NAME [options]"
2083 takes_optiongroups
= {
2084 "sambaopts": options
.SambaOptions
,
2085 "versionopts": options
.VersionOptions
,
2086 "localdcopts": LocalDCCredentialsOptions
,
2092 takes_args
= ["domain"]
2094 def run(self
, domain
, sambaopts
=None, versionopts
=None, localdcopts
=None):
2096 local_server
= self
.setup_local_server(sambaopts
, localdcopts
)
2098 local_lsa
= self
.new_local_lsa_connection()
2099 except RuntimeError as error
:
2100 raise self
.LocalRuntimeError(self
, error
, "failed to connect lsa server")
2103 local_policy_access
= lsa
.LSA_POLICY_VIEW_LOCAL_INFORMATION
2104 (local_policy
, local_lsa_info
) = self
.get_lsa_info(local_lsa
, local_policy_access
)
2105 except RuntimeError as error
:
2106 raise self
.LocalRuntimeError(self
, error
, "failed to query LSA_POLICY_INFO_DNS")
2108 self
.outf
.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2109 local_lsa_info
.name
.string
,
2110 local_lsa_info
.dns_domain
.string
,
2111 local_lsa_info
.sid
))
2113 lsaString
= lsa
.String()
2114 lsaString
.string
= domain
2116 local_tdo_full
= local_lsa
.QueryTrustedDomainInfoByName(local_policy
,
2117 lsaString
, lsa
.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO
)
2118 local_tdo_info
= local_tdo_full
.info_ex
2119 local_tdo_posix
= local_tdo_full
.posix_offset
2120 except NTSTATUSError
as error
:
2121 if self
.check_runtime_error(error
, ntstatus
.NT_STATUS_OBJECT_NAME_NOT_FOUND
):
2122 raise CommandError("trusted domain object does not exist for domain [%s]" % domain
)
2124 raise self
.LocalRuntimeError(self
, error
, "QueryTrustedDomainInfoByName(FULL_INFO) failed")
2127 local_tdo_enctypes
= local_lsa
.QueryTrustedDomainInfoByName(local_policy
,
2128 lsaString
, lsa
.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES
)
2129 except NTSTATUSError
as error
:
2130 if self
.check_runtime_error(error
, ntstatus
.NT_STATUS_INVALID_PARAMETER
):
2132 if self
.check_runtime_error(error
, ntstatus
.NT_STATUS_INVALID_INFO_CLASS
):
2135 if error
is not None:
2136 raise self
.LocalRuntimeError(self
, error
,
2137 "QueryTrustedDomainInfoByName(SUPPORTED_ENCRYPTION_TYPES) failed")
2139 local_tdo_enctypes
= lsa
.TrustDomainInfoSupportedEncTypes()
2140 local_tdo_enctypes
.enc_types
= 0
2143 local_tdo_forest
= None
2144 if local_tdo_info
.trust_attributes
& lsa
.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
:
2145 local_tdo_forest
= local_lsa
.lsaRQueryForestTrustInformation(local_policy
,
2146 lsaString
, lsa
.LSA_FOREST_TRUST_DOMAIN_INFO
)
2147 except RuntimeError as error
:
2148 if self
.check_runtime_error(error
, ntstatus
.NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE
):
2150 if self
.check_runtime_error(error
, ntstatus
.NT_STATUS_NOT_FOUND
):
2152 if error
is not None:
2153 raise self
.LocalRuntimeError(self
, error
, "lsaRQueryForestTrustInformation failed")
2155 local_tdo_forest
= lsa
.ForestTrustInformation()
2156 local_tdo_forest
.count
= 0
2157 local_tdo_forest
.entries
= []
2159 self
.outf
.write("TrusteDomain:\n\n");
2160 self
.outf
.write("NetbiosName: %s\n" % local_tdo_info
.netbios_name
.string
)
2161 if local_tdo_info
.netbios_name
.string
!= local_tdo_info
.domain_name
.string
:
2162 self
.outf
.write("DnsName: %s\n" % local_tdo_info
.domain_name
.string
)
2163 self
.outf
.write("SID: %s\n" % local_tdo_info
.sid
)
2164 self
.outf
.write("Type: %s\n" % self
.trustType_string(local_tdo_info
.trust_type
))
2165 self
.outf
.write("Direction: %s\n" % self
.trustDirection_string(local_tdo_info
.trust_direction
))
2166 self
.outf
.write("Attributes: %s\n" % self
.trustAttributes_string(local_tdo_info
.trust_attributes
))
2167 posix_offset_u32
= ctypes
.c_uint32(local_tdo_posix
.posix_offset
).value
2168 posix_offset_i32
= ctypes
.c_int32(local_tdo_posix
.posix_offset
).value
2169 self
.outf
.write("PosixOffset: 0x%08X (%d)\n" % (posix_offset_u32
, posix_offset_i32
))
2170 self
.outf
.write("kerb_EncTypes: %s\n" % self
.kerb_EncTypes_string(local_tdo_enctypes
.enc_types
))
2172 if local_tdo_info
.trust_attributes
& lsa
.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
:
2173 self
.write_forest_trust_info(local_tdo_forest
,
2174 tln
=local_tdo_info
.domain_name
.string
)
2178 class cmd_domain_trust_create(DomainTrustCommand
):
2179 """Create a domain or forest trust."""
2181 synopsis
= "%prog DOMAIN [options]"
2183 takes_optiongroups
= {
2184 "sambaopts": options
.SambaOptions
,
2185 "versionopts": options
.VersionOptions
,
2186 "credopts": options
.CredentialsOptions
,
2187 "localdcopts": LocalDCCredentialsOptions
,
2191 Option("--type", type="choice", metavar
="TYPE",
2192 choices
=["external", "forest"],
2193 help="The type of the trust: 'external' or 'forest'.",
2195 default
="external"),
2196 Option("--direction", type="choice", metavar
="DIRECTION",
2197 choices
=["incoming", "outgoing", "both"],
2198 help="The trust direction: 'incoming', 'outgoing' or 'both'.",
2199 dest
='trust_direction',
2201 Option("--create-location", type="choice", metavar
="LOCATION",
2202 choices
=["local", "both"],
2203 help="Where to create the trusted domain object: 'local' or 'both'.",
2204 dest
='create_location',
2206 Option("--cross-organisation", action
="store_true",
2207 help="The related domains does not belong to the same organisation.",
2208 dest
='cross_organisation',
2210 Option("--quarantined", type="choice", metavar
="yes|no",
2211 choices
=["yes", "no", None],
2212 help="Special SID filtering rules are applied to the trust. "
2213 "With --type=external the default is yes. "
2214 "With --type=forest the default is no.",
2215 dest
='quarantined_arg',
2217 Option("--not-transitive", action
="store_true",
2218 help="The forest trust is not transitive.",
2219 dest
='not_transitive',
2221 Option("--treat-as-external", action
="store_true",
2222 help="The treat the forest trust as external.",
2223 dest
='treat_as_external',
2225 Option("--no-aes-keys", action
="store_false",
2226 help="The trust uses aes kerberos keys.",
2227 dest
='use_aes_keys',
2229 Option("--skip-validation", action
="store_false",
2230 help="Skip validation of the trust.",
2235 takes_args
= ["domain"]
2237 def run(self
, domain
, sambaopts
=None, localdcopts
=None, credopts
=None, versionopts
=None,
2238 trust_type
=None, trust_direction
=None, create_location
=None,
2239 cross_organisation
=False, quarantined_arg
=None,
2240 not_transitive
=False, treat_as_external
=False,
2241 use_aes_keys
=False, validate
=True):
2243 lsaString
= lsa
.String()
2246 if quarantined_arg
is None:
2247 if trust_type
== 'external':
2249 elif quarantined_arg
== 'yes':
2252 if trust_type
!= 'forest':
2254 raise CommandError("--not-transitive requires --type=forest")
2255 if treat_as_external
:
2256 raise CommandError("--treat-as-external requires --type=forest")
2260 enc_types
= lsa
.TrustDomainInfoSupportedEncTypes()
2261 enc_types
.enc_types
= security
.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96
2262 enc_types
.enc_types |
= security
.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96
2264 local_policy_access
= lsa
.LSA_POLICY_VIEW_LOCAL_INFORMATION
2265 local_policy_access |
= lsa
.LSA_POLICY_TRUST_ADMIN
2266 local_policy_access |
= lsa
.LSA_POLICY_CREATE_SECRET
2268 local_trust_info
= lsa
.TrustDomainInfoInfoEx()
2269 local_trust_info
.trust_type
= lsa
.LSA_TRUST_TYPE_UPLEVEL
2270 local_trust_info
.trust_direction
= 0
2271 if trust_direction
== "both":
2272 local_trust_info
.trust_direction |
= lsa
.LSA_TRUST_DIRECTION_INBOUND
2273 local_trust_info
.trust_direction |
= lsa
.LSA_TRUST_DIRECTION_OUTBOUND
2274 elif trust_direction
== "incoming":
2275 local_trust_info
.trust_direction |
= lsa
.LSA_TRUST_DIRECTION_INBOUND
2276 elif trust_direction
== "outgoing":
2277 local_trust_info
.trust_direction |
= lsa
.LSA_TRUST_DIRECTION_OUTBOUND
2278 local_trust_info
.trust_attributes
= 0
2279 if cross_organisation
:
2280 local_trust_info
.trust_attributes |
= lsa
.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2282 local_trust_info
.trust_attributes |
= lsa
.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2283 if trust_type
== "forest":
2284 local_trust_info
.trust_attributes |
= lsa
.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2286 local_trust_info
.trust_attributes |
= lsa
.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2287 if treat_as_external
:
2288 local_trust_info
.trust_attributes |
= lsa
.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2290 def get_password(name
):
2293 if password
is not None and password
is not '':
2295 password
= getpass("New %s Password: " % name
)
2296 passwordverify
= getpass("Retype %s Password: " % name
)
2297 if not password
== passwordverify
:
2299 self
.outf
.write("Sorry, passwords do not match.\n")
2301 incoming_secret
= None
2302 outgoing_secret
= None
2303 remote_policy_access
= lsa
.LSA_POLICY_VIEW_LOCAL_INFORMATION
2304 if create_location
== "local":
2305 if local_trust_info
.trust_direction
& lsa
.LSA_TRUST_DIRECTION_INBOUND
:
2306 incoming_password
= get_password("Incoming Trust")
2307 incoming_secret
= string_to_byte_array(incoming_password
.encode('utf-16-le'))
2308 if local_trust_info
.trust_direction
& lsa
.LSA_TRUST_DIRECTION_OUTBOUND
:
2309 outgoing_password
= get_password("Outgoing Trust")
2310 outgoing_secret
= string_to_byte_array(outgoing_password
.encode('utf-16-le'))
2312 remote_trust_info
= None
2314 # We use 240 random bytes.
2315 # Windows uses 28 or 240 random bytes. I guess it's
2316 # based on the trust type external vs. forest.
2318 # The initial trust password can be up to 512 bytes
2319 # while the versioned passwords used for periodic updates
2320 # can only be up to 498 bytes, as netr_ServerPasswordSet2()
2321 # needs to pass the NL_PASSWORD_VERSION structure within the
2322 # 512 bytes and a 2 bytes confounder is required.
2324 def random_trust_secret(length
):
2325 pw
= samba
.generate_random_machine_password(length
/2, length
/2)
2326 return string_to_byte_array(pw
.encode('utf-16-le'))
2328 if local_trust_info
.trust_direction
& lsa
.LSA_TRUST_DIRECTION_INBOUND
:
2329 incoming_secret
= random_trust_secret(240)
2330 if local_trust_info
.trust_direction
& lsa
.LSA_TRUST_DIRECTION_OUTBOUND
:
2331 outgoing_secret
= random_trust_secret(240)
2333 remote_policy_access |
= lsa
.LSA_POLICY_TRUST_ADMIN
2334 remote_policy_access |
= lsa
.LSA_POLICY_CREATE_SECRET
2336 remote_trust_info
= lsa
.TrustDomainInfoInfoEx()
2337 remote_trust_info
.trust_type
= lsa
.LSA_TRUST_TYPE_UPLEVEL
2338 remote_trust_info
.trust_direction
= 0
2339 if trust_direction
== "both":
2340 remote_trust_info
.trust_direction |
= lsa
.LSA_TRUST_DIRECTION_INBOUND
2341 remote_trust_info
.trust_direction |
= lsa
.LSA_TRUST_DIRECTION_OUTBOUND
2342 elif trust_direction
== "incoming":
2343 remote_trust_info
.trust_direction |
= lsa
.LSA_TRUST_DIRECTION_OUTBOUND
2344 elif trust_direction
== "outgoing":
2345 remote_trust_info
.trust_direction |
= lsa
.LSA_TRUST_DIRECTION_INBOUND
2346 remote_trust_info
.trust_attributes
= 0
2347 if cross_organisation
:
2348 remote_trust_info
.trust_attributes |
= lsa
.LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION
2350 remote_trust_info
.trust_attributes |
= lsa
.LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN
2351 if trust_type
== "forest":
2352 remote_trust_info
.trust_attributes |
= lsa
.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
2354 remote_trust_info
.trust_attributes |
= lsa
.LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
2355 if treat_as_external
:
2356 remote_trust_info
.trust_attributes |
= lsa
.LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL
2358 local_server
= self
.setup_local_server(sambaopts
, localdcopts
)
2360 local_lsa
= self
.new_local_lsa_connection()
2361 except RuntimeError as error
:
2362 raise self
.LocalRuntimeError(self
, error
, "failed to connect lsa server")
2365 (local_policy
, local_lsa_info
) = self
.get_lsa_info(local_lsa
, local_policy_access
)
2366 except RuntimeError as error
:
2367 raise self
.LocalRuntimeError(self
, error
, "failed to query LSA_POLICY_INFO_DNS")
2369 self
.outf
.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2370 local_lsa_info
.name
.string
,
2371 local_lsa_info
.dns_domain
.string
,
2372 local_lsa_info
.sid
))
2375 remote_server
= self
.setup_remote_server(credopts
, domain
)
2376 except RuntimeError as error
:
2377 raise self
.RemoteRuntimeError(self
, error
, "failed to locate remote server")
2380 remote_lsa
= self
.new_remote_lsa_connection()
2381 except RuntimeError as error
:
2382 raise self
.RemoteRuntimeError(self
, error
, "failed to connect lsa server")
2385 (remote_policy
, remote_lsa_info
) = self
.get_lsa_info(remote_lsa
, remote_policy_access
)
2386 except RuntimeError as error
:
2387 raise self
.RemoteRuntimeError(self
, error
, "failed to query LSA_POLICY_INFO_DNS")
2389 self
.outf
.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2390 remote_lsa_info
.name
.string
,
2391 remote_lsa_info
.dns_domain
.string
,
2392 remote_lsa_info
.sid
))
2394 local_trust_info
.domain_name
.string
= remote_lsa_info
.dns_domain
.string
2395 local_trust_info
.netbios_name
.string
= remote_lsa_info
.name
.string
2396 local_trust_info
.sid
= remote_lsa_info
.sid
2398 if remote_trust_info
:
2399 remote_trust_info
.domain_name
.string
= local_lsa_info
.dns_domain
.string
2400 remote_trust_info
.netbios_name
.string
= local_lsa_info
.name
.string
2401 remote_trust_info
.sid
= local_lsa_info
.sid
2404 lsaString
.string
= local_trust_info
.domain_name
.string
2405 local_old_netbios
= local_lsa
.QueryTrustedDomainInfoByName(local_policy
,
2406 lsaString
, lsa
.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO
)
2407 raise CommandError("TrustedDomain %s already exist'" % lsaString
.string
)
2408 except NTSTATUSError
as error
:
2409 if not self
.check_runtime_error(error
, ntstatus
.NT_STATUS_OBJECT_NAME_NOT_FOUND
):
2410 raise self
.LocalRuntimeError(self
, error
,
2411 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2415 lsaString
.string
= local_trust_info
.netbios_name
.string
2416 local_old_dns
= local_lsa
.QueryTrustedDomainInfoByName(local_policy
,
2417 lsaString
, lsa
.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO
)
2418 raise CommandError("TrustedDomain %s already exist'" % lsaString
.string
)
2419 except NTSTATUSError
as error
:
2420 if not self
.check_runtime_error(error
, ntstatus
.NT_STATUS_OBJECT_NAME_NOT_FOUND
):
2421 raise self
.LocalRuntimeError(self
, error
,
2422 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2425 if remote_trust_info
:
2427 lsaString
.string
= remote_trust_info
.domain_name
.string
2428 remote_old_netbios
= remote_lsa
.QueryTrustedDomainInfoByName(remote_policy
,
2429 lsaString
, lsa
.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO
)
2430 raise CommandError("TrustedDomain %s already exist'" % lsaString
.string
)
2431 except NTSTATUSError
as error
:
2432 if not self
.check_runtime_error(error
, ntstatus
.NT_STATUS_OBJECT_NAME_NOT_FOUND
):
2433 raise self
.RemoteRuntimeError(self
, error
,
2434 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2438 lsaString
.string
= remote_trust_info
.netbios_name
.string
2439 remote_old_dns
= remote_lsa
.QueryTrustedDomainInfoByName(remote_policy
,
2440 lsaString
, lsa
.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO
)
2441 raise CommandError("TrustedDomain %s already exist'" % lsaString
.string
)
2442 except NTSTATUSError
as error
:
2443 if not self
.check_runtime_error(error
, ntstatus
.NT_STATUS_OBJECT_NAME_NOT_FOUND
):
2444 raise self
.RemoteRuntimeError(self
, error
,
2445 "QueryTrustedDomainInfoByName(%s, FULL_INFO) failed" % (
2449 local_netlogon
= self
.new_local_netlogon_connection()
2450 except RuntimeError as error
:
2451 raise self
.LocalRuntimeError(self
, error
, "failed to connect netlogon server")
2454 local_netlogon_info
= self
.get_netlogon_dc_info(local_netlogon
, local_server
)
2455 except RuntimeError as error
:
2456 raise self
.LocalRuntimeError(self
, error
, "failed to get netlogon dc info")
2458 if remote_trust_info
:
2460 remote_netlogon
= self
.new_remote_netlogon_connection()
2461 except RuntimeError as error
:
2462 raise self
.RemoteRuntimeError(self
, error
, "failed to connect netlogon server")
2465 remote_netlogon_info
= self
.get_netlogon_dc_info(remote_netlogon
, remote_server
)
2466 except RuntimeError as error
:
2467 raise self
.RemoteRuntimeError(self
, error
, "failed to get netlogon dc info")
2469 def generate_AuthInOutBlob(secret
, update_time
):
2471 blob
= drsblobs
.trustAuthInOutBlob()
2476 clear
= drsblobs
.AuthInfoClear()
2477 clear
.size
= len(secret
)
2478 clear
.password
= secret
2480 info
= drsblobs
.AuthenticationInformation()
2481 info
.LastUpdateTime
= samba
.unix2nttime(update_time
)
2482 info
.AuthType
= lsa
.TRUST_AUTH_TYPE_CLEAR
2483 info
.AuthInfo
= clear
2485 array
= drsblobs
.AuthenticationInformationArray()
2487 array
.array
= [info
]
2489 blob
= drsblobs
.trustAuthInOutBlob()
2491 blob
.current
= array
2495 def generate_AuthInfoInternal(session_key
, incoming
=None, outgoing
=None):
2496 confounder
= [0] * 512
2497 for i
in range(len(confounder
)):
2498 confounder
[i
] = random
.randint(0, 255)
2500 trustpass
= drsblobs
.trustDomainPasswords()
2502 trustpass
.confounder
= confounder
2503 trustpass
.outgoing
= outgoing
2504 trustpass
.incoming
= incoming
2506 trustpass_blob
= ndr_pack(trustpass
)
2508 encrypted_trustpass
= arcfour_encrypt(session_key
, trustpass_blob
)
2510 auth_blob
= lsa
.DATA_BUF2()
2511 auth_blob
.size
= len(encrypted_trustpass
)
2512 auth_blob
.data
= string_to_byte_array(encrypted_trustpass
)
2514 auth_info
= lsa
.TrustDomainInfoAuthInfoInternal()
2515 auth_info
.auth_blob
= auth_blob
2519 update_time
= samba
.current_unix_time()
2520 incoming_blob
= generate_AuthInOutBlob(incoming_secret
, update_time
)
2521 outgoing_blob
= generate_AuthInOutBlob(outgoing_secret
, update_time
)
2523 local_tdo_handle
= None
2524 remote_tdo_handle
= None
2526 local_auth_info
= generate_AuthInfoInternal(local_lsa
.session_key
,
2527 incoming
=incoming_blob
,
2528 outgoing
=outgoing_blob
)
2529 if remote_trust_info
:
2530 remote_auth_info
= generate_AuthInfoInternal(remote_lsa
.session_key
,
2531 incoming
=outgoing_blob
,
2532 outgoing
=incoming_blob
)
2535 if remote_trust_info
:
2536 self
.outf
.write("Creating remote TDO.\n")
2537 current_request
= { "location": "remote", "name": "CreateTrustedDomainEx2"}
2538 remote_tdo_handle
= remote_lsa
.CreateTrustedDomainEx2(remote_policy
,
2541 lsa
.LSA_TRUSTED_DOMAIN_ALL_ACCESS
)
2542 self
.outf
.write("Remote TDO created.\n")
2544 self
.outf
.write("Setting supported encryption types on remote TDO.\n")
2545 current_request
= { "location": "remote", "name": "SetInformationTrustedDomain"}
2546 remote_lsa
.SetInformationTrustedDomain(remote_tdo_handle
,
2547 lsa
.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES
,
2550 self
.outf
.write("Creating local TDO.\n")
2551 current_request
= { "location": "local", "name": "CreateTrustedDomainEx2"}
2552 local_tdo_handle
= local_lsa
.CreateTrustedDomainEx2(local_policy
,
2555 lsa
.LSA_TRUSTED_DOMAIN_ALL_ACCESS
)
2556 self
.outf
.write("Local TDO created\n")
2558 self
.outf
.write("Setting supported encryption types on local TDO.\n")
2559 current_request
= { "location": "local", "name": "SetInformationTrustedDomain"}
2560 local_lsa
.SetInformationTrustedDomain(local_tdo_handle
,
2561 lsa
.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES
,
2563 except RuntimeError as error
:
2564 self
.outf
.write("Error: %s failed %sly - cleaning up\n" % (
2565 current_request
['name'], current_request
['location']))
2566 if remote_tdo_handle
:
2567 self
.outf
.write("Deleting remote TDO.\n")
2568 remote_lsa
.DeleteObject(remote_tdo_handle
)
2569 remote_tdo_handle
= None
2570 if local_tdo_handle
:
2571 self
.outf
.write("Deleting local TDO.\n")
2572 local_lsa
.DeleteObject(local_tdo_handle
)
2573 local_tdo_handle
= None
2574 if current_request
['location'] is "remote":
2575 raise self
.RemoteRuntimeError(self
, error
, "%s" % (
2576 current_request
['name']))
2577 raise self
.LocalRuntimeError(self
, error
, "%s" % (
2578 current_request
['name']))
2581 if local_trust_info
.trust_attributes
& lsa
.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
:
2582 self
.outf
.write("Setup local forest trust information...\n")
2584 # get all information about the remote trust
2585 # this triggers netr_GetForestTrustInformation to the remote domain
2586 # and lsaRSetForestTrustInformation() locally, but new top level
2587 # names are disabled by default.
2588 local_forest_info
= local_netlogon
.netr_DsRGetForestTrustInformation(local_netlogon_info
.dc_unc
,
2589 remote_lsa_info
.dns_domain
.string
,
2590 netlogon
.DS_GFTI_UPDATE_TDO
)
2591 except RuntimeError as error
:
2592 raise self
.LocalRuntimeError(self
, error
, "netr_DsRGetForestTrustInformation() failed")
2595 # here we try to enable all top level names
2596 local_forest_collision
= local_lsa
.lsaRSetForestTrustInformation(local_policy
,
2597 remote_lsa_info
.dns_domain
,
2598 lsa
.LSA_FOREST_TRUST_DOMAIN_INFO
,
2601 except RuntimeError as error
:
2602 raise self
.LocalRuntimeError(self
, error
, "lsaRSetForestTrustInformation() failed")
2604 self
.write_forest_trust_info(local_forest_info
,
2605 tln
=remote_lsa_info
.dns_domain
.string
,
2606 collisions
=local_forest_collision
)
2608 if remote_trust_info
:
2609 self
.outf
.write("Setup remote forest trust information...\n")
2611 # get all information about the local trust (from the perspective of the remote domain)
2612 # this triggers netr_GetForestTrustInformation to our domain.
2613 # and lsaRSetForestTrustInformation() remotely, but new top level
2614 # names are disabled by default.
2615 remote_forest_info
= remote_netlogon
.netr_DsRGetForestTrustInformation(remote_netlogon_info
.dc_unc
,
2616 local_lsa_info
.dns_domain
.string
,
2617 netlogon
.DS_GFTI_UPDATE_TDO
)
2618 except RuntimeError as error
:
2619 raise self
.RemoteRuntimeError(self
, error
, "netr_DsRGetForestTrustInformation() failed")
2622 # here we try to enable all top level names
2623 remote_forest_collision
= remote_lsa
.lsaRSetForestTrustInformation(remote_policy
,
2624 local_lsa_info
.dns_domain
,
2625 lsa
.LSA_FOREST_TRUST_DOMAIN_INFO
,
2628 except RuntimeError as error
:
2629 raise self
.RemoteRuntimeError(self
, error
, "lsaRSetForestTrustInformation() failed")
2631 self
.write_forest_trust_info(remote_forest_info
,
2632 tln
=local_lsa_info
.dns_domain
.string
,
2633 collisions
=remote_forest_collision
)
2635 if local_trust_info
.trust_direction
& lsa
.LSA_TRUST_DIRECTION_OUTBOUND
:
2636 self
.outf
.write("Validating outgoing trust...\n")
2638 local_trust_verify
= local_netlogon
.netr_LogonControl2Ex(local_netlogon_info
.dc_unc
,
2639 netlogon
.NETLOGON_CONTROL_TC_VERIFY
,
2641 remote_lsa_info
.dns_domain
.string
)
2642 except RuntimeError as error
:
2643 raise self
.LocalRuntimeError(self
, error
, "NETLOGON_CONTROL_TC_VERIFY failed")
2645 local_trust_status
= self
._uint
32(local_trust_verify
.pdc_connection_status
[0])
2646 local_conn_status
= self
._uint
32(local_trust_verify
.tc_connection_status
[0])
2648 if local_trust_verify
.flags
& netlogon
.NETLOGON_VERIFY_STATUS_RETURNED
:
2649 local_validation
= "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2650 local_trust_verify
.trusted_dc_name
,
2651 local_trust_verify
.tc_connection_status
[1],
2652 local_trust_verify
.pdc_connection_status
[1])
2654 local_validation
= "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2655 local_trust_verify
.trusted_dc_name
,
2656 local_trust_verify
.tc_connection_status
[1],
2657 local_trust_verify
.pdc_connection_status
[1])
2659 if local_trust_status
!= werror
.WERR_SUCCESS
or local_conn_status
!= werror
.WERR_SUCCESS
:
2660 raise CommandError(local_validation
)
2662 self
.outf
.write("OK: %s\n" % local_validation
)
2664 if remote_trust_info
:
2665 if remote_trust_info
.trust_direction
& lsa
.LSA_TRUST_DIRECTION_OUTBOUND
:
2666 self
.outf
.write("Validating incoming trust...\n")
2668 remote_trust_verify
= remote_netlogon
.netr_LogonControl2Ex(remote_netlogon_info
.dc_unc
,
2669 netlogon
.NETLOGON_CONTROL_TC_VERIFY
,
2671 local_lsa_info
.dns_domain
.string
)
2672 except RuntimeError as error
:
2673 raise self
.RemoteRuntimeError(self
, error
, "NETLOGON_CONTROL_TC_VERIFY failed")
2675 remote_trust_status
= self
._uint
32(remote_trust_verify
.pdc_connection_status
[0])
2676 remote_conn_status
= self
._uint
32(remote_trust_verify
.tc_connection_status
[0])
2678 if remote_trust_verify
.flags
& netlogon
.NETLOGON_VERIFY_STATUS_RETURNED
:
2679 remote_validation
= "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2680 remote_trust_verify
.trusted_dc_name
,
2681 remote_trust_verify
.tc_connection_status
[1],
2682 remote_trust_verify
.pdc_connection_status
[1])
2684 remote_validation
= "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2685 remote_trust_verify
.trusted_dc_name
,
2686 remote_trust_verify
.tc_connection_status
[1],
2687 remote_trust_verify
.pdc_connection_status
[1])
2689 if remote_trust_status
!= werror
.WERR_SUCCESS
or remote_conn_status
!= werror
.WERR_SUCCESS
:
2690 raise CommandError(remote_validation
)
2692 self
.outf
.write("OK: %s\n" % remote_validation
)
2694 if remote_tdo_handle
is not None:
2696 remote_lsa
.Close(remote_tdo_handle
)
2697 except RuntimeError as error
:
2699 remote_tdo_handle
= None
2700 if local_tdo_handle
is not None:
2702 local_lsa
.Close(local_tdo_handle
)
2703 except RuntimeError as error
:
2705 local_tdo_handle
= None
2707 self
.outf
.write("Success.\n")
2710 class cmd_domain_trust_delete(DomainTrustCommand
):
2711 """Delete a domain trust."""
2713 synopsis
= "%prog DOMAIN [options]"
2715 takes_optiongroups
= {
2716 "sambaopts": options
.SambaOptions
,
2717 "versionopts": options
.VersionOptions
,
2718 "credopts": options
.CredentialsOptions
,
2719 "localdcopts": LocalDCCredentialsOptions
,
2723 Option("--delete-location", type="choice", metavar
="LOCATION",
2724 choices
=["local", "both"],
2725 help="Where to delete the trusted domain object: 'local' or 'both'.",
2726 dest
='delete_location',
2730 takes_args
= ["domain"]
2732 def run(self
, domain
, sambaopts
=None, localdcopts
=None, credopts
=None, versionopts
=None,
2733 delete_location
=None):
2735 local_policy_access
= lsa
.LSA_POLICY_VIEW_LOCAL_INFORMATION
2736 local_policy_access |
= lsa
.LSA_POLICY_TRUST_ADMIN
2737 local_policy_access |
= lsa
.LSA_POLICY_CREATE_SECRET
2739 if delete_location
== "local":
2740 remote_policy_access
= None
2742 remote_policy_access
= lsa
.LSA_POLICY_VIEW_LOCAL_INFORMATION
2743 remote_policy_access |
= lsa
.LSA_POLICY_TRUST_ADMIN
2744 remote_policy_access |
= lsa
.LSA_POLICY_CREATE_SECRET
2746 local_server
= self
.setup_local_server(sambaopts
, localdcopts
)
2748 local_lsa
= self
.new_local_lsa_connection()
2749 except RuntimeError as error
:
2750 raise self
.LocalRuntimeError(self
, error
, "failed to connect lsa server")
2753 (local_policy
, local_lsa_info
) = self
.get_lsa_info(local_lsa
, local_policy_access
)
2754 except RuntimeError as error
:
2755 raise self
.LocalRuntimeError(self
, error
, "failed to query LSA_POLICY_INFO_DNS")
2757 self
.outf
.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2758 local_lsa_info
.name
.string
,
2759 local_lsa_info
.dns_domain
.string
,
2760 local_lsa_info
.sid
))
2762 local_tdo_info
= None
2763 local_tdo_handle
= None
2764 remote_tdo_info
= None
2765 remote_tdo_handle
= None
2767 lsaString
= lsa
.String()
2769 lsaString
.string
= domain
2770 local_tdo_info
= local_lsa
.QueryTrustedDomainInfoByName(local_policy
,
2771 lsaString
, lsa
.LSA_TRUSTED_DOMAIN_INFO_INFO_EX
)
2772 except NTSTATUSError
as error
:
2773 if self
.check_runtime_error(error
, ntstatus
.NT_STATUS_OBJECT_NAME_NOT_FOUND
):
2774 raise CommandError("Failed to find trust for domain '%s'" % domain
)
2775 raise self
.RemoteRuntimeError(self
, error
, "failed to locate remote server")
2778 if remote_policy_access
is not None:
2780 remote_server
= self
.setup_remote_server(credopts
, domain
)
2781 except RuntimeError as error
:
2782 raise self
.RemoteRuntimeError(self
, error
, "failed to locate remote server")
2785 remote_lsa
= self
.new_remote_lsa_connection()
2786 except RuntimeError as error
:
2787 raise self
.RemoteRuntimeError(self
, error
, "failed to connect lsa server")
2790 (remote_policy
, remote_lsa_info
) = self
.get_lsa_info(remote_lsa
, remote_policy_access
)
2791 except RuntimeError as error
:
2792 raise self
.RemoteRuntimeError(self
, error
, "failed to query LSA_POLICY_INFO_DNS")
2794 self
.outf
.write("RemoteDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2795 remote_lsa_info
.name
.string
,
2796 remote_lsa_info
.dns_domain
.string
,
2797 remote_lsa_info
.sid
))
2799 if remote_lsa_info
.sid
!= local_tdo_info
.sid
or \
2800 remote_lsa_info
.name
.string
!= local_tdo_info
.netbios_name
.string
or \
2801 remote_lsa_info
.dns_domain
.string
!= local_tdo_info
.domain_name
.string
:
2802 raise CommandError("LocalTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2803 local_tdo_info
.netbios_name
.string
,
2804 local_tdo_info
.domain_name
.string
,
2805 local_tdo_info
.sid
))
2808 lsaString
.string
= local_lsa_info
.dns_domain
.string
2809 remote_tdo_info
= remote_lsa
.QueryTrustedDomainInfoByName(remote_policy
,
2810 lsaString
, lsa
.LSA_TRUSTED_DOMAIN_INFO_INFO_EX
)
2811 except NTSTATUSError
as error
:
2812 if not self
.check_runtime_error(error
, ntstatus
.NT_STATUS_OBJECT_NAME_NOT_FOUND
):
2813 raise self
.RemoteRuntimeError(self
, error
, "QueryTrustedDomainInfoByName(%s)" % (
2817 if remote_tdo_info
is not None:
2818 if local_lsa_info
.sid
!= remote_tdo_info
.sid
or \
2819 local_lsa_info
.name
.string
!= remote_tdo_info
.netbios_name
.string
or \
2820 local_lsa_info
.dns_domain
.string
!= remote_tdo_info
.domain_name
.string
:
2821 raise CommandError("RemoteTDO inconsistend: Netbios[%s] DNS[%s] SID[%s]" % (
2822 remote_tdo_info
.netbios_name
.string
,
2823 remote_tdo_info
.domain_name
.string
,
2824 remote_tdo_info
.sid
))
2826 if local_tdo_info
is not None:
2828 lsaString
.string
= local_tdo_info
.domain_name
.string
2829 local_tdo_handle
= local_lsa
.OpenTrustedDomainByName(local_policy
,
2831 security
.SEC_STD_DELETE
)
2832 except RuntimeError as error
:
2833 raise self
.LocalRuntimeError(self
, error
, "OpenTrustedDomainByName(%s)" % (
2836 local_lsa
.DeleteObject(local_tdo_handle
)
2837 local_tdo_handle
= None
2839 if remote_tdo_info
is not None:
2841 lsaString
.string
= remote_tdo_info
.domain_name
.string
2842 remote_tdo_handle
= remote_lsa
.OpenTrustedDomainByName(remote_policy
,
2844 security
.SEC_STD_DELETE
)
2845 except RuntimeError as error
:
2846 raise self
.RemoteRuntimeError(self
, error
, "OpenTrustedDomainByName(%s)" % (
2849 if remote_tdo_handle
is not None:
2851 remote_lsa
.DeleteObject(remote_tdo_handle
)
2852 remote_tdo_handle
= None
2853 self
.outf
.write("RemoteTDO deleted.\n")
2854 except RuntimeError as error
:
2855 self
.outf
.write("%s\n" % self
.RemoteRuntimeError(self
, error
, "DeleteObject() failed"))
2857 if local_tdo_handle
is not None:
2859 local_lsa
.DeleteObject(local_tdo_handle
)
2860 local_tdo_handle
= None
2861 self
.outf
.write("LocalTDO deleted.\n")
2862 except RuntimeError as error
:
2863 self
.outf
.write("%s\n" % self
.LocalRuntimeError(self
, error
, "DeleteObject() failed"))
2867 class cmd_domain_trust_validate(DomainTrustCommand
):
2868 """Validate a domain trust."""
2870 synopsis
= "%prog DOMAIN [options]"
2872 takes_optiongroups
= {
2873 "sambaopts": options
.SambaOptions
,
2874 "versionopts": options
.VersionOptions
,
2875 "credopts": options
.CredentialsOptions
,
2876 "localdcopts": LocalDCCredentialsOptions
,
2880 Option("--validate-location", type="choice", metavar
="LOCATION",
2881 choices
=["local", "both"],
2882 help="Where to validate the trusted domain object: 'local' or 'both'.",
2883 dest
='validate_location',
2887 takes_args
= ["domain"]
2889 def run(self
, domain
, sambaopts
=None, versionopts
=None, credopts
=None, localdcopts
=None,
2890 validate_location
=None):
2892 local_policy_access
= lsa
.LSA_POLICY_VIEW_LOCAL_INFORMATION
2894 local_server
= self
.setup_local_server(sambaopts
, localdcopts
)
2896 local_lsa
= self
.new_local_lsa_connection()
2897 except RuntimeError as error
:
2898 raise self
.LocalRuntimeError(self
, error
, "failed to connect lsa server")
2901 (local_policy
, local_lsa_info
) = self
.get_lsa_info(local_lsa
, local_policy_access
)
2902 except RuntimeError as error
:
2903 raise self
.LocalRuntimeError(self
, error
, "failed to query LSA_POLICY_INFO_DNS")
2905 self
.outf
.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
2906 local_lsa_info
.name
.string
,
2907 local_lsa_info
.dns_domain
.string
,
2908 local_lsa_info
.sid
))
2911 lsaString
= lsa
.String()
2912 lsaString
.string
= domain
2913 local_tdo_info
= local_lsa
.QueryTrustedDomainInfoByName(local_policy
,
2914 lsaString
, lsa
.LSA_TRUSTED_DOMAIN_INFO_INFO_EX
)
2915 except NTSTATUSError
as error
:
2916 if self
.check_runtime_error(error
, ntstatus
.NT_STATUS_OBJECT_NAME_NOT_FOUND
):
2917 raise CommandError("trusted domain object does not exist for domain [%s]" % domain
)
2919 raise self
.LocalRuntimeError(self
, error
, "QueryTrustedDomainInfoByName(INFO_EX) failed")
2921 self
.outf
.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
2922 local_tdo_info
.netbios_name
.string
,
2923 local_tdo_info
.domain_name
.string
,
2924 local_tdo_info
.sid
))
2927 local_netlogon
= self
.new_local_netlogon_connection()
2928 except RuntimeError as error
:
2929 raise self
.LocalRuntimeError(self
, error
, "failed to connect netlogon server")
2932 local_trust_verify
= local_netlogon
.netr_LogonControl2Ex(local_server
,
2933 netlogon
.NETLOGON_CONTROL_TC_VERIFY
,
2935 local_tdo_info
.domain_name
.string
)
2936 except RuntimeError as error
:
2937 raise self
.LocalRuntimeError(self
, error
, "NETLOGON_CONTROL_TC_VERIFY failed")
2939 local_trust_status
= self
._uint
32(local_trust_verify
.pdc_connection_status
[0])
2940 local_conn_status
= self
._uint
32(local_trust_verify
.tc_connection_status
[0])
2942 if local_trust_verify
.flags
& netlogon
.NETLOGON_VERIFY_STATUS_RETURNED
:
2943 local_validation
= "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
2944 local_trust_verify
.trusted_dc_name
,
2945 local_trust_verify
.tc_connection_status
[1],
2946 local_trust_verify
.pdc_connection_status
[1])
2948 local_validation
= "LocalValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
2949 local_trust_verify
.trusted_dc_name
,
2950 local_trust_verify
.tc_connection_status
[1],
2951 local_trust_verify
.pdc_connection_status
[1])
2953 if local_trust_status
!= werror
.WERR_SUCCESS
or local_conn_status
!= werror
.WERR_SUCCESS
:
2954 raise CommandError(local_validation
)
2956 self
.outf
.write("OK: %s\n" % local_validation
)
2959 server
= local_trust_verify
.trusted_dc_name
.replace('\\', '')
2960 domain_and_server
= "%s\\%s" % (local_tdo_info
.domain_name
.string
, server
)
2961 local_trust_rediscover
= local_netlogon
.netr_LogonControl2Ex(local_server
,
2962 netlogon
.NETLOGON_CONTROL_REDISCOVER
,
2965 except RuntimeError as error
:
2966 raise self
.LocalRuntimeError(self
, error
, "NETLOGON_CONTROL_REDISCOVER failed")
2968 local_conn_status
= self
._uint
32(local_trust_rediscover
.tc_connection_status
[0])
2969 local_rediscover
= "LocalRediscover: DC[%s] CONNECTION[%s]" % (
2970 local_trust_rediscover
.trusted_dc_name
,
2971 local_trust_rediscover
.tc_connection_status
[1])
2973 if local_conn_status
!= werror
.WERR_SUCCESS
:
2974 raise CommandError(local_rediscover
)
2976 self
.outf
.write("OK: %s\n" % local_rediscover
)
2978 if validate_location
!= "local":
2980 remote_server
= self
.setup_remote_server(credopts
, domain
, require_pdc
=False)
2981 except RuntimeError as error
:
2982 raise self
.RemoteRuntimeError(self
, error
, "failed to locate remote server")
2985 remote_netlogon
= self
.new_remote_netlogon_connection()
2986 except RuntimeError as error
:
2987 raise self
.RemoteRuntimeError(self
, error
, "failed to connect netlogon server")
2990 remote_trust_verify
= remote_netlogon
.netr_LogonControl2Ex(remote_server
,
2991 netlogon
.NETLOGON_CONTROL_TC_VERIFY
,
2993 local_lsa_info
.dns_domain
.string
)
2994 except RuntimeError as error
:
2995 raise self
.RemoteRuntimeError(self
, error
, "NETLOGON_CONTROL_TC_VERIFY failed")
2997 remote_trust_status
= self
._uint
32(remote_trust_verify
.pdc_connection_status
[0])
2998 remote_conn_status
= self
._uint
32(remote_trust_verify
.tc_connection_status
[0])
3000 if remote_trust_verify
.flags
& netlogon
.NETLOGON_VERIFY_STATUS_RETURNED
:
3001 remote_validation
= "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s] VERIFY_STATUS_RETURNED" % (
3002 remote_trust_verify
.trusted_dc_name
,
3003 remote_trust_verify
.tc_connection_status
[1],
3004 remote_trust_verify
.pdc_connection_status
[1])
3006 remote_validation
= "RemoteValidation: DC[%s] CONNECTION[%s] TRUST[%s]" % (
3007 remote_trust_verify
.trusted_dc_name
,
3008 remote_trust_verify
.tc_connection_status
[1],
3009 remote_trust_verify
.pdc_connection_status
[1])
3011 if remote_trust_status
!= werror
.WERR_SUCCESS
or remote_conn_status
!= werror
.WERR_SUCCESS
:
3012 raise CommandError(remote_validation
)
3014 self
.outf
.write("OK: %s\n" % remote_validation
)
3017 server
= remote_trust_verify
.trusted_dc_name
.replace('\\', '')
3018 domain_and_server
= "%s\\%s" % (local_lsa_info
.dns_domain
.string
, server
)
3019 remote_trust_rediscover
= remote_netlogon
.netr_LogonControl2Ex(remote_server
,
3020 netlogon
.NETLOGON_CONTROL_REDISCOVER
,
3023 except RuntimeError as error
:
3024 raise self
.RemoteRuntimeError(self
, error
, "NETLOGON_CONTROL_REDISCOVER failed")
3026 remote_conn_status
= self
._uint
32(remote_trust_rediscover
.tc_connection_status
[0])
3028 remote_rediscover
= "RemoteRediscover: DC[%s] CONNECTION[%s]" % (
3029 remote_trust_rediscover
.trusted_dc_name
,
3030 remote_trust_rediscover
.tc_connection_status
[1])
3032 if remote_conn_status
!= werror
.WERR_SUCCESS
:
3033 raise CommandError(remote_rediscover
)
3035 self
.outf
.write("OK: %s\n" % remote_rediscover
)
3039 class cmd_domain_trust_namespaces(DomainTrustCommand
):
3040 """Manage forest trust namespaces."""
3042 synopsis
= "%prog [DOMAIN] [options]"
3044 takes_optiongroups
= {
3045 "sambaopts": options
.SambaOptions
,
3046 "versionopts": options
.VersionOptions
,
3047 "localdcopts": LocalDCCredentialsOptions
,
3051 Option("--refresh", type="choice", metavar
="check|store",
3052 choices
=["check", "store", None],
3053 help="List and maybe store refreshed forest trust information: 'check' or 'store'.",
3056 Option("--enable-all", action
="store_true",
3057 help="Try to update disabled entries, not allowed with --refresh=check.",
3060 Option("--enable-tln", action
="append", metavar
='DNSDOMAIN',
3061 help="Enable a top level name entry. Can be specified multiple times.",
3064 Option("--disable-tln", action
="append", metavar
='DNSDOMAIN',
3065 help="Disable a top level name entry. Can be specified multiple times.",
3068 Option("--add-tln-ex", action
="append", metavar
='DNSDOMAIN',
3069 help="Add a top level exclusion entry. Can be specified multiple times.",
3072 Option("--delete-tln-ex", action
="append", metavar
='DNSDOMAIN',
3073 help="Delete a top level exclusion entry. Can be specified multiple times.",
3074 dest
='delete_tln_ex',
3076 Option("--enable-nb", action
="append", metavar
='NETBIOSDOMAIN',
3077 help="Enable a netbios name in a domain entry. Can be specified multiple times.",
3080 Option("--disable-nb", action
="append", metavar
='NETBIOSDOMAIN',
3081 help="Disable a netbios name in a domain entry. Can be specified multiple times.",
3084 Option("--enable-sid", action
="append", metavar
='DOMAINSID',
3085 help="Enable a SID in a domain entry. Can be specified multiple times.",
3086 dest
='enable_sid_str',
3088 Option("--disable-sid", action
="append", metavar
='DOMAINSID',
3089 help="Disable a SID in a domain entry. Can be specified multiple times.",
3090 dest
='disable_sid_str',
3092 Option("--add-upn-suffix", action
="append", metavar
='DNSDOMAIN',
3093 help="Add a new uPNSuffixes attribute for the local forest. Can be specified multiple times.",
3096 Option("--delete-upn-suffix", action
="append", metavar
='DNSDOMAIN',
3097 help="Delete an existing uPNSuffixes attribute of the local forest. Can be specified multiple times.",
3100 Option("--add-spn-suffix", action
="append", metavar
='DNSDOMAIN',
3101 help="Add a new msDS-SPNSuffixes attribute for the local forest. Can be specified multiple times.",
3104 Option("--delete-spn-suffix", action
="append", metavar
='DNSDOMAIN',
3105 help="Delete an existing msDS-SPNSuffixes attribute of the local forest. Can be specified multiple times.",
3110 takes_args
= ["domain?"]
3112 def run(self
, domain
=None, sambaopts
=None, localdcopts
=None, versionopts
=None,
3113 refresh
=None, enable_all
=False,
3114 enable_tln
=[], disable_tln
=[], add_tln_ex
=[], delete_tln_ex
=[],
3115 enable_sid_str
=[], disable_sid_str
=[], enable_nb
=[], disable_nb
=[],
3116 add_upn
=[], delete_upn
=[], add_spn
=[], delete_spn
=[]):
3118 require_update
= False
3121 if refresh
== "store":
3122 raise CommandError("--refresh=%s not allowed without DOMAIN" % refresh
)
3125 raise CommandError("--enable-all not allowed without DOMAIN")
3127 if len(enable_tln
) > 0:
3128 raise CommandError("--enable-tln not allowed without DOMAIN")
3129 if len(disable_tln
) > 0:
3130 raise CommandError("--disable-tln not allowed without DOMAIN")
3132 if len(add_tln_ex
) > 0:
3133 raise CommandError("--add-tln-ex not allowed without DOMAIN")
3134 if len(delete_tln_ex
) > 0:
3135 raise CommandError("--delete-tln-ex not allowed without DOMAIN")
3137 if len(enable_nb
) > 0:
3138 raise CommandError("--enable-nb not allowed without DOMAIN")
3139 if len(disable_nb
) > 0:
3140 raise CommandError("--disable-nb not allowed without DOMAIN")
3142 if len(enable_sid_str
) > 0:
3143 raise CommandError("--enable-sid not allowed without DOMAIN")
3144 if len(disable_sid_str
) > 0:
3145 raise CommandError("--disable-sid not allowed without DOMAIN")
3147 if len(add_upn
) > 0:
3149 if not n
.startswith("*."):
3151 raise CommandError("value[%s] specified for --add-upn-suffix should not include with '*.'" % n
)
3152 require_update
= True
3153 if len(delete_upn
) > 0:
3154 for n
in delete_upn
:
3155 if not n
.startswith("*."):
3157 raise CommandError("value[%s] specified for --delete-upn-suffix should not include with '*.'" % n
)
3158 require_update
= True
3160 for d
in delete_upn
:
3161 if a
.lower() != d
.lower():
3163 raise CommandError("value[%s] specified for --add-upn-suffix and --delete-upn-suffix" % a
)
3165 if len(add_spn
) > 0:
3167 if not n
.startswith("*."):
3169 raise CommandError("value[%s] specified for --add-spn-suffix should not include with '*.'" % n
)
3170 require_update
= True
3171 if len(delete_spn
) > 0:
3172 for n
in delete_spn
:
3173 if not n
.startswith("*."):
3175 raise CommandError("value[%s] specified for --delete-spn-suffix should not include with '*.'" % n
)
3176 require_update
= True
3178 for d
in delete_spn
:
3179 if a
.lower() != d
.lower():
3181 raise CommandError("value[%s] specified for --add-spn-suffix and --delete-spn-suffix" % a
)
3183 if len(add_upn
) > 0:
3184 raise CommandError("--add-upn-suffix not allowed together with DOMAIN")
3185 if len(delete_upn
) > 0:
3186 raise CommandError("--delete-upn-suffix not allowed together with DOMAIN")
3187 if len(add_spn
) > 0:
3188 raise CommandError("--add-spn-suffix not allowed together with DOMAIN")
3189 if len(delete_spn
) > 0:
3190 raise CommandError("--delete-spn-suffix not allowed together with DOMAIN")
3192 if refresh
is not None:
3193 if refresh
== "store":
3194 require_update
= True
3196 if enable_all
and refresh
!= "store":
3197 raise CommandError("--enable-all not allowed together with --refresh=%s" % refresh
)
3199 if len(enable_tln
) > 0:
3200 raise CommandError("--enable-tln not allowed together with --refresh")
3201 if len(disable_tln
) > 0:
3202 raise CommandError("--disable-tln not allowed together with --refresh")
3204 if len(add_tln_ex
) > 0:
3205 raise CommandError("--add-tln-ex not allowed together with --refresh")
3206 if len(delete_tln_ex
) > 0:
3207 raise CommandError("--delete-tln-ex not allowed together with --refresh")
3209 if len(enable_nb
) > 0:
3210 raise CommandError("--enable-nb not allowed together with --refresh")
3211 if len(disable_nb
) > 0:
3212 raise CommandError("--disable-nb not allowed together with --refresh")
3214 if len(enable_sid_str
) > 0:
3215 raise CommandError("--enable-sid not allowed together with --refresh")
3216 if len(disable_sid_str
) > 0:
3217 raise CommandError("--disable-sid not allowed together with --refresh")
3220 require_update
= True
3222 if len(enable_tln
) > 0:
3223 raise CommandError("--enable-tln not allowed together with --enable-all")
3225 if len(enable_nb
) > 0:
3226 raise CommandError("--enable-nb not allowed together with --enable-all")
3228 if len(enable_sid_str
) > 0:
3229 raise CommandError("--enable-sid not allowed together with --enable-all")
3231 if len(enable_tln
) > 0:
3232 require_update
= True
3233 if len(disable_tln
) > 0:
3234 require_update
= True
3235 for e
in enable_tln
:
3236 for d
in disable_tln
:
3237 if e
.lower() != d
.lower():
3239 raise CommandError("value[%s] specified for --enable-tln and --disable-tln" % e
)
3241 if len(add_tln_ex
) > 0:
3242 for n
in add_tln_ex
:
3243 if not n
.startswith("*."):
3245 raise CommandError("value[%s] specified for --add-tln-ex should not include with '*.'" % n
)
3246 require_update
= True
3247 if len(delete_tln_ex
) > 0:
3248 for n
in delete_tln_ex
:
3249 if not n
.startswith("*."):
3251 raise CommandError("value[%s] specified for --delete-tln-ex should not include with '*.'" % n
)
3252 require_update
= True
3253 for a
in add_tln_ex
:
3254 for d
in delete_tln_ex
:
3255 if a
.lower() != d
.lower():
3257 raise CommandError("value[%s] specified for --add-tln-ex and --delete-tln-ex" % a
)
3259 if len(enable_nb
) > 0:
3260 require_update
= True
3261 if len(disable_nb
) > 0:
3262 require_update
= True
3264 for d
in disable_nb
:
3265 if e
.upper() != d
.upper():
3267 raise CommandError("value[%s] specified for --enable-nb and --disable-nb" % e
)
3270 for s
in enable_sid_str
:
3272 sid
= security
.dom_sid(s
)
3273 except TypeError as error
:
3274 raise CommandError("value[%s] specified for --enable-sid is not a valid SID" % s
)
3275 enable_sid
.append(sid
)
3277 for s
in disable_sid_str
:
3279 sid
= security
.dom_sid(s
)
3280 except TypeError as error
:
3281 raise CommandError("value[%s] specified for --disable-sid is not a valid SID" % s
)
3282 disable_sid
.append(sid
)
3283 if len(enable_sid
) > 0:
3284 require_update
= True
3285 if len(disable_sid
) > 0:
3286 require_update
= True
3287 for e
in enable_sid
:
3288 for d
in disable_sid
:
3291 raise CommandError("value[%s] specified for --enable-sid and --disable-sid" % e
)
3293 local_policy_access
= lsa
.LSA_POLICY_VIEW_LOCAL_INFORMATION
3295 local_policy_access |
= lsa
.LSA_POLICY_TRUST_ADMIN
3297 local_server
= self
.setup_local_server(sambaopts
, localdcopts
)
3299 local_lsa
= self
.new_local_lsa_connection()
3300 except RuntimeError as error
:
3301 raise self
.LocalRuntimeError(self
, error
, "failed to connect lsa server")
3304 (local_policy
, local_lsa_info
) = self
.get_lsa_info(local_lsa
, local_policy_access
)
3305 except RuntimeError as error
:
3306 raise self
.LocalRuntimeError(self
, error
, "failed to query LSA_POLICY_INFO_DNS")
3308 self
.outf
.write("LocalDomain Netbios[%s] DNS[%s] SID[%s]\n" % (
3309 local_lsa_info
.name
.string
,
3310 local_lsa_info
.dns_domain
.string
,
3311 local_lsa_info
.sid
))
3315 local_netlogon
= self
.new_local_netlogon_connection()
3316 except RuntimeError as error
:
3317 raise self
.LocalRuntimeError(self
, error
, "failed to connect netlogon server")
3320 local_netlogon_info
= self
.get_netlogon_dc_info(local_netlogon
, local_server
)
3321 except RuntimeError as error
:
3322 raise self
.LocalRuntimeError(self
, error
, "failed to get netlogon dc info")
3324 if local_netlogon_info
.domain_name
!= local_netlogon_info
.forest_name
:
3325 raise CommandError("The local domain [%s] is not the forest root [%s]" % (
3326 local_netlogon_info
.domain_name
,
3327 local_netlogon_info
.forest_name
))
3330 # get all information about our own forest
3331 own_forest_info
= local_netlogon
.netr_DsRGetForestTrustInformation(local_netlogon_info
.dc_unc
,
3333 except RuntimeError as error
:
3334 if self
.check_runtime_error(error
, werror
.WERR_RPC_S_PROCNUM_OUT_OF_RANGE
):
3335 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3338 if self
.check_runtime_error(error
, werror
.WERR_INVALID_FUNCTION
):
3339 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3342 if self
.check_runtime_error(error
, werror
.WERR_NERR_ACFNOTLOADED
):
3343 raise CommandError("LOCAL_DC[%s]: netr_DsRGetForestTrustInformation() not supported." % (
3346 raise self
.LocalRuntimeError(self
, error
, "netr_DsRGetForestTrustInformation() failed")
3348 self
.outf
.write("Own forest trust information...\n")
3349 self
.write_forest_trust_info(own_forest_info
,
3350 tln
=local_lsa_info
.dns_domain
.string
)
3353 local_samdb
= self
.new_local_ldap_connection()
3354 except RuntimeError as error
:
3355 raise self
.LocalRuntimeError(self
, error
, "failed to connect to SamDB")
3357 local_partitions_dn
= "CN=Partitions,%s" % str(local_samdb
.get_config_basedn())
3358 attrs
= ['uPNSuffixes', 'msDS-SPNSuffixes']
3360 msgs
= local_samdb
.search(base
=local_partitions_dn
,
3361 scope
=ldb
.SCOPE_BASE
,
3362 expression
="(objectClass=crossRefContainer)",
3364 stored_msg
= msgs
[0]
3365 except ldb
.LdbError
as error
:
3366 raise self
.LocalLdbError(self
, error
, "failed to search partition dn")
3368 stored_upn_vals
= []
3369 if 'uPNSuffixes' in stored_msg
:
3370 stored_upn_vals
.extend(stored_msg
['uPNSuffixes'])
3372 stored_spn_vals
= []
3373 if 'msDS-SPNSuffixes' in stored_msg
:
3374 stored_spn_vals
.extend(stored_msg
['msDS-SPNSuffixes'])
3376 self
.outf
.write("Stored uPNSuffixes attributes[%d]:\n" % len(stored_upn_vals
))
3377 for v
in stored_upn_vals
:
3378 self
.outf
.write("TLN: %-32s DNS[*.%s]\n" % ("", v
))
3379 self
.outf
.write("Stored msDS-SPNSuffixes attributes[%d]:\n" % len(stored_spn_vals
))
3380 for v
in stored_spn_vals
:
3381 self
.outf
.write("TLN: %-32s DNS[*.%s]\n" % ("", v
))
3383 if not require_update
:
3387 update_upn_vals
= []
3388 update_upn_vals
.extend(stored_upn_vals
)
3391 update_spn_vals
= []
3392 update_spn_vals
.extend(stored_spn_vals
)
3396 for i
in xrange(0, len(update_upn_vals
)):
3397 v
= update_upn_vals
[i
]
3398 if v
.lower() != upn
.lower():
3403 raise CommandError("Entry already present for value[%s] specified for --add-upn-suffix" % upn
)
3404 update_upn_vals
.append(upn
)
3407 for upn
in delete_upn
:
3409 for i
in xrange(0, len(update_upn_vals
)):
3410 v
= update_upn_vals
[i
]
3411 if v
.lower() != upn
.lower():
3416 raise CommandError("Entry not found for value[%s] specified for --delete-upn-suffix" % upn
)
3418 update_upn_vals
.pop(idx
)
3423 for i
in xrange(0, len(update_spn_vals
)):
3424 v
= update_spn_vals
[i
]
3425 if v
.lower() != spn
.lower():
3430 raise CommandError("Entry already present for value[%s] specified for --add-spn-suffix" % spn
)
3431 update_spn_vals
.append(spn
)
3434 for spn
in delete_spn
:
3436 for i
in xrange(0, len(update_spn_vals
)):
3437 v
= update_spn_vals
[i
]
3438 if v
.lower() != spn
.lower():
3443 raise CommandError("Entry not found for value[%s] specified for --delete-spn-suffix" % spn
)
3445 update_spn_vals
.pop(idx
)
3448 self
.outf
.write("Update uPNSuffixes attributes[%d]:\n" % len(update_upn_vals
))
3449 for v
in update_upn_vals
:
3450 self
.outf
.write("TLN: %-32s DNS[*.%s]\n" % ("", v
))
3451 self
.outf
.write("Update msDS-SPNSuffixes attributes[%d]:\n" % len(update_spn_vals
))
3452 for v
in update_spn_vals
:
3453 self
.outf
.write("TLN: %-32s DNS[*.%s]\n" % ("", v
))
3455 update_msg
= ldb
.Message()
3456 update_msg
.dn
= stored_msg
.dn
3459 update_msg
['uPNSuffixes'] = ldb
.MessageElement(update_upn_vals
,
3460 ldb
.FLAG_MOD_REPLACE
,
3463 update_msg
['msDS-SPNSuffixes'] = ldb
.MessageElement(update_spn_vals
,
3464 ldb
.FLAG_MOD_REPLACE
,
3467 local_samdb
.modify(update_msg
)
3468 except ldb
.LdbError
as error
:
3469 raise self
.LocalLdbError(self
, error
, "failed to update partition dn")
3472 stored_forest_info
= local_netlogon
.netr_DsRGetForestTrustInformation(local_netlogon_info
.dc_unc
,
3474 except RuntimeError as error
:
3475 raise self
.LocalRuntimeError(self
, error
, "netr_DsRGetForestTrustInformation() failed")
3477 self
.outf
.write("Stored forest trust information...\n")
3478 self
.write_forest_trust_info(stored_forest_info
,
3479 tln
=local_lsa_info
.dns_domain
.string
)
3483 lsaString
= lsa
.String()
3484 lsaString
.string
= domain
3485 local_tdo_info
= local_lsa
.QueryTrustedDomainInfoByName(local_policy
,
3486 lsaString
, lsa
.LSA_TRUSTED_DOMAIN_INFO_INFO_EX
)
3487 except NTSTATUSError
as error
:
3488 if self
.check_runtime_error(error
, ntstatus
.NT_STATUS_OBJECT_NAME_NOT_FOUND
):
3489 raise CommandError("trusted domain object does not exist for domain [%s]" % domain
)
3491 raise self
.LocalRuntimeError(self
, error
, "QueryTrustedDomainInfoByName(INFO_EX) failed")
3493 self
.outf
.write("LocalTDO Netbios[%s] DNS[%s] SID[%s]\n" % (
3494 local_tdo_info
.netbios_name
.string
,
3495 local_tdo_info
.domain_name
.string
,
3496 local_tdo_info
.sid
))
3498 if not local_tdo_info
.trust_attributes
& lsa
.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE
:
3499 raise CommandError("trusted domain object for domain [%s] is not marked as FOREST_TRANSITIVE." % domain
)
3501 if refresh
is not None:
3503 local_netlogon
= self
.new_local_netlogon_connection()
3504 except RuntimeError as error
:
3505 raise self
.LocalRuntimeError(self
, error
, "failed to connect netlogon server")
3508 local_netlogon_info
= self
.get_netlogon_dc_info(local_netlogon
, local_server
)
3509 except RuntimeError as error
:
3510 raise self
.LocalRuntimeError(self
, error
, "failed to get netlogon dc info")
3512 lsa_update_check
= 1
3513 if refresh
== "store":
3514 netlogon_update_tdo
= netlogon
.DS_GFTI_UPDATE_TDO
3516 lsa_update_check
= 0
3518 netlogon_update_tdo
= 0
3521 # get all information about the remote trust
3522 # this triggers netr_GetForestTrustInformation to the remote domain
3523 # and lsaRSetForestTrustInformation() locally, but new top level
3524 # names are disabled by default.
3525 fresh_forest_info
= local_netlogon
.netr_DsRGetForestTrustInformation(local_netlogon_info
.dc_unc
,
3526 local_tdo_info
.domain_name
.string
,
3527 netlogon_update_tdo
)
3528 except RuntimeError as error
:
3529 raise self
.LocalRuntimeError(self
, error
, "netr_DsRGetForestTrustInformation() failed")
3532 fresh_forest_collision
= local_lsa
.lsaRSetForestTrustInformation(local_policy
,
3533 local_tdo_info
.domain_name
,
3534 lsa
.LSA_FOREST_TRUST_DOMAIN_INFO
,
3537 except RuntimeError as error
:
3538 raise self
.LocalRuntimeError(self
, error
, "lsaRSetForestTrustInformation() failed")
3540 self
.outf
.write("Fresh forest trust information...\n")
3541 self
.write_forest_trust_info(fresh_forest_info
,
3542 tln
=local_tdo_info
.domain_name
.string
,
3543 collisions
=fresh_forest_collision
)
3545 if refresh
== "store":
3547 lsaString
= lsa
.String()
3548 lsaString
.string
= local_tdo_info
.domain_name
.string
3549 stored_forest_info
= local_lsa
.lsaRQueryForestTrustInformation(local_policy
,
3551 lsa
.LSA_FOREST_TRUST_DOMAIN_INFO
)
3552 except RuntimeError as error
:
3553 raise self
.LocalRuntimeError(self
, error
, "lsaRQueryForestTrustInformation() failed")
3555 self
.outf
.write("Stored forest trust information...\n")
3556 self
.write_forest_trust_info(stored_forest_info
,
3557 tln
=local_tdo_info
.domain_name
.string
)
3562 # The none --refresh path
3566 lsaString
= lsa
.String()
3567 lsaString
.string
= local_tdo_info
.domain_name
.string
3568 local_forest_info
= local_lsa
.lsaRQueryForestTrustInformation(local_policy
,
3570 lsa
.LSA_FOREST_TRUST_DOMAIN_INFO
)
3571 except RuntimeError as error
:
3572 raise self
.LocalRuntimeError(self
, error
, "lsaRQueryForestTrustInformation() failed")
3574 self
.outf
.write("Local forest trust information...\n")
3575 self
.write_forest_trust_info(local_forest_info
,
3576 tln
=local_tdo_info
.domain_name
.string
)
3578 if not require_update
:
3582 entries
.extend(local_forest_info
.entries
)
3583 update_forest_info
= lsa
.ForestTrustInformation()
3584 update_forest_info
.count
= len(entries
)
3585 update_forest_info
.entries
= entries
3588 for i
in xrange(0, len(update_forest_info
.entries
)):
3589 r
= update_forest_info
.entries
[i
]
3590 if r
.type != lsa
.LSA_FOREST_TRUST_TOP_LEVEL_NAME
:
3592 if update_forest_info
.entries
[i
].flags
== 0:
3594 update_forest_info
.entries
[i
].time
= 0
3595 update_forest_info
.entries
[i
].flags
&= ~lsa
.LSA_TLN_DISABLED_MASK
3596 for i
in xrange(0, len(update_forest_info
.entries
)):
3597 r
= update_forest_info
.entries
[i
]
3598 if r
.type != lsa
.LSA_FOREST_TRUST_DOMAIN_INFO
:
3600 if update_forest_info
.entries
[i
].flags
== 0:
3602 update_forest_info
.entries
[i
].time
= 0
3603 update_forest_info
.entries
[i
].flags
&= ~lsa
.LSA_NB_DISABLED_MASK
3604 update_forest_info
.entries
[i
].flags
&= ~lsa
.LSA_SID_DISABLED_MASK
3606 for tln
in enable_tln
:
3608 for i
in xrange(0, len(update_forest_info
.entries
)):
3609 r
= update_forest_info
.entries
[i
]
3610 if r
.type != lsa
.LSA_FOREST_TRUST_TOP_LEVEL_NAME
:
3612 if r
.forest_trust_data
.string
.lower() != tln
.lower():
3617 raise CommandError("Entry not found for value[%s] specified for --enable-tln" % tln
)
3618 if not update_forest_info
.entries
[idx
].flags
& lsa
.LSA_TLN_DISABLED_MASK
:
3619 raise CommandError("Entry found for value[%s] specified for --enable-tln is already enabled" % tln
)
3620 update_forest_info
.entries
[idx
].time
= 0
3621 update_forest_info
.entries
[idx
].flags
&= ~lsa
.LSA_TLN_DISABLED_MASK
3623 for tln
in disable_tln
:
3625 for i
in xrange(0, len(update_forest_info
.entries
)):
3626 r
= update_forest_info
.entries
[i
]
3627 if r
.type != lsa
.LSA_FOREST_TRUST_TOP_LEVEL_NAME
:
3629 if r
.forest_trust_data
.string
.lower() != tln
.lower():
3634 raise CommandError("Entry not found for value[%s] specified for --disable-tln" % tln
)
3635 if update_forest_info
.entries
[idx
].flags
& lsa
.LSA_TLN_DISABLED_ADMIN
:
3636 raise CommandError("Entry found for value[%s] specified for --disable-tln is already disabled" % tln
)
3637 update_forest_info
.entries
[idx
].time
= 0
3638 update_forest_info
.entries
[idx
].flags
&= ~lsa
.LSA_TLN_DISABLED_MASK
3639 update_forest_info
.entries
[idx
].flags |
= lsa
.LSA_TLN_DISABLED_ADMIN
3641 for tln_ex
in add_tln_ex
:
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_EX
:
3647 if r
.forest_trust_data
.string
.lower() != tln_ex
.lower():
3652 raise CommandError("Entry already present for value[%s] specified for --add-tln-ex" % tln_ex
)
3654 tln_dot
= ".%s" % tln_ex
.lower()
3656 for i
in xrange(0, len(update_forest_info
.entries
)):
3657 r
= update_forest_info
.entries
[i
]
3658 if r
.type != lsa
.LSA_FOREST_TRUST_TOP_LEVEL_NAME
:
3660 r_dot
= ".%s" % r
.forest_trust_data
.string
.lower()
3661 if tln_dot
== r_dot
:
3662 raise CommandError("TLN entry present for value[%s] specified for --add-tln-ex" % tln_ex
)
3663 if not tln_dot
.endswith(r_dot
):
3669 raise CommandError("No TLN parent present for value[%s] specified for --add-tln-ex" % tln_ex
)
3671 r
= lsa
.ForestTrustRecord()
3672 r
.type = lsa
.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX
3675 r
.forest_trust_data
.string
= tln_ex
3678 entries
.extend(update_forest_info
.entries
)
3679 entries
.insert(idx
+ 1, r
)
3680 update_forest_info
.count
= len(entries
)
3681 update_forest_info
.entries
= entries
3683 for tln_ex
in delete_tln_ex
:
3685 for i
in xrange(0, len(update_forest_info
.entries
)):
3686 r
= update_forest_info
.entries
[i
]
3687 if r
.type != lsa
.LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX
:
3689 if r
.forest_trust_data
.string
.lower() != tln_ex
.lower():
3694 raise CommandError("Entry not found for value[%s] specified for --delete-tln-ex" % tln_ex
)
3697 entries
.extend(update_forest_info
.entries
)
3699 update_forest_info
.count
= len(entries
)
3700 update_forest_info
.entries
= entries
3702 for nb
in enable_nb
:
3704 for i
in xrange(0, len(update_forest_info
.entries
)):
3705 r
= update_forest_info
.entries
[i
]
3706 if r
.type != lsa
.LSA_FOREST_TRUST_DOMAIN_INFO
:
3708 if r
.forest_trust_data
.netbios_domain_name
.string
.upper() != nb
.upper():
3713 raise CommandError("Entry not found for value[%s] specified for --enable-nb" % nb
)
3714 if not update_forest_info
.entries
[idx
].flags
& lsa
.LSA_NB_DISABLED_MASK
:
3715 raise CommandError("Entry found for value[%s] specified for --enable-nb is already enabled" % nb
)
3716 update_forest_info
.entries
[idx
].time
= 0
3717 update_forest_info
.entries
[idx
].flags
&= ~lsa
.LSA_NB_DISABLED_MASK
3719 for nb
in disable_nb
:
3721 for i
in xrange(0, len(update_forest_info
.entries
)):
3722 r
= update_forest_info
.entries
[i
]
3723 if r
.type != lsa
.LSA_FOREST_TRUST_DOMAIN_INFO
:
3725 if r
.forest_trust_data
.netbios_domain_name
.string
.upper() != nb
.upper():
3730 raise CommandError("Entry not found for value[%s] specified for --delete-nb" % nb
)
3731 if update_forest_info
.entries
[idx
].flags
& lsa
.LSA_NB_DISABLED_ADMIN
:
3732 raise CommandError("Entry found for value[%s] specified for --disable-nb is already disabled" % nb
)
3733 update_forest_info
.entries
[idx
].time
= 0
3734 update_forest_info
.entries
[idx
].flags
&= ~lsa
.LSA_NB_DISABLED_MASK
3735 update_forest_info
.entries
[idx
].flags |
= lsa
.LSA_NB_DISABLED_ADMIN
3737 for sid
in enable_sid
:
3739 for i
in xrange(0, len(update_forest_info
.entries
)):
3740 r
= update_forest_info
.entries
[i
]
3741 if r
.type != lsa
.LSA_FOREST_TRUST_DOMAIN_INFO
:
3743 if r
.forest_trust_data
.domain_sid
!= sid
:
3748 raise CommandError("Entry not found for value[%s] specified for --enable-sid" % sid
)
3749 if not update_forest_info
.entries
[idx
].flags
& lsa
.LSA_SID_DISABLED_MASK
:
3750 raise CommandError("Entry found for value[%s] specified for --enable-sid is already enabled" % nb
)
3751 update_forest_info
.entries
[idx
].time
= 0
3752 update_forest_info
.entries
[idx
].flags
&= ~lsa
.LSA_SID_DISABLED_MASK
3754 for sid
in disable_sid
:
3756 for i
in xrange(0, len(update_forest_info
.entries
)):
3757 r
= update_forest_info
.entries
[i
]
3758 if r
.type != lsa
.LSA_FOREST_TRUST_DOMAIN_INFO
:
3760 if r
.forest_trust_data
.domain_sid
!= sid
:
3765 raise CommandError("Entry not found for value[%s] specified for --delete-sid" % sid
)
3766 if update_forest_info
.entries
[idx
].flags
& lsa
.LSA_SID_DISABLED_ADMIN
:
3767 raise CommandError("Entry found for value[%s] specified for --disable-sid is already disabled" % nb
)
3768 update_forest_info
.entries
[idx
].time
= 0
3769 update_forest_info
.entries
[idx
].flags
&= ~lsa
.LSA_SID_DISABLED_MASK
3770 update_forest_info
.entries
[idx
].flags |
= lsa
.LSA_SID_DISABLED_ADMIN
3773 update_forest_collision
= local_lsa
.lsaRSetForestTrustInformation(local_policy
,
3774 local_tdo_info
.domain_name
,
3775 lsa
.LSA_FOREST_TRUST_DOMAIN_INFO
,
3776 update_forest_info
, 0)
3777 except RuntimeError as error
:
3778 raise self
.LocalRuntimeError(self
, error
, "lsaRSetForestTrustInformation() failed")
3780 self
.outf
.write("Updated forest trust information...\n")
3781 self
.write_forest_trust_info(update_forest_info
,
3782 tln
=local_tdo_info
.domain_name
.string
,
3783 collisions
=update_forest_collision
)
3786 lsaString
= lsa
.String()
3787 lsaString
.string
= local_tdo_info
.domain_name
.string
3788 stored_forest_info
= local_lsa
.lsaRQueryForestTrustInformation(local_policy
,
3790 lsa
.LSA_FOREST_TRUST_DOMAIN_INFO
)
3791 except RuntimeError as error
:
3792 raise self
.LocalRuntimeError(self
, error
, "lsaRQueryForestTrustInformation() failed")
3794 self
.outf
.write("Stored forest trust information...\n")
3795 self
.write_forest_trust_info(stored_forest_info
,
3796 tln
=local_tdo_info
.domain_name
.string
)
3799 class cmd_domain_tombstones_expunge(Command
):
3800 """Expunge tombstones from the database.
3802 This command expunges tombstones from the database."""
3803 synopsis
= "%prog NC [NC [...]] [options]"
3806 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3807 metavar
="URL", dest
="H"),
3808 Option("--current-time",
3809 help="The current time to evaluate the tombstone lifetime from, expressed as YYYY-MM-DD",
3811 Option("--tombstone-lifetime", help="Number of days a tombstone should be preserved for", type=int),
3814 takes_args
= ["nc*"]
3816 takes_optiongroups
= {
3817 "sambaopts": options
.SambaOptions
,
3818 "credopts": options
.CredentialsOptions
,
3819 "versionopts": options
.VersionOptions
,
3822 def run(self
, *ncs
, **kwargs
):
3823 sambaopts
= kwargs
.get("sambaopts")
3824 credopts
= kwargs
.get("credopts")
3825 versionpts
= kwargs
.get("versionopts")
3827 current_time_string
= kwargs
.get("current_time")
3828 tombstone_lifetime
= kwargs
.get("tombstone_lifetime")
3829 lp
= sambaopts
.get_loadparm()
3830 creds
= credopts
.get_credentials(lp
)
3831 samdb
= SamDB(url
=H
, session_info
=system_session(),
3832 credentials
=creds
, lp
=lp
)
3834 if current_time_string
is not None:
3835 current_time_obj
= time
.strptime(current_time_string
, "%Y-%m-%d")
3836 current_time
= long(time
.mktime(current_time_obj
))
3839 current_time
= long(time
.time())
3842 res
= samdb
.search(expression
="", base
="", scope
=ldb
.SCOPE_BASE
,
3843 attrs
=["namingContexts"])
3846 for nc
in res
[0]["namingContexts"]:
3851 started_transaction
= False
3853 samdb
.transaction_start()
3854 started_transaction
= True
3856 removed_links
) = samdb
.garbage_collect_tombstones(ncs
,
3857 current_time
=current_time
,
3858 tombstone_lifetime
=tombstone_lifetime
)
3860 except Exception as err
:
3861 if started_transaction
:
3862 samdb
.transaction_cancel()
3863 raise CommandError("Failed to expunge / garbage collect tombstones", err
)
3865 samdb
.transaction_commit()
3867 self
.outf
.write("Removed %d objects and %d links successfully\n"
3868 % (removed_objects
, removed_links
))
3872 class cmd_domain_trust(SuperCommand
):
3873 """Domain and forest trust management."""
3876 subcommands
["list"] = cmd_domain_trust_list()
3877 subcommands
["show"] = cmd_domain_trust_show()
3878 subcommands
["create"] = cmd_domain_trust_create()
3879 subcommands
["delete"] = cmd_domain_trust_delete()
3880 subcommands
["validate"] = cmd_domain_trust_validate()
3881 subcommands
["namespaces"] = cmd_domain_trust_namespaces()
3883 class cmd_domain_tombstones(SuperCommand
):
3884 """Domain tombstone and recycled object management."""
3887 subcommands
["expunge"] = cmd_domain_tombstones_expunge()
3889 class ldif_schema_update
:
3890 """Helper class for applying LDIF schema updates"""
3893 self
.is_defunct
= False
3894 self
.unknown_oid
= None
3898 def _ldap_schemaUpdateNow(self
, samdb
):
3902 add: schemaUpdateNow
3905 samdb
.modify_ldif(ldif
)
3907 def can_ignore_failure(self
, error
):
3908 """Checks if we can safely ignore failure to apply an LDIF update"""
3909 (num
, errstr
) = error
.args
3911 # Microsoft has marked objects as defunct that Samba doesn't know about
3912 if num
== ldb
.ERR_NO_SUCH_OBJECT
and self
.is_defunct
:
3913 print("Defunct object %s doesn't exist, skipping" % self
.dn
)
3915 elif self
.unknown_oid
is not None:
3916 print("Skipping unknown OID %s for object %s" %(self
.unknown_oid
, self
.dn
))
3921 def apply(self
, samdb
):
3922 """Applies a single LDIF update to the schema"""
3926 samdb
.modify_ldif(self
.ldif
, controls
=['relax:0'])
3927 except ldb
.LdbError
as e
:
3928 if e
.args
[0] == ldb
.ERR_INVALID_ATTRIBUTE_SYNTAX
:
3930 # REFRESH after a failed change
3932 # Otherwise the OID-to-attribute mapping in
3933 # _apply_updates_in_file() won't work, because it
3934 # can't lookup the new OID in the schema
3935 self
._ldap
_schemaUpdateNow
(samdb
)
3937 samdb
.modify_ldif(self
.ldif
, controls
=['relax:0'])
3940 except ldb
.LdbError
as e
:
3941 if self
.can_ignore_failure(e
):
3944 print("Exception: %s" % e
)
3945 print("Encountered while trying to apply the following LDIF")
3946 print("----------------------------------------------------")
3947 print("%s" % self
.ldif
)
3953 class cmd_domain_schema_upgrade(Command
):
3954 """Domain schema upgrading"""
3956 synopsis
= "%prog [options]"
3958 takes_optiongroups
= {
3959 "sambaopts": options
.SambaOptions
,
3960 "versionopts": options
.VersionOptions
,
3961 "credopts": options
.CredentialsOptions
,
3965 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
3966 metavar
="URL", dest
="H"),
3967 Option("--quiet", help="Be quiet", action
="store_true"),
3968 Option("--verbose", help="Be verbose", action
="store_true"),
3969 Option("--schema", type="choice", metavar
="SCHEMA",
3970 choices
=["2012", "2012_R2"],
3971 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
3973 Option("--ldf-file", type=str, default
=None,
3974 help="Just apply the schema updates in the adprep/.LDF file(s) specified"),
3975 Option("--base-dir", type=str, default
=None,
3976 help="Location of ldf files Default is ${SETUPDIR}/adprep.")
3979 def _apply_updates_in_file(self
, samdb
, ldif_file
):
3981 Applies a series of updates specified in an .LDIF file. The .LDIF file
3982 is based on the adprep Schema updates provided by Microsoft.
3985 ldif_op
= ldif_schema_update()
3987 # parse the file line by line and work out each update operation to apply
3988 for line
in ldif_file
:
3990 line
= line
.rstrip()
3992 # the operations in the .LDIF file are separated by blank lines. If
3993 # we hit a blank line, try to apply the update we've parsed so far
3996 # keep going if we haven't parsed anything yet
3997 if ldif_op
.ldif
== '':
4000 # Apply the individual change
4001 count
+= ldif_op
.apply(samdb
)
4003 # start storing the next operation from scratch again
4004 ldif_op
= ldif_schema_update()
4007 # replace the placeholder domain name in the .ldif file with the real domain
4008 if line
.upper().endswith('DC=X'):
4009 line
= line
[:-len('DC=X')] + str(samdb
.get_default_basedn())
4010 elif line
.upper().endswith('CN=X'):
4011 line
= line
[:-len('CN=X')] + str(samdb
.get_default_basedn())
4013 values
= line
.split(':')
4015 if values
[0].lower() == 'dn':
4016 ldif_op
.dn
= values
[1].strip()
4018 # replace the Windows-specific operation with the Samba one
4019 if values
[0].lower() == 'changetype':
4020 line
= line
.lower().replace(': ntdsschemaadd',
4022 line
= line
.lower().replace(': ntdsschemamodify',
4025 if values
[0].lower() in ['rdnattid', 'subclassof',
4026 'systemposssuperiors',
4028 'systemauxiliaryclass']:
4031 # The Microsoft updates contain some OIDs we don't recognize.
4032 # Query the DB to see if we can work out the OID this update is
4033 # referring to. If we find a match, then replace the OID with
4034 # the ldapDisplayname
4036 res
= samdb
.search(base
=samdb
.get_schema_basedn(),
4037 expression
="(|(attributeId=%s)(governsId=%s))" %
4039 attrs
=['ldapDisplayName'])
4042 ldif_op
.unknown_oid
= value
4044 display_name
= res
[0]['ldapDisplayName'][0]
4045 line
= line
.replace(value
, ' ' + display_name
)
4047 # Microsoft has marked objects as defunct that Samba doesn't know about
4048 if values
[0].lower() == 'isdefunct' and values
[1].strip().lower() == 'true':
4049 ldif_op
.is_defunct
= True
4051 # Samba has added the showInAdvancedViewOnly attribute to all objects,
4052 # so rather than doing an add, we need to do a replace
4053 if values
[0].lower() == 'add' and values
[1].strip().lower() == 'showinadvancedviewonly':
4054 line
= 'replace: showInAdvancedViewOnly'
4056 # Add the line to the current LDIF operation (including the newline
4057 # we stripped off at the start of the loop)
4058 ldif_op
.ldif
+= line
+ '\n'
4063 def _apply_update(self
, samdb
, update_file
, base_dir
):
4064 """Wrapper function for parsing an LDIF file and applying the updates"""
4066 print("Applying %s updates..." % update_file
)
4070 ldif_file
= open(os
.path
.join(base_dir
, update_file
))
4072 count
= self
._apply
_updates
_in
_file
(samdb
, ldif_file
)
4078 print("%u changes applied" % count
)
4082 def run(self
, **kwargs
):
4083 from samba
.ms_schema_markdown
import read_ms_markdown
4084 from samba
.schema
import Schema
4086 updates_allowed_overriden
= False
4087 sambaopts
= kwargs
.get("sambaopts")
4088 credopts
= kwargs
.get("credopts")
4089 versionpts
= kwargs
.get("versionopts")
4090 lp
= sambaopts
.get_loadparm()
4091 creds
= credopts
.get_credentials(lp
)
4093 target_schema
= kwargs
.get("schema")
4094 ldf_files
= kwargs
.get("ldf_file")
4095 base_dir
= kwargs
.get("base_dir")
4099 samdb
= SamDB(url
=H
, session_info
=system_session(), credentials
=creds
, lp
=lp
)
4101 # we're not going to get far if the config doesn't allow schema updates
4102 if lp
.get("dsdb:schema update allowed") is None:
4103 lp
.set("dsdb:schema update allowed", "yes")
4104 print("Temporarily overriding 'dsdb:schema update allowed' setting")
4105 updates_allowed_overriden
= True
4107 own_dn
= ldb
.Dn(samdb
, samdb
.get_dsServiceName())
4108 master
= get_fsmo_roleowner(samdb
, str(samdb
.get_schema_basedn()),
4110 if own_dn
!= master
:
4111 raise CommandError("This server is not the schema master.")
4113 # if specific LDIF files were specified, just apply them
4115 schema_updates
= ldf_files
.split(",")
4119 # work out the version of the target schema we're upgrading to
4120 end
= Schema
.get_version(target_schema
)
4122 # work out the version of the schema we're currently using
4123 res
= samdb
.search(base
=samdb
.get_schema_basedn(),
4124 scope
=ldb
.SCOPE_BASE
, attrs
=['objectVersion'])
4127 raise CommandError('Could not determine current schema version')
4128 start
= int(res
[0]['objectVersion'][0]) + 1
4130 diff_dir
= setup_path("adprep/WindowsServerDocs")
4131 if base_dir
is None:
4132 # Read from the Schema-Updates.md file
4133 temp_folder
= tempfile
.mkdtemp()
4135 update_file
= setup_path("adprep/WindowsServerDocs/Schema-Updates.md")
4138 read_ms_markdown(update_file
, temp_folder
)
4139 except Exception as e
:
4140 print("Exception in markdown parsing: %s" % e
)
4141 shutil
.rmtree(temp_folder
)
4142 raise CommandError('Failed to upgrade schema')
4144 base_dir
= temp_folder
4146 for version
in range(start
, end
+ 1):
4147 update
= 'Sch%d.ldf' % version
4148 schema_updates
.append(update
)
4150 # Apply patches if we parsed the Schema-Updates.md file
4151 diff
= os
.path
.abspath(os
.path
.join(diff_dir
, update
+ '.diff'))
4152 if temp_folder
and os
.path
.exists(diff
):
4154 p
= subprocess
.Popen(['patch', update
, '-i', diff
],
4155 stdout
=subprocess
.PIPE
,
4156 stderr
=subprocess
.PIPE
, cwd
=temp_folder
)
4157 except (OSError, IOError):
4158 shutil
.rmtree(temp_folder
)
4159 raise CommandError("Failed to upgrade schema. Check if 'patch' is installed.")
4161 stdout
, stderr
= p
.communicate()
4164 print("Exception in patch: %s\n%s" % (stdout
, stderr
))
4165 shutil
.rmtree(temp_folder
)
4166 raise CommandError('Failed to upgrade schema')
4168 print("Patched %s using %s" % (update
, diff
))
4170 if base_dir
is None:
4171 base_dir
= setup_path("adprep")
4173 samdb
.transaction_start()
4175 error_encountered
= False
4178 # Apply the schema updates needed to move to the new schema version
4179 for ldif_file
in schema_updates
:
4180 count
+= self
._apply
_update
(samdb
, ldif_file
, base_dir
)
4183 samdb
.transaction_commit()
4184 print("Schema successfully updated")
4186 print("No changes applied to schema")
4187 samdb
.transaction_cancel()
4188 except Exception as e
:
4189 print("Exception: %s" % e
)
4190 print("Error encountered, aborting schema upgrade")
4191 samdb
.transaction_cancel()
4192 error_encountered
= True
4194 if updates_allowed_overriden
:
4195 lp
.set("dsdb:schema update allowed", "no")
4198 shutil
.rmtree(temp_folder
)
4200 if error_encountered
:
4201 raise CommandError('Failed to upgrade schema')
4203 class cmd_domain_functional_prep(Command
):
4204 """Domain functional level preparation"""
4206 synopsis
= "%prog [options]"
4208 takes_optiongroups
= {
4209 "sambaopts": options
.SambaOptions
,
4210 "versionopts": options
.VersionOptions
,
4211 "credopts": options
.CredentialsOptions
,
4215 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
4216 metavar
="URL", dest
="H"),
4217 Option("--quiet", help="Be quiet", action
="store_true"),
4218 Option("--verbose", help="Be verbose", action
="store_true"),
4219 Option("--function-level", type="choice", metavar
="FUNCTION_LEVEL",
4220 choices
=["2008_R2", "2012", "2012_R2"],
4221 help="The schema file to upgrade to. Default is (Windows) 2012_R2.",
4223 Option("--forest-prep", action
="store_true",
4224 help="Run the forest prep (by default, both the domain and forest prep are run)."),
4225 Option("--domain-prep", action
="store_true",
4226 help="Run the domain prep (by default, both the domain and forest prep are run).")
4229 def run(self
, **kwargs
):
4230 updates_allowed_overriden
= False
4231 sambaopts
= kwargs
.get("sambaopts")
4232 credopts
= kwargs
.get("credopts")
4233 versionpts
= kwargs
.get("versionopts")
4234 lp
= sambaopts
.get_loadparm()
4235 creds
= credopts
.get_credentials(lp
)
4237 target_level
= string_version_to_constant
[kwargs
.get("function_level")]
4238 forest_prep
= kwargs
.get("forest_prep")
4239 domain_prep
= kwargs
.get("domain_prep")
4241 samdb
= SamDB(url
=H
, session_info
=system_session(), credentials
=creds
, lp
=lp
)
4243 # we're not going to get far if the config doesn't allow schema updates
4244 if lp
.get("dsdb:schema update allowed") is None:
4245 lp
.set("dsdb:schema update allowed", "yes")
4246 print("Temporarily overriding 'dsdb:schema update allowed' setting")
4247 updates_allowed_overriden
= True
4249 if forest_prep
is None and domain_prep
is None:
4253 own_dn
= ldb
.Dn(samdb
, samdb
.get_dsServiceName())
4255 master
= get_fsmo_roleowner(samdb
, str(samdb
.get_schema_basedn()),
4257 if own_dn
!= master
:
4258 raise CommandError("This server is not the schema master.")
4261 domain_dn
= samdb
.domain_dn()
4262 infrastructure_dn
= "CN=Infrastructure," + domain_dn
4263 master
= get_fsmo_roleowner(samdb
, infrastructure_dn
,
4265 if own_dn
!= master
:
4266 raise CommandError("This server is not the infrastructure master.")
4269 samdb
.transaction_start()
4270 error_encountered
= False
4272 from samba
.forest_update
import ForestUpdate
4273 forest
= ForestUpdate(samdb
, fix
=True)
4275 forest
.check_updates_iterator([53, 79, 80, 81, 82, 83])
4276 forest
.check_updates_functional_level(target_level
,
4277 DS_DOMAIN_FUNCTION_2008_R2
,
4278 update_revision
=True)
4280 samdb
.transaction_commit()
4281 except Exception as e
:
4282 print("Exception: %s" % e
)
4283 samdb
.transaction_cancel()
4284 error_encountered
= True
4287 samdb
.transaction_start()
4288 error_encountered
= False
4290 from samba
.domain_update
import DomainUpdate
4292 domain
= DomainUpdate(samdb
, fix
=True)
4293 domain
.check_updates_functional_level(target_level
,
4294 DS_DOMAIN_FUNCTION_2008
,
4295 update_revision
=True)
4297 samdb
.transaction_commit()
4298 except Exception as e
:
4299 print("Exception: %s" % e
)
4300 samdb
.transaction_cancel()
4301 error_encountered
= True
4303 if updates_allowed_overriden
:
4304 lp
.set("dsdb:schema update allowed", "no")
4306 if error_encountered
:
4307 raise CommandError('Failed to perform functional prep')
4309 class cmd_domain(SuperCommand
):
4310 """Domain management."""
4313 subcommands
["demote"] = cmd_domain_demote()
4314 if cmd_domain_export_keytab
is not None:
4315 subcommands
["exportkeytab"] = cmd_domain_export_keytab()
4316 subcommands
["info"] = cmd_domain_info()
4317 subcommands
["provision"] = cmd_domain_provision()
4318 subcommands
["join"] = cmd_domain_join()
4319 subcommands
["dcpromo"] = cmd_domain_dcpromo()
4320 subcommands
["level"] = cmd_domain_level()
4321 subcommands
["passwordsettings"] = cmd_domain_passwordsettings()
4322 subcommands
["classicupgrade"] = cmd_domain_classicupgrade()
4323 subcommands
["samba3upgrade"] = cmd_domain_samba3upgrade()
4324 subcommands
["trust"] = cmd_domain_trust()
4325 subcommands
["tombstones"] = cmd_domain_tombstones()
4326 subcommands
["schemaupgrade"] = cmd_domain_schema_upgrade()
4327 subcommands
["functionalprep"] = cmd_domain_functional_prep()