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)
428 configdn = "CN=Configuration," + rootdn
430 schemadn = "CN=Schema," + configdn
437 names = ProvisionNames()
438 names.rootdn = rootdn
439 names.domaindn = domaindn
440 names.configdn = configdn
441 names.schemadn = schemadn
442 names.sambadn = sambadn
443 names.ldapmanagerdn = "CN=Manager," + rootdn
444 names.dnsdomain = dnsdomain
445 names.domain = domain
447 names.netbiosname = netbiosname
448 names.hostname = hostname
449 names.sitename = sitename
450 names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (netbiosname, sitename, configdn)
455 def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
457 """Create a new smb.conf file based on a couple of basic settings.
459 assert smbconf is not None
461 hostname = socket.gethostname().split(".")[0].lower()
463 if serverrole is None:
464 serverrole = "standalone"
466 assert serverrole in ("domain controller", "member server", "standalone")
467 if serverrole == "domain controller":
469 elif serverrole == "member server":
470 smbconfsuffix = "member"
471 elif serverrole == "standalone":
472 smbconfsuffix = "standalone"
474 assert domain is not None
475 assert realm is not None
477 default_lp = param.LoadParm()
478 #Load non-existant file
479 if os.path.exists(smbconf):
480 default_lp.load(smbconf)
482 if targetdir is not None:
483 privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
484 lockdir_line = "lock dir = " + os.path.abspath(targetdir)
486 default_lp.set("lock dir", os.path.abspath(targetdir))
491 sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
492 netlogon = os.path.join(sysvol, realm.lower(), "scripts")
494 setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
496 "HOSTNAME": hostname,
499 "SERVERROLE": serverrole,
500 "NETLOGONPATH": netlogon,
501 "SYSVOLPATH": sysvol,
502 "PRIVATEDIR_LINE": privatedir_line,
503 "LOCKDIR_LINE": lockdir_line
507 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
508 users_gid, wheel_gid):
509 """setup reasonable name mappings for sam names to unix names.
511 :param samdb: SamDB object.
512 :param idmap: IDmap db object.
513 :param sid: The domain sid.
514 :param domaindn: The domain DN.
515 :param root_uid: uid of the UNIX root user.
516 :param nobody_uid: uid of the UNIX nobody user.
517 :param users_gid: gid of the UNIX users group.
518 :param wheel_gid: gid of the UNIX wheel group."""
520 idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
521 idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
523 idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
524 idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
526 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
528 serverrole, ldap_backend=None,
530 """Setup the partitions for the SAM database.
532 Alternatively, provision() may call this, and then populate the database.
534 :note: This will wipe the Sam Database!
536 :note: This function always removes the local SAM LDB file. The erase
537 parameter controls whether to erase the existing data, which
538 may not be stored locally but in LDAP.
540 assert session_info is not None
542 # We use options=["modules:"] to stop the modules loading - we
543 # just want to wipe and re-initialise the database, not start it up
546 samdb = Ldb(url=samdb_path, session_info=session_info,
547 credentials=credentials, lp=lp, options=["modules:"])
549 samdb.erase_except_schema_controlled()
551 os.unlink(samdb_path)
552 samdb = Ldb(url=samdb_path, session_info=session_info,
553 credentials=credentials, lp=lp, options=["modules:"])
555 samdb.erase_except_schema_controlled()
558 #Add modules to the list to activate them by default
559 #beware often order is important
561 # Some Known ordering constraints:
562 # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
563 # - objectclass must be before password_hash, because password_hash checks
564 # that the objectclass is of type person (filled in by objectclass
565 # module when expanding the objectclass list)
566 # - partition must be last
567 # - each partition has its own module list then
568 modules_list = ["rootdse",
586 "extended_dn_out_ldb"]
587 modules_list2 = ["show_deleted",
590 domaindn_ldb = "users.ldb"
591 configdn_ldb = "configuration.ldb"
592 schemadn_ldb = "schema.ldb"
593 if ldap_backend is not None:
594 domaindn_ldb = ldap_backend.ldapi_uri
595 configdn_ldb = ldap_backend.ldapi_uri
596 schemadn_ldb = ldap_backend.ldapi_uri
598 if ldap_backend.ldap_backend_type == "fedora-ds":
599 backend_modules = ["nsuniqueid", "paged_searches"]
600 # We can handle linked attributes here, as we don't have directory-side subtree operations
601 tdb_modules_list = ["linked_attributes", "extended_dn_out_dereference"]
602 elif ldap_backend.ldap_backend_type == "openldap":
603 backend_modules = ["entryuuid", "paged_searches"]
604 # OpenLDAP handles subtree renames, so we don't want to do any of these things
605 tdb_modules_list = ["extended_dn_out_dereference"]
607 elif serverrole == "domain controller":
608 tdb_modules_list.insert(0, "repl_meta_data")
611 backend_modules = ["objectguid"]
613 if tdb_modules_list is None:
614 tdb_modules_list_as_string = ""
616 tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
618 samdb.transaction_start()
620 message("Setting up sam.ldb partitions and settings")
621 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
622 "SCHEMADN": names.schemadn,
623 "SCHEMADN_LDB": schemadn_ldb,
624 "SCHEMADN_MOD2": ",objectguid",
625 "CONFIGDN": names.configdn,
626 "CONFIGDN_LDB": configdn_ldb,
627 "DOMAINDN": names.domaindn,
628 "DOMAINDN_LDB": domaindn_ldb,
629 "SCHEMADN_MOD": "schema_fsmo,instancetype",
630 "CONFIGDN_MOD": "naming_fsmo,instancetype",
631 "DOMAINDN_MOD": "pdc_fsmo,instancetype",
632 "MODULES_LIST": ",".join(modules_list),
633 "TDB_MODULES_LIST": tdb_modules_list_as_string,
634 "MODULES_LIST2": ",".join(modules_list2),
635 "BACKEND_MOD": ",".join(backend_modules),
638 samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
640 message("Setting up sam.ldb rootDSE")
641 setup_samdb_rootdse(samdb, setup_path, names)
644 samdb.transaction_cancel()
647 samdb.transaction_commit()
651 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain,
652 netbiosname, domainsid, keytab_path, samdb_url,
653 dns_keytab_path, dnspass, machinepass):
654 """Add DC-specific bits to a secrets database.
656 :param secretsdb: Ldb Handle to the secrets database
657 :param setup_path: Setup path function
658 :param machinepass: Machine password
660 setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), {
661 "MACHINEPASS_B64": b64encode(machinepass),
664 "DNSDOMAIN": dnsdomain,
665 "DOMAINSID": str(domainsid),
666 "SECRETS_KEYTAB": keytab_path,
667 "NETBIOSNAME": netbiosname,
668 "SAM_LDB": samdb_url,
669 "DNS_KEYTAB": dns_keytab_path,
670 "DNSPASS_B64": b64encode(dnspass),
674 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
675 """Setup the secrets database.
677 :param path: Path to the secrets database.
678 :param setup_path: Get the path to a setup file.
679 :param session_info: Session info.
680 :param credentials: Credentials
681 :param lp: Loadparm context
682 :return: LDB handle for the created secrets database
684 if os.path.exists(path):
686 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
689 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
690 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
692 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
694 if credentials is not None and credentials.authentication_requested():
695 if credentials.get_bind_dn() is not None:
696 setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
697 "LDAPMANAGERDN": credentials.get_bind_dn(),
698 "LDAPMANAGERPASS_B64": b64encode(credentials.get_password())
701 setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
702 "LDAPADMINUSER": credentials.get_username(),
703 "LDAPADMINREALM": credentials.get_realm(),
704 "LDAPADMINPASS_B64": b64encode(credentials.get_password())
709 def setup_registry(path, setup_path, session_info, lp):
710 """Setup the registry.
712 :param path: Path to the registry database
713 :param setup_path: Function that returns the path to a setup.
714 :param session_info: Session information
715 :param credentials: Credentials
716 :param lp: Loadparm context
718 reg = registry.Registry()
719 hive = registry.open_ldb(path, session_info=session_info,
721 reg.mount_hive(hive, registry.HKEY_LOCAL_MACHINE)
722 provision_reg = setup_path("provision.reg")
723 assert os.path.exists(provision_reg)
724 reg.diff_apply(provision_reg)
727 def setup_idmapdb(path, setup_path, session_info, lp):
728 """Setup the idmap database.
730 :param path: path to the idmap database
731 :param setup_path: Function that returns a path to a setup file
732 :param session_info: Session information
733 :param credentials: Credentials
734 :param lp: Loadparm context
736 if os.path.exists(path):
739 idmap_ldb = IDmapDB(path, session_info=session_info,
743 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
747 def setup_samdb_rootdse(samdb, setup_path, names):
748 """Setup the SamDB rootdse.
750 :param samdb: Sam Database handle
751 :param setup_path: Obtain setup path
753 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
754 "SCHEMADN": names.schemadn,
755 "NETBIOSNAME": names.netbiosname,
756 "DNSDOMAIN": names.dnsdomain,
757 "REALM": names.realm,
758 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
759 "DOMAINDN": names.domaindn,
760 "ROOTDN": names.rootdn,
761 "CONFIGDN": names.configdn,
762 "SERVERDN": names.serverdn,
766 def setup_self_join(samdb, names,
767 machinepass, dnspass,
768 domainsid, invocationid, setup_path,
769 policyguid, domainControllerFunctionality):
770 """Join a host to its own domain."""
771 assert isinstance(invocationid, str)
772 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
773 "CONFIGDN": names.configdn,
774 "SCHEMADN": names.schemadn,
775 "DOMAINDN": names.domaindn,
776 "SERVERDN": names.serverdn,
777 "INVOCATIONID": invocationid,
778 "NETBIOSNAME": names.netbiosname,
779 "DEFAULTSITE": names.sitename,
780 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
781 "MACHINEPASS_B64": b64encode(machinepass),
782 "DNSPASS_B64": b64encode(dnspass),
783 "REALM": names.realm,
784 "DOMAIN": names.domain,
785 "DNSDOMAIN": names.dnsdomain,
786 "SAMBA_VERSION_STRING": version,
787 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
789 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
790 "POLICYGUID": policyguid,
791 "DNSDOMAIN": names.dnsdomain,
792 "DOMAINSID": str(domainsid),
793 "DOMAINDN": names.domaindn})
795 # add the NTDSGUID based SPNs
796 ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
797 names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
798 expression="", scope=SCOPE_BASE)
799 assert isinstance(names.ntdsguid, str)
801 # Setup fSMORoleOwner entries to point at the newly created DC entry
802 setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
803 "DOMAIN": names.domain,
804 "DNSDOMAIN": names.dnsdomain,
805 "DOMAINDN": names.domaindn,
806 "CONFIGDN": names.configdn,
807 "SCHEMADN": names.schemadn,
808 "DEFAULTSITE": names.sitename,
809 "SERVERDN": names.serverdn,
810 "NETBIOSNAME": names.netbiosname,
811 "NTDSGUID": names.ntdsguid
815 def setup_samdb(path, setup_path, session_info, credentials, lp,
817 domainsid, domainguid, policyguid,
818 fill, adminpass, krbtgtpass,
819 machinepass, invocationid, dnspass,
820 serverrole, schema=None, ldap_backend=None):
821 """Setup a complete SAM Database.
823 :note: This will wipe the main SAM database file!
826 domainFunctionality = DS_BEHAVIOR_WIN2008
827 forestFunctionality = DS_BEHAVIOR_WIN2008
828 domainControllerFunctionality = DS_BEHAVIOR_WIN2008
830 # Also wipes the database
831 setup_samdb_partitions(path, setup_path, message=message, lp=lp,
832 credentials=credentials, session_info=session_info,
834 ldap_backend=ldap_backend, serverrole=serverrole)
837 schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn,
838 sambadn=names.sambadn, ldap_backend_type=ldap_backend.ldap_backend_type)
840 # Load the database, but importantly, use Ldb not SamDB as we don't want to load the global schema
841 samdb = Ldb(session_info=session_info,
842 credentials=credentials, lp=lp)
844 message("Pre-loading the Samba 4 and AD schema")
846 # Load the schema from the one we computed earlier
847 samdb.set_schema_from_ldb(schema.ldb)
849 # And now we can connect to the DB - the schema won't be loaded from the DB
853 samdb.load_ldif_file_add(setup_path("provision_options.ldif"))
858 samdb.transaction_start()
860 message("Erasing data from partitions")
861 # Load the schema (again). This time it will force a reindex,
862 # and will therefore make the erase_partitions() below
863 # computationally sane
864 samdb.set_schema_from_ldb(schema.ldb)
865 samdb.erase_partitions()
867 # Set the domain functionality levels onto the database.
868 # Various module (the password_hash module in particular) need
869 # to know what level of AD we are emulating.
871 # These will be fixed into the database via the database
872 # modifictions below, but we need them set from the start.
873 samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
874 samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
875 samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
877 samdb.set_domain_sid(str(domainsid))
878 if serverrole == "domain controller":
879 samdb.set_invocation_id(invocationid)
881 message("Adding DomainDN: %s" % names.domaindn)
882 if serverrole == "domain controller":
883 domain_oc = "domainDNS"
885 domain_oc = "samba4LocalDomain"
887 #impersonate domain admin
888 admin_session_info = admin_session(lp, str(domainsid))
889 samdb.set_session_info(admin_session_info)
891 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
892 "DOMAINDN": names.domaindn,
893 "DOMAIN_OC": domain_oc
896 message("Modifying DomainDN: " + names.domaindn + "")
897 if domainguid is not None:
898 domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
902 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
903 "LDAPTIME": timestring(int(time.time())),
904 "DOMAINSID": str(domainsid),
905 "SCHEMADN": names.schemadn,
906 "NETBIOSNAME": names.netbiosname,
907 "DEFAULTSITE": names.sitename,
908 "CONFIGDN": names.configdn,
909 "SERVERDN": names.serverdn,
910 "POLICYGUID": policyguid,
911 "DOMAINDN": names.domaindn,
912 "DOMAINGUID_MOD": domainguid_mod,
913 "DOMAIN_FUNCTIONALITY": str(domainFunctionality)
916 message("Adding configuration container")
917 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
918 "CONFIGDN": names.configdn,
920 message("Modifying configuration container")
921 setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
922 "CONFIGDN": names.configdn,
923 "SCHEMADN": names.schemadn,
926 # The LDIF here was created when the Schema object was constructed
927 message("Setting up sam.ldb schema")
928 samdb.add_ldif(schema.schema_dn_add)
929 samdb.modify_ldif(schema.schema_dn_modify)
930 samdb.write_prefixes_from_schema()
931 samdb.add_ldif(schema.schema_data)
932 setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
933 {"SCHEMADN": names.schemadn})
935 message("Setting up sam.ldb configuration data")
936 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
937 "CONFIGDN": names.configdn,
938 "NETBIOSNAME": names.netbiosname,
939 "DEFAULTSITE": names.sitename,
940 "DNSDOMAIN": names.dnsdomain,
941 "DOMAIN": names.domain,
942 "SCHEMADN": names.schemadn,
943 "DOMAINDN": names.domaindn,
944 "SERVERDN": names.serverdn,
945 "FOREST_FUNCTIONALALITY": str(forestFunctionality)
948 message("Setting up display specifiers")
949 display_specifiers_ldif = read_ms_ldif(setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
950 display_specifiers_ldif = substitute_var(display_specifiers_ldif, {"CONFIGDN": names.configdn})
951 check_all_substituted(display_specifiers_ldif)
952 samdb.add_ldif(display_specifiers_ldif)
954 message("Adding users container")
955 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
956 "DOMAINDN": names.domaindn})
957 message("Modifying users container")
958 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
959 "DOMAINDN": names.domaindn})
960 message("Adding computers container")
961 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
962 "DOMAINDN": names.domaindn})
963 message("Modifying computers container")
964 setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
965 "DOMAINDN": names.domaindn})
966 message("Setting up sam.ldb data")
967 setup_add_ldif(samdb, setup_path("provision.ldif"), {
968 "DOMAINDN": names.domaindn,
969 "NETBIOSNAME": names.netbiosname,
970 "DEFAULTSITE": names.sitename,
971 "CONFIGDN": names.configdn,
972 "SERVERDN": names.serverdn
975 if fill == FILL_FULL:
976 message("Setting up sam.ldb users and groups")
977 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
978 "DOMAINDN": names.domaindn,
979 "DOMAINSID": str(domainsid),
980 "CONFIGDN": names.configdn,
981 "ADMINPASS_B64": b64encode(adminpass),
982 "KRBTGTPASS_B64": b64encode(krbtgtpass),
985 if serverrole == "domain controller":
986 message("Setting up self join")
987 setup_self_join(samdb, names=names, invocationid=invocationid,
989 machinepass=machinepass,
990 domainsid=domainsid, policyguid=policyguid,
991 setup_path=setup_path,
992 domainControllerFunctionality=domainControllerFunctionality)
993 # add the NTDSGUID based SPNs
994 ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
995 names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
996 expression="", scope=SCOPE_BASE)
997 assert isinstance(names.ntdsguid, str)
1000 samdb.transaction_cancel()
1003 samdb.transaction_commit()
1008 FILL_NT4SYNC = "NT4SYNC"
1012 def provision(setup_dir, message, session_info,
1013 credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL,
1015 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1017 domain=None, hostname=None, hostip=None, hostip6=None,
1018 domainsid=None, adminpass=None, ldapadminpass=None,
1019 krbtgtpass=None, domainguid=None,
1020 policyguid=None, invocationid=None, machinepass=None,
1021 dnspass=None, root=None, nobody=None, users=None,
1022 wheel=None, backup=None, aci=None, serverrole=None,
1023 ldap_backend_extra_port=None, ldap_backend_type=None,
1025 ol_mmr_urls=None, ol_olc=None,
1026 setup_ds_path=None, slapd_path=None, nosync=False,
1027 ldap_dryrun_mode=False):
1030 :note: caution, this wipes all existing data!
1033 def setup_path(file):
1034 return os.path.join(setup_dir, file)
1036 if domainsid is None:
1037 domainsid = security.random_sid()
1039 if policyguid is None:
1040 policyguid = str(uuid.uuid4())
1041 if adminpass is None:
1042 adminpass = glue.generate_random_str(12)
1043 if krbtgtpass is None:
1044 krbtgtpass = glue.generate_random_str(12)
1045 if machinepass is None:
1046 machinepass = glue.generate_random_str(12)
1048 dnspass = glue.generate_random_str(12)
1049 if ldapadminpass is None:
1050 #Make a new, random password between Samba and it's LDAP server
1051 ldapadminpass=glue.generate_random_str(12)
1054 root_uid = findnss_uid([root or "root"])
1055 nobody_uid = findnss_uid([nobody or "nobody"])
1056 users_gid = findnss_gid([users or "users"])
1058 wheel_gid = findnss_gid(["wheel", "adm"])
1060 wheel_gid = findnss_gid([wheel])
1062 if targetdir is not None:
1063 if (not os.path.exists(os.path.join(targetdir, "etc"))):
1064 os.makedirs(os.path.join(targetdir, "etc"))
1065 smbconf = os.path.join(targetdir, "etc", "smb.conf")
1066 elif smbconf is None:
1067 smbconf = param.default_path()
1069 # only install a new smb.conf if there isn't one there already
1070 if not os.path.exists(smbconf):
1071 make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
1074 lp = param.LoadParm()
1077 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1078 dnsdomain=realm, serverrole=serverrole, sitename=sitename,
1079 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1082 paths = provision_paths_from_lp(lp, names.dnsdomain)
1086 hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1087 except socket.gaierror, (socket.EAI_NODATA, msg):
1092 hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1093 except socket.gaierror, (socket.EAI_NODATA, msg):
1096 if serverrole is None:
1097 serverrole = lp.get("server role")
1099 assert serverrole in ("domain controller", "member server", "standalone")
1100 if invocationid is None and serverrole == "domain controller":
1101 invocationid = str(uuid.uuid4())
1103 if not os.path.exists(paths.private_dir):
1104 os.mkdir(paths.private_dir)
1106 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1108 schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn,
1109 sambadn=names.sambadn, ldap_backend_type=ldap_backend_type)
1111 secrets_credentials = credentials
1112 provision_backend = None
1113 if ldap_backend_type:
1114 # We only support an LDAP backend over ldapi://
1116 provision_backend = ProvisionBackend(paths=paths, setup_path=setup_path,
1117 lp=lp, credentials=credentials,
1119 message=message, hostname=hostname,
1120 root=root, schema=schema,
1121 ldap_backend_type=ldap_backend_type,
1122 ldapadminpass=ldapadminpass,
1123 ldap_backend_extra_port=ldap_backend_extra_port,
1124 ol_mmr_urls=ol_mmr_urls,
1125 slapd_path=slapd_path,
1126 setup_ds_path=setup_ds_path,
1127 ldap_dryrun_mode=ldap_dryrun_mode)
1129 # Now use the backend credentials to access the databases
1130 credentials = provision_backend.credentials
1131 secrets_credentials = provision_backend.adminCredentials
1132 ldapi_url = provision_backend.ldapi_uri
1134 # only install a new shares config db if there is none
1135 if not os.path.exists(paths.shareconf):
1136 message("Setting up share.ldb")
1137 share_ldb = Ldb(paths.shareconf, session_info=session_info,
1138 credentials=credentials, lp=lp)
1139 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1142 message("Setting up secrets.ldb")
1143 secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
1144 session_info=session_info,
1145 credentials=secrets_credentials, lp=lp)
1147 message("Setting up the registry")
1148 setup_registry(paths.hklm, setup_path, session_info,
1151 message("Setting up idmap db")
1152 idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1155 message("Setting up SAM db")
1156 samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info,
1157 credentials=credentials, lp=lp, names=names,
1159 domainsid=domainsid,
1160 schema=schema, domainguid=domainguid, policyguid=policyguid,
1162 adminpass=adminpass, krbtgtpass=krbtgtpass,
1163 invocationid=invocationid,
1164 machinepass=machinepass, dnspass=dnspass,
1165 serverrole=serverrole, ldap_backend=provision_backend)
1167 if serverrole == "domain controller":
1168 if paths.netlogon is None:
1169 message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1170 message("Please either remove %s or see the template at %s" %
1171 ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1172 assert(paths.netlogon is not None)
1174 if paths.sysvol is None:
1175 message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1176 message("Please either remove %s or see the template at %s" %
1177 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1178 assert(paths.sysvol is not None)
1180 policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1181 "{" + policyguid + "}")
1182 os.makedirs(policy_path, 0755)
1183 open(os.path.join(policy_path, "GPT.INI"), 'w').write("")
1184 os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1185 os.makedirs(os.path.join(policy_path, "User"), 0755)
1186 if not os.path.isdir(paths.netlogon):
1187 os.makedirs(paths.netlogon, 0755)
1189 if samdb_fill == FILL_FULL:
1190 setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1191 root_uid=root_uid, nobody_uid=nobody_uid,
1192 users_gid=users_gid, wheel_gid=wheel_gid)
1194 message("Setting up sam.ldb rootDSE marking as synchronized")
1195 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1197 # Only make a zone file on the first DC, it should be replicated with DNS replication
1198 if serverrole == "domain controller":
1199 secrets_ldb = Ldb(paths.secrets, session_info=session_info,
1200 credentials=credentials, lp=lp)
1201 secretsdb_become_dc(secrets_ldb, setup_path, domain=domain,
1203 netbiosname=names.netbiosname,
1204 domainsid=domainsid,
1205 keytab_path=paths.keytab, samdb_url=paths.samdb,
1206 dns_keytab_path=paths.dns_keytab,
1207 dnspass=dnspass, machinepass=machinepass,
1208 dnsdomain=names.dnsdomain)
1210 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1211 assert isinstance(domainguid, str)
1213 create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1214 domaindn=names.domaindn, hostip=hostip,
1215 hostip6=hostip6, hostname=names.hostname,
1216 dnspass=dnspass, realm=names.realm,
1217 domainguid=domainguid, ntdsguid=names.ntdsguid)
1219 create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1220 dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1222 create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1223 dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1224 keytab_name=paths.dns_keytab)
1225 message("See %s for an example configuration include file for BIND" % paths.namedconf)
1226 message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1228 create_krb5_conf(paths.krb5conf, setup_path,
1229 dnsdomain=names.dnsdomain, hostname=names.hostname,
1231 message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1234 if provision_backend is not None:
1235 if ldap_backend_type == "fedora-ds":
1236 ldapi_db = Ldb(provision_backend.ldapi_uri, lp=lp, credentials=credentials)
1238 # delete default SASL mappings
1239 res = ldapi_db.search(expression="(!(cn=samba-admin mapping))", base="cn=mapping,cn=sasl,cn=config", scope=SCOPE_ONELEVEL, attrs=["dn"])
1241 # configure in-directory access control on Fedora DS via the aci attribute (over a direct ldapi:// socket)
1242 for i in range (0, len(res)):
1243 dn = str(res[i]["dn"])
1246 aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % names.sambadn
1249 m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci")
1251 m.dn = ldb.Dn(1, names.domaindn)
1254 m.dn = ldb.Dn(1, names.configdn)
1257 m.dn = ldb.Dn(1, names.schemadn)
1260 # if an LDAP backend is in use, terminate slapd after final provision and check its proper termination
1261 if provision_backend.slapd.poll() is None:
1263 if hasattr(provision_backend.slapd, "terminate"):
1264 provision_backend.slapd.terminate()
1266 # Older python versions don't have .terminate()
1268 os.kill(provision_backend.slapd.pid, signal.SIGTERM)
1270 #and now wait for it to die
1271 provision_backend.slapd.communicate()
1273 # now display slapd_command_file.txt to show how slapd must be started next time
1274 message("Use later the following commandline to start slapd, then Samba:")
1275 slapd_command = "\'" + "\' \'".join(provision_backend.slapd_command) + "\'"
1276 message(slapd_command)
1277 message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
1279 setup_file(setup_path("ldap_backend_startup.sh"), paths.ldapdir + "/ldap_backend_startup.sh", {
1280 "SLAPD_COMMAND" : slapd_command})
1283 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1286 message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1288 message("Once the above files are installed, your Samba4 server will be ready to use")
1289 message("Server Role: %s" % serverrole)
1290 message("Hostname: %s" % names.hostname)
1291 message("NetBIOS Domain: %s" % names.domain)
1292 message("DNS Domain: %s" % names.dnsdomain)
1293 message("DOMAIN SID: %s" % str(domainsid))
1294 if samdb_fill == FILL_FULL:
1295 message("Admin password: %s" % adminpass)
1296 if provision_backend:
1297 if provision_backend.credentials.get_bind_dn() is not None:
1298 message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1300 message("LDAP Admin User: %s" % provision_backend.credentials.get_username())
1302 message("LDAP Admin Password: %s" % provision_backend.credentials.get_password())
1304 result = ProvisionResult()
1305 result.domaindn = domaindn
1306 result.paths = paths
1308 result.samdb = samdb
1313 def provision_become_dc(setup_dir=None,
1314 smbconf=None, targetdir=None, realm=None,
1315 rootdn=None, domaindn=None, schemadn=None,
1316 configdn=None, serverdn=None,
1317 domain=None, hostname=None, domainsid=None,
1318 adminpass=None, krbtgtpass=None, domainguid=None,
1319 policyguid=None, invocationid=None, machinepass=None,
1320 dnspass=None, root=None, nobody=None, users=None,
1321 wheel=None, backup=None, serverrole=None,
1322 ldap_backend=None, ldap_backend_type=None,
1323 sitename=None, debuglevel=1):
1326 """print a message if quiet is not set."""
1329 glue.set_debug_level(debuglevel)
1331 return provision(setup_dir, message, system_session(), None,
1332 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1333 realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1334 configdn=configdn, serverdn=serverdn, domain=domain,
1335 hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
1336 machinepass=machinepass, serverrole="domain controller",
1340 def setup_db_config(setup_path, dbdir):
1341 """Setup a Berkeley database.
1343 :param setup_path: Setup path function.
1344 :param dbdir: Database directory."""
1345 if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1346 os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1347 if not os.path.isdir(os.path.join(dbdir, "tmp")):
1348 os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1350 setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1351 {"LDAPDBDIR": dbdir})
1353 class ProvisionBackend(object):
1354 def __init__(self, paths=None, setup_path=None, lp=None, credentials=None,
1355 names=None, message=None,
1356 hostname=None, root=None,
1357 schema=None, ldapadminpass=None,
1358 ldap_backend_type=None, ldap_backend_extra_port=None,
1360 setup_ds_path=None, slapd_path=None,
1361 nosync=False, ldap_dryrun_mode=False):
1362 """Provision an LDAP backend for samba4
1364 This works for OpenLDAP and Fedora DS
1367 self.ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.ldapdir, "ldapi"), safe="")
1369 if not os.path.isdir(paths.ldapdir):
1370 os.makedirs(paths.ldapdir, 0700)
1372 if ldap_backend_type == "existing":
1373 #Check to see that this 'existing' LDAP backend in fact exists
1374 ldapi_db = Ldb(self.ldapi_uri, credentials=credentials)
1375 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1376 expression="(objectClass=OpenLDAProotDSE)")
1378 # If we have got here, then we must have a valid connection to the LDAP server, with valid credentials supplied
1379 # This caused them to be set into the long-term database later in the script.
1380 self.credentials = credentials
1381 self.ldap_backend_type = "openldap" #For now, assume existing backends at least emulate OpenLDAP
1384 # we will shortly start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via self.ldapi_uri
1385 # if another instance of slapd is already running
1387 ldapi_db = Ldb(self.ldapi_uri)
1388 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1389 expression="(objectClass=OpenLDAProotDSE)");
1391 f = open(paths.slapdpid, "r")
1394 message("Check for slapd Process with PID: " + str(p) + " and terminate it manually.")
1398 raise ProvisioningError("Warning: Another slapd Instance seems already running on this host, listening to " + self.ldapi_uri + ". Please shut it down before you continue. ")
1403 # Try to print helpful messages when the user has not specified the path to slapd
1404 if slapd_path is None:
1405 raise ProvisioningError("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
1406 if not os.path.exists(slapd_path):
1407 message (slapd_path)
1408 raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1410 schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1412 os.unlink(schemadb_path)
1417 # Put the LDIF of the schema into a database so we can search on
1418 # it to generate schema-dependent configurations in Fedora DS and
1420 os.path.join(paths.ldapdir, "schema-tmp.ldb")
1421 schema.ldb.connect(schemadb_path)
1422 schema.ldb.transaction_start()
1424 # These bits of LDIF are supplied when the Schema object is created
1425 schema.ldb.add_ldif(schema.schema_dn_add)
1426 schema.ldb.modify_ldif(schema.schema_dn_modify)
1427 schema.ldb.add_ldif(schema.schema_data)
1428 schema.ldb.transaction_commit()
1430 self.credentials = Credentials()
1431 self.credentials.guess(lp)
1432 #Kerberos to an ldapi:// backend makes no sense
1433 self.credentials.set_kerberos_state(DONT_USE_KERBEROS)
1435 self.adminCredentials = Credentials()
1436 self.adminCredentials.guess(lp)
1437 #Kerberos to an ldapi:// backend makes no sense
1438 self.adminCredentials.set_kerberos_state(DONT_USE_KERBEROS)
1440 self.ldap_backend_type = ldap_backend_type
1442 if ldap_backend_type == "fedora-ds":
1443 provision_fds_backend(self, paths=paths, setup_path=setup_path,
1444 names=names, message=message,
1446 ldapadminpass=ldapadminpass, root=root,
1448 ldap_backend_extra_port=ldap_backend_extra_port,
1449 setup_ds_path=setup_ds_path,
1450 slapd_path=slapd_path,
1452 ldap_dryrun_mode=ldap_dryrun_mode)
1454 elif ldap_backend_type == "openldap":
1455 provision_openldap_backend(self, paths=paths, setup_path=setup_path,
1456 names=names, message=message,
1458 ldapadminpass=ldapadminpass, root=root,
1460 ldap_backend_extra_port=ldap_backend_extra_port,
1461 ol_mmr_urls=ol_mmr_urls,
1462 slapd_path=slapd_path,
1464 ldap_dryrun_mode=ldap_dryrun_mode)
1466 raise ProvisioningError("Unknown LDAP backend type selected")
1468 self.credentials.set_password(ldapadminpass)
1469 self.adminCredentials.set_username("samba-admin")
1470 self.adminCredentials.set_password(ldapadminpass)
1472 # Now start the slapd, so we can provision onto it. We keep the
1473 # subprocess context around, to kill this off at the successful
1475 self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False)
1477 while self.slapd.poll() is None:
1478 # Wait until the socket appears
1480 ldapi_db = Ldb(self.ldapi_uri, lp=lp, credentials=self.credentials)
1481 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1482 expression="(objectClass=OpenLDAProotDSE)")
1483 # If we have got here, then we must have a valid connection to the LDAP server!
1489 raise ProvisioningError("slapd died before we could make a connection to it")
1492 def provision_openldap_backend(result, paths=None, setup_path=None, names=None,
1494 hostname=None, ldapadminpass=None, root=None,
1496 ldap_backend_extra_port=None,
1498 slapd_path=None, nosync=False,
1499 ldap_dryrun_mode=False):
1501 #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
1504 nosync_config = "dbnosync"
1506 lnkattr = get_linked_attributes(names.schemadn,schema.ldb)
1507 refint_attributes = ""
1508 memberof_config = "# Generated from Samba4 schema\n"
1509 for att in lnkattr.keys():
1510 if lnkattr[att] is not None:
1511 refint_attributes = refint_attributes + " " + att
1513 memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1514 { "MEMBER_ATTR" : att ,
1515 "MEMBEROF_ATTR" : lnkattr[att] })
1517 refint_config = read_and_sub_file(setup_path("refint.conf"),
1518 { "LINK_ATTRS" : refint_attributes})
1520 attrs = ["linkID", "lDAPDisplayName"]
1521 res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1523 for i in range (0, len(res)):
1524 index_attr = res[i]["lDAPDisplayName"][0]
1525 if index_attr == "objectGUID":
1526 index_attr = "entryUUID"
1528 index_config += "index " + index_attr + " eq\n"
1530 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1532 mmr_replicator_acl = ""
1533 mmr_serverids_config = ""
1534 mmr_syncrepl_schema_config = ""
1535 mmr_syncrepl_config_config = ""
1536 mmr_syncrepl_user_config = ""
1539 if ol_mmr_urls is not None:
1540 # For now, make these equal
1541 mmr_pass = ldapadminpass
1543 url_list=filter(None,ol_mmr_urls.split(' '))
1544 if (len(url_list) == 1):
1545 url_list=filter(None,ol_mmr_urls.split(','))
1548 mmr_on_config = "MirrorMode On"
1549 mmr_replicator_acl = " by dn=cn=replicator,cn=samba read"
1551 for url in url_list:
1553 mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1554 { "SERVERID" : str(serverid),
1555 "LDAPSERVER" : url })
1558 mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1560 "MMRDN": names.schemadn,
1562 "MMR_PASSWORD": mmr_pass})
1565 mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1567 "MMRDN": names.configdn,
1569 "MMR_PASSWORD": mmr_pass})
1572 mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1574 "MMRDN": names.domaindn,
1576 "MMR_PASSWORD": mmr_pass })
1577 # OpenLDAP cn=config initialisation
1578 olc_syncrepl_config = ""
1580 # if mmr = yes, generate cn=config-replication directives
1581 # and olc_seed.lif for the other mmr-servers
1582 if ol_mmr_urls is not None:
1584 olc_serverids_config = ""
1585 olc_syncrepl_seed_config = ""
1586 olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
1588 for url in url_list:
1590 olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
1591 { "SERVERID" : str(serverid),
1592 "LDAPSERVER" : url })
1595 olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
1598 "MMR_PASSWORD": mmr_pass})
1600 olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1602 "LDAPSERVER" : url})
1604 setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif,
1605 {"OLC_SERVER_ID_CONF": olc_serverids_config,
1606 "OLC_PW": ldapadminpass,
1607 "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
1610 setup_file(setup_path("slapd.conf"), paths.slapdconf,
1611 {"DNSDOMAIN": names.dnsdomain,
1612 "LDAPDIR": paths.ldapdir,
1613 "DOMAINDN": names.domaindn,
1614 "CONFIGDN": names.configdn,
1615 "SCHEMADN": names.schemadn,
1616 "MEMBEROF_CONFIG": memberof_config,
1617 "MIRRORMODE": mmr_on_config,
1618 "REPLICATOR_ACL": mmr_replicator_acl,
1619 "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1620 "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1621 "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1622 "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1623 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
1624 "OLC_MMR_CONFIG": olc_mmr_config,
1625 "REFINT_CONFIG": refint_config,
1626 "INDEX_CONFIG": index_config,
1627 "NOSYNC": nosync_config})
1629 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1630 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1631 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1633 if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba", "cn=samba")):
1634 os.makedirs(os.path.join(paths.ldapdir, "db", "samba", "cn=samba"), 0700)
1636 setup_file(setup_path("cn=samba.ldif"),
1637 os.path.join(paths.ldapdir, "db", "samba", "cn=samba.ldif"),
1638 { "UUID": str(uuid.uuid4()),
1639 "LDAPTIME": timestring(int(time.time()))} )
1640 setup_file(setup_path("cn=samba-admin.ldif"),
1641 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=samba-admin.ldif"),
1642 {"LDAPADMINPASS_B64": b64encode(ldapadminpass),
1643 "UUID": str(uuid.uuid4()),
1644 "LDAPTIME": timestring(int(time.time()))} )
1646 if ol_mmr_urls is not None:
1647 setup_file(setup_path("cn=replicator.ldif"),
1648 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=replicator.ldif"),
1649 {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1650 "UUID": str(uuid.uuid4()),
1651 "LDAPTIME": timestring(int(time.time()))} )
1654 mapping = "schema-map-openldap-2.3"
1655 backend_schema = "backend-schema.schema"
1657 backend_schema_data = schema.ldb.convert_schema_to_openldap("openldap", open(setup_path(mapping), 'r').read())
1658 assert backend_schema_data is not None
1659 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1661 # now we generate the needed strings to start slapd automatically,
1662 # first ldapi_uri...
1663 if ldap_backend_extra_port is not None:
1664 # When we use MMR, we can't use 0.0.0.0 as it uses the name
1665 # specified there as part of it's clue as to it's own name,
1666 # and not to replicate to itself
1667 if ol_mmr_urls is None:
1668 server_port_string = "ldap://0.0.0.0:%d" % ldap_backend_extra_port
1670 server_port_string = "ldap://" + names.hostname + "." + names.dnsdomain +":%d" % ldap_backend_extra_port
1672 server_port_string = ""
1674 # Prepare the 'result' information - the commands to return in particular
1675 result.slapd_provision_command = [slapd_path]
1677 result.slapd_provision_command.append("-F" + paths.olcdir)
1679 result.slapd_provision_command.append("-h")
1681 # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands
1682 result.slapd_command = list(result.slapd_provision_command)
1684 result.slapd_provision_command.append(result.ldapi_uri)
1685 result.slapd_provision_command.append("-d0")
1687 uris = result.ldapi_uri
1688 if server_port_string is not "":
1689 uris = uris + " " + server_port_string
1691 result.slapd_command.append(uris)
1693 # Set the username - done here because Fedora DS still uses the admin DN and simple bind
1694 result.credentials.set_username("samba-admin")
1696 # If we were just looking for crashes up to this point, it's a
1697 # good time to exit before we realise we don't have OpenLDAP on
1699 if ldap_dryrun_mode:
1702 # Finally, convert the configuration into cn=config style!
1703 if not os.path.isdir(paths.olcdir):
1704 os.makedirs(paths.olcdir, 0770)
1706 retcode = subprocess.call([slapd_path, "-Ttest", "-f", paths.slapdconf, "-F", paths.olcdir], close_fds=True, shell=False)
1708 # We can't do this, as OpenLDAP is strange. It gives an error
1709 # output to the above, but does the conversion sucessfully...
1712 # raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1714 if not os.path.exists(os.path.join(paths.olcdir, "cn=config.ldif")):
1715 raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1717 # Don't confuse the admin by leaving the slapd.conf around
1718 os.remove(paths.slapdconf)
1721 def provision_fds_backend(result, paths=None, setup_path=None, names=None,
1723 hostname=None, ldapadminpass=None, root=None,
1725 ldap_backend_extra_port=None,
1729 ldap_dryrun_mode=False):
1731 if ldap_backend_extra_port is not None:
1732 serverport = "ServerPort=%d" % ldap_backend_extra_port
1736 setup_file(setup_path("fedorads.inf"), paths.fedoradsinf,
1738 "HOSTNAME": hostname,
1739 "DNSDOMAIN": names.dnsdomain,
1740 "LDAPDIR": paths.ldapdir,
1741 "DOMAINDN": names.domaindn,
1742 "LDAPMANAGERDN": names.ldapmanagerdn,
1743 "LDAPMANAGERPASS": ldapadminpass,
1744 "SERVERPORT": serverport})
1746 setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions,
1747 {"CONFIGDN": names.configdn,
1748 "SCHEMADN": names.schemadn,
1749 "SAMBADN": names.sambadn,
1752 setup_file(setup_path("fedorads-sasl.ldif"), paths.fedoradssasl,
1753 {"SAMBADN": names.sambadn,
1756 setup_file(setup_path("fedorads-samba.ldif"), paths.fedoradssamba,
1757 {"SAMBADN": names.sambadn,
1758 "LDAPADMINPASS": ldapadminpass
1761 mapping = "schema-map-fedora-ds-1.0"
1762 backend_schema = "99_ad.ldif"
1764 # Build a schema file in Fedora DS format
1765 backend_schema_data = schema.ldb.convert_schema_to_openldap("fedora-ds", open(setup_path(mapping), 'r').read())
1766 assert backend_schema_data is not None
1767 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1769 result.credentials.set_bind_dn(names.ldapmanagerdn)
1771 # Destory the target directory, or else setup-ds.pl will complain
1772 fedora_ds_dir = os.path.join(paths.ldapdir, "slapd-samba4")
1773 shutil.rmtree(fedora_ds_dir, True)
1775 result.slapd_provision_command = [slapd_path, "-D", fedora_ds_dir, "-i", paths.slapdpid];
1776 #In the 'provision' command line, stay in the foreground so we can easily kill it
1777 result.slapd_provision_command.append("-d0")
1779 #the command for the final run is the normal script
1780 result.slapd_command = [os.path.join(paths.ldapdir, "slapd-samba4", "start-slapd")]
1782 # If we were just looking for crashes up to this point, it's a
1783 # good time to exit before we realise we don't have Fedora DS on
1784 if ldap_dryrun_mode:
1787 # Try to print helpful messages when the user has not specified the path to the setup-ds tool
1788 if setup_ds_path is None:
1789 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\"!")
1790 if not os.path.exists(setup_ds_path):
1791 message (setup_ds_path)
1792 raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1794 # Run the Fedora DS setup utility
1795 retcode = subprocess.call([setup_ds_path, "--silent", "--file", paths.fedoradsinf], close_fds=True, shell=False)
1797 raise ProvisioningError("setup-ds failed")
1800 retcode = subprocess.call([
1801 os.path.join(paths.ldapdir, "slapd-samba4", "ldif2db"), "-s", names.sambadn, "-i", paths.fedoradssamba],
1802 close_fds=True, shell=False)
1804 raise("ldib2db failed")
1806 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1807 """Create a PHP LDAP admin configuration file.
1809 :param path: Path to write the configuration to.
1810 :param setup_path: Function to generate setup paths.
1812 setup_file(setup_path("phpldapadmin-config.php"), path,
1813 {"S4_LDAPI_URI": ldapi_uri})
1816 def create_zone_file(path, setup_path, dnsdomain, domaindn,
1817 hostip, hostip6, hostname, dnspass, realm, domainguid,
1819 """Write out a DNS zone file, from the info in the current database.
1821 :param path: Path of the new zone file.
1822 :param setup_path: Setup path function.
1823 :param dnsdomain: DNS Domain name
1824 :param domaindn: DN of the Domain
1825 :param hostip: Local IPv4 IP
1826 :param hostip6: Local IPv6 IP
1827 :param hostname: Local hostname
1828 :param dnspass: Password for DNS
1829 :param realm: Realm name
1830 :param domainguid: GUID of the domain.
1831 :param ntdsguid: GUID of the hosts nTDSDSA record.
1833 assert isinstance(domainguid, str)
1835 if hostip6 is not None:
1836 hostip6_base_line = " IN AAAA " + hostip6
1837 hostip6_host_line = hostname + " IN AAAA " + hostip6
1839 hostip6_base_line = ""
1840 hostip6_host_line = ""
1842 if hostip is not None:
1843 hostip_base_line = " IN A " + hostip
1844 hostip_host_line = hostname + " IN A " + hostip
1846 hostip_base_line = ""
1847 hostip_host_line = ""
1849 setup_file(setup_path("provision.zone"), path, {
1850 "DNSPASS_B64": b64encode(dnspass),
1851 "HOSTNAME": hostname,
1852 "DNSDOMAIN": dnsdomain,
1854 "HOSTIP_BASE_LINE": hostip_base_line,
1855 "HOSTIP_HOST_LINE": hostip_host_line,
1856 "DOMAINGUID": domainguid,
1857 "DATESTRING": time.strftime("%Y%m%d%H"),
1858 "DEFAULTSITE": DEFAULTSITE,
1859 "NTDSGUID": ntdsguid,
1860 "HOSTIP6_BASE_LINE": hostip6_base_line,
1861 "HOSTIP6_HOST_LINE": hostip6_host_line,
1865 def create_named_conf(path, setup_path, realm, dnsdomain,
1867 """Write out a file containing zone statements suitable for inclusion in a
1868 named.conf file (including GSS-TSIG configuration).
1870 :param path: Path of the new named.conf file.
1871 :param setup_path: Setup path function.
1872 :param realm: Realm name
1873 :param dnsdomain: DNS Domain name
1874 :param private_dir: Path to private directory
1875 :param keytab_name: File name of DNS keytab file
1878 setup_file(setup_path("named.conf"), path, {
1879 "DNSDOMAIN": dnsdomain,
1881 "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1882 "PRIVATE_DIR": private_dir
1885 def create_named_txt(path, setup_path, realm, dnsdomain,
1886 private_dir, keytab_name):
1887 """Write out a file containing zone statements suitable for inclusion in a
1888 named.conf file (including GSS-TSIG configuration).
1890 :param path: Path of the new named.conf file.
1891 :param setup_path: Setup path function.
1892 :param realm: Realm name
1893 :param dnsdomain: DNS Domain name
1894 :param private_dir: Path to private directory
1895 :param keytab_name: File name of DNS keytab file
1898 setup_file(setup_path("named.txt"), path, {
1899 "DNSDOMAIN": dnsdomain,
1901 "DNS_KEYTAB": keytab_name,
1902 "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1903 "PRIVATE_DIR": private_dir
1906 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1907 """Write out a file containing zone statements suitable for inclusion in a
1908 named.conf file (including GSS-TSIG configuration).
1910 :param path: Path of the new named.conf file.
1911 :param setup_path: Setup path function.
1912 :param dnsdomain: DNS Domain name
1913 :param hostname: Local hostname
1914 :param realm: Realm name
1917 setup_file(setup_path("krb5.conf"), path, {
1918 "DNSDOMAIN": dnsdomain,
1919 "HOSTNAME": hostname,