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
42 from credentials import Credentials, DONT_USE_KERBEROS
43 from auth import system_session
44 from samba import version, Ldb, substitute_var, valid_netbios_name, check_all_substituted, \
46 from samba.samdb import SamDB
47 from samba.idmap import IDmapDB
48 from samba.dcerpc import security
50 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, timestring
51 from ms_schema import read_ms_schema
52 from signal import SIGTERM
54 __docformat__ = "restructuredText"
58 """Find the setup directory used by provision."""
59 dirname = os.path.dirname(__file__)
60 if "/site-packages/" in dirname:
61 prefix = "/".join(dirname[:dirname.index("/site-packages/")].split("/")[:-2])
62 for suffix in ["share/setup", "share/samba/setup", "setup"]:
63 ret = os.path.join(prefix, suffix)
64 if os.path.isdir(ret):
67 ret = os.path.join(dirname, "../../../setup")
68 if os.path.isdir(ret):
70 raise Exception("Unable to find setup directory.")
73 DEFAULTSITE = "Default-First-Site-Name"
75 class InvalidNetbiosName(Exception):
76 """A specified name was not a valid NetBIOS name."""
77 def __init__(self, name):
78 super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)
81 class ProvisionPaths(object):
94 self.dns_keytab = None
97 self.private_dir = None
100 self.modulesconf = None
101 self.memberofconf = None
102 self.fedoradsinf = None
103 self.fedoradspartitions = None
105 self.olmmrserveridsconf = None
106 self.olmmrsyncreplconf = None
109 self.olcseedldif = None
112 class ProvisionNames(object):
118 self.ldapmanagerdn = None
119 self.dnsdomain = None
121 self.netbiosname = None
128 class ProvisionResult(object):
135 class Schema(object):
136 def __init__(self, setup_path, schemadn=None,
138 """Load schema for the SamDB from the AD schema files and samba4_schema.ldif
140 :param samdb: Load a schema into a SamDB.
141 :param setup_path: Setup path function.
142 :param schemadn: DN of the schema
143 :param serverdn: DN of the server
145 Returns the schema data loaded, to avoid double-parsing when then needing to add it to the db
149 self.schema_data = read_ms_schema(setup_path('ad-schema/MS-AD_Schema_2K8_Attributes.txt'),
150 setup_path('ad-schema/MS-AD_Schema_2K8_Classes.txt'))
151 self.schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
152 self.schema_data = substitute_var(self.schema_data, {"SCHEMADN": schemadn})
153 check_all_substituted(self.schema_data)
155 self.schema_dn_modify = read_and_sub_file(setup_path("provision_schema_basedn_modify.ldif"),
156 {"SCHEMADN": schemadn,
157 "SERVERDN": serverdn,
159 self.schema_dn_add = read_and_sub_file(setup_path("provision_schema_basedn.ldif"),
160 {"SCHEMADN": schemadn
163 prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
164 prefixmap = b64encode(prefixmap)
166 # We don't actually add this ldif, just parse it
167 prefixmap_ldif = "dn: cn=schema\nprefixMap:: %s\n\n" % prefixmap
168 self.ldb.set_schema_from_ldif(prefixmap_ldif, self.schema_data)
171 # Return a hash with the forward attribute as a key and the back as the value
172 def get_linked_attributes(schemadn,schemaldb):
173 attrs = ["linkID", "lDAPDisplayName"]
174 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)
176 for i in range (0, len(res)):
177 expression = "(&(objectclass=attributeSchema)(linkID=%d)(attributeSyntax=2.5.5.1))" % (int(res[i]["linkID"][0])+1)
178 target = schemaldb.searchone(basedn=schemadn,
179 expression=expression,
180 attribute="lDAPDisplayName",
182 if target is not None:
183 attributes[str(res[i]["lDAPDisplayName"])]=str(target)
187 def get_dnsyntax_attributes(schemadn,schemaldb):
188 attrs = ["linkID", "lDAPDisplayName"]
189 res = schemaldb.search(expression="(&(!(linkID=*))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1))", base=schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
191 for i in range (0, len(res)):
192 attributes.append(str(res[i]["lDAPDisplayName"]))
197 def check_install(lp, session_info, credentials):
198 """Check whether the current install seems ok.
200 :param lp: Loadparm context
201 :param session_info: Session information
202 :param credentials: Credentials
204 if lp.get("realm") == "":
205 raise Exception("Realm empty")
206 ldb = Ldb(lp.get("sam database"), session_info=session_info,
207 credentials=credentials, lp=lp)
208 if len(ldb.search("(cn=Administrator)")) != 1:
209 raise "No administrator account found"
212 def findnss(nssfn, names):
213 """Find a user or group from a list of possibilities.
215 :param nssfn: NSS Function to try (should raise KeyError if not found)
216 :param names: Names to check.
217 :return: Value return by first names list.
224 raise KeyError("Unable to find user/group %r" % names)
227 findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
228 findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
231 def read_and_sub_file(file, subst_vars):
232 """Read a file and sub in variables found in it
234 :param file: File to be read (typically from setup directory)
235 param subst_vars: Optional variables to subsitute in the file.
237 data = open(file, 'r').read()
238 if subst_vars is not None:
239 data = substitute_var(data, subst_vars)
240 check_all_substituted(data)
244 def setup_add_ldif(ldb, ldif_path, subst_vars=None):
245 """Setup a ldb in the private dir.
247 :param ldb: LDB file to import data into
248 :param ldif_path: Path of the LDIF file to load
249 :param subst_vars: Optional variables to subsitute in LDIF.
251 assert isinstance(ldif_path, str)
253 data = read_and_sub_file(ldif_path, subst_vars)
257 def setup_modify_ldif(ldb, ldif_path, subst_vars=None):
258 """Modify a ldb in the private dir.
260 :param ldb: LDB object.
261 :param ldif_path: LDIF file path.
262 :param subst_vars: Optional dictionary with substitution variables.
264 data = read_and_sub_file(ldif_path, subst_vars)
266 ldb.modify_ldif(data)
269 def setup_ldb(ldb, ldif_path, subst_vars):
270 """Import a LDIF a file into a LDB handle, optionally substituting variables.
272 :note: Either all LDIF data will be added or none (using transactions).
274 :param ldb: LDB file to import into.
275 :param ldif_path: Path to the LDIF file.
276 :param subst_vars: Dictionary with substitution variables.
278 assert ldb is not None
279 ldb.transaction_start()
281 setup_add_ldif(ldb, ldif_path, subst_vars)
283 ldb.transaction_cancel()
285 ldb.transaction_commit()
288 def setup_file(template, fname, subst_vars):
289 """Setup a file in the private dir.
291 :param template: Path of the template file.
292 :param fname: Path of the file to create.
293 :param subst_vars: Substitution variables.
297 if os.path.exists(f):
300 data = read_and_sub_file(template, subst_vars)
301 open(f, 'w').write(data)
304 def provision_paths_from_lp(lp, dnsdomain):
305 """Set the default paths for provisioning.
307 :param lp: Loadparm context.
308 :param dnsdomain: DNS Domain name
310 paths = ProvisionPaths()
311 paths.private_dir = lp.get("private dir")
312 paths.keytab = "secrets.keytab"
313 paths.dns_keytab = "dns.keytab"
315 paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
316 paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb")
317 paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb")
318 paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
319 paths.templates = os.path.join(paths.private_dir, "templates.ldb")
320 paths.dns = os.path.join(paths.private_dir, dnsdomain + ".zone")
321 paths.namedconf = os.path.join(paths.private_dir, "named.conf")
322 paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
323 paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
324 paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
325 paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
326 paths.phpldapadminconfig = os.path.join(paths.private_dir,
327 "phpldapadmin-config.php")
328 paths.ldapdir = os.path.join(paths.private_dir,
330 paths.slapdconf = os.path.join(paths.ldapdir,
332 paths.slapdpid = os.path.join(paths.ldapdir,
334 paths.modulesconf = os.path.join(paths.ldapdir,
336 paths.memberofconf = os.path.join(paths.ldapdir,
338 paths.fedoradsinf = os.path.join(paths.ldapdir,
340 paths.fedoradspartitions = os.path.join(paths.ldapdir,
341 "fedorads-partitions.ldif")
342 paths.olmmrserveridsconf = os.path.join(paths.ldapdir,
343 "mmr_serverids.conf")
344 paths.olmmrsyncreplconf = os.path.join(paths.ldapdir,
346 paths.olcdir = os.path.join(paths.ldapdir,
348 paths.olcseedldif = os.path.join(paths.ldapdir,
350 paths.hklm = "hklm.ldb"
351 paths.hkcr = "hkcr.ldb"
352 paths.hkcu = "hkcu.ldb"
353 paths.hku = "hku.ldb"
354 paths.hkpd = "hkpd.ldb"
355 paths.hkpt = "hkpt.ldb"
357 paths.sysvol = lp.get("path", "sysvol")
359 paths.netlogon = lp.get("path", "netlogon")
361 paths.smbconf = lp.configfile
366 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, serverrole=None,
367 rootdn=None, domaindn=None, configdn=None, schemadn=None, serverdn=None,
369 """Guess configuration settings to use."""
372 hostname = socket.gethostname().split(".")[0].lower()
374 netbiosname = hostname.upper()
375 if not valid_netbios_name(netbiosname):
376 raise InvalidNetbiosName(netbiosname)
378 hostname = hostname.lower()
380 if dnsdomain is None:
381 dnsdomain = lp.get("realm")
383 if serverrole is None:
384 serverrole = lp.get("server role")
386 assert dnsdomain is not None
387 realm = dnsdomain.upper()
389 if lp.get("realm").upper() != realm:
390 raise Exception("realm '%s' in %s must match chosen realm '%s'" %
391 (lp.get("realm"), lp.configfile, realm))
393 dnsdomain = dnsdomain.lower()
395 if serverrole == "domain controller":
397 domain = lp.get("workgroup")
399 domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
400 if lp.get("workgroup").upper() != domain.upper():
401 raise Exception("workgroup '%s' in smb.conf must match chosen domain '%s'",
402 lp.get("workgroup"), domain)
406 domaindn = "CN=" + netbiosname
408 assert domain is not None
409 domain = domain.upper()
410 if not valid_netbios_name(domain):
411 raise InvalidNetbiosName(domain)
417 configdn = "CN=Configuration," + rootdn
419 schemadn = "CN=Schema," + configdn
424 names = ProvisionNames()
425 names.rootdn = rootdn
426 names.domaindn = domaindn
427 names.configdn = configdn
428 names.schemadn = schemadn
429 names.ldapmanagerdn = "CN=Manager," + rootdn
430 names.dnsdomain = dnsdomain
431 names.domain = domain
433 names.netbiosname = netbiosname
434 names.hostname = hostname
435 names.sitename = sitename
436 names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (netbiosname, sitename, configdn)
441 def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
443 """Create a new smb.conf file based on a couple of basic settings.
445 assert smbconf is not None
447 hostname = socket.gethostname().split(".")[0].lower()
449 if serverrole is None:
450 serverrole = "standalone"
452 assert serverrole in ("domain controller", "member server", "standalone")
453 if serverrole == "domain controller":
455 elif serverrole == "member server":
456 smbconfsuffix = "member"
457 elif serverrole == "standalone":
458 smbconfsuffix = "standalone"
460 assert domain is not None
461 assert realm is not None
463 default_lp = param.LoadParm()
464 #Load non-existant file
465 if os.path.exists(smbconf):
466 default_lp.load(smbconf)
468 if targetdir is not None:
469 privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
470 lockdir_line = "lock dir = " + os.path.abspath(targetdir)
472 default_lp.set("lock dir", os.path.abspath(targetdir))
477 sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
478 netlogon = os.path.join(sysvol, realm.lower(), "scripts")
480 setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
482 "HOSTNAME": hostname,
485 "SERVERROLE": serverrole,
486 "NETLOGONPATH": netlogon,
487 "SYSVOLPATH": sysvol,
488 "PRIVATEDIR_LINE": privatedir_line,
489 "LOCKDIR_LINE": lockdir_line
493 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
494 users_gid, wheel_gid):
495 """setup reasonable name mappings for sam names to unix names.
497 :param samdb: SamDB object.
498 :param idmap: IDmap db object.
499 :param sid: The domain sid.
500 :param domaindn: The domain DN.
501 :param root_uid: uid of the UNIX root user.
502 :param nobody_uid: uid of the UNIX nobody user.
503 :param users_gid: gid of the UNIX users group.
504 :param wheel_gid: gid of the UNIX wheel group."""
506 def add_foreign(self, domaindn, sid, desc):
507 """Add a foreign security principle."""
509 dn: CN=%s,CN=ForeignSecurityPrincipals,%s
511 objectClass: foreignSecurityPrincipal
513 """ % (sid, domaindn, desc)
514 # deliberately ignore errors from this, as the records may
516 for msg in self.parse_ldif(add):
519 add_foreign(samdb, domaindn, "S-1-5-7", "Anonymous")
520 add_foreign(samdb, domaindn, "S-1-1-0", "World")
521 add_foreign(samdb, domaindn, "S-1-5-2", "Network")
522 add_foreign(samdb, domaindn, "S-1-5-18", "System")
523 add_foreign(samdb, domaindn, "S-1-5-11", "Authenticated Users")
525 idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
526 idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
528 idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
529 idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
531 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
533 serverrole, ldap_backend=None,
535 """Setup the partitions for the SAM database.
537 Alternatively, provision() may call this, and then populate the database.
539 :note: This will wipe the Sam Database!
541 :note: This function always removes the local SAM LDB file. The erase
542 parameter controls whether to erase the existing data, which
543 may not be stored locally but in LDAP.
545 assert session_info is not None
547 # We use options=["modules:"] to stop the modules loading - we
548 # just want to wipe and re-initialise the database, not start it up
551 samdb = Ldb(url=samdb_path, session_info=session_info,
552 credentials=credentials, lp=lp, options=["modules:"])
554 samdb.erase_except_schema_controlled()
556 os.unlink(samdb_path)
557 samdb = Ldb(url=samdb_path, session_info=session_info,
558 credentials=credentials, lp=lp, options=["modules:"])
560 samdb.erase_except_schema_controlled()
563 #Add modules to the list to activate them by default
564 #beware often order is important
566 # Some Known ordering constraints:
567 # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
568 # - objectclass must be before password_hash, because password_hash checks
569 # that the objectclass is of type person (filled in by objectclass
570 # module when expanding the objectclass list)
571 # - partition must be last
572 # - each partition has its own module list then
573 modules_list = ["rootdse",
591 "extended_dn_out_ldb"]
592 modules_list2 = ["show_deleted",
595 domaindn_ldb = "users.ldb"
596 configdn_ldb = "configuration.ldb"
597 schemadn_ldb = "schema.ldb"
598 if ldap_backend is not None:
599 domaindn_ldb = ldap_backend.ldapi_uri
600 configdn_ldb = ldap_backend.ldapi_uri
601 schemadn_ldb = ldap_backend.ldapi_uri
603 if ldap_backend.ldap_backend_type == "fedora-ds":
604 backend_modules = ["nsuniqueid", "paged_searches"]
605 # We can handle linked attributes here, as we don't have directory-side subtree operations
606 tdb_modules_list = ["linked_attributes", "extended_dn_out_dereference"]
607 elif ldap_backend.ldap_backend_type == "openldap":
608 backend_modules = ["entryuuid", "paged_searches"]
609 # OpenLDAP handles subtree renames, so we don't want to do any of these things
610 tdb_modules_list = ["extended_dn_out_dereference"]
612 elif serverrole == "domain controller":
613 backend_modules = ["repl_meta_data"]
615 backend_modules = ["objectguid"]
617 if tdb_modules_list is None:
618 tdb_modules_list_as_string = ""
620 tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
622 samdb.transaction_start()
624 message("Setting up sam.ldb partitions and settings")
625 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
626 "SCHEMADN": names.schemadn,
627 "SCHEMADN_LDB": schemadn_ldb,
628 "SCHEMADN_MOD2": ",objectguid",
629 "CONFIGDN": names.configdn,
630 "CONFIGDN_LDB": configdn_ldb,
631 "DOMAINDN": names.domaindn,
632 "DOMAINDN_LDB": domaindn_ldb,
633 "SCHEMADN_MOD": "schema_fsmo,instancetype",
634 "CONFIGDN_MOD": "naming_fsmo,instancetype",
635 "DOMAINDN_MOD": "pdc_fsmo,instancetype",
636 "MODULES_LIST": ",".join(modules_list),
637 "TDB_MODULES_LIST": tdb_modules_list_as_string,
638 "MODULES_LIST2": ",".join(modules_list2),
639 "BACKEND_MOD": ",".join(backend_modules),
642 samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
644 message("Setting up sam.ldb rootDSE")
645 setup_samdb_rootdse(samdb, setup_path, names)
648 samdb.transaction_cancel()
651 samdb.transaction_commit()
655 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain,
656 netbiosname, domainsid, keytab_path, samdb_url,
657 dns_keytab_path, dnspass, machinepass):
658 """Add DC-specific bits to a secrets database.
660 :param secretsdb: Ldb Handle to the secrets database
661 :param setup_path: Setup path function
662 :param machinepass: Machine password
664 setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), {
665 "MACHINEPASS_B64": b64encode(machinepass),
668 "DNSDOMAIN": dnsdomain,
669 "DOMAINSID": str(domainsid),
670 "SECRETS_KEYTAB": keytab_path,
671 "NETBIOSNAME": netbiosname,
672 "SAM_LDB": samdb_url,
673 "DNS_KEYTAB": dns_keytab_path,
674 "DNSPASS_B64": b64encode(dnspass),
678 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
679 """Setup the secrets database.
681 :param path: Path to the secrets database.
682 :param setup_path: Get the path to a setup file.
683 :param session_info: Session info.
684 :param credentials: Credentials
685 :param lp: Loadparm context
686 :return: LDB handle for the created secrets database
688 if os.path.exists(path):
690 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
693 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
694 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
696 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
698 if credentials is not None and credentials.authentication_requested():
699 if credentials.get_bind_dn() is not None:
700 setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
701 "LDAPMANAGERDN": credentials.get_bind_dn(),
702 "LDAPMANAGERPASS_B64": b64encode(credentials.get_password())
705 setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
706 "LDAPADMINUSER": credentials.get_username(),
707 "LDAPADMINREALM": credentials.get_realm(),
708 "LDAPADMINPASS_B64": b64encode(credentials.get_password())
714 def setup_templatesdb(path, setup_path, session_info, lp):
715 """Setup the templates database.
717 :param path: Path to the database.
718 :param setup_path: Function for obtaining the path to setup files.
719 :param session_info: Session info
720 :param credentials: Credentials
721 :param lp: Loadparm context
723 templates_ldb = Ldb(url=path, session_info=session_info,
727 templates_ldb.erase()
728 # This should be 'except LdbError', but on a re-provision the assert in ldb.erase fires, and we need to catch that too
732 templates_ldb.load_ldif_file_add(setup_path("provision_templates_init.ldif"))
734 templates_ldb = Ldb(url=path, session_info=session_info,
737 templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif"))
740 def setup_registry(path, setup_path, session_info, lp):
741 """Setup the registry.
743 :param path: Path to the registry database
744 :param setup_path: Function that returns the path to a setup.
745 :param session_info: Session information
746 :param credentials: Credentials
747 :param lp: Loadparm context
749 reg = registry.Registry()
750 hive = registry.open_ldb(path, session_info=session_info,
752 reg.mount_hive(hive, registry.HKEY_LOCAL_MACHINE)
753 provision_reg = setup_path("provision.reg")
754 assert os.path.exists(provision_reg)
755 reg.diff_apply(provision_reg)
758 def setup_idmapdb(path, setup_path, session_info, lp):
759 """Setup the idmap database.
761 :param path: path to the idmap database
762 :param setup_path: Function that returns a path to a setup file
763 :param session_info: Session information
764 :param credentials: Credentials
765 :param lp: Loadparm context
767 if os.path.exists(path):
770 idmap_ldb = IDmapDB(path, session_info=session_info,
774 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
778 def setup_samdb_rootdse(samdb, setup_path, names):
779 """Setup the SamDB rootdse.
781 :param samdb: Sam Database handle
782 :param setup_path: Obtain setup path
784 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
785 "SCHEMADN": names.schemadn,
786 "NETBIOSNAME": names.netbiosname,
787 "DNSDOMAIN": names.dnsdomain,
788 "REALM": names.realm,
789 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
790 "DOMAINDN": names.domaindn,
791 "ROOTDN": names.rootdn,
792 "CONFIGDN": names.configdn,
793 "SERVERDN": names.serverdn,
797 def setup_self_join(samdb, names,
798 machinepass, dnspass,
799 domainsid, invocationid, setup_path,
800 policyguid, domainControllerFunctionality):
801 """Join a host to its own domain."""
802 assert isinstance(invocationid, str)
803 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
804 "CONFIGDN": names.configdn,
805 "SCHEMADN": names.schemadn,
806 "DOMAINDN": names.domaindn,
807 "SERVERDN": names.serverdn,
808 "INVOCATIONID": invocationid,
809 "NETBIOSNAME": names.netbiosname,
810 "DEFAULTSITE": names.sitename,
811 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
812 "MACHINEPASS_B64": b64encode(machinepass),
813 "DNSPASS_B64": b64encode(dnspass),
814 "REALM": names.realm,
815 "DOMAIN": names.domain,
816 "DNSDOMAIN": names.dnsdomain,
817 "SAMBA_VERSION_STRING": version,
818 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
820 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
821 "POLICYGUID": policyguid,
822 "DNSDOMAIN": names.dnsdomain,
823 "DOMAINSID": str(domainsid),
824 "DOMAINDN": names.domaindn})
826 # Setup fSMORoleOwner entries to point at the newly created DC entry
827 setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
828 "DOMAINDN": names.domaindn,
829 "CONFIGDN": names.configdn,
830 "SCHEMADN": names.schemadn,
831 "DEFAULTSITE": names.sitename,
832 "SERVERDN": names.serverdn
836 def setup_samdb(path, setup_path, session_info, credentials, lp,
838 domainsid, domainguid, policyguid,
839 fill, adminpass, krbtgtpass,
840 machinepass, invocationid, dnspass,
841 serverrole, schema=None, ldap_backend=None):
842 """Setup a complete SAM Database.
844 :note: This will wipe the main SAM database file!
847 domainFunctionality = DS_BEHAVIOR_WIN2008
848 forestFunctionality = DS_BEHAVIOR_WIN2008
849 domainControllerFunctionality = DS_BEHAVIOR_WIN2008
851 # Also wipes the database
852 setup_samdb_partitions(path, setup_path, message=message, lp=lp,
853 credentials=credentials, session_info=session_info,
855 ldap_backend=ldap_backend, serverrole=serverrole)
858 schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn)
860 # Load the database, but importantly, use Ldb not SamDB as we don't want to load the global schema
861 samdb = Ldb(session_info=session_info,
862 credentials=credentials, lp=lp)
864 message("Pre-loading the Samba 4 and AD schema")
866 # Load the schema from the one we computed earlier
867 samdb.set_schema_from_ldb(schema.ldb)
869 # And now we can connect to the DB - the schema won't be loaded from the DB
873 samdb.load_ldif_file_add(setup_path("provision_options.ldif"))
878 samdb.transaction_start()
880 message("Erasing data from partitions")
881 # Load the schema (again). This time it will force a reindex,
882 # and will therefore make the erase_partitions() below
883 # computationally sane
884 samdb.set_schema_from_ldb(schema.ldb)
885 samdb.erase_partitions()
887 # Set the domain functionality levels onto the database.
888 # Various module (the password_hash module in particular) need
889 # to know what level of AD we are emulating.
891 # These will be fixed into the database via the database
892 # modifictions below, but we need them set from the start.
893 samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
894 samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
895 samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
897 samdb.set_domain_sid(str(domainsid))
898 if serverrole == "domain controller":
899 samdb.set_invocation_id(invocationid)
901 message("Adding DomainDN: %s" % names.domaindn)
902 if serverrole == "domain controller":
903 domain_oc = "domainDNS"
905 domain_oc = "samba4LocalDomain"
907 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
908 "DOMAINDN": names.domaindn,
909 "DOMAIN_OC": domain_oc
912 message("Modifying DomainDN: " + names.domaindn + "")
913 if domainguid is not None:
914 domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
918 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
919 "LDAPTIME": timestring(int(time.time())),
920 "DOMAINSID": str(domainsid),
921 "SCHEMADN": names.schemadn,
922 "NETBIOSNAME": names.netbiosname,
923 "DEFAULTSITE": names.sitename,
924 "CONFIGDN": names.configdn,
925 "SERVERDN": names.serverdn,
926 "POLICYGUID": policyguid,
927 "DOMAINDN": names.domaindn,
928 "DOMAINGUID_MOD": domainguid_mod,
929 "DOMAIN_FUNCTIONALITY": str(domainFunctionality)
932 message("Adding configuration container")
933 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
934 "CONFIGDN": names.configdn,
936 message("Modifying configuration container")
937 setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
938 "CONFIGDN": names.configdn,
939 "SCHEMADN": names.schemadn,
942 # The LDIF here was created when the Schema object was constructed
943 message("Setting up sam.ldb schema")
944 samdb.add_ldif(schema.schema_dn_add)
945 samdb.modify_ldif(schema.schema_dn_modify)
946 samdb.write_prefixes_from_schema()
947 samdb.add_ldif(schema.schema_data)
948 setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
949 {"SCHEMADN": names.schemadn})
951 message("Setting up sam.ldb configuration data")
952 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
953 "CONFIGDN": names.configdn,
954 "NETBIOSNAME": names.netbiosname,
955 "DEFAULTSITE": names.sitename,
956 "DNSDOMAIN": names.dnsdomain,
957 "DOMAIN": names.domain,
958 "SCHEMADN": names.schemadn,
959 "DOMAINDN": names.domaindn,
960 "SERVERDN": names.serverdn,
961 "FOREST_FUNCTIONALALITY": str(forestFunctionality)
964 message("Setting up display specifiers")
965 setup_add_ldif(samdb, setup_path("display_specifiers.ldif"),
966 {"CONFIGDN": names.configdn})
968 message("Adding users container")
969 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
970 "DOMAINDN": names.domaindn})
971 message("Modifying users container")
972 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
973 "DOMAINDN": names.domaindn})
974 message("Adding computers container")
975 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
976 "DOMAINDN": names.domaindn})
977 message("Modifying computers container")
978 setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
979 "DOMAINDN": names.domaindn})
980 message("Setting up sam.ldb data")
981 setup_add_ldif(samdb, setup_path("provision.ldif"), {
982 "DOMAINDN": names.domaindn,
983 "NETBIOSNAME": names.netbiosname,
984 "DEFAULTSITE": names.sitename,
985 "CONFIGDN": names.configdn,
986 "SERVERDN": names.serverdn
989 if fill == FILL_FULL:
990 message("Setting up sam.ldb users and groups")
991 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
992 "DOMAINDN": names.domaindn,
993 "DOMAINSID": str(domainsid),
994 "CONFIGDN": names.configdn,
995 "ADMINPASS_B64": b64encode(adminpass),
996 "KRBTGTPASS_B64": b64encode(krbtgtpass),
999 if serverrole == "domain controller":
1000 message("Setting up self join")
1001 setup_self_join(samdb, names=names, invocationid=invocationid,
1003 machinepass=machinepass,
1004 domainsid=domainsid, policyguid=policyguid,
1005 setup_path=setup_path, domainControllerFunctionality=domainControllerFunctionality)
1008 samdb.transaction_cancel()
1011 samdb.transaction_commit()
1016 FILL_NT4SYNC = "NT4SYNC"
1020 def provision(setup_dir, message, session_info,
1021 credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL, realm=None,
1022 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1024 domain=None, hostname=None, hostip=None, hostip6=None,
1025 domainsid=None, adminpass=None, ldapadminpass=None,
1026 krbtgtpass=None, domainguid=None,
1027 policyguid=None, invocationid=None, machinepass=None,
1028 dnspass=None, root=None, nobody=None, users=None,
1029 wheel=None, backup=None, aci=None, serverrole=None,
1030 ldap_backend_extra_port=None, ldap_backend_type=None, sitename=None,
1031 ol_mmr_urls=None, ol_olc=None,
1032 setup_ds_path=None, slapd_path=None, nosync=False,
1033 ldap_dryrun_mode=False):
1036 :note: caution, this wipes all existing data!
1039 def setup_path(file):
1040 return os.path.join(setup_dir, file)
1042 if domainsid is None:
1043 domainsid = security.random_sid()
1045 if policyguid is None:
1046 policyguid = str(uuid.uuid4())
1047 if adminpass is None:
1048 adminpass = glue.generate_random_str(12)
1049 if krbtgtpass is None:
1050 krbtgtpass = glue.generate_random_str(12)
1051 if machinepass is None:
1052 machinepass = glue.generate_random_str(12)
1054 dnspass = glue.generate_random_str(12)
1055 if ldapadminpass is None:
1056 #Make a new, random password between Samba and it's LDAP server
1057 ldapadminpass=glue.generate_random_str(12)
1060 root_uid = findnss_uid([root or "root"])
1061 nobody_uid = findnss_uid([nobody or "nobody"])
1062 users_gid = findnss_gid([users or "users"])
1064 wheel_gid = findnss_gid(["wheel", "adm"])
1066 wheel_gid = findnss_gid([wheel])
1068 if targetdir is not None:
1069 if (not os.path.exists(os.path.join(targetdir, "etc"))):
1070 os.makedirs(os.path.join(targetdir, "etc"))
1071 smbconf = os.path.join(targetdir, "etc", "smb.conf")
1072 elif smbconf is None:
1073 smbconf = param.default_path()
1075 # only install a new smb.conf if there isn't one there already
1076 if not os.path.exists(smbconf):
1077 make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
1080 lp = param.LoadParm()
1083 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1084 dnsdomain=realm, serverrole=serverrole, sitename=sitename,
1085 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1088 paths = provision_paths_from_lp(lp, names.dnsdomain)
1092 hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1093 except socket.gaierror, (socket.EAI_NODATA, msg):
1098 hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1099 except socket.gaierror, (socket.EAI_NODATA, msg):
1102 if serverrole is None:
1103 serverrole = lp.get("server role")
1105 assert serverrole in ("domain controller", "member server", "standalone")
1106 if invocationid is None and serverrole == "domain controller":
1107 invocationid = str(uuid.uuid4())
1109 if not os.path.exists(paths.private_dir):
1110 os.mkdir(paths.private_dir)
1112 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1114 schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn)
1116 provision_backend = None
1117 if ldap_backend_type:
1118 # We only support an LDAP backend over ldapi://
1120 provision_backend = ProvisionBackend(paths=paths, setup_path=setup_path, lp=lp, credentials=credentials,
1122 message=message, hostname=hostname,
1123 root=root, schema=schema, ldap_backend_type=ldap_backend_type,
1124 ldapadminpass=ldapadminpass,
1125 ldap_backend_extra_port=ldap_backend_extra_port,
1126 ol_mmr_urls=ol_mmr_urls,
1127 slapd_path=slapd_path,
1128 setup_ds_path=setup_ds_path,
1129 ldap_dryrun_mode=ldap_dryrun_mode)
1131 # Now use the backend credentials to access the databases
1132 credentials = provision_backend.credentials
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=credentials, lp=lp)
1147 message("Setting up the registry")
1148 setup_registry(paths.hklm, setup_path, session_info,
1151 message("Setting up templates db")
1152 setup_templatesdb(paths.templates, setup_path, session_info=session_info,
1155 message("Setting up idmap db")
1156 idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1159 message("Setting up SAM db")
1160 samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info,
1161 credentials=credentials, lp=lp, names=names,
1163 domainsid=domainsid,
1164 schema=schema, domainguid=domainguid, policyguid=policyguid,
1166 adminpass=adminpass, krbtgtpass=krbtgtpass,
1167 invocationid=invocationid,
1168 machinepass=machinepass, dnspass=dnspass,
1169 serverrole=serverrole, ldap_backend=provision_backend)
1171 if serverrole == "domain controller":
1172 if paths.netlogon is None:
1173 message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1174 message("Please either remove %s or see the template at %s" %
1175 ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1176 assert(paths.netlogon is not None)
1178 if paths.sysvol is None:
1179 message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1180 message("Please either remove %s or see the template at %s" %
1181 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1182 assert(paths.sysvol is not None)
1184 policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1185 "{" + policyguid + "}")
1186 os.makedirs(policy_path, 0755)
1187 open(os.path.join(policy_path, "GPT.INI"), 'w').write("")
1188 os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1189 os.makedirs(os.path.join(policy_path, "User"), 0755)
1190 if not os.path.isdir(paths.netlogon):
1191 os.makedirs(paths.netlogon, 0755)
1193 if samdb_fill == FILL_FULL:
1194 setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1195 root_uid=root_uid, nobody_uid=nobody_uid,
1196 users_gid=users_gid, wheel_gid=wheel_gid)
1198 message("Setting up sam.ldb rootDSE marking as synchronized")
1199 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1201 # Only make a zone file on the first DC, it should be replicated with DNS replication
1202 if serverrole == "domain controller":
1203 secrets_ldb = Ldb(paths.secrets, session_info=session_info,
1204 credentials=credentials, lp=lp)
1205 secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1206 netbiosname=names.netbiosname, domainsid=domainsid,
1207 keytab_path=paths.keytab, samdb_url=paths.samdb,
1208 dns_keytab_path=paths.dns_keytab, dnspass=dnspass,
1209 machinepass=machinepass, dnsdomain=names.dnsdomain)
1211 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1212 assert isinstance(domainguid, str)
1213 hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1214 expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1215 scope=SCOPE_SUBTREE)
1216 assert isinstance(hostguid, str)
1218 create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1219 domaindn=names.domaindn, hostip=hostip,
1220 hostip6=hostip6, hostname=names.hostname,
1221 dnspass=dnspass, realm=names.realm,
1222 domainguid=domainguid, hostguid=hostguid)
1224 create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1225 dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1227 create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1228 dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1229 keytab_name=paths.dns_keytab)
1230 message("See %s for an example configuration include file for BIND" % paths.namedconf)
1231 message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1233 create_krb5_conf(paths.krb5conf, setup_path, dnsdomain=names.dnsdomain,
1234 hostname=names.hostname, realm=names.realm)
1235 message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1238 # if backend is openldap, terminate slapd after final provision and check its proper termination
1239 if provision_backend is not None and provision_backend.slapd is not None:
1240 if provision_backend.slapd.poll() is None:
1242 if hasattr(provision_backend.slapd, "terminate"):
1243 provision_backend.slapd.terminate()
1246 os.kill(provision_backend.slapd.pid, signal.SIGTERM)
1248 #and now wait for it to die
1249 provision_backend.slapd.communicate()
1251 # now display slapd_command_file.txt to show how slapd must be started next time
1252 message("Use later the following commandline to start slapd, then Samba:")
1253 slapd_command = "\'" + "\' \'".join(provision_backend.slapd_command) + "\'"
1254 message(slapd_command)
1255 message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
1257 setup_file(setup_path("ldap_backend_startup.sh"), paths.ldapdir + "/ldap_backend_startup.sh", {
1258 "SLAPD_COMMAND" : slapd_command})
1261 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1264 message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1266 message("Once the above files are installed, your Samba4 server will be ready to use")
1267 message("Server Role: %s" % serverrole)
1268 message("Hostname: %s" % names.hostname)
1269 message("NetBIOS Domain: %s" % names.domain)
1270 message("DNS Domain: %s" % names.dnsdomain)
1271 message("DOMAIN SID: %s" % str(domainsid))
1272 if samdb_fill == FILL_FULL:
1273 message("Admin password: %s" % adminpass)
1274 if provision_backend:
1275 if provision_backend.credentials.get_bind_dn() is not None:
1276 message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1278 message("LDAP Admin User: %s" % provision_backend.credentials.get_username())
1280 message("LDAP Admin Password: %s" % provision_backend.credentials.get_password())
1282 result = ProvisionResult()
1283 result.domaindn = domaindn
1284 result.paths = paths
1286 result.samdb = samdb
1291 def provision_become_dc(setup_dir=None,
1292 smbconf=None, targetdir=None, realm=None,
1293 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1295 domain=None, hostname=None, domainsid=None,
1296 adminpass=None, krbtgtpass=None, domainguid=None,
1297 policyguid=None, invocationid=None, machinepass=None,
1298 dnspass=None, root=None, nobody=None, users=None,
1299 wheel=None, backup=None, serverrole=None,
1300 ldap_backend=None, ldap_backend_type=None, sitename=None):
1303 """print a message if quiet is not set."""
1306 return provision(setup_dir, message, system_session(), None,
1307 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm,
1308 rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn, serverdn=serverdn,
1309 domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename)
1312 def setup_db_config(setup_path, dbdir):
1313 """Setup a Berkeley database.
1315 :param setup_path: Setup path function.
1316 :param dbdir: Database directory."""
1317 if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1318 os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1319 if not os.path.isdir(os.path.join(dbdir, "tmp")):
1320 os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1322 setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1323 {"LDAPDBDIR": dbdir})
1325 class ProvisionBackend(object):
1326 def __init__(self, paths=None, setup_path=None, lp=None, credentials=None,
1327 names=None, message=None,
1328 hostname=None, root=None,
1329 schema=None, ldapadminpass=None,
1330 ldap_backend_type=None, ldap_backend_extra_port=None,
1332 setup_ds_path=None, slapd_path=None,
1333 nosync=False, ldap_dryrun_mode=False):
1334 """Provision an LDAP backend for samba4
1336 This works for OpenLDAP and Fedora DS
1339 self.ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.ldapdir, "ldapi"), safe="")
1341 if not os.path.isdir(paths.ldapdir):
1342 os.makedirs(paths.ldapdir, 0700)
1344 if ldap_backend_type == "existing":
1345 #Check to see that this 'existing' LDAP backend in fact exists
1346 ldapi_db = Ldb(self.ldapi_uri, credentials=credentials)
1347 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1348 expression="(objectClass=OpenLDAProotDSE)")
1350 # If we have got here, then we must have a valid connection to the LDAP server, with valid credentials supplied
1351 # This caused them to be set into the long-term database later in the script.
1352 self.credentials = credentials
1353 self.ldap_backend_type = "openldap" #For now, assume existing backends at least emulate OpenLDAP
1356 # we will shortly start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via self.ldapi_uri
1357 # if another instance of slapd is already running
1359 ldapi_db = Ldb(self.ldapi_uri)
1360 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1361 expression="(objectClass=OpenLDAProotDSE)");
1363 f = open(paths.slapdpid, "r")
1366 message("Check for slapd Process with PID: " + str(p) + " and terminate it manually.")
1370 raise("Warning: Another slapd Instance seems already running on this host, listening to " + self.ldapi_uri + ". Please shut it down before you continue. ")
1375 # Try to print helpful messages when the user has not specified the path to slapd
1376 if slapd_path is None:
1377 raise("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
1378 if not os.path.exists(slapd_path):
1379 message (slapd_path)
1380 raise("Warning: Given Path to slapd does not exist!")
1382 schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1384 os.unlink(schemadb_path)
1389 # Put the LDIF of the schema into a database so we can search on
1390 # it to generate schema-dependent configurations in Fedora DS and
1392 os.path.join(paths.ldapdir, "schema-tmp.ldb")
1393 schema.ldb.connect(schemadb_path)
1394 schema.ldb.transaction_start()
1396 # These bits of LDIF are supplied when the Schema object is created
1397 schema.ldb.add_ldif(schema.schema_dn_add)
1398 schema.ldb.modify_ldif(schema.schema_dn_modify)
1399 schema.ldb.add_ldif(schema.schema_data)
1400 schema.ldb.transaction_commit()
1402 self.credentials = Credentials()
1403 self.credentials.guess(lp)
1404 #Kerberos to an ldapi:// backend makes no sense
1405 self.credentials.set_kerberos_state(DONT_USE_KERBEROS)
1406 self.ldap_backend_type = ldap_backend_type
1408 if ldap_backend_type == "fedora-ds":
1409 provision_fds_backend(self, paths=paths, setup_path=setup_path, names=names, message=message,
1410 hostname=hostname, ldapadminpass=ldapadminpass, root=root,
1411 schema=schema, ldap_backend_extra_port=ldap_backend_extra_port,
1412 setup_ds_path=setup_ds_path, slapd_path=slapd_path,
1413 nosync=nosync, ldap_dryrun_mode=ldap_dryrun_mode)
1415 elif ldap_backend_type == "openldap":
1416 provision_openldap_backend(self, paths=paths, setup_path=setup_path, names=names, message=message,
1417 hostname=hostname, ldapadminpass=ldapadminpass, root=root,
1418 schema=schema, ldap_backend_extra_port=ldap_backend_extra_port,
1419 ol_mmr_urls=ol_mmr_urls,
1420 slapd_path=slapd_path,
1421 nosync=nosync, ldap_dryrun_mode=ldap_dryrun_mode)
1423 raise("Unknown LDAP backend type selected")
1425 self.credentials.set_password(ldapadminpass)
1427 # Now start the slapd, so we can provision onto it. We keep the
1428 # subprocess context around, to kill this off at the successful
1430 self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False)
1432 while self.slapd.poll() is None:
1433 # Wait until the socket appears
1435 ldapi_db = Ldb(self.ldapi_uri, lp=lp, credentials=self.credentials)
1436 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1437 expression="(objectClass=OpenLDAProotDSE)")
1438 # If we have got here, then we must have a valid connection to the LDAP server!
1444 raise "slapd died before we could make a connection to it"
1447 def provision_openldap_backend(result, paths=None, setup_path=None, names=None, message=None,
1448 hostname=None, ldapadminpass=None, root=None,
1450 ldap_backend_extra_port=None,
1452 slapd_path=None, nosync=False,
1453 ldap_dryrun_mode=False):
1455 #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
1458 nosync_config = "dbnosync"
1460 lnkattr = get_linked_attributes(names.schemadn,schema.ldb)
1461 refint_attributes = ""
1462 memberof_config = "# Generated from Samba4 schema\n"
1463 for att in lnkattr.keys():
1464 if lnkattr[att] is not None:
1465 refint_attributes = refint_attributes + " " + att
1467 memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1468 { "MEMBER_ATTR" : att ,
1469 "MEMBEROF_ATTR" : lnkattr[att] })
1471 refint_config = read_and_sub_file(setup_path("refint.conf"),
1472 { "LINK_ATTRS" : refint_attributes})
1474 attrs = ["linkID", "lDAPDisplayName"]
1475 res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1477 for i in range (0, len(res)):
1478 index_attr = res[i]["lDAPDisplayName"][0]
1479 if index_attr == "objectGUID":
1480 index_attr = "entryUUID"
1482 index_config += "index " + index_attr + " eq\n"
1484 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1486 mmr_replicator_acl = ""
1487 mmr_serverids_config = ""
1488 mmr_syncrepl_schema_config = ""
1489 mmr_syncrepl_config_config = ""
1490 mmr_syncrepl_user_config = ""
1493 if ol_mmr_urls is not None:
1494 # For now, make these equal
1495 mmr_pass = ldapadminpass
1497 url_list=filter(None,ol_mmr_urls.split(' '))
1498 if (len(url_list) == 1):
1499 url_list=filter(None,ol_mmr_urls.split(','))
1502 mmr_on_config = "MirrorMode On"
1503 mmr_replicator_acl = " by dn=cn=replicator,cn=samba read"
1505 for url in url_list:
1507 mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1508 { "SERVERID" : str(serverid),
1509 "LDAPSERVER" : url })
1512 mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1514 "MMRDN": names.schemadn,
1516 "MMR_PASSWORD": mmr_pass})
1519 mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1521 "MMRDN": names.configdn,
1523 "MMR_PASSWORD": mmr_pass})
1526 mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1528 "MMRDN": names.domaindn,
1530 "MMR_PASSWORD": mmr_pass })
1531 # OpenLDAP cn=config initialisation
1532 olc_syncrepl_config = ""
1534 # if mmr = yes, generate cn=config-replication directives
1535 # and olc_seed.lif for the other mmr-servers
1536 if ol_mmr_urls is not None:
1538 olc_serverids_config = ""
1539 olc_syncrepl_seed_config = ""
1540 olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
1542 for url in url_list:
1544 olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
1545 { "SERVERID" : str(serverid),
1546 "LDAPSERVER" : url })
1549 olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
1552 "MMR_PASSWORD": mmr_pass})
1554 olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1556 "LDAPSERVER" : url})
1558 setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif,
1559 {"OLC_SERVER_ID_CONF": olc_serverids_config,
1560 "OLC_PW": ldapadminpass,
1561 "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
1564 setup_file(setup_path("slapd.conf"), paths.slapdconf,
1565 {"DNSDOMAIN": names.dnsdomain,
1566 "LDAPDIR": paths.ldapdir,
1567 "DOMAINDN": names.domaindn,
1568 "CONFIGDN": names.configdn,
1569 "SCHEMADN": names.schemadn,
1570 "MEMBEROF_CONFIG": memberof_config,
1571 "MIRRORMODE": mmr_on_config,
1572 "REPLICATOR_ACL": mmr_replicator_acl,
1573 "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1574 "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1575 "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1576 "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1577 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
1578 "OLC_MMR_CONFIG": olc_mmr_config,
1579 "REFINT_CONFIG": refint_config,
1580 "INDEX_CONFIG": index_config,
1581 "NOSYNC": nosync_config})
1583 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1584 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1585 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1587 if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba", "cn=samba")):
1588 os.makedirs(os.path.join(paths.ldapdir, "db", "samba", "cn=samba"), 0700)
1590 setup_file(setup_path("cn=samba.ldif"),
1591 os.path.join(paths.ldapdir, "db", "samba", "cn=samba.ldif"),
1592 { "UUID": str(uuid.uuid4()),
1593 "LDAPTIME": timestring(int(time.time()))} )
1594 setup_file(setup_path("cn=samba-admin.ldif"),
1595 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=samba-admin.ldif"),
1596 {"LDAPADMINPASS_B64": b64encode(ldapadminpass),
1597 "UUID": str(uuid.uuid4()),
1598 "LDAPTIME": timestring(int(time.time()))} )
1600 if ol_mmr_urls is not None:
1601 setup_file(setup_path("cn=replicator.ldif"),
1602 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=replicator.ldif"),
1603 {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1604 "UUID": str(uuid.uuid4()),
1605 "LDAPTIME": timestring(int(time.time()))} )
1608 mapping = "schema-map-openldap-2.3"
1609 backend_schema = "backend-schema.schema"
1611 backend_schema_data = schema.ldb.convert_schema_to_openldap("openldap", open(setup_path(mapping), 'r').read())
1612 assert backend_schema_data is not None
1613 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1615 # now we generate the needed strings to start slapd automatically,
1616 # first ldapi_uri...
1617 if ldap_backend_extra_port is not None:
1618 # When we use MMR, we can't use 0.0.0.0 as it uses the name
1619 # specified there as part of it's clue as to it's own name,
1620 # and not to replicate to itself
1621 if ol_mmr_urls is None:
1622 server_port_string = "ldap://0.0.0.0:%d" % ldap_backend_extra_port
1624 server_port_string = "ldap://" + names.hostname + "." + names.dnsdomain +":%d" % ldap_backend_extra_port
1626 server_port_string = ""
1628 # Prepare the 'result' information - the commands to return in particular
1629 result.slapd_provision_command = [slapd_path]
1631 result.slapd_provision_command.append("-F" + paths.olcdir)
1633 result.slapd_provision_command.append("-h")
1635 # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands
1636 result.slapd_command = list(result.slapd_provision_command)
1638 result.slapd_provision_command.append(result.ldapi_uri)
1639 result.slapd_provision_command.append("-d0")
1641 uris = result.ldapi_uri
1642 if server_port_string is not "":
1643 uris = uris + " " + server_port_string
1645 result.slapd_command.append(uris)
1647 # Set the username - done here because Fedora DS still uses the admin DN and simple bind
1648 result.credentials.set_username("samba-admin")
1650 # If we were just looking for crashes up to this point, it's a
1651 # good time to exit before we realise we don't have OpenLDAP on
1653 if ldap_dryrun_mode:
1656 # Finally, convert the configuration into cn=config style!
1657 if not os.path.isdir(paths.olcdir):
1658 os.makedirs(paths.olcdir, 0770)
1660 retcode = subprocess.call([slapd_path, "-Ttest", "-f", paths.slapdconf, "-F", paths.olcdir], close_fds=True, shell=False)
1662 # We can't do this, as OpenLDAP is strange. It gives an error
1663 # output to the above, but does the conversion sucessfully...
1666 # raise("conversion from slapd.conf to cn=config failed")
1668 if not os.path.exists(os.path.join(paths.olcdir, "cn=config.ldif")):
1669 raise("conversion from slapd.conf to cn=config failed")
1671 # Don't confuse the admin by leaving the slapd.conf around
1672 os.remove(paths.slapdconf)
1675 def provision_fds_backend(result, paths=None, setup_path=None, names=None, message=None,
1676 hostname=None, ldapadminpass=None, root=None,
1678 ldap_backend_extra_port=None,
1682 ldap_dryrun_mode=False):
1684 if ldap_backend_extra_port is not None:
1685 serverport = "ServerPort=%d" % ldap_backend_extra_port
1689 setup_file(setup_path("fedorads.inf"), paths.fedoradsinf,
1691 "HOSTNAME": hostname,
1692 "DNSDOMAIN": names.dnsdomain,
1693 "LDAPDIR": paths.ldapdir,
1694 "DOMAINDN": names.domaindn,
1695 "LDAPMANAGERDN": names.ldapmanagerdn,
1696 "LDAPMANAGERPASS": ldapadminpass,
1697 "SERVERPORT": serverport})
1699 setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions,
1700 {"CONFIGDN": names.configdn,
1701 "SCHEMADN": names.schemadn,
1704 mapping = "schema-map-fedora-ds-1.0"
1705 backend_schema = "99_ad.ldif"
1707 # Build a schema file in Fedora DS format
1708 backend_schema_data = schema.ldb.convert_schema_to_openldap("fedora-ds", open(setup_path(mapping), 'r').read())
1709 assert backend_schema_data is not None
1710 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1712 result.credentials.set_bind_dn(names.ldapmanagerdn)
1714 # Destory the target directory, or else setup-ds.pl will complain
1715 fedora_ds_dir = os.path.join(paths.ldapdir, "slapd-samba4")
1716 shutil.rmtree(fedora_ds_dir, True)
1718 result.slapd_provision_command = [slapd_path, "-D", fedora_ds_dir, "-i", paths.slapdpid];
1719 #In the 'provision' command line, stay in the foreground so we can easily kill it
1720 result.slapd_provision_command.append("-d0")
1722 #the command for the final run is the normal script
1723 result.slapd_command = [os.path.join(paths.ldapdir, "slapd-samba4", "start-slapd")]
1725 # If we were just looking for crashes up to this point, it's a
1726 # good time to exit before we realise we don't have Fedora DS on
1727 if ldap_dryrun_mode:
1730 # Try to print helpful messages when the user has not specified the path to the setup-ds tool
1731 if setup_ds_path is None:
1732 raise("Warning: Fedora DS LDAP-Backend must be setup with path to setup-ds, e.g. --setup-ds-path=\"/usr/sbin/setup-ds.pl\"!")
1733 if not os.path.exists(setup_ds_path):
1734 message (setup_ds_path)
1735 raise("Warning: Given Path to slapd does not exist!")
1737 # Run the Fedora DS setup utility
1738 retcode = subprocess.call([setup_ds_path, "--silent", "--file", paths.fedoradsinf], close_fds=True, shell=False)
1740 raise("setup-ds failed")
1742 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1743 """Create a PHP LDAP admin configuration file.
1745 :param path: Path to write the configuration to.
1746 :param setup_path: Function to generate setup paths.
1748 setup_file(setup_path("phpldapadmin-config.php"), path,
1749 {"S4_LDAPI_URI": ldapi_uri})
1752 def create_zone_file(path, setup_path, dnsdomain, domaindn,
1753 hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid):
1754 """Write out a DNS zone file, from the info in the current database.
1756 :param path: Path of the new zone file.
1757 :param setup_path: Setup path function.
1758 :param dnsdomain: DNS Domain name
1759 :param domaindn: DN of the Domain
1760 :param hostip: Local IPv4 IP
1761 :param hostip6: Local IPv6 IP
1762 :param hostname: Local hostname
1763 :param dnspass: Password for DNS
1764 :param realm: Realm name
1765 :param domainguid: GUID of the domain.
1766 :param hostguid: GUID of the host.
1768 assert isinstance(domainguid, str)
1770 if hostip6 is not None:
1771 hostip6_base_line = " IN AAAA " + hostip6
1772 hostip6_host_line = hostname + " IN AAAA " + hostip6
1774 hostip6_base_line = ""
1775 hostip6_host_line = ""
1777 if hostip is not None:
1778 hostip_base_line = " IN A " + hostip
1779 hostip_host_line = hostname + " IN A " + hostip
1781 hostip_base_line = ""
1782 hostip_host_line = ""
1784 setup_file(setup_path("provision.zone"), path, {
1785 "DNSPASS_B64": b64encode(dnspass),
1786 "HOSTNAME": hostname,
1787 "DNSDOMAIN": dnsdomain,
1789 "HOSTIP_BASE_LINE": hostip_base_line,
1790 "HOSTIP_HOST_LINE": hostip_host_line,
1791 "DOMAINGUID": domainguid,
1792 "DATESTRING": time.strftime("%Y%m%d%H"),
1793 "DEFAULTSITE": DEFAULTSITE,
1794 "HOSTGUID": hostguid,
1795 "HOSTIP6_BASE_LINE": hostip6_base_line,
1796 "HOSTIP6_HOST_LINE": hostip6_host_line,
1800 def create_named_conf(path, setup_path, realm, dnsdomain,
1802 """Write out a file containing zone statements suitable for inclusion in a
1803 named.conf file (including GSS-TSIG configuration).
1805 :param path: Path of the new named.conf file.
1806 :param setup_path: Setup path function.
1807 :param realm: Realm name
1808 :param dnsdomain: DNS Domain name
1809 :param private_dir: Path to private directory
1810 :param keytab_name: File name of DNS keytab file
1813 setup_file(setup_path("named.conf"), path, {
1814 "DNSDOMAIN": dnsdomain,
1816 "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1817 "PRIVATE_DIR": private_dir
1820 def create_named_txt(path, setup_path, realm, dnsdomain,
1821 private_dir, keytab_name):
1822 """Write out a file containing zone statements suitable for inclusion in a
1823 named.conf file (including GSS-TSIG configuration).
1825 :param path: Path of the new named.conf file.
1826 :param setup_path: Setup path function.
1827 :param realm: Realm name
1828 :param dnsdomain: DNS Domain name
1829 :param private_dir: Path to private directory
1830 :param keytab_name: File name of DNS keytab file
1833 setup_file(setup_path("named.txt"), path, {
1834 "DNSDOMAIN": dnsdomain,
1836 "DNS_KEYTAB": keytab_name,
1837 "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1838 "PRIVATE_DIR": private_dir
1841 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1842 """Write out a file containing zone statements suitable for inclusion in a
1843 named.conf file (including GSS-TSIG configuration).
1845 :param path: Path of the new named.conf file.
1846 :param setup_path: Setup path function.
1847 :param dnsdomain: DNS Domain name
1848 :param hostname: Local hostname
1849 :param realm: Realm name
1852 setup_file(setup_path("krb5.conf"), path, {
1853 "DNSDOMAIN": dnsdomain,
1854 "HOSTNAME": hostname,