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
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
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)
154 prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
155 prefixmap = b64encode(prefixmap)
157 self.schema_dn_modify = read_and_sub_file(setup_path("provision_schema_basedn_modify.ldif"),
158 {"SCHEMADN": schemadn,
159 "PREFIXMAP_B64": prefixmap,
160 "SERVERDN": serverdn,
162 self.schema_dn_add = read_and_sub_file(setup_path("provision_schema_basedn.ldif"),
163 {"SCHEMADN": schemadn
165 self.ldb.set_schema_from_ldif(self.schema_dn_modify, self.schema_data)
168 def check_install(lp, session_info, credentials):
169 """Check whether the current install seems ok.
171 :param lp: Loadparm context
172 :param session_info: Session information
173 :param credentials: Credentials
175 if lp.get("realm") == "":
176 raise Exception("Realm empty")
177 ldb = Ldb(lp.get("sam database"), session_info=session_info,
178 credentials=credentials, lp=lp)
179 if len(ldb.search("(cn=Administrator)")) != 1:
180 raise "No administrator account found"
183 def findnss(nssfn, names):
184 """Find a user or group from a list of possibilities.
186 :param nssfn: NSS Function to try (should raise KeyError if not found)
187 :param names: Names to check.
188 :return: Value return by first names list.
195 raise KeyError("Unable to find user/group %r" % names)
198 findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
199 findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
202 def read_and_sub_file(file, subst_vars):
203 """Read a file and sub in variables found in it
205 :param file: File to be read (typically from setup directory)
206 param subst_vars: Optional variables to subsitute in the file.
208 data = open(file, 'r').read()
209 if subst_vars is not None:
210 data = substitute_var(data, subst_vars)
211 check_all_substituted(data)
215 def setup_add_ldif(ldb, ldif_path, subst_vars=None):
216 """Setup a ldb in the private dir.
218 :param ldb: LDB file to import data into
219 :param ldif_path: Path of the LDIF file to load
220 :param subst_vars: Optional variables to subsitute in LDIF.
222 assert isinstance(ldif_path, str)
224 data = read_and_sub_file(ldif_path, subst_vars)
228 def setup_modify_ldif(ldb, ldif_path, subst_vars=None):
229 """Modify a ldb in the private dir.
231 :param ldb: LDB object.
232 :param ldif_path: LDIF file path.
233 :param subst_vars: Optional dictionary with substitution variables.
235 data = read_and_sub_file(ldif_path, subst_vars)
237 ldb.modify_ldif(data)
240 def setup_ldb(ldb, ldif_path, subst_vars):
241 """Import a LDIF a file into a LDB handle, optionally substituting variables.
243 :note: Either all LDIF data will be added or none (using transactions).
245 :param ldb: LDB file to import into.
246 :param ldif_path: Path to the LDIF file.
247 :param subst_vars: Dictionary with substitution variables.
249 assert ldb is not None
250 ldb.transaction_start()
252 setup_add_ldif(ldb, ldif_path, subst_vars)
254 ldb.transaction_cancel()
256 ldb.transaction_commit()
259 def setup_file(template, fname, subst_vars):
260 """Setup a file in the private dir.
262 :param template: Path of the template file.
263 :param fname: Path of the file to create.
264 :param subst_vars: Substitution variables.
268 if os.path.exists(f):
271 data = read_and_sub_file(template, subst_vars)
272 open(f, 'w').write(data)
275 def provision_paths_from_lp(lp, dnsdomain):
276 """Set the default paths for provisioning.
278 :param lp: Loadparm context.
279 :param dnsdomain: DNS Domain name
281 paths = ProvisionPaths()
282 paths.private_dir = lp.get("private dir")
283 paths.keytab = "secrets.keytab"
284 paths.dns_keytab = "dns.keytab"
286 paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
287 paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb")
288 paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb")
289 paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
290 paths.templates = os.path.join(paths.private_dir, "templates.ldb")
291 paths.dns = os.path.join(paths.private_dir, dnsdomain + ".zone")
292 paths.namedconf = os.path.join(paths.private_dir, "named.conf")
293 paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
294 paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
295 paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
296 paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
297 paths.phpldapadminconfig = os.path.join(paths.private_dir,
298 "phpldapadmin-config.php")
299 paths.ldapdir = os.path.join(paths.private_dir,
301 paths.slapdconf = os.path.join(paths.ldapdir,
303 paths.slapdpid = os.path.join(paths.ldapdir,
305 paths.modulesconf = os.path.join(paths.ldapdir,
307 paths.memberofconf = os.path.join(paths.ldapdir,
309 paths.fedoradsinf = os.path.join(paths.ldapdir,
311 paths.fedoradspartitions = os.path.join(paths.ldapdir,
312 "fedorads-partitions.ldif")
313 paths.olmmrserveridsconf = os.path.join(paths.ldapdir,
314 "mmr_serverids.conf")
315 paths.olmmrsyncreplconf = os.path.join(paths.ldapdir,
317 paths.olcdir = os.path.join(paths.ldapdir,
319 paths.olcseedldif = os.path.join(paths.ldapdir,
321 paths.hklm = "hklm.ldb"
322 paths.hkcr = "hkcr.ldb"
323 paths.hkcu = "hkcu.ldb"
324 paths.hku = "hku.ldb"
325 paths.hkpd = "hkpd.ldb"
326 paths.hkpt = "hkpt.ldb"
328 paths.sysvol = lp.get("path", "sysvol")
330 paths.netlogon = lp.get("path", "netlogon")
332 paths.smbconf = lp.configfile
337 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, serverrole=None,
338 rootdn=None, domaindn=None, configdn=None, schemadn=None, serverdn=None,
340 """Guess configuration settings to use."""
343 hostname = socket.gethostname().split(".")[0].lower()
345 netbiosname = hostname.upper()
346 if not valid_netbios_name(netbiosname):
347 raise InvalidNetbiosName(netbiosname)
349 hostname = hostname.lower()
351 if dnsdomain is None:
352 dnsdomain = lp.get("realm")
354 if serverrole is None:
355 serverrole = lp.get("server role")
357 assert dnsdomain is not None
358 realm = dnsdomain.upper()
360 if lp.get("realm").upper() != realm:
361 raise Exception("realm '%s' in %s must match chosen realm '%s'" %
362 (lp.get("realm"), lp.configfile, realm))
364 dnsdomain = dnsdomain.lower()
366 if serverrole == "domain controller":
368 domain = lp.get("workgroup")
370 domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
371 if lp.get("workgroup").upper() != domain.upper():
372 raise Exception("workgroup '%s' in smb.conf must match chosen domain '%s'",
373 lp.get("workgroup"), domain)
377 domaindn = "CN=" + netbiosname
379 assert domain is not None
380 domain = domain.upper()
381 if not valid_netbios_name(domain):
382 raise InvalidNetbiosName(domain)
388 configdn = "CN=Configuration," + rootdn
390 schemadn = "CN=Schema," + configdn
395 names = ProvisionNames()
396 names.rootdn = rootdn
397 names.domaindn = domaindn
398 names.configdn = configdn
399 names.schemadn = schemadn
400 names.ldapmanagerdn = "CN=Manager," + rootdn
401 names.dnsdomain = dnsdomain
402 names.domain = domain
404 names.netbiosname = netbiosname
405 names.hostname = hostname
406 names.sitename = sitename
407 names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (netbiosname, sitename, configdn)
412 def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
414 """Create a new smb.conf file based on a couple of basic settings.
416 assert smbconf is not None
418 hostname = socket.gethostname().split(".")[0].lower()
420 if serverrole is None:
421 serverrole = "standalone"
423 assert serverrole in ("domain controller", "member server", "standalone")
424 if serverrole == "domain controller":
426 elif serverrole == "member server":
427 smbconfsuffix = "member"
428 elif serverrole == "standalone":
429 smbconfsuffix = "standalone"
431 assert domain is not None
432 assert realm is not None
434 default_lp = param.LoadParm()
435 #Load non-existant file
436 if os.path.exists(smbconf):
437 default_lp.load(smbconf)
439 if targetdir is not None:
440 privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
441 lockdir_line = "lock dir = " + os.path.abspath(targetdir)
443 default_lp.set("lock dir", os.path.abspath(targetdir))
448 sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
449 netlogon = os.path.join(sysvol, realm.lower(), "scripts")
451 setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
453 "HOSTNAME": hostname,
456 "SERVERROLE": serverrole,
457 "NETLOGONPATH": netlogon,
458 "SYSVOLPATH": sysvol,
459 "PRIVATEDIR_LINE": privatedir_line,
460 "LOCKDIR_LINE": lockdir_line
464 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
465 users_gid, wheel_gid):
466 """setup reasonable name mappings for sam names to unix names.
468 :param samdb: SamDB object.
469 :param idmap: IDmap db object.
470 :param sid: The domain sid.
471 :param domaindn: The domain DN.
472 :param root_uid: uid of the UNIX root user.
473 :param nobody_uid: uid of the UNIX nobody user.
474 :param users_gid: gid of the UNIX users group.
475 :param wheel_gid: gid of the UNIX wheel group."""
477 def add_foreign(self, domaindn, sid, desc):
478 """Add a foreign security principle."""
480 dn: CN=%s,CN=ForeignSecurityPrincipals,%s
482 objectClass: foreignSecurityPrincipal
484 """ % (sid, domaindn, desc)
485 # deliberately ignore errors from this, as the records may
487 for msg in self.parse_ldif(add):
490 # add some foreign sids
491 add_foreign(samdb, domaindn, "S-1-5-7", "Anonymous")
492 add_foreign(samdb, domaindn, "S-1-1-0", "World")
493 add_foreign(samdb, domaindn, "S-1-5-2", "Network")
494 add_foreign(samdb, domaindn, "S-1-5-18", "System")
495 add_foreign(samdb, domaindn, "S-1-5-11", "Authenticated Users")
498 idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
499 idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
501 idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
502 idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
505 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
507 serverrole, ldap_backend=None,
509 """Setup the partitions for the SAM database.
511 Alternatively, provision() may call this, and then populate the database.
513 :note: This will wipe the Sam Database!
515 :note: This function always removes the local SAM LDB file. The erase
516 parameter controls whether to erase the existing data, which
517 may not be stored locally but in LDAP.
519 assert session_info is not None
521 # We use options=["modules:"] to stop the modules loading - we
522 # just want to wipe and re-initialise the database, not start it up
525 samdb = Ldb(url=samdb_path, session_info=session_info,
526 credentials=credentials, lp=lp, options=["modules:"])
530 os.unlink(samdb_path)
531 samdb = Ldb(url=samdb_path, session_info=session_info,
532 credentials=credentials, lp=lp, options=["modules:"])
537 #Add modules to the list to activate them by default
538 #beware often order is important
540 # Some Known ordering constraints:
541 # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
542 # - objectclass must be before password_hash, because password_hash checks
543 # that the objectclass is of type person (filled in by objectclass
544 # module when expanding the objectclass list)
545 # - partition must be last
546 # - each partition has its own module list then
547 modules_list = ["rootdse",
565 "extended_dn_out_ldb"]
566 modules_list2 = ["show_deleted",
569 domaindn_ldb = "users.ldb"
570 configdn_ldb = "configuration.ldb"
571 schemadn_ldb = "schema.ldb"
572 if ldap_backend is not None:
573 domaindn_ldb = ldap_backend.ldapi_uri
574 configdn_ldb = ldap_backend.ldapi_uri
575 schemadn_ldb = ldap_backend.ldapi_uri
577 if ldap_backend.ldap_backend_type == "fedora-ds":
578 backend_modules = ["nsuniqueid", "paged_searches"]
579 # We can handle linked attributes here, as we don't have directory-side subtree operations
580 tdb_modules_list = ["linked_attributes", "extended_dn_out_dereference"]
581 elif ldap_backend.ldap_backend_type == "openldap":
582 backend_modules = ["entryuuid", "paged_searches"]
583 # OpenLDAP handles subtree renames, so we don't want to do any of these things
584 tdb_modules_list = ["extended_dn_out_dereference"]
586 elif serverrole == "domain controller":
587 backend_modules = ["repl_meta_data"]
589 backend_modules = ["objectguid"]
591 if tdb_modules_list is None:
592 tdb_modules_list_as_string = ""
594 tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
596 samdb.transaction_start()
598 message("Setting up sam.ldb partitions and settings")
599 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
600 "SCHEMADN": names.schemadn,
601 "SCHEMADN_LDB": schemadn_ldb,
602 "SCHEMADN_MOD2": ",objectguid",
603 "CONFIGDN": names.configdn,
604 "CONFIGDN_LDB": configdn_ldb,
605 "DOMAINDN": names.domaindn,
606 "DOMAINDN_LDB": domaindn_ldb,
607 "SCHEMADN_MOD": "schema_fsmo,instancetype",
608 "CONFIGDN_MOD": "naming_fsmo,instancetype",
609 "DOMAINDN_MOD": "pdc_fsmo,instancetype",
610 "MODULES_LIST": ",".join(modules_list),
611 "TDB_MODULES_LIST": tdb_modules_list_as_string,
612 "MODULES_LIST2": ",".join(modules_list2),
613 "BACKEND_MOD": ",".join(backend_modules),
616 samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
618 message("Setting up sam.ldb rootDSE")
619 setup_samdb_rootdse(samdb, setup_path, names)
622 samdb.transaction_cancel()
625 samdb.transaction_commit()
629 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain,
630 netbiosname, domainsid, keytab_path, samdb_url,
631 dns_keytab_path, dnspass, machinepass):
632 """Add DC-specific bits to a secrets database.
634 :param secretsdb: Ldb Handle to the secrets database
635 :param setup_path: Setup path function
636 :param machinepass: Machine password
638 setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), {
639 "MACHINEPASS_B64": b64encode(machinepass),
642 "DNSDOMAIN": dnsdomain,
643 "DOMAINSID": str(domainsid),
644 "SECRETS_KEYTAB": keytab_path,
645 "NETBIOSNAME": netbiosname,
646 "SAM_LDB": samdb_url,
647 "DNS_KEYTAB": dns_keytab_path,
648 "DNSPASS_B64": b64encode(dnspass),
652 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
653 """Setup the secrets database.
655 :param path: Path to the secrets database.
656 :param setup_path: Get the path to a setup file.
657 :param session_info: Session info.
658 :param credentials: Credentials
659 :param lp: Loadparm context
660 :return: LDB handle for the created secrets database
662 if os.path.exists(path):
664 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
667 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
668 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
670 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
672 if credentials is not None and credentials.authentication_requested():
673 if credentials.get_bind_dn() is not None:
674 setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
675 "LDAPMANAGERDN": credentials.get_bind_dn(),
676 "LDAPMANAGERPASS_B64": b64encode(credentials.get_password())
679 setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
680 "LDAPADMINUSER": credentials.get_username(),
681 "LDAPADMINREALM": credentials.get_realm(),
682 "LDAPADMINPASS_B64": b64encode(credentials.get_password())
688 def setup_templatesdb(path, setup_path, session_info, lp):
689 """Setup the templates database.
691 :param path: Path to the database.
692 :param setup_path: Function for obtaining the path to setup files.
693 :param session_info: Session info
694 :param credentials: Credentials
695 :param lp: Loadparm context
697 templates_ldb = Ldb(url=path, session_info=session_info,
701 templates_ldb.erase()
702 # This should be 'except LdbError', but on a re-provision the assert in ldb.erase fires, and we need to catch that too
706 templates_ldb.load_ldif_file_add(setup_path("provision_templates_init.ldif"))
708 templates_ldb = Ldb(url=path, session_info=session_info,
711 templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif"))
714 def setup_registry(path, setup_path, session_info, lp):
715 """Setup the registry.
717 :param path: Path to the registry database
718 :param setup_path: Function that returns the path to a setup.
719 :param session_info: Session information
720 :param credentials: Credentials
721 :param lp: Loadparm context
723 reg = registry.Registry()
724 hive = registry.open_ldb(path, session_info=session_info,
726 reg.mount_hive(hive, registry.HKEY_LOCAL_MACHINE)
727 provision_reg = setup_path("provision.reg")
728 assert os.path.exists(provision_reg)
729 reg.diff_apply(provision_reg)
732 def setup_idmapdb(path, setup_path, session_info, lp):
733 """Setup the idmap database.
735 :param path: path to the idmap database
736 :param setup_path: Function that returns a path to a setup file
737 :param session_info: Session information
738 :param credentials: Credentials
739 :param lp: Loadparm context
741 if os.path.exists(path):
744 idmap_ldb = IDmapDB(path, session_info=session_info,
748 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
752 def setup_samdb_rootdse(samdb, setup_path, names):
753 """Setup the SamDB rootdse.
755 :param samdb: Sam Database handle
756 :param setup_path: Obtain setup path
758 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
759 "SCHEMADN": names.schemadn,
760 "NETBIOSNAME": names.netbiosname,
761 "DNSDOMAIN": names.dnsdomain,
762 "REALM": names.realm,
763 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
764 "DOMAINDN": names.domaindn,
765 "ROOTDN": names.rootdn,
766 "CONFIGDN": names.configdn,
767 "SERVERDN": names.serverdn,
771 def setup_self_join(samdb, names,
772 machinepass, dnspass,
773 domainsid, invocationid, setup_path,
774 policyguid, domainControllerFunctionality):
775 """Join a host to its own domain."""
776 assert isinstance(invocationid, str)
777 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
778 "CONFIGDN": names.configdn,
779 "SCHEMADN": names.schemadn,
780 "DOMAINDN": names.domaindn,
781 "SERVERDN": names.serverdn,
782 "INVOCATIONID": invocationid,
783 "NETBIOSNAME": names.netbiosname,
784 "DEFAULTSITE": names.sitename,
785 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
786 "MACHINEPASS_B64": b64encode(machinepass),
787 "DNSPASS_B64": b64encode(dnspass),
788 "REALM": names.realm,
789 "DOMAIN": names.domain,
790 "DNSDOMAIN": names.dnsdomain,
791 "SAMBA_VERSION_STRING": version,
792 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
793 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
794 "POLICYGUID": policyguid,
795 "DNSDOMAIN": names.dnsdomain,
796 "DOMAINSID": str(domainsid),
797 "DOMAINDN": names.domaindn})
800 def setup_samdb(path, setup_path, session_info, credentials, lp,
802 domainsid, domainguid, policyguid,
803 fill, adminpass, krbtgtpass,
804 machinepass, invocationid, dnspass,
805 serverrole, schema=None, ldap_backend=None):
806 """Setup a complete SAM Database.
808 :note: This will wipe the main SAM database file!
811 domainFunctionality = DS_BEHAVIOR_WIN2008
812 forestFunctionality = DS_BEHAVIOR_WIN2008
813 domainControllerFunctionality = DS_BEHAVIOR_WIN2008
815 # Also wipes the database
816 setup_samdb_partitions(path, setup_path, message=message, lp=lp,
817 credentials=credentials, session_info=session_info,
819 ldap_backend=ldap_backend, serverrole=serverrole)
821 # Load the database, but importantly, use Ldb not SamDB as we don't want to load the global schema
822 samdb = Ldb(session_info=session_info,
823 credentials=credentials, lp=lp)
825 message("Pre-loading the Samba 4 and AD schema")
827 # Load the schema from the one we computed earlier
828 samdb.set_schema_from_ldb(schema.ldb)
830 # And now we can connect to the DB - the schema won't be loaded from the DB
835 samdb.transaction_start()
837 message("Erasing data from partitions")
838 # Load the schema (again). This time it will force a reindex, to make the below computationally sane
839 samdb.set_schema_from_ldb(schema.ldb)
840 samdb.erase_partitions()
843 samdb.transaction_cancel()
846 samdb.transaction_commit()
848 samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
849 samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
850 samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
852 samdb.set_domain_sid(str(domainsid))
853 if serverrole == "domain controller":
854 samdb.set_invocation_id(invocationid)
856 samdb.transaction_start()
859 message("Adding DomainDN: %s" % names.domaindn)
860 if serverrole == "domain controller":
861 domain_oc = "domainDNS"
863 domain_oc = "samba4LocalDomain"
865 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
866 "DOMAINDN": names.domaindn,
867 "DOMAIN_OC": domain_oc
870 message("Modifying DomainDN: " + names.domaindn + "")
871 if domainguid is not None:
872 domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
876 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
877 "LDAPTIME": timestring(int(time.time())),
878 "DOMAINSID": str(domainsid),
879 "SCHEMADN": names.schemadn,
880 "NETBIOSNAME": names.netbiosname,
881 "DEFAULTSITE": names.sitename,
882 "CONFIGDN": names.configdn,
883 "SERVERDN": names.serverdn,
884 "POLICYGUID": policyguid,
885 "DOMAINDN": names.domaindn,
886 "DOMAINGUID_MOD": domainguid_mod,
887 "DOMAIN_FUNCTIONALITY": str(domainFunctionality)
890 message("Adding configuration container")
891 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
892 "CONFIGDN": names.configdn,
894 message("Modifying configuration container")
895 setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
896 "CONFIGDN": names.configdn,
897 "SCHEMADN": names.schemadn,
900 message("Setting up sam.ldb schema")
901 samdb.add_ldif(schema.schema_dn_add)
902 samdb.modify_ldif(schema.schema_dn_modify)
903 samdb.add_ldif(schema.schema_data)
904 setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
905 {"SCHEMADN": names.schemadn})
907 message("Setting up sam.ldb configuration data")
908 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
909 "CONFIGDN": names.configdn,
910 "NETBIOSNAME": names.netbiosname,
911 "DEFAULTSITE": names.sitename,
912 "DNSDOMAIN": names.dnsdomain,
913 "DOMAIN": names.domain,
914 "SCHEMADN": names.schemadn,
915 "DOMAINDN": names.domaindn,
916 "SERVERDN": names.serverdn,
917 "FOREST_FUNCTIONALALITY": str(forestFunctionality)
920 message("Setting up display specifiers")
921 setup_add_ldif(samdb, setup_path("display_specifiers.ldif"),
922 {"CONFIGDN": names.configdn})
924 message("Adding users container")
925 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
926 "DOMAINDN": names.domaindn})
927 message("Modifying users container")
928 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
929 "DOMAINDN": names.domaindn})
930 message("Adding computers container")
931 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
932 "DOMAINDN": names.domaindn})
933 message("Modifying computers container")
934 setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
935 "DOMAINDN": names.domaindn})
936 message("Setting up sam.ldb data")
937 setup_add_ldif(samdb, setup_path("provision.ldif"), {
938 "DOMAINDN": names.domaindn,
939 "NETBIOSNAME": names.netbiosname,
940 "DEFAULTSITE": names.sitename,
941 "CONFIGDN": names.configdn,
942 "SERVERDN": names.serverdn
945 if fill == FILL_FULL:
946 message("Setting up sam.ldb users and groups")
947 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
948 "DOMAINDN": names.domaindn,
949 "DOMAINSID": str(domainsid),
950 "CONFIGDN": names.configdn,
951 "ADMINPASS_B64": b64encode(adminpass),
952 "KRBTGTPASS_B64": b64encode(krbtgtpass),
955 if serverrole == "domain controller":
956 message("Setting up self join")
957 setup_self_join(samdb, names=names, invocationid=invocationid,
959 machinepass=machinepass,
960 domainsid=domainsid, policyguid=policyguid,
961 setup_path=setup_path, domainControllerFunctionality=domainControllerFunctionality)
964 samdb.transaction_cancel()
967 samdb.transaction_commit()
972 FILL_NT4SYNC = "NT4SYNC"
976 def provision(setup_dir, message, session_info,
977 credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL, realm=None,
978 rootdn=None, domaindn=None, schemadn=None, configdn=None,
980 domain=None, hostname=None, hostip=None, hostip6=None,
981 domainsid=None, adminpass=None, krbtgtpass=None, domainguid=None,
982 policyguid=None, invocationid=None, machinepass=None,
983 dnspass=None, root=None, nobody=None, users=None,
984 wheel=None, backup=None, aci=None, serverrole=None,
985 ldap_backend_extra_port=None, ldap_backend_type=None, sitename=None,
986 ol_mmr_urls=None, ol_olc=None,
987 setup_ds_path=None, slapd_path=None, nosync=False,
988 ldap_dryrun_mode=False):
991 :note: caution, this wipes all existing data!
994 def setup_path(file):
995 return os.path.join(setup_dir, file)
997 if domainsid is None:
998 domainsid = security.random_sid()
1000 if policyguid is None:
1001 policyguid = str(uuid.uuid4())
1002 if adminpass is None:
1003 adminpass = glue.generate_random_str(12)
1004 if krbtgtpass is None:
1005 krbtgtpass = glue.generate_random_str(12)
1006 if machinepass is None:
1007 machinepass = glue.generate_random_str(12)
1009 dnspass = glue.generate_random_str(12)
1010 root_uid = findnss_uid([root or "root"])
1011 nobody_uid = findnss_uid([nobody or "nobody"])
1012 users_gid = findnss_gid([users or "users"])
1014 wheel_gid = findnss_gid(["wheel", "adm"])
1016 wheel_gid = findnss_gid([wheel])
1018 if targetdir is not None:
1019 if (not os.path.exists(os.path.join(targetdir, "etc"))):
1020 os.makedirs(os.path.join(targetdir, "etc"))
1021 smbconf = os.path.join(targetdir, "etc", "smb.conf")
1022 elif smbconf is None:
1023 smbconf = param.default_path()
1025 # only install a new smb.conf if there isn't one there already
1026 if not os.path.exists(smbconf):
1027 make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
1030 lp = param.LoadParm()
1033 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1034 dnsdomain=realm, serverrole=serverrole, sitename=sitename,
1035 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1038 paths = provision_paths_from_lp(lp, names.dnsdomain)
1042 hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1043 except socket.gaierror, (socket.EAI_NODATA, msg):
1048 hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1049 except socket.gaierror, (socket.EAI_NODATA, msg):
1052 if serverrole is None:
1053 serverrole = lp.get("server role")
1055 assert serverrole in ("domain controller", "member server", "standalone")
1056 if invocationid is None and serverrole == "domain controller":
1057 invocationid = str(uuid.uuid4())
1059 if not os.path.exists(paths.private_dir):
1060 os.mkdir(paths.private_dir)
1062 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1064 schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn)
1066 provision_backend = None
1067 if ldap_backend_type:
1068 # We only support an LDAP backend over ldapi://
1070 provision_backend = ProvisionBackend(paths=paths, setup_path=setup_path, lp=lp, credentials=credentials,
1072 message=message, hostname=hostname,
1073 root=root, schema=schema, ldap_backend_type=ldap_backend_type,
1074 ldap_backend_extra_port=ldap_backend_extra_port,
1075 ol_mmr_urls=ol_mmr_urls,
1076 slapd_path=slapd_path,
1077 setup_ds_path=setup_ds_path,
1078 ldap_dryrun_mode=ldap_dryrun_mode)
1080 # Now use the backend credentials to access the databases
1081 credentials = provision_backend.credentials
1083 # only install a new shares config db if there is none
1084 if not os.path.exists(paths.shareconf):
1085 message("Setting up share.ldb")
1086 share_ldb = Ldb(paths.shareconf, session_info=session_info,
1087 credentials=credentials, lp=lp)
1088 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1091 message("Setting up secrets.ldb")
1092 secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
1093 session_info=session_info,
1094 credentials=credentials, lp=lp)
1096 message("Setting up the registry")
1097 setup_registry(paths.hklm, setup_path, session_info,
1100 message("Setting up templates db")
1101 setup_templatesdb(paths.templates, setup_path, session_info=session_info,
1104 message("Setting up idmap db")
1105 idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1108 message("Setting up SAM db")
1109 samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info,
1110 credentials=credentials, lp=lp, names=names,
1112 domainsid=domainsid,
1113 schema=schema, domainguid=domainguid, policyguid=policyguid,
1115 adminpass=adminpass, krbtgtpass=krbtgtpass,
1116 invocationid=invocationid,
1117 machinepass=machinepass, dnspass=dnspass,
1118 serverrole=serverrole, ldap_backend=provision_backend)
1120 if serverrole == "domain controller":
1121 if paths.netlogon is None:
1122 message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1123 message("Please either remove %s or see the template at %s" %
1124 ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1125 assert(paths.netlogon is not None)
1127 if paths.sysvol is None:
1128 message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1129 message("Please either remove %s or see the template at %s" %
1130 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1131 assert(paths.sysvol is not None)
1133 policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1134 "{" + policyguid + "}")
1135 os.makedirs(policy_path, 0755)
1136 open(os.path.join(policy_path, "GPT.INI"), 'w').write("")
1137 os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1138 os.makedirs(os.path.join(policy_path, "User"), 0755)
1139 if not os.path.isdir(paths.netlogon):
1140 os.makedirs(paths.netlogon, 0755)
1142 if samdb_fill == FILL_FULL:
1143 setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1144 root_uid=root_uid, nobody_uid=nobody_uid,
1145 users_gid=users_gid, wheel_gid=wheel_gid)
1147 message("Setting up sam.ldb rootDSE marking as synchronized")
1148 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1150 # Only make a zone file on the first DC, it should be replicated with DNS replication
1151 if serverrole == "domain controller":
1152 secrets_ldb = Ldb(paths.secrets, session_info=session_info,
1153 credentials=credentials, lp=lp)
1154 secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1155 netbiosname=names.netbiosname, domainsid=domainsid,
1156 keytab_path=paths.keytab, samdb_url=paths.samdb,
1157 dns_keytab_path=paths.dns_keytab, dnspass=dnspass,
1158 machinepass=machinepass, dnsdomain=names.dnsdomain)
1160 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1161 assert isinstance(domainguid, str)
1162 hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1163 expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1164 scope=SCOPE_SUBTREE)
1165 assert isinstance(hostguid, str)
1167 create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1168 domaindn=names.domaindn, hostip=hostip,
1169 hostip6=hostip6, hostname=names.hostname,
1170 dnspass=dnspass, realm=names.realm,
1171 domainguid=domainguid, hostguid=hostguid)
1173 create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1174 dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1176 create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1177 dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1178 keytab_name=paths.dns_keytab)
1179 message("See %s for an example configuration include file for BIND" % paths.namedconf)
1180 message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1182 create_krb5_conf(paths.krb5conf, setup_path, dnsdomain=names.dnsdomain,
1183 hostname=names.hostname, realm=names.realm)
1184 message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1187 # if backend is openldap, terminate slapd after final provision and check its proper termination
1188 if provision_backend is not None and provision_backend.slapd is not None:
1189 if provision_backend.slapd.poll() is None:
1191 if hasattr(provision_backend.slapd, "terminate"):
1192 provision_backend.slapd.terminate()
1195 os.kill(provision_backend.slapd.pid, signal.SIGTERM)
1197 #and now wait for it to die
1198 provision_backend.slapd.communicate()
1200 # now display slapd_command_file.txt to show how slapd must be started next time
1201 message("Use later the following commandline to start slapd, then Samba:")
1202 slapd_command = "\'" + "\' \'".join(provision_backend.slapd_command) + "\'"
1203 message(slapd_command)
1204 message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
1206 setup_file(setup_path("ldap_backend_startup.sh"), paths.ldapdir + "/ldap_backend_startup.sh", {
1207 "SLAPD_COMMAND" : slapd_command})
1209 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1212 message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1214 message("Once the above files are installed, your Samba4 server will be ready to use")
1215 message("Server Role: %s" % serverrole)
1216 message("Hostname: %s" % names.hostname)
1217 message("NetBIOS Domain: %s" % names.domain)
1218 message("DNS Domain: %s" % names.dnsdomain)
1219 message("DOMAIN SID: %s" % str(domainsid))
1220 if samdb_fill == FILL_FULL:
1221 message("Admin password: %s" % adminpass)
1223 result = ProvisionResult()
1224 result.domaindn = domaindn
1225 result.paths = paths
1227 result.samdb = samdb
1232 def provision_become_dc(setup_dir=None,
1233 smbconf=None, targetdir=None, realm=None,
1234 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1236 domain=None, hostname=None, domainsid=None,
1237 adminpass=None, krbtgtpass=None, domainguid=None,
1238 policyguid=None, invocationid=None, machinepass=None,
1239 dnspass=None, root=None, nobody=None, users=None,
1240 wheel=None, backup=None, serverrole=None,
1241 ldap_backend=None, ldap_backend_type=None, sitename=None):
1244 """print a message if quiet is not set."""
1247 return provision(setup_dir, message, system_session(), None,
1248 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm,
1249 rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn, serverdn=serverdn,
1250 domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename)
1253 def setup_db_config(setup_path, dbdir):
1254 """Setup a Berkeley database.
1256 :param setup_path: Setup path function.
1257 :param dbdir: Database directory."""
1258 if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1259 os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1260 if not os.path.isdir(os.path.join(dbdir, "tmp")):
1261 os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1263 setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1264 {"LDAPDBDIR": dbdir})
1266 class ProvisionBackend(object):
1267 def __init__(self, paths=None, setup_path=None, lp=None, credentials=None,
1268 names=None, message=None,
1269 hostname=None, root=None,
1271 ldap_backend_type=None, ldap_backend_extra_port=None,
1273 setup_ds_path=None, slapd_path=None,
1274 nosync=False, ldap_dryrun_mode=False):
1275 """Provision an LDAP backend for samba4
1277 This works for OpenLDAP and Fedora DS
1280 self.ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.ldapdir, "ldapi"), safe="")
1282 if not os.path.isdir(paths.ldapdir):
1283 os.makedirs(paths.ldapdir, 0700)
1285 if ldap_backend_type == "existing":
1286 #Check to see that this 'existing' LDAP backend in fact exists
1287 ldapi_db = Ldb(self.ldapi_uri, credentials=credentials)
1288 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1289 expression="(objectClass=OpenLDAProotDSE)")
1291 # If we have got here, then we must have a valid connection to the LDAP server, with valid credentials supplied
1292 # This caused them to be set into the long-term database later in the script.
1293 self.credentials = credentials
1294 self.ldap_backend_type = "openldap" #For now, assume existing backends at least emulate OpenLDAP
1297 # we will shortly start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via self.ldapi_uri
1298 # if another instance of slapd is already running
1300 ldapi_db = Ldb(self.ldapi_uri)
1301 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1302 expression="(objectClass=OpenLDAProotDSE)");
1304 f = open(paths.slapdpid, "r")
1307 message("Check for slapd Process with PID: " + str(p) + " and terminate it manually.")
1311 raise("Warning: Another slapd Instance seems already running on this host, listening to " + ldapi_uri + ". Please shut it down before you continue. ")
1316 # Try to print helpful messages when the user has not specified the path to slapd
1317 if slapd_path is None:
1318 raise("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
1319 if not os.path.exists(slapd_path):
1320 message (slapd_path)
1321 raise("Warning: Given Path to slapd does not exist!")
1323 schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1325 os.unlink(schemadb_path)
1330 # Put the LDIF of the schema into a database so we can search on
1331 # it to generate schema-dependent configurations in Fedora DS and
1333 os.path.join(paths.ldapdir, "schema-tmp.ldb")
1334 schema.ldb.connect(schemadb_path)
1335 schema.ldb.transaction_start()
1337 # These bits of LDIF are supplied when the Schema object is created
1338 schema.ldb.add_ldif(schema.schema_dn_add)
1339 schema.ldb.modify_ldif(schema.schema_dn_modify)
1340 schema.ldb.add_ldif(schema.schema_data)
1341 schema.ldb.transaction_commit()
1343 self.credentials = Credentials()
1344 self.credentials.guess(lp)
1345 self.ldap_backend_type = ldap_backend_type
1347 #Make a new, random password between Samba and it's LDAP server
1348 ldapadminpass=glue.generate_random_str(12)
1350 if ldap_backend_type == "fedora-ds":
1351 provision_fds_backend(self, paths=paths, setup_path=setup_path, names=names, message=message,
1352 hostname=hostname, ldapadminpass=ldapadminpass, root=root,
1353 schema=schema, ldap_backend_extra_port=ldap_backend_extra_port,
1354 setup_ds_path=setup_ds_path, slapd_path=slapd_path,
1355 nosync=nosync, ldap_dryrun_mode=ldap_dryrun_mode)
1357 elif ldap_backend_type == "openldap":
1358 provision_openldap_backend(self, paths=paths, setup_path=setup_path, names=names, message=message,
1359 hostname=hostname, ldapadminpass=ldapadminpass, root=root,
1360 schema=schema, ldap_backend_extra_port=ldap_backend_extra_port,
1361 ol_mmr_urls=ol_mmr_urls,
1362 slapd_path=slapd_path,
1363 nosync=nosync, ldap_dryrun_mode=ldap_dryrun_mode)
1365 raise("Unknown LDAP backend type selected")
1367 self.credentials.set_password(ldapadminpass)
1369 # Now start the slapd, so we can provision onto it. We keep the
1370 # subprocess context around, to kill this off at the successful
1372 self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False)
1374 while self.slapd.poll() is None:
1375 # Wait until the socket appears
1377 ldapi_db = Ldb(self.ldapi_uri, lp=lp, credentials=self.credentials)
1378 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1379 expression="(objectClass=OpenLDAProotDSE)")
1380 # If we have got here, then we must have a valid connection to the LDAP server!
1386 raise "slapd died before we could make a connection to it"
1389 def provision_openldap_backend(result, paths=None, setup_path=None, names=None, message=None,
1390 hostname=None, ldapadminpass=None, root=None,
1392 ldap_backend_extra_port=None,
1394 slapd_path=None, nosync=False,
1395 ldap_dryrun_mode=False):
1397 #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
1400 nosync_config = "dbnosync"
1403 attrs = ["linkID", "lDAPDisplayName"]
1404 res = schema.ldb.search(expression="(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1406 memberof_config = "# Generated from Samba4 schema\n"
1407 refint_attributes = ""
1408 for i in range (0, len(res)):
1409 expression = "(&(objectclass=attributeSchema)(linkID=%d)(attributeSyntax=2.5.5.1))" % (int(res[i]["linkID"][0])+1)
1410 target = schema.ldb.searchone(basedn=names.schemadn,
1411 expression=expression,
1412 attribute="lDAPDisplayName",
1413 scope=SCOPE_SUBTREE)
1414 if target is not None:
1415 refint_attributes = refint_attributes + " " + res[i]["lDAPDisplayName"][0]
1417 memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1418 { "MEMBER_ATTR" : str(res[i]["lDAPDisplayName"][0]),
1419 "MEMBEROF_ATTR" : str(target) })
1421 refint_config = read_and_sub_file(setup_path("refint.conf"),
1422 { "LINK_ATTRS" : refint_attributes})
1424 res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1426 for i in range (0, len(res)):
1427 index_attr = res[i]["lDAPDisplayName"][0]
1428 if index_attr == "objectGUID":
1429 index_attr = "entryUUID"
1431 index_config += "index " + index_attr + " eq\n"
1433 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1435 mmr_replicator_acl = ""
1436 mmr_serverids_config = ""
1437 mmr_syncrepl_schema_config = ""
1438 mmr_syncrepl_config_config = ""
1439 mmr_syncrepl_user_config = ""
1442 if ol_mmr_urls is not None:
1443 # For now, make these equal
1444 mmr_pass = ldapadminpass
1446 url_list=filter(None,ol_mmr_urls.split(' '))
1447 if (len(url_list) == 1):
1448 url_list=filter(None,ol_mmr_urls.split(','))
1451 mmr_on_config = "MirrorMode On"
1452 mmr_replicator_acl = " by dn=cn=replicator,cn=samba read"
1454 for url in url_list:
1456 mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1457 { "SERVERID" : str(serverid),
1458 "LDAPSERVER" : url })
1461 mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1463 "MMRDN": names.schemadn,
1465 "MMR_PASSWORD": mmr_pass})
1468 mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1470 "MMRDN": names.configdn,
1472 "MMR_PASSWORD": mmr_pass})
1475 mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1477 "MMRDN": names.domaindn,
1479 "MMR_PASSWORD": mmr_pass })
1480 # OpenLDAP cn=config initialisation
1481 olc_syncrepl_config = ""
1483 # if mmr = yes, generate cn=config-replication directives
1484 # and olc_seed.lif for the other mmr-servers
1485 if ol_mmr_urls is not None:
1487 olc_serverids_config = ""
1488 olc_syncrepl_seed_config = ""
1489 olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
1491 for url in url_list:
1493 olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
1494 { "SERVERID" : str(serverid),
1495 "LDAPSERVER" : url })
1498 olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
1501 "MMR_PASSWORD": mmr_pass})
1503 olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1505 "LDAPSERVER" : url})
1507 setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif,
1508 {"OLC_SERVER_ID_CONF": olc_serverids_config,
1509 "OLC_PW": ldapadminpass,
1510 "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
1513 setup_file(setup_path("slapd.conf"), paths.slapdconf,
1514 {"DNSDOMAIN": names.dnsdomain,
1515 "LDAPDIR": paths.ldapdir,
1516 "DOMAINDN": names.domaindn,
1517 "CONFIGDN": names.configdn,
1518 "SCHEMADN": names.schemadn,
1519 "MEMBEROF_CONFIG": memberof_config,
1520 "MIRRORMODE": mmr_on_config,
1521 "REPLICATOR_ACL": mmr_replicator_acl,
1522 "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1523 "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1524 "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1525 "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1526 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
1527 "OLC_MMR_CONFIG": olc_mmr_config,
1528 "REFINT_CONFIG": refint_config,
1529 "INDEX_CONFIG": index_config,
1530 "NOSYNC": nosync_config})
1532 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1533 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1534 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1536 if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba", "cn=samba")):
1537 os.makedirs(os.path.join(paths.ldapdir, "db", "samba", "cn=samba"), 0700)
1539 setup_file(setup_path("cn=samba.ldif"),
1540 os.path.join(paths.ldapdir, "db", "samba", "cn=samba.ldif"),
1541 { "UUID": str(uuid.uuid4()),
1542 "LDAPTIME": timestring(int(time.time()))} )
1543 setup_file(setup_path("cn=samba-admin.ldif"),
1544 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=samba-admin.ldif"),
1545 {"LDAPADMINPASS_B64": b64encode(ldapadminpass),
1546 "UUID": str(uuid.uuid4()),
1547 "LDAPTIME": timestring(int(time.time()))} )
1549 if ol_mmr_urls is not None:
1550 setup_file(setup_path("cn=replicator.ldif"),
1551 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=replicator.ldif"),
1552 {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1553 "UUID": str(uuid.uuid4()),
1554 "LDAPTIME": timestring(int(time.time()))} )
1557 mapping = "schema-map-openldap-2.3"
1558 backend_schema = "backend-schema.schema"
1560 backend_schema_data = schema.ldb.convert_schema_to_openldap("openldap", open(setup_path(mapping), 'r').read())
1561 assert backend_schema_data is not None
1562 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1564 # now we generate the needed strings to start slapd automatically,
1565 # first ldapi_uri...
1566 if ldap_backend_extra_port is not None:
1567 # When we use MMR, we can't use 0.0.0.0 as it uses the name
1568 # specified there as part of it's clue as to it's own name,
1569 # and not to replicate to itself
1570 if ol_mmr_urls is None:
1571 server_port_string = "ldap://0.0.0.0:%d" % ldap_backend_extra_port
1573 server_port_string = "ldap://" + names.hostname + "." + names.dnsdomain +":%d" % ldap_backend_extra_port
1575 server_port_string = ""
1577 # Prepare the 'result' information - the commands to return in particular
1578 result.slapd_provision_command = [slapd_path]
1580 result.slapd_provision_command.append("-F" + paths.olcdir)
1582 result.slapd_provision_command.append("-h")
1584 # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands
1585 result.slapd_command = list(result.slapd_provision_command)
1587 result.slapd_provision_command.append(result.ldapi_uri)
1588 result.slapd_provision_command.append("-d0")
1590 uris = result.ldapi_uri
1591 if server_port_string is not "":
1592 uris = uris + " " + server_port_string
1594 result.slapd_command.append(uris)
1596 # Set the username - done here because Fedora DS still uses the admin DN and simple bind
1597 result.credentials.set_username("samba-admin")
1599 # If we were just looking for crashes up to this point, it's a
1600 # good time to exit before we realise we don't have OpenLDAP on
1602 if ldap_dryrun_mode:
1605 # Finally, convert the configuration into cn=config style!
1606 if not os.path.isdir(paths.olcdir):
1607 os.makedirs(paths.olcdir, 0770)
1609 retcode = subprocess.call([slapd_path, "-Ttest", "-f", paths.slapdconf, "-F", paths.olcdir], close_fds=True, shell=False)
1611 # We can't do this, as OpenLDAP is strange. It gives an error
1612 # output to the above, but does the conversion sucessfully...
1615 # raise("conversion from slapd.conf to cn=config failed")
1617 if not os.path.exists(os.path.join(paths.olcdir, "cn=config.ldif")):
1618 raise("conversion from slapd.conf to cn=config failed")
1620 # Don't confuse the admin by leaving the slapd.conf around
1621 os.remove(paths.slapdconf)
1624 def provision_fds_backend(result, paths=None, setup_path=None, names=None, message=None,
1625 hostname=None, ldapadminpass=None, root=None,
1627 ldap_backend_extra_port=None,
1631 ldap_dryrun_mode=False):
1633 if ldap_backend_extra_port is not None:
1634 serverport = "ServerPort=%d" % ldap_backend_extra_port
1638 setup_file(setup_path("fedorads.inf"), paths.fedoradsinf,
1640 "HOSTNAME": hostname,
1641 "DNSDOMAIN": names.dnsdomain,
1642 "LDAPDIR": paths.ldapdir,
1643 "DOMAINDN": names.domaindn,
1644 "LDAPMANAGERDN": names.ldapmanagerdn,
1645 "LDAPMANAGERPASS": ldapadminpass,
1646 "SERVERPORT": serverport})
1648 setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions,
1649 {"CONFIGDN": names.configdn,
1650 "SCHEMADN": names.schemadn,
1653 mapping = "schema-map-fedora-ds-1.0"
1654 backend_schema = "99_ad.ldif"
1656 # Build a schema file in Fedora DS format
1657 backend_schema_data = schema.ldb.convert_schema_to_openldap("fedora-ds", 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 result.credentials.set_bind_dn(names.ldapmanagerdn)
1663 # Destory the target directory, or else setup-ds.pl will complain
1664 fedora_ds_dir = os.path.join(paths.ldapdir, "slapd-samba4")
1665 shutil.rmtree(fedora_ds_dir, True)
1667 result.slapd_provision_command = [slapd_path, "-D", fedora_ds_dir, "-i", paths.slapdpid];
1668 #In the 'provision' command line, stay in the foreground so we can easily kill it
1669 result.slapd_provision_command.append("-d0")
1671 #the command for the final run is the normal script
1672 result.slapd_command = [os.path.join(paths.ldapdir, "slapd-samba4", "start-slapd")]
1674 # If we were just looking for crashes up to this point, it's a
1675 # good time to exit before we realise we don't have Fedora DS on
1676 if ldap_dryrun_mode:
1679 # Try to print helpful messages when the user has not specified the path to the setup-ds tool
1680 if setup_ds_path is None:
1681 raise("Warning: Fedora DS LDAP-Backend must be setup with path to setup-ds, e.g. --setup-ds-path=\"/usr/sbin/setup-ds.pl\"!")
1682 if not os.path.exists(setup_ds_path):
1683 message (setup_ds_path)
1684 raise("Warning: Given Path to slapd does not exist!")
1686 # Run the Fedora DS setup utility
1687 retcode = subprocess.call([setup_ds_path, "--silent", "--file", paths.fedoradsinf], close_fds=True, shell=False)
1689 raise("setup-ds failed")
1691 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1692 """Create a PHP LDAP admin configuration file.
1694 :param path: Path to write the configuration to.
1695 :param setup_path: Function to generate setup paths.
1697 setup_file(setup_path("phpldapadmin-config.php"), path,
1698 {"S4_LDAPI_URI": ldapi_uri})
1701 def create_zone_file(path, setup_path, dnsdomain, domaindn,
1702 hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid):
1703 """Write out a DNS zone file, from the info in the current database.
1705 :param path: Path of the new zone file.
1706 :param setup_path: Setup path function.
1707 :param dnsdomain: DNS Domain name
1708 :param domaindn: DN of the Domain
1709 :param hostip: Local IPv4 IP
1710 :param hostip6: Local IPv6 IP
1711 :param hostname: Local hostname
1712 :param dnspass: Password for DNS
1713 :param realm: Realm name
1714 :param domainguid: GUID of the domain.
1715 :param hostguid: GUID of the host.
1717 assert isinstance(domainguid, str)
1719 if hostip6 is not None:
1720 hostip6_base_line = " IN AAAA " + hostip6
1721 hostip6_host_line = hostname + " IN AAAA " + hostip6
1723 hostip6_base_line = ""
1724 hostip6_host_line = ""
1726 if hostip is not None:
1727 hostip_base_line = " IN A " + hostip
1728 hostip_host_line = hostname + " IN A " + hostip
1730 hostip_base_line = ""
1731 hostip_host_line = ""
1733 setup_file(setup_path("provision.zone"), path, {
1734 "DNSPASS_B64": b64encode(dnspass),
1735 "HOSTNAME": hostname,
1736 "DNSDOMAIN": dnsdomain,
1738 "HOSTIP_BASE_LINE": hostip_base_line,
1739 "HOSTIP_HOST_LINE": hostip_host_line,
1740 "DOMAINGUID": domainguid,
1741 "DATESTRING": time.strftime("%Y%m%d%H"),
1742 "DEFAULTSITE": DEFAULTSITE,
1743 "HOSTGUID": hostguid,
1744 "HOSTIP6_BASE_LINE": hostip6_base_line,
1745 "HOSTIP6_HOST_LINE": hostip6_host_line,
1749 def create_named_conf(path, setup_path, realm, dnsdomain,
1751 """Write out a file containing zone statements suitable for inclusion in a
1752 named.conf file (including GSS-TSIG configuration).
1754 :param path: Path of the new named.conf file.
1755 :param setup_path: Setup path function.
1756 :param realm: Realm name
1757 :param dnsdomain: DNS Domain name
1758 :param private_dir: Path to private directory
1759 :param keytab_name: File name of DNS keytab file
1762 setup_file(setup_path("named.conf"), path, {
1763 "DNSDOMAIN": dnsdomain,
1765 "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1766 "PRIVATE_DIR": private_dir
1769 def create_named_txt(path, setup_path, realm, dnsdomain,
1770 private_dir, keytab_name):
1771 """Write out a file containing zone statements suitable for inclusion in a
1772 named.conf file (including GSS-TSIG configuration).
1774 :param path: Path of the new named.conf file.
1775 :param setup_path: Setup path function.
1776 :param realm: Realm name
1777 :param dnsdomain: DNS Domain name
1778 :param private_dir: Path to private directory
1779 :param keytab_name: File name of DNS keytab file
1782 setup_file(setup_path("named.txt"), path, {
1783 "DNSDOMAIN": dnsdomain,
1785 "DNS_KEYTAB": keytab_name,
1786 "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1787 "PRIVATE_DIR": private_dir
1790 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1791 """Write out a file containing zone statements suitable for inclusion in a
1792 named.conf file (including GSS-TSIG configuration).
1794 :param path: Path of the new named.conf file.
1795 :param setup_path: Setup path function.
1796 :param dnsdomain: DNS Domain name
1797 :param hostname: Local hostname
1798 :param realm: Realm name
1801 setup_file(setup_path("krb5.conf"), path, {
1802 "DNSDOMAIN": dnsdomain,
1803 "HOSTNAME": hostname,