2 # Unix SMB/CIFS implementation.
3 # backend code for provisioning a Samba4 server
5 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
6 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008-2009
7 # Copyright (C) Oliver Liebel <oliver@itc.li> 2008-2009
9 # Based on the original in EJS:
10 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
12 # This program is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 3 of the License, or
15 # (at your option) any later version.
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
22 # You should have received a copy of the GNU General Public License
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 """Functions for setting up a Samba configuration."""
28 from base64 import b64encode
43 from credentials import Credentials, DONT_USE_KERBEROS
44 from auth import system_session, admin_session
45 from samba import version, Ldb, substitute_var, valid_netbios_name, check_all_substituted, \
47 from samba.samdb import SamDB
48 from samba.idmap import IDmapDB
49 from samba.dcerpc import security
51 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, timestring
52 from ms_schema import read_ms_schema
53 from ms_display_specifiers import read_ms_ldif
54 from signal import SIGTERM
56 __docformat__ = "restructuredText"
59 class ProvisioningError(ValueError):
64 """Find the setup directory used by provision."""
65 dirname = os.path.dirname(__file__)
66 if "/site-packages/" in dirname:
67 prefix = "/".join(dirname[:dirname.index("/site-packages/")].split("/")[:-2])
68 for suffix in ["share/setup", "share/samba/setup", "setup"]:
69 ret = os.path.join(prefix, suffix)
70 if os.path.isdir(ret):
73 ret = os.path.join(dirname, "../../../setup")
74 if os.path.isdir(ret):
76 raise Exception("Unable to find setup directory.")
79 DEFAULTSITE = "Default-First-Site-Name"
81 class InvalidNetbiosName(Exception):
82 """A specified name was not a valid NetBIOS name."""
83 def __init__(self, name):
84 super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)
87 class ProvisionPaths(object):
100 self.dns_keytab = None
103 self.private_dir = None
105 self.slapdconf = None
106 self.modulesconf = None
107 self.memberofconf = None
108 self.fedoradsinf = None
109 self.fedoradspartitions = None
110 self.fedoradssasl = None
112 self.olmmrserveridsconf = None
113 self.olmmrsyncreplconf = None
116 self.olcseedldif = None
119 class ProvisionNames(object):
126 self.ldapmanagerdn = None
127 self.dnsdomain = None
129 self.netbiosname = None
136 class ProvisionResult(object):
143 class Schema(object):
144 def __init__(self, setup_path, schemadn=None,
145 serverdn=None, sambadn=None, ldap_backend_type=None):
146 """Load schema for the SamDB from the AD schema files and samba4_schema.ldif
148 :param samdb: Load a schema into a SamDB.
149 :param setup_path: Setup path function.
150 :param schemadn: DN of the schema
151 :param serverdn: DN of the server
153 Returns the schema data loaded, to avoid double-parsing when then needing to add it to the db
157 self.schema_data = read_ms_schema(setup_path('ad-schema/MS-AD_Schema_2K8_Attributes.txt'),
158 setup_path('ad-schema/MS-AD_Schema_2K8_Classes.txt'))
159 self.schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
160 self.schema_data = substitute_var(self.schema_data, {"SCHEMADN": schemadn})
161 check_all_substituted(self.schema_data)
163 self.schema_dn_modify = read_and_sub_file(setup_path("provision_schema_basedn_modify.ldif"),
164 {"SCHEMADN": schemadn,
165 "SERVERDN": serverdn,
167 self.schema_dn_add = read_and_sub_file(setup_path("provision_schema_basedn.ldif"),
168 {"SCHEMADN": schemadn
171 prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
172 prefixmap = b64encode(prefixmap)
174 # We don't actually add this ldif, just parse it
175 prefixmap_ldif = "dn: cn=schema\nprefixMap:: %s\n\n" % prefixmap
176 self.ldb.set_schema_from_ldif(prefixmap_ldif, self.schema_data)
179 # Return a hash with the forward attribute as a key and the back as the value
180 def get_linked_attributes(schemadn,schemaldb):
181 attrs = ["linkID", "lDAPDisplayName"]
182 res = schemaldb.search(expression="(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1))", base=schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
184 for i in range (0, len(res)):
185 expression = "(&(objectclass=attributeSchema)(linkID=%d)(attributeSyntax=2.5.5.1))" % (int(res[i]["linkID"][0])+1)
186 target = schemaldb.searchone(basedn=schemadn,
187 expression=expression,
188 attribute="lDAPDisplayName",
190 if target is not None:
191 attributes[str(res[i]["lDAPDisplayName"])]=str(target)
195 def get_dnsyntax_attributes(schemadn,schemaldb):
196 attrs = ["linkID", "lDAPDisplayName"]
197 res = schemaldb.search(expression="(&(!(linkID=*))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1))", base=schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
199 for i in range (0, len(res)):
200 attributes.append(str(res[i]["lDAPDisplayName"]))
205 def check_install(lp, session_info, credentials):
206 """Check whether the current install seems ok.
208 :param lp: Loadparm context
209 :param session_info: Session information
210 :param credentials: Credentials
212 if lp.get("realm") == "":
213 raise Exception("Realm empty")
214 ldb = Ldb(lp.get("sam database"), session_info=session_info,
215 credentials=credentials, lp=lp)
216 if len(ldb.search("(cn=Administrator)")) != 1:
217 raise ProvisioningError("No administrator account found")
220 def findnss(nssfn, names):
221 """Find a user or group from a list of possibilities.
223 :param nssfn: NSS Function to try (should raise KeyError if not found)
224 :param names: Names to check.
225 :return: Value return by first names list.
232 raise KeyError("Unable to find user/group %r" % names)
235 findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
236 findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
239 def read_and_sub_file(file, subst_vars):
240 """Read a file and sub in variables found in it
242 :param file: File to be read (typically from setup directory)
243 param subst_vars: Optional variables to subsitute in the file.
245 data = open(file, 'r').read()
246 if subst_vars is not None:
247 data = substitute_var(data, subst_vars)
248 check_all_substituted(data)
252 def setup_add_ldif(ldb, ldif_path, subst_vars=None):
253 """Setup a ldb in the private dir.
255 :param ldb: LDB file to import data into
256 :param ldif_path: Path of the LDIF file to load
257 :param subst_vars: Optional variables to subsitute in LDIF.
259 assert isinstance(ldif_path, str)
261 data = read_and_sub_file(ldif_path, subst_vars)
265 def setup_modify_ldif(ldb, ldif_path, subst_vars=None):
266 """Modify a ldb in the private dir.
268 :param ldb: LDB object.
269 :param ldif_path: LDIF file path.
270 :param subst_vars: Optional dictionary with substitution variables.
272 data = read_and_sub_file(ldif_path, subst_vars)
274 ldb.modify_ldif(data)
277 def setup_ldb(ldb, ldif_path, subst_vars):
278 """Import a LDIF a file into a LDB handle, optionally substituting variables.
280 :note: Either all LDIF data will be added or none (using transactions).
282 :param ldb: LDB file to import into.
283 :param ldif_path: Path to the LDIF file.
284 :param subst_vars: Dictionary with substitution variables.
286 assert ldb is not None
287 ldb.transaction_start()
289 setup_add_ldif(ldb, ldif_path, subst_vars)
291 ldb.transaction_cancel()
293 ldb.transaction_commit()
296 def setup_file(template, fname, subst_vars):
297 """Setup a file in the private dir.
299 :param template: Path of the template file.
300 :param fname: Path of the file to create.
301 :param subst_vars: Substitution variables.
305 if os.path.exists(f):
308 data = read_and_sub_file(template, subst_vars)
309 open(f, 'w').write(data)
312 def provision_paths_from_lp(lp, dnsdomain):
313 """Set the default paths for provisioning.
315 :param lp: Loadparm context.
316 :param dnsdomain: DNS Domain name
318 paths = ProvisionPaths()
319 paths.private_dir = lp.get("private dir")
320 paths.keytab = "secrets.keytab"
321 paths.dns_keytab = "dns.keytab"
323 paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
324 paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb")
325 paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb")
326 paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
327 paths.dns = os.path.join(paths.private_dir, dnsdomain + ".zone")
328 paths.namedconf = os.path.join(paths.private_dir, "named.conf")
329 paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
330 paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
331 paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
332 paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
333 paths.phpldapadminconfig = os.path.join(paths.private_dir,
334 "phpldapadmin-config.php")
335 paths.ldapdir = os.path.join(paths.private_dir,
337 paths.slapdconf = os.path.join(paths.ldapdir,
339 paths.slapdpid = os.path.join(paths.ldapdir,
341 paths.modulesconf = os.path.join(paths.ldapdir,
343 paths.memberofconf = os.path.join(paths.ldapdir,
345 paths.fedoradsinf = os.path.join(paths.ldapdir,
347 paths.fedoradspartitions = os.path.join(paths.ldapdir,
348 "fedorads-partitions.ldif")
349 paths.fedoradssasl = os.path.join(paths.ldapdir,
350 "fedorads-sasl.ldif")
351 paths.fedoradssamba = os.path.join(paths.ldapdir,
352 "fedorads-samba.ldif")
353 paths.olmmrserveridsconf = os.path.join(paths.ldapdir,
354 "mmr_serverids.conf")
355 paths.olmmrsyncreplconf = os.path.join(paths.ldapdir,
357 paths.olcdir = os.path.join(paths.ldapdir,
359 paths.olcseedldif = os.path.join(paths.ldapdir,
361 paths.hklm = "hklm.ldb"
362 paths.hkcr = "hkcr.ldb"
363 paths.hkcu = "hkcu.ldb"
364 paths.hku = "hku.ldb"
365 paths.hkpd = "hkpd.ldb"
366 paths.hkpt = "hkpt.ldb"
368 paths.sysvol = lp.get("path", "sysvol")
370 paths.netlogon = lp.get("path", "netlogon")
372 paths.smbconf = lp.configfile
377 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
378 serverrole=None, rootdn=None, domaindn=None, configdn=None,
379 schemadn=None, serverdn=None, sitename=None, sambadn=None):
380 """Guess configuration settings to use."""
383 hostname = socket.gethostname().split(".")[0].lower()
385 netbiosname = hostname.upper()
386 if not valid_netbios_name(netbiosname):
387 raise InvalidNetbiosName(netbiosname)
389 hostname = hostname.lower()
391 if dnsdomain is None:
392 dnsdomain = lp.get("realm")
394 if serverrole is None:
395 serverrole = lp.get("server role")
397 assert dnsdomain is not None
398 realm = dnsdomain.upper()
400 if lp.get("realm").upper() != realm:
401 raise Exception("realm '%s' in %s must match chosen realm '%s'" %
402 (lp.get("realm"), lp.configfile, realm))
404 dnsdomain = dnsdomain.lower()
406 if serverrole == "domain controller":
408 domain = lp.get("workgroup")
410 domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
411 if lp.get("workgroup").upper() != domain.upper():
412 raise Exception("workgroup '%s' in smb.conf must match chosen domain '%s'",
413 lp.get("workgroup"), domain)
417 domaindn = "CN=" + netbiosname
419 assert domain is not None
420 domain = domain.upper()
421 if not valid_netbios_name(domain):
422 raise InvalidNetbiosName(domain)
424 if netbiosname.upper() == realm.upper():
425 raise Exception("realm %s must not be equal to netbios domain name %s", realm, netbiosname)
427 if hostname.upper() == realm.upper():
428 raise Exception("realm %s must not be equal to hostname %s", realm, hostname)
430 if domain.upper() == realm.upper():
431 raise Exception("realm %s must not be equal to domain name %s", realm, domain)
437 configdn = "CN=Configuration," + rootdn
439 schemadn = "CN=Schema," + configdn
446 names = ProvisionNames()
447 names.rootdn = rootdn
448 names.domaindn = domaindn
449 names.configdn = configdn
450 names.schemadn = schemadn
451 names.sambadn = sambadn
452 names.ldapmanagerdn = "CN=Manager," + rootdn
453 names.dnsdomain = dnsdomain
454 names.domain = domain
456 names.netbiosname = netbiosname
457 names.hostname = hostname
458 names.sitename = sitename
459 names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (netbiosname, sitename, configdn)
464 def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
466 """Create a new smb.conf file based on a couple of basic settings.
468 assert smbconf is not None
470 hostname = socket.gethostname().split(".")[0].lower()
472 if serverrole is None:
473 serverrole = "standalone"
475 assert serverrole in ("domain controller", "member server", "standalone")
476 if serverrole == "domain controller":
478 elif serverrole == "member server":
479 smbconfsuffix = "member"
480 elif serverrole == "standalone":
481 smbconfsuffix = "standalone"
483 assert domain is not None
484 assert realm is not None
486 default_lp = param.LoadParm()
487 #Load non-existant file
488 if os.path.exists(smbconf):
489 default_lp.load(smbconf)
491 if targetdir is not None:
492 privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
493 lockdir_line = "lock dir = " + os.path.abspath(targetdir)
495 default_lp.set("lock dir", os.path.abspath(targetdir))
500 sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
501 netlogon = os.path.join(sysvol, realm.lower(), "scripts")
503 setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
505 "HOSTNAME": hostname,
508 "SERVERROLE": serverrole,
509 "NETLOGONPATH": netlogon,
510 "SYSVOLPATH": sysvol,
511 "PRIVATEDIR_LINE": privatedir_line,
512 "LOCKDIR_LINE": lockdir_line
516 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
517 users_gid, wheel_gid):
518 """setup reasonable name mappings for sam names to unix names.
520 :param samdb: SamDB object.
521 :param idmap: IDmap db object.
522 :param sid: The domain sid.
523 :param domaindn: The domain DN.
524 :param root_uid: uid of the UNIX root user.
525 :param nobody_uid: uid of the UNIX nobody user.
526 :param users_gid: gid of the UNIX users group.
527 :param wheel_gid: gid of the UNIX wheel group."""
529 idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
530 idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
532 idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
533 idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
535 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
537 serverrole, ldap_backend=None,
539 """Setup the partitions for the SAM database.
541 Alternatively, provision() may call this, and then populate the database.
543 :note: This will wipe the Sam Database!
545 :note: This function always removes the local SAM LDB file. The erase
546 parameter controls whether to erase the existing data, which
547 may not be stored locally but in LDAP.
549 assert session_info is not None
551 # We use options=["modules:"] to stop the modules loading - we
552 # just want to wipe and re-initialise the database, not start it up
555 samdb = Ldb(url=samdb_path, session_info=session_info,
556 credentials=credentials, lp=lp, options=["modules:"])
558 samdb.erase_except_schema_controlled()
560 os.unlink(samdb_path)
561 samdb = Ldb(url=samdb_path, session_info=session_info,
562 credentials=credentials, lp=lp, options=["modules:"])
564 samdb.erase_except_schema_controlled()
567 #Add modules to the list to activate them by default
568 #beware often order is important
570 # Some Known ordering constraints:
571 # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
572 # - objectclass must be before password_hash, because password_hash checks
573 # that the objectclass is of type person (filled in by objectclass
574 # module when expanding the objectclass list)
575 # - partition must be last
576 # - each partition has its own module list then
577 modules_list = ["rootdse",
595 "extended_dn_out_ldb"]
596 modules_list2 = ["show_deleted",
599 domaindn_ldb = "users.ldb"
600 configdn_ldb = "configuration.ldb"
601 schemadn_ldb = "schema.ldb"
602 if ldap_backend is not None:
603 domaindn_ldb = ldap_backend.ldapi_uri
604 configdn_ldb = ldap_backend.ldapi_uri
605 schemadn_ldb = ldap_backend.ldapi_uri
607 if ldap_backend.ldap_backend_type == "fedora-ds":
608 backend_modules = ["nsuniqueid", "paged_searches"]
609 # We can handle linked attributes here, as we don't have directory-side subtree operations
610 tdb_modules_list = ["linked_attributes", "extended_dn_out_dereference"]
611 elif ldap_backend.ldap_backend_type == "openldap":
612 backend_modules = ["entryuuid", "paged_searches"]
613 # OpenLDAP handles subtree renames, so we don't want to do any of these things
614 tdb_modules_list = ["extended_dn_out_dereference"]
616 elif serverrole == "domain controller":
617 tdb_modules_list.insert(0, "repl_meta_data")
620 backend_modules = ["objectguid"]
622 if tdb_modules_list is None:
623 tdb_modules_list_as_string = ""
625 tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
627 samdb.transaction_start()
629 message("Setting up sam.ldb partitions and settings")
630 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
631 "SCHEMADN": names.schemadn,
632 "SCHEMADN_LDB": schemadn_ldb,
633 "SCHEMADN_MOD2": ",objectguid",
634 "CONFIGDN": names.configdn,
635 "CONFIGDN_LDB": configdn_ldb,
636 "DOMAINDN": names.domaindn,
637 "DOMAINDN_LDB": domaindn_ldb,
638 "SCHEMADN_MOD": "schema_fsmo,instancetype",
639 "CONFIGDN_MOD": "naming_fsmo,instancetype",
640 "DOMAINDN_MOD": "pdc_fsmo,instancetype",
641 "MODULES_LIST": ",".join(modules_list),
642 "TDB_MODULES_LIST": tdb_modules_list_as_string,
643 "MODULES_LIST2": ",".join(modules_list2),
644 "BACKEND_MOD": ",".join(backend_modules),
647 samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
649 message("Setting up sam.ldb rootDSE")
650 setup_samdb_rootdse(samdb, setup_path, names)
653 samdb.transaction_cancel()
656 samdb.transaction_commit()
660 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain,
661 netbiosname, domainsid, keytab_path, samdb_url,
662 dns_keytab_path, dnspass, machinepass):
663 """Add DC-specific bits to a secrets database.
665 :param secretsdb: Ldb Handle to the secrets database
666 :param setup_path: Setup path function
667 :param machinepass: Machine password
669 setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), {
670 "MACHINEPASS_B64": b64encode(machinepass),
673 "DNSDOMAIN": dnsdomain,
674 "DOMAINSID": str(domainsid),
675 "SECRETS_KEYTAB": keytab_path,
676 "NETBIOSNAME": netbiosname,
677 "SAM_LDB": samdb_url,
678 "DNS_KEYTAB": dns_keytab_path,
679 "DNSPASS_B64": b64encode(dnspass),
683 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
684 """Setup the secrets database.
686 :param path: Path to the secrets database.
687 :param setup_path: Get the path to a setup file.
688 :param session_info: Session info.
689 :param credentials: Credentials
690 :param lp: Loadparm context
691 :return: LDB handle for the created secrets database
693 if os.path.exists(path):
695 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
698 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
699 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
701 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
703 if credentials is not None and credentials.authentication_requested():
704 if credentials.get_bind_dn() is not None:
705 setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
706 "LDAPMANAGERDN": credentials.get_bind_dn(),
707 "LDAPMANAGERPASS_B64": b64encode(credentials.get_password())
710 setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
711 "LDAPADMINUSER": credentials.get_username(),
712 "LDAPADMINREALM": credentials.get_realm(),
713 "LDAPADMINPASS_B64": b64encode(credentials.get_password())
718 def setup_registry(path, setup_path, session_info, lp):
719 """Setup the registry.
721 :param path: Path to the registry database
722 :param setup_path: Function that returns the path to a setup.
723 :param session_info: Session information
724 :param credentials: Credentials
725 :param lp: Loadparm context
727 reg = registry.Registry()
728 hive = registry.open_ldb(path, session_info=session_info,
730 reg.mount_hive(hive, registry.HKEY_LOCAL_MACHINE)
731 provision_reg = setup_path("provision.reg")
732 assert os.path.exists(provision_reg)
733 reg.diff_apply(provision_reg)
736 def setup_idmapdb(path, setup_path, session_info, lp):
737 """Setup the idmap database.
739 :param path: path to the idmap database
740 :param setup_path: Function that returns a path to a setup file
741 :param session_info: Session information
742 :param credentials: Credentials
743 :param lp: Loadparm context
745 if os.path.exists(path):
748 idmap_ldb = IDmapDB(path, session_info=session_info,
752 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
756 def setup_samdb_rootdse(samdb, setup_path, names):
757 """Setup the SamDB rootdse.
759 :param samdb: Sam Database handle
760 :param setup_path: Obtain setup path
762 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
763 "SCHEMADN": names.schemadn,
764 "NETBIOSNAME": names.netbiosname,
765 "DNSDOMAIN": names.dnsdomain,
766 "REALM": names.realm,
767 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
768 "DOMAINDN": names.domaindn,
769 "ROOTDN": names.rootdn,
770 "CONFIGDN": names.configdn,
771 "SERVERDN": names.serverdn,
775 def setup_self_join(samdb, names,
776 machinepass, dnspass,
777 domainsid, invocationid, setup_path,
778 policyguid, policyguid_dc, domainControllerFunctionality):
779 """Join a host to its own domain."""
780 assert isinstance(invocationid, str)
781 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
782 "CONFIGDN": names.configdn,
783 "SCHEMADN": names.schemadn,
784 "DOMAINDN": names.domaindn,
785 "SERVERDN": names.serverdn,
786 "INVOCATIONID": invocationid,
787 "NETBIOSNAME": names.netbiosname,
788 "DEFAULTSITE": names.sitename,
789 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
790 "MACHINEPASS_B64": b64encode(machinepass),
791 "DNSPASS_B64": b64encode(dnspass),
792 "REALM": names.realm,
793 "DOMAIN": names.domain,
794 "DNSDOMAIN": names.dnsdomain,
795 "SAMBA_VERSION_STRING": version,
796 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
798 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
799 "POLICYGUID": policyguid,
800 "POLICYGUID_DC": policyguid_dc,
801 "DNSDOMAIN": names.dnsdomain,
802 "DOMAINSID": str(domainsid),
803 "DOMAINDN": names.domaindn})
805 # add the NTDSGUID based SPNs
806 ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
807 names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
808 expression="", scope=SCOPE_BASE)
809 assert isinstance(names.ntdsguid, str)
811 # Setup fSMORoleOwner entries to point at the newly created DC entry
812 setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
813 "DOMAIN": names.domain,
814 "DNSDOMAIN": names.dnsdomain,
815 "DOMAINDN": names.domaindn,
816 "CONFIGDN": names.configdn,
817 "SCHEMADN": names.schemadn,
818 "DEFAULTSITE": names.sitename,
819 "SERVERDN": names.serverdn,
820 "NETBIOSNAME": names.netbiosname,
821 "NTDSGUID": names.ntdsguid
825 def setup_samdb(path, setup_path, session_info, credentials, lp,
827 domainsid, domainguid, policyguid, policyguid_dc,
828 fill, adminpass, krbtgtpass,
829 machinepass, invocationid, dnspass,
830 serverrole, schema=None, ldap_backend=None):
831 """Setup a complete SAM Database.
833 :note: This will wipe the main SAM database file!
836 domainFunctionality = DS_BEHAVIOR_WIN2008
837 forestFunctionality = DS_BEHAVIOR_WIN2008
838 domainControllerFunctionality = DS_BEHAVIOR_WIN2008
840 # Also wipes the database
841 setup_samdb_partitions(path, setup_path, message=message, lp=lp,
842 credentials=credentials, session_info=session_info,
844 ldap_backend=ldap_backend, serverrole=serverrole)
847 schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn,
848 sambadn=names.sambadn, ldap_backend_type=ldap_backend.ldap_backend_type)
850 # Load the database, but importantly, use Ldb not SamDB as we don't want to load the global schema
851 samdb = Ldb(session_info=session_info,
852 credentials=credentials, lp=lp)
854 message("Pre-loading the Samba 4 and AD schema")
856 # Load the schema from the one we computed earlier
857 samdb.set_schema_from_ldb(schema.ldb)
859 # And now we can connect to the DB - the schema won't be loaded from the DB
863 samdb.load_ldif_file_add(setup_path("provision_options.ldif"))
868 samdb.transaction_start()
870 message("Erasing data from partitions")
871 # Load the schema (again). This time it will force a reindex,
872 # and will therefore make the erase_partitions() below
873 # computationally sane
874 samdb.set_schema_from_ldb(schema.ldb)
875 samdb.erase_partitions()
877 # Set the domain functionality levels onto the database.
878 # Various module (the password_hash module in particular) need
879 # to know what level of AD we are emulating.
881 # These will be fixed into the database via the database
882 # modifictions below, but we need them set from the start.
883 samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
884 samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
885 samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
887 samdb.set_domain_sid(str(domainsid))
888 if serverrole == "domain controller":
889 samdb.set_invocation_id(invocationid)
891 message("Adding DomainDN: %s" % names.domaindn)
892 if serverrole == "domain controller":
893 domain_oc = "domainDNS"
895 domain_oc = "samba4LocalDomain"
897 #impersonate domain admin
898 admin_session_info = admin_session(lp, str(domainsid))
899 samdb.set_session_info(admin_session_info)
901 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
902 "DOMAINDN": names.domaindn,
903 "DOMAIN_OC": domain_oc
906 message("Modifying DomainDN: " + names.domaindn + "")
907 if domainguid is not None:
908 domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
912 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
913 "LDAPTIME": timestring(int(time.time())),
914 "DOMAINSID": str(domainsid),
915 "SCHEMADN": names.schemadn,
916 "NETBIOSNAME": names.netbiosname,
917 "DEFAULTSITE": names.sitename,
918 "CONFIGDN": names.configdn,
919 "SERVERDN": names.serverdn,
920 "POLICYGUID": policyguid,
921 "DOMAINDN": names.domaindn,
922 "DOMAINGUID_MOD": domainguid_mod,
923 "DOMAIN_FUNCTIONALITY": str(domainFunctionality)
926 message("Adding configuration container")
927 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
928 "CONFIGDN": names.configdn,
930 message("Modifying configuration container")
931 setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
932 "CONFIGDN": names.configdn,
933 "SCHEMADN": names.schemadn,
936 # The LDIF here was created when the Schema object was constructed
937 message("Setting up sam.ldb schema")
938 samdb.add_ldif(schema.schema_dn_add)
939 samdb.modify_ldif(schema.schema_dn_modify)
940 samdb.write_prefixes_from_schema()
941 samdb.add_ldif(schema.schema_data)
942 setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
943 {"SCHEMADN": names.schemadn})
945 message("Setting up sam.ldb configuration data")
946 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
947 "CONFIGDN": names.configdn,
948 "NETBIOSNAME": names.netbiosname,
949 "DEFAULTSITE": names.sitename,
950 "DNSDOMAIN": names.dnsdomain,
951 "DOMAIN": names.domain,
952 "SCHEMADN": names.schemadn,
953 "DOMAINDN": names.domaindn,
954 "SERVERDN": names.serverdn,
955 "FOREST_FUNCTIONALALITY": str(forestFunctionality)
958 message("Setting up display specifiers")
959 display_specifiers_ldif = read_ms_ldif(setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
960 display_specifiers_ldif = substitute_var(display_specifiers_ldif, {"CONFIGDN": names.configdn})
961 check_all_substituted(display_specifiers_ldif)
962 samdb.add_ldif(display_specifiers_ldif)
964 message("Adding users container")
965 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
966 "DOMAINDN": names.domaindn})
967 message("Modifying users container")
968 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
969 "DOMAINDN": names.domaindn})
970 message("Adding computers container")
971 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
972 "DOMAINDN": names.domaindn})
973 message("Modifying computers container")
974 setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
975 "DOMAINDN": names.domaindn})
976 message("Setting up sam.ldb data")
977 setup_add_ldif(samdb, setup_path("provision.ldif"), {
978 "DOMAINDN": names.domaindn,
979 "NETBIOSNAME": names.netbiosname,
980 "DEFAULTSITE": names.sitename,
981 "CONFIGDN": names.configdn,
982 "SERVERDN": names.serverdn,
983 "POLICYGUID_DC": policyguid_dc
986 if fill == FILL_FULL:
987 message("Setting up sam.ldb users and groups")
988 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
989 "DOMAINDN": names.domaindn,
990 "DOMAINSID": str(domainsid),
991 "CONFIGDN": names.configdn,
992 "ADMINPASS_B64": b64encode(adminpass),
993 "KRBTGTPASS_B64": b64encode(krbtgtpass),
996 if serverrole == "domain controller":
997 message("Setting up self join")
998 setup_self_join(samdb, names=names, invocationid=invocationid,
1000 machinepass=machinepass,
1001 domainsid=domainsid, policyguid=policyguid,
1002 policyguid_dc=policyguid_dc,
1003 setup_path=setup_path,
1004 domainControllerFunctionality=domainControllerFunctionality)
1005 # add the NTDSGUID based SPNs
1006 ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
1007 names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
1008 expression="", scope=SCOPE_BASE)
1009 assert isinstance(names.ntdsguid, str)
1012 samdb.transaction_cancel()
1015 samdb.transaction_commit()
1020 FILL_NT4SYNC = "NT4SYNC"
1024 def provision(setup_dir, message, session_info,
1025 credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL,
1027 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1029 domain=None, hostname=None, hostip=None, hostip6=None,
1030 domainsid=None, adminpass=None, ldapadminpass=None,
1031 krbtgtpass=None, domainguid=None,
1032 policyguid=None, policyguid_dc=None, invocationid=None,
1034 dnspass=None, root=None, nobody=None, users=None,
1035 wheel=None, backup=None, aci=None, serverrole=None,
1036 ldap_backend_extra_port=None, ldap_backend_type=None,
1038 ol_mmr_urls=None, ol_olc=None,
1039 setup_ds_path=None, slapd_path=None, nosync=False,
1040 ldap_dryrun_mode=False):
1043 :note: caution, this wipes all existing data!
1046 def setup_path(file):
1047 return os.path.join(setup_dir, file)
1049 if domainsid is None:
1050 domainsid = security.random_sid()
1052 # create/adapt the group policy GUIDs
1053 if policyguid is None:
1054 policyguid = str(uuid.uuid4())
1055 policyguid = policyguid.upper()
1056 if policyguid_dc is None:
1057 policyguid_dc = str(uuid.uuid4())
1058 policyguid_dc = policyguid_dc.upper()
1060 if adminpass is None:
1061 adminpass = glue.generate_random_str(12)
1062 if krbtgtpass is None:
1063 krbtgtpass = glue.generate_random_str(12)
1064 if machinepass is None:
1065 machinepass = glue.generate_random_str(12)
1067 dnspass = glue.generate_random_str(12)
1068 if ldapadminpass is None:
1069 #Make a new, random password between Samba and it's LDAP server
1070 ldapadminpass=glue.generate_random_str(12)
1073 root_uid = findnss_uid([root or "root"])
1074 nobody_uid = findnss_uid([nobody or "nobody"])
1075 users_gid = findnss_gid([users or "users"])
1077 wheel_gid = findnss_gid(["wheel", "adm"])
1079 wheel_gid = findnss_gid([wheel])
1081 if targetdir is not None:
1082 if (not os.path.exists(os.path.join(targetdir, "etc"))):
1083 os.makedirs(os.path.join(targetdir, "etc"))
1084 smbconf = os.path.join(targetdir, "etc", "smb.conf")
1085 elif smbconf is None:
1086 smbconf = param.default_path()
1088 # only install a new smb.conf if there isn't one there already
1089 if not os.path.exists(smbconf):
1090 make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
1093 lp = param.LoadParm()
1096 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1097 dnsdomain=realm, serverrole=serverrole, sitename=sitename,
1098 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1101 paths = provision_paths_from_lp(lp, names.dnsdomain)
1105 hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1106 except socket.gaierror, (socket.EAI_NODATA, msg):
1111 hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1112 except socket.gaierror, (socket.EAI_NODATA, msg):
1115 if serverrole is None:
1116 serverrole = lp.get("server role")
1118 assert serverrole in ("domain controller", "member server", "standalone")
1119 if invocationid is None and serverrole == "domain controller":
1120 invocationid = str(uuid.uuid4())
1122 if not os.path.exists(paths.private_dir):
1123 os.mkdir(paths.private_dir)
1125 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1127 schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn,
1128 sambadn=names.sambadn, ldap_backend_type=ldap_backend_type)
1130 secrets_credentials = credentials
1131 provision_backend = None
1132 if ldap_backend_type:
1133 # We only support an LDAP backend over ldapi://
1135 provision_backend = ProvisionBackend(paths=paths, setup_path=setup_path,
1136 lp=lp, credentials=credentials,
1138 message=message, hostname=hostname,
1139 root=root, schema=schema,
1140 ldap_backend_type=ldap_backend_type,
1141 ldapadminpass=ldapadminpass,
1142 ldap_backend_extra_port=ldap_backend_extra_port,
1143 ol_mmr_urls=ol_mmr_urls,
1144 slapd_path=slapd_path,
1145 setup_ds_path=setup_ds_path,
1146 ldap_dryrun_mode=ldap_dryrun_mode)
1148 # Now use the backend credentials to access the databases
1149 credentials = provision_backend.credentials
1150 secrets_credentials = provision_backend.adminCredentials
1151 ldapi_url = provision_backend.ldapi_uri
1153 # only install a new shares config db if there is none
1154 if not os.path.exists(paths.shareconf):
1155 message("Setting up share.ldb")
1156 share_ldb = Ldb(paths.shareconf, session_info=session_info,
1157 credentials=credentials, lp=lp)
1158 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1161 message("Setting up secrets.ldb")
1162 secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
1163 session_info=session_info,
1164 credentials=secrets_credentials, lp=lp)
1166 message("Setting up the registry")
1167 setup_registry(paths.hklm, setup_path, session_info,
1170 message("Setting up idmap db")
1171 idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1174 message("Setting up SAM db")
1175 samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info,
1176 credentials=credentials, lp=lp, names=names,
1178 domainsid=domainsid,
1179 schema=schema, domainguid=domainguid,
1180 policyguid=policyguid, policyguid_dc=policyguid_dc,
1182 adminpass=adminpass, krbtgtpass=krbtgtpass,
1183 invocationid=invocationid,
1184 machinepass=machinepass, dnspass=dnspass,
1185 serverrole=serverrole, ldap_backend=provision_backend)
1187 if serverrole == "domain controller":
1188 if paths.netlogon is None:
1189 message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1190 message("Please either remove %s or see the template at %s" %
1191 ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1192 assert(paths.netlogon is not None)
1194 if paths.sysvol is None:
1195 message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1196 message("Please either remove %s or see the template at %s" %
1197 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1198 assert(paths.sysvol is not None)
1200 # Set up group policies (domain policy and domain controller policy)
1202 policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1203 "{" + policyguid + "}")
1204 os.makedirs(policy_path, 0755)
1205 open(os.path.join(policy_path, "GPT.INI"), 'w').write(
1206 "[General]\r\nVersion=65544")
1207 os.makedirs(os.path.join(policy_path, "MACHINE"), 0755)
1208 os.makedirs(os.path.join(policy_path, "USER"), 0755)
1210 policy_path_dc = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1211 "{" + policyguid_dc + "}")
1212 os.makedirs(policy_path_dc, 0755)
1213 open(os.path.join(policy_path_dc, "GPT.INI"), 'w').write(
1214 "[General]\r\nVersion=2")
1215 os.makedirs(os.path.join(policy_path_dc, "MACHINE"), 0755)
1216 os.makedirs(os.path.join(policy_path_dc, "USER"), 0755)
1218 if not os.path.isdir(paths.netlogon):
1219 os.makedirs(paths.netlogon, 0755)
1221 if samdb_fill == FILL_FULL:
1222 setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1223 root_uid=root_uid, nobody_uid=nobody_uid,
1224 users_gid=users_gid, wheel_gid=wheel_gid)
1226 message("Setting up sam.ldb rootDSE marking as synchronized")
1227 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1229 # Only make a zone file on the first DC, it should be replicated with DNS replication
1230 if serverrole == "domain controller":
1231 secrets_ldb = Ldb(paths.secrets, session_info=session_info,
1232 credentials=credentials, lp=lp)
1233 secretsdb_become_dc(secrets_ldb, setup_path, domain=domain,
1235 netbiosname=names.netbiosname,
1236 domainsid=domainsid,
1237 keytab_path=paths.keytab, samdb_url=paths.samdb,
1238 dns_keytab_path=paths.dns_keytab,
1239 dnspass=dnspass, machinepass=machinepass,
1240 dnsdomain=names.dnsdomain)
1242 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1243 assert isinstance(domainguid, str)
1245 create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1246 domaindn=names.domaindn, hostip=hostip,
1247 hostip6=hostip6, hostname=names.hostname,
1248 dnspass=dnspass, realm=names.realm,
1249 domainguid=domainguid, ntdsguid=names.ntdsguid)
1251 create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1252 dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1254 create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1255 dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1256 keytab_name=paths.dns_keytab)
1257 message("See %s for an example configuration include file for BIND" % paths.namedconf)
1258 message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1260 create_krb5_conf(paths.krb5conf, setup_path,
1261 dnsdomain=names.dnsdomain, hostname=names.hostname,
1263 message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1266 if provision_backend is not None:
1267 if ldap_backend_type == "fedora-ds":
1268 ldapi_db = Ldb(provision_backend.ldapi_uri, lp=lp, credentials=credentials)
1270 # delete default SASL mappings
1271 res = ldapi_db.search(expression="(!(cn=samba-admin mapping))", base="cn=mapping,cn=sasl,cn=config", scope=SCOPE_ONELEVEL, attrs=["dn"])
1273 # configure in-directory access control on Fedora DS via the aci attribute (over a direct ldapi:// socket)
1274 for i in range (0, len(res)):
1275 dn = str(res[i]["dn"])
1278 aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % names.sambadn
1281 m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci")
1283 m.dn = ldb.Dn(1, names.domaindn)
1286 m.dn = ldb.Dn(1, names.configdn)
1289 m.dn = ldb.Dn(1, names.schemadn)
1292 # if an LDAP backend is in use, terminate slapd after final provision and check its proper termination
1293 if provision_backend.slapd.poll() is None:
1295 if hasattr(provision_backend.slapd, "terminate"):
1296 provision_backend.slapd.terminate()
1298 # Older python versions don't have .terminate()
1300 os.kill(provision_backend.slapd.pid, signal.SIGTERM)
1302 #and now wait for it to die
1303 provision_backend.slapd.communicate()
1305 # now display slapd_command_file.txt to show how slapd must be started next time
1306 message("Use later the following commandline to start slapd, then Samba:")
1307 slapd_command = "\'" + "\' \'".join(provision_backend.slapd_command) + "\'"
1308 message(slapd_command)
1309 message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
1311 setup_file(setup_path("ldap_backend_startup.sh"), paths.ldapdir + "/ldap_backend_startup.sh", {
1312 "SLAPD_COMMAND" : slapd_command})
1315 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1318 message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1320 message("Once the above files are installed, your Samba4 server will be ready to use")
1321 message("Server Role: %s" % serverrole)
1322 message("Hostname: %s" % names.hostname)
1323 message("NetBIOS Domain: %s" % names.domain)
1324 message("DNS Domain: %s" % names.dnsdomain)
1325 message("DOMAIN SID: %s" % str(domainsid))
1326 if samdb_fill == FILL_FULL:
1327 message("Admin password: %s" % adminpass)
1328 if provision_backend:
1329 if provision_backend.credentials.get_bind_dn() is not None:
1330 message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1332 message("LDAP Admin User: %s" % provision_backend.credentials.get_username())
1334 message("LDAP Admin Password: %s" % provision_backend.credentials.get_password())
1336 result = ProvisionResult()
1337 result.domaindn = domaindn
1338 result.paths = paths
1340 result.samdb = samdb
1345 def provision_become_dc(setup_dir=None,
1346 smbconf=None, targetdir=None, realm=None,
1347 rootdn=None, domaindn=None, schemadn=None,
1348 configdn=None, serverdn=None,
1349 domain=None, hostname=None, domainsid=None,
1350 adminpass=None, krbtgtpass=None, domainguid=None,
1351 policyguid=None, policyguid_dc=None, invocationid=None,
1353 dnspass=None, root=None, nobody=None, users=None,
1354 wheel=None, backup=None, serverrole=None,
1355 ldap_backend=None, ldap_backend_type=None,
1356 sitename=None, debuglevel=1):
1359 """print a message if quiet is not set."""
1362 glue.set_debug_level(debuglevel)
1364 return provision(setup_dir, message, system_session(), None,
1365 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1366 realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1367 configdn=configdn, serverdn=serverdn, domain=domain,
1368 hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
1369 machinepass=machinepass, serverrole="domain controller",
1373 def setup_db_config(setup_path, dbdir):
1374 """Setup a Berkeley database.
1376 :param setup_path: Setup path function.
1377 :param dbdir: Database directory."""
1378 if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1379 os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1380 if not os.path.isdir(os.path.join(dbdir, "tmp")):
1381 os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1383 setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1384 {"LDAPDBDIR": dbdir})
1386 class ProvisionBackend(object):
1387 def __init__(self, paths=None, setup_path=None, lp=None, credentials=None,
1388 names=None, message=None,
1389 hostname=None, root=None,
1390 schema=None, ldapadminpass=None,
1391 ldap_backend_type=None, ldap_backend_extra_port=None,
1393 setup_ds_path=None, slapd_path=None,
1394 nosync=False, ldap_dryrun_mode=False):
1395 """Provision an LDAP backend for samba4
1397 This works for OpenLDAP and Fedora DS
1400 self.ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.ldapdir, "ldapi"), safe="")
1402 if not os.path.isdir(paths.ldapdir):
1403 os.makedirs(paths.ldapdir, 0700)
1405 if ldap_backend_type == "existing":
1406 #Check to see that this 'existing' LDAP backend in fact exists
1407 ldapi_db = Ldb(self.ldapi_uri, credentials=credentials)
1408 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1409 expression="(objectClass=OpenLDAProotDSE)")
1411 # If we have got here, then we must have a valid connection to the LDAP server, with valid credentials supplied
1412 # This caused them to be set into the long-term database later in the script.
1413 self.credentials = credentials
1414 self.ldap_backend_type = "openldap" #For now, assume existing backends at least emulate OpenLDAP
1417 # we will shortly start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via self.ldapi_uri
1418 # if another instance of slapd is already running
1420 ldapi_db = Ldb(self.ldapi_uri)
1421 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1422 expression="(objectClass=OpenLDAProotDSE)");
1424 f = open(paths.slapdpid, "r")
1427 message("Check for slapd Process with PID: " + str(p) + " and terminate it manually.")
1431 raise ProvisioningError("Warning: Another slapd Instance seems already running on this host, listening to " + self.ldapi_uri + ". Please shut it down before you continue. ")
1436 # Try to print helpful messages when the user has not specified the path to slapd
1437 if slapd_path is None:
1438 raise ProvisioningError("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
1439 if not os.path.exists(slapd_path):
1440 message (slapd_path)
1441 raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1443 schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1445 os.unlink(schemadb_path)
1450 # Put the LDIF of the schema into a database so we can search on
1451 # it to generate schema-dependent configurations in Fedora DS and
1453 os.path.join(paths.ldapdir, "schema-tmp.ldb")
1454 schema.ldb.connect(schemadb_path)
1455 schema.ldb.transaction_start()
1457 # These bits of LDIF are supplied when the Schema object is created
1458 schema.ldb.add_ldif(schema.schema_dn_add)
1459 schema.ldb.modify_ldif(schema.schema_dn_modify)
1460 schema.ldb.add_ldif(schema.schema_data)
1461 schema.ldb.transaction_commit()
1463 self.credentials = Credentials()
1464 self.credentials.guess(lp)
1465 #Kerberos to an ldapi:// backend makes no sense
1466 self.credentials.set_kerberos_state(DONT_USE_KERBEROS)
1468 self.adminCredentials = Credentials()
1469 self.adminCredentials.guess(lp)
1470 #Kerberos to an ldapi:// backend makes no sense
1471 self.adminCredentials.set_kerberos_state(DONT_USE_KERBEROS)
1473 self.ldap_backend_type = ldap_backend_type
1475 if ldap_backend_type == "fedora-ds":
1476 provision_fds_backend(self, paths=paths, setup_path=setup_path,
1477 names=names, message=message,
1479 ldapadminpass=ldapadminpass, root=root,
1481 ldap_backend_extra_port=ldap_backend_extra_port,
1482 setup_ds_path=setup_ds_path,
1483 slapd_path=slapd_path,
1485 ldap_dryrun_mode=ldap_dryrun_mode)
1487 elif ldap_backend_type == "openldap":
1488 provision_openldap_backend(self, paths=paths, setup_path=setup_path,
1489 names=names, message=message,
1491 ldapadminpass=ldapadminpass, root=root,
1493 ldap_backend_extra_port=ldap_backend_extra_port,
1494 ol_mmr_urls=ol_mmr_urls,
1495 slapd_path=slapd_path,
1497 ldap_dryrun_mode=ldap_dryrun_mode)
1499 raise ProvisioningError("Unknown LDAP backend type selected")
1501 self.credentials.set_password(ldapadminpass)
1502 self.adminCredentials.set_username("samba-admin")
1503 self.adminCredentials.set_password(ldapadminpass)
1505 # Now start the slapd, so we can provision onto it. We keep the
1506 # subprocess context around, to kill this off at the successful
1508 self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False)
1510 while self.slapd.poll() is None:
1511 # Wait until the socket appears
1513 ldapi_db = Ldb(self.ldapi_uri, lp=lp, credentials=self.credentials)
1514 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1515 expression="(objectClass=OpenLDAProotDSE)")
1516 # If we have got here, then we must have a valid connection to the LDAP server!
1522 raise ProvisioningError("slapd died before we could make a connection to it")
1525 def provision_openldap_backend(result, paths=None, setup_path=None, names=None,
1527 hostname=None, ldapadminpass=None, root=None,
1529 ldap_backend_extra_port=None,
1531 slapd_path=None, nosync=False,
1532 ldap_dryrun_mode=False):
1534 #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
1537 nosync_config = "dbnosync"
1539 lnkattr = get_linked_attributes(names.schemadn,schema.ldb)
1540 refint_attributes = ""
1541 memberof_config = "# Generated from Samba4 schema\n"
1542 for att in lnkattr.keys():
1543 if lnkattr[att] is not None:
1544 refint_attributes = refint_attributes + " " + att
1546 memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1547 { "MEMBER_ATTR" : att ,
1548 "MEMBEROF_ATTR" : lnkattr[att] })
1550 refint_config = read_and_sub_file(setup_path("refint.conf"),
1551 { "LINK_ATTRS" : refint_attributes})
1553 attrs = ["linkID", "lDAPDisplayName"]
1554 res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1556 for i in range (0, len(res)):
1557 index_attr = res[i]["lDAPDisplayName"][0]
1558 if index_attr == "objectGUID":
1559 index_attr = "entryUUID"
1561 index_config += "index " + index_attr + " eq\n"
1563 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1565 mmr_replicator_acl = ""
1566 mmr_serverids_config = ""
1567 mmr_syncrepl_schema_config = ""
1568 mmr_syncrepl_config_config = ""
1569 mmr_syncrepl_user_config = ""
1572 if ol_mmr_urls is not None:
1573 # For now, make these equal
1574 mmr_pass = ldapadminpass
1576 url_list=filter(None,ol_mmr_urls.split(' '))
1577 if (len(url_list) == 1):
1578 url_list=filter(None,ol_mmr_urls.split(','))
1581 mmr_on_config = "MirrorMode On"
1582 mmr_replicator_acl = " by dn=cn=replicator,cn=samba read"
1584 for url in url_list:
1586 mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1587 { "SERVERID" : str(serverid),
1588 "LDAPSERVER" : url })
1591 mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1593 "MMRDN": names.schemadn,
1595 "MMR_PASSWORD": mmr_pass})
1598 mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1600 "MMRDN": names.configdn,
1602 "MMR_PASSWORD": mmr_pass})
1605 mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1607 "MMRDN": names.domaindn,
1609 "MMR_PASSWORD": mmr_pass })
1610 # OpenLDAP cn=config initialisation
1611 olc_syncrepl_config = ""
1613 # if mmr = yes, generate cn=config-replication directives
1614 # and olc_seed.lif for the other mmr-servers
1615 if ol_mmr_urls is not None:
1617 olc_serverids_config = ""
1618 olc_syncrepl_seed_config = ""
1619 olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
1621 for url in url_list:
1623 olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
1624 { "SERVERID" : str(serverid),
1625 "LDAPSERVER" : url })
1628 olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
1631 "MMR_PASSWORD": mmr_pass})
1633 olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1635 "LDAPSERVER" : url})
1637 setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif,
1638 {"OLC_SERVER_ID_CONF": olc_serverids_config,
1639 "OLC_PW": ldapadminpass,
1640 "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
1643 setup_file(setup_path("slapd.conf"), paths.slapdconf,
1644 {"DNSDOMAIN": names.dnsdomain,
1645 "LDAPDIR": paths.ldapdir,
1646 "DOMAINDN": names.domaindn,
1647 "CONFIGDN": names.configdn,
1648 "SCHEMADN": names.schemadn,
1649 "MEMBEROF_CONFIG": memberof_config,
1650 "MIRRORMODE": mmr_on_config,
1651 "REPLICATOR_ACL": mmr_replicator_acl,
1652 "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1653 "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1654 "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1655 "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1656 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
1657 "OLC_MMR_CONFIG": olc_mmr_config,
1658 "REFINT_CONFIG": refint_config,
1659 "INDEX_CONFIG": index_config,
1660 "NOSYNC": nosync_config})
1662 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1663 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1664 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1666 if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba", "cn=samba")):
1667 os.makedirs(os.path.join(paths.ldapdir, "db", "samba", "cn=samba"), 0700)
1669 setup_file(setup_path("cn=samba.ldif"),
1670 os.path.join(paths.ldapdir, "db", "samba", "cn=samba.ldif"),
1671 { "UUID": str(uuid.uuid4()),
1672 "LDAPTIME": timestring(int(time.time()))} )
1673 setup_file(setup_path("cn=samba-admin.ldif"),
1674 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=samba-admin.ldif"),
1675 {"LDAPADMINPASS_B64": b64encode(ldapadminpass),
1676 "UUID": str(uuid.uuid4()),
1677 "LDAPTIME": timestring(int(time.time()))} )
1679 if ol_mmr_urls is not None:
1680 setup_file(setup_path("cn=replicator.ldif"),
1681 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=replicator.ldif"),
1682 {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1683 "UUID": str(uuid.uuid4()),
1684 "LDAPTIME": timestring(int(time.time()))} )
1687 mapping = "schema-map-openldap-2.3"
1688 backend_schema = "backend-schema.schema"
1690 backend_schema_data = schema.ldb.convert_schema_to_openldap("openldap", open(setup_path(mapping), 'r').read())
1691 assert backend_schema_data is not None
1692 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1694 # now we generate the needed strings to start slapd automatically,
1695 # first ldapi_uri...
1696 if ldap_backend_extra_port is not None:
1697 # When we use MMR, we can't use 0.0.0.0 as it uses the name
1698 # specified there as part of it's clue as to it's own name,
1699 # and not to replicate to itself
1700 if ol_mmr_urls is None:
1701 server_port_string = "ldap://0.0.0.0:%d" % ldap_backend_extra_port
1703 server_port_string = "ldap://" + names.hostname + "." + names.dnsdomain +":%d" % ldap_backend_extra_port
1705 server_port_string = ""
1707 # Prepare the 'result' information - the commands to return in particular
1708 result.slapd_provision_command = [slapd_path]
1710 result.slapd_provision_command.append("-F" + paths.olcdir)
1712 result.slapd_provision_command.append("-h")
1714 # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands
1715 result.slapd_command = list(result.slapd_provision_command)
1717 result.slapd_provision_command.append(result.ldapi_uri)
1718 result.slapd_provision_command.append("-d0")
1720 uris = result.ldapi_uri
1721 if server_port_string is not "":
1722 uris = uris + " " + server_port_string
1724 result.slapd_command.append(uris)
1726 # Set the username - done here because Fedora DS still uses the admin DN and simple bind
1727 result.credentials.set_username("samba-admin")
1729 # If we were just looking for crashes up to this point, it's a
1730 # good time to exit before we realise we don't have OpenLDAP on
1732 if ldap_dryrun_mode:
1735 # Finally, convert the configuration into cn=config style!
1736 if not os.path.isdir(paths.olcdir):
1737 os.makedirs(paths.olcdir, 0770)
1739 retcode = subprocess.call([slapd_path, "-Ttest", "-f", paths.slapdconf, "-F", paths.olcdir], close_fds=True, shell=False)
1741 # We can't do this, as OpenLDAP is strange. It gives an error
1742 # output to the above, but does the conversion sucessfully...
1745 # raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1747 if not os.path.exists(os.path.join(paths.olcdir, "cn=config.ldif")):
1748 raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1750 # Don't confuse the admin by leaving the slapd.conf around
1751 os.remove(paths.slapdconf)
1754 def provision_fds_backend(result, paths=None, setup_path=None, names=None,
1756 hostname=None, ldapadminpass=None, root=None,
1758 ldap_backend_extra_port=None,
1762 ldap_dryrun_mode=False):
1764 if ldap_backend_extra_port is not None:
1765 serverport = "ServerPort=%d" % ldap_backend_extra_port
1769 setup_file(setup_path("fedorads.inf"), paths.fedoradsinf,
1771 "HOSTNAME": hostname,
1772 "DNSDOMAIN": names.dnsdomain,
1773 "LDAPDIR": paths.ldapdir,
1774 "DOMAINDN": names.domaindn,
1775 "LDAPMANAGERDN": names.ldapmanagerdn,
1776 "LDAPMANAGERPASS": ldapadminpass,
1777 "SERVERPORT": serverport})
1779 setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions,
1780 {"CONFIGDN": names.configdn,
1781 "SCHEMADN": names.schemadn,
1782 "SAMBADN": names.sambadn,
1785 setup_file(setup_path("fedorads-sasl.ldif"), paths.fedoradssasl,
1786 {"SAMBADN": names.sambadn,
1789 setup_file(setup_path("fedorads-samba.ldif"), paths.fedoradssamba,
1790 {"SAMBADN": names.sambadn,
1791 "LDAPADMINPASS": ldapadminpass
1794 mapping = "schema-map-fedora-ds-1.0"
1795 backend_schema = "99_ad.ldif"
1797 # Build a schema file in Fedora DS format
1798 backend_schema_data = schema.ldb.convert_schema_to_openldap("fedora-ds", open(setup_path(mapping), 'r').read())
1799 assert backend_schema_data is not None
1800 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1802 result.credentials.set_bind_dn(names.ldapmanagerdn)
1804 # Destory the target directory, or else setup-ds.pl will complain
1805 fedora_ds_dir = os.path.join(paths.ldapdir, "slapd-samba4")
1806 shutil.rmtree(fedora_ds_dir, True)
1808 result.slapd_provision_command = [slapd_path, "-D", fedora_ds_dir, "-i", paths.slapdpid];
1809 #In the 'provision' command line, stay in the foreground so we can easily kill it
1810 result.slapd_provision_command.append("-d0")
1812 #the command for the final run is the normal script
1813 result.slapd_command = [os.path.join(paths.ldapdir, "slapd-samba4", "start-slapd")]
1815 # If we were just looking for crashes up to this point, it's a
1816 # good time to exit before we realise we don't have Fedora DS on
1817 if ldap_dryrun_mode:
1820 # Try to print helpful messages when the user has not specified the path to the setup-ds tool
1821 if setup_ds_path is None:
1822 raise ProvisioningError("Warning: Fedora DS LDAP-Backend must be setup with path to setup-ds, e.g. --setup-ds-path=\"/usr/sbin/setup-ds.pl\"!")
1823 if not os.path.exists(setup_ds_path):
1824 message (setup_ds_path)
1825 raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1827 # Run the Fedora DS setup utility
1828 retcode = subprocess.call([setup_ds_path, "--silent", "--file", paths.fedoradsinf], close_fds=True, shell=False)
1830 raise ProvisioningError("setup-ds failed")
1833 retcode = subprocess.call([
1834 os.path.join(paths.ldapdir, "slapd-samba4", "ldif2db"), "-s", names.sambadn, "-i", paths.fedoradssamba],
1835 close_fds=True, shell=False)
1837 raise("ldib2db failed")
1839 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1840 """Create a PHP LDAP admin configuration file.
1842 :param path: Path to write the configuration to.
1843 :param setup_path: Function to generate setup paths.
1845 setup_file(setup_path("phpldapadmin-config.php"), path,
1846 {"S4_LDAPI_URI": ldapi_uri})
1849 def create_zone_file(path, setup_path, dnsdomain, domaindn,
1850 hostip, hostip6, hostname, dnspass, realm, domainguid,
1852 """Write out a DNS zone file, from the info in the current database.
1854 :param path: Path of the new zone file.
1855 :param setup_path: Setup path function.
1856 :param dnsdomain: DNS Domain name
1857 :param domaindn: DN of the Domain
1858 :param hostip: Local IPv4 IP
1859 :param hostip6: Local IPv6 IP
1860 :param hostname: Local hostname
1861 :param dnspass: Password for DNS
1862 :param realm: Realm name
1863 :param domainguid: GUID of the domain.
1864 :param ntdsguid: GUID of the hosts nTDSDSA record.
1866 assert isinstance(domainguid, str)
1868 if hostip6 is not None:
1869 hostip6_base_line = " IN AAAA " + hostip6
1870 hostip6_host_line = hostname + " IN AAAA " + hostip6
1872 hostip6_base_line = ""
1873 hostip6_host_line = ""
1875 if hostip is not None:
1876 hostip_base_line = " IN A " + hostip
1877 hostip_host_line = hostname + " IN A " + hostip
1879 hostip_base_line = ""
1880 hostip_host_line = ""
1882 setup_file(setup_path("provision.zone"), path, {
1883 "DNSPASS_B64": b64encode(dnspass),
1884 "HOSTNAME": hostname,
1885 "DNSDOMAIN": dnsdomain,
1887 "HOSTIP_BASE_LINE": hostip_base_line,
1888 "HOSTIP_HOST_LINE": hostip_host_line,
1889 "DOMAINGUID": domainguid,
1890 "DATESTRING": time.strftime("%Y%m%d%H"),
1891 "DEFAULTSITE": DEFAULTSITE,
1892 "NTDSGUID": ntdsguid,
1893 "HOSTIP6_BASE_LINE": hostip6_base_line,
1894 "HOSTIP6_HOST_LINE": hostip6_host_line,
1898 def create_named_conf(path, setup_path, realm, dnsdomain,
1900 """Write out a file containing zone statements suitable for inclusion in a
1901 named.conf file (including GSS-TSIG configuration).
1903 :param path: Path of the new named.conf file.
1904 :param setup_path: Setup path function.
1905 :param realm: Realm name
1906 :param dnsdomain: DNS Domain name
1907 :param private_dir: Path to private directory
1908 :param keytab_name: File name of DNS keytab file
1911 setup_file(setup_path("named.conf"), path, {
1912 "DNSDOMAIN": dnsdomain,
1914 "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1915 "PRIVATE_DIR": private_dir
1918 def create_named_txt(path, setup_path, realm, dnsdomain,
1919 private_dir, keytab_name):
1920 """Write out a file containing zone statements suitable for inclusion in a
1921 named.conf file (including GSS-TSIG configuration).
1923 :param path: Path of the new named.conf file.
1924 :param setup_path: Setup path function.
1925 :param realm: Realm name
1926 :param dnsdomain: DNS Domain name
1927 :param private_dir: Path to private directory
1928 :param keytab_name: File name of DNS keytab file
1931 setup_file(setup_path("named.txt"), path, {
1932 "DNSDOMAIN": dnsdomain,
1934 "DNS_KEYTAB": keytab_name,
1935 "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1936 "PRIVATE_DIR": private_dir
1939 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1940 """Write out a file containing zone statements suitable for inclusion in a
1941 named.conf file (including GSS-TSIG configuration).
1943 :param path: Path of the new named.conf file.
1944 :param setup_path: Setup path function.
1945 :param dnsdomain: DNS Domain name
1946 :param hostname: Local hostname
1947 :param realm: Realm name
1950 setup_file(setup_path("krb5.conf"), path, {
1951 "DNSDOMAIN": dnsdomain,
1952 "HOSTNAME": hostname,