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.samr import DOMAIN_PASSWORD_COMPLEX, DOMAIN_PASSWORD_STORE_CLEARTEXT
39 from samba.netcmd import (
45 from samba.netcmd.common import netcmd_get_domain_infos_via_cldap
46 from samba.samba3 import Samba3
47 from samba.samba3 import param as s3param
48 from samba.upgrade import upgrade_from_samba3
49 from samba.drs_utils import (
50 sendDsReplicaSync, drsuapi_connect, drsException,
54 from samba.dsdb import (
55 DS_DOMAIN_FUNCTION_2000,
56 DS_DOMAIN_FUNCTION_2003,
57 DS_DOMAIN_FUNCTION_2003_MIXED,
58 DS_DOMAIN_FUNCTION_2008,
59 DS_DOMAIN_FUNCTION_2008_R2,
60 DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL,
61 DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL,
62 UF_WORKSTATION_TRUST_ACCOUNT,
63 UF_SERVER_TRUST_ACCOUNT,
64 UF_TRUSTED_FOR_DELEGATION
67 from samba.credentials import DONT_USE_KERBEROS
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) 2003 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 dap_backend_forced_uri is not None:
403 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 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 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 session = system_session()
412 result = provision(self.logger,
413 session, smbconf=smbconf, targetdir=targetdir,
414 samdb_fill=samdb_fill, realm=realm, domain=domain,
415 domainguid=domain_guid, domainsid=domain_sid,
417 hostip=host_ip, hostip6=host_ip6,
418 sitename=site, ntdsguid=ntds_guid,
419 invocationid=invocationid, adminpass=adminpass,
420 krbtgtpass=krbtgtpass, machinepass=machinepass,
421 dns_backend=dns_backend, dns_forwarder=dns_forwarder,
422 dnspass=dnspass, root=root, nobody=nobody,
424 serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
425 backend_type=ldap_backend_type,
426 ldapadminpass=ldapadminpass, ol_mmr_urls=ol_mmr_urls, slapd_path=slapd_path,
427 useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
428 use_rfc2307=use_rfc2307, skip_sysvolacl=False,
429 ldap_backend_extra_port=ldap_backend_extra_port,
430 ldap_backend_forced_uri=ldap_backend_forced_uri,
431 nosync=ldap_backend_nosync, ldap_dryrun_mode=ldap_dryrun_mode)
433 except ProvisioningError, e:
434 raise CommandError("Provision failed", e)
436 result.report_logger(self.logger)
438 def _get_nameserver_ip(self):
439 """Grab the nameserver IP address from /etc/resolv.conf."""
441 RESOLV_CONF="/etc/resolv.conf"
443 if not path.isfile(RESOLV_CONF):
444 self.logger.warning("Failed to locate %s" % RESOLV_CONF)
449 handle = open(RESOLV_CONF, 'r')
451 if not line.startswith('nameserver'):
453 # we want the last non-space continuous string of the line
454 return line.strip().split()[-1]
456 if handle is not None:
459 self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
462 class cmd_domain_dcpromo(Command):
463 """Promote an existing domain member or NT4 PDC to an AD DC."""
465 synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
467 takes_optiongroups = {
468 "sambaopts": options.SambaOptions,
469 "versionopts": options.VersionOptions,
470 "credopts": options.CredentialsOptions,
474 Option("--server", help="DC to join", type=str),
475 Option("--site", help="site to join", type=str),
476 Option("--targetdir", help="where to store provision", type=str),
477 Option("--domain-critical-only",
478 help="only replicate critical domain objects",
479 action="store_true"),
480 Option("--machinepass", type=str, metavar="PASSWORD",
481 help="choose machine password (otherwise random)"),
482 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
483 action="store_true"),
484 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
485 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
486 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
487 "BIND9_DLZ uses samba4 AD to store zone information, "
488 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
489 default="SAMBA_INTERNAL"),
490 Option("--quiet", help="Be quiet", action="store_true"),
491 Option("--verbose", help="Be verbose", action="store_true")
494 takes_args = ["domain", "role?"]
496 def run(self, domain, role=None, sambaopts=None, credopts=None,
497 versionopts=None, server=None, site=None, targetdir=None,
498 domain_critical_only=False, parent_domain=None, machinepass=None,
499 use_ntvfs=False, dns_backend=None,
500 quiet=False, verbose=False):
501 lp = sambaopts.get_loadparm()
502 creds = credopts.get_credentials(lp)
503 net = Net(creds, lp, server=credopts.ipaddress)
506 site = "Default-First-Site-Name"
508 logger = self.get_logger()
510 logger.setLevel(logging.DEBUG)
512 logger.setLevel(logging.WARNING)
514 logger.setLevel(logging.INFO)
516 netbios_name = lp.get("netbios name")
522 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
523 site=site, netbios_name=netbios_name, targetdir=targetdir,
524 domain_critical_only=domain_critical_only,
525 machinepass=machinepass, use_ntvfs=use_ntvfs,
526 dns_backend=dns_backend,
527 promote_existing=True)
529 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
530 site=site, netbios_name=netbios_name, targetdir=targetdir,
531 domain_critical_only=domain_critical_only,
532 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
533 promote_existing=True)
535 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
538 class cmd_domain_join(Command):
539 """Join domain as either member or backup domain controller."""
541 synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
543 takes_optiongroups = {
544 "sambaopts": options.SambaOptions,
545 "versionopts": options.VersionOptions,
546 "credopts": options.CredentialsOptions,
550 Option("--server", help="DC to join", type=str),
551 Option("--site", help="site to join", type=str),
552 Option("--targetdir", help="where to store provision", type=str),
553 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
554 Option("--domain-critical-only",
555 help="only replicate critical domain objects",
556 action="store_true"),
557 Option("--machinepass", type=str, metavar="PASSWORD",
558 help="choose machine password (otherwise random)"),
559 Option("--adminpass", type="string", metavar="PASSWORD",
560 help="choose adminstrator password when joining as a subdomain (otherwise random)"),
561 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
562 action="store_true"),
563 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
564 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
565 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
566 "BIND9_DLZ uses samba4 AD to store zone information, "
567 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
568 default="SAMBA_INTERNAL"),
569 Option("--quiet", help="Be quiet", action="store_true"),
570 Option("--verbose", help="Be verbose", action="store_true")
573 takes_args = ["domain", "role?"]
575 def run(self, domain, role=None, sambaopts=None, credopts=None,
576 versionopts=None, server=None, site=None, targetdir=None,
577 domain_critical_only=False, parent_domain=None, machinepass=None,
578 use_ntvfs=False, dns_backend=None, adminpass=None,
579 quiet=False, verbose=False):
580 lp = sambaopts.get_loadparm()
581 creds = credopts.get_credentials(lp)
582 net = Net(creds, lp, server=credopts.ipaddress)
585 site = "Default-First-Site-Name"
587 logger = self.get_logger()
589 logger.setLevel(logging.DEBUG)
591 logger.setLevel(logging.WARNING)
593 logger.setLevel(logging.INFO)
595 netbios_name = lp.get("netbios name")
600 if role is None or role == "MEMBER":
601 (join_password, sid, domain_name) = net.join_member(
602 domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
603 machinepass=machinepass)
605 self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
607 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
608 site=site, netbios_name=netbios_name, targetdir=targetdir,
609 domain_critical_only=domain_critical_only,
610 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend)
612 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
613 site=site, netbios_name=netbios_name, targetdir=targetdir,
614 domain_critical_only=domain_critical_only,
615 machinepass=machinepass, use_ntvfs=use_ntvfs,
616 dns_backend=dns_backend)
617 elif role == "SUBDOMAIN":
619 logger.info("Administrator password will be set randomly!")
621 netbios_domain = lp.get("workgroup")
622 if parent_domain is None:
623 parent_domain = ".".join(domain.split(".")[1:])
624 join_subdomain(logger=logger, server=server, creds=creds, lp=lp, dnsdomain=domain,
625 parent_domain=parent_domain, site=site,
626 netbios_name=netbios_name, netbios_domain=netbios_domain,
627 targetdir=targetdir, machinepass=machinepass,
628 use_ntvfs=use_ntvfs, dns_backend=dns_backend,
631 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role)
634 class cmd_domain_demote(Command):
635 """Demote ourselves from the role of Domain Controller."""
637 synopsis = "%prog [options]"
640 Option("--server", help="DC to force replication before demote", type=str),
641 Option("--targetdir", help="where provision is stored", type=str),
644 takes_optiongroups = {
645 "sambaopts": options.SambaOptions,
646 "credopts": options.CredentialsOptions,
647 "versionopts": options.VersionOptions,
650 def run(self, sambaopts=None, credopts=None,
651 versionopts=None, server=None, targetdir=None):
652 lp = sambaopts.get_loadparm()
653 creds = credopts.get_credentials(lp)
654 net = Net(creds, lp, server=credopts.ipaddress)
656 netbios_name = lp.get("netbios name")
657 samdb = SamDB(session_info=system_session(), credentials=creds, lp=lp)
659 res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
661 raise CommandError("Unable to search for servers")
664 raise CommandError("You are the latest server in the domain")
668 if str(e["name"]).lower() != netbios_name.lower():
669 server = e["dnsHostName"]
672 ntds_guid = samdb.get_ntds_GUID()
673 msg = samdb.search(base=str(samdb.get_config_basedn()),
674 scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
676 if len(msg) == 0 or "options" not in msg[0]:
677 raise CommandError("Failed to find options on %s" % ntds_guid)
680 dsa_options = int(str(msg[0]['options']))
682 res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
683 controls=["search_options:1:2"])
686 raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res))
688 self.errf.write("Using %s as partner server for the demotion\n" %
690 (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
692 self.errf.write("Desactivating inbound replication\n")
697 dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
698 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
701 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
703 self.errf.write("Asking partner server %s to synchronize from us\n"
705 for part in (samdb.get_schema_basedn(),
706 samdb.get_config_basedn(),
707 samdb.get_root_basedn()):
709 sendDsReplicaSync(drsuapiBind, drsuapi_handle, ntds_guid, str(part), drsuapi.DRSUAPI_DRS_WRIT_REP)
710 except drsException, e:
712 "Error while demoting, "
713 "re-enabling inbound replication\n")
714 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
715 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
717 raise CommandError("Error while sending a DsReplicaSync for partion %s" % str(part), e)
719 remote_samdb = SamDB(url="ldap://%s" % server,
720 session_info=system_session(),
721 credentials=creds, lp=lp)
723 self.errf.write("Changing userControl and container\n")
724 res = remote_samdb.search(base=str(remote_samdb.get_root_basedn()),
725 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
726 netbios_name.upper(),
727 attrs=["userAccountControl"])
729 uac = int(str(res[0]["userAccountControl"]))
733 "Error while demoting, re-enabling inbound replication\n")
734 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
735 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
737 raise CommandError("Error while changing account control", e)
741 "Error while demoting, re-enabling inbound replication")
742 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
743 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
745 raise CommandError("Unable to find object with samaccountName = %s$"
746 " in the remote dc" % netbios_name.upper())
750 uac ^= (UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION)
751 uac |= UF_WORKSTATION_TRUST_ACCOUNT
756 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
757 ldb.FLAG_MOD_REPLACE,
758 "userAccountControl")
760 remote_samdb.modify(msg)
763 "Error while demoting, re-enabling inbound replication")
764 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
765 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
768 raise CommandError("Error while changing account control", e)
770 parent = msg.dn.parent()
772 rdn = string.replace(rdn, ",%s" % str(parent), "")
773 # Let's move to the Computer container
777 computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.get_root_basedn()))
778 res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
781 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
782 scope=ldb.SCOPE_ONELEVEL)
783 while(len(res) != 0 and i < 100):
785 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
786 scope=ldb.SCOPE_ONELEVEL)
790 "Error while demoting, re-enabling inbound replication\n")
791 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
792 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
798 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
799 ldb.FLAG_MOD_REPLACE,
800 "userAccountControl")
802 remote_samdb.modify(msg)
804 raise CommandError("Unable to find a slot for renaming %s,"
805 " all names from %s-1 to %s-%d seemed used" %
806 (str(dc_dn), rdn, rdn, i - 9))
808 newrdn = "%s-%d" % (rdn, i)
811 newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
812 remote_samdb.rename(dc_dn, newdn)
815 "Error while demoting, re-enabling inbound replication\n")
816 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
817 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
823 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
824 ldb.FLAG_MOD_REPLACE,
825 "userAccountControl")
827 remote_samdb.modify(msg)
828 raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
831 server_dsa_dn = samdb.get_serverName()
832 domain = remote_samdb.get_root_basedn()
835 sendRemoveDsServer(drsuapiBind, drsuapi_handle, server_dsa_dn, domain)
836 except drsException, e:
838 "Error while demoting, re-enabling inbound replication\n")
839 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
840 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
846 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
847 ldb.FLAG_MOD_REPLACE,
848 "userAccountControl")
850 remote_samdb.modify(msg)
851 remote_samdb.rename(newdn, dc_dn)
852 raise CommandError("Error while sending a removeDsServer", e)
854 for s in ("CN=Enterprise,CN=Microsoft System Volumes,CN=System,CN=Configuration",
855 "CN=%s,CN=Microsoft System Volumes,CN=System,CN=Configuration" % lp.get("realm"),
856 "CN=Domain System Volumes (SYSVOL share),CN=File Replication Service,CN=System"):
858 remote_samdb.delete(ldb.Dn(remote_samdb,
859 "%s,%s,%s" % (str(rdn), s, str(remote_samdb.get_root_basedn()))))
860 except ldb.LdbError, l:
863 for s in ("CN=Enterprise,CN=NTFRS Subscriptions",
864 "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
865 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
866 "CN=NTFRS Subscriptions"):
868 remote_samdb.delete(ldb.Dn(remote_samdb,
869 "%s,%s" % (s, str(newdn))))
870 except ldb.LdbError, l:
873 self.errf.write("Demote successfull\n")
876 class cmd_domain_level(Command):
877 """Raise domain and forest function levels."""
879 synopsis = "%prog (show|raise <options>) [options]"
881 takes_optiongroups = {
882 "sambaopts": options.SambaOptions,
883 "credopts": options.CredentialsOptions,
884 "versionopts": options.VersionOptions,
888 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
889 metavar="URL", dest="H"),
890 Option("--quiet", help="Be quiet", action="store_true"),
891 Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2"],
892 help="The forest function level (2003 | 2008 | 2008_R2)"),
893 Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2"],
894 help="The domain function level (2003 | 2008 | 2008_R2)")
897 takes_args = ["subcommand"]
899 def run(self, subcommand, H=None, forest_level=None, domain_level=None,
900 quiet=False, credopts=None, sambaopts=None, versionopts=None):
901 lp = sambaopts.get_loadparm()
902 creds = credopts.get_credentials(lp, fallback_machine=True)
904 samdb = SamDB(url=H, session_info=system_session(),
905 credentials=creds, lp=lp)
907 domain_dn = samdb.domain_dn()
909 res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
910 scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
911 assert len(res_forest) == 1
913 res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
914 attrs=["msDS-Behavior-Version", "nTMixedDomain"])
915 assert len(res_domain) == 1
917 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
918 scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
919 attrs=["msDS-Behavior-Version"])
920 assert len(res_dc_s) >= 1
923 level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
924 level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
925 level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
927 min_level_dc = int(res_dc_s[0]["msDS-Behavior-Version"][0]) # Init value
929 if int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
930 min_level_dc = int(msg["msDS-Behavior-Version"][0])
932 if level_forest < 0 or level_domain < 0:
933 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
935 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
936 if level_forest > level_domain:
937 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
938 if level_domain > min_level_dc:
939 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
942 raise CommandError("Could not retrieve the actual domain, forest level and/or lowest DC function level!")
944 if subcommand == "show":
945 self.message("Domain and forest function level for domain '%s'" % domain_dn)
946 if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
947 self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
948 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
949 self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
950 if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
951 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)!")
955 if level_forest == DS_DOMAIN_FUNCTION_2000:
957 elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
958 outstr = "2003 with mixed domains/interim (NT4 DC support)"
959 elif level_forest == DS_DOMAIN_FUNCTION_2003:
961 elif level_forest == DS_DOMAIN_FUNCTION_2008:
963 elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
966 outstr = "higher than 2008 R2"
967 self.message("Forest function level: (Windows) " + outstr)
969 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
970 outstr = "2000 mixed (NT4 DC support)"
971 elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
973 elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
974 outstr = "2003 with mixed domains/interim (NT4 DC support)"
975 elif level_domain == DS_DOMAIN_FUNCTION_2003:
977 elif level_domain == DS_DOMAIN_FUNCTION_2008:
979 elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
982 outstr = "higher than 2008 R2"
983 self.message("Domain function level: (Windows) " + outstr)
985 if min_level_dc == DS_DOMAIN_FUNCTION_2000:
987 elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
989 elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
991 elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
994 outstr = "higher than 2008 R2"
995 self.message("Lowest function level of a DC: (Windows) " + outstr)
997 elif subcommand == "raise":
1000 if domain_level is not None:
1001 if domain_level == "2003":
1002 new_level_domain = DS_DOMAIN_FUNCTION_2003
1003 elif domain_level == "2008":
1004 new_level_domain = DS_DOMAIN_FUNCTION_2008
1005 elif domain_level == "2008_R2":
1006 new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
1008 if new_level_domain <= level_domain and level_domain_mixed == 0:
1009 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
1011 if new_level_domain > min_level_dc:
1012 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
1014 # Deactivate mixed/interim domain support
1015 if level_domain_mixed != 0:
1016 # Directly on the base DN
1018 m.dn = ldb.Dn(samdb, domain_dn)
1019 m["nTMixedDomain"] = ldb.MessageElement("0",
1020 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1024 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
1025 m["nTMixedDomain"] = ldb.MessageElement("0",
1026 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1029 except ldb.LdbError, (enum, emsg):
1030 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1033 # Directly on the base DN
1035 m.dn = ldb.Dn(samdb, domain_dn)
1036 m["msDS-Behavior-Version"]= ldb.MessageElement(
1037 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1038 "msDS-Behavior-Version")
1042 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
1043 + ",CN=Partitions,%s" % samdb.get_config_basedn())
1044 m["msDS-Behavior-Version"]= ldb.MessageElement(
1045 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1046 "msDS-Behavior-Version")
1049 except ldb.LdbError, (enum, emsg):
1050 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1053 level_domain = new_level_domain
1054 msgs.append("Domain function level changed!")
1056 if forest_level is not None:
1057 if forest_level == "2003":
1058 new_level_forest = DS_DOMAIN_FUNCTION_2003
1059 elif forest_level == "2008":
1060 new_level_forest = DS_DOMAIN_FUNCTION_2008
1061 elif forest_level == "2008_R2":
1062 new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
1063 if new_level_forest <= level_forest:
1064 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1065 if new_level_forest > level_domain:
1066 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1068 m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
1069 m["msDS-Behavior-Version"]= ldb.MessageElement(
1070 str(new_level_forest), ldb.FLAG_MOD_REPLACE,
1071 "msDS-Behavior-Version")
1073 msgs.append("Forest function level changed!")
1074 msgs.append("All changes applied successfully!")
1075 self.message("\n".join(msgs))
1077 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
1080 class cmd_domain_passwordsettings(Command):
1081 """Set password settings.
1083 Password complexity, password lockout policy, history length,
1084 minimum password length, the minimum and maximum password age) on
1085 a Samba AD DC server.
1087 Use against a Windows DC is possible, but group policy will override it.
1090 synopsis = "%prog (show|set <options>) [options]"
1092 takes_optiongroups = {
1093 "sambaopts": options.SambaOptions,
1094 "versionopts": options.VersionOptions,
1095 "credopts": options.CredentialsOptions,
1099 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1100 metavar="URL", dest="H"),
1101 Option("--quiet", help="Be quiet", action="store_true"),
1102 Option("--complexity", type="choice", choices=["on","off","default"],
1103 help="The password complexity (on | off | default). Default is 'on'"),
1104 Option("--store-plaintext", type="choice", choices=["on","off","default"],
1105 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1106 Option("--history-length",
1107 help="The password history length (<integer> | default). Default is 24.", type=str),
1108 Option("--min-pwd-length",
1109 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1110 Option("--min-pwd-age",
1111 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1112 Option("--max-pwd-age",
1113 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1114 Option("--account-lockout-duration",
1115 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),
1116 Option("--account-lockout-threshold",
1117 help="The number of bad password attempts allowed before locking out the account (<integer> | default). Default is 0 (never lock out).", type=str),
1118 Option("--reset-account-lockout-after",
1119 help="After this time is elapsed, the recorded number of attempts restarts from zero (<integer> | default). Default is 30.", type=str),
1122 takes_args = ["subcommand"]
1124 def run(self, subcommand, H=None, min_pwd_age=None, max_pwd_age=None,
1125 quiet=False, complexity=None, store_plaintext=None, history_length=None,
1126 min_pwd_length=None, account_lockout_duration=None, account_lockout_threshold=None,
1127 reset_account_lockout_after=None, credopts=None, sambaopts=None,
1129 lp = sambaopts.get_loadparm()
1130 creds = credopts.get_credentials(lp)
1132 samdb = SamDB(url=H, session_info=system_session(),
1133 credentials=creds, lp=lp)
1135 domain_dn = samdb.domain_dn()
1136 res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1137 attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1138 "minPwdAge", "maxPwdAge", "lockoutDuration", "lockoutThreshold",
1139 "lockOutObservationWindow"])
1140 assert(len(res) == 1)
1142 pwd_props = int(res[0]["pwdProperties"][0])
1143 pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
1144 cur_min_pwd_len = int(res[0]["minPwdLength"][0])
1146 cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1147 if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000:
1150 cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1151 cur_account_lockout_threshold = int(res[0]["lockoutThreshold"][0])
1153 if int(res[0]["lockoutDuration"][0]) == -0x8000000000000000:
1154 cur_account_lockout_duration = 0
1156 cur_account_lockout_duration = abs(int(res[0]["lockoutDuration"][0])) / (1e7 * 60)
1157 cur_reset_account_lockout_after = abs(int(res[0]["lockOutObservationWindow"][0])) / (1e7 * 60)
1158 except Exception, e:
1159 raise CommandError("Could not retrieve password properties!", e)
1161 if subcommand == "show":
1162 self.message("Password informations for domain '%s'" % domain_dn)
1164 if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
1165 self.message("Password complexity: on")
1167 self.message("Password complexity: off")
1168 if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
1169 self.message("Store plaintext passwords: on")
1171 self.message("Store plaintext passwords: off")
1172 self.message("Password history length: %d" % pwd_hist_len)
1173 self.message("Minimum password length: %d" % cur_min_pwd_len)
1174 self.message("Minimum password age (days): %d" % cur_min_pwd_age)
1175 self.message("Maximum password age (days): %d" % cur_max_pwd_age)
1176 self.message("Account lockout duration (mins): %d" % cur_account_lockout_duration)
1177 self.message("Account lockout threshold (attempts): %d" % cur_account_lockout_threshold)
1178 self.message("Reset account lockout after (mins): %d" % cur_reset_account_lockout_after)
1179 elif subcommand == "set":
1182 m.dn = ldb.Dn(samdb, domain_dn)
1184 if complexity is not None:
1185 if complexity == "on" or complexity == "default":
1186 pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
1187 msgs.append("Password complexity activated!")
1188 elif complexity == "off":
1189 pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
1190 msgs.append("Password complexity deactivated!")
1192 if store_plaintext is not None:
1193 if store_plaintext == "on" or store_plaintext == "default":
1194 pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1195 msgs.append("Plaintext password storage for changed passwords activated!")
1196 elif store_plaintext == "off":
1197 pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
1198 msgs.append("Plaintext password storage for changed passwords deactivated!")
1200 if complexity is not None or store_plaintext is not None:
1201 m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
1202 ldb.FLAG_MOD_REPLACE, "pwdProperties")
1204 if history_length is not None:
1205 if history_length == "default":
1208 pwd_hist_len = int(history_length)
1210 if pwd_hist_len < 0 or pwd_hist_len > 24:
1211 raise CommandError("Password history length must be in the range of 0 to 24!")
1213 m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
1214 ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
1215 msgs.append("Password history length changed!")
1217 if min_pwd_length is not None:
1218 if min_pwd_length == "default":
1221 min_pwd_len = int(min_pwd_length)
1223 if min_pwd_len < 0 or min_pwd_len > 14:
1224 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1226 m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
1227 ldb.FLAG_MOD_REPLACE, "minPwdLength")
1228 msgs.append("Minimum password length changed!")
1230 if min_pwd_age is not None:
1231 if min_pwd_age == "default":
1234 min_pwd_age = int(min_pwd_age)
1236 if min_pwd_age < 0 or min_pwd_age > 998:
1237 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1240 min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
1242 m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
1243 ldb.FLAG_MOD_REPLACE, "minPwdAge")
1244 msgs.append("Minimum password age changed!")
1246 if max_pwd_age is not None:
1247 if max_pwd_age == "default":
1250 max_pwd_age = int(max_pwd_age)
1252 if max_pwd_age < 0 or max_pwd_age > 999:
1253 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1256 if max_pwd_age == 0:
1257 max_pwd_age_ticks = -0x8000000000000000
1259 max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
1261 m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
1262 ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1263 msgs.append("Maximum password age changed!")
1265 if account_lockout_duration is not None:
1266 if account_lockout_duration == "default":
1267 account_lockout_duration = 30
1269 account_lockout_duration = int(account_lockout_duration)
1271 if account_lockout_duration < 0 or account_lockout_duration > 99999:
1272 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1275 if account_lockout_duration == 0:
1276 account_lockout_duration_ticks = -0x8000000000000000
1278 account_lockout_duration_ticks = -int(account_lockout_duration * (60 * 1e7))
1280 m["lockoutDuration"] = ldb.MessageElement(str(account_lockout_duration_ticks),
1281 ldb.FLAG_MOD_REPLACE, "lockoutDuration")
1282 msgs.append("Account lockout duration changed!")
1284 if account_lockout_threshold is not None:
1285 if account_lockout_threshold == "default":
1286 account_lockout_threshold = 0
1288 account_lockout_threshold = int(account_lockout_threshold)
1290 m["lockoutThreshold"] = ldb.MessageElement(str(account_lockout_threshold),
1291 ldb.FLAG_MOD_REPLACE, "lockoutThreshold")
1292 msgs.append("Account lockout threshold changed!")
1294 if reset_account_lockout_after is not None:
1295 if reset_account_lockout_after == "default":
1296 reset_account_lockout_after = 30
1298 reset_account_lockout_after = int(reset_account_lockout_after)
1300 if reset_account_lockout_after < 0 or reset_account_lockout_after > 99999:
1301 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1304 if reset_account_lockout_after == 0:
1305 reset_account_lockout_after_ticks = -0x8000000000000000
1307 reset_account_lockout_after_ticks = -int(reset_account_lockout_after * (60 * 1e7))
1309 m["lockOutObservationWindow"] = ldb.MessageElement(str(reset_account_lockout_after_ticks),
1310 ldb.FLAG_MOD_REPLACE, "lockOutObservationWindow")
1311 msgs.append("Duration to reset account lockout after changed!")
1313 if max_pwd_age > 0 and min_pwd_age >= max_pwd_age:
1314 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
1317 raise CommandError("You must specify at least one option to set. Try --help")
1319 msgs.append("All changes applied successfully!")
1320 self.message("\n".join(msgs))
1322 raise CommandError("Wrong argument '%s'!" % subcommand)
1325 class cmd_domain_classicupgrade(Command):
1326 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1328 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1329 the testparm utility from your classic installation (with --testparm).
1332 synopsis = "%prog [options] <classic_smb_conf>"
1334 takes_optiongroups = {
1335 "sambaopts": options.SambaOptions,
1336 "versionopts": options.VersionOptions
1340 Option("--dbdir", type="string", metavar="DIR",
1341 help="Path to samba classic DC database directory"),
1342 Option("--testparm", type="string", metavar="PATH",
1343 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1344 Option("--targetdir", type="string", metavar="DIR",
1345 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1346 Option("--quiet", help="Be quiet", action="store_true"),
1347 Option("--verbose", help="Be verbose", action="store_true"),
1348 Option("--use-xattrs", type="choice", choices=["yes","no","auto"], metavar="[yes|no|auto]",
1349 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"),
1350 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
1351 action="store_true"),
1352 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
1353 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1354 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1355 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1356 "BIND9_DLZ uses samba4 AD to store zone information, "
1357 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1358 default="SAMBA_INTERNAL")
1361 takes_args = ["smbconf"]
1363 def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
1364 quiet=False, verbose=False, use_xattrs=None, sambaopts=None, versionopts=None,
1365 dns_backend=None, use_ntvfs=False):
1367 if not os.path.exists(smbconf):
1368 raise CommandError("File %s does not exist" % smbconf)
1370 if testparm and not os.path.exists(testparm):
1371 raise CommandError("Testparm utility %s does not exist" % testparm)
1373 if dbdir and not os.path.exists(dbdir):
1374 raise CommandError("Directory %s does not exist" % dbdir)
1376 if not dbdir and not testparm:
1377 raise CommandError("Please specify either dbdir or testparm")
1379 logger = self.get_logger()
1381 logger.setLevel(logging.DEBUG)
1383 logger.setLevel(logging.WARNING)
1385 logger.setLevel(logging.INFO)
1387 if dbdir and testparm:
1388 logger.warning("both dbdir and testparm specified, ignoring dbdir.")
1391 lp = sambaopts.get_loadparm()
1393 s3conf = s3param.get_context()
1396 s3conf.set("realm", sambaopts.realm)
1398 if targetdir is not None:
1399 if not os.path.isdir(targetdir):
1403 if use_xattrs == "yes":
1405 elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
1407 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
1409 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
1412 samba.ntacls.setntacl(lp, tmpfile.name,
1413 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1416 # FIXME: Don't catch all exceptions here
1417 logger.info("You are not root or your system do not support xattr, using tdb backend for attributes. "
1418 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1422 # Set correct default values from dbdir or testparm
1425 paths["state directory"] = dbdir
1426 paths["private dir"] = dbdir
1427 paths["lock directory"] = dbdir
1428 paths["smb passwd file"] = dbdir + "/smbpasswd"
1430 paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
1431 paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
1432 paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
1433 paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
1434 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1435 # "state directory", instead make use of "lock directory"
1436 if len(paths["state directory"]) == 0:
1437 paths["state directory"] = paths["lock directory"]
1440 s3conf.set(p, paths[p])
1442 # load smb.conf parameters
1443 logger.info("Reading smb.conf")
1444 s3conf.load(smbconf)
1445 samba3 = Samba3(smbconf, s3conf)
1447 logger.info("Provisioning")
1448 upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
1449 useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
1452 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
1453 __doc__ = cmd_domain_classicupgrade.__doc__
1455 # This command is present for backwards compatibility only,
1456 # and should not be shown.
1461 class cmd_domain(SuperCommand):
1462 """Domain management."""
1465 subcommands["demote"] = cmd_domain_demote()
1466 if cmd_domain_export_keytab is not None:
1467 subcommands["exportkeytab"] = cmd_domain_export_keytab()
1468 subcommands["info"] = cmd_domain_info()
1469 subcommands["provision"] = cmd_domain_provision()
1470 subcommands["join"] = cmd_domain_join()
1471 subcommands["dcpromo"] = cmd_domain_dcpromo()
1472 subcommands["level"] = cmd_domain_level()
1473 subcommands["passwordsettings"] = cmd_domain_passwordsettings()
1474 subcommands["classicupgrade"] = cmd_domain_classicupgrade()
1475 subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()