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)
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:"])
528 samdb.erase_except_schema_controlled()
530 os.unlink(samdb_path)
531 samdb = Ldb(url=samdb_path, session_info=session_info,
532 credentials=credentials, lp=lp, options=["modules:"])
534 samdb.erase_except_schema_controlled()
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()
842 samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
843 samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
844 samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
846 samdb.set_domain_sid(str(domainsid))
847 if serverrole == "domain controller":
848 samdb.set_invocation_id(invocationid)
850 message("Adding DomainDN: %s" % names.domaindn)
851 if serverrole == "domain controller":
852 domain_oc = "domainDNS"
854 domain_oc = "samba4LocalDomain"
856 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
857 "DOMAINDN": names.domaindn,
858 "DOMAIN_OC": domain_oc
861 message("Modifying DomainDN: " + names.domaindn + "")
862 if domainguid is not None:
863 domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
867 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
868 "LDAPTIME": timestring(int(time.time())),
869 "DOMAINSID": str(domainsid),
870 "SCHEMADN": names.schemadn,
871 "NETBIOSNAME": names.netbiosname,
872 "DEFAULTSITE": names.sitename,
873 "CONFIGDN": names.configdn,
874 "SERVERDN": names.serverdn,
875 "POLICYGUID": policyguid,
876 "DOMAINDN": names.domaindn,
877 "DOMAINGUID_MOD": domainguid_mod,
878 "DOMAIN_FUNCTIONALITY": str(domainFunctionality)
881 message("Adding configuration container")
882 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
883 "CONFIGDN": names.configdn,
885 message("Modifying configuration container")
886 setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
887 "CONFIGDN": names.configdn,
888 "SCHEMADN": names.schemadn,
891 message("Setting up sam.ldb schema")
892 samdb.add_ldif(schema.schema_dn_add)
893 samdb.modify_ldif(schema.schema_dn_modify)
894 samdb.add_ldif(schema.schema_data)
895 setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
896 {"SCHEMADN": names.schemadn})
898 message("Setting up sam.ldb configuration data")
899 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
900 "CONFIGDN": names.configdn,
901 "NETBIOSNAME": names.netbiosname,
902 "DEFAULTSITE": names.sitename,
903 "DNSDOMAIN": names.dnsdomain,
904 "DOMAIN": names.domain,
905 "SCHEMADN": names.schemadn,
906 "DOMAINDN": names.domaindn,
907 "SERVERDN": names.serverdn,
908 "FOREST_FUNCTIONALALITY": str(forestFunctionality)
911 message("Setting up display specifiers")
912 setup_add_ldif(samdb, setup_path("display_specifiers.ldif"),
913 {"CONFIGDN": names.configdn})
915 message("Adding users container")
916 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
917 "DOMAINDN": names.domaindn})
918 message("Modifying users container")
919 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
920 "DOMAINDN": names.domaindn})
921 message("Adding computers container")
922 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
923 "DOMAINDN": names.domaindn})
924 message("Modifying computers container")
925 setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
926 "DOMAINDN": names.domaindn})
927 message("Setting up sam.ldb data")
928 setup_add_ldif(samdb, setup_path("provision.ldif"), {
929 "DOMAINDN": names.domaindn,
930 "NETBIOSNAME": names.netbiosname,
931 "DEFAULTSITE": names.sitename,
932 "CONFIGDN": names.configdn,
933 "SERVERDN": names.serverdn
936 if fill == FILL_FULL:
937 message("Setting up sam.ldb users and groups")
938 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
939 "DOMAINDN": names.domaindn,
940 "DOMAINSID": str(domainsid),
941 "CONFIGDN": names.configdn,
942 "ADMINPASS_B64": b64encode(adminpass),
943 "KRBTGTPASS_B64": b64encode(krbtgtpass),
946 if serverrole == "domain controller":
947 message("Setting up self join")
948 setup_self_join(samdb, names=names, invocationid=invocationid,
950 machinepass=machinepass,
951 domainsid=domainsid, policyguid=policyguid,
952 setup_path=setup_path, domainControllerFunctionality=domainControllerFunctionality)
955 samdb.transaction_cancel()
958 samdb.transaction_commit()
963 FILL_NT4SYNC = "NT4SYNC"
967 def provision(setup_dir, message, session_info,
968 credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL, realm=None,
969 rootdn=None, domaindn=None, schemadn=None, configdn=None,
971 domain=None, hostname=None, hostip=None, hostip6=None,
972 domainsid=None, adminpass=None, ldapadminpass=None,
973 krbtgtpass=None, domainguid=None,
974 policyguid=None, invocationid=None, machinepass=None,
975 dnspass=None, root=None, nobody=None, users=None,
976 wheel=None, backup=None, aci=None, serverrole=None,
977 ldap_backend_extra_port=None, ldap_backend_type=None, sitename=None,
978 ol_mmr_urls=None, ol_olc=None,
979 setup_ds_path=None, slapd_path=None, nosync=False,
980 ldap_dryrun_mode=False):
983 :note: caution, this wipes all existing data!
986 def setup_path(file):
987 return os.path.join(setup_dir, file)
989 if domainsid is None:
990 domainsid = security.random_sid()
992 if policyguid is None:
993 policyguid = str(uuid.uuid4())
994 if adminpass is None:
995 adminpass = glue.generate_random_str(12)
996 if krbtgtpass is None:
997 krbtgtpass = glue.generate_random_str(12)
998 if machinepass is None:
999 machinepass = glue.generate_random_str(12)
1001 dnspass = glue.generate_random_str(12)
1002 if ldapadminpass is None:
1003 #Make a new, random password between Samba and it's LDAP server
1004 ldapadminpass=glue.generate_random_str(12)
1007 root_uid = findnss_uid([root or "root"])
1008 nobody_uid = findnss_uid([nobody or "nobody"])
1009 users_gid = findnss_gid([users or "users"])
1011 wheel_gid = findnss_gid(["wheel", "adm"])
1013 wheel_gid = findnss_gid([wheel])
1015 if targetdir is not None:
1016 if (not os.path.exists(os.path.join(targetdir, "etc"))):
1017 os.makedirs(os.path.join(targetdir, "etc"))
1018 smbconf = os.path.join(targetdir, "etc", "smb.conf")
1019 elif smbconf is None:
1020 smbconf = param.default_path()
1022 # only install a new smb.conf if there isn't one there already
1023 if not os.path.exists(smbconf):
1024 make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
1027 lp = param.LoadParm()
1030 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1031 dnsdomain=realm, serverrole=serverrole, sitename=sitename,
1032 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1035 paths = provision_paths_from_lp(lp, names.dnsdomain)
1039 hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1040 except socket.gaierror, (socket.EAI_NODATA, msg):
1045 hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1046 except socket.gaierror, (socket.EAI_NODATA, msg):
1049 if serverrole is None:
1050 serverrole = lp.get("server role")
1052 assert serverrole in ("domain controller", "member server", "standalone")
1053 if invocationid is None and serverrole == "domain controller":
1054 invocationid = str(uuid.uuid4())
1056 if not os.path.exists(paths.private_dir):
1057 os.mkdir(paths.private_dir)
1059 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1061 schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn)
1063 provision_backend = None
1064 if ldap_backend_type:
1065 # We only support an LDAP backend over ldapi://
1067 provision_backend = ProvisionBackend(paths=paths, setup_path=setup_path, lp=lp, credentials=credentials,
1069 message=message, hostname=hostname,
1070 root=root, schema=schema, ldap_backend_type=ldap_backend_type,
1071 ldapadminpass=ldapadminpass,
1072 ldap_backend_extra_port=ldap_backend_extra_port,
1073 ol_mmr_urls=ol_mmr_urls,
1074 slapd_path=slapd_path,
1075 setup_ds_path=setup_ds_path,
1076 ldap_dryrun_mode=ldap_dryrun_mode)
1078 # Now use the backend credentials to access the databases
1079 credentials = provision_backend.credentials
1081 # only install a new shares config db if there is none
1082 if not os.path.exists(paths.shareconf):
1083 message("Setting up share.ldb")
1084 share_ldb = Ldb(paths.shareconf, session_info=session_info,
1085 credentials=credentials, lp=lp)
1086 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1089 message("Setting up secrets.ldb")
1090 secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
1091 session_info=session_info,
1092 credentials=credentials, lp=lp)
1094 message("Setting up the registry")
1095 setup_registry(paths.hklm, setup_path, session_info,
1098 message("Setting up templates db")
1099 setup_templatesdb(paths.templates, setup_path, session_info=session_info,
1102 message("Setting up idmap db")
1103 idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1106 message("Setting up SAM db")
1107 samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info,
1108 credentials=credentials, lp=lp, names=names,
1110 domainsid=domainsid,
1111 schema=schema, domainguid=domainguid, policyguid=policyguid,
1113 adminpass=adminpass, krbtgtpass=krbtgtpass,
1114 invocationid=invocationid,
1115 machinepass=machinepass, dnspass=dnspass,
1116 serverrole=serverrole, ldap_backend=provision_backend)
1118 if serverrole == "domain controller":
1119 if paths.netlogon is None:
1120 message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1121 message("Please either remove %s or see the template at %s" %
1122 ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1123 assert(paths.netlogon is not None)
1125 if paths.sysvol is None:
1126 message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1127 message("Please either remove %s or see the template at %s" %
1128 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1129 assert(paths.sysvol is not None)
1131 policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1132 "{" + policyguid + "}")
1133 os.makedirs(policy_path, 0755)
1134 open(os.path.join(policy_path, "GPT.INI"), 'w').write("")
1135 os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1136 os.makedirs(os.path.join(policy_path, "User"), 0755)
1137 if not os.path.isdir(paths.netlogon):
1138 os.makedirs(paths.netlogon, 0755)
1140 if samdb_fill == FILL_FULL:
1141 setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1142 root_uid=root_uid, nobody_uid=nobody_uid,
1143 users_gid=users_gid, wheel_gid=wheel_gid)
1145 message("Setting up sam.ldb rootDSE marking as synchronized")
1146 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1148 # Only make a zone file on the first DC, it should be replicated with DNS replication
1149 if serverrole == "domain controller":
1150 secrets_ldb = Ldb(paths.secrets, session_info=session_info,
1151 credentials=credentials, lp=lp)
1152 secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1153 netbiosname=names.netbiosname, domainsid=domainsid,
1154 keytab_path=paths.keytab, samdb_url=paths.samdb,
1155 dns_keytab_path=paths.dns_keytab, dnspass=dnspass,
1156 machinepass=machinepass, dnsdomain=names.dnsdomain)
1158 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1159 assert isinstance(domainguid, str)
1160 hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1161 expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1162 scope=SCOPE_SUBTREE)
1163 assert isinstance(hostguid, str)
1165 create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1166 domaindn=names.domaindn, hostip=hostip,
1167 hostip6=hostip6, hostname=names.hostname,
1168 dnspass=dnspass, realm=names.realm,
1169 domainguid=domainguid, hostguid=hostguid)
1171 create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1172 dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1174 create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1175 dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1176 keytab_name=paths.dns_keytab)
1177 message("See %s for an example configuration include file for BIND" % paths.namedconf)
1178 message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1180 create_krb5_conf(paths.krb5conf, setup_path, dnsdomain=names.dnsdomain,
1181 hostname=names.hostname, realm=names.realm)
1182 message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1185 # if backend is openldap, terminate slapd after final provision and check its proper termination
1186 if provision_backend is not None and provision_backend.slapd is not None:
1187 if provision_backend.slapd.poll() is None:
1189 if hasattr(provision_backend.slapd, "terminate"):
1190 provision_backend.slapd.terminate()
1193 os.kill(provision_backend.slapd.pid, signal.SIGTERM)
1195 #and now wait for it to die
1196 provision_backend.slapd.communicate()
1198 # now display slapd_command_file.txt to show how slapd must be started next time
1199 message("Use later the following commandline to start slapd, then Samba:")
1200 slapd_command = "\'" + "\' \'".join(provision_backend.slapd_command) + "\'"
1201 message(slapd_command)
1202 message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
1204 setup_file(setup_path("ldap_backend_startup.sh"), paths.ldapdir + "/ldap_backend_startup.sh", {
1205 "SLAPD_COMMAND" : slapd_command})
1208 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1211 message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1213 message("Once the above files are installed, your Samba4 server will be ready to use")
1214 message("Server Role: %s" % serverrole)
1215 message("Hostname: %s" % names.hostname)
1216 message("NetBIOS Domain: %s" % names.domain)
1217 message("DNS Domain: %s" % names.dnsdomain)
1218 message("DOMAIN SID: %s" % str(domainsid))
1219 if samdb_fill == FILL_FULL:
1220 message("Admin password: %s" % adminpass)
1221 if provision_backend:
1222 if provision_backend.credentials.get_bind_dn() is not None:
1223 message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1225 message("LDAP Admin User: %s" % provision_backend.credentials.get_username())
1227 message("LDAP Admin Password: %s" % provision_backend.credentials.get_password())
1229 result = ProvisionResult()
1230 result.domaindn = domaindn
1231 result.paths = paths
1233 result.samdb = samdb
1238 def provision_become_dc(setup_dir=None,
1239 smbconf=None, targetdir=None, realm=None,
1240 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1242 domain=None, hostname=None, domainsid=None,
1243 adminpass=None, krbtgtpass=None, domainguid=None,
1244 policyguid=None, invocationid=None, machinepass=None,
1245 dnspass=None, root=None, nobody=None, users=None,
1246 wheel=None, backup=None, serverrole=None,
1247 ldap_backend=None, ldap_backend_type=None, sitename=None):
1250 """print a message if quiet is not set."""
1253 return provision(setup_dir, message, system_session(), None,
1254 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm,
1255 rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn, serverdn=serverdn,
1256 domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename)
1259 def setup_db_config(setup_path, dbdir):
1260 """Setup a Berkeley database.
1262 :param setup_path: Setup path function.
1263 :param dbdir: Database directory."""
1264 if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1265 os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1266 if not os.path.isdir(os.path.join(dbdir, "tmp")):
1267 os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1269 setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1270 {"LDAPDBDIR": dbdir})
1272 class ProvisionBackend(object):
1273 def __init__(self, paths=None, setup_path=None, lp=None, credentials=None,
1274 names=None, message=None,
1275 hostname=None, root=None,
1276 schema=None, ldapadminpass=None,
1277 ldap_backend_type=None, ldap_backend_extra_port=None,
1279 setup_ds_path=None, slapd_path=None,
1280 nosync=False, ldap_dryrun_mode=False):
1281 """Provision an LDAP backend for samba4
1283 This works for OpenLDAP and Fedora DS
1286 self.ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.ldapdir, "ldapi"), safe="")
1288 if not os.path.isdir(paths.ldapdir):
1289 os.makedirs(paths.ldapdir, 0700)
1291 if ldap_backend_type == "existing":
1292 #Check to see that this 'existing' LDAP backend in fact exists
1293 ldapi_db = Ldb(self.ldapi_uri, credentials=credentials)
1294 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1295 expression="(objectClass=OpenLDAProotDSE)")
1297 # If we have got here, then we must have a valid connection to the LDAP server, with valid credentials supplied
1298 # This caused them to be set into the long-term database later in the script.
1299 self.credentials = credentials
1300 self.ldap_backend_type = "openldap" #For now, assume existing backends at least emulate OpenLDAP
1303 # we will shortly start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via self.ldapi_uri
1304 # if another instance of slapd is already running
1306 ldapi_db = Ldb(self.ldapi_uri)
1307 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1308 expression="(objectClass=OpenLDAProotDSE)");
1310 f = open(paths.slapdpid, "r")
1313 message("Check for slapd Process with PID: " + str(p) + " and terminate it manually.")
1317 raise("Warning: Another slapd Instance seems already running on this host, listening to " + self.ldapi_uri + ". Please shut it down before you continue. ")
1322 # Try to print helpful messages when the user has not specified the path to slapd
1323 if slapd_path is None:
1324 raise("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
1325 if not os.path.exists(slapd_path):
1326 message (slapd_path)
1327 raise("Warning: Given Path to slapd does not exist!")
1329 schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1331 os.unlink(schemadb_path)
1336 # Put the LDIF of the schema into a database so we can search on
1337 # it to generate schema-dependent configurations in Fedora DS and
1339 os.path.join(paths.ldapdir, "schema-tmp.ldb")
1340 schema.ldb.connect(schemadb_path)
1341 schema.ldb.transaction_start()
1343 # These bits of LDIF are supplied when the Schema object is created
1344 schema.ldb.add_ldif(schema.schema_dn_add)
1345 schema.ldb.modify_ldif(schema.schema_dn_modify)
1346 schema.ldb.add_ldif(schema.schema_data)
1347 schema.ldb.transaction_commit()
1349 self.credentials = Credentials()
1350 self.credentials.guess(lp)
1351 #Kerberos to an ldapi:// backend makes no sense
1352 self.credentials.set_kerberos_state(DONT_USE_KERBEROS)
1353 self.ldap_backend_type = ldap_backend_type
1355 if ldap_backend_type == "fedora-ds":
1356 provision_fds_backend(self, paths=paths, setup_path=setup_path, names=names, message=message,
1357 hostname=hostname, ldapadminpass=ldapadminpass, root=root,
1358 schema=schema, ldap_backend_extra_port=ldap_backend_extra_port,
1359 setup_ds_path=setup_ds_path, slapd_path=slapd_path,
1360 nosync=nosync, ldap_dryrun_mode=ldap_dryrun_mode)
1362 elif ldap_backend_type == "openldap":
1363 provision_openldap_backend(self, paths=paths, setup_path=setup_path, names=names, message=message,
1364 hostname=hostname, ldapadminpass=ldapadminpass, root=root,
1365 schema=schema, ldap_backend_extra_port=ldap_backend_extra_port,
1366 ol_mmr_urls=ol_mmr_urls,
1367 slapd_path=slapd_path,
1368 nosync=nosync, ldap_dryrun_mode=ldap_dryrun_mode)
1370 raise("Unknown LDAP backend type selected")
1372 self.credentials.set_password(ldapadminpass)
1374 # Now start the slapd, so we can provision onto it. We keep the
1375 # subprocess context around, to kill this off at the successful
1377 self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False)
1379 while self.slapd.poll() is None:
1380 # Wait until the socket appears
1382 ldapi_db = Ldb(self.ldapi_uri, lp=lp, credentials=self.credentials)
1383 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1384 expression="(objectClass=OpenLDAProotDSE)")
1385 # If we have got here, then we must have a valid connection to the LDAP server!
1391 raise "slapd died before we could make a connection to it"
1394 def provision_openldap_backend(result, paths=None, setup_path=None, names=None, message=None,
1395 hostname=None, ldapadminpass=None, root=None,
1397 ldap_backend_extra_port=None,
1399 slapd_path=None, nosync=False,
1400 ldap_dryrun_mode=False):
1402 #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
1405 nosync_config = "dbnosync"
1408 attrs = ["linkID", "lDAPDisplayName"]
1409 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)
1411 memberof_config = "# Generated from Samba4 schema\n"
1412 refint_attributes = ""
1413 for i in range (0, len(res)):
1414 expression = "(&(objectclass=attributeSchema)(linkID=%d)(attributeSyntax=2.5.5.1))" % (int(res[i]["linkID"][0])+1)
1415 target = schema.ldb.searchone(basedn=names.schemadn,
1416 expression=expression,
1417 attribute="lDAPDisplayName",
1418 scope=SCOPE_SUBTREE)
1419 if target is not None:
1420 refint_attributes = refint_attributes + " " + res[i]["lDAPDisplayName"][0]
1422 memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1423 { "MEMBER_ATTR" : str(res[i]["lDAPDisplayName"][0]),
1424 "MEMBEROF_ATTR" : str(target) })
1426 refint_config = read_and_sub_file(setup_path("refint.conf"),
1427 { "LINK_ATTRS" : refint_attributes})
1429 res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1431 for i in range (0, len(res)):
1432 index_attr = res[i]["lDAPDisplayName"][0]
1433 if index_attr == "objectGUID":
1434 index_attr = "entryUUID"
1436 index_config += "index " + index_attr + " eq\n"
1438 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1440 mmr_replicator_acl = ""
1441 mmr_serverids_config = ""
1442 mmr_syncrepl_schema_config = ""
1443 mmr_syncrepl_config_config = ""
1444 mmr_syncrepl_user_config = ""
1447 if ol_mmr_urls is not None:
1448 # For now, make these equal
1449 mmr_pass = ldapadminpass
1451 url_list=filter(None,ol_mmr_urls.split(' '))
1452 if (len(url_list) == 1):
1453 url_list=filter(None,ol_mmr_urls.split(','))
1456 mmr_on_config = "MirrorMode On"
1457 mmr_replicator_acl = " by dn=cn=replicator,cn=samba read"
1459 for url in url_list:
1461 mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1462 { "SERVERID" : str(serverid),
1463 "LDAPSERVER" : url })
1466 mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1468 "MMRDN": names.schemadn,
1470 "MMR_PASSWORD": mmr_pass})
1473 mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1475 "MMRDN": names.configdn,
1477 "MMR_PASSWORD": mmr_pass})
1480 mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1482 "MMRDN": names.domaindn,
1484 "MMR_PASSWORD": mmr_pass })
1485 # OpenLDAP cn=config initialisation
1486 olc_syncrepl_config = ""
1488 # if mmr = yes, generate cn=config-replication directives
1489 # and olc_seed.lif for the other mmr-servers
1490 if ol_mmr_urls is not None:
1492 olc_serverids_config = ""
1493 olc_syncrepl_seed_config = ""
1494 olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
1496 for url in url_list:
1498 olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
1499 { "SERVERID" : str(serverid),
1500 "LDAPSERVER" : url })
1503 olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
1506 "MMR_PASSWORD": mmr_pass})
1508 olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1510 "LDAPSERVER" : url})
1512 setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif,
1513 {"OLC_SERVER_ID_CONF": olc_serverids_config,
1514 "OLC_PW": ldapadminpass,
1515 "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
1518 setup_file(setup_path("slapd.conf"), paths.slapdconf,
1519 {"DNSDOMAIN": names.dnsdomain,
1520 "LDAPDIR": paths.ldapdir,
1521 "DOMAINDN": names.domaindn,
1522 "CONFIGDN": names.configdn,
1523 "SCHEMADN": names.schemadn,
1524 "MEMBEROF_CONFIG": memberof_config,
1525 "MIRRORMODE": mmr_on_config,
1526 "REPLICATOR_ACL": mmr_replicator_acl,
1527 "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1528 "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1529 "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1530 "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1531 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
1532 "OLC_MMR_CONFIG": olc_mmr_config,
1533 "REFINT_CONFIG": refint_config,
1534 "INDEX_CONFIG": index_config,
1535 "NOSYNC": nosync_config})
1537 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1538 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1539 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1541 if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba", "cn=samba")):
1542 os.makedirs(os.path.join(paths.ldapdir, "db", "samba", "cn=samba"), 0700)
1544 setup_file(setup_path("cn=samba.ldif"),
1545 os.path.join(paths.ldapdir, "db", "samba", "cn=samba.ldif"),
1546 { "UUID": str(uuid.uuid4()),
1547 "LDAPTIME": timestring(int(time.time()))} )
1548 setup_file(setup_path("cn=samba-admin.ldif"),
1549 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=samba-admin.ldif"),
1550 {"LDAPADMINPASS_B64": b64encode(ldapadminpass),
1551 "UUID": str(uuid.uuid4()),
1552 "LDAPTIME": timestring(int(time.time()))} )
1554 if ol_mmr_urls is not None:
1555 setup_file(setup_path("cn=replicator.ldif"),
1556 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=replicator.ldif"),
1557 {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1558 "UUID": str(uuid.uuid4()),
1559 "LDAPTIME": timestring(int(time.time()))} )
1562 mapping = "schema-map-openldap-2.3"
1563 backend_schema = "backend-schema.schema"
1565 backend_schema_data = schema.ldb.convert_schema_to_openldap("openldap", open(setup_path(mapping), 'r').read())
1566 assert backend_schema_data is not None
1567 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1569 # now we generate the needed strings to start slapd automatically,
1570 # first ldapi_uri...
1571 if ldap_backend_extra_port is not None:
1572 # When we use MMR, we can't use 0.0.0.0 as it uses the name
1573 # specified there as part of it's clue as to it's own name,
1574 # and not to replicate to itself
1575 if ol_mmr_urls is None:
1576 server_port_string = "ldap://0.0.0.0:%d" % ldap_backend_extra_port
1578 server_port_string = "ldap://" + names.hostname + "." + names.dnsdomain +":%d" % ldap_backend_extra_port
1580 server_port_string = ""
1582 # Prepare the 'result' information - the commands to return in particular
1583 result.slapd_provision_command = [slapd_path]
1585 result.slapd_provision_command.append("-F" + paths.olcdir)
1587 result.slapd_provision_command.append("-h")
1589 # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands
1590 result.slapd_command = list(result.slapd_provision_command)
1592 result.slapd_provision_command.append(result.ldapi_uri)
1593 result.slapd_provision_command.append("-d0")
1595 uris = result.ldapi_uri
1596 if server_port_string is not "":
1597 uris = uris + " " + server_port_string
1599 result.slapd_command.append(uris)
1601 # Set the username - done here because Fedora DS still uses the admin DN and simple bind
1602 result.credentials.set_username("samba-admin")
1604 # If we were just looking for crashes up to this point, it's a
1605 # good time to exit before we realise we don't have OpenLDAP on
1607 if ldap_dryrun_mode:
1610 # Finally, convert the configuration into cn=config style!
1611 if not os.path.isdir(paths.olcdir):
1612 os.makedirs(paths.olcdir, 0770)
1614 retcode = subprocess.call([slapd_path, "-Ttest", "-f", paths.slapdconf, "-F", paths.olcdir], close_fds=True, shell=False)
1616 # We can't do this, as OpenLDAP is strange. It gives an error
1617 # output to the above, but does the conversion sucessfully...
1620 # raise("conversion from slapd.conf to cn=config failed")
1622 if not os.path.exists(os.path.join(paths.olcdir, "cn=config.ldif")):
1623 raise("conversion from slapd.conf to cn=config failed")
1625 # Don't confuse the admin by leaving the slapd.conf around
1626 os.remove(paths.slapdconf)
1629 def provision_fds_backend(result, paths=None, setup_path=None, names=None, message=None,
1630 hostname=None, ldapadminpass=None, root=None,
1632 ldap_backend_extra_port=None,
1636 ldap_dryrun_mode=False):
1638 if ldap_backend_extra_port is not None:
1639 serverport = "ServerPort=%d" % ldap_backend_extra_port
1643 setup_file(setup_path("fedorads.inf"), paths.fedoradsinf,
1645 "HOSTNAME": hostname,
1646 "DNSDOMAIN": names.dnsdomain,
1647 "LDAPDIR": paths.ldapdir,
1648 "DOMAINDN": names.domaindn,
1649 "LDAPMANAGERDN": names.ldapmanagerdn,
1650 "LDAPMANAGERPASS": ldapadminpass,
1651 "SERVERPORT": serverport})
1653 setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions,
1654 {"CONFIGDN": names.configdn,
1655 "SCHEMADN": names.schemadn,
1658 mapping = "schema-map-fedora-ds-1.0"
1659 backend_schema = "99_ad.ldif"
1661 # Build a schema file in Fedora DS format
1662 backend_schema_data = schema.ldb.convert_schema_to_openldap("fedora-ds", open(setup_path(mapping), 'r').read())
1663 assert backend_schema_data is not None
1664 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1666 result.credentials.set_bind_dn(names.ldapmanagerdn)
1668 # Destory the target directory, or else setup-ds.pl will complain
1669 fedora_ds_dir = os.path.join(paths.ldapdir, "slapd-samba4")
1670 shutil.rmtree(fedora_ds_dir, True)
1672 result.slapd_provision_command = [slapd_path, "-D", fedora_ds_dir, "-i", paths.slapdpid];
1673 #In the 'provision' command line, stay in the foreground so we can easily kill it
1674 result.slapd_provision_command.append("-d0")
1676 #the command for the final run is the normal script
1677 result.slapd_command = [os.path.join(paths.ldapdir, "slapd-samba4", "start-slapd")]
1679 # If we were just looking for crashes up to this point, it's a
1680 # good time to exit before we realise we don't have Fedora DS on
1681 if ldap_dryrun_mode:
1684 # Try to print helpful messages when the user has not specified the path to the setup-ds tool
1685 if setup_ds_path is None:
1686 raise("Warning: Fedora DS LDAP-Backend must be setup with path to setup-ds, e.g. --setup-ds-path=\"/usr/sbin/setup-ds.pl\"!")
1687 if not os.path.exists(setup_ds_path):
1688 message (setup_ds_path)
1689 raise("Warning: Given Path to slapd does not exist!")
1691 # Run the Fedora DS setup utility
1692 retcode = subprocess.call([setup_ds_path, "--silent", "--file", paths.fedoradsinf], close_fds=True, shell=False)
1694 raise("setup-ds failed")
1696 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1697 """Create a PHP LDAP admin configuration file.
1699 :param path: Path to write the configuration to.
1700 :param setup_path: Function to generate setup paths.
1702 setup_file(setup_path("phpldapadmin-config.php"), path,
1703 {"S4_LDAPI_URI": ldapi_uri})
1706 def create_zone_file(path, setup_path, dnsdomain, domaindn,
1707 hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid):
1708 """Write out a DNS zone file, from the info in the current database.
1710 :param path: Path of the new zone file.
1711 :param setup_path: Setup path function.
1712 :param dnsdomain: DNS Domain name
1713 :param domaindn: DN of the Domain
1714 :param hostip: Local IPv4 IP
1715 :param hostip6: Local IPv6 IP
1716 :param hostname: Local hostname
1717 :param dnspass: Password for DNS
1718 :param realm: Realm name
1719 :param domainguid: GUID of the domain.
1720 :param hostguid: GUID of the host.
1722 assert isinstance(domainguid, str)
1724 if hostip6 is not None:
1725 hostip6_base_line = " IN AAAA " + hostip6
1726 hostip6_host_line = hostname + " IN AAAA " + hostip6
1728 hostip6_base_line = ""
1729 hostip6_host_line = ""
1731 if hostip is not None:
1732 hostip_base_line = " IN A " + hostip
1733 hostip_host_line = hostname + " IN A " + hostip
1735 hostip_base_line = ""
1736 hostip_host_line = ""
1738 setup_file(setup_path("provision.zone"), path, {
1739 "DNSPASS_B64": b64encode(dnspass),
1740 "HOSTNAME": hostname,
1741 "DNSDOMAIN": dnsdomain,
1743 "HOSTIP_BASE_LINE": hostip_base_line,
1744 "HOSTIP_HOST_LINE": hostip_host_line,
1745 "DOMAINGUID": domainguid,
1746 "DATESTRING": time.strftime("%Y%m%d%H"),
1747 "DEFAULTSITE": DEFAULTSITE,
1748 "HOSTGUID": hostguid,
1749 "HOSTIP6_BASE_LINE": hostip6_base_line,
1750 "HOSTIP6_HOST_LINE": hostip6_host_line,
1754 def create_named_conf(path, setup_path, realm, dnsdomain,
1756 """Write out a file containing zone statements suitable for inclusion in a
1757 named.conf file (including GSS-TSIG configuration).
1759 :param path: Path of the new named.conf file.
1760 :param setup_path: Setup path function.
1761 :param realm: Realm name
1762 :param dnsdomain: DNS Domain name
1763 :param private_dir: Path to private directory
1764 :param keytab_name: File name of DNS keytab file
1767 setup_file(setup_path("named.conf"), path, {
1768 "DNSDOMAIN": dnsdomain,
1770 "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1771 "PRIVATE_DIR": private_dir
1774 def create_named_txt(path, setup_path, realm, dnsdomain,
1775 private_dir, keytab_name):
1776 """Write out a file containing zone statements suitable for inclusion in a
1777 named.conf file (including GSS-TSIG configuration).
1779 :param path: Path of the new named.conf file.
1780 :param setup_path: Setup path function.
1781 :param realm: Realm name
1782 :param dnsdomain: DNS Domain name
1783 :param private_dir: Path to private directory
1784 :param keytab_name: File name of DNS keytab file
1787 setup_file(setup_path("named.txt"), path, {
1788 "DNSDOMAIN": dnsdomain,
1790 "DNS_KEYTAB": keytab_name,
1791 "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1792 "PRIVATE_DIR": private_dir
1795 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1796 """Write out a file containing zone statements suitable for inclusion in a
1797 named.conf file (including GSS-TSIG configuration).
1799 :param path: Path of the new named.conf file.
1800 :param setup_path: Setup path function.
1801 :param dnsdomain: DNS Domain name
1802 :param hostname: Local hostname
1803 :param realm: Realm name
1806 setup_file(setup_path("krb5.conf"), path, {
1807 "DNSDOMAIN": dnsdomain,
1808 "HOSTNAME": hostname,