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
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
32 from samba
.net
import Net
, LIBNET_JOIN_AUTOMATIC
34 from samba
.join
import join_RODC
, join_DC
, join_subdomain
35 from samba
.auth
import system_session
36 from samba
.samdb
import SamDB
37 from samba
.dcerpc
import drsuapi
38 from samba
.dcerpc
import security
39 from samba
.dcerpc
.samr
import DOMAIN_PASSWORD_COMPLEX
, DOMAIN_PASSWORD_STORE_CLEARTEXT
40 from samba
.netcmd
import (
46 from samba
.netcmd
.common
import netcmd_get_domain_infos_via_cldap
47 from samba
.samba3
import Samba3
48 from samba
.samba3
import param
as s3param
49 from samba
.upgrade
import upgrade_from_samba3
50 from samba
.drs_utils
import (
51 sendDsReplicaSync
, drsuapi_connect
, drsException
,
55 from samba
.dsdb
import (
56 DS_DOMAIN_FUNCTION_2000
,
57 DS_DOMAIN_FUNCTION_2003
,
58 DS_DOMAIN_FUNCTION_2003_MIXED
,
59 DS_DOMAIN_FUNCTION_2008
,
60 DS_DOMAIN_FUNCTION_2008_R2
,
61 DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL
,
62 DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
,
63 UF_WORKSTATION_TRUST_ACCOUNT
,
64 UF_SERVER_TRUST_ACCOUNT
,
65 UF_TRUSTED_FOR_DELEGATION
68 from samba
.provision
import (
73 from samba
.provision
.common
import (
79 def get_testparm_var(testparm
, smbconf
, varname
):
80 cmd
= "%s -s -l --parameter-name='%s' %s 2>/dev/null" % (testparm
, varname
, smbconf
)
81 output
= os
.popen(cmd
, 'r').readline()
87 cmd_domain_export_keytab
= None
89 class cmd_domain_export_keytab(Command
):
90 """Dump Kerberos keys of the domain into a keytab."""
92 synopsis
= "%prog <keytab> [options]"
94 takes_optiongroups
= {
95 "sambaopts": options
.SambaOptions
,
96 "credopts": options
.CredentialsOptions
,
97 "versionopts": options
.VersionOptions
,
101 Option("--principal", help="extract only this principal", type=str),
104 takes_args
= ["keytab"]
106 def run(self
, keytab
, credopts
=None, sambaopts
=None, versionopts
=None, principal
=None):
107 lp
= sambaopts
.get_loadparm()
109 net
.export_keytab(keytab
=keytab
, principal
=principal
)
112 class cmd_domain_info(Command
):
113 """Print basic info about a domain and the DC passed as parameter."""
115 synopsis
= "%prog <ip_address> [options]"
120 takes_optiongroups
= {
121 "sambaopts": options
.SambaOptions
,
122 "credopts": options
.CredentialsOptions
,
123 "versionopts": options
.VersionOptions
,
126 takes_args
= ["address"]
128 def run(self
, address
, credopts
=None, sambaopts
=None, versionopts
=None):
129 lp
= sambaopts
.get_loadparm()
131 res
= netcmd_get_domain_infos_via_cldap(lp
, None, address
)
133 raise CommandError("Invalid IP address '" + address
+ "'!")
134 self
.outf
.write("Forest : %s\n" % res
.forest
)
135 self
.outf
.write("Domain : %s\n" % res
.dns_domain
)
136 self
.outf
.write("Netbios domain : %s\n" % res
.domain_name
)
137 self
.outf
.write("DC name : %s\n" % res
.pdc_dns_name
)
138 self
.outf
.write("DC netbios name : %s\n" % res
.pdc_name
)
139 self
.outf
.write("Server site : %s\n" % res
.server_site
)
140 self
.outf
.write("Client site : %s\n" % res
.client_site
)
143 class cmd_domain_provision(Command
):
144 """Provision a domain."""
146 synopsis
= "%prog [options]"
148 takes_optiongroups
= {
149 "sambaopts": options
.SambaOptions
,
150 "versionopts": options
.VersionOptions
,
154 Option("--interactive", help="Ask for names", action
="store_true"),
155 Option("--domain", type="string", metavar
="DOMAIN",
157 Option("--domain-guid", type="string", metavar
="GUID",
158 help="set domainguid (otherwise random)"),
159 Option("--domain-sid", type="string", metavar
="SID",
160 help="set domainsid (otherwise random)"),
161 Option("--ntds-guid", type="string", metavar
="GUID",
162 help="set NTDS object GUID (otherwise random)"),
163 Option("--invocationid", type="string", metavar
="GUID",
164 help="set invocationid (otherwise random)"),
165 Option("--host-name", type="string", metavar
="HOSTNAME",
166 help="set hostname"),
167 Option("--host-ip", type="string", metavar
="IPADDRESS",
168 help="set IPv4 ipaddress"),
169 Option("--host-ip6", type="string", metavar
="IP6ADDRESS",
170 help="set IPv6 ipaddress"),
171 Option("--site", type="string", metavar
="SITENAME",
172 help="set site name"),
173 Option("--adminpass", type="string", metavar
="PASSWORD",
174 help="choose admin password (otherwise random)"),
175 Option("--krbtgtpass", type="string", metavar
="PASSWORD",
176 help="choose krbtgt password (otherwise random)"),
177 Option("--machinepass", type="string", metavar
="PASSWORD",
178 help="choose machine password (otherwise random)"),
179 Option("--dns-backend", type="choice", metavar
="NAMESERVER-BACKEND",
180 choices
=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
181 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
182 "BIND9_FLATFILE uses bind9 text database to store zone information, "
183 "BIND9_DLZ uses samba4 AD to store zone information, "
184 "NONE skips the DNS setup entirely (not recommended)",
185 default
="SAMBA_INTERNAL"),
186 Option("--dnspass", type="string", metavar
="PASSWORD",
187 help="choose dns password (otherwise random)"),
188 Option("--ldapadminpass", type="string", metavar
="PASSWORD",
189 help="choose password to set between Samba and it's LDAP backend (otherwise random)"),
190 Option("--root", type="string", metavar
="USERNAME",
191 help="choose 'root' unix username"),
192 Option("--nobody", type="string", metavar
="USERNAME",
193 help="choose 'nobody' user"),
194 Option("--users", type="string", metavar
="GROUPNAME",
195 help="choose 'users' group"),
196 Option("--quiet", help="Be quiet", action
="store_true"),
197 Option("--blank", action
="store_true",
198 help="do not add users or groups, just the structure"),
199 Option("--ldap-backend-type", type="choice", metavar
="LDAP-BACKEND-TYPE",
200 help="Test initialisation support for unsupported LDAP backend type (fedora-ds or openldap) DO NOT USE",
201 choices
=["fedora-ds", "openldap"]),
202 Option("--server-role", type="choice", metavar
="ROLE",
203 choices
=["domain controller", "dc", "member server", "member", "standalone"],
204 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
205 default
="domain controller"),
206 Option("--function-level", type="choice", metavar
="FOR-FUN-LEVEL",
207 choices
=["2000", "2003", "2008", "2008_R2"],
208 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2008_R2 Native.",
210 Option("--next-rid", type="int", metavar
="NEXTRID", default
=1000,
211 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
212 Option("--partitions-only",
213 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action
="store_true"),
214 Option("--targetdir", type="string", metavar
="DIR",
215 help="Set target directory"),
216 Option("--ol-mmr-urls", type="string", metavar
="LDAPSERVER",
217 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\""),
218 Option("--use-xattrs", type="choice", choices
=["yes", "no", "auto"], help="Define if we should use the native fs capabilities or a tdb file for storing attributes likes ntacl, auto tries to make an inteligent guess based on the user rights and system capabilities", default
="auto"),
219 Option("--use-ntvfs", action
="store_true", help="Use NTVFS for the fileserver (default = no)"),
220 Option("--use-rfc2307", action
="store_true", help="Use AD to store posix attributes (default = no)"),
224 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",
225 action
="store_true"),
226 Option("--slapd-path", type="string", metavar
="SLAPD-PATH",
227 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."),
228 Option("--ldap-backend-extra-port", type="int", metavar
="LDAP-BACKEND-EXTRA-PORT", help="Additional TCP port for LDAP backend server (to use for replication)"),
229 Option("--ldap-backend-forced-uri", type="string", metavar
="LDAP-BACKEND-FORCED-URI",
230 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"),
231 Option("--ldap-backend-nosync", help="Configure LDAP backend not to call fsync() (for performance in test environments)", action
="store_true"),
234 if os
.getenv('TEST_LDAP', "no") == "yes":
235 takes_options
.extend(openldap_options
)
239 def run(self
, sambaopts
=None, versionopts
=None,
262 ldap_backend_type
=None,
266 partitions_only
=None,
273 ldap_backend_nosync
=None,
274 ldap_backend_extra_port
=None,
275 ldap_backend_forced_uri
=None,
276 ldap_dryrun_mode
=None):
278 self
.logger
= self
.get_logger("provision")
280 self
.logger
.setLevel(logging
.WARNING
)
282 self
.logger
.setLevel(logging
.INFO
)
284 lp
= sambaopts
.get_loadparm()
285 smbconf
= lp
.configfile
287 if dns_forwarder
is not None:
288 suggested_forwarder
= dns_forwarder
290 suggested_forwarder
= self
._get
_nameserver
_ip
()
291 if suggested_forwarder
is None:
292 suggested_forwarder
= "none"
294 if len(self
.raw_argv
) == 1:
298 from getpass
import getpass
301 def ask(prompt
, default
=None):
302 if default
is not None:
303 print "%s [%s]: " % (prompt
, default
),
305 print "%s: " % (prompt
,),
306 return sys
.stdin
.readline().rstrip("\n") or default
309 default
= socket
.getfqdn().split(".", 1)[1].upper()
312 realm
= ask("Realm", default
)
313 if realm
in (None, ""):
314 raise CommandError("No realm set!")
317 default
= realm
.split(".")[0]
320 domain
= ask("Domain", default
)
322 raise CommandError("No domain set!")
324 server_role
= ask("Server Role (dc, member, standalone)", "dc")
326 dns_backend
= ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
327 if dns_backend
in (None, ''):
328 raise CommandError("No DNS backend set!")
330 if dns_backend
== "SAMBA_INTERNAL":
331 dns_forwarder
= ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder
)
332 if dns_forwarder
.lower() in (None, 'none'):
333 suggested_forwarder
= None
337 adminpassplain
= getpass("Administrator password: ")
338 if not adminpassplain
:
339 self
.errf
.write("Invalid administrator password.\n")
341 adminpassverify
= getpass("Retype password: ")
342 if not adminpassplain
== adminpassverify
:
343 self
.errf
.write("Sorry, passwords do not match.\n")
345 adminpass
= adminpassplain
349 realm
= sambaopts
._lp
.get('realm')
351 raise CommandError("No realm set!")
353 raise CommandError("No domain set!")
356 self
.logger
.info("Administrator password will be set randomly!")
358 if function_level
== "2000":
359 dom_for_fun_level
= DS_DOMAIN_FUNCTION_2000
360 elif function_level
== "2003":
361 dom_for_fun_level
= DS_DOMAIN_FUNCTION_2003
362 elif function_level
== "2008":
363 dom_for_fun_level
= DS_DOMAIN_FUNCTION_2008
364 elif function_level
== "2008_R2":
365 dom_for_fun_level
= DS_DOMAIN_FUNCTION_2008_R2
367 if dns_backend
== "SAMBA_INTERNAL" and dns_forwarder
is None:
368 dns_forwarder
= suggested_forwarder
370 samdb_fill
= FILL_FULL
372 samdb_fill
= FILL_NT4SYNC
373 elif partitions_only
:
374 samdb_fill
= FILL_DRS
376 if targetdir
is not None:
377 if not os
.path
.isdir(targetdir
):
382 if use_xattrs
== "yes":
384 elif use_xattrs
== "auto" and not lp
.get("posix:eadb"):
386 file = tempfile
.NamedTemporaryFile(dir=os
.path
.abspath(targetdir
))
388 file = tempfile
.NamedTemporaryFile(dir=os
.path
.abspath(os
.path
.dirname(lp
.get("private dir"))))
391 samba
.ntacls
.setntacl(lp
, file.name
,
392 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
395 self
.logger
.info("You are not root or your system do not support xattr, using tdb backend for attributes. ")
400 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.")
401 if ldap_backend_type
== "existing":
402 if ldap_backend_forced_uri
is not None:
403 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
)
405 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")
407 if ldap_backend_forced_uri
is not None:
408 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")
410 if domain_sid
is not None:
411 domain_sid
= security
.dom_sid(domain_sid
)
413 session
= system_session()
415 result
= provision(self
.logger
,
416 session
, smbconf
=smbconf
, targetdir
=targetdir
,
417 samdb_fill
=samdb_fill
, realm
=realm
, domain
=domain
,
418 domainguid
=domain_guid
, domainsid
=domain_sid
,
420 hostip
=host_ip
, hostip6
=host_ip6
,
421 sitename
=site
, ntdsguid
=ntds_guid
,
422 invocationid
=invocationid
, adminpass
=adminpass
,
423 krbtgtpass
=krbtgtpass
, machinepass
=machinepass
,
424 dns_backend
=dns_backend
, dns_forwarder
=dns_forwarder
,
425 dnspass
=dnspass
, root
=root
, nobody
=nobody
,
427 serverrole
=server_role
, dom_for_fun_level
=dom_for_fun_level
,
428 backend_type
=ldap_backend_type
,
429 ldapadminpass
=ldapadminpass
, ol_mmr_urls
=ol_mmr_urls
, slapd_path
=slapd_path
,
430 useeadb
=eadb
, next_rid
=next_rid
, lp
=lp
, use_ntvfs
=use_ntvfs
,
431 use_rfc2307
=use_rfc2307
, skip_sysvolacl
=False,
432 ldap_backend_extra_port
=ldap_backend_extra_port
,
433 ldap_backend_forced_uri
=ldap_backend_forced_uri
,
434 nosync
=ldap_backend_nosync
, ldap_dryrun_mode
=ldap_dryrun_mode
)
436 except ProvisioningError
, e
:
437 raise CommandError("Provision failed", e
)
439 result
.report_logger(self
.logger
)
441 def _get_nameserver_ip(self
):
442 """Grab the nameserver IP address from /etc/resolv.conf."""
444 RESOLV_CONF
="/etc/resolv.conf"
446 if not path
.isfile(RESOLV_CONF
):
447 self
.logger
.warning("Failed to locate %s" % RESOLV_CONF
)
452 handle
= open(RESOLV_CONF
, 'r')
454 if not line
.startswith('nameserver'):
456 # we want the last non-space continuous string of the line
457 return line
.strip().split()[-1]
459 if handle
is not None:
462 self
.logger
.warning("No nameserver found in %s" % RESOLV_CONF
)
465 class cmd_domain_dcpromo(Command
):
466 """Promote an existing domain member or NT4 PDC to an AD DC."""
468 synopsis
= "%prog <dnsdomain> [DC|RODC] [options]"
470 takes_optiongroups
= {
471 "sambaopts": options
.SambaOptions
,
472 "versionopts": options
.VersionOptions
,
473 "credopts": options
.CredentialsOptions
,
477 Option("--server", help="DC to join", type=str),
478 Option("--site", help="site to join", type=str),
479 Option("--targetdir", help="where to store provision", type=str),
480 Option("--domain-critical-only",
481 help="only replicate critical domain objects",
482 action
="store_true"),
483 Option("--machinepass", type=str, metavar
="PASSWORD",
484 help="choose machine password (otherwise random)"),
485 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
486 action
="store_true"),
487 Option("--dns-backend", type="choice", metavar
="NAMESERVER-BACKEND",
488 choices
=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
489 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
490 "BIND9_DLZ uses samba4 AD to store zone information, "
491 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
492 default
="SAMBA_INTERNAL"),
493 Option("--quiet", help="Be quiet", action
="store_true"),
494 Option("--verbose", help="Be verbose", action
="store_true")
497 takes_args
= ["domain", "role?"]
499 def run(self
, domain
, role
=None, sambaopts
=None, credopts
=None,
500 versionopts
=None, server
=None, site
=None, targetdir
=None,
501 domain_critical_only
=False, parent_domain
=None, machinepass
=None,
502 use_ntvfs
=False, dns_backend
=None,
503 quiet
=False, verbose
=False):
504 lp
= sambaopts
.get_loadparm()
505 creds
= credopts
.get_credentials(lp
)
506 net
= Net(creds
, lp
, server
=credopts
.ipaddress
)
509 site
= "Default-First-Site-Name"
511 logger
= self
.get_logger()
513 logger
.setLevel(logging
.DEBUG
)
515 logger
.setLevel(logging
.WARNING
)
517 logger
.setLevel(logging
.INFO
)
519 netbios_name
= lp
.get("netbios name")
525 join_DC(logger
=logger
, server
=server
, creds
=creds
, lp
=lp
, domain
=domain
,
526 site
=site
, netbios_name
=netbios_name
, targetdir
=targetdir
,
527 domain_critical_only
=domain_critical_only
,
528 machinepass
=machinepass
, use_ntvfs
=use_ntvfs
,
529 dns_backend
=dns_backend
,
530 promote_existing
=True)
532 join_RODC(logger
=logger
, server
=server
, creds
=creds
, lp
=lp
, domain
=domain
,
533 site
=site
, netbios_name
=netbios_name
, targetdir
=targetdir
,
534 domain_critical_only
=domain_critical_only
,
535 machinepass
=machinepass
, use_ntvfs
=use_ntvfs
, dns_backend
=dns_backend
,
536 promote_existing
=True)
538 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role
)
541 class cmd_domain_join(Command
):
542 """Join domain as either member or backup domain controller."""
544 synopsis
= "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
546 takes_optiongroups
= {
547 "sambaopts": options
.SambaOptions
,
548 "versionopts": options
.VersionOptions
,
549 "credopts": options
.CredentialsOptions
,
553 Option("--server", help="DC to join", type=str),
554 Option("--site", help="site to join", type=str),
555 Option("--targetdir", help="where to store provision", type=str),
556 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
557 Option("--domain-critical-only",
558 help="only replicate critical domain objects",
559 action
="store_true"),
560 Option("--machinepass", type=str, metavar
="PASSWORD",
561 help="choose machine password (otherwise random)"),
562 Option("--adminpass", type="string", metavar
="PASSWORD",
563 help="choose adminstrator password when joining as a subdomain (otherwise random)"),
564 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
565 action
="store_true"),
566 Option("--dns-backend", type="choice", metavar
="NAMESERVER-BACKEND",
567 choices
=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
568 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
569 "BIND9_DLZ uses samba4 AD to store zone information, "
570 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
571 default
="SAMBA_INTERNAL"),
572 Option("--quiet", help="Be quiet", action
="store_true"),
573 Option("--verbose", help="Be verbose", action
="store_true")
576 takes_args
= ["domain", "role?"]
578 def run(self
, domain
, role
=None, sambaopts
=None, credopts
=None,
579 versionopts
=None, server
=None, site
=None, targetdir
=None,
580 domain_critical_only
=False, parent_domain
=None, machinepass
=None,
581 use_ntvfs
=False, dns_backend
=None, adminpass
=None,
582 quiet
=False, verbose
=False):
583 lp
= sambaopts
.get_loadparm()
584 creds
= credopts
.get_credentials(lp
)
585 net
= Net(creds
, lp
, server
=credopts
.ipaddress
)
588 site
= "Default-First-Site-Name"
590 logger
= self
.get_logger()
592 logger
.setLevel(logging
.DEBUG
)
594 logger
.setLevel(logging
.WARNING
)
596 logger
.setLevel(logging
.INFO
)
598 netbios_name
= lp
.get("netbios name")
603 if role
is None or role
== "MEMBER":
604 (join_password
, sid
, domain_name
) = net
.join_member(
605 domain
, netbios_name
, LIBNET_JOIN_AUTOMATIC
,
606 machinepass
=machinepass
)
608 self
.errf
.write("Joined domain %s (%s)\n" % (domain_name
, sid
))
610 join_DC(logger
=logger
, server
=server
, creds
=creds
, lp
=lp
, domain
=domain
,
611 site
=site
, netbios_name
=netbios_name
, targetdir
=targetdir
,
612 domain_critical_only
=domain_critical_only
,
613 machinepass
=machinepass
, use_ntvfs
=use_ntvfs
, dns_backend
=dns_backend
)
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
,
619 dns_backend
=dns_backend
)
620 elif role
== "SUBDOMAIN":
622 logger
.info("Administrator password will be set randomly!")
624 netbios_domain
= lp
.get("workgroup")
625 if parent_domain
is None:
626 parent_domain
= ".".join(domain
.split(".")[1:])
627 join_subdomain(logger
=logger
, server
=server
, creds
=creds
, lp
=lp
, dnsdomain
=domain
,
628 parent_domain
=parent_domain
, site
=site
,
629 netbios_name
=netbios_name
, netbios_domain
=netbios_domain
,
630 targetdir
=targetdir
, machinepass
=machinepass
,
631 use_ntvfs
=use_ntvfs
, dns_backend
=dns_backend
,
634 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role
)
637 class cmd_domain_demote(Command
):
638 """Demote ourselves from the role of Domain Controller."""
640 synopsis
= "%prog [options]"
643 Option("--server", help="DC to force replication before demote", type=str),
644 Option("--targetdir", help="where provision is stored", type=str),
647 takes_optiongroups
= {
648 "sambaopts": options
.SambaOptions
,
649 "credopts": options
.CredentialsOptions
,
650 "versionopts": options
.VersionOptions
,
653 def run(self
, sambaopts
=None, credopts
=None,
654 versionopts
=None, server
=None, targetdir
=None):
655 lp
= sambaopts
.get_loadparm()
656 creds
= credopts
.get_credentials(lp
)
657 net
= Net(creds
, lp
, server
=credopts
.ipaddress
)
659 netbios_name
= lp
.get("netbios name")
660 samdb
= SamDB(session_info
=system_session(), credentials
=creds
, lp
=lp
)
662 res
= samdb
.search(expression
='(&(objectClass=computer)(serverReferenceBL=*))', attrs
=["dnsHostName", "name"])
664 raise CommandError("Unable to search for servers")
667 raise CommandError("You are the latest server in the domain")
671 if str(e
["name"]).lower() != netbios_name
.lower():
672 server
= e
["dnsHostName"]
675 ntds_guid
= samdb
.get_ntds_GUID()
676 msg
= samdb
.search(base
=str(samdb
.get_config_basedn()),
677 scope
=ldb
.SCOPE_SUBTREE
, expression
="(objectGUID=%s)" % ntds_guid
,
679 if len(msg
) == 0 or "options" not in msg
[0]:
680 raise CommandError("Failed to find options on %s" % ntds_guid
)
683 dsa_options
= int(str(msg
[0]['options']))
685 res
= samdb
.search(expression
="(fSMORoleOwner=%s)" % str(ntds_dn
),
686 controls
=["search_options:1:2"])
689 raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res
))
691 self
.errf
.write("Using %s as partner server for the demotion\n" %
693 (drsuapiBind
, drsuapi_handle
, supportedExtensions
) = drsuapi_connect(server
, lp
, creds
)
695 self
.errf
.write("Deactivating inbound replication\n")
700 dsa_options |
= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
701 nmsg
["options"] = ldb
.MessageElement(str(dsa_options
), ldb
.FLAG_MOD_REPLACE
, "options")
704 if not (dsa_options
& DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL
) and not samdb
.am_rodc():
706 self
.errf
.write("Asking partner server %s to synchronize from us\n"
708 for part
in (samdb
.get_schema_basedn(),
709 samdb
.get_config_basedn(),
710 samdb
.get_root_basedn()):
712 sendDsReplicaSync(drsuapiBind
, drsuapi_handle
, ntds_guid
, str(part
), drsuapi
.DRSUAPI_DRS_WRIT_REP
)
713 except drsException
, e
:
715 "Error while demoting, "
716 "re-enabling inbound replication\n")
717 dsa_options ^
= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
718 nmsg
["options"] = ldb
.MessageElement(str(dsa_options
), ldb
.FLAG_MOD_REPLACE
, "options")
720 raise CommandError("Error while sending a DsReplicaSync for partion %s" % str(part
), e
)
722 remote_samdb
= SamDB(url
="ldap://%s" % server
,
723 session_info
=system_session(),
724 credentials
=creds
, lp
=lp
)
726 self
.errf
.write("Changing userControl and container\n")
727 res
= remote_samdb
.search(base
=str(remote_samdb
.get_root_basedn()),
728 expression
="(&(objectClass=user)(sAMAccountName=%s$))" %
729 netbios_name
.upper(),
730 attrs
=["userAccountControl"])
732 uac
= int(str(res
[0]["userAccountControl"]))
736 "Error while demoting, re-enabling inbound replication\n")
737 dsa_options ^
= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
738 nmsg
["options"] = ldb
.MessageElement(str(dsa_options
), ldb
.FLAG_MOD_REPLACE
, "options")
740 raise CommandError("Error while changing account control", e
)
744 "Error while demoting, re-enabling inbound replication")
745 dsa_options ^
= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
746 nmsg
["options"] = ldb
.MessageElement(str(dsa_options
), ldb
.FLAG_MOD_REPLACE
, "options")
748 raise CommandError("Unable to find object with samaccountName = %s$"
749 " in the remote dc" % netbios_name
.upper())
753 uac ^
= (UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION
)
754 uac |
= UF_WORKSTATION_TRUST_ACCOUNT
759 msg
["userAccountControl"] = ldb
.MessageElement("%d" % uac
,
760 ldb
.FLAG_MOD_REPLACE
,
761 "userAccountControl")
763 remote_samdb
.modify(msg
)
766 "Error while demoting, re-enabling inbound replication")
767 dsa_options ^
= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
768 nmsg
["options"] = ldb
.MessageElement(str(dsa_options
), ldb
.FLAG_MOD_REPLACE
, "options")
771 raise CommandError("Error while changing account control", e
)
773 parent
= msg
.dn
.parent()
775 rdn
= string
.replace(rdn
, ",%s" % str(parent
), "")
776 # Let's move to the Computer container
780 computer_dn
= ldb
.Dn(remote_samdb
, "CN=Computers,%s" % str(remote_samdb
.get_root_basedn()))
781 res
= remote_samdb
.search(base
=computer_dn
, expression
=rdn
, scope
=ldb
.SCOPE_ONELEVEL
)
784 res
= remote_samdb
.search(base
=computer_dn
, expression
="%s-%d" % (rdn
, i
),
785 scope
=ldb
.SCOPE_ONELEVEL
)
786 while(len(res
) != 0 and i
< 100):
788 res
= remote_samdb
.search(base
=computer_dn
, expression
="%s-%d" % (rdn
, i
),
789 scope
=ldb
.SCOPE_ONELEVEL
)
793 "Error while demoting, re-enabling inbound replication\n")
794 dsa_options ^
= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
795 nmsg
["options"] = ldb
.MessageElement(str(dsa_options
), ldb
.FLAG_MOD_REPLACE
, "options")
801 msg
["userAccountControl"] = ldb
.MessageElement("%d" % uac
,
802 ldb
.FLAG_MOD_REPLACE
,
803 "userAccountControl")
805 remote_samdb
.modify(msg
)
807 raise CommandError("Unable to find a slot for renaming %s,"
808 " all names from %s-1 to %s-%d seemed used" %
809 (str(dc_dn
), rdn
, rdn
, i
- 9))
811 newrdn
= "%s-%d" % (rdn
, i
)
814 newdn
= ldb
.Dn(remote_samdb
, "%s,%s" % (newrdn
, str(computer_dn
)))
815 remote_samdb
.rename(dc_dn
, newdn
)
818 "Error while demoting, re-enabling inbound replication\n")
819 dsa_options ^
= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
820 nmsg
["options"] = ldb
.MessageElement(str(dsa_options
), ldb
.FLAG_MOD_REPLACE
, "options")
826 msg
["userAccountControl"] = ldb
.MessageElement("%d" % uac
,
827 ldb
.FLAG_MOD_REPLACE
,
828 "userAccountControl")
830 remote_samdb
.modify(msg
)
831 raise CommandError("Error while renaming %s to %s" % (str(dc_dn
), str(newdn
)), e
)
834 server_dsa_dn
= samdb
.get_serverName()
835 domain
= remote_samdb
.get_root_basedn()
838 sendRemoveDsServer(drsuapiBind
, drsuapi_handle
, server_dsa_dn
, domain
)
839 except drsException
, e
:
841 "Error while demoting, re-enabling inbound replication\n")
842 dsa_options ^
= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
843 nmsg
["options"] = ldb
.MessageElement(str(dsa_options
), ldb
.FLAG_MOD_REPLACE
, "options")
849 msg
["userAccountControl"] = ldb
.MessageElement("%d" % uac
,
850 ldb
.FLAG_MOD_REPLACE
,
851 "userAccountControl")
853 remote_samdb
.modify(msg
)
854 remote_samdb
.rename(newdn
, dc_dn
)
855 raise CommandError("Error while sending a removeDsServer", e
)
857 for s
in ("CN=Enterprise,CN=Microsoft System Volumes,CN=System,CN=Configuration",
858 "CN=%s,CN=Microsoft System Volumes,CN=System,CN=Configuration" % lp
.get("realm"),
859 "CN=Domain System Volumes (SYSVOL share),CN=File Replication Service,CN=System"):
861 remote_samdb
.delete(ldb
.Dn(remote_samdb
,
862 "%s,%s,%s" % (str(rdn
), s
, str(remote_samdb
.get_root_basedn()))))
863 except ldb
.LdbError
, l
:
866 for s
in ("CN=Enterprise,CN=NTFRS Subscriptions",
867 "CN=%s, CN=NTFRS Subscriptions" % lp
.get("realm"),
868 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
869 "CN=NTFRS Subscriptions"):
871 remote_samdb
.delete(ldb
.Dn(remote_samdb
,
872 "%s,%s" % (s
, str(newdn
))))
873 except ldb
.LdbError
, l
:
876 self
.errf
.write("Demote successful\n")
879 class cmd_domain_level(Command
):
880 """Raise domain and forest function levels."""
882 synopsis
= "%prog (show|raise <options>) [options]"
884 takes_optiongroups
= {
885 "sambaopts": options
.SambaOptions
,
886 "credopts": options
.CredentialsOptions
,
887 "versionopts": options
.VersionOptions
,
891 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
892 metavar
="URL", dest
="H"),
893 Option("--quiet", help="Be quiet", action
="store_true"),
894 Option("--forest-level", type="choice", choices
=["2003", "2008", "2008_R2"],
895 help="The forest function level (2003 | 2008 | 2008_R2)"),
896 Option("--domain-level", type="choice", choices
=["2003", "2008", "2008_R2"],
897 help="The domain function level (2003 | 2008 | 2008_R2)")
900 takes_args
= ["subcommand"]
902 def run(self
, subcommand
, H
=None, forest_level
=None, domain_level
=None,
903 quiet
=False, credopts
=None, sambaopts
=None, versionopts
=None):
904 lp
= sambaopts
.get_loadparm()
905 creds
= credopts
.get_credentials(lp
, fallback_machine
=True)
907 samdb
= SamDB(url
=H
, session_info
=system_session(),
908 credentials
=creds
, lp
=lp
)
910 domain_dn
= samdb
.domain_dn()
912 res_forest
= samdb
.search("CN=Partitions,%s" % samdb
.get_config_basedn(),
913 scope
=ldb
.SCOPE_BASE
, attrs
=["msDS-Behavior-Version"])
914 assert len(res_forest
) == 1
916 res_domain
= samdb
.search(domain_dn
, scope
=ldb
.SCOPE_BASE
,
917 attrs
=["msDS-Behavior-Version", "nTMixedDomain"])
918 assert len(res_domain
) == 1
920 res_dc_s
= samdb
.search("CN=Sites,%s" % samdb
.get_config_basedn(),
921 scope
=ldb
.SCOPE_SUBTREE
, expression
="(objectClass=nTDSDSA)",
922 attrs
=["msDS-Behavior-Version"])
923 assert len(res_dc_s
) >= 1
926 level_forest
= int(res_forest
[0]["msDS-Behavior-Version"][0])
927 level_domain
= int(res_domain
[0]["msDS-Behavior-Version"][0])
928 level_domain_mixed
= int(res_domain
[0]["nTMixedDomain"][0])
930 min_level_dc
= int(res_dc_s
[0]["msDS-Behavior-Version"][0]) # Init value
932 if int(msg
["msDS-Behavior-Version"][0]) < min_level_dc
:
933 min_level_dc
= int(msg
["msDS-Behavior-Version"][0])
935 if level_forest
< 0 or level_domain
< 0:
936 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
938 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
939 if level_forest
> level_domain
:
940 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
941 if level_domain
> min_level_dc
:
942 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
945 raise CommandError("Could not retrieve the actual domain, forest level and/or lowest DC function level!")
947 if subcommand
== "show":
948 self
.message("Domain and forest function level for domain '%s'" % domain_dn
)
949 if level_forest
== DS_DOMAIN_FUNCTION_2000
and level_domain_mixed
!= 0:
950 self
.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
951 if level_domain
== DS_DOMAIN_FUNCTION_2000
and level_domain_mixed
!= 0:
952 self
.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
953 if min_level_dc
== DS_DOMAIN_FUNCTION_2000
and level_domain_mixed
!= 0:
954 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)!")
958 if level_forest
== DS_DOMAIN_FUNCTION_2000
:
960 elif level_forest
== DS_DOMAIN_FUNCTION_2003_MIXED
:
961 outstr
= "2003 with mixed domains/interim (NT4 DC support)"
962 elif level_forest
== DS_DOMAIN_FUNCTION_2003
:
964 elif level_forest
== DS_DOMAIN_FUNCTION_2008
:
966 elif level_forest
== DS_DOMAIN_FUNCTION_2008_R2
:
969 outstr
= "higher than 2008 R2"
970 self
.message("Forest function level: (Windows) " + outstr
)
972 if level_domain
== DS_DOMAIN_FUNCTION_2000
and level_domain_mixed
!= 0:
973 outstr
= "2000 mixed (NT4 DC support)"
974 elif level_domain
== DS_DOMAIN_FUNCTION_2000
and level_domain_mixed
== 0:
976 elif level_domain
== DS_DOMAIN_FUNCTION_2003_MIXED
:
977 outstr
= "2003 with mixed domains/interim (NT4 DC support)"
978 elif level_domain
== DS_DOMAIN_FUNCTION_2003
:
980 elif level_domain
== DS_DOMAIN_FUNCTION_2008
:
982 elif level_domain
== DS_DOMAIN_FUNCTION_2008_R2
:
985 outstr
= "higher than 2008 R2"
986 self
.message("Domain function level: (Windows) " + outstr
)
988 if min_level_dc
== DS_DOMAIN_FUNCTION_2000
:
990 elif min_level_dc
== DS_DOMAIN_FUNCTION_2003
:
992 elif min_level_dc
== DS_DOMAIN_FUNCTION_2008
:
994 elif min_level_dc
== DS_DOMAIN_FUNCTION_2008_R2
:
997 outstr
= "higher than 2008 R2"
998 self
.message("Lowest function level of a DC: (Windows) " + outstr
)
1000 elif subcommand
== "raise":
1003 if domain_level
is not None:
1004 if domain_level
== "2003":
1005 new_level_domain
= DS_DOMAIN_FUNCTION_2003
1006 elif domain_level
== "2008":
1007 new_level_domain
= DS_DOMAIN_FUNCTION_2008
1008 elif domain_level
== "2008_R2":
1009 new_level_domain
= DS_DOMAIN_FUNCTION_2008_R2
1011 if new_level_domain
<= level_domain
and level_domain_mixed
== 0:
1012 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
1014 if new_level_domain
> min_level_dc
:
1015 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
1017 # Deactivate mixed/interim domain support
1018 if level_domain_mixed
!= 0:
1019 # Directly on the base DN
1021 m
.dn
= ldb
.Dn(samdb
, domain_dn
)
1022 m
["nTMixedDomain"] = ldb
.MessageElement("0",
1023 ldb
.FLAG_MOD_REPLACE
, "nTMixedDomain")
1027 m
.dn
= ldb
.Dn(samdb
, "CN=" + lp
.get("workgroup") + ",CN=Partitions,%s" % samdb
.get_config_basedn())
1028 m
["nTMixedDomain"] = ldb
.MessageElement("0",
1029 ldb
.FLAG_MOD_REPLACE
, "nTMixedDomain")
1032 except ldb
.LdbError
, (enum
, emsg
):
1033 if enum
!= ldb
.ERR_UNWILLING_TO_PERFORM
:
1036 # Directly on the base DN
1038 m
.dn
= ldb
.Dn(samdb
, domain_dn
)
1039 m
["msDS-Behavior-Version"]= ldb
.MessageElement(
1040 str(new_level_domain
), ldb
.FLAG_MOD_REPLACE
,
1041 "msDS-Behavior-Version")
1045 m
.dn
= ldb
.Dn(samdb
, "CN=" + lp
.get("workgroup")
1046 + ",CN=Partitions,%s" % samdb
.get_config_basedn())
1047 m
["msDS-Behavior-Version"]= ldb
.MessageElement(
1048 str(new_level_domain
), ldb
.FLAG_MOD_REPLACE
,
1049 "msDS-Behavior-Version")
1052 except ldb
.LdbError
, (enum
, emsg
):
1053 if enum
!= ldb
.ERR_UNWILLING_TO_PERFORM
:
1056 level_domain
= new_level_domain
1057 msgs
.append("Domain function level changed!")
1059 if forest_level
is not None:
1060 if forest_level
== "2003":
1061 new_level_forest
= DS_DOMAIN_FUNCTION_2003
1062 elif forest_level
== "2008":
1063 new_level_forest
= DS_DOMAIN_FUNCTION_2008
1064 elif forest_level
== "2008_R2":
1065 new_level_forest
= DS_DOMAIN_FUNCTION_2008_R2
1066 if new_level_forest
<= level_forest
:
1067 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1068 if new_level_forest
> level_domain
:
1069 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1071 m
.dn
= ldb
.Dn(samdb
, "CN=Partitions,%s" % samdb
.get_config_basedn())
1072 m
["msDS-Behavior-Version"]= ldb
.MessageElement(
1073 str(new_level_forest
), ldb
.FLAG_MOD_REPLACE
,
1074 "msDS-Behavior-Version")
1076 msgs
.append("Forest function level changed!")
1077 msgs
.append("All changes applied successfully!")
1078 self
.message("\n".join(msgs
))
1080 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand
)
1083 class cmd_domain_passwordsettings(Command
):
1084 """Set password settings.
1086 Password complexity, password lockout policy, history length,
1087 minimum password length, the minimum and maximum password age) on
1088 a Samba AD DC server.
1090 Use against a Windows DC is possible, but group policy will override it.
1093 synopsis
= "%prog (show|set <options>) [options]"
1095 takes_optiongroups
= {
1096 "sambaopts": options
.SambaOptions
,
1097 "versionopts": options
.VersionOptions
,
1098 "credopts": options
.CredentialsOptions
,
1102 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1103 metavar
="URL", dest
="H"),
1104 Option("--quiet", help="Be quiet", action
="store_true"),
1105 Option("--complexity", type="choice", choices
=["on","off","default"],
1106 help="The password complexity (on | off | default). Default is 'on'"),
1107 Option("--store-plaintext", type="choice", choices
=["on","off","default"],
1108 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1109 Option("--history-length",
1110 help="The password history length (<integer> | default). Default is 24.", type=str),
1111 Option("--min-pwd-length",
1112 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1113 Option("--min-pwd-age",
1114 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1115 Option("--max-pwd-age",
1116 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1117 Option("--account-lockout-duration",
1118 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),
1119 Option("--account-lockout-threshold",
1120 help="The number of bad password attempts allowed before locking out the account (<integer> | default). Default is 0 (never lock out).", type=str),
1121 Option("--reset-account-lockout-after",
1122 help="After this time is elapsed, the recorded number of attempts restarts from zero (<integer> | default). Default is 30.", type=str),
1125 takes_args
= ["subcommand"]
1127 def run(self
, subcommand
, H
=None, min_pwd_age
=None, max_pwd_age
=None,
1128 quiet
=False, complexity
=None, store_plaintext
=None, history_length
=None,
1129 min_pwd_length
=None, account_lockout_duration
=None, account_lockout_threshold
=None,
1130 reset_account_lockout_after
=None, credopts
=None, sambaopts
=None,
1132 lp
= sambaopts
.get_loadparm()
1133 creds
= credopts
.get_credentials(lp
)
1135 samdb
= SamDB(url
=H
, session_info
=system_session(),
1136 credentials
=creds
, lp
=lp
)
1138 domain_dn
= samdb
.domain_dn()
1139 res
= samdb
.search(domain_dn
, scope
=ldb
.SCOPE_BASE
,
1140 attrs
=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1141 "minPwdAge", "maxPwdAge", "lockoutDuration", "lockoutThreshold",
1142 "lockOutObservationWindow"])
1143 assert(len(res
) == 1)
1145 pwd_props
= int(res
[0]["pwdProperties"][0])
1146 pwd_hist_len
= int(res
[0]["pwdHistoryLength"][0])
1147 cur_min_pwd_len
= int(res
[0]["minPwdLength"][0])
1149 cur_min_pwd_age
= int(abs(int(res
[0]["minPwdAge"][0])) / (1e7
* 60 * 60 * 24))
1150 if int(res
[0]["maxPwdAge"][0]) == -0x8000000000000000:
1153 cur_max_pwd_age
= int(abs(int(res
[0]["maxPwdAge"][0])) / (1e7
* 60 * 60 * 24))
1154 cur_account_lockout_threshold
= int(res
[0]["lockoutThreshold"][0])
1156 if int(res
[0]["lockoutDuration"][0]) == -0x8000000000000000:
1157 cur_account_lockout_duration
= 0
1159 cur_account_lockout_duration
= abs(int(res
[0]["lockoutDuration"][0])) / (1e7
* 60)
1160 cur_reset_account_lockout_after
= abs(int(res
[0]["lockOutObservationWindow"][0])) / (1e7
* 60)
1161 except Exception, e
:
1162 raise CommandError("Could not retrieve password properties!", e
)
1164 if subcommand
== "show":
1165 self
.message("Password informations for domain '%s'" % domain_dn
)
1167 if pwd_props
& DOMAIN_PASSWORD_COMPLEX
!= 0:
1168 self
.message("Password complexity: on")
1170 self
.message("Password complexity: off")
1171 if pwd_props
& DOMAIN_PASSWORD_STORE_CLEARTEXT
!= 0:
1172 self
.message("Store plaintext passwords: on")
1174 self
.message("Store plaintext passwords: off")
1175 self
.message("Password history length: %d" % pwd_hist_len
)
1176 self
.message("Minimum password length: %d" % cur_min_pwd_len
)
1177 self
.message("Minimum password age (days): %d" % cur_min_pwd_age
)
1178 self
.message("Maximum password age (days): %d" % cur_max_pwd_age
)
1179 self
.message("Account lockout duration (mins): %d" % cur_account_lockout_duration
)
1180 self
.message("Account lockout threshold (attempts): %d" % cur_account_lockout_threshold
)
1181 self
.message("Reset account lockout after (mins): %d" % cur_reset_account_lockout_after
)
1182 elif subcommand
== "set":
1185 m
.dn
= ldb
.Dn(samdb
, domain_dn
)
1187 if complexity
is not None:
1188 if complexity
== "on" or complexity
== "default":
1189 pwd_props
= pwd_props | DOMAIN_PASSWORD_COMPLEX
1190 msgs
.append("Password complexity activated!")
1191 elif complexity
== "off":
1192 pwd_props
= pwd_props
& (~DOMAIN_PASSWORD_COMPLEX
)
1193 msgs
.append("Password complexity deactivated!")
1195 if store_plaintext
is not None:
1196 if store_plaintext
== "on" or store_plaintext
== "default":
1197 pwd_props
= pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1198 msgs
.append("Plaintext password storage for changed passwords activated!")
1199 elif store_plaintext
== "off":
1200 pwd_props
= pwd_props
& (~DOMAIN_PASSWORD_STORE_CLEARTEXT
)
1201 msgs
.append("Plaintext password storage for changed passwords deactivated!")
1203 if complexity
is not None or store_plaintext
is not None:
1204 m
["pwdProperties"] = ldb
.MessageElement(str(pwd_props
),
1205 ldb
.FLAG_MOD_REPLACE
, "pwdProperties")
1207 if history_length
is not None:
1208 if history_length
== "default":
1211 pwd_hist_len
= int(history_length
)
1213 if pwd_hist_len
< 0 or pwd_hist_len
> 24:
1214 raise CommandError("Password history length must be in the range of 0 to 24!")
1216 m
["pwdHistoryLength"] = ldb
.MessageElement(str(pwd_hist_len
),
1217 ldb
.FLAG_MOD_REPLACE
, "pwdHistoryLength")
1218 msgs
.append("Password history length changed!")
1220 if min_pwd_length
is not None:
1221 if min_pwd_length
== "default":
1224 min_pwd_len
= int(min_pwd_length
)
1226 if min_pwd_len
< 0 or min_pwd_len
> 14:
1227 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1229 m
["minPwdLength"] = ldb
.MessageElement(str(min_pwd_len
),
1230 ldb
.FLAG_MOD_REPLACE
, "minPwdLength")
1231 msgs
.append("Minimum password length changed!")
1233 if min_pwd_age
is not None:
1234 if min_pwd_age
== "default":
1237 min_pwd_age
= int(min_pwd_age
)
1239 if min_pwd_age
< 0 or min_pwd_age
> 998:
1240 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1243 min_pwd_age_ticks
= -int(min_pwd_age
* (24 * 60 * 60 * 1e7
))
1245 m
["minPwdAge"] = ldb
.MessageElement(str(min_pwd_age_ticks
),
1246 ldb
.FLAG_MOD_REPLACE
, "minPwdAge")
1247 msgs
.append("Minimum password age changed!")
1249 if max_pwd_age
is not None:
1250 if max_pwd_age
== "default":
1253 max_pwd_age
= int(max_pwd_age
)
1255 if max_pwd_age
< 0 or max_pwd_age
> 999:
1256 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1259 if max_pwd_age
== 0:
1260 max_pwd_age_ticks
= -0x8000000000000000
1262 max_pwd_age_ticks
= -int(max_pwd_age
* (24 * 60 * 60 * 1e7
))
1264 m
["maxPwdAge"] = ldb
.MessageElement(str(max_pwd_age_ticks
),
1265 ldb
.FLAG_MOD_REPLACE
, "maxPwdAge")
1266 msgs
.append("Maximum password age changed!")
1268 if account_lockout_duration
is not None:
1269 if account_lockout_duration
== "default":
1270 account_lockout_duration
= 30
1272 account_lockout_duration
= int(account_lockout_duration
)
1274 if account_lockout_duration
< 0 or account_lockout_duration
> 99999:
1275 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1278 if account_lockout_duration
== 0:
1279 account_lockout_duration_ticks
= -0x8000000000000000
1281 account_lockout_duration_ticks
= -int(account_lockout_duration
* (60 * 1e7
))
1283 m
["lockoutDuration"] = ldb
.MessageElement(str(account_lockout_duration_ticks
),
1284 ldb
.FLAG_MOD_REPLACE
, "lockoutDuration")
1285 msgs
.append("Account lockout duration changed!")
1287 if account_lockout_threshold
is not None:
1288 if account_lockout_threshold
== "default":
1289 account_lockout_threshold
= 0
1291 account_lockout_threshold
= int(account_lockout_threshold
)
1293 m
["lockoutThreshold"] = ldb
.MessageElement(str(account_lockout_threshold
),
1294 ldb
.FLAG_MOD_REPLACE
, "lockoutThreshold")
1295 msgs
.append("Account lockout threshold changed!")
1297 if reset_account_lockout_after
is not None:
1298 if reset_account_lockout_after
== "default":
1299 reset_account_lockout_after
= 30
1301 reset_account_lockout_after
= int(reset_account_lockout_after
)
1303 if reset_account_lockout_after
< 0 or reset_account_lockout_after
> 99999:
1304 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1307 if reset_account_lockout_after
== 0:
1308 reset_account_lockout_after_ticks
= -0x8000000000000000
1310 reset_account_lockout_after_ticks
= -int(reset_account_lockout_after
* (60 * 1e7
))
1312 m
["lockOutObservationWindow"] = ldb
.MessageElement(str(reset_account_lockout_after_ticks
),
1313 ldb
.FLAG_MOD_REPLACE
, "lockOutObservationWindow")
1314 msgs
.append("Duration to reset account lockout after changed!")
1316 if max_pwd_age
> 0 and min_pwd_age
>= max_pwd_age
:
1317 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age
, min_pwd_age
))
1320 raise CommandError("You must specify at least one option to set. Try --help")
1322 msgs
.append("All changes applied successfully!")
1323 self
.message("\n".join(msgs
))
1325 raise CommandError("Wrong argument '%s'!" % subcommand
)
1328 class cmd_domain_classicupgrade(Command
):
1329 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1331 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1332 the testparm utility from your classic installation (with --testparm).
1335 synopsis
= "%prog [options] <classic_smb_conf>"
1337 takes_optiongroups
= {
1338 "sambaopts": options
.SambaOptions
,
1339 "versionopts": options
.VersionOptions
1343 Option("--dbdir", type="string", metavar
="DIR",
1344 help="Path to samba classic DC database directory"),
1345 Option("--testparm", type="string", metavar
="PATH",
1346 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1347 Option("--targetdir", type="string", metavar
="DIR",
1348 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1349 Option("--quiet", help="Be quiet", action
="store_true"),
1350 Option("--verbose", help="Be verbose", action
="store_true"),
1351 Option("--use-xattrs", type="choice", choices
=["yes","no","auto"], metavar
="[yes|no|auto]",
1352 help="Define if we should use the native fs capabilities or a tdb file for storing attributes likes ntacl, auto tries to make an inteligent guess based on the user rights and system capabilities", default
="auto"),
1353 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
1354 action
="store_true"),
1355 Option("--dns-backend", type="choice", metavar
="NAMESERVER-BACKEND",
1356 choices
=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1357 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1358 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1359 "BIND9_DLZ uses samba4 AD to store zone information, "
1360 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1361 default
="SAMBA_INTERNAL")
1364 takes_args
= ["smbconf"]
1366 def run(self
, smbconf
=None, targetdir
=None, dbdir
=None, testparm
=None,
1367 quiet
=False, verbose
=False, use_xattrs
=None, sambaopts
=None, versionopts
=None,
1368 dns_backend
=None, use_ntvfs
=False):
1370 if not os
.path
.exists(smbconf
):
1371 raise CommandError("File %s does not exist" % smbconf
)
1373 if testparm
and not os
.path
.exists(testparm
):
1374 raise CommandError("Testparm utility %s does not exist" % testparm
)
1376 if dbdir
and not os
.path
.exists(dbdir
):
1377 raise CommandError("Directory %s does not exist" % dbdir
)
1379 if not dbdir
and not testparm
:
1380 raise CommandError("Please specify either dbdir or testparm")
1382 logger
= self
.get_logger()
1384 logger
.setLevel(logging
.DEBUG
)
1386 logger
.setLevel(logging
.WARNING
)
1388 logger
.setLevel(logging
.INFO
)
1390 if dbdir
and testparm
:
1391 logger
.warning("both dbdir and testparm specified, ignoring dbdir.")
1394 lp
= sambaopts
.get_loadparm()
1396 s3conf
= s3param
.get_context()
1399 s3conf
.set("realm", sambaopts
.realm
)
1401 if targetdir
is not None:
1402 if not os
.path
.isdir(targetdir
):
1406 if use_xattrs
== "yes":
1408 elif use_xattrs
== "auto" and not s3conf
.get("posix:eadb"):
1410 tmpfile
= tempfile
.NamedTemporaryFile(dir=os
.path
.abspath(targetdir
))
1412 tmpfile
= tempfile
.NamedTemporaryFile(dir=os
.path
.abspath(os
.path
.dirname(lp
.get("private dir"))))
1415 samba
.ntacls
.setntacl(lp
, tmpfile
.name
,
1416 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1419 # FIXME: Don't catch all exceptions here
1420 logger
.info("You are not root or your system do not support xattr, using tdb backend for attributes. "
1421 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1425 # Set correct default values from dbdir or testparm
1428 paths
["state directory"] = dbdir
1429 paths
["private dir"] = dbdir
1430 paths
["lock directory"] = dbdir
1431 paths
["smb passwd file"] = dbdir
+ "/smbpasswd"
1433 paths
["state directory"] = get_testparm_var(testparm
, smbconf
, "state directory")
1434 paths
["private dir"] = get_testparm_var(testparm
, smbconf
, "private dir")
1435 paths
["smb passwd file"] = get_testparm_var(testparm
, smbconf
, "smb passwd file")
1436 paths
["lock directory"] = get_testparm_var(testparm
, smbconf
, "lock directory")
1437 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1438 # "state directory", instead make use of "lock directory"
1439 if len(paths
["state directory"]) == 0:
1440 paths
["state directory"] = paths
["lock directory"]
1443 s3conf
.set(p
, paths
[p
])
1445 # load smb.conf parameters
1446 logger
.info("Reading smb.conf")
1447 s3conf
.load(smbconf
)
1448 samba3
= Samba3(smbconf
, s3conf
)
1450 logger
.info("Provisioning")
1451 upgrade_from_samba3(samba3
, logger
, targetdir
, session_info
=system_session(),
1452 useeadb
=eadb
, dns_backend
=dns_backend
, use_ntvfs
=use_ntvfs
)
1455 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade
):
1456 __doc__
= cmd_domain_classicupgrade
.__doc
__
1458 # This command is present for backwards compatibility only,
1459 # and should not be shown.
1464 class cmd_domain(SuperCommand
):
1465 """Domain management."""
1468 subcommands
["demote"] = cmd_domain_demote()
1469 if cmd_domain_export_keytab
is not None:
1470 subcommands
["exportkeytab"] = cmd_domain_export_keytab()
1471 subcommands
["info"] = cmd_domain_info()
1472 subcommands
["provision"] = cmd_domain_provision()
1473 subcommands
["join"] = cmd_domain_join()
1474 subcommands
["dcpromo"] = cmd_domain_dcpromo()
1475 subcommands
["level"] = cmd_domain_level()
1476 subcommands
["passwordsettings"] = cmd_domain_passwordsettings()
1477 subcommands
["classicupgrade"] = cmd_domain_classicupgrade()
1478 subcommands
["samba3upgrade"] = cmd_domain_samba3upgrade()