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()
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, krbtgtpass=None, domainguid=None,
973 policyguid=None, invocationid=None, machinepass=None,
974 dnspass=None, root=None, nobody=None, users=None,
975 wheel=None, backup=None, aci=None, serverrole=None,
976 ldap_backend_extra_port=None, ldap_backend_type=None, sitename=None,
977 ol_mmr_urls=None, ol_olc=None,
978 setup_ds_path=None, slapd_path=None, nosync=False,
979 ldap_dryrun_mode=False):
982 :note: caution, this wipes all existing data!
985 def setup_path(file):
986 return os.path.join(setup_dir, file)
988 if domainsid is None:
989 domainsid = security.random_sid()
991 if policyguid is None:
992 policyguid = str(uuid.uuid4())
993 if adminpass is None:
994 adminpass = glue.generate_random_str(12)
995 if krbtgtpass is None:
996 krbtgtpass = glue.generate_random_str(12)
997 if machinepass is None:
998 machinepass = glue.generate_random_str(12)
1000 dnspass = glue.generate_random_str(12)
1001 root_uid = findnss_uid([root or "root"])
1002 nobody_uid = findnss_uid([nobody or "nobody"])
1003 users_gid = findnss_gid([users or "users"])
1005 wheel_gid = findnss_gid(["wheel", "adm"])
1007 wheel_gid = findnss_gid([wheel])
1009 if targetdir is not None:
1010 if (not os.path.exists(os.path.join(targetdir, "etc"))):
1011 os.makedirs(os.path.join(targetdir, "etc"))
1012 smbconf = os.path.join(targetdir, "etc", "smb.conf")
1013 elif smbconf is None:
1014 smbconf = param.default_path()
1016 # only install a new smb.conf if there isn't one there already
1017 if not os.path.exists(smbconf):
1018 make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
1021 lp = param.LoadParm()
1024 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1025 dnsdomain=realm, serverrole=serverrole, sitename=sitename,
1026 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1029 paths = provision_paths_from_lp(lp, names.dnsdomain)
1033 hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1034 except socket.gaierror, (socket.EAI_NODATA, msg):
1039 hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1040 except socket.gaierror, (socket.EAI_NODATA, msg):
1043 if serverrole is None:
1044 serverrole = lp.get("server role")
1046 assert serverrole in ("domain controller", "member server", "standalone")
1047 if invocationid is None and serverrole == "domain controller":
1048 invocationid = str(uuid.uuid4())
1050 if not os.path.exists(paths.private_dir):
1051 os.mkdir(paths.private_dir)
1053 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1055 schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn)
1057 provision_backend = None
1058 if ldap_backend_type:
1059 # We only support an LDAP backend over ldapi://
1061 provision_backend = ProvisionBackend(paths=paths, setup_path=setup_path, lp=lp, credentials=credentials,
1063 message=message, hostname=hostname,
1064 root=root, schema=schema, ldap_backend_type=ldap_backend_type,
1065 ldap_backend_extra_port=ldap_backend_extra_port,
1066 ol_mmr_urls=ol_mmr_urls,
1067 slapd_path=slapd_path,
1068 setup_ds_path=setup_ds_path,
1069 ldap_dryrun_mode=ldap_dryrun_mode)
1071 # Now use the backend credentials to access the databases
1072 credentials = provision_backend.credentials
1074 # only install a new shares config db if there is none
1075 if not os.path.exists(paths.shareconf):
1076 message("Setting up share.ldb")
1077 share_ldb = Ldb(paths.shareconf, session_info=session_info,
1078 credentials=credentials, lp=lp)
1079 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1082 message("Setting up secrets.ldb")
1083 secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
1084 session_info=session_info,
1085 credentials=credentials, lp=lp)
1087 message("Setting up the registry")
1088 setup_registry(paths.hklm, setup_path, session_info,
1091 message("Setting up templates db")
1092 setup_templatesdb(paths.templates, setup_path, session_info=session_info,
1095 message("Setting up idmap db")
1096 idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1099 message("Setting up SAM db")
1100 samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info,
1101 credentials=credentials, lp=lp, names=names,
1103 domainsid=domainsid,
1104 schema=schema, domainguid=domainguid, policyguid=policyguid,
1106 adminpass=adminpass, krbtgtpass=krbtgtpass,
1107 invocationid=invocationid,
1108 machinepass=machinepass, dnspass=dnspass,
1109 serverrole=serverrole, ldap_backend=provision_backend)
1111 if serverrole == "domain controller":
1112 if paths.netlogon is None:
1113 message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1114 message("Please either remove %s or see the template at %s" %
1115 ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1116 assert(paths.netlogon is not None)
1118 if paths.sysvol is None:
1119 message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1120 message("Please either remove %s or see the template at %s" %
1121 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1122 assert(paths.sysvol is not None)
1124 policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1125 "{" + policyguid + "}")
1126 os.makedirs(policy_path, 0755)
1127 open(os.path.join(policy_path, "GPT.INI"), 'w').write("")
1128 os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1129 os.makedirs(os.path.join(policy_path, "User"), 0755)
1130 if not os.path.isdir(paths.netlogon):
1131 os.makedirs(paths.netlogon, 0755)
1133 if samdb_fill == FILL_FULL:
1134 setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1135 root_uid=root_uid, nobody_uid=nobody_uid,
1136 users_gid=users_gid, wheel_gid=wheel_gid)
1138 message("Setting up sam.ldb rootDSE marking as synchronized")
1139 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1141 # Only make a zone file on the first DC, it should be replicated with DNS replication
1142 if serverrole == "domain controller":
1143 secrets_ldb = Ldb(paths.secrets, session_info=session_info,
1144 credentials=credentials, lp=lp)
1145 secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1146 netbiosname=names.netbiosname, domainsid=domainsid,
1147 keytab_path=paths.keytab, samdb_url=paths.samdb,
1148 dns_keytab_path=paths.dns_keytab, dnspass=dnspass,
1149 machinepass=machinepass, dnsdomain=names.dnsdomain)
1151 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1152 assert isinstance(domainguid, str)
1153 hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1154 expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1155 scope=SCOPE_SUBTREE)
1156 assert isinstance(hostguid, str)
1158 create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1159 domaindn=names.domaindn, hostip=hostip,
1160 hostip6=hostip6, hostname=names.hostname,
1161 dnspass=dnspass, realm=names.realm,
1162 domainguid=domainguid, hostguid=hostguid)
1164 create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1165 dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1167 create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1168 dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1169 keytab_name=paths.dns_keytab)
1170 message("See %s for an example configuration include file for BIND" % paths.namedconf)
1171 message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1173 create_krb5_conf(paths.krb5conf, setup_path, dnsdomain=names.dnsdomain,
1174 hostname=names.hostname, realm=names.realm)
1175 message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1178 # if backend is openldap, terminate slapd after final provision and check its proper termination
1179 if provision_backend is not None and provision_backend.slapd is not None:
1180 if provision_backend.slapd.poll() is None:
1182 if hasattr(provision_backend.slapd, "terminate"):
1183 provision_backend.slapd.terminate()
1186 os.kill(provision_backend.slapd.pid, signal.SIGTERM)
1188 #and now wait for it to die
1189 provision_backend.slapd.communicate()
1191 # now display slapd_command_file.txt to show how slapd must be started next time
1192 message("Use later the following commandline to start slapd, then Samba:")
1193 slapd_command = "\'" + "\' \'".join(provision_backend.slapd_command) + "\'"
1194 message(slapd_command)
1195 message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
1197 setup_file(setup_path("ldap_backend_startup.sh"), paths.ldapdir + "/ldap_backend_startup.sh", {
1198 "SLAPD_COMMAND" : slapd_command})
1200 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1203 message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1205 message("Once the above files are installed, your Samba4 server will be ready to use")
1206 message("Server Role: %s" % serverrole)
1207 message("Hostname: %s" % names.hostname)
1208 message("NetBIOS Domain: %s" % names.domain)
1209 message("DNS Domain: %s" % names.dnsdomain)
1210 message("DOMAIN SID: %s" % str(domainsid))
1211 if samdb_fill == FILL_FULL:
1212 message("Admin password: %s" % adminpass)
1214 result = ProvisionResult()
1215 result.domaindn = domaindn
1216 result.paths = paths
1218 result.samdb = samdb
1223 def provision_become_dc(setup_dir=None,
1224 smbconf=None, targetdir=None, realm=None,
1225 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1227 domain=None, hostname=None, domainsid=None,
1228 adminpass=None, krbtgtpass=None, domainguid=None,
1229 policyguid=None, invocationid=None, machinepass=None,
1230 dnspass=None, root=None, nobody=None, users=None,
1231 wheel=None, backup=None, serverrole=None,
1232 ldap_backend=None, ldap_backend_type=None, sitename=None):
1235 """print a message if quiet is not set."""
1238 return provision(setup_dir, message, system_session(), None,
1239 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm,
1240 rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn, serverdn=serverdn,
1241 domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename)
1244 def setup_db_config(setup_path, dbdir):
1245 """Setup a Berkeley database.
1247 :param setup_path: Setup path function.
1248 :param dbdir: Database directory."""
1249 if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1250 os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1251 if not os.path.isdir(os.path.join(dbdir, "tmp")):
1252 os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1254 setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1255 {"LDAPDBDIR": dbdir})
1257 class ProvisionBackend(object):
1258 def __init__(self, paths=None, setup_path=None, lp=None, credentials=None,
1259 names=None, message=None,
1260 hostname=None, root=None,
1262 ldap_backend_type=None, ldap_backend_extra_port=None,
1264 setup_ds_path=None, slapd_path=None,
1265 nosync=False, ldap_dryrun_mode=False):
1266 """Provision an LDAP backend for samba4
1268 This works for OpenLDAP and Fedora DS
1271 self.ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.ldapdir, "ldapi"), safe="")
1273 if not os.path.isdir(paths.ldapdir):
1274 os.makedirs(paths.ldapdir, 0700)
1276 if ldap_backend_type == "existing":
1277 #Check to see that this 'existing' LDAP backend in fact exists
1278 ldapi_db = Ldb(self.ldapi_uri, credentials=credentials)
1279 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1280 expression="(objectClass=OpenLDAProotDSE)")
1282 # If we have got here, then we must have a valid connection to the LDAP server, with valid credentials supplied
1283 # This caused them to be set into the long-term database later in the script.
1284 self.credentials = credentials
1285 self.ldap_backend_type = "openldap" #For now, assume existing backends at least emulate OpenLDAP
1288 # we will shortly start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via self.ldapi_uri
1289 # if another instance of slapd is already running
1291 ldapi_db = Ldb(self.ldapi_uri)
1292 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1293 expression="(objectClass=OpenLDAProotDSE)");
1295 f = open(paths.slapdpid, "r")
1298 message("Check for slapd Process with PID: " + str(p) + " and terminate it manually.")
1302 raise("Warning: Another slapd Instance seems already running on this host, listening to " + ldapi_uri + ". Please shut it down before you continue. ")
1307 # Try to print helpful messages when the user has not specified the path to slapd
1308 if slapd_path is None:
1309 raise("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
1310 if not os.path.exists(slapd_path):
1311 message (slapd_path)
1312 raise("Warning: Given Path to slapd does not exist!")
1314 schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1316 os.unlink(schemadb_path)
1321 # Put the LDIF of the schema into a database so we can search on
1322 # it to generate schema-dependent configurations in Fedora DS and
1324 os.path.join(paths.ldapdir, "schema-tmp.ldb")
1325 schema.ldb.connect(schemadb_path)
1326 schema.ldb.transaction_start()
1328 # These bits of LDIF are supplied when the Schema object is created
1329 schema.ldb.add_ldif(schema.schema_dn_add)
1330 schema.ldb.modify_ldif(schema.schema_dn_modify)
1331 schema.ldb.add_ldif(schema.schema_data)
1332 schema.ldb.transaction_commit()
1334 self.credentials = Credentials()
1335 self.credentials.guess(lp)
1336 self.ldap_backend_type = ldap_backend_type
1338 #Make a new, random password between Samba and it's LDAP server
1339 ldapadminpass=glue.generate_random_str(12)
1341 if ldap_backend_type == "fedora-ds":
1342 provision_fds_backend(self, paths=paths, setup_path=setup_path, names=names, message=message,
1343 hostname=hostname, ldapadminpass=ldapadminpass, root=root,
1344 schema=schema, ldap_backend_extra_port=ldap_backend_extra_port,
1345 setup_ds_path=setup_ds_path, slapd_path=slapd_path,
1346 nosync=nosync, ldap_dryrun_mode=ldap_dryrun_mode)
1348 elif ldap_backend_type == "openldap":
1349 provision_openldap_backend(self, paths=paths, setup_path=setup_path, names=names, message=message,
1350 hostname=hostname, ldapadminpass=ldapadminpass, root=root,
1351 schema=schema, ldap_backend_extra_port=ldap_backend_extra_port,
1352 ol_mmr_urls=ol_mmr_urls,
1353 slapd_path=slapd_path,
1354 nosync=nosync, ldap_dryrun_mode=ldap_dryrun_mode)
1356 raise("Unknown LDAP backend type selected")
1358 self.credentials.set_password(ldapadminpass)
1360 # Now start the slapd, so we can provision onto it. We keep the
1361 # subprocess context around, to kill this off at the successful
1363 self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False)
1365 while self.slapd.poll() is None:
1366 # Wait until the socket appears
1368 ldapi_db = Ldb(self.ldapi_uri, lp=lp, credentials=self.credentials)
1369 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1370 expression="(objectClass=OpenLDAProotDSE)")
1371 # If we have got here, then we must have a valid connection to the LDAP server!
1377 raise "slapd died before we could make a connection to it"
1380 def provision_openldap_backend(result, paths=None, setup_path=None, names=None, message=None,
1381 hostname=None, ldapadminpass=None, root=None,
1383 ldap_backend_extra_port=None,
1385 slapd_path=None, nosync=False,
1386 ldap_dryrun_mode=False):
1388 #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
1391 nosync_config = "dbnosync"
1394 attrs = ["linkID", "lDAPDisplayName"]
1395 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)
1397 memberof_config = "# Generated from Samba4 schema\n"
1398 refint_attributes = ""
1399 for i in range (0, len(res)):
1400 expression = "(&(objectclass=attributeSchema)(linkID=%d)(attributeSyntax=2.5.5.1))" % (int(res[i]["linkID"][0])+1)
1401 target = schema.ldb.searchone(basedn=names.schemadn,
1402 expression=expression,
1403 attribute="lDAPDisplayName",
1404 scope=SCOPE_SUBTREE)
1405 if target is not None:
1406 refint_attributes = refint_attributes + " " + res[i]["lDAPDisplayName"][0]
1408 memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1409 { "MEMBER_ATTR" : str(res[i]["lDAPDisplayName"][0]),
1410 "MEMBEROF_ATTR" : str(target) })
1412 refint_config = read_and_sub_file(setup_path("refint.conf"),
1413 { "LINK_ATTRS" : refint_attributes})
1415 res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1417 for i in range (0, len(res)):
1418 index_attr = res[i]["lDAPDisplayName"][0]
1419 if index_attr == "objectGUID":
1420 index_attr = "entryUUID"
1422 index_config += "index " + index_attr + " eq\n"
1424 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1426 mmr_replicator_acl = ""
1427 mmr_serverids_config = ""
1428 mmr_syncrepl_schema_config = ""
1429 mmr_syncrepl_config_config = ""
1430 mmr_syncrepl_user_config = ""
1433 if ol_mmr_urls is not None:
1434 # For now, make these equal
1435 mmr_pass = ldapadminpass
1437 url_list=filter(None,ol_mmr_urls.split(' '))
1438 if (len(url_list) == 1):
1439 url_list=filter(None,ol_mmr_urls.split(','))
1442 mmr_on_config = "MirrorMode On"
1443 mmr_replicator_acl = " by dn=cn=replicator,cn=samba read"
1445 for url in url_list:
1447 mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1448 { "SERVERID" : str(serverid),
1449 "LDAPSERVER" : url })
1452 mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1454 "MMRDN": names.schemadn,
1456 "MMR_PASSWORD": mmr_pass})
1459 mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1461 "MMRDN": names.configdn,
1463 "MMR_PASSWORD": mmr_pass})
1466 mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1468 "MMRDN": names.domaindn,
1470 "MMR_PASSWORD": mmr_pass })
1471 # OpenLDAP cn=config initialisation
1472 olc_syncrepl_config = ""
1474 # if mmr = yes, generate cn=config-replication directives
1475 # and olc_seed.lif for the other mmr-servers
1476 if ol_mmr_urls is not None:
1478 olc_serverids_config = ""
1479 olc_syncrepl_seed_config = ""
1480 olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
1482 for url in url_list:
1484 olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
1485 { "SERVERID" : str(serverid),
1486 "LDAPSERVER" : url })
1489 olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
1492 "MMR_PASSWORD": mmr_pass})
1494 olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1496 "LDAPSERVER" : url})
1498 setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif,
1499 {"OLC_SERVER_ID_CONF": olc_serverids_config,
1500 "OLC_PW": ldapadminpass,
1501 "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
1504 setup_file(setup_path("slapd.conf"), paths.slapdconf,
1505 {"DNSDOMAIN": names.dnsdomain,
1506 "LDAPDIR": paths.ldapdir,
1507 "DOMAINDN": names.domaindn,
1508 "CONFIGDN": names.configdn,
1509 "SCHEMADN": names.schemadn,
1510 "MEMBEROF_CONFIG": memberof_config,
1511 "MIRRORMODE": mmr_on_config,
1512 "REPLICATOR_ACL": mmr_replicator_acl,
1513 "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1514 "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1515 "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1516 "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1517 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
1518 "OLC_MMR_CONFIG": olc_mmr_config,
1519 "REFINT_CONFIG": refint_config,
1520 "INDEX_CONFIG": index_config,
1521 "NOSYNC": nosync_config})
1523 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1524 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1525 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1527 if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba", "cn=samba")):
1528 os.makedirs(os.path.join(paths.ldapdir, "db", "samba", "cn=samba"), 0700)
1530 setup_file(setup_path("cn=samba.ldif"),
1531 os.path.join(paths.ldapdir, "db", "samba", "cn=samba.ldif"),
1532 { "UUID": str(uuid.uuid4()),
1533 "LDAPTIME": timestring(int(time.time()))} )
1534 setup_file(setup_path("cn=samba-admin.ldif"),
1535 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=samba-admin.ldif"),
1536 {"LDAPADMINPASS_B64": b64encode(ldapadminpass),
1537 "UUID": str(uuid.uuid4()),
1538 "LDAPTIME": timestring(int(time.time()))} )
1540 if ol_mmr_urls is not None:
1541 setup_file(setup_path("cn=replicator.ldif"),
1542 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=replicator.ldif"),
1543 {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1544 "UUID": str(uuid.uuid4()),
1545 "LDAPTIME": timestring(int(time.time()))} )
1548 mapping = "schema-map-openldap-2.3"
1549 backend_schema = "backend-schema.schema"
1551 backend_schema_data = schema.ldb.convert_schema_to_openldap("openldap", open(setup_path(mapping), 'r').read())
1552 assert backend_schema_data is not None
1553 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1555 # now we generate the needed strings to start slapd automatically,
1556 # first ldapi_uri...
1557 if ldap_backend_extra_port is not None:
1558 # When we use MMR, we can't use 0.0.0.0 as it uses the name
1559 # specified there as part of it's clue as to it's own name,
1560 # and not to replicate to itself
1561 if ol_mmr_urls is None:
1562 server_port_string = "ldap://0.0.0.0:%d" % ldap_backend_extra_port
1564 server_port_string = "ldap://" + names.hostname + "." + names.dnsdomain +":%d" % ldap_backend_extra_port
1566 server_port_string = ""
1568 # Prepare the 'result' information - the commands to return in particular
1569 result.slapd_provision_command = [slapd_path]
1571 result.slapd_provision_command.append("-F" + paths.olcdir)
1573 result.slapd_provision_command.append("-h")
1575 # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands
1576 result.slapd_command = list(result.slapd_provision_command)
1578 result.slapd_provision_command.append(result.ldapi_uri)
1579 result.slapd_provision_command.append("-d0")
1581 uris = result.ldapi_uri
1582 if server_port_string is not "":
1583 uris = uris + " " + server_port_string
1585 result.slapd_command.append(uris)
1587 # Set the username - done here because Fedora DS still uses the admin DN and simple bind
1588 result.credentials.set_username("samba-admin")
1590 # If we were just looking for crashes up to this point, it's a
1591 # good time to exit before we realise we don't have OpenLDAP on
1593 if ldap_dryrun_mode:
1596 # Finally, convert the configuration into cn=config style!
1597 if not os.path.isdir(paths.olcdir):
1598 os.makedirs(paths.olcdir, 0770)
1600 retcode = subprocess.call([slapd_path, "-Ttest", "-f", paths.slapdconf, "-F", paths.olcdir], close_fds=True, shell=False)
1602 # We can't do this, as OpenLDAP is strange. It gives an error
1603 # output to the above, but does the conversion sucessfully...
1606 # raise("conversion from slapd.conf to cn=config failed")
1608 if not os.path.exists(os.path.join(paths.olcdir, "cn=config.ldif")):
1609 raise("conversion from slapd.conf to cn=config failed")
1611 # Don't confuse the admin by leaving the slapd.conf around
1612 os.remove(paths.slapdconf)
1615 def provision_fds_backend(result, paths=None, setup_path=None, names=None, message=None,
1616 hostname=None, ldapadminpass=None, root=None,
1618 ldap_backend_extra_port=None,
1622 ldap_dryrun_mode=False):
1624 if ldap_backend_extra_port is not None:
1625 serverport = "ServerPort=%d" % ldap_backend_extra_port
1629 setup_file(setup_path("fedorads.inf"), paths.fedoradsinf,
1631 "HOSTNAME": hostname,
1632 "DNSDOMAIN": names.dnsdomain,
1633 "LDAPDIR": paths.ldapdir,
1634 "DOMAINDN": names.domaindn,
1635 "LDAPMANAGERDN": names.ldapmanagerdn,
1636 "LDAPMANAGERPASS": ldapadminpass,
1637 "SERVERPORT": serverport})
1639 setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions,
1640 {"CONFIGDN": names.configdn,
1641 "SCHEMADN": names.schemadn,
1644 mapping = "schema-map-fedora-ds-1.0"
1645 backend_schema = "99_ad.ldif"
1647 # Build a schema file in Fedora DS format
1648 backend_schema_data = schema.ldb.convert_schema_to_openldap("fedora-ds", open(setup_path(mapping), 'r').read())
1649 assert backend_schema_data is not None
1650 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1652 result.credentials.set_bind_dn(names.ldapmanagerdn)
1654 # Destory the target directory, or else setup-ds.pl will complain
1655 fedora_ds_dir = os.path.join(paths.ldapdir, "slapd-samba4")
1656 shutil.rmtree(fedora_ds_dir, True)
1658 result.slapd_provision_command = [slapd_path, "-D", fedora_ds_dir, "-i", paths.slapdpid];
1659 #In the 'provision' command line, stay in the foreground so we can easily kill it
1660 result.slapd_provision_command.append("-d0")
1662 #the command for the final run is the normal script
1663 result.slapd_command = [os.path.join(paths.ldapdir, "slapd-samba4", "start-slapd")]
1665 # If we were just looking for crashes up to this point, it's a
1666 # good time to exit before we realise we don't have Fedora DS on
1667 if ldap_dryrun_mode:
1670 # Try to print helpful messages when the user has not specified the path to the setup-ds tool
1671 if setup_ds_path is None:
1672 raise("Warning: Fedora DS LDAP-Backend must be setup with path to setup-ds, e.g. --setup-ds-path=\"/usr/sbin/setup-ds.pl\"!")
1673 if not os.path.exists(setup_ds_path):
1674 message (setup_ds_path)
1675 raise("Warning: Given Path to slapd does not exist!")
1677 # Run the Fedora DS setup utility
1678 retcode = subprocess.call([setup_ds_path, "--silent", "--file", paths.fedoradsinf], close_fds=True, shell=False)
1680 raise("setup-ds failed")
1682 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1683 """Create a PHP LDAP admin configuration file.
1685 :param path: Path to write the configuration to.
1686 :param setup_path: Function to generate setup paths.
1688 setup_file(setup_path("phpldapadmin-config.php"), path,
1689 {"S4_LDAPI_URI": ldapi_uri})
1692 def create_zone_file(path, setup_path, dnsdomain, domaindn,
1693 hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid):
1694 """Write out a DNS zone file, from the info in the current database.
1696 :param path: Path of the new zone file.
1697 :param setup_path: Setup path function.
1698 :param dnsdomain: DNS Domain name
1699 :param domaindn: DN of the Domain
1700 :param hostip: Local IPv4 IP
1701 :param hostip6: Local IPv6 IP
1702 :param hostname: Local hostname
1703 :param dnspass: Password for DNS
1704 :param realm: Realm name
1705 :param domainguid: GUID of the domain.
1706 :param hostguid: GUID of the host.
1708 assert isinstance(domainguid, str)
1710 if hostip6 is not None:
1711 hostip6_base_line = " IN AAAA " + hostip6
1712 hostip6_host_line = hostname + " IN AAAA " + hostip6
1714 hostip6_base_line = ""
1715 hostip6_host_line = ""
1717 if hostip is not None:
1718 hostip_base_line = " IN A " + hostip
1719 hostip_host_line = hostname + " IN A " + hostip
1721 hostip_base_line = ""
1722 hostip_host_line = ""
1724 setup_file(setup_path("provision.zone"), path, {
1725 "DNSPASS_B64": b64encode(dnspass),
1726 "HOSTNAME": hostname,
1727 "DNSDOMAIN": dnsdomain,
1729 "HOSTIP_BASE_LINE": hostip_base_line,
1730 "HOSTIP_HOST_LINE": hostip_host_line,
1731 "DOMAINGUID": domainguid,
1732 "DATESTRING": time.strftime("%Y%m%d%H"),
1733 "DEFAULTSITE": DEFAULTSITE,
1734 "HOSTGUID": hostguid,
1735 "HOSTIP6_BASE_LINE": hostip6_base_line,
1736 "HOSTIP6_HOST_LINE": hostip6_host_line,
1740 def create_named_conf(path, setup_path, realm, dnsdomain,
1742 """Write out a file containing zone statements suitable for inclusion in a
1743 named.conf file (including GSS-TSIG configuration).
1745 :param path: Path of the new named.conf file.
1746 :param setup_path: Setup path function.
1747 :param realm: Realm name
1748 :param dnsdomain: DNS Domain name
1749 :param private_dir: Path to private directory
1750 :param keytab_name: File name of DNS keytab file
1753 setup_file(setup_path("named.conf"), path, {
1754 "DNSDOMAIN": dnsdomain,
1756 "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1757 "PRIVATE_DIR": private_dir
1760 def create_named_txt(path, setup_path, realm, dnsdomain,
1761 private_dir, keytab_name):
1762 """Write out a file containing zone statements suitable for inclusion in a
1763 named.conf file (including GSS-TSIG configuration).
1765 :param path: Path of the new named.conf file.
1766 :param setup_path: Setup path function.
1767 :param realm: Realm name
1768 :param dnsdomain: DNS Domain name
1769 :param private_dir: Path to private directory
1770 :param keytab_name: File name of DNS keytab file
1773 setup_file(setup_path("named.txt"), path, {
1774 "DNSDOMAIN": dnsdomain,
1776 "DNS_KEYTAB": keytab_name,
1777 "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1778 "PRIVATE_DIR": private_dir
1781 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1782 """Write out a file containing zone statements suitable for inclusion in a
1783 named.conf file (including GSS-TSIG configuration).
1785 :param path: Path of the new named.conf file.
1786 :param setup_path: Setup path function.
1787 :param dnsdomain: DNS Domain name
1788 :param hostname: Local hostname
1789 :param realm: Realm name
1792 setup_file(setup_path("krb5.conf"), path, {
1793 "DNSDOMAIN": dnsdomain,
1794 "HOSTNAME": hostname,