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("--adminpass", type="string", metavar="PASSWORD",
171 help="choose admin password (otherwise random)"),
172 Option("--krbtgtpass", type="string", metavar="PASSWORD",
173 help="choose krbtgt password (otherwise random)"),
174 Option("--machinepass", type="string", metavar="PASSWORD",
175 help="choose machine password (otherwise random)"),
176 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
177 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
178 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
179 "BIND9_FLATFILE uses bind9 text database to store zone information, "
180 "BIND9_DLZ uses samba4 AD to store zone information, "
181 "NONE skips the DNS setup entirely (not recommended)",
182 default="SAMBA_INTERNAL"),
183 Option("--dnspass", type="string", metavar="PASSWORD",
184 help="choose dns password (otherwise random)"),
185 Option("--ldapadminpass", type="string", metavar="PASSWORD",
186 help="choose password to set between Samba and it's LDAP backend (otherwise random)"),
187 Option("--root", type="string", metavar="USERNAME",
188 help="choose 'root' unix username"),
189 Option("--nobody", type="string", metavar="USERNAME",
190 help="choose 'nobody' user"),
191 Option("--users", type="string", metavar="GROUPNAME",
192 help="choose 'users' group"),
193 Option("--quiet", help="Be quiet", action="store_true"),
194 Option("--blank", action="store_true",
195 help="do not add users or groups, just the structure"),
196 Option("--ldap-backend-type", type="choice", metavar="LDAP-BACKEND-TYPE",
197 help="Test initialisation support for unsupported LDAP backend type (fedora-ds or openldap) DO NOT USE",
198 choices=["fedora-ds", "openldap"]),
199 Option("--server-role", type="choice", metavar="ROLE",
200 choices=["domain controller", "dc", "member server", "member", "standalone"],
201 help="The server role (domain controller | dc | member server | member | standalone). Default is dc.",
202 default="domain controller"),
203 Option("--function-level", type="choice", metavar="FOR-FUN-LEVEL",
204 choices=["2000", "2003", "2008", "2008_R2"],
205 help="The domain and forest function level (2000 | 2003 | 2008 | 2008_R2 - always native). Default is (Windows) 2003 Native.",
207 Option("--next-rid", type="int", metavar="NEXTRID", default=1000,
208 help="The initial nextRid value (only needed for upgrades). Default is 1000."),
209 Option("--partitions-only",
210 help="Configure Samba's partitions, but do not modify them (ie, join a BDC)", action="store_true"),
211 Option("--targetdir", type="string", metavar="DIR",
212 help="Set target directory"),
213 Option("--ol-mmr-urls", type="string", metavar="LDAPSERVER",
214 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\""),
215 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"),
216 Option("--use-ntvfs", action="store_true", help="Use NTVFS for the fileserver (default = no)"),
217 Option("--use-rfc2307", action="store_true", help="Use AD to store posix attributes (default = no)"),
221 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",
222 action="store_true"),
223 Option("--slapd-path", type="string", metavar="SLAPD-PATH",
224 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."),
225 Option("--ldap-backend-extra-port", type="int", metavar="LDAP-BACKEND-EXTRA-PORT", help="Additional TCP port for LDAP backend server (to use for replication)"),
226 Option("--ldap-backend-forced-uri", type="string", metavar="LDAP-BACKEND-FORCED-URI",
227 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"),
228 Option("--ldap-backend-nosync", help="Configure LDAP backend not to call fsync() (for performance in test environments)", action="store_true"),
231 if os.getenv('TEST_LDAP', "no") == "yes":
232 takes_options.extend(openldap_options)
236 def run(self, sambaopts=None, versionopts=None,
258 ldap_backend_type=None,
262 partitions_only=None,
269 ldap_backend_nosync=None,
270 ldap_backend_extra_port=None,
271 ldap_backend_forced_uri=None,
272 ldap_dryrun_mode=None):
274 self.logger = self.get_logger("provision")
276 self.logger.setLevel(logging.WARNING)
278 self.logger.setLevel(logging.INFO)
280 lp = sambaopts.get_loadparm()
281 smbconf = lp.configfile
283 if dns_forwarder is not None:
284 suggested_forwarder = dns_forwarder
286 suggested_forwarder = self._get_nameserver_ip()
287 if suggested_forwarder is None:
288 suggested_forwarder = "none"
290 if len(self.raw_argv) == 1:
294 from getpass import getpass
297 def ask(prompt, default=None):
298 if default is not None:
299 print "%s [%s]: " % (prompt, default),
301 print "%s: " % (prompt,),
302 return sys.stdin.readline().rstrip("\n") or default
305 default = socket.getfqdn().split(".", 1)[1].upper()
308 realm = ask("Realm", default)
309 if realm in (None, ""):
310 raise CommandError("No realm set!")
313 default = realm.split(".")[0]
316 domain = ask("Domain", default)
318 raise CommandError("No domain set!")
320 server_role = ask("Server Role (dc, member, standalone)", "dc")
322 dns_backend = ask("DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE)", "SAMBA_INTERNAL")
323 if dns_backend in (None, ''):
324 raise CommandError("No DNS backend set!")
326 if dns_backend == "SAMBA_INTERNAL":
327 dns_forwarder = ask("DNS forwarder IP address (write 'none' to disable forwarding)", suggested_forwarder)
328 if dns_forwarder.lower() in (None, 'none'):
329 suggested_forwarder = None
333 adminpassplain = getpass("Administrator password: ")
334 if not adminpassplain:
335 self.errf.write("Invalid administrator password.\n")
337 adminpassverify = getpass("Retype password: ")
338 if not adminpassplain == adminpassverify:
339 self.errf.write("Sorry, passwords do not match.\n")
341 adminpass = adminpassplain
345 realm = sambaopts._lp.get('realm')
347 raise CommandError("No realm set!")
349 raise CommandError("No domain set!")
352 self.logger.info("Administrator password will be set randomly!")
354 if function_level == "2000":
355 dom_for_fun_level = DS_DOMAIN_FUNCTION_2000
356 elif function_level == "2003":
357 dom_for_fun_level = DS_DOMAIN_FUNCTION_2003
358 elif function_level == "2008":
359 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008
360 elif function_level == "2008_R2":
361 dom_for_fun_level = DS_DOMAIN_FUNCTION_2008_R2
363 if dns_backend == "SAMBA_INTERNAL" and dns_forwarder is None:
364 dns_forwarder = suggested_forwarder
366 samdb_fill = FILL_FULL
368 samdb_fill = FILL_NT4SYNC
369 elif partitions_only:
370 samdb_fill = FILL_DRS
372 if targetdir is not None:
373 if not os.path.isdir(targetdir):
378 if use_xattrs == "yes":
380 elif use_xattrs == "auto" and not lp.get("posix:eadb"):
382 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
384 file = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
387 samba.ntacls.setntacl(lp, file.name,
388 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
391 self.logger.info("You are not root or your system do not support xattr, using tdb backend for attributes. ")
396 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.")
397 if ldap_backend_type == "existing":
398 if dap_backend_forced_uri is not None:
399 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)
401 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")
403 if ldap_backend_forced_uri is not None:
404 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")
406 session = system_session()
408 result = provision(self.logger,
409 session, smbconf=smbconf, targetdir=targetdir,
410 samdb_fill=samdb_fill, realm=realm, domain=domain,
411 domainguid=domain_guid, domainsid=domain_sid,
413 hostip=host_ip, hostip6=host_ip6,
415 invocationid=invocationid, adminpass=adminpass,
416 krbtgtpass=krbtgtpass, machinepass=machinepass,
417 dns_backend=dns_backend, dns_forwarder=dns_forwarder,
418 dnspass=dnspass, root=root, nobody=nobody,
420 serverrole=server_role, dom_for_fun_level=dom_for_fun_level,
421 backend_type=ldap_backend_type,
422 ldapadminpass=ldapadminpass, ol_mmr_urls=ol_mmr_urls, slapd_path=slapd_path,
423 useeadb=eadb, next_rid=next_rid, lp=lp, use_ntvfs=use_ntvfs,
424 use_rfc2307=use_rfc2307, skip_sysvolacl=False,
425 ldap_backend_extra_port=ldap_backend_extra_port,
426 ldap_backend_forced_uri=ldap_backend_forced_uri,
427 nosync=ldap_backend_nosync, ldap_dryrun_mode=ldap_dryrun_mode)
429 except ProvisioningError, e:
430 raise CommandError("Provision failed", e)
432 result.report_logger(self.logger)
434 def _get_nameserver_ip(self):
435 """Grab the nameserver IP address from /etc/resolv.conf."""
437 RESOLV_CONF="/etc/resolv.conf"
439 if not path.isfile(RESOLV_CONF):
440 self.logger.warning("Failed to locate %s" % RESOLV_CONF)
445 handle = open(RESOLV_CONF, 'r')
447 if not line.startswith('nameserver'):
449 # we want the last non-space continuous string of the line
450 return line.strip().split()[-1]
452 if handle is not None:
455 self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
458 class cmd_domain_dcpromo(Command):
459 """Promote an existing domain member or NT4 PDC to an AD DC."""
461 synopsis = "%prog <dnsdomain> [DC|RODC] [options]"
463 takes_optiongroups = {
464 "sambaopts": options.SambaOptions,
465 "versionopts": options.VersionOptions,
466 "credopts": options.CredentialsOptions,
470 Option("--server", help="DC to join", type=str),
471 Option("--site", help="site to join", type=str),
472 Option("--targetdir", help="where to store provision", type=str),
473 Option("--domain-critical-only",
474 help="only replicate critical domain objects",
475 action="store_true"),
476 Option("--machinepass", type=str, metavar="PASSWORD",
477 help="choose machine password (otherwise random)"),
478 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
479 action="store_true"),
480 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
481 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
482 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
483 "BIND9_DLZ uses samba4 AD to store zone information, "
484 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
485 default="SAMBA_INTERNAL"),
486 Option("--quiet", help="Be quiet", action="store_true"),
487 Option("--verbose", help="Be verbose", action="store_true")
490 takes_args = ["domain", "role?"]
492 def run(self, domain, role=None, sambaopts=None, credopts=None,
493 versionopts=None, server=None, site=None, targetdir=None,
494 domain_critical_only=False, parent_domain=None, machinepass=None,
495 use_ntvfs=False, dns_backend=None,
496 quiet=False, verbose=False):
497 lp = sambaopts.get_loadparm()
498 creds = credopts.get_credentials(lp)
499 net = Net(creds, lp, server=credopts.ipaddress)
502 site = "Default-First-Site-Name"
504 logger = self.get_logger()
506 logger.setLevel(logging.DEBUG)
508 logger.setLevel(logging.WARNING)
510 logger.setLevel(logging.INFO)
513 site = "Default-First-Site-Name"
515 netbios_name = lp.get("netbios name")
521 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
522 site=site, netbios_name=netbios_name, targetdir=targetdir,
523 domain_critical_only=domain_critical_only,
524 machinepass=machinepass, use_ntvfs=use_ntvfs,
525 dns_backend=dns_backend,
526 promote_existing=True)
528 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
529 site=site, netbios_name=netbios_name, targetdir=targetdir,
530 domain_critical_only=domain_critical_only,
531 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend,
532 promote_existing=True)
534 raise CommandError("Invalid role '%s' (possible values: DC, RODC)" % role)
537 class cmd_domain_join(Command):
538 """Join domain as either member or backup domain controller."""
540 synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|SUBDOMAIN] [options]"
542 takes_optiongroups = {
543 "sambaopts": options.SambaOptions,
544 "versionopts": options.VersionOptions,
545 "credopts": options.CredentialsOptions,
549 Option("--server", help="DC to join", type=str),
550 Option("--site", help="site to join", type=str),
551 Option("--targetdir", help="where to store provision", type=str),
552 Option("--parent-domain", help="parent domain to create subdomain under", type=str),
553 Option("--domain-critical-only",
554 help="only replicate critical domain objects",
555 action="store_true"),
556 Option("--machinepass", type=str, metavar="PASSWORD",
557 help="choose machine password (otherwise random)"),
558 Option("--adminpass", type="string", metavar="PASSWORD",
559 help="choose adminstrator password when joining as a subdomain (otherwise random)"),
560 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
561 action="store_true"),
562 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
563 choices=["SAMBA_INTERNAL", "BIND9_DLZ", "NONE"],
564 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
565 "BIND9_DLZ uses samba4 AD to store zone information, "
566 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
567 default="SAMBA_INTERNAL"),
568 Option("--quiet", help="Be quiet", action="store_true"),
569 Option("--verbose", help="Be verbose", action="store_true")
572 takes_args = ["domain", "role?"]
574 def run(self, domain, role=None, sambaopts=None, credopts=None,
575 versionopts=None, server=None, site=None, targetdir=None,
576 domain_critical_only=False, parent_domain=None, machinepass=None,
577 use_ntvfs=False, dns_backend=None, adminpass=None,
578 quiet=False, verbose=False):
579 lp = sambaopts.get_loadparm()
580 creds = credopts.get_credentials(lp)
581 net = Net(creds, lp, server=credopts.ipaddress)
584 site = "Default-First-Site-Name"
586 logger = self.get_logger()
588 logger.setLevel(logging.DEBUG)
590 logger.setLevel(logging.WARNING)
592 logger.setLevel(logging.INFO)
594 netbios_name = lp.get("netbios name")
599 if role is None or role == "MEMBER":
600 (join_password, sid, domain_name) = net.join_member(
601 domain, netbios_name, LIBNET_JOIN_AUTOMATIC,
602 machinepass=machinepass)
604 self.errf.write("Joined domain %s (%s)\n" % (domain_name, sid))
606 join_DC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
607 site=site, netbios_name=netbios_name, targetdir=targetdir,
608 domain_critical_only=domain_critical_only,
609 machinepass=machinepass, use_ntvfs=use_ntvfs, dns_backend=dns_backend)
611 join_RODC(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
612 site=site, netbios_name=netbios_name, targetdir=targetdir,
613 domain_critical_only=domain_critical_only,
614 machinepass=machinepass, use_ntvfs=use_ntvfs,
615 dns_backend=dns_backend)
616 elif role == "SUBDOMAIN":
618 logger.info("Administrator password will be set randomly!")
620 netbios_domain = lp.get("workgroup")
621 if parent_domain is None:
622 parent_domain = ".".join(domain.split(".")[1:])
623 join_subdomain(logger=logger, server=server, creds=creds, lp=lp, dnsdomain=domain,
624 parent_domain=parent_domain, site=site,
625 netbios_name=netbios_name, netbios_domain=netbios_domain,
626 targetdir=targetdir, machinepass=machinepass,
627 use_ntvfs=use_ntvfs, dns_backend=dns_backend,
630 raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, SUBDOMAIN)" % role)
633 class cmd_domain_demote(Command):
634 """Demote ourselves from the role of Domain Controller."""
636 synopsis = "%prog [options]"
639 Option("--server", help="DC to force replication before demote", type=str),
640 Option("--targetdir", help="where provision is stored", type=str),
643 takes_optiongroups = {
644 "sambaopts": options.SambaOptions,
645 "credopts": options.CredentialsOptions,
646 "versionopts": options.VersionOptions,
649 def run(self, sambaopts=None, credopts=None,
650 versionopts=None, server=None, targetdir=None):
651 lp = sambaopts.get_loadparm()
652 creds = credopts.get_credentials(lp)
653 net = Net(creds, lp, server=credopts.ipaddress)
655 netbios_name = lp.get("netbios name")
656 samdb = SamDB(session_info=system_session(), credentials=creds, lp=lp)
658 res = samdb.search(expression='(&(objectClass=computer)(serverReferenceBL=*))', attrs=["dnsHostName", "name"])
660 raise CommandError("Unable to search for servers")
663 raise CommandError("You are the latest server in the domain")
667 if str(e["name"]).lower() != netbios_name.lower():
668 server = e["dnsHostName"]
671 ntds_guid = samdb.get_ntds_GUID()
672 msg = samdb.search(base=str(samdb.get_config_basedn()),
673 scope=ldb.SCOPE_SUBTREE, expression="(objectGUID=%s)" % ntds_guid,
675 if len(msg) == 0 or "options" not in msg[0]:
676 raise CommandError("Failed to find options on %s" % ntds_guid)
679 dsa_options = int(str(msg[0]['options']))
681 res = samdb.search(expression="(fSMORoleOwner=%s)" % str(ntds_dn),
682 controls=["search_options:1:2"])
685 raise CommandError("Current DC is still the owner of %d role(s), use the role command to transfer roles to another DC" % len(res))
687 self.errf.write("Using %s as partner server for the demotion\n" %
689 (drsuapiBind, drsuapi_handle, supportedExtensions) = drsuapi_connect(server, lp, creds)
691 self.errf.write("Desactivating inbound replication\n")
696 dsa_options |= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
697 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
700 if not (dsa_options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) and not samdb.am_rodc():
702 self.errf.write("Asking partner server %s to synchronize from us\n"
704 for part in (samdb.get_schema_basedn(),
705 samdb.get_config_basedn(),
706 samdb.get_root_basedn()):
708 sendDsReplicaSync(drsuapiBind, drsuapi_handle, ntds_guid, str(part), drsuapi.DRSUAPI_DRS_WRIT_REP)
709 except drsException, e:
711 "Error while demoting, "
712 "re-enabling inbound replication\n")
713 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
714 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
716 raise CommandError("Error while sending a DsReplicaSync for partion %s" % str(part), e)
718 remote_samdb = SamDB(url="ldap://%s" % server,
719 session_info=system_session(),
720 credentials=creds, lp=lp)
722 self.errf.write("Changing userControl and container\n")
723 res = remote_samdb.search(base=str(remote_samdb.get_root_basedn()),
724 expression="(&(objectClass=user)(sAMAccountName=%s$))" %
725 netbios_name.upper(),
726 attrs=["userAccountControl"])
728 uac = int(str(res[0]["userAccountControl"]))
732 "Error while demoting, re-enabling inbound replication\n")
733 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
734 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
736 raise CommandError("Error while changing account control", e)
740 "Error while demoting, re-enabling inbound replication")
741 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
742 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
744 raise CommandError("Unable to find object with samaccountName = %s$"
745 " in the remote dc" % netbios_name.upper())
749 uac ^= (UF_SERVER_TRUST_ACCOUNT|UF_TRUSTED_FOR_DELEGATION)
750 uac |= UF_WORKSTATION_TRUST_ACCOUNT
755 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
756 ldb.FLAG_MOD_REPLACE,
757 "userAccountControl")
759 remote_samdb.modify(msg)
762 "Error while demoting, re-enabling inbound replication")
763 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
764 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
767 raise CommandError("Error while changing account control", e)
769 parent = msg.dn.parent()
771 rdn = string.replace(rdn, ",%s" % str(parent), "")
772 # Let's move to the Computer container
776 computer_dn = ldb.Dn(remote_samdb, "CN=Computers,%s" % str(remote_samdb.get_root_basedn()))
777 res = remote_samdb.search(base=computer_dn, expression=rdn, scope=ldb.SCOPE_ONELEVEL)
780 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
781 scope=ldb.SCOPE_ONELEVEL)
782 while(len(res) != 0 and i < 100):
784 res = remote_samdb.search(base=computer_dn, expression="%s-%d" % (rdn, i),
785 scope=ldb.SCOPE_ONELEVEL)
789 "Error while demoting, re-enabling inbound replication\n")
790 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
791 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
797 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
798 ldb.FLAG_MOD_REPLACE,
799 "userAccountControl")
801 remote_samdb.modify(msg)
803 raise CommandError("Unable to find a slot for renaming %s,"
804 " all names from %s-1 to %s-%d seemed used" %
805 (str(dc_dn), rdn, rdn, i - 9))
807 newrdn = "%s-%d" % (rdn, i)
810 newdn = ldb.Dn(remote_samdb, "%s,%s" % (newrdn, str(computer_dn)))
811 remote_samdb.rename(dc_dn, newdn)
814 "Error while demoting, re-enabling inbound replication\n")
815 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
816 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
822 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
823 ldb.FLAG_MOD_REPLACE,
824 "userAccountControl")
826 remote_samdb.modify(msg)
827 raise CommandError("Error while renaming %s to %s" % (str(dc_dn), str(newdn)), e)
830 server_dsa_dn = samdb.get_serverName()
831 domain = remote_samdb.get_root_basedn()
834 sendRemoveDsServer(drsuapiBind, drsuapi_handle, server_dsa_dn, domain)
835 except drsException, e:
837 "Error while demoting, re-enabling inbound replication\n")
838 dsa_options ^= DS_NTDSDSA_OPT_DISABLE_INBOUND_REPL
839 nmsg["options"] = ldb.MessageElement(str(dsa_options), ldb.FLAG_MOD_REPLACE, "options")
845 msg["userAccountControl"] = ldb.MessageElement("%d" % uac,
846 ldb.FLAG_MOD_REPLACE,
847 "userAccountControl")
849 remote_samdb.modify(msg)
850 remote_samdb.rename(newdn, dc_dn)
851 raise CommandError("Error while sending a removeDsServer", e)
853 for s in ("CN=Entreprise,CN=Microsoft System Volumes,CN=System,CN=Configuration",
854 "CN=%s,CN=Microsoft System Volumes,CN=System,CN=Configuration" % lp.get("realm"),
855 "CN=Domain System Volumes (SYSVOL share),CN=File Replication Service,CN=System"):
857 remote_samdb.delete(ldb.Dn(remote_samdb,
858 "%s,%s,%s" % (str(rdn), s, str(remote_samdb.get_root_basedn()))))
859 except ldb.LdbError, l:
862 for s in ("CN=Entreprise,CN=NTFRS Subscriptions",
863 "CN=%s, CN=NTFRS Subscriptions" % lp.get("realm"),
864 "CN=Domain system Volumes (SYSVOL Share), CN=NTFRS Subscriptions",
865 "CN=NTFRS Subscriptions"):
867 remote_samdb.delete(ldb.Dn(remote_samdb,
868 "%s,%s" % (s, str(newdn))))
869 except ldb.LdbError, l:
872 self.errf.write("Demote successfull\n")
875 class cmd_domain_level(Command):
876 """Raise domain and forest function levels."""
878 synopsis = "%prog (show|raise <options>) [options]"
880 takes_optiongroups = {
881 "sambaopts": options.SambaOptions,
882 "credopts": options.CredentialsOptions,
883 "versionopts": options.VersionOptions,
887 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
888 metavar="URL", dest="H"),
889 Option("--quiet", help="Be quiet", action="store_true"),
890 Option("--forest-level", type="choice", choices=["2003", "2008", "2008_R2"],
891 help="The forest function level (2003 | 2008 | 2008_R2)"),
892 Option("--domain-level", type="choice", choices=["2003", "2008", "2008_R2"],
893 help="The domain function level (2003 | 2008 | 2008_R2)")
896 takes_args = ["subcommand"]
898 def run(self, subcommand, H=None, forest_level=None, domain_level=None,
899 quiet=False, credopts=None, sambaopts=None, versionopts=None):
900 lp = sambaopts.get_loadparm()
901 creds = credopts.get_credentials(lp, fallback_machine=True)
903 samdb = SamDB(url=H, session_info=system_session(),
904 credentials=creds, lp=lp)
906 domain_dn = samdb.domain_dn()
908 res_forest = samdb.search("CN=Partitions,%s" % samdb.get_config_basedn(),
909 scope=ldb.SCOPE_BASE, attrs=["msDS-Behavior-Version"])
910 assert len(res_forest) == 1
912 res_domain = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
913 attrs=["msDS-Behavior-Version", "nTMixedDomain"])
914 assert len(res_domain) == 1
916 res_dc_s = samdb.search("CN=Sites,%s" % samdb.get_config_basedn(),
917 scope=ldb.SCOPE_SUBTREE, expression="(objectClass=nTDSDSA)",
918 attrs=["msDS-Behavior-Version"])
919 assert len(res_dc_s) >= 1
922 level_forest = int(res_forest[0]["msDS-Behavior-Version"][0])
923 level_domain = int(res_domain[0]["msDS-Behavior-Version"][0])
924 level_domain_mixed = int(res_domain[0]["nTMixedDomain"][0])
926 min_level_dc = int(res_dc_s[0]["msDS-Behavior-Version"][0]) # Init value
928 if int(msg["msDS-Behavior-Version"][0]) < min_level_dc:
929 min_level_dc = int(msg["msDS-Behavior-Version"][0])
931 if level_forest < 0 or level_domain < 0:
932 raise CommandError("Domain and/or forest function level(s) is/are invalid. Correct them or reprovision!")
934 raise CommandError("Lowest function level of a DC is invalid. Correct this or reprovision!")
935 if level_forest > level_domain:
936 raise CommandError("Forest function level is higher than the domain level(s). Correct this or reprovision!")
937 if level_domain > min_level_dc:
938 raise CommandError("Domain function level is higher than the lowest function level of a DC. Correct this or reprovision!")
941 raise CommandError("Could not retrieve the actual domain, forest level and/or lowest DC function level!")
943 if subcommand == "show":
944 self.message("Domain and forest function level for domain '%s'" % domain_dn)
945 if level_forest == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
946 self.message("\nATTENTION: You run SAMBA 4 on a forest function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
947 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
948 self.message("\nATTENTION: You run SAMBA 4 on a domain function level lower than Windows 2000 (Native). This isn't supported! Please raise!")
949 if min_level_dc == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
950 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)!")
954 if level_forest == DS_DOMAIN_FUNCTION_2000:
956 elif level_forest == DS_DOMAIN_FUNCTION_2003_MIXED:
957 outstr = "2003 with mixed domains/interim (NT4 DC support)"
958 elif level_forest == DS_DOMAIN_FUNCTION_2003:
960 elif level_forest == DS_DOMAIN_FUNCTION_2008:
962 elif level_forest == DS_DOMAIN_FUNCTION_2008_R2:
965 outstr = "higher than 2008 R2"
966 self.message("Forest function level: (Windows) " + outstr)
968 if level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed != 0:
969 outstr = "2000 mixed (NT4 DC support)"
970 elif level_domain == DS_DOMAIN_FUNCTION_2000 and level_domain_mixed == 0:
972 elif level_domain == DS_DOMAIN_FUNCTION_2003_MIXED:
973 outstr = "2003 with mixed domains/interim (NT4 DC support)"
974 elif level_domain == DS_DOMAIN_FUNCTION_2003:
976 elif level_domain == DS_DOMAIN_FUNCTION_2008:
978 elif level_domain == DS_DOMAIN_FUNCTION_2008_R2:
981 outstr = "higher than 2008 R2"
982 self.message("Domain function level: (Windows) " + outstr)
984 if min_level_dc == DS_DOMAIN_FUNCTION_2000:
986 elif min_level_dc == DS_DOMAIN_FUNCTION_2003:
988 elif min_level_dc == DS_DOMAIN_FUNCTION_2008:
990 elif min_level_dc == DS_DOMAIN_FUNCTION_2008_R2:
993 outstr = "higher than 2008 R2"
994 self.message("Lowest function level of a DC: (Windows) " + outstr)
996 elif subcommand == "raise":
999 if domain_level is not None:
1000 if domain_level == "2003":
1001 new_level_domain = DS_DOMAIN_FUNCTION_2003
1002 elif domain_level == "2008":
1003 new_level_domain = DS_DOMAIN_FUNCTION_2008
1004 elif domain_level == "2008_R2":
1005 new_level_domain = DS_DOMAIN_FUNCTION_2008_R2
1007 if new_level_domain <= level_domain and level_domain_mixed == 0:
1008 raise CommandError("Domain function level can't be smaller than or equal to the actual one!")
1010 if new_level_domain > min_level_dc:
1011 raise CommandError("Domain function level can't be higher than the lowest function level of a DC!")
1013 # Deactivate mixed/interim domain support
1014 if level_domain_mixed != 0:
1015 # Directly on the base DN
1017 m.dn = ldb.Dn(samdb, domain_dn)
1018 m["nTMixedDomain"] = ldb.MessageElement("0",
1019 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1023 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup") + ",CN=Partitions,%s" % samdb.get_config_basedn())
1024 m["nTMixedDomain"] = ldb.MessageElement("0",
1025 ldb.FLAG_MOD_REPLACE, "nTMixedDomain")
1028 except ldb.LdbError, (enum, emsg):
1029 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1032 # Directly on the base DN
1034 m.dn = ldb.Dn(samdb, domain_dn)
1035 m["msDS-Behavior-Version"]= ldb.MessageElement(
1036 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1037 "msDS-Behavior-Version")
1041 m.dn = ldb.Dn(samdb, "CN=" + lp.get("workgroup")
1042 + ",CN=Partitions,%s" % samdb.get_config_basedn())
1043 m["msDS-Behavior-Version"]= ldb.MessageElement(
1044 str(new_level_domain), ldb.FLAG_MOD_REPLACE,
1045 "msDS-Behavior-Version")
1048 except ldb.LdbError, (enum, emsg):
1049 if enum != ldb.ERR_UNWILLING_TO_PERFORM:
1052 level_domain = new_level_domain
1053 msgs.append("Domain function level changed!")
1055 if forest_level is not None:
1056 if forest_level == "2003":
1057 new_level_forest = DS_DOMAIN_FUNCTION_2003
1058 elif forest_level == "2008":
1059 new_level_forest = DS_DOMAIN_FUNCTION_2008
1060 elif forest_level == "2008_R2":
1061 new_level_forest = DS_DOMAIN_FUNCTION_2008_R2
1062 if new_level_forest <= level_forest:
1063 raise CommandError("Forest function level can't be smaller than or equal to the actual one!")
1064 if new_level_forest > level_domain:
1065 raise CommandError("Forest function level can't be higher than the domain function level(s). Please raise it/them first!")
1067 m.dn = ldb.Dn(samdb, "CN=Partitions,%s" % samdb.get_config_basedn())
1068 m["msDS-Behavior-Version"]= ldb.MessageElement(
1069 str(new_level_forest), ldb.FLAG_MOD_REPLACE,
1070 "msDS-Behavior-Version")
1072 msgs.append("Forest function level changed!")
1073 msgs.append("All changes applied successfully!")
1074 self.message("\n".join(msgs))
1076 raise CommandError("invalid argument: '%s' (choose from 'show', 'raise')" % subcommand)
1079 class cmd_domain_passwordsettings(Command):
1080 """Set password settings.
1082 Password complexity, history length, minimum password length, the minimum
1083 and maximum password age) on a Samba4 server.
1086 synopsis = "%prog (show|set <options>) [options]"
1088 takes_optiongroups = {
1089 "sambaopts": options.SambaOptions,
1090 "versionopts": options.VersionOptions,
1091 "credopts": options.CredentialsOptions,
1095 Option("-H", "--URL", help="LDB URL for database or target server", type=str,
1096 metavar="URL", dest="H"),
1097 Option("--quiet", help="Be quiet", action="store_true"),
1098 Option("--complexity", type="choice", choices=["on","off","default"],
1099 help="The password complexity (on | off | default). Default is 'on'"),
1100 Option("--store-plaintext", type="choice", choices=["on","off","default"],
1101 help="Store plaintext passwords where account have 'store passwords with reversible encryption' set (on | off | default). Default is 'off'"),
1102 Option("--history-length",
1103 help="The password history length (<integer> | default). Default is 24.", type=str),
1104 Option("--min-pwd-length",
1105 help="The minimum password length (<integer> | default). Default is 7.", type=str),
1106 Option("--min-pwd-age",
1107 help="The minimum password age (<integer in days> | default). Default is 1.", type=str),
1108 Option("--max-pwd-age",
1109 help="The maximum password age (<integer in days> | default). Default is 43.", type=str),
1112 takes_args = ["subcommand"]
1114 def run(self, subcommand, H=None, min_pwd_age=None, max_pwd_age=None,
1115 quiet=False, complexity=None, store_plaintext=None, history_length=None,
1116 min_pwd_length=None, credopts=None, sambaopts=None,
1118 lp = sambaopts.get_loadparm()
1119 creds = credopts.get_credentials(lp)
1121 samdb = SamDB(url=H, session_info=system_session(),
1122 credentials=creds, lp=lp)
1124 domain_dn = samdb.domain_dn()
1125 res = samdb.search(domain_dn, scope=ldb.SCOPE_BASE,
1126 attrs=["pwdProperties", "pwdHistoryLength", "minPwdLength",
1127 "minPwdAge", "maxPwdAge"])
1128 assert(len(res) == 1)
1130 pwd_props = int(res[0]["pwdProperties"][0])
1131 pwd_hist_len = int(res[0]["pwdHistoryLength"][0])
1132 cur_min_pwd_len = int(res[0]["minPwdLength"][0])
1134 cur_min_pwd_age = int(abs(int(res[0]["minPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1135 if int(res[0]["maxPwdAge"][0]) == -0x8000000000000000:
1138 cur_max_pwd_age = int(abs(int(res[0]["maxPwdAge"][0])) / (1e7 * 60 * 60 * 24))
1139 except Exception, e:
1140 raise CommandError("Could not retrieve password properties!", e)
1142 if subcommand == "show":
1143 self.message("Password informations for domain '%s'" % domain_dn)
1145 if pwd_props & DOMAIN_PASSWORD_COMPLEX != 0:
1146 self.message("Password complexity: on")
1148 self.message("Password complexity: off")
1149 if pwd_props & DOMAIN_PASSWORD_STORE_CLEARTEXT != 0:
1150 self.message("Store plaintext passwords: on")
1152 self.message("Store plaintext passwords: off")
1153 self.message("Password history length: %d" % pwd_hist_len)
1154 self.message("Minimum password length: %d" % cur_min_pwd_len)
1155 self.message("Minimum password age (days): %d" % cur_min_pwd_age)
1156 self.message("Maximum password age (days): %d" % cur_max_pwd_age)
1157 elif subcommand == "set":
1160 m.dn = ldb.Dn(samdb, domain_dn)
1162 if complexity is not None:
1163 if complexity == "on" or complexity == "default":
1164 pwd_props = pwd_props | DOMAIN_PASSWORD_COMPLEX
1165 msgs.append("Password complexity activated!")
1166 elif complexity == "off":
1167 pwd_props = pwd_props & (~DOMAIN_PASSWORD_COMPLEX)
1168 msgs.append("Password complexity deactivated!")
1170 if store_plaintext is not None:
1171 if store_plaintext == "on" or store_plaintext == "default":
1172 pwd_props = pwd_props | DOMAIN_PASSWORD_STORE_CLEARTEXT
1173 msgs.append("Plaintext password storage for changed passwords activated!")
1174 elif store_plaintext == "off":
1175 pwd_props = pwd_props & (~DOMAIN_PASSWORD_STORE_CLEARTEXT)
1176 msgs.append("Plaintext password storage for changed passwords deactivated!")
1178 if complexity is not None or store_plaintext is not None:
1179 m["pwdProperties"] = ldb.MessageElement(str(pwd_props),
1180 ldb.FLAG_MOD_REPLACE, "pwdProperties")
1182 if history_length is not None:
1183 if history_length == "default":
1186 pwd_hist_len = int(history_length)
1188 if pwd_hist_len < 0 or pwd_hist_len > 24:
1189 raise CommandError("Password history length must be in the range of 0 to 24!")
1191 m["pwdHistoryLength"] = ldb.MessageElement(str(pwd_hist_len),
1192 ldb.FLAG_MOD_REPLACE, "pwdHistoryLength")
1193 msgs.append("Password history length changed!")
1195 if min_pwd_length is not None:
1196 if min_pwd_length == "default":
1199 min_pwd_len = int(min_pwd_length)
1201 if min_pwd_len < 0 or min_pwd_len > 14:
1202 raise CommandError("Minimum password length must be in the range of 0 to 14!")
1204 m["minPwdLength"] = ldb.MessageElement(str(min_pwd_len),
1205 ldb.FLAG_MOD_REPLACE, "minPwdLength")
1206 msgs.append("Minimum password length changed!")
1208 if min_pwd_age is not None:
1209 if min_pwd_age == "default":
1212 min_pwd_age = int(min_pwd_age)
1214 if min_pwd_age < 0 or min_pwd_age > 998:
1215 raise CommandError("Minimum password age must be in the range of 0 to 998!")
1218 min_pwd_age_ticks = -int(min_pwd_age * (24 * 60 * 60 * 1e7))
1220 m["minPwdAge"] = ldb.MessageElement(str(min_pwd_age_ticks),
1221 ldb.FLAG_MOD_REPLACE, "minPwdAge")
1222 msgs.append("Minimum password age changed!")
1224 if max_pwd_age is not None:
1225 if max_pwd_age == "default":
1228 max_pwd_age = int(max_pwd_age)
1230 if max_pwd_age < 0 or max_pwd_age > 999:
1231 raise CommandError("Maximum password age must be in the range of 0 to 999!")
1234 if max_pwd_age == 0:
1235 max_pwd_age_ticks = -0x8000000000000000
1237 max_pwd_age_ticks = -int(max_pwd_age * (24 * 60 * 60 * 1e7))
1239 m["maxPwdAge"] = ldb.MessageElement(str(max_pwd_age_ticks),
1240 ldb.FLAG_MOD_REPLACE, "maxPwdAge")
1241 msgs.append("Maximum password age changed!")
1243 if max_pwd_age > 0 and min_pwd_age >= max_pwd_age:
1244 raise CommandError("Maximum password age (%d) must be greater than minimum password age (%d)!" % (max_pwd_age, min_pwd_age))
1247 raise CommandError("You must specify at least one option to set. Try --help")
1249 msgs.append("All changes applied successfully!")
1250 self.message("\n".join(msgs))
1252 raise CommandError("Wrong argument '%s'!" % subcommand)
1255 class cmd_domain_classicupgrade(Command):
1256 """Upgrade from Samba classic (NT4-like) database to Samba AD DC database.
1258 Specify either a directory with all Samba classic DC databases and state files (with --dbdir) or
1259 the testparm utility from your classic installation (with --testparm).
1262 synopsis = "%prog [options] <classic_smb_conf>"
1264 takes_optiongroups = {
1265 "sambaopts": options.SambaOptions,
1266 "versionopts": options.VersionOptions
1270 Option("--dbdir", type="string", metavar="DIR",
1271 help="Path to samba classic DC database directory"),
1272 Option("--testparm", type="string", metavar="PATH",
1273 help="Path to samba classic DC testparm utility from the previous installation. This allows the default paths of the previous installation to be followed"),
1274 Option("--targetdir", type="string", metavar="DIR",
1275 help="Path prefix where the new Samba 4.0 AD domain should be initialised"),
1276 Option("--quiet", help="Be quiet", action="store_true"),
1277 Option("--verbose", help="Be verbose", action="store_true"),
1278 Option("--use-xattrs", type="choice", choices=["yes","no","auto"], metavar="[yes|no|auto]",
1279 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"),
1280 Option("--use-ntvfs", help="Use NTVFS for the fileserver (default = no)",
1281 action="store_true"),
1282 Option("--dns-backend", type="choice", metavar="NAMESERVER-BACKEND",
1283 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
1284 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
1285 "BIND9_FLATFILE uses bind9 text database to store zone information, "
1286 "BIND9_DLZ uses samba4 AD to store zone information, "
1287 "NONE skips the DNS setup entirely (this DC will not be a DNS server)",
1288 default="SAMBA_INTERNAL")
1291 takes_args = ["smbconf"]
1293 def run(self, smbconf=None, targetdir=None, dbdir=None, testparm=None,
1294 quiet=False, verbose=False, use_xattrs=None, sambaopts=None, versionopts=None,
1295 dns_backend=None, use_ntvfs=False):
1297 if not os.path.exists(smbconf):
1298 raise CommandError("File %s does not exist" % smbconf)
1300 if testparm and not os.path.exists(testparm):
1301 raise CommandError("Testparm utility %s does not exist" % testparm)
1303 if dbdir and not os.path.exists(dbdir):
1304 raise CommandError("Directory %s does not exist" % dbdir)
1306 if not dbdir and not testparm:
1307 raise CommandError("Please specify either dbdir or testparm")
1309 logger = self.get_logger()
1311 logger.setLevel(logging.DEBUG)
1313 logger.setLevel(logging.WARNING)
1315 logger.setLevel(logging.INFO)
1317 if dbdir and testparm:
1318 logger.warning("both dbdir and testparm specified, ignoring dbdir.")
1321 lp = sambaopts.get_loadparm()
1323 s3conf = s3param.get_context()
1326 s3conf.set("realm", sambaopts.realm)
1328 if targetdir is not None:
1329 if not os.path.isdir(targetdir):
1333 if use_xattrs == "yes":
1335 elif use_xattrs == "auto" and not s3conf.get("posix:eadb"):
1337 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(targetdir))
1339 tmpfile = tempfile.NamedTemporaryFile(dir=os.path.abspath(os.path.dirname(lp.get("private dir"))))
1342 samba.ntacls.setntacl(lp, tmpfile.name,
1343 "O:S-1-5-32G:S-1-5-32", "S-1-5-32", "native")
1346 # FIXME: Don't catch all exceptions here
1347 logger.info("You are not root or your system do not support xattr, using tdb backend for attributes. "
1348 "If you intend to use this provision in production, rerun the script as root on a system supporting xattrs.")
1352 # Set correct default values from dbdir or testparm
1355 paths["state directory"] = dbdir
1356 paths["private dir"] = dbdir
1357 paths["lock directory"] = dbdir
1358 paths["smb passwd file"] = dbdir + "/smbpasswd"
1360 paths["state directory"] = get_testparm_var(testparm, smbconf, "state directory")
1361 paths["private dir"] = get_testparm_var(testparm, smbconf, "private dir")
1362 paths["smb passwd file"] = get_testparm_var(testparm, smbconf, "smb passwd file")
1363 paths["lock directory"] = get_testparm_var(testparm, smbconf, "lock directory")
1364 # "testparm" from Samba 3 < 3.4.x is not aware of the parameter
1365 # "state directory", instead make use of "lock directory"
1366 if len(paths["state directory"]) == 0:
1367 paths["state directory"] = paths["lock directory"]
1370 s3conf.set(p, paths[p])
1372 # load smb.conf parameters
1373 logger.info("Reading smb.conf")
1374 s3conf.load(smbconf)
1375 samba3 = Samba3(smbconf, s3conf)
1377 logger.info("Provisioning")
1378 upgrade_from_samba3(samba3, logger, targetdir, session_info=system_session(),
1379 useeadb=eadb, dns_backend=dns_backend, use_ntvfs=use_ntvfs)
1382 class cmd_domain_samba3upgrade(cmd_domain_classicupgrade):
1383 __doc__ = cmd_domain_classicupgrade.__doc__
1385 # This command is present for backwards compatibility only,
1386 # and should not be shown.
1391 class cmd_domain(SuperCommand):
1392 """Domain management."""
1395 subcommands["demote"] = cmd_domain_demote()
1396 if cmd_domain_export_keytab is not None:
1397 subcommands["exportkeytab"] = cmd_domain_export_keytab()
1398 subcommands["info"] = cmd_domain_info()
1399 subcommands["provision"] = cmd_domain_provision()
1400 subcommands["join"] = cmd_domain_join()
1401 subcommands["dcpromo"] = cmd_domain_dcpromo()
1402 subcommands["level"] = cmd_domain_level()
1403 subcommands["passwordsettings"] = cmd_domain_passwordsettings()
1404 subcommands["classicupgrade"] = cmd_domain_classicupgrade()
1405 subcommands["samba3upgrade"] = cmd_domain_samba3upgrade()