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_foreign(samdb, domaindn, "S-1-5-7", "Anonymous")
491 add_foreign(samdb, domaindn, "S-1-1-0", "World")
492 add_foreign(samdb, domaindn, "S-1-5-2", "Network")
493 add_foreign(samdb, domaindn, "S-1-5-18", "System")
494 add_foreign(samdb, domaindn, "S-1-5-11", "Authenticated Users")
496 idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
497 idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
499 idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
500 idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
502 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
504 serverrole, ldap_backend=None,
506 """Setup the partitions for the SAM database.
508 Alternatively, provision() may call this, and then populate the database.
510 :note: This will wipe the Sam Database!
512 :note: This function always removes the local SAM LDB file. The erase
513 parameter controls whether to erase the existing data, which
514 may not be stored locally but in LDAP.
516 assert session_info is not None
518 # We use options=["modules:"] to stop the modules loading - we
519 # just want to wipe and re-initialise the database, not start it up
522 samdb = Ldb(url=samdb_path, session_info=session_info,
523 credentials=credentials, lp=lp, options=["modules:"])
525 samdb.erase_except_schema_controlled()
527 os.unlink(samdb_path)
528 samdb = Ldb(url=samdb_path, session_info=session_info,
529 credentials=credentials, lp=lp, options=["modules:"])
531 samdb.erase_except_schema_controlled()
534 #Add modules to the list to activate them by default
535 #beware often order is important
537 # Some Known ordering constraints:
538 # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
539 # - objectclass must be before password_hash, because password_hash checks
540 # that the objectclass is of type person (filled in by objectclass
541 # module when expanding the objectclass list)
542 # - partition must be last
543 # - each partition has its own module list then
544 modules_list = ["rootdse",
562 "extended_dn_out_ldb"]
563 modules_list2 = ["show_deleted",
566 domaindn_ldb = "users.ldb"
567 configdn_ldb = "configuration.ldb"
568 schemadn_ldb = "schema.ldb"
569 if ldap_backend is not None:
570 domaindn_ldb = ldap_backend.ldapi_uri
571 configdn_ldb = ldap_backend.ldapi_uri
572 schemadn_ldb = ldap_backend.ldapi_uri
574 if ldap_backend.ldap_backend_type == "fedora-ds":
575 backend_modules = ["nsuniqueid", "paged_searches"]
576 # We can handle linked attributes here, as we don't have directory-side subtree operations
577 tdb_modules_list = ["linked_attributes", "extended_dn_out_dereference"]
578 elif ldap_backend.ldap_backend_type == "openldap":
579 backend_modules = ["entryuuid", "paged_searches"]
580 # OpenLDAP handles subtree renames, so we don't want to do any of these things
581 tdb_modules_list = ["extended_dn_out_dereference"]
583 elif serverrole == "domain controller":
584 backend_modules = ["repl_meta_data"]
586 backend_modules = ["objectguid"]
588 if tdb_modules_list is None:
589 tdb_modules_list_as_string = ""
591 tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
593 samdb.transaction_start()
595 message("Setting up sam.ldb partitions and settings")
596 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
597 "SCHEMADN": names.schemadn,
598 "SCHEMADN_LDB": schemadn_ldb,
599 "SCHEMADN_MOD2": ",objectguid",
600 "CONFIGDN": names.configdn,
601 "CONFIGDN_LDB": configdn_ldb,
602 "DOMAINDN": names.domaindn,
603 "DOMAINDN_LDB": domaindn_ldb,
604 "SCHEMADN_MOD": "schema_fsmo,instancetype",
605 "CONFIGDN_MOD": "naming_fsmo,instancetype",
606 "DOMAINDN_MOD": "pdc_fsmo,instancetype",
607 "MODULES_LIST": ",".join(modules_list),
608 "TDB_MODULES_LIST": tdb_modules_list_as_string,
609 "MODULES_LIST2": ",".join(modules_list2),
610 "BACKEND_MOD": ",".join(backend_modules),
613 samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
615 message("Setting up sam.ldb rootDSE")
616 setup_samdb_rootdse(samdb, setup_path, names)
619 samdb.transaction_cancel()
622 samdb.transaction_commit()
626 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain,
627 netbiosname, domainsid, keytab_path, samdb_url,
628 dns_keytab_path, dnspass, machinepass):
629 """Add DC-specific bits to a secrets database.
631 :param secretsdb: Ldb Handle to the secrets database
632 :param setup_path: Setup path function
633 :param machinepass: Machine password
635 setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), {
636 "MACHINEPASS_B64": b64encode(machinepass),
639 "DNSDOMAIN": dnsdomain,
640 "DOMAINSID": str(domainsid),
641 "SECRETS_KEYTAB": keytab_path,
642 "NETBIOSNAME": netbiosname,
643 "SAM_LDB": samdb_url,
644 "DNS_KEYTAB": dns_keytab_path,
645 "DNSPASS_B64": b64encode(dnspass),
649 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
650 """Setup the secrets database.
652 :param path: Path to the secrets database.
653 :param setup_path: Get the path to a setup file.
654 :param session_info: Session info.
655 :param credentials: Credentials
656 :param lp: Loadparm context
657 :return: LDB handle for the created secrets database
659 if os.path.exists(path):
661 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
664 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
665 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
667 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
669 if credentials is not None and credentials.authentication_requested():
670 if credentials.get_bind_dn() is not None:
671 setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
672 "LDAPMANAGERDN": credentials.get_bind_dn(),
673 "LDAPMANAGERPASS_B64": b64encode(credentials.get_password())
676 setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
677 "LDAPADMINUSER": credentials.get_username(),
678 "LDAPADMINREALM": credentials.get_realm(),
679 "LDAPADMINPASS_B64": b64encode(credentials.get_password())
685 def setup_templatesdb(path, setup_path, session_info, lp):
686 """Setup the templates database.
688 :param path: Path to the database.
689 :param setup_path: Function for obtaining the path to setup files.
690 :param session_info: Session info
691 :param credentials: Credentials
692 :param lp: Loadparm context
694 templates_ldb = Ldb(url=path, session_info=session_info,
698 templates_ldb.erase()
699 # This should be 'except LdbError', but on a re-provision the assert in ldb.erase fires, and we need to catch that too
703 templates_ldb.load_ldif_file_add(setup_path("provision_templates_init.ldif"))
705 templates_ldb = Ldb(url=path, session_info=session_info,
708 templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif"))
711 def setup_registry(path, setup_path, session_info, lp):
712 """Setup the registry.
714 :param path: Path to the registry database
715 :param setup_path: Function that returns the path to a setup.
716 :param session_info: Session information
717 :param credentials: Credentials
718 :param lp: Loadparm context
720 reg = registry.Registry()
721 hive = registry.open_ldb(path, session_info=session_info,
723 reg.mount_hive(hive, registry.HKEY_LOCAL_MACHINE)
724 provision_reg = setup_path("provision.reg")
725 assert os.path.exists(provision_reg)
726 reg.diff_apply(provision_reg)
729 def setup_idmapdb(path, setup_path, session_info, lp):
730 """Setup the idmap database.
732 :param path: path to the idmap database
733 :param setup_path: Function that returns a path to a setup file
734 :param session_info: Session information
735 :param credentials: Credentials
736 :param lp: Loadparm context
738 if os.path.exists(path):
741 idmap_ldb = IDmapDB(path, session_info=session_info,
745 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
749 def setup_samdb_rootdse(samdb, setup_path, names):
750 """Setup the SamDB rootdse.
752 :param samdb: Sam Database handle
753 :param setup_path: Obtain setup path
755 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
756 "SCHEMADN": names.schemadn,
757 "NETBIOSNAME": names.netbiosname,
758 "DNSDOMAIN": names.dnsdomain,
759 "REALM": names.realm,
760 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
761 "DOMAINDN": names.domaindn,
762 "ROOTDN": names.rootdn,
763 "CONFIGDN": names.configdn,
764 "SERVERDN": names.serverdn,
768 def setup_self_join(samdb, names,
769 machinepass, dnspass,
770 domainsid, invocationid, setup_path,
771 policyguid, domainControllerFunctionality):
772 """Join a host to its own domain."""
773 assert isinstance(invocationid, str)
774 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
775 "CONFIGDN": names.configdn,
776 "SCHEMADN": names.schemadn,
777 "DOMAINDN": names.domaindn,
778 "SERVERDN": names.serverdn,
779 "INVOCATIONID": invocationid,
780 "NETBIOSNAME": names.netbiosname,
781 "DEFAULTSITE": names.sitename,
782 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
783 "MACHINEPASS_B64": b64encode(machinepass),
784 "DNSPASS_B64": b64encode(dnspass),
785 "REALM": names.realm,
786 "DOMAIN": names.domain,
787 "DNSDOMAIN": names.dnsdomain,
788 "SAMBA_VERSION_STRING": version,
789 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
791 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
792 "POLICYGUID": policyguid,
793 "DNSDOMAIN": names.dnsdomain,
794 "DOMAINSID": str(domainsid),
795 "DOMAINDN": names.domaindn})
797 # Setup fSMORoleOwner entries to point at the newly created DC entry
798 setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
799 "DOMAINDN": names.domaindn,
800 "CONFIGDN": names.configdn,
801 "SCHEMADN": names.schemadn,
802 "DEFAULTSITE": names.sitename,
803 "SERVERDN": names.serverdn
807 def setup_samdb(path, setup_path, session_info, credentials, lp,
809 domainsid, domainguid, policyguid,
810 fill, adminpass, krbtgtpass,
811 machinepass, invocationid, dnspass,
812 serverrole, schema=None, ldap_backend=None):
813 """Setup a complete SAM Database.
815 :note: This will wipe the main SAM database file!
818 domainFunctionality = DS_BEHAVIOR_WIN2008
819 forestFunctionality = DS_BEHAVIOR_WIN2008
820 domainControllerFunctionality = DS_BEHAVIOR_WIN2008
822 # Also wipes the database
823 setup_samdb_partitions(path, setup_path, message=message, lp=lp,
824 credentials=credentials, session_info=session_info,
826 ldap_backend=ldap_backend, serverrole=serverrole)
829 schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn)
831 # Load the database, but importantly, use Ldb not SamDB as we don't want to load the global schema
832 samdb = Ldb(session_info=session_info,
833 credentials=credentials, lp=lp)
835 message("Pre-loading the Samba 4 and AD schema")
837 # Load the schema from the one we computed earlier
838 samdb.set_schema_from_ldb(schema.ldb)
840 # And now we can connect to the DB - the schema won't be loaded from the DB
845 samdb.transaction_start()
847 message("Erasing data from partitions")
848 # Load the schema (again). This time it will force a reindex,
849 # and will therefore make the erase_partitions() below
850 # computationally sane
851 samdb.set_schema_from_ldb(schema.ldb)
852 samdb.erase_partitions()
854 # Set the domain functionality levels onto the database.
855 # Various module (the password_hash module in particular) need
856 # to know what level of AD we are emulating.
858 # These will be fixed into the database via the database
859 # modifictions below, but we need them set from the start.
860 samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
861 samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
862 samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
864 samdb.set_domain_sid(str(domainsid))
865 if serverrole == "domain controller":
866 samdb.set_invocation_id(invocationid)
868 message("Adding DomainDN: %s" % names.domaindn)
869 if serverrole == "domain controller":
870 domain_oc = "domainDNS"
872 domain_oc = "samba4LocalDomain"
874 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
875 "DOMAINDN": names.domaindn,
876 "DOMAIN_OC": domain_oc
879 message("Modifying DomainDN: " + names.domaindn + "")
880 if domainguid is not None:
881 domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
885 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
886 "LDAPTIME": timestring(int(time.time())),
887 "DOMAINSID": str(domainsid),
888 "SCHEMADN": names.schemadn,
889 "NETBIOSNAME": names.netbiosname,
890 "DEFAULTSITE": names.sitename,
891 "CONFIGDN": names.configdn,
892 "SERVERDN": names.serverdn,
893 "POLICYGUID": policyguid,
894 "DOMAINDN": names.domaindn,
895 "DOMAINGUID_MOD": domainguid_mod,
896 "DOMAIN_FUNCTIONALITY": str(domainFunctionality)
899 message("Adding configuration container")
900 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
901 "CONFIGDN": names.configdn,
903 message("Modifying configuration container")
904 setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
905 "CONFIGDN": names.configdn,
906 "SCHEMADN": names.schemadn,
909 # The LDIF here was created when the Schema object was constructed
910 message("Setting up sam.ldb schema")
911 samdb.add_ldif(schema.schema_dn_add)
912 samdb.modify_ldif(schema.schema_dn_modify)
913 samdb.add_ldif(schema.schema_data)
914 setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
915 {"SCHEMADN": names.schemadn})
917 message("Setting up sam.ldb configuration data")
918 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
919 "CONFIGDN": names.configdn,
920 "NETBIOSNAME": names.netbiosname,
921 "DEFAULTSITE": names.sitename,
922 "DNSDOMAIN": names.dnsdomain,
923 "DOMAIN": names.domain,
924 "SCHEMADN": names.schemadn,
925 "DOMAINDN": names.domaindn,
926 "SERVERDN": names.serverdn,
927 "FOREST_FUNCTIONALALITY": str(forestFunctionality)
930 message("Setting up display specifiers")
931 setup_add_ldif(samdb, setup_path("display_specifiers.ldif"),
932 {"CONFIGDN": names.configdn})
934 message("Adding users container")
935 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
936 "DOMAINDN": names.domaindn})
937 message("Modifying users container")
938 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
939 "DOMAINDN": names.domaindn})
940 message("Adding computers container")
941 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
942 "DOMAINDN": names.domaindn})
943 message("Modifying computers container")
944 setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
945 "DOMAINDN": names.domaindn})
946 message("Setting up sam.ldb data")
947 setup_add_ldif(samdb, setup_path("provision.ldif"), {
948 "DOMAINDN": names.domaindn,
949 "NETBIOSNAME": names.netbiosname,
950 "DEFAULTSITE": names.sitename,
951 "CONFIGDN": names.configdn,
952 "SERVERDN": names.serverdn
955 if fill == FILL_FULL:
956 message("Setting up sam.ldb users and groups")
957 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
958 "DOMAINDN": names.domaindn,
959 "DOMAINSID": str(domainsid),
960 "CONFIGDN": names.configdn,
961 "ADMINPASS_B64": b64encode(adminpass),
962 "KRBTGTPASS_B64": b64encode(krbtgtpass),
965 if serverrole == "domain controller":
966 message("Setting up self join")
967 setup_self_join(samdb, names=names, invocationid=invocationid,
969 machinepass=machinepass,
970 domainsid=domainsid, policyguid=policyguid,
971 setup_path=setup_path, domainControllerFunctionality=domainControllerFunctionality)
974 samdb.transaction_cancel()
977 samdb.transaction_commit()
982 FILL_NT4SYNC = "NT4SYNC"
986 def provision(setup_dir, message, session_info,
987 credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL, realm=None,
988 rootdn=None, domaindn=None, schemadn=None, configdn=None,
990 domain=None, hostname=None, hostip=None, hostip6=None,
991 domainsid=None, adminpass=None, ldapadminpass=None,
992 krbtgtpass=None, domainguid=None,
993 policyguid=None, invocationid=None, machinepass=None,
994 dnspass=None, root=None, nobody=None, users=None,
995 wheel=None, backup=None, aci=None, serverrole=None,
996 ldap_backend_extra_port=None, ldap_backend_type=None, sitename=None,
997 ol_mmr_urls=None, ol_olc=None,
998 setup_ds_path=None, slapd_path=None, nosync=False,
999 ldap_dryrun_mode=False):
1002 :note: caution, this wipes all existing data!
1005 def setup_path(file):
1006 return os.path.join(setup_dir, file)
1008 if domainsid is None:
1009 domainsid = security.random_sid()
1011 if policyguid is None:
1012 policyguid = str(uuid.uuid4())
1013 if adminpass is None:
1014 adminpass = glue.generate_random_str(12)
1015 if krbtgtpass is None:
1016 krbtgtpass = glue.generate_random_str(12)
1017 if machinepass is None:
1018 machinepass = glue.generate_random_str(12)
1020 dnspass = glue.generate_random_str(12)
1021 if ldapadminpass is None:
1022 #Make a new, random password between Samba and it's LDAP server
1023 ldapadminpass=glue.generate_random_str(12)
1026 root_uid = findnss_uid([root or "root"])
1027 nobody_uid = findnss_uid([nobody or "nobody"])
1028 users_gid = findnss_gid([users or "users"])
1030 wheel_gid = findnss_gid(["wheel", "adm"])
1032 wheel_gid = findnss_gid([wheel])
1034 if targetdir is not None:
1035 if (not os.path.exists(os.path.join(targetdir, "etc"))):
1036 os.makedirs(os.path.join(targetdir, "etc"))
1037 smbconf = os.path.join(targetdir, "etc", "smb.conf")
1038 elif smbconf is None:
1039 smbconf = param.default_path()
1041 # only install a new smb.conf if there isn't one there already
1042 if not os.path.exists(smbconf):
1043 make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
1046 lp = param.LoadParm()
1049 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1050 dnsdomain=realm, serverrole=serverrole, sitename=sitename,
1051 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1054 paths = provision_paths_from_lp(lp, names.dnsdomain)
1058 hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1059 except socket.gaierror, (socket.EAI_NODATA, msg):
1064 hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1065 except socket.gaierror, (socket.EAI_NODATA, msg):
1068 if serverrole is None:
1069 serverrole = lp.get("server role")
1071 assert serverrole in ("domain controller", "member server", "standalone")
1072 if invocationid is None and serverrole == "domain controller":
1073 invocationid = str(uuid.uuid4())
1075 if not os.path.exists(paths.private_dir):
1076 os.mkdir(paths.private_dir)
1078 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1080 schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn)
1082 provision_backend = None
1083 if ldap_backend_type:
1084 # We only support an LDAP backend over ldapi://
1086 provision_backend = ProvisionBackend(paths=paths, setup_path=setup_path, lp=lp, credentials=credentials,
1088 message=message, hostname=hostname,
1089 root=root, schema=schema, ldap_backend_type=ldap_backend_type,
1090 ldapadminpass=ldapadminpass,
1091 ldap_backend_extra_port=ldap_backend_extra_port,
1092 ol_mmr_urls=ol_mmr_urls,
1093 slapd_path=slapd_path,
1094 setup_ds_path=setup_ds_path,
1095 ldap_dryrun_mode=ldap_dryrun_mode)
1097 # Now use the backend credentials to access the databases
1098 credentials = provision_backend.credentials
1100 # only install a new shares config db if there is none
1101 if not os.path.exists(paths.shareconf):
1102 message("Setting up share.ldb")
1103 share_ldb = Ldb(paths.shareconf, session_info=session_info,
1104 credentials=credentials, lp=lp)
1105 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1108 message("Setting up secrets.ldb")
1109 secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
1110 session_info=session_info,
1111 credentials=credentials, lp=lp)
1113 message("Setting up the registry")
1114 setup_registry(paths.hklm, setup_path, session_info,
1117 message("Setting up templates db")
1118 setup_templatesdb(paths.templates, setup_path, session_info=session_info,
1121 message("Setting up idmap db")
1122 idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1125 message("Setting up SAM db")
1126 samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info,
1127 credentials=credentials, lp=lp, names=names,
1129 domainsid=domainsid,
1130 schema=schema, domainguid=domainguid, policyguid=policyguid,
1132 adminpass=adminpass, krbtgtpass=krbtgtpass,
1133 invocationid=invocationid,
1134 machinepass=machinepass, dnspass=dnspass,
1135 serverrole=serverrole, ldap_backend=provision_backend)
1137 if serverrole == "domain controller":
1138 if paths.netlogon is None:
1139 message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1140 message("Please either remove %s or see the template at %s" %
1141 ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1142 assert(paths.netlogon is not None)
1144 if paths.sysvol is None:
1145 message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1146 message("Please either remove %s or see the template at %s" %
1147 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1148 assert(paths.sysvol is not None)
1150 policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1151 "{" + policyguid + "}")
1152 os.makedirs(policy_path, 0755)
1153 open(os.path.join(policy_path, "GPT.INI"), 'w').write("")
1154 os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1155 os.makedirs(os.path.join(policy_path, "User"), 0755)
1156 if not os.path.isdir(paths.netlogon):
1157 os.makedirs(paths.netlogon, 0755)
1159 if samdb_fill == FILL_FULL:
1160 setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1161 root_uid=root_uid, nobody_uid=nobody_uid,
1162 users_gid=users_gid, wheel_gid=wheel_gid)
1164 message("Setting up sam.ldb rootDSE marking as synchronized")
1165 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1167 # Only make a zone file on the first DC, it should be replicated with DNS replication
1168 if serverrole == "domain controller":
1169 secrets_ldb = Ldb(paths.secrets, session_info=session_info,
1170 credentials=credentials, lp=lp)
1171 secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1172 netbiosname=names.netbiosname, domainsid=domainsid,
1173 keytab_path=paths.keytab, samdb_url=paths.samdb,
1174 dns_keytab_path=paths.dns_keytab, dnspass=dnspass,
1175 machinepass=machinepass, dnsdomain=names.dnsdomain)
1177 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1178 assert isinstance(domainguid, str)
1179 hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1180 expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1181 scope=SCOPE_SUBTREE)
1182 assert isinstance(hostguid, str)
1184 create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1185 domaindn=names.domaindn, hostip=hostip,
1186 hostip6=hostip6, hostname=names.hostname,
1187 dnspass=dnspass, realm=names.realm,
1188 domainguid=domainguid, hostguid=hostguid)
1190 create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1191 dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1193 create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1194 dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1195 keytab_name=paths.dns_keytab)
1196 message("See %s for an example configuration include file for BIND" % paths.namedconf)
1197 message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1199 create_krb5_conf(paths.krb5conf, setup_path, dnsdomain=names.dnsdomain,
1200 hostname=names.hostname, realm=names.realm)
1201 message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1204 # if backend is openldap, terminate slapd after final provision and check its proper termination
1205 if provision_backend is not None and provision_backend.slapd is not None:
1206 if provision_backend.slapd.poll() is None:
1208 if hasattr(provision_backend.slapd, "terminate"):
1209 provision_backend.slapd.terminate()
1212 os.kill(provision_backend.slapd.pid, signal.SIGTERM)
1214 #and now wait for it to die
1215 provision_backend.slapd.communicate()
1217 # now display slapd_command_file.txt to show how slapd must be started next time
1218 message("Use later the following commandline to start slapd, then Samba:")
1219 slapd_command = "\'" + "\' \'".join(provision_backend.slapd_command) + "\'"
1220 message(slapd_command)
1221 message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
1223 setup_file(setup_path("ldap_backend_startup.sh"), paths.ldapdir + "/ldap_backend_startup.sh", {
1224 "SLAPD_COMMAND" : slapd_command})
1227 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1230 message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1232 message("Once the above files are installed, your Samba4 server will be ready to use")
1233 message("Server Role: %s" % serverrole)
1234 message("Hostname: %s" % names.hostname)
1235 message("NetBIOS Domain: %s" % names.domain)
1236 message("DNS Domain: %s" % names.dnsdomain)
1237 message("DOMAIN SID: %s" % str(domainsid))
1238 if samdb_fill == FILL_FULL:
1239 message("Admin password: %s" % adminpass)
1240 if provision_backend:
1241 if provision_backend.credentials.get_bind_dn() is not None:
1242 message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1244 message("LDAP Admin User: %s" % provision_backend.credentials.get_username())
1246 message("LDAP Admin Password: %s" % provision_backend.credentials.get_password())
1248 result = ProvisionResult()
1249 result.domaindn = domaindn
1250 result.paths = paths
1252 result.samdb = samdb
1257 def provision_become_dc(setup_dir=None,
1258 smbconf=None, targetdir=None, realm=None,
1259 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1261 domain=None, hostname=None, domainsid=None,
1262 adminpass=None, krbtgtpass=None, domainguid=None,
1263 policyguid=None, invocationid=None, machinepass=None,
1264 dnspass=None, root=None, nobody=None, users=None,
1265 wheel=None, backup=None, serverrole=None,
1266 ldap_backend=None, ldap_backend_type=None, sitename=None):
1269 """print a message if quiet is not set."""
1272 return provision(setup_dir, message, system_session(), None,
1273 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm,
1274 rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn, serverdn=serverdn,
1275 domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename)
1278 def setup_db_config(setup_path, dbdir):
1279 """Setup a Berkeley database.
1281 :param setup_path: Setup path function.
1282 :param dbdir: Database directory."""
1283 if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1284 os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1285 if not os.path.isdir(os.path.join(dbdir, "tmp")):
1286 os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1288 setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1289 {"LDAPDBDIR": dbdir})
1291 class ProvisionBackend(object):
1292 def __init__(self, paths=None, setup_path=None, lp=None, credentials=None,
1293 names=None, message=None,
1294 hostname=None, root=None,
1295 schema=None, ldapadminpass=None,
1296 ldap_backend_type=None, ldap_backend_extra_port=None,
1298 setup_ds_path=None, slapd_path=None,
1299 nosync=False, ldap_dryrun_mode=False):
1300 """Provision an LDAP backend for samba4
1302 This works for OpenLDAP and Fedora DS
1305 self.ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.ldapdir, "ldapi"), safe="")
1307 if not os.path.isdir(paths.ldapdir):
1308 os.makedirs(paths.ldapdir, 0700)
1310 if ldap_backend_type == "existing":
1311 #Check to see that this 'existing' LDAP backend in fact exists
1312 ldapi_db = Ldb(self.ldapi_uri, credentials=credentials)
1313 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1314 expression="(objectClass=OpenLDAProotDSE)")
1316 # If we have got here, then we must have a valid connection to the LDAP server, with valid credentials supplied
1317 # This caused them to be set into the long-term database later in the script.
1318 self.credentials = credentials
1319 self.ldap_backend_type = "openldap" #For now, assume existing backends at least emulate OpenLDAP
1322 # we will shortly start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via self.ldapi_uri
1323 # if another instance of slapd is already running
1325 ldapi_db = Ldb(self.ldapi_uri)
1326 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1327 expression="(objectClass=OpenLDAProotDSE)");
1329 f = open(paths.slapdpid, "r")
1332 message("Check for slapd Process with PID: " + str(p) + " and terminate it manually.")
1336 raise("Warning: Another slapd Instance seems already running on this host, listening to " + self.ldapi_uri + ". Please shut it down before you continue. ")
1341 # Try to print helpful messages when the user has not specified the path to slapd
1342 if slapd_path is None:
1343 raise("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
1344 if not os.path.exists(slapd_path):
1345 message (slapd_path)
1346 raise("Warning: Given Path to slapd does not exist!")
1348 schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1350 os.unlink(schemadb_path)
1355 # Put the LDIF of the schema into a database so we can search on
1356 # it to generate schema-dependent configurations in Fedora DS and
1358 os.path.join(paths.ldapdir, "schema-tmp.ldb")
1359 schema.ldb.connect(schemadb_path)
1360 schema.ldb.transaction_start()
1362 # These bits of LDIF are supplied when the Schema object is created
1363 schema.ldb.add_ldif(schema.schema_dn_add)
1364 schema.ldb.modify_ldif(schema.schema_dn_modify)
1365 schema.ldb.add_ldif(schema.schema_data)
1366 schema.ldb.transaction_commit()
1368 self.credentials = Credentials()
1369 self.credentials.guess(lp)
1370 #Kerberos to an ldapi:// backend makes no sense
1371 self.credentials.set_kerberos_state(DONT_USE_KERBEROS)
1372 self.ldap_backend_type = ldap_backend_type
1374 if ldap_backend_type == "fedora-ds":
1375 provision_fds_backend(self, paths=paths, setup_path=setup_path, names=names, message=message,
1376 hostname=hostname, ldapadminpass=ldapadminpass, root=root,
1377 schema=schema, ldap_backend_extra_port=ldap_backend_extra_port,
1378 setup_ds_path=setup_ds_path, slapd_path=slapd_path,
1379 nosync=nosync, ldap_dryrun_mode=ldap_dryrun_mode)
1381 elif ldap_backend_type == "openldap":
1382 provision_openldap_backend(self, paths=paths, setup_path=setup_path, names=names, message=message,
1383 hostname=hostname, ldapadminpass=ldapadminpass, root=root,
1384 schema=schema, ldap_backend_extra_port=ldap_backend_extra_port,
1385 ol_mmr_urls=ol_mmr_urls,
1386 slapd_path=slapd_path,
1387 nosync=nosync, ldap_dryrun_mode=ldap_dryrun_mode)
1389 raise("Unknown LDAP backend type selected")
1391 self.credentials.set_password(ldapadminpass)
1393 # Now start the slapd, so we can provision onto it. We keep the
1394 # subprocess context around, to kill this off at the successful
1396 self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False)
1398 while self.slapd.poll() is None:
1399 # Wait until the socket appears
1401 ldapi_db = Ldb(self.ldapi_uri, lp=lp, credentials=self.credentials)
1402 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1403 expression="(objectClass=OpenLDAProotDSE)")
1404 # If we have got here, then we must have a valid connection to the LDAP server!
1410 raise "slapd died before we could make a connection to it"
1413 def provision_openldap_backend(result, paths=None, setup_path=None, names=None, message=None,
1414 hostname=None, ldapadminpass=None, root=None,
1416 ldap_backend_extra_port=None,
1418 slapd_path=None, nosync=False,
1419 ldap_dryrun_mode=False):
1421 #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
1424 nosync_config = "dbnosync"
1427 attrs = ["linkID", "lDAPDisplayName"]
1428 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)
1430 memberof_config = "# Generated from Samba4 schema\n"
1431 refint_attributes = ""
1432 for i in range (0, len(res)):
1433 expression = "(&(objectclass=attributeSchema)(linkID=%d)(attributeSyntax=2.5.5.1))" % (int(res[i]["linkID"][0])+1)
1434 target = schema.ldb.searchone(basedn=names.schemadn,
1435 expression=expression,
1436 attribute="lDAPDisplayName",
1437 scope=SCOPE_SUBTREE)
1438 if target is not None:
1439 refint_attributes = refint_attributes + " " + res[i]["lDAPDisplayName"][0]
1441 memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1442 { "MEMBER_ATTR" : str(res[i]["lDAPDisplayName"][0]),
1443 "MEMBEROF_ATTR" : str(target) })
1445 refint_config = read_and_sub_file(setup_path("refint.conf"),
1446 { "LINK_ATTRS" : refint_attributes})
1448 res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1450 for i in range (0, len(res)):
1451 index_attr = res[i]["lDAPDisplayName"][0]
1452 if index_attr == "objectGUID":
1453 index_attr = "entryUUID"
1455 index_config += "index " + index_attr + " eq\n"
1457 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1459 mmr_replicator_acl = ""
1460 mmr_serverids_config = ""
1461 mmr_syncrepl_schema_config = ""
1462 mmr_syncrepl_config_config = ""
1463 mmr_syncrepl_user_config = ""
1466 if ol_mmr_urls is not None:
1467 # For now, make these equal
1468 mmr_pass = ldapadminpass
1470 url_list=filter(None,ol_mmr_urls.split(' '))
1471 if (len(url_list) == 1):
1472 url_list=filter(None,ol_mmr_urls.split(','))
1475 mmr_on_config = "MirrorMode On"
1476 mmr_replicator_acl = " by dn=cn=replicator,cn=samba read"
1478 for url in url_list:
1480 mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1481 { "SERVERID" : str(serverid),
1482 "LDAPSERVER" : url })
1485 mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1487 "MMRDN": names.schemadn,
1489 "MMR_PASSWORD": mmr_pass})
1492 mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1494 "MMRDN": names.configdn,
1496 "MMR_PASSWORD": mmr_pass})
1499 mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1501 "MMRDN": names.domaindn,
1503 "MMR_PASSWORD": mmr_pass })
1504 # OpenLDAP cn=config initialisation
1505 olc_syncrepl_config = ""
1507 # if mmr = yes, generate cn=config-replication directives
1508 # and olc_seed.lif for the other mmr-servers
1509 if ol_mmr_urls is not None:
1511 olc_serverids_config = ""
1512 olc_syncrepl_seed_config = ""
1513 olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
1515 for url in url_list:
1517 olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
1518 { "SERVERID" : str(serverid),
1519 "LDAPSERVER" : url })
1522 olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
1525 "MMR_PASSWORD": mmr_pass})
1527 olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1529 "LDAPSERVER" : url})
1531 setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif,
1532 {"OLC_SERVER_ID_CONF": olc_serverids_config,
1533 "OLC_PW": ldapadminpass,
1534 "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
1537 setup_file(setup_path("slapd.conf"), paths.slapdconf,
1538 {"DNSDOMAIN": names.dnsdomain,
1539 "LDAPDIR": paths.ldapdir,
1540 "DOMAINDN": names.domaindn,
1541 "CONFIGDN": names.configdn,
1542 "SCHEMADN": names.schemadn,
1543 "MEMBEROF_CONFIG": memberof_config,
1544 "MIRRORMODE": mmr_on_config,
1545 "REPLICATOR_ACL": mmr_replicator_acl,
1546 "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1547 "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1548 "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1549 "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1550 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
1551 "OLC_MMR_CONFIG": olc_mmr_config,
1552 "REFINT_CONFIG": refint_config,
1553 "INDEX_CONFIG": index_config,
1554 "NOSYNC": nosync_config})
1556 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1557 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1558 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1560 if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba", "cn=samba")):
1561 os.makedirs(os.path.join(paths.ldapdir, "db", "samba", "cn=samba"), 0700)
1563 setup_file(setup_path("cn=samba.ldif"),
1564 os.path.join(paths.ldapdir, "db", "samba", "cn=samba.ldif"),
1565 { "UUID": str(uuid.uuid4()),
1566 "LDAPTIME": timestring(int(time.time()))} )
1567 setup_file(setup_path("cn=samba-admin.ldif"),
1568 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=samba-admin.ldif"),
1569 {"LDAPADMINPASS_B64": b64encode(ldapadminpass),
1570 "UUID": str(uuid.uuid4()),
1571 "LDAPTIME": timestring(int(time.time()))} )
1573 if ol_mmr_urls is not None:
1574 setup_file(setup_path("cn=replicator.ldif"),
1575 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=replicator.ldif"),
1576 {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1577 "UUID": str(uuid.uuid4()),
1578 "LDAPTIME": timestring(int(time.time()))} )
1581 mapping = "schema-map-openldap-2.3"
1582 backend_schema = "backend-schema.schema"
1584 backend_schema_data = schema.ldb.convert_schema_to_openldap("openldap", open(setup_path(mapping), 'r').read())
1585 assert backend_schema_data is not None
1586 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1588 # now we generate the needed strings to start slapd automatically,
1589 # first ldapi_uri...
1590 if ldap_backend_extra_port is not None:
1591 # When we use MMR, we can't use 0.0.0.0 as it uses the name
1592 # specified there as part of it's clue as to it's own name,
1593 # and not to replicate to itself
1594 if ol_mmr_urls is None:
1595 server_port_string = "ldap://0.0.0.0:%d" % ldap_backend_extra_port
1597 server_port_string = "ldap://" + names.hostname + "." + names.dnsdomain +":%d" % ldap_backend_extra_port
1599 server_port_string = ""
1601 # Prepare the 'result' information - the commands to return in particular
1602 result.slapd_provision_command = [slapd_path]
1604 result.slapd_provision_command.append("-F" + paths.olcdir)
1606 result.slapd_provision_command.append("-h")
1608 # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands
1609 result.slapd_command = list(result.slapd_provision_command)
1611 result.slapd_provision_command.append(result.ldapi_uri)
1612 result.slapd_provision_command.append("-d0")
1614 uris = result.ldapi_uri
1615 if server_port_string is not "":
1616 uris = uris + " " + server_port_string
1618 result.slapd_command.append(uris)
1620 # Set the username - done here because Fedora DS still uses the admin DN and simple bind
1621 result.credentials.set_username("samba-admin")
1623 # If we were just looking for crashes up to this point, it's a
1624 # good time to exit before we realise we don't have OpenLDAP on
1626 if ldap_dryrun_mode:
1629 # Finally, convert the configuration into cn=config style!
1630 if not os.path.isdir(paths.olcdir):
1631 os.makedirs(paths.olcdir, 0770)
1633 retcode = subprocess.call([slapd_path, "-Ttest", "-f", paths.slapdconf, "-F", paths.olcdir], close_fds=True, shell=False)
1635 # We can't do this, as OpenLDAP is strange. It gives an error
1636 # output to the above, but does the conversion sucessfully...
1639 # raise("conversion from slapd.conf to cn=config failed")
1641 if not os.path.exists(os.path.join(paths.olcdir, "cn=config.ldif")):
1642 raise("conversion from slapd.conf to cn=config failed")
1644 # Don't confuse the admin by leaving the slapd.conf around
1645 os.remove(paths.slapdconf)
1648 def provision_fds_backend(result, paths=None, setup_path=None, names=None, message=None,
1649 hostname=None, ldapadminpass=None, root=None,
1651 ldap_backend_extra_port=None,
1655 ldap_dryrun_mode=False):
1657 if ldap_backend_extra_port is not None:
1658 serverport = "ServerPort=%d" % ldap_backend_extra_port
1662 setup_file(setup_path("fedorads.inf"), paths.fedoradsinf,
1664 "HOSTNAME": hostname,
1665 "DNSDOMAIN": names.dnsdomain,
1666 "LDAPDIR": paths.ldapdir,
1667 "DOMAINDN": names.domaindn,
1668 "LDAPMANAGERDN": names.ldapmanagerdn,
1669 "LDAPMANAGERPASS": ldapadminpass,
1670 "SERVERPORT": serverport})
1672 setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions,
1673 {"CONFIGDN": names.configdn,
1674 "SCHEMADN": names.schemadn,
1677 mapping = "schema-map-fedora-ds-1.0"
1678 backend_schema = "99_ad.ldif"
1680 # Build a schema file in Fedora DS format
1681 backend_schema_data = schema.ldb.convert_schema_to_openldap("fedora-ds", open(setup_path(mapping), 'r').read())
1682 assert backend_schema_data is not None
1683 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1685 result.credentials.set_bind_dn(names.ldapmanagerdn)
1687 # Destory the target directory, or else setup-ds.pl will complain
1688 fedora_ds_dir = os.path.join(paths.ldapdir, "slapd-samba4")
1689 shutil.rmtree(fedora_ds_dir, True)
1691 result.slapd_provision_command = [slapd_path, "-D", fedora_ds_dir, "-i", paths.slapdpid];
1692 #In the 'provision' command line, stay in the foreground so we can easily kill it
1693 result.slapd_provision_command.append("-d0")
1695 #the command for the final run is the normal script
1696 result.slapd_command = [os.path.join(paths.ldapdir, "slapd-samba4", "start-slapd")]
1698 # If we were just looking for crashes up to this point, it's a
1699 # good time to exit before we realise we don't have Fedora DS on
1700 if ldap_dryrun_mode:
1703 # Try to print helpful messages when the user has not specified the path to the setup-ds tool
1704 if setup_ds_path is None:
1705 raise("Warning: Fedora DS LDAP-Backend must be setup with path to setup-ds, e.g. --setup-ds-path=\"/usr/sbin/setup-ds.pl\"!")
1706 if not os.path.exists(setup_ds_path):
1707 message (setup_ds_path)
1708 raise("Warning: Given Path to slapd does not exist!")
1710 # Run the Fedora DS setup utility
1711 retcode = subprocess.call([setup_ds_path, "--silent", "--file", paths.fedoradsinf], close_fds=True, shell=False)
1713 raise("setup-ds failed")
1715 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1716 """Create a PHP LDAP admin configuration file.
1718 :param path: Path to write the configuration to.
1719 :param setup_path: Function to generate setup paths.
1721 setup_file(setup_path("phpldapadmin-config.php"), path,
1722 {"S4_LDAPI_URI": ldapi_uri})
1725 def create_zone_file(path, setup_path, dnsdomain, domaindn,
1726 hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid):
1727 """Write out a DNS zone file, from the info in the current database.
1729 :param path: Path of the new zone file.
1730 :param setup_path: Setup path function.
1731 :param dnsdomain: DNS Domain name
1732 :param domaindn: DN of the Domain
1733 :param hostip: Local IPv4 IP
1734 :param hostip6: Local IPv6 IP
1735 :param hostname: Local hostname
1736 :param dnspass: Password for DNS
1737 :param realm: Realm name
1738 :param domainguid: GUID of the domain.
1739 :param hostguid: GUID of the host.
1741 assert isinstance(domainguid, str)
1743 if hostip6 is not None:
1744 hostip6_base_line = " IN AAAA " + hostip6
1745 hostip6_host_line = hostname + " IN AAAA " + hostip6
1747 hostip6_base_line = ""
1748 hostip6_host_line = ""
1750 if hostip is not None:
1751 hostip_base_line = " IN A " + hostip
1752 hostip_host_line = hostname + " IN A " + hostip
1754 hostip_base_line = ""
1755 hostip_host_line = ""
1757 setup_file(setup_path("provision.zone"), path, {
1758 "DNSPASS_B64": b64encode(dnspass),
1759 "HOSTNAME": hostname,
1760 "DNSDOMAIN": dnsdomain,
1762 "HOSTIP_BASE_LINE": hostip_base_line,
1763 "HOSTIP_HOST_LINE": hostip_host_line,
1764 "DOMAINGUID": domainguid,
1765 "DATESTRING": time.strftime("%Y%m%d%H"),
1766 "DEFAULTSITE": DEFAULTSITE,
1767 "HOSTGUID": hostguid,
1768 "HOSTIP6_BASE_LINE": hostip6_base_line,
1769 "HOSTIP6_HOST_LINE": hostip6_host_line,
1773 def create_named_conf(path, setup_path, realm, dnsdomain,
1775 """Write out a file containing zone statements suitable for inclusion in a
1776 named.conf file (including GSS-TSIG configuration).
1778 :param path: Path of the new named.conf file.
1779 :param setup_path: Setup path function.
1780 :param realm: Realm name
1781 :param dnsdomain: DNS Domain name
1782 :param private_dir: Path to private directory
1783 :param keytab_name: File name of DNS keytab file
1786 setup_file(setup_path("named.conf"), path, {
1787 "DNSDOMAIN": dnsdomain,
1789 "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1790 "PRIVATE_DIR": private_dir
1793 def create_named_txt(path, setup_path, realm, dnsdomain,
1794 private_dir, keytab_name):
1795 """Write out a file containing zone statements suitable for inclusion in a
1796 named.conf file (including GSS-TSIG configuration).
1798 :param path: Path of the new named.conf file.
1799 :param setup_path: Setup path function.
1800 :param realm: Realm name
1801 :param dnsdomain: DNS Domain name
1802 :param private_dir: Path to private directory
1803 :param keytab_name: File name of DNS keytab file
1806 setup_file(setup_path("named.txt"), path, {
1807 "DNSDOMAIN": dnsdomain,
1809 "DNS_KEYTAB": keytab_name,
1810 "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1811 "PRIVATE_DIR": private_dir
1814 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1815 """Write out a file containing zone statements suitable for inclusion in a
1816 named.conf file (including GSS-TSIG configuration).
1818 :param path: Path of the new named.conf file.
1819 :param setup_path: Setup path function.
1820 :param dnsdomain: DNS Domain name
1821 :param hostname: Local hostname
1822 :param realm: Realm name
1825 setup_file(setup_path("krb5.conf"), path, {
1826 "DNSDOMAIN": dnsdomain,
1827 "HOSTNAME": hostname,