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()
86 class cmd_domain_export_keytab(Command):
87 """Dump Kerberos keys of the domain into a keytab."""
89 synopsis = "%prog <keytab> [options]"
91 takes_optiongroups = {
92 "sambaopts": options.SambaOptions,
93 "credopts": options.CredentialsOptions,
94 "versionopts": options.VersionOptions,
98 Option("--principal", help="extract only this principal", type=str),
101 takes_args = ["keytab"]
103 def run(self, keytab, credopts=None, sambaopts=None, versionopts=None, principal=None):
104 lp = sambaopts.get_loadparm()
106 net.export_keytab(keytab=keytab, principal=principal)
108 cmd_domain_export_keytab = None
111 class cmd_domain_info(Command):
112 """Print basic info about a domain and the DC passed as parameter."""
114 synopsis = "%prog <ip_address> [options]"
119 takes_optiongroups = {
120 "sambaopts": options.SambaOptions,
121 "credopts": options.CredentialsOptions,
122 "versionopts": options.VersionOptions,
125 takes_args = ["address"]
127 def run(self, address, credopts=None, sambaopts=None, versionopts=None):
128 lp = sambaopts.get_loadparm()
130 res = netcmd_get_domain_infos_via_cldap(lp, None, address)
132 raise CommandError("Invalid IP address '" + address + "'!")
133 self.outf.write("Forest : %s\n" % res.forest)
134 self.outf.write("Domain : %s\n" % res.dns_domain)
135 self.outf.write("Netbios domain : %s\n" % res.domain_name)
136 self.outf.write("DC name : %s\n" % res.pdc_dns_name)
137 self.outf.write("DC netbios name : %s\n" % res.pdc_name)
138 self.outf.write("Server site : %s\n" % res.server_site)
139 self.outf.write("Client site : %s\n" % res.client_site)
142 class cmd_domain_provision(Command):
143 """Provision a domain."""
145 synopsis = "%prog [options]"
147 takes_optiongroups = {
148 "sambaopts": options.SambaOptions,
149 "versionopts": options.VersionOptions,
153 Option("--interactive", help="Ask for names", action="store_true"),
154 Option("--domain", type="string", metavar="DOMAIN",
156 Option("--domain-guid", type="string", metavar="GUID",
157 help="set domainguid (otherwise random)"),
158 Option("--domain-sid", type="string", metavar="SID",
159 help="set domainsid (otherwise random)"),
160 Option("--ntds-guid", type="string", metavar="GUID",
161 help="set NTDS object GUID (otherwise random)"),
162 Option("--invocationid", type="string", metavar="GUID",
163 help="set invocationid (otherwise random)"),
164 Option("--host-name", type="string", metavar="HOSTNAME",
165 help="set hostname"),
166 Option("--host-ip", type="string", metavar="IPADDRESS",
167 help="set IPv4 ipaddress"),
168 Option("--host-ip6", type="string", metavar="IP6ADDRESS",
169 help="set IPv6 ipaddress"),
170 Option("--site", type="string", metavar="SITENAME",
171 help="set site name"),
172 Option("--adminpass", type="string", metavar="PASSWORD",
173 help="choose admin password (otherwise random)"),
174 Option("--krbtgtpass", type="string", metavar="PASSWORD",
175 help="choose krbtgt password (otherwise random)"),
176 Option("--machinepass", type="string", metavar="PASSWORD",
177 help="choose machine password (otherwise random)"),
178 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
179 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
180 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
181 "BIND9_FLATFILE uses bind9 text database to store zone information, "
182 "BIND9_DLZ uses samba4 AD to store zone information, "
183 "NONE skips the DNS setup entirely (not recommended)",
184 default="SAMBA_INTERNAL"),
185 Option("--dnspass", type="string", metavar="PASSWORD",
186 help="choose dns password (otherwise random)"),
187 Option("--ldapadminpass", type="string", metavar="PASSWORD",
188 help="choose password to set between Samba and it's LDAP backend (otherwise random)"),
189 Option("--root", type="string", metavar="USERNAME",
190 help="choose 'root' unix username"),
191 Option("--nobody", type="string", metavar="USERNAME",
192 help="choose 'nobody' user"),
193 Option("--users", type="string", metavar="GROUPNAME",
194 help="choose 'users' group"),
195 Option("--quiet", help="Be quiet", action="store_true"),
196 Option("--blank", action="store_true",
197 help="do not add users or groups, just the structure"),
198 Option("--ldap-backend-type", type="choice", metavar="LDAP-BACKEND-TYPE",
199 help="Test initialisation support for unsupported LDAP backend type (fedora-ds or openldap) DO NOT USE",
200 choices=["fedora-ds", "openldap"]),
201 Option("--server-role", type="choice", metavar="ROLE",
202 choices=["domain controller", "dc", "member server", "member", "standalone"],
203 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
204 default="domain controller"),
205 Option("--function-level", type="choice", metavar="FOR-FUN-LEVEL",
206 choices=["2000", "2003", "2008", "2008_R2"],
207 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2003 Native.",
209 Option("--next-rid", type="int", metavar="NEXTRID", default=1000,
210 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
211 Option("--partitions-only",
212 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"),
213 Option("--targetdir", type="string", metavar="DIR",
214 help="Set target directory"),
215 Option("--ol-mmr-urls", type="string", metavar="LDAPSERVER",
216 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\""),
217 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"),
218 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
219 Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
223 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",
224 action="store_true"),
225 Option("--slapd-path", type="string", metavar="SLAPD-PATH",
226 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."),
227 Option("--ldap-backend-extra-port", type="int", metavar="LDAP-BACKEND-EXTRA-PORT", help="Additional TCP port for LDAP backend server (to use for replication)"),
228 Option("--ldap-backend-forced-uri", type="string", metavar="LDAP-BACKEND-FORCED-URI",
229 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"),
230 Option("--ldap-backend-nosync", help="Configure LDAP backend not to call fsync() (for performance in test environments)", action="store_true"),
233 if os.getenv('TEST_LDAP', "no") == "yes":
234 takes_options.extend(openldap_options)
238 def run(self, sambaopts=None, versionopts=None,
261 ldap_backend_type=None,
265 partitions_only=None,
272 ldap_backend_nosync=None,
273 ldap_backend_extra_port=None,
274 ldap_backend_forced_uri=None,
275 ldap_dryrun_mode=None):
277 self.logger = self.get_logger("provision")
279 self.logger.setLevel(logging.WARNING)
281 self.logger.setLevel(logging.INFO)
283 lp = sambaopts.get_loadparm()
284 smbconf = lp.configfile
286 if dns_forwarder is not None:
287 suggested_forwarder = dns_forwarder
289 suggested_forwarder = self._get_nameserver_ip()
290 if suggested_forwarder is None:
291 suggested_forwarder = "none"
293 if len(self.raw_argv) == 1:
297 from getpass import getpass
300 def ask(prompt, default=None):
301 if default is not None:
302 print "%s [%s]: " % (prompt, default),
304 print "%s: " % (prompt,),
305 return sys.stdin.readline().rstrip("\n") or default
308 default = socket.getfqdn().split(".", 1)[1].upper()
311 realm = ask("Realm", default)
312 if realm in (None, ""):
313 raise CommandError("No realm set!")
316 default = realm.split(".")[0]
319 domain = ask("Domain", default)
321 raise CommandError("No domain set!")
323 server_role = ask("Server Role (dc, member, standalone)", "dc")
325 dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
326 if dns_backend in (None, ''):
327 raise CommandError("No DNS backend set!")
329 if dns_backend == "SAMBA_INTERNAL":
330 dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
331 if dns_forwarder.lower() in (None, 'none'):
332 suggested_forwarder = None
336 adminpassplain = getpass("Administrator password: ")
337 if not adminpassplain:
338 self.errf.write("Invalid administrator password.\n")
340 adminpassverify = getpass("Retype password: ")
341 if not adminpassplain == adminpassverify:
342 self.errf.write("Sorry, passwords do not match.\n")
344 adminpass = adminpassplain
348 realm = sambaopts._lp.get('realm')
350 raise CommandError("No realm set!")
352 raise CommandError("No domain set!")
355 self.logger.info("Administrator password will be set randomly!")
357 if function_level == "2000":
358 dom_for_fun_level = DS_DOMAIN_FUNCTION_2000
359 elif function_level == "2003":
360 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
361 elif function_level == "2008":
362 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
363 elif function_level == "2008_R2":
364 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
366 if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
367 dns_forwarder = suggested_forwarder
369 samdb_fill = FILL_FULL
371 samdb_fill = FILL_NT4SYNC
372 elif partitions_only:
373 samdb_fill = FILL_DRS
375 if targetdir is not None:
376 if not os.path.isdir(targetdir):
381 if use_xattrs == "yes":
383 elif use_xattrs == "auto" and not lp.get("posix:eadb"):
385 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
387 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
390 samba.ntacls.setntacl(lp, file.name,
391 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
394 self.logger.info("You are not root or your system do not support xattr, using tdb backend for attributes. ")
399 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.")
400 if ldap_backend_type == "existing":
401 if dap_backend_forced_uri is not None:
402 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)
404 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")
406 if ldap_backend_forced_uri is not None:
407 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")
409 session = system_session()
411 result = provision(self.logger,
412 session, smbconf=smbconf, targetdir=targetdir,
413 samdb_fill=samdb_fill, realm=realm, domain=domain,
414 domainguid=domain_guid, domainsid=domain_sid,
416 hostip=host_ip, hostip6=host_ip6,
417 sitename=site, ntdsguid=ntds_guid,
418 invocationid=invocationid, adminpass=adminpass,
419 krbtgtpass=krbtgtpass, machinepass=machinepass,
420 dns_backend=dns_backend, dns_forwarder=dns_forwarder,
421 dnspass=dnspass, root=root, nobody=nobody,
423 serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
424 backend_type=ldap_backend_type,
425 ldapadminpass=ldapadminpass, ol_mmr_urls=ol_mmr_urls, slapd_path=slapd_path,
426 useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
427 use_rfc2307=use_rfc2307, skip_sysvolacl=False,
428 ldap_backend_extra_port=ldap_backend_extra_port,
429 ldap_backend_forced_uri=ldap_backend_forced_uri,
430 nosync=ldap_backend_nosync, ldap_dryrun_mode=ldap_dryrun_mode)
432 except ProvisioningError, e:
433 raise CommandError("Provision failed", e)
435 result.report_logger(self.logger)
437 def _get_nameserver_ip(self):
438 """Grab the nameserver IP address from /etc/resolv.conf."""
440 RESOLV_CONF="/etc/resolv.conf"
442 if not path.isfile(RESOLV_CONF):
443 self.logger.warning("Failed to locate %s" % RESOLV_CONF)
448 handle = open(RESOLV_CONF, 'r')
450 if not line.startswith('nameserver'):
452 # we want the last non-space continuous string of the line
453 return line.strip().split()[-1]
455 if handle is not None:
458 self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
461 class cmd_domain_dcpromo(Command):
462 """Promote an existing domain member or NT4 PDC to an AD DC."""
464 synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
466 takes_optiongroups = {
467 "sambaopts": options.SambaOptions,
468 "versionopts": options.VersionOptions,
469 "credopts": options.CredentialsOptions,
473 Option("--server", help="DC to join", type=str),
474 Option("--site", help="site to join", type=str),
475 Option("--targetdir", help="where to store provision", type=str),
476 Option("--domain-critical-only",
477 help="only replicate critical domain objects",
478 action="store_true"),
479 Option("--machinepass", type=str, metavar="PASSWORD",
480 help="choose machine password (otherwise random)"),
481 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
482 action="store_true"),
483 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
484 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
485 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
486 "BIND9_DLZ uses samba4 AD to store zone information, "
487 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
488 default="SAMBA_INTERNAL"),
489 Option("--quiet", help="Be quiet", action="store_true"),
490 Option("--verbose", help="Be verbose", action="store_true")
493 takes_args = ["domain", "role?"]
495 def run(self, domain, role=None, sambaopts=None, credopts=None,
496 versionopts=None, server=None, site=None, targetdir=None,
497 domain_critical_only=False, parent_domain=None, machinepass=None,
498 use_ntvfs=False, dns_backend=None,
499 quiet=False, verbose=False):
500 lp = sambaopts.get_loadparm()
501 creds = credopts.get_credentials(lp)
502 net = Net(creds, lp, server=credopts.ipaddress)
505 site = "Default-First-Site-Name"
507 logger = self.get_logger()
509 logger.setLevel(logging.DEBUG)
511 logger.setLevel(logging.WARNING)
513 logger.setLevel(logging.INFO)
516 site = "Default-First-Site-Name"
518 netbios_name = lp.get("netbios name")
524 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
525 site=site, netbios_name=netbios_name, targetdir=targetdir,
526 domain_critical_only=domain_critical_only,
527 machinepass=machinepass, use_ntvfs=use_ntvfs,
528 dns_backend=dns_backend,
529 promote_existing=True)
531 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
532 site=site, netbios_name=netbios_name, targetdir=targetdir,
533 domain_critical_only=domain_critical_only,
534 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
535 promote_existing=True)
537 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
540 class cmd_domain_join(Command):
541 """Join domain as either member or backup domain controller."""
543 synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
545 takes_optiongroups = {
546 "sambaopts": options.SambaOptions,
547 "versionopts": options.VersionOptions,
548 "credopts": options.CredentialsOptions,
552 Option("--server", help="DC to join", type=str),
553 Option("--site", help="site to join", type=str),
554 Option("--targetdir", help="where to store provision", type=str),
555 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
556 Option("--domain-critical-only",
557 help="only replicate critical domain objects",
558 action="store_true"),
559 Option("--machinepass", type=str, metavar="PASSWORD",
560 help="choose machine password (otherwise random)"),
561 Option("--adminpass", type="string", metavar="PASSWORD",
562 help="choose adminstrator password when joining as a subdomain (otherwise random)"),
563 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
564 action="store_true"),
565 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
566 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
567 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
568 "BIND9_DLZ uses samba4 AD to store zone information, "
569 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
570 default="SAMBA_INTERNAL"),
571 Option("--quiet", help="Be quiet", action="store_true"),
572 Option("--verbose", help="Be verbose", action="store_true")
575 takes_args = ["domain", "role?"]
577 def run(self, domain, role=None, sambaopts=None, credopts=None,
578 versionopts=None, server=None, site=None, targetdir=None,
579 domain_critical_only=False, parent_domain=None, machinepass=None,
580 use_ntvfs=False, dns_backend=None, adminpass=None,
581 quiet=False, verbose=False):
582 lp = sambaopts.get_loadparm()
583 creds = credopts.get_credentials(lp)
584 net = Net(creds, lp, server=credopts.ipaddress)
587 site = "Default-First-Site-Name"
589 logger = self.get_logger()
591 logger.setLevel(logging.DEBUG)
593 logger.setLevel(logging.WARNING)
595 logger.setLevel(logging.INFO)
597 netbios_name = lp.get("netbios name")
602 if role is None or role == "MEMBER":
603 (join_password, sid, domain_name) = net.join_member(
604 domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
605 machinepass=machinepass)
607 self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
609 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
610 site=site, netbios_name=netbios_name, targetdir=targetdir,
611 domain_critical_only=domain_critical_only,
612 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend)
614 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
615 site=site, netbios_name=netbios_name, targetdir=targetdir,
616 domain_critical_only=domain_critical_only,
617 machinepass=machinepass, use_ntvfs=use_ntvfs,
618 dns_backend=dns_backend)
619 elif role == "SUBDOMAIN":
621 logger.info("Administrator password will be set randomly!")
623 netbios_domain = lp.get("workgroup")
624 if parent_domain is None:
625 parent_domain = ".".join(domain.split(".")[1:])
626 join_subdomain(logger=logger, server=server, creds=creds, lp=lp, dnsdomain=domain,
627 parent_domain=parent_domain, site=site,
628 netbios_name=netbios_name, netbios_domain=netbios_domain,
629 targetdir=targetdir, machinepass=machinepass,
630 use_ntvfs=use_ntvfs, dns_backend=dns_backend,
633 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role)
636 class cmd_domain_demote(Command):
637 """Demote ourselves from the role of Domain Controller."""
639 synopsis = "%prog [options]"
642 Option("--server", help="DC to force replication before demote", type=str),
643 Option("--targetdir", help="where provision is stored", type=str),
646 takes_optiongroups = {
647 "sambaopts": options.SambaOptions,
648 "credopts": options.CredentialsOptions,
649 "versionopts": options.VersionOptions,
652 def run(self, sambaopts=None, credopts=None,
653 versionopts=None, server=None, targetdir=None):
654 lp = sambaopts.get_loadparm()
655 creds = credopts.get_credentials(lp)
656 net = Net(creds, lp, server=credopts.ipaddress)
658 netbios_name = lp.get("netbios name")
659 samdb = SamDB(session_info=system_session(), credentials=creds, lp=lp)
661 res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
663 raise CommandError("Unable to search for servers")
666 raise CommandError("You are the latest server in the domain")
670 if str(e["name"]).lower() != netbios_name.lower():
671 server = e["dnsHostName"]
674 ntds_guid = samdb.get_ntds_GUID()
675 msg = samdb.search(base=str(samdb.get_config_basedn()),
676 scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
678 if len(msg) == 0 or "options" not in msg[0]:
679 raise CommandError("Failed to find options on %s" % ntds_guid)
682 dsa_options = int(str(msg[0]['options']))
684 res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
685 controls=["search_options:1:2"])
688 raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res))
690 self.errf.write("Using %s as partner server for the demotion\n" %
692 (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
694 self.errf.write("Desactivating inbound replication\n")
699 dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
700 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
703 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
705 self.errf.write("Asking partner server %s to synchronize from us\n"
707 for part in (samdb.get_schema_basedn(),
708 samdb.get_config_basedn(),
709 samdb.get_root_basedn()):
711 sendDsReplicaSync(drsuapiBind, drsuapi_handle, ntds_guid, str(part), drsuapi.DRSUAPI_DRS_WRIT_REP)
712 except drsException, e:
714 "Error while demoting, "
715 "re-enabling inbound replication\n")
716 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
717 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
719 raise CommandError("Error while sending a DsReplicaSync for partion %s" % str(part), e)
721 remote_samdb = SamDB(url="ldap://%s" % server,
722 session_info=system_session(),
723 credentials=creds, lp=lp)
725 self.errf.write("Changing userControl and container\n")
726 res = remote_samdb.search(base=str(remote_samdb.get_root_basedn()),
727 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
728 netbios_name.upper(),
729 attrs=["userAccountControl"])
731 uac = int(str(res[0]["userAccountControl"]))
735 "Error while demoting, re-enabling inbound replication\n")
736 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
737 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
739 raise CommandError("Error while changing account control", e)
743 "Error while demoting, re-enabling inbound replication")
744 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
745 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
747 raise CommandError("Unable to find object with samaccountName = %s$"
748 " in the remote dc" % netbios_name.upper())
752 uac ^= (UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION)
753 uac |= UF_WORKSTATION_TRUST_ACCOUNT
758 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
759 ldb.FLAG_MOD_REPLACE,
760 "userAccountControl")
762 remote_samdb.modify(msg)
765 "Error while demoting, re-enabling inbound replication")
766 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
767 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
770 raise CommandError("Error while changing account control", e)
772 parent = msg.dn.parent()
774 rdn = string.replace(rdn, ",%s" % str(parent), "")
775 # Let's move to the Computer container
779 computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.get_root_basedn()))
780 res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
783 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
784 scope=ldb.SCOPE_ONELEVEL)
785 while(len(res) != 0 and i < 100):
787 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
788 scope=ldb.SCOPE_ONELEVEL)
792 "Error while demoting, re-enabling inbound replication\n")
793 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
794 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
800 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
801 ldb.FLAG_MOD_REPLACE,
802 "userAccountControl")
804 remote_samdb.modify(msg)
806 raise CommandError("Unable to find a slot for renaming %s,"
807 " all names from %s-1 to %s-%d seemed used" %
808 (str(dc_dn), rdn, rdn, i - 9))
810 newrdn = "%s-%d" % (rdn, i)
813 newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
814 remote_samdb.rename(dc_dn, newdn)
817 "Error while demoting, re-enabling inbound replication\n")
818 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
819 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
825 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
826 ldb.FLAG_MOD_REPLACE,
827 "userAccountControl")
829 remote_samdb.modify(msg)
830 raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
833 server_dsa_dn = samdb.get_serverName()
834 domain = remote_samdb.get_root_basedn()
837 sendRemoveDsServer(drsuapiBind, drsuapi_handle, server_dsa_dn, domain)
838 except drsException, e:
840 "Error while demoting, re-enabling inbound replication\n")
841 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
842 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
848 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
849 ldb.FLAG_MOD_REPLACE,
850 "userAccountControl")
852 remote_samdb.modify(msg)
853 remote_samdb.rename(newdn, dc_dn)
854 raise CommandError("Error while sending a removeDsServer", e)
856 for s in ("CN=Enterprise,CN=Microsoft System Volumes,CN=System,CN=Configuration",
857 "CN=%s,CN=Microsoft System Volumes,CN=System,CN=Configuration" % lp.get("realm"),
858 "CN=Domain System Volumes (SYSVOL share),CN=File Replication Service,CN=System"):
860 remote_samdb.delete(ldb.Dn(remote_samdb,
861 "%s,%s,%s" % (str(rdn), s, str(remote_samdb.get_root_basedn()))))
862 except ldb.LdbError, l:
865 for s in ("CN=Enterprise,CN=NTFRS Subscriptions",
866 "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
867 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
868 "CN=NTFRS Subscriptions"):
870 remote_samdb.delete(ldb.Dn(remote_samdb,
871 "%s,%s" % (s, str(newdn))))
872 except ldb.LdbError, l:
875 self.errf.write("Demote successfull\n")
878 class cmd_domain_level(Command):
879 """Raise domain and forest function levels."""
881 synopsis = "%prog (show|raise <options>) [options]"
883 takes_optiongroups = {
884 "sambaopts": options.SambaOptions,
885 "credopts": options.CredentialsOptions,
886 "versionopts": options.VersionOptions,
890 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
891 metavar="URL", dest="H"),
892 Option("--quiet", help="Be quiet", action="store_true"),
893 Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2"],
894 help="The forest function level (2003 | 2008 | 2008_R2)"),
895 Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2"],
896 help="The domain function level (2003 | 2008 | 2008_R2)")
899 takes_args = ["subcommand"]
901 def run(self, subcommand, H=None, forest_level=None, domain_level=None,
902 quiet=False, credopts=None, sambaopts=None, versionopts=None):
903 lp = sambaopts.get_loadparm()
904 creds = credopts.get_credentials(lp, fallback_machine=True)
906 samdb = SamDB(url=H, session_info=system_session(),
907 credentials=creds, lp=lp)
909 domain_dn = samdb.domain_dn()
911 res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
912 scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
913 assert len(res_forest) == 1
915 res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
916 attrs=["msDS-Behavior-Version", "nTMixedDomain"])
917 assert len(res_domain) == 1
919 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
920 scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
921 attrs=["msDS-Behavior-Version"])
922 assert len(res_dc_s) >= 1
925 level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
926 level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
927 level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
929 min_level_dc = int(res_dc_s[0]["msDS-Behavior-Version"][0]) # Init value
931 if int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
932 min_level_dc = int(msg["msDS-Behavior-Version"][0])
934 if level_forest < 0 or level_domain < 0:
935 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
937 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
938 if level_forest > level_domain:
939 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
940 if level_domain > min_level_dc:
941 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
944 raise CommandError("Could not retrieve the actual domain, forest level and/or lowest DC function level!")
946 if subcommand == "show":
947 self.message("Domain and forest function level for domain '%s'" % domain_dn)
948 if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
949 self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
950 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
951 self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
952 if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
953 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)!")
957 if level_forest == DS_DOMAIN_FUNCTION_2000:
959 elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
960 outstr = "2003 with mixed domains/interim (NT4 DC support)"
961 elif level_forest == DS_DOMAIN_FUNCTION_2003:
963 elif level_forest == DS_DOMAIN_FUNCTION_2008:
965 elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
968 outstr = "higher than 2008 R2"
969 self.message("Forest function level: (Windows) " + outstr)
971 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
972 outstr = "2000 mixed (NT4 DC support)"
973 elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
975 elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
976 outstr = "2003 with mixed domains/interim (NT4 DC support)"
977 elif level_domain == DS_DOMAIN_FUNCTION_2003:
979 elif level_domain == DS_DOMAIN_FUNCTION_2008:
981 elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
984 outstr = "higher than 2008 R2"
985 self.message("Domain function level: (Windows) " + outstr)
987 if min_level_dc == DS_DOMAIN_FUNCTION_2000:
989 elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
991 elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
993 elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
996 outstr = "higher than 2008 R2"
997 self.message("Lowest function level of a DC: (Windows) " + outstr)
999 elif subcommand == "raise":
1002 if domain_level is not None:
1003 if domain_level == "2003":
1004 new_level_domain = DS_DOMAIN_FUNCTION_2003
1005 elif domain_level == "2008":
1006 new_level_domain = DS_DOMAIN_FUNCTION_2008
1007 elif domain_level == "2008_R2":
1008 new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
1010 if new_level_domain <= level_domain and level_domain_mixed == 0:
1011 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
1013 if new_level_domain > min_level_dc:
1014 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
1016 # Deactivate mixed/interim domain support
1017 if level_domain_mixed != 0:
1018 # Directly on the base DN
1020 m.dn = ldb.Dn(samdb, domain_dn)
1021 m["nTMixedDomain"] = ldb.MessageElement("0",
1022 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1026 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
1027 m["nTMixedDomain"] = ldb.MessageElement("0",
1028 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1031 except ldb.LdbError, (enum, emsg):
1032 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1035 # Directly on the base DN
1037 m.dn = ldb.Dn(samdb, domain_dn)
1038 m["msDS-Behavior-Version"]= ldb.MessageElement(
1039 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1040 "msDS-Behavior-Version")
1044 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
1045 + ",CN=Partitions,%s" % samdb.get_config_basedn())
1046 m["msDS-Behavior-Version"]= ldb.MessageElement(
1047 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1048 "msDS-Behavior-Version")
1051 except ldb.LdbError, (enum, emsg):
1052 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1055 level_domain = new_level_domain
1056 msgs.append("Domain function level changed!")
1058 if forest_level is not None:
1059 if forest_level == "2003":
1060 new_level_forest = DS_DOMAIN_FUNCTION_2003
1061 elif forest_level == "2008":
1062 new_level_forest = DS_DOMAIN_FUNCTION_2008
1063 elif forest_level == "2008_R2":
1064 new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
1065 if new_level_forest <= level_forest:
1066 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1067 if new_level_forest > level_domain:
1068 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1070 m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
1071 m["msDS-Behavior-Version"]= ldb.MessageElement(
1072 str(new_level_forest), ldb.FLAG_MOD_REPLACE,
1073 "msDS-Behavior-Version")
1075 msgs.append("Forest function level changed!")
1076 msgs.append("All changes applied successfully!")
1077 self.message("\n".join(msgs))
1079 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
1082 class cmd_domain_passwordsettings(Command):
1083 """Set password settings.
1085 Password complexity, password lockout policy, history length,
1086 minimum password length, the minimum and maximum password age) on
1087 a Samba AD DC server.
1089 Use against a Windows DC is possible, but group policy will override it.
1092 synopsis = "%prog (show|set <options>) [options]"
1094 takes_optiongroups = {
1095 "sambaopts": options.SambaOptions,
1096 "versionopts": options.VersionOptions,
1097 "credopts": options.CredentialsOptions,
1101 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1102 metavar="URL", dest="H"),
1103 Option("--quiet", help="Be quiet", action="store_true"),
1104 Option("--complexity", type="choice", choices=["on","off","default"],
1105 help="The password complexity (on | off | default). Default is 'on'"),
1106 Option("--store-plaintext", type="choice", choices=["on","off","default"],
1107 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1108 Option("--history-length",
1109 help="The password history length (<integer> | default). Default is 24.", type=str),
1110 Option("--min-pwd-length",
1111 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1112 Option("--min-pwd-age",
1113 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1114 Option("--max-pwd-age",
1115 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1116 Option("--account-lockout-duration",
1117 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),
1118 Option("--account-lockout-threshold",
1119 help="The number of bad password attempts allowed before locking out the account (<integer> | default). Default is 0 (never lock out).", type=str),
1120 Option("--reset-account-lockout-after",
1121 help="After this time is elapsed, the recorded number of attempts restarts from zero (<integer> | default). Default is 30.", type=str),
1124 takes_args = ["subcommand"]
1126 def run(self, subcommand, H=None, min_pwd_age=None, max_pwd_age=None,
1127 quiet=False, complexity=None, store_plaintext=None, history_length=None,
1128 min_pwd_length=None, account_lockout_duration=None, account_lockout_threshold=None,
1129 reset_account_lockout_after=None, credopts=None, sambaopts=None,
1131 lp = sambaopts.get_loadparm()
1132 creds = credopts.get_credentials(lp)
1134 samdb = SamDB(url=H, session_info=system_session(),
1135 credentials=creds, lp=lp)
1137 domain_dn = samdb.domain_dn()
1138 res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1139 attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1140 "minPwdAge", "maxPwdAge", "lockoutDuration", "lockoutThreshold",
1141 "lockOutObservationWindow"])
1142 assert(len(res) == 1)
1144 pwd_props = int(res[0]["pwdProperties"][0])
1145 pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
1146 cur_min_pwd_len = int(res[0]["minPwdLength"][0])
1148 cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1149 if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000:
1152 cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1153 cur_account_lockout_threshold = int(res[0]["lockoutThreshold"][0])
1155 if int(res[0]["lockoutDuration"][0]) == -0x8000000000000000:
1156 cur_account_lockout_duration = 0
1158 cur_account_lockout_duration = abs(int(res[0]["lockoutDuration"][0])) / (1e7 * 60)
1159 cur_reset_account_lockout_after = abs(int(res[0]["lockOutObservationWindow"][0])) / (1e7 * 60)
1160 except Exception, e:
1161 raise CommandError("Could not retrieve password properties!", e)
1163 if subcommand == "show":
1164 self.message("Password informations for domain '%s'" % domain_dn)
1166 if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
1167 self.message("Password complexity: on")
1169 self.message("Password complexity: off")
1170 if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
1171 self.message("Store plaintext passwords: on")
1173 self.message("Store plaintext passwords: off")
1174 self.message("Password history length: %d" % pwd_hist_len)
1175 self.message("Minimum password length: %d" % cur_min_pwd_len)
1176 self.message("Minimum password age (days): %d" % cur_min_pwd_age)
1177 self.message("Maximum password age (days): %d" % cur_max_pwd_age)
1178 self.message("Account lockout duration (mins): %d" % cur_account_lockout_duration)
1179 self.message("Account lockout threshold (attempts): %d" % cur_account_lockout_threshold)
1180 self.message("Reset account lockout after (mins): %d" % cur_reset_account_lockout_after)
1181 elif subcommand == "set":
1184 m.dn = ldb.Dn(samdb, domain_dn)
1186 if complexity is not None:
1187 if complexity == "on" or complexity == "default":
1188 pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
1189 msgs.append("Password complexity activated!")
1190 elif complexity == "off":
1191 pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
1192 msgs.append("Password complexity deactivated!")
1194 if store_plaintext is not None:
1195 if store_plaintext == "on" or store_plaintext == "default":
1196 pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1197 msgs.append("Plaintext password storage for changed passwords activated!")
1198 elif store_plaintext == "off":
1199 pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
1200 msgs.append("Plaintext password storage for changed passwords deactivated!")
1202 if complexity is not None or store_plaintext is not None:
1203 m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
1204 ldb.FLAG_MOD_REPLACE, "pwdProperties")
1206 if history_length is not None:
1207 if history_length == "default":
1210 pwd_hist_len = int(history_length)
1212 if pwd_hist_len < 0 or pwd_hist_len > 24:
1213 raise CommandError("Password history length must be in the range of 0 to 24!")
1215 m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
1216 ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
1217 msgs.append("Password history length changed!")
1219 if min_pwd_length is not None:
1220 if min_pwd_length == "default":
1223 min_pwd_len = int(min_pwd_length)
1225 if min_pwd_len < 0 or min_pwd_len > 14:
1226 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1228 m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
1229 ldb.FLAG_MOD_REPLACE, "minPwdLength")
1230 msgs.append("Minimum password length changed!")
1232 if min_pwd_age is not None:
1233 if min_pwd_age == "default":
1236 min_pwd_age = int(min_pwd_age)
1238 if min_pwd_age < 0 or min_pwd_age > 998:
1239 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1242 min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
1244 m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
1245 ldb.FLAG_MOD_REPLACE, "minPwdAge")
1246 msgs.append("Minimum password age changed!")
1248 if max_pwd_age is not None:
1249 if max_pwd_age == "default":
1252 max_pwd_age = int(max_pwd_age)
1254 if max_pwd_age < 0 or max_pwd_age > 999:
1255 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1258 if max_pwd_age == 0:
1259 max_pwd_age_ticks = -0x8000000000000000
1261 max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
1263 m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
1264 ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1265 msgs.append("Maximum password age changed!")
1267 if account_lockout_duration is not None:
1268 if account_lockout_duration == "default":
1269 account_lockout_duration = 30
1271 account_lockout_duration = int(account_lockout_duration)
1273 if account_lockout_duration < 0 or account_lockout_duration > 99999:
1274 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1277 if account_lockout_duration == 0:
1278 account_lockout_duration_ticks = -0x8000000000000000
1280 account_lockout_duration_ticks = -int(account_lockout_duration * (60 * 1e7))
1282 m["lockoutDuration"] = ldb.MessageElement(str(account_lockout_duration_ticks),
1283 ldb.FLAG_MOD_REPLACE, "lockoutDuration")
1284 msgs.append("Account lockout duration changed!")
1286 if account_lockout_threshold is not None:
1287 if account_lockout_threshold == "default":
1288 account_lockout_threshold = 0
1290 account_lockout_threshold = int(account_lockout_threshold)
1292 m["lockoutThreshold"] = ldb.MessageElement(str(account_lockout_threshold),
1293 ldb.FLAG_MOD_REPLACE, "lockoutThreshold")
1294 msgs.append("Account lockout threshold changed!")
1296 if reset_account_lockout_after is not None:
1297 if reset_account_lockout_after == "default":
1298 reset_account_lockout_after = 30
1300 reset_account_lockout_after = int(reset_account_lockout_after)
1302 if reset_account_lockout_after < 0 or reset_account_lockout_after > 99999:
1303 raise CommandError("Maximum password age must be in the range of 0 to 99999!")
1306 if reset_account_lockout_after == 0:
1307 reset_account_lockout_after_ticks = -0x8000000000000000
1309 reset_account_lockout_after_ticks = -int(reset_account_lockout_after * (60 * 1e7))
1311 m["lockOutObservationWindow"] = ldb.MessageElement(str(reset_account_lockout_after_ticks),
1312 ldb.FLAG_MOD_REPLACE, "lockOutObservationWindow")
1313 msgs.append("Duration to reset account lockout after changed!")
1315 if max_pwd_age > 0 and min_pwd_age >= max_pwd_age:
1316 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
1319 raise CommandError("You must specify at least one option to set. Try --help")
1321 msgs.append("All changes applied successfully!")
1322 self.message("\n".join(msgs))
1324 raise CommandError("Wrong argument '%s'!" % subcommand)
1327 class cmd_domain_classicupgrade(Command):
1328 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1330 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1331 the testparm utility from your classic installation (with --testparm).
1334 synopsis = "%prog [options] <classic_smb_conf>"
1336 takes_optiongroups = {
1337 "sambaopts": options.SambaOptions,
1338 "versionopts": options.VersionOptions
1342 Option("--dbdir", type="string", metavar="DIR",
1343 help="Path to samba classic DC database directory"),
1344 Option("--testparm", type="string", metavar="PATH",
1345 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1346 Option("--targetdir", type="string", metavar="DIR",
1347 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1348 Option("--quiet", help="Be quiet", action="store_true"),
1349 Option("--verbose", help="Be verbose", action="store_true"),
1350 Option("--use-xattrs", type="choice", choices=["yes","no","auto"], metavar="[yes|no|auto]",
1351 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"),
1352 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
1353 action="store_true"),
1354 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
1355 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1356 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1357 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1358 "BIND9_DLZ uses samba4 AD to store zone information, "
1359 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1360 default="SAMBA_INTERNAL")
1363 takes_args = ["smbconf"]
1365 def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
1366 quiet=False, verbose=False, use_xattrs=None, sambaopts=None, versionopts=None,
1367 dns_backend=None, use_ntvfs=False):
1369 if not os.path.exists(smbconf):
1370 raise CommandError("File %s does not exist" % smbconf)
1372 if testparm and not os.path.exists(testparm):
1373 raise CommandError("Testparm utility %s does not exist" % testparm)
1375 if dbdir and not os.path.exists(dbdir):
1376 raise CommandError("Directory %s does not exist" % dbdir)
1378 if not dbdir and not testparm:
1379 raise CommandError("Please specify either dbdir or testparm")
1381 logger = self.get_logger()
1383 logger.setLevel(logging.DEBUG)
1385 logger.setLevel(logging.WARNING)
1387 logger.setLevel(logging.INFO)
1389 if dbdir and testparm:
1390 logger.warning("both dbdir and testparm specified, ignoring dbdir.")
1393 lp = sambaopts.get_loadparm()
1395 s3conf = s3param.get_context()
1398 s3conf.set("realm", sambaopts.realm)
1400 if targetdir is not None:
1401 if not os.path.isdir(targetdir):
1405 if use_xattrs == "yes":
1407 elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
1409 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
1411 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
1414 samba.ntacls.setntacl(lp, tmpfile.name,
1415 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1418 # FIXME: Don't catch all exceptions here
1419 logger.info("You are not root or your system do not support xattr, using tdb backend for attributes. "
1420 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1424 # Set correct default values from dbdir or testparm
1427 paths["state directory"] = dbdir
1428 paths["private dir"] = dbdir
1429 paths["lock directory"] = dbdir
1430 paths["smb passwd file"] = dbdir + "/smbpasswd"
1432 paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
1433 paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
1434 paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
1435 paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
1436 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1437 # "state directory", instead make use of "lock directory"
1438 if len(paths["state directory"]) == 0:
1439 paths["state directory"] = paths["lock directory"]
1442 s3conf.set(p, paths[p])
1444 # load smb.conf parameters
1445 logger.info("Reading smb.conf")
1446 s3conf.load(smbconf)
1447 samba3 = Samba3(smbconf, s3conf)
1449 logger.info("Provisioning")
1450 upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
1451 useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
1454 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
1455 __doc__ = cmd_domain_classicupgrade.__doc__
1457 # This command is present for backwards compatibility only,
1458 # and should not be shown.
1463 class cmd_domain(SuperCommand):
1464 """Domain management."""
1467 subcommands["demote"] = cmd_domain_demote()
1468 if cmd_domain_export_keytab is not None:
1469 subcommands["exportkeytab"] = cmd_domain_export_keytab()
1470 subcommands["info"] = cmd_domain_info()
1471 subcommands["provision"] = cmd_domain_provision()
1472 subcommands["join"] = cmd_domain_join()
1473 subcommands["dcpromo"] = cmd_domain_dcpromo()
1474 subcommands["level"] = cmd_domain_level()
1475 subcommands["passwordsettings"] = cmd_domain_passwordsettings()
1476 subcommands["classicupgrade"] = cmd_domain_classicupgrade()
1477 subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()