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 (
76 def get_testparm_var(testparm, smbconf, varname):
77 cmd = "%s -s -l --parameter-name='%s' %s 2>/dev/null" % (testparm, varname, smbconf)
78 output = os.popen(cmd, 'r').readline()
83 class cmd_domain_export_keytab(Command):
84 """Dump Kerberos keys of the domain into a keytab."""
86 synopsis = "%prog <keytab> [options]"
88 takes_optiongroups = {
89 "sambaopts": options.SambaOptions,
90 "credopts": options.CredentialsOptions,
91 "versionopts": options.VersionOptions,
95 Option("--principal", help="extract only this principal", type=str),
98 takes_args = ["keytab"]
100 def run(self, keytab, credopts=None, sambaopts=None, versionopts=None, principal=None):
101 lp = sambaopts.get_loadparm()
103 net.export_keytab(keytab=keytab, principal=principal)
105 cmd_domain_export_keytab = None
108 class cmd_domain_info(Command):
109 """Print basic info about a domain and the DC passed as parameter."""
111 synopsis = "%prog <ip_address> [options]"
116 takes_optiongroups = {
117 "sambaopts": options.SambaOptions,
118 "credopts": options.CredentialsOptions,
119 "versionopts": options.VersionOptions,
122 takes_args = ["address"]
124 def run(self, address, credopts=None, sambaopts=None, versionopts=None):
125 lp = sambaopts.get_loadparm()
127 res = netcmd_get_domain_infos_via_cldap(lp, None, address)
129 raise CommandError("Invalid IP address '" + address + "'!")
130 self.outf.write("Forest : %s\n" % res.forest)
131 self.outf.write("Domain : %s\n" % res.dns_domain)
132 self.outf.write("Netbios domain : %s\n" % res.domain_name)
133 self.outf.write("DC name : %s\n" % res.pdc_dns_name)
134 self.outf.write("DC netbios name : %s\n" % res.pdc_name)
135 self.outf.write("Server site : %s\n" % res.server_site)
136 self.outf.write("Client site : %s\n" % res.client_site)
139 class cmd_domain_provision(Command):
140 """Provision a domain."""
142 synopsis = "%prog [options]"
144 takes_optiongroups = {
145 "sambaopts": options.SambaOptions,
146 "versionopts": options.VersionOptions,
147 "credopts": options.CredentialsOptions,
151 Option("--interactive", help="Ask for names", action="store_true"),
152 Option("--domain", type="string", metavar="DOMAIN",
154 Option("--domain-guid", type="string", metavar="GUID",
155 help="set domainguid (otherwise random)"),
156 Option("--domain-sid", type="string", metavar="SID",
157 help="set domainsid (otherwise random)"),
158 Option("--ntds-guid", type="string", metavar="GUID",
159 help="set NTDS object GUID (otherwise random)"),
160 Option("--invocationid", type="string", metavar="GUID",
161 help="set invocationid (otherwise random)"),
162 Option("--host-name", type="string", metavar="HOSTNAME",
163 help="set hostname"),
164 Option("--host-ip", type="string", metavar="IPADDRESS",
165 help="set IPv4 ipaddress"),
166 Option("--host-ip6", type="string", metavar="IP6ADDRESS",
167 help="set IPv6 ipaddress"),
168 Option("--adminpass", type="string", metavar="PASSWORD",
169 help="choose admin password (otherwise random)"),
170 Option("--krbtgtpass", type="string", metavar="PASSWORD",
171 help="choose krbtgt password (otherwise random)"),
172 Option("--machinepass", type="string", metavar="PASSWORD",
173 help="choose machine password (otherwise random)"),
174 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
175 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
176 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
177 "BIND9_FLATFILE uses bind9 text database to store zone information, "
178 "BIND9_DLZ uses samba4 AD to store zone information, "
179 "NONE skips the DNS setup entirely (not recommended)",
180 default="SAMBA_INTERNAL"),
181 Option("--dnspass", type="string", metavar="PASSWORD",
182 help="choose dns password (otherwise random)"),
183 Option("--ldapadminpass", type="string", metavar="PASSWORD",
184 help="choose password to set between Samba and it's LDAP backend (otherwise random)"),
185 Option("--root", type="string", metavar="USERNAME",
186 help="choose 'root' unix username"),
187 Option("--nobody", type="string", metavar="USERNAME",
188 help="choose 'nobody' user"),
189 Option("--users", type="string", metavar="GROUPNAME",
190 help="choose 'users' group"),
191 Option("--quiet", help="Be quiet", action="store_true"),
192 Option("--blank", action="store_true",
193 help="do not add users or groups, just the structure"),
194 Option("--ldap-backend-type", type="choice", metavar="LDAP-BACKEND-TYPE",
195 help="Test initialisation support for unsupported LDAP backend type (fedora-ds or openldap) DO NOT USE",
196 choices=["fedora-ds", "openldap"]),
197 Option("--server-role", type="choice", metavar="ROLE",
198 choices=["domain controller", "dc", "member server", "member", "standalone"],
199 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
200 default="domain controller"),
201 Option("--function-level", type="choice", metavar="FOR-FUN-LEVEL",
202 choices=["2000", "2003", "2008", "2008_R2"],
203 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2003 Native.",
205 Option("--next-rid", type="int", metavar="NEXTRID", default=1000,
206 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
207 Option("--partitions-only",
208 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"),
209 Option("--targetdir", type="string", metavar="DIR",
210 help="Set target directory"),
211 Option("--ol-mmr-urls", type="string", metavar="LDAPSERVER",
212 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\""),
213 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"),
214 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
215 Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
219 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",
220 action="store_true"),
221 Option("--slapd-path", type="string", metavar="SLAPD-PATH",
222 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."),
223 Option("--ldap-backend-extra-port", type="int", metavar="LDAP-BACKEND-EXTRA-PORT", help="Additional TCP port for LDAP backend server (to use for replication)"),
224 Option("--ldap-backend-forced-uri", type="string", metavar="LDAP-BACKEND-FORCED-URI",
225 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"),
226 Option("--ldap-backend-nosync", help="Configure LDAP backend not to call fsync() (for performance in test environments)", action="store_true"),
229 if os.getenv('TEST_LDAP', "no") == "yes":
230 takes_options.extend(openldap_options)
234 def run(self, sambaopts=None, credopts=None, versionopts=None,
256 ldap_backend_type=None,
260 partitions_only=None,
267 ldap_backend_nosync=None,
268 ldap_backend_extra_port=None,
269 ldap_backend_forced_uri=None,
270 ldap_dryrun_mode=None):
272 self.logger = self.get_logger("provision")
274 self.logger.setLevel(logging.WARNING)
276 self.logger.setLevel(logging.INFO)
278 lp = sambaopts.get_loadparm()
279 smbconf = lp.configfile
281 creds = credopts.get_credentials(lp)
283 creds.set_kerberos_state(DONT_USE_KERBEROS)
285 if dns_forwarder is not None:
286 suggested_forwarder = dns_forwarder
288 suggested_forwarder = self._get_nameserver_ip()
289 if suggested_forwarder is None:
290 suggested_forwarder = "none"
292 if len(self.raw_argv) == 1:
296 from getpass import getpass
299 def ask(prompt, default=None):
300 if default is not None:
301 print "%s [%s]: " % (prompt, default),
303 print "%s: " % (prompt,),
304 return sys.stdin.readline().rstrip("\n") or default
307 default = socket.getfqdn().split(".", 1)[1].upper()
310 realm = ask("Realm", default)
311 if realm in (None, ""):
312 raise CommandError("No realm set!")
315 default = realm.split(".")[0]
318 domain = ask("Domain", default)
320 raise CommandError("No domain set!")
322 server_role = ask("Server Role (dc, member, standalone)", "dc")
324 dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
325 if dns_backend in (None, ''):
326 raise CommandError("No DNS backend set!")
328 if dns_backend == "SAMBA_INTERNAL":
329 dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
330 if dns_forwarder.lower() in (None, 'none'):
331 suggested_forwarder = None
335 adminpassplain = getpass("Administrator password: ")
336 if not adminpassplain:
337 self.errf.write("Invalid administrator password.\n")
339 adminpassverify = getpass("Retype password: ")
340 if not adminpassplain == adminpassverify:
341 self.errf.write("Sorry, passwords do not match.\n")
343 adminpass = adminpassplain
347 realm = sambaopts._lp.get('realm')
349 raise CommandError("No realm set!")
351 raise CommandError("No domain set!")
354 self.logger.info("Administrator password will be set randomly!")
356 if function_level == "2000":
357 dom_for_fun_level = DS_DOMAIN_FUNCTION_2000
358 elif function_level == "2003":
359 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
360 elif function_level == "2008":
361 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
362 elif function_level == "2008_R2":
363 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
365 if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
366 dns_forwarder = suggested_forwarder
368 samdb_fill = FILL_FULL
370 samdb_fill = FILL_NT4SYNC
371 elif partitions_only:
372 samdb_fill = FILL_DRS
374 if targetdir is not None:
375 if not os.path.isdir(targetdir):
380 if use_xattrs == "yes":
382 elif use_xattrs == "auto" and not lp.get("posix:eadb"):
384 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
386 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
389 samba.ntacls.setntacl(lp, file.name,
390 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
393 self.logger.info("You are not root or your system do not support xattr, using tdb backend for attributes. ")
398 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.")
399 if ldap_backend_type == "existing":
400 if dap_backend_forced_uri is not None:
401 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)
403 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")
405 if ldap_backend_forced_uri is not None:
406 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")
408 session = system_session()
410 result = provision(self.logger,
411 session, creds, smbconf=smbconf, targetdir=targetdir,
412 samdb_fill=samdb_fill, realm=realm, domain=domain,
413 domainguid=domain_guid, domainsid=domain_sid,
415 hostip=host_ip, hostip6=host_ip6,
417 invocationid=invocationid, adminpass=adminpass,
418 krbtgtpass=krbtgtpass, machinepass=machinepass,
419 dns_backend=dns_backend, dns_forwarder=dns_forwarder,
420 dnspass=dnspass, root=root, nobody=nobody,
422 serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
423 backend_type=ldap_backend_type,
424 ldapadminpass=ldapadminpass, ol_mmr_urls=ol_mmr_urls, slapd_path=slapd_path,
425 useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
426 use_rfc2307=use_rfc2307, skip_sysvolacl=False,
427 ldap_backend_extra_port=ldap_backend_extra_port,
428 ldap_backend_forced_uri=ldap_backend_forced_uri,
429 nosync=ldap_backend_nosync, ldap_dryrun_mode=ldap_dryrun_mode)
431 except ProvisioningError, e:
432 raise CommandError("Provision failed", e)
434 result.report_logger(self.logger)
436 def _get_nameserver_ip(self):
437 """Grab the nameserver IP address from /etc/resolv.conf."""
439 RESOLV_CONF="/etc/resolv.conf"
441 if not path.isfile(RESOLV_CONF):
442 self.logger.warning("Failed to locate %s" % RESOLV_CONF)
447 handle = open(RESOLV_CONF, 'r')
449 if not line.startswith('nameserver'):
451 # we want the last non-space continuous string of the line
452 return line.strip().split()[-1]
454 if handle is not None:
457 self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
460 class cmd_domain_dcpromo(Command):
461 """Promote an existing domain member or NT4 PDC to an AD DC."""
463 synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
465 takes_optiongroups = {
466 "sambaopts": options.SambaOptions,
467 "versionopts": options.VersionOptions,
468 "credopts": options.CredentialsOptions,
472 Option("--server", help="DC to join", type=str),
473 Option("--site", help="site to join", type=str),
474 Option("--targetdir", help="where to store provision", type=str),
475 Option("--domain-critical-only",
476 help="only replicate critical domain objects",
477 action="store_true"),
478 Option("--machinepass", type=str, metavar="PASSWORD",
479 help="choose machine password (otherwise random)"),
480 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
481 action="store_true"),
482 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
483 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
484 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
485 "BIND9_DLZ uses samba4 AD to store zone information, "
486 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
487 default="SAMBA_INTERNAL"),
488 Option("--quiet", help="Be quiet", action="store_true"),
489 Option("--verbose", help="Be verbose", action="store_true")
492 takes_args = ["domain", "role?"]
494 def run(self, domain, role=None, sambaopts=None, credopts=None,
495 versionopts=None, server=None, site=None, targetdir=None,
496 domain_critical_only=False, parent_domain=None, machinepass=None,
497 use_ntvfs=False, dns_backend=None,
498 quiet=False, verbose=False):
499 lp = sambaopts.get_loadparm()
500 creds = credopts.get_credentials(lp)
501 net = Net(creds, lp, server=credopts.ipaddress)
504 site = "Default-First-Site-Name"
506 logger = self.get_logger()
508 logger.setLevel(logging.DEBUG)
510 logger.setLevel(logging.WARNING)
512 logger.setLevel(logging.INFO)
515 site = "Default-First-Site-Name"
517 netbios_name = lp.get("netbios name")
523 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
524 site=site, netbios_name=netbios_name, targetdir=targetdir,
525 domain_critical_only=domain_critical_only,
526 machinepass=machinepass, use_ntvfs=use_ntvfs,
527 dns_backend=dns_backend,
528 promote_existing=True)
530 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
531 site=site, netbios_name=netbios_name, targetdir=targetdir,
532 domain_critical_only=domain_critical_only,
533 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
534 promote_existing=True)
536 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
539 class cmd_domain_join(Command):
540 """Join domain as either member or backup domain controller."""
542 synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
544 takes_optiongroups = {
545 "sambaopts": options.SambaOptions,
546 "versionopts": options.VersionOptions,
547 "credopts": options.CredentialsOptions,
551 Option("--server", help="DC to join", type=str),
552 Option("--site", help="site to join", type=str),
553 Option("--targetdir", help="where to store provision", type=str),
554 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
555 Option("--domain-critical-only",
556 help="only replicate critical domain objects",
557 action="store_true"),
558 Option("--machinepass", type=str, metavar="PASSWORD",
559 help="choose machine password (otherwise random)"),
560 Option("--adminpass", type="string", metavar="PASSWORD",
561 help="choose adminstrator password when joining as a subdomain (otherwise random)"),
562 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
563 action="store_true"),
564 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
565 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
566 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
567 "BIND9_DLZ uses samba4 AD to store zone information, "
568 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
569 default="SAMBA_INTERNAL"),
570 Option("--quiet", help="Be quiet", action="store_true"),
571 Option("--verbose", help="Be verbose", action="store_true")
574 takes_args = ["domain", "role?"]
576 def run(self, domain, role=None, sambaopts=None, credopts=None,
577 versionopts=None, server=None, site=None, targetdir=None,
578 domain_critical_only=False, parent_domain=None, machinepass=None,
579 use_ntvfs=False, dns_backend=None, adminpass=None,
580 quiet=False, verbose=False):
581 lp = sambaopts.get_loadparm()
582 creds = credopts.get_credentials(lp)
583 net = Net(creds, lp, server=credopts.ipaddress)
586 site = "Default-First-Site-Name"
588 logger = self.get_logger()
590 logger.setLevel(logging.DEBUG)
592 logger.setLevel(logging.WARNING)
594 logger.setLevel(logging.INFO)
596 netbios_name = lp.get("netbios name")
601 if role is None or role == "MEMBER":
602 (join_password, sid, domain_name) = net.join_member(
603 domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
604 machinepass=machinepass)
606 self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
608 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
609 site=site, netbios_name=netbios_name, targetdir=targetdir,
610 domain_critical_only=domain_critical_only,
611 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend)
613 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
614 site=site, netbios_name=netbios_name, targetdir=targetdir,
615 domain_critical_only=domain_critical_only,
616 machinepass=machinepass, use_ntvfs=use_ntvfs,
617 dns_backend=dns_backend)
618 elif role == "SUBDOMAIN":
620 logger.info("Administrator password will be set randomly!")
622 netbios_domain = lp.get("workgroup")
623 if parent_domain is None:
624 parent_domain = ".".join(domain.split(".")[1:])
625 join_subdomain(logger=logger, server=server, creds=creds, lp=lp, dnsdomain=domain,
626 parent_domain=parent_domain, site=site,
627 netbios_name=netbios_name, netbios_domain=netbios_domain,
628 targetdir=targetdir, machinepass=machinepass,
629 use_ntvfs=use_ntvfs, dns_backend=dns_backend,
632 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role)
635 class cmd_domain_demote(Command):
636 """Demote ourselves from the role of Domain Controller."""
638 synopsis = "%prog [options]"
641 Option("--server", help="DC to force replication before demote", type=str),
642 Option("--targetdir", help="where provision is stored", type=str),
645 takes_optiongroups = {
646 "sambaopts": options.SambaOptions,
647 "credopts": options.CredentialsOptions,
648 "versionopts": options.VersionOptions,
651 def run(self, sambaopts=None, credopts=None,
652 versionopts=None, server=None, targetdir=None):
653 lp = sambaopts.get_loadparm()
654 creds = credopts.get_credentials(lp)
655 net = Net(creds, lp, server=credopts.ipaddress)
657 netbios_name = lp.get("netbios name")
658 samdb = SamDB(session_info=system_session(), credentials=creds, lp=lp)
660 res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
662 raise CommandError("Unable to search for servers")
665 raise CommandError("You are the latest server in the domain")
669 if str(e["name"]).lower() != netbios_name.lower():
670 server = e["dnsHostName"]
673 ntds_guid = samdb.get_ntds_GUID()
674 msg = samdb.search(base=str(samdb.get_config_basedn()),
675 scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
677 if len(msg) == 0 or "options" not in msg[0]:
678 raise CommandError("Failed to find options on %s" % ntds_guid)
681 dsa_options = int(str(msg[0]['options']))
683 res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
684 controls=["search_options:1:2"])
687 raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res))
689 self.errf.write("Using %s as partner server for the demotion\n" %
691 (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
693 self.errf.write("Desactivating inbound replication\n")
698 dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
699 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
702 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
704 self.errf.write("Asking partner server %s to synchronize from us\n"
706 for part in (samdb.get_schema_basedn(),
707 samdb.get_config_basedn(),
708 samdb.get_root_basedn()):
710 sendDsReplicaSync(drsuapiBind, drsuapi_handle, ntds_guid, str(part), drsuapi.DRSUAPI_DRS_WRIT_REP)
711 except drsException, e:
713 "Error while demoting, "
714 "re-enabling inbound replication\n")
715 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
716 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
718 raise CommandError("Error while sending a DsReplicaSync for partion %s" % str(part), e)
720 remote_samdb = SamDB(url="ldap://%s" % server,
721 session_info=system_session(),
722 credentials=creds, lp=lp)
724 self.errf.write("Changing userControl and container\n")
725 res = remote_samdb.search(base=str(remote_samdb.get_root_basedn()),
726 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
727 netbios_name.upper(),
728 attrs=["userAccountControl"])
730 uac = int(str(res[0]["userAccountControl"]))
734 "Error while demoting, re-enabling inbound replication\n")
735 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
736 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
738 raise CommandError("Error while changing account control", e)
742 "Error while demoting, re-enabling inbound replication")
743 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
744 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
746 raise CommandError("Unable to find object with samaccountName = %s$"
747 " in the remote dc" % netbios_name.upper())
751 uac ^= (UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION)
752 uac |= UF_WORKSTATION_TRUST_ACCOUNT
757 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
758 ldb.FLAG_MOD_REPLACE,
759 "userAccountControl")
761 remote_samdb.modify(msg)
764 "Error while demoting, re-enabling inbound replication")
765 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
766 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
769 raise CommandError("Error while changing account control", e)
771 parent = msg.dn.parent()
773 rdn = string.replace(rdn, ",%s" % str(parent), "")
774 # Let's move to the Computer container
778 computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.get_root_basedn()))
779 res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
782 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
783 scope=ldb.SCOPE_ONELEVEL)
784 while(len(res) != 0 and i < 100):
786 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
787 scope=ldb.SCOPE_ONELEVEL)
791 "Error while demoting, re-enabling inbound replication\n")
792 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
793 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
799 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
800 ldb.FLAG_MOD_REPLACE,
801 "userAccountControl")
803 remote_samdb.modify(msg)
805 raise CommandError("Unable to find a slot for renaming %s,"
806 " all names from %s-1 to %s-%d seemed used" %
807 (str(dc_dn), rdn, rdn, i - 9))
809 newrdn = "%s-%d" % (rdn, i)
812 newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
813 remote_samdb.rename(dc_dn, newdn)
816 "Error while demoting, re-enabling inbound replication\n")
817 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
818 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
824 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
825 ldb.FLAG_MOD_REPLACE,
826 "userAccountControl")
828 remote_samdb.modify(msg)
829 raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
832 server_dsa_dn = samdb.get_serverName()
833 domain = remote_samdb.get_root_basedn()
836 sendRemoveDsServer(drsuapiBind, drsuapi_handle, server_dsa_dn, domain)
837 except drsException, e:
839 "Error while demoting, re-enabling inbound replication\n")
840 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
841 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
847 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
848 ldb.FLAG_MOD_REPLACE,
849 "userAccountControl")
851 remote_samdb.modify(msg)
852 remote_samdb.rename(newdn, dc_dn)
853 raise CommandError("Error while sending a removeDsServer", e)
855 for s in ("CN=Entreprise,CN=Microsoft System Volumes,CN=System,CN=Configuration",
856 "CN=%s,CN=Microsoft System Volumes,CN=System,CN=Configuration" % lp.get("realm"),
857 "CN=Domain System Volumes (SYSVOL share),CN=File Replication Service,CN=System"):
859 remote_samdb.delete(ldb.Dn(remote_samdb,
860 "%s,%s,%s" % (str(rdn), s, str(remote_samdb.get_root_basedn()))))
861 except ldb.LdbError, l:
864 for s in ("CN=Entreprise,CN=NTFRS Subscriptions",
865 "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
866 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
867 "CN=NTFRS Subscriptions"):
869 remote_samdb.delete(ldb.Dn(remote_samdb,
870 "%s,%s" % (s, str(newdn))))
871 except ldb.LdbError, l:
874 self.errf.write("Demote successfull\n")
877 class cmd_domain_level(Command):
878 """Raise domain and forest function levels."""
880 synopsis = "%prog (show|raise <options>) [options]"
882 takes_optiongroups = {
883 "sambaopts": options.SambaOptions,
884 "credopts": options.CredentialsOptions,
885 "versionopts": options.VersionOptions,
889 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
890 metavar="URL", dest="H"),
891 Option("--quiet", help="Be quiet", action="store_true"),
892 Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2"],
893 help="The forest function level (2003 | 2008 | 2008_R2)"),
894 Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2"],
895 help="The domain function level (2003 | 2008 | 2008_R2)")
898 takes_args = ["subcommand"]
900 def run(self, subcommand, H=None, forest_level=None, domain_level=None,
901 quiet=False, credopts=None, sambaopts=None, versionopts=None):
902 lp = sambaopts.get_loadparm()
903 creds = credopts.get_credentials(lp, fallback_machine=True)
905 samdb = SamDB(url=H, session_info=system_session(),
906 credentials=creds, lp=lp)
908 domain_dn = samdb.domain_dn()
910 res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
911 scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
912 assert len(res_forest) == 1
914 res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
915 attrs=["msDS-Behavior-Version", "nTMixedDomain"])
916 assert len(res_domain) == 1
918 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
919 scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
920 attrs=["msDS-Behavior-Version"])
921 assert len(res_dc_s) >= 1
924 level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
925 level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
926 level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
928 min_level_dc = int(res_dc_s[0]["msDS-Behavior-Version"][0]) # Init value
930 if int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
931 min_level_dc = int(msg["msDS-Behavior-Version"][0])
933 if level_forest < 0 or level_domain < 0:
934 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
936 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
937 if level_forest > level_domain:
938 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
939 if level_domain > min_level_dc:
940 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
943 raise CommandError("Could not retrieve the actual domain, forest level and/or lowest DC function level!")
945 if subcommand == "show":
946 self.message("Domain and forest function level for domain '%s'" % domain_dn)
947 if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
948 self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
949 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
950 self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
951 if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
952 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)!")
956 if level_forest == DS_DOMAIN_FUNCTION_2000:
958 elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
959 outstr = "2003 with mixed domains/interim (NT4 DC support)"
960 elif level_forest == DS_DOMAIN_FUNCTION_2003:
962 elif level_forest == DS_DOMAIN_FUNCTION_2008:
964 elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
967 outstr = "higher than 2008 R2"
968 self.message("Forest function level: (Windows) " + outstr)
970 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
971 outstr = "2000 mixed (NT4 DC support)"
972 elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
974 elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
975 outstr = "2003 with mixed domains/interim (NT4 DC support)"
976 elif level_domain == DS_DOMAIN_FUNCTION_2003:
978 elif level_domain == DS_DOMAIN_FUNCTION_2008:
980 elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
983 outstr = "higher than 2008 R2"
984 self.message("Domain function level: (Windows) " + outstr)
986 if min_level_dc == DS_DOMAIN_FUNCTION_2000:
988 elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
990 elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
992 elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
995 outstr = "higher than 2008 R2"
996 self.message("Lowest function level of a DC: (Windows) " + outstr)
998 elif subcommand == "raise":
1001 if domain_level is not None:
1002 if domain_level == "2003":
1003 new_level_domain = DS_DOMAIN_FUNCTION_2003
1004 elif domain_level == "2008":
1005 new_level_domain = DS_DOMAIN_FUNCTION_2008
1006 elif domain_level == "2008_R2":
1007 new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
1009 if new_level_domain <= level_domain and level_domain_mixed == 0:
1010 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
1012 if new_level_domain > min_level_dc:
1013 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
1015 # Deactivate mixed/interim domain support
1016 if level_domain_mixed != 0:
1017 # Directly on the base DN
1019 m.dn = ldb.Dn(samdb, domain_dn)
1020 m["nTMixedDomain"] = ldb.MessageElement("0",
1021 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1025 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
1026 m["nTMixedDomain"] = ldb.MessageElement("0",
1027 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1030 except ldb.LdbError, (enum, emsg):
1031 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1034 # Directly on the base DN
1036 m.dn = ldb.Dn(samdb, domain_dn)
1037 m["msDS-Behavior-Version"]= ldb.MessageElement(
1038 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1039 "msDS-Behavior-Version")
1043 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
1044 + ",CN=Partitions,%s" % samdb.get_config_basedn())
1045 m["msDS-Behavior-Version"]= ldb.MessageElement(
1046 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1047 "msDS-Behavior-Version")
1050 except ldb.LdbError, (enum, emsg):
1051 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1054 level_domain = new_level_domain
1055 msgs.append("Domain function level changed!")
1057 if forest_level is not None:
1058 if forest_level == "2003":
1059 new_level_forest = DS_DOMAIN_FUNCTION_2003
1060 elif forest_level == "2008":
1061 new_level_forest = DS_DOMAIN_FUNCTION_2008
1062 elif forest_level == "2008_R2":
1063 new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
1064 if new_level_forest <= level_forest:
1065 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1066 if new_level_forest > level_domain:
1067 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1069 m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
1070 m["msDS-Behavior-Version"]= ldb.MessageElement(
1071 str(new_level_forest), ldb.FLAG_MOD_REPLACE,
1072 "msDS-Behavior-Version")
1074 msgs.append("Forest function level changed!")
1075 msgs.append("All changes applied successfully!")
1076 self.message("\n".join(msgs))
1078 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
1081 class cmd_domain_passwordsettings(Command):
1082 """Set password settings.
1084 Password complexity, history length, minimum password length, the minimum
1085 and maximum password age) on a Samba4 server.
1088 synopsis = "%prog (show|set <options>) [options]"
1090 takes_optiongroups = {
1091 "sambaopts": options.SambaOptions,
1092 "versionopts": options.VersionOptions,
1093 "credopts": options.CredentialsOptions,
1097 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1098 metavar="URL", dest="H"),
1099 Option("--quiet", help="Be quiet", action="store_true"),
1100 Option("--complexity", type="choice", choices=["on","off","default"],
1101 help="The password complexity (on | off | default). Default is 'on'"),
1102 Option("--store-plaintext", type="choice", choices=["on","off","default"],
1103 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1104 Option("--history-length",
1105 help="The password history length (<integer> | default). Default is 24.", type=str),
1106 Option("--min-pwd-length",
1107 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1108 Option("--min-pwd-age",
1109 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1110 Option("--max-pwd-age",
1111 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1114 takes_args = ["subcommand"]
1116 def run(self, subcommand, H=None, min_pwd_age=None, max_pwd_age=None,
1117 quiet=False, complexity=None, store_plaintext=None, history_length=None,
1118 min_pwd_length=None, credopts=None, sambaopts=None,
1120 lp = sambaopts.get_loadparm()
1121 creds = credopts.get_credentials(lp)
1123 samdb = SamDB(url=H, session_info=system_session(),
1124 credentials=creds, lp=lp)
1126 domain_dn = samdb.domain_dn()
1127 res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1128 attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1129 "minPwdAge", "maxPwdAge"])
1130 assert(len(res) == 1)
1132 pwd_props = int(res[0]["pwdProperties"][0])
1133 pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
1134 cur_min_pwd_len = int(res[0]["minPwdLength"][0])
1136 cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1137 if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000:
1140 cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1141 except Exception, e:
1142 raise CommandError("Could not retrieve password properties!", e)
1144 if subcommand == "show":
1145 self.message("Password informations for domain '%s'" % domain_dn)
1147 if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
1148 self.message("Password complexity: on")
1150 self.message("Password complexity: off")
1151 if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
1152 self.message("Store plaintext passwords: on")
1154 self.message("Store plaintext passwords: off")
1155 self.message("Password history length: %d" % pwd_hist_len)
1156 self.message("Minimum password length: %d" % cur_min_pwd_len)
1157 self.message("Minimum password age (days): %d" % cur_min_pwd_age)
1158 self.message("Maximum password age (days): %d" % cur_max_pwd_age)
1159 elif subcommand == "set":
1162 m.dn = ldb.Dn(samdb, domain_dn)
1164 if complexity is not None:
1165 if complexity == "on" or complexity == "default":
1166 pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
1167 msgs.append("Password complexity activated!")
1168 elif complexity == "off":
1169 pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
1170 msgs.append("Password complexity deactivated!")
1172 if store_plaintext is not None:
1173 if store_plaintext == "on" or store_plaintext == "default":
1174 pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1175 msgs.append("Plaintext password storage for changed passwords activated!")
1176 elif store_plaintext == "off":
1177 pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
1178 msgs.append("Plaintext password storage for changed passwords deactivated!")
1180 if complexity is not None or store_plaintext is not None:
1181 m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
1182 ldb.FLAG_MOD_REPLACE, "pwdProperties")
1184 if history_length is not None:
1185 if history_length == "default":
1188 pwd_hist_len = int(history_length)
1190 if pwd_hist_len < 0 or pwd_hist_len > 24:
1191 raise CommandError("Password history length must be in the range of 0 to 24!")
1193 m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
1194 ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
1195 msgs.append("Password history length changed!")
1197 if min_pwd_length is not None:
1198 if min_pwd_length == "default":
1201 min_pwd_len = int(min_pwd_length)
1203 if min_pwd_len < 0 or min_pwd_len > 14:
1204 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1206 m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
1207 ldb.FLAG_MOD_REPLACE, "minPwdLength")
1208 msgs.append("Minimum password length changed!")
1210 if min_pwd_age is not None:
1211 if min_pwd_age == "default":
1214 min_pwd_age = int(min_pwd_age)
1216 if min_pwd_age < 0 or min_pwd_age > 998:
1217 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1220 min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
1222 m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
1223 ldb.FLAG_MOD_REPLACE, "minPwdAge")
1224 msgs.append("Minimum password age changed!")
1226 if max_pwd_age is not None:
1227 if max_pwd_age == "default":
1230 max_pwd_age = int(max_pwd_age)
1232 if max_pwd_age < 0 or max_pwd_age > 999:
1233 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1236 if max_pwd_age == 0:
1237 max_pwd_age_ticks = -0x8000000000000000
1239 max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
1241 m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
1242 ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1243 msgs.append("Maximum password age changed!")
1245 if max_pwd_age > 0 and min_pwd_age >= max_pwd_age:
1246 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
1249 raise CommandError("You must specify at least one option to set. Try --help")
1251 msgs.append("All changes applied successfully!")
1252 self.message("\n".join(msgs))
1254 raise CommandError("Wrong argument '%s'!" % subcommand)
1257 class cmd_domain_classicupgrade(Command):
1258 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1260 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1261 the testparm utility from your classic installation (with --testparm).
1264 synopsis = "%prog [options] <classic_smb_conf>"
1266 takes_optiongroups = {
1267 "sambaopts": options.SambaOptions,
1268 "versionopts": options.VersionOptions
1272 Option("--dbdir", type="string", metavar="DIR",
1273 help="Path to samba classic DC database directory"),
1274 Option("--testparm", type="string", metavar="PATH",
1275 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1276 Option("--targetdir", type="string", metavar="DIR",
1277 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1278 Option("--quiet", help="Be quiet", action="store_true"),
1279 Option("--verbose", help="Be verbose", action="store_true"),
1280 Option("--use-xattrs", type="choice", choices=["yes","no","auto"], metavar="[yes|no|auto]",
1281 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"),
1282 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
1283 action="store_true"),
1284 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
1285 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1286 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1287 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1288 "BIND9_DLZ uses samba4 AD to store zone information, "
1289 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1290 default="SAMBA_INTERNAL")
1293 takes_args = ["smbconf"]
1295 def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
1296 quiet=False, verbose=False, use_xattrs=None, sambaopts=None, versionopts=None,
1297 dns_backend=None, use_ntvfs=False):
1299 if not os.path.exists(smbconf):
1300 raise CommandError("File %s does not exist" % smbconf)
1302 if testparm and not os.path.exists(testparm):
1303 raise CommandError("Testparm utility %s does not exist" % testparm)
1305 if dbdir and not os.path.exists(dbdir):
1306 raise CommandError("Directory %s does not exist" % dbdir)
1308 if not dbdir and not testparm:
1309 raise CommandError("Please specify either dbdir or testparm")
1311 logger = self.get_logger()
1313 logger.setLevel(logging.DEBUG)
1315 logger.setLevel(logging.WARNING)
1317 logger.setLevel(logging.INFO)
1319 if dbdir and testparm:
1320 logger.warning("both dbdir and testparm specified, ignoring dbdir.")
1323 lp = sambaopts.get_loadparm()
1325 s3conf = s3param.get_context()
1328 s3conf.set("realm", sambaopts.realm)
1330 if targetdir is not None:
1331 if not os.path.isdir(targetdir):
1335 if use_xattrs == "yes":
1337 elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
1339 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
1341 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
1344 samba.ntacls.setntacl(lp, tmpfile.name,
1345 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1348 # FIXME: Don't catch all exceptions here
1349 logger.info("You are not root or your system do not support xattr, using tdb backend for attributes. "
1350 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1354 # Set correct default values from dbdir or testparm
1357 paths["state directory"] = dbdir
1358 paths["private dir"] = dbdir
1359 paths["lock directory"] = dbdir
1360 paths["smb passwd file"] = dbdir + "/smbpasswd"
1362 paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
1363 paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
1364 paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
1365 paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
1366 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1367 # "state directory", instead make use of "lock directory"
1368 if len(paths["state directory"]) == 0:
1369 paths["state directory"] = paths["lock directory"]
1372 s3conf.set(p, paths[p])
1374 # load smb.conf parameters
1375 logger.info("Reading smb.conf")
1376 s3conf.load(smbconf)
1377 samba3 = Samba3(smbconf, s3conf)
1379 logger.info("Provisioning")
1380 upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
1381 useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
1384 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
1385 __doc__ = cmd_domain_classicupgrade.__doc__
1387 # This command is present for backwards compatibility only,
1388 # and should not be shown.
1393 class cmd_domain(SuperCommand):
1394 """Domain management."""
1397 subcommands["demote"] = cmd_domain_demote()
1398 if cmd_domain_export_keytab is not None:
1399 subcommands["exportkeytab"] = cmd_domain_export_keytab()
1400 subcommands["info"] = cmd_domain_info()
1401 subcommands["provision"] = cmd_domain_provision()
1402 subcommands["join"] = cmd_domain_join()
1403 subcommands["dcpromo"] = cmd_domain_dcpromo()
1404 subcommands["level"] = cmd_domain_level()
1405 subcommands["passwordsettings"] = cmd_domain_passwordsettings()
1406 subcommands["classicupgrade"] = cmd_domain_classicupgrade()
1407 subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()