2 # Unix SMB/CIFS implementation.
3 # backend code for provisioning a Samba4 server
5 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
6 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008-2009
7 # Copyright (C) Oliver Liebel <oliver@itc.li> 2008-2009
9 # Based on the original in EJS:
10 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
12 # This program is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 3 of the License, or
15 # (at your option) any later version.
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
22 # You should have received a copy of the GNU General Public License
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 """Functions for setting up a Samba configuration."""
28 from base64 import b64encode
42 from credentials import Credentials, DONT_USE_KERBEROS
43 from auth import system_session
44 from samba import version, Ldb, substitute_var, valid_netbios_name, check_all_substituted, \
46 from samba.samdb import SamDB
47 from samba.idmap import IDmapDB
48 from samba.dcerpc import security
50 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, timestring
51 from ms_schema import read_ms_schema
52 from signal import SIGTERM
54 __docformat__ = "restructuredText"
58 """Find the setup directory used by provision."""
59 dirname = os.path.dirname(__file__)
60 if "/site-packages/" in dirname:
61 prefix = "/".join(dirname[:dirname.index("/site-packages/")].split("/")[:-2])
62 for suffix in ["share/setup", "share/samba/setup", "setup"]:
63 ret = os.path.join(prefix, suffix)
64 if os.path.isdir(ret):
67 ret = os.path.join(dirname, "../../../setup")
68 if os.path.isdir(ret):
70 raise Exception("Unable to find setup directory.")
73 DEFAULTSITE = "Default-First-Site-Name"
75 class InvalidNetbiosName(Exception):
76 """A specified name was not a valid NetBIOS name."""
77 def __init__(self, name):
78 super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)
81 class ProvisionPaths(object):
94 self.dns_keytab = None
97 self.private_dir = None
100 self.modulesconf = None
101 self.memberofconf = None
102 self.fedoradsinf = None
103 self.fedoradspartitions = None
105 self.olmmrserveridsconf = None
106 self.olmmrsyncreplconf = None
109 self.olcseedldif = None
112 class ProvisionNames(object):
118 self.ldapmanagerdn = None
119 self.dnsdomain = None
121 self.netbiosname = None
128 class ProvisionResult(object):
135 class Schema(object):
136 def __init__(self, setup_path, schemadn=None,
138 """Load schema for the SamDB from the AD schema files and samba4_schema.ldif
140 :param samdb: Load a schema into a SamDB.
141 :param setup_path: Setup path function.
142 :param schemadn: DN of the schema
143 :param serverdn: DN of the server
145 Returns the schema data loaded, to avoid double-parsing when then needing to add it to the db
149 self.schema_data = read_ms_schema(setup_path('ad-schema/MS-AD_Schema_2K8_Attributes.txt'),
150 setup_path('ad-schema/MS-AD_Schema_2K8_Classes.txt'))
151 self.schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
152 self.schema_data = substitute_var(self.schema_data, {"SCHEMADN": schemadn})
153 check_all_substituted(self.schema_data)
155 self.schema_dn_modify = read_and_sub_file(setup_path("provision_schema_basedn_modify.ldif"),
156 {"SCHEMADN": schemadn,
157 "SERVERDN": serverdn,
159 self.schema_dn_add = read_and_sub_file(setup_path("provision_schema_basedn.ldif"),
160 {"SCHEMADN": schemadn
163 prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
164 prefixmap = b64encode(prefixmap)
166 # We don't actually add this ldif, just parse it
167 prefixmap_ldif = "dn: cn=schema\nprefixMap:: %s\n\n" % prefixmap
168 self.ldb.set_schema_from_ldif(prefixmap_ldif, self.schema_data)
171 def check_install(lp, session_info, credentials):
172 """Check whether the current install seems ok.
174 :param lp: Loadparm context
175 :param session_info: Session information
176 :param credentials: Credentials
178 if lp.get("realm") == "":
179 raise Exception("Realm empty")
180 ldb = Ldb(lp.get("sam database"), session_info=session_info,
181 credentials=credentials, lp=lp)
182 if len(ldb.search("(cn=Administrator)")) != 1:
183 raise "No administrator account found"
186 def findnss(nssfn, names):
187 """Find a user or group from a list of possibilities.
189 :param nssfn: NSS Function to try (should raise KeyError if not found)
190 :param names: Names to check.
191 :return: Value return by first names list.
198 raise KeyError("Unable to find user/group %r" % names)
201 findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
202 findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
205 def read_and_sub_file(file, subst_vars):
206 """Read a file and sub in variables found in it
208 :param file: File to be read (typically from setup directory)
209 param subst_vars: Optional variables to subsitute in the file.
211 data = open(file, 'r').read()
212 if subst_vars is not None:
213 data = substitute_var(data, subst_vars)
214 check_all_substituted(data)
218 def setup_add_ldif(ldb, ldif_path, subst_vars=None):
219 """Setup a ldb in the private dir.
221 :param ldb: LDB file to import data into
222 :param ldif_path: Path of the LDIF file to load
223 :param subst_vars: Optional variables to subsitute in LDIF.
225 assert isinstance(ldif_path, str)
227 data = read_and_sub_file(ldif_path, subst_vars)
231 def setup_modify_ldif(ldb, ldif_path, subst_vars=None):
232 """Modify a ldb in the private dir.
234 :param ldb: LDB object.
235 :param ldif_path: LDIF file path.
236 :param subst_vars: Optional dictionary with substitution variables.
238 data = read_and_sub_file(ldif_path, subst_vars)
240 ldb.modify_ldif(data)
243 def setup_ldb(ldb, ldif_path, subst_vars):
244 """Import a LDIF a file into a LDB handle, optionally substituting variables.
246 :note: Either all LDIF data will be added or none (using transactions).
248 :param ldb: LDB file to import into.
249 :param ldif_path: Path to the LDIF file.
250 :param subst_vars: Dictionary with substitution variables.
252 assert ldb is not None
253 ldb.transaction_start()
255 setup_add_ldif(ldb, ldif_path, subst_vars)
257 ldb.transaction_cancel()
259 ldb.transaction_commit()
262 def setup_file(template, fname, subst_vars):
263 """Setup a file in the private dir.
265 :param template: Path of the template file.
266 :param fname: Path of the file to create.
267 :param subst_vars: Substitution variables.
271 if os.path.exists(f):
274 data = read_and_sub_file(template, subst_vars)
275 open(f, 'w').write(data)
278 def provision_paths_from_lp(lp, dnsdomain):
279 """Set the default paths for provisioning.
281 :param lp: Loadparm context.
282 :param dnsdomain: DNS Domain name
284 paths = ProvisionPaths()
285 paths.private_dir = lp.get("private dir")
286 paths.keytab = "secrets.keytab"
287 paths.dns_keytab = "dns.keytab"
289 paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
290 paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb")
291 paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb")
292 paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
293 paths.templates = os.path.join(paths.private_dir, "templates.ldb")
294 paths.dns = os.path.join(paths.private_dir, dnsdomain + ".zone")
295 paths.namedconf = os.path.join(paths.private_dir, "named.conf")
296 paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
297 paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
298 paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
299 paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
300 paths.phpldapadminconfig = os.path.join(paths.private_dir,
301 "phpldapadmin-config.php")
302 paths.ldapdir = os.path.join(paths.private_dir,
304 paths.slapdconf = os.path.join(paths.ldapdir,
306 paths.slapdpid = os.path.join(paths.ldapdir,
308 paths.modulesconf = os.path.join(paths.ldapdir,
310 paths.memberofconf = os.path.join(paths.ldapdir,
312 paths.fedoradsinf = os.path.join(paths.ldapdir,
314 paths.fedoradspartitions = os.path.join(paths.ldapdir,
315 "fedorads-partitions.ldif")
316 paths.olmmrserveridsconf = os.path.join(paths.ldapdir,
317 "mmr_serverids.conf")
318 paths.olmmrsyncreplconf = os.path.join(paths.ldapdir,
320 paths.olcdir = os.path.join(paths.ldapdir,
322 paths.olcseedldif = os.path.join(paths.ldapdir,
324 paths.hklm = "hklm.ldb"
325 paths.hkcr = "hkcr.ldb"
326 paths.hkcu = "hkcu.ldb"
327 paths.hku = "hku.ldb"
328 paths.hkpd = "hkpd.ldb"
329 paths.hkpt = "hkpt.ldb"
331 paths.sysvol = lp.get("path", "sysvol")
333 paths.netlogon = lp.get("path", "netlogon")
335 paths.smbconf = lp.configfile
340 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, serverrole=None,
341 rootdn=None, domaindn=None, configdn=None, schemadn=None, serverdn=None,
343 """Guess configuration settings to use."""
346 hostname = socket.gethostname().split(".")[0].lower()
348 netbiosname = hostname.upper()
349 if not valid_netbios_name(netbiosname):
350 raise InvalidNetbiosName(netbiosname)
352 hostname = hostname.lower()
354 if dnsdomain is None:
355 dnsdomain = lp.get("realm")
357 if serverrole is None:
358 serverrole = lp.get("server role")
360 assert dnsdomain is not None
361 realm = dnsdomain.upper()
363 if lp.get("realm").upper() != realm:
364 raise Exception("realm '%s' in %s must match chosen realm '%s'" %
365 (lp.get("realm"), lp.configfile, realm))
367 dnsdomain = dnsdomain.lower()
369 if serverrole == "domain controller":
371 domain = lp.get("workgroup")
373 domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
374 if lp.get("workgroup").upper() != domain.upper():
375 raise Exception("workgroup '%s' in smb.conf must match chosen domain '%s'",
376 lp.get("workgroup"), domain)
380 domaindn = "CN=" + netbiosname
382 assert domain is not None
383 domain = domain.upper()
384 if not valid_netbios_name(domain):
385 raise InvalidNetbiosName(domain)
391 configdn = "CN=Configuration," + rootdn
393 schemadn = "CN=Schema," + configdn
398 names = ProvisionNames()
399 names.rootdn = rootdn
400 names.domaindn = domaindn
401 names.configdn = configdn
402 names.schemadn = schemadn
403 names.ldapmanagerdn = "CN=Manager," + rootdn
404 names.dnsdomain = dnsdomain
405 names.domain = domain
407 names.netbiosname = netbiosname
408 names.hostname = hostname
409 names.sitename = sitename
410 names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (netbiosname, sitename, configdn)
415 def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
417 """Create a new smb.conf file based on a couple of basic settings.
419 assert smbconf is not None
421 hostname = socket.gethostname().split(".")[0].lower()
423 if serverrole is None:
424 serverrole = "standalone"
426 assert serverrole in ("domain controller", "member server", "standalone")
427 if serverrole == "domain controller":
429 elif serverrole == "member server":
430 smbconfsuffix = "member"
431 elif serverrole == "standalone":
432 smbconfsuffix = "standalone"
434 assert domain is not None
435 assert realm is not None
437 default_lp = param.LoadParm()
438 #Load non-existant file
439 if os.path.exists(smbconf):
440 default_lp.load(smbconf)
442 if targetdir is not None:
443 privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
444 lockdir_line = "lock dir = " + os.path.abspath(targetdir)
446 default_lp.set("lock dir", os.path.abspath(targetdir))
451 sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
452 netlogon = os.path.join(sysvol, realm.lower(), "scripts")
454 setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
456 "HOSTNAME": hostname,
459 "SERVERROLE": serverrole,
460 "NETLOGONPATH": netlogon,
461 "SYSVOLPATH": sysvol,
462 "PRIVATEDIR_LINE": privatedir_line,
463 "LOCKDIR_LINE": lockdir_line
467 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
468 users_gid, wheel_gid):
469 """setup reasonable name mappings for sam names to unix names.
471 :param samdb: SamDB object.
472 :param idmap: IDmap db object.
473 :param sid: The domain sid.
474 :param domaindn: The domain DN.
475 :param root_uid: uid of the UNIX root user.
476 :param nobody_uid: uid of the UNIX nobody user.
477 :param users_gid: gid of the UNIX users group.
478 :param wheel_gid: gid of the UNIX wheel group."""
480 def add_foreign(self, domaindn, sid, desc):
481 """Add a foreign security principle."""
483 dn: CN=%s,CN=ForeignSecurityPrincipals,%s
485 objectClass: foreignSecurityPrincipal
487 """ % (sid, domaindn, desc)
488 # deliberately ignore errors from this, as the records may
490 for msg in self.parse_ldif(add):
493 add_foreign(samdb, domaindn, "S-1-5-7", "Anonymous")
494 add_foreign(samdb, domaindn, "S-1-1-0", "World")
495 add_foreign(samdb, domaindn, "S-1-5-2", "Network")
496 add_foreign(samdb, domaindn, "S-1-5-18", "System")
497 add_foreign(samdb, domaindn, "S-1-5-11", "Authenticated Users")
499 idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
500 idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
502 idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
503 idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
505 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
507 serverrole, ldap_backend=None,
509 """Setup the partitions for the SAM database.
511 Alternatively, provision() may call this, and then populate the database.
513 :note: This will wipe the Sam Database!
515 :note: This function always removes the local SAM LDB file. The erase
516 parameter controls whether to erase the existing data, which
517 may not be stored locally but in LDAP.
519 assert session_info is not None
521 # We use options=["modules:"] to stop the modules loading - we
522 # just want to wipe and re-initialise the database, not start it up
525 samdb = Ldb(url=samdb_path, session_info=session_info,
526 credentials=credentials, lp=lp, options=["modules:"])
528 samdb.erase_except_schema_controlled()
530 os.unlink(samdb_path)
531 samdb = Ldb(url=samdb_path, session_info=session_info,
532 credentials=credentials, lp=lp, options=["modules:"])
534 samdb.erase_except_schema_controlled()
537 #Add modules to the list to activate them by default
538 #beware often order is important
540 # Some Known ordering constraints:
541 # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
542 # - objectclass must be before password_hash, because password_hash checks
543 # that the objectclass is of type person (filled in by objectclass
544 # module when expanding the objectclass list)
545 # - partition must be last
546 # - each partition has its own module list then
547 modules_list = ["rootdse",
565 "extended_dn_out_ldb"]
566 modules_list2 = ["show_deleted",
569 domaindn_ldb = "users.ldb"
570 configdn_ldb = "configuration.ldb"
571 schemadn_ldb = "schema.ldb"
572 if ldap_backend is not None:
573 domaindn_ldb = ldap_backend.ldapi_uri
574 configdn_ldb = ldap_backend.ldapi_uri
575 schemadn_ldb = ldap_backend.ldapi_uri
577 if ldap_backend.ldap_backend_type == "fedora-ds":
578 backend_modules = ["nsuniqueid", "paged_searches"]
579 # We can handle linked attributes here, as we don't have directory-side subtree operations
580 tdb_modules_list = ["linked_attributes", "extended_dn_out_dereference"]
581 elif ldap_backend.ldap_backend_type == "openldap":
582 backend_modules = ["entryuuid", "paged_searches"]
583 # OpenLDAP handles subtree renames, so we don't want to do any of these things
584 tdb_modules_list = ["extended_dn_out_dereference"]
586 elif serverrole == "domain controller":
587 backend_modules = ["repl_meta_data"]
589 backend_modules = ["objectguid"]
591 if tdb_modules_list is None:
592 tdb_modules_list_as_string = ""
594 tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
596 samdb.transaction_start()
598 message("Setting up sam.ldb partitions and settings")
599 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
600 "SCHEMADN": names.schemadn,
601 "SCHEMADN_LDB": schemadn_ldb,
602 "SCHEMADN_MOD2": ",objectguid",
603 "CONFIGDN": names.configdn,
604 "CONFIGDN_LDB": configdn_ldb,
605 "DOMAINDN": names.domaindn,
606 "DOMAINDN_LDB": domaindn_ldb,
607 "SCHEMADN_MOD": "schema_fsmo,instancetype",
608 "CONFIGDN_MOD": "naming_fsmo,instancetype",
609 "DOMAINDN_MOD": "pdc_fsmo,instancetype",
610 "MODULES_LIST": ",".join(modules_list),
611 "TDB_MODULES_LIST": tdb_modules_list_as_string,
612 "MODULES_LIST2": ",".join(modules_list2),
613 "BACKEND_MOD": ",".join(backend_modules),
616 samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
618 message("Setting up sam.ldb rootDSE")
619 setup_samdb_rootdse(samdb, setup_path, names)
622 samdb.transaction_cancel()
625 samdb.transaction_commit()
629 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain,
630 netbiosname, domainsid, keytab_path, samdb_url,
631 dns_keytab_path, dnspass, machinepass):
632 """Add DC-specific bits to a secrets database.
634 :param secretsdb: Ldb Handle to the secrets database
635 :param setup_path: Setup path function
636 :param machinepass: Machine password
638 setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), {
639 "MACHINEPASS_B64": b64encode(machinepass),
642 "DNSDOMAIN": dnsdomain,
643 "DOMAINSID": str(domainsid),
644 "SECRETS_KEYTAB": keytab_path,
645 "NETBIOSNAME": netbiosname,
646 "SAM_LDB": samdb_url,
647 "DNS_KEYTAB": dns_keytab_path,
648 "DNSPASS_B64": b64encode(dnspass),
652 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
653 """Setup the secrets database.
655 :param path: Path to the secrets database.
656 :param setup_path: Get the path to a setup file.
657 :param session_info: Session info.
658 :param credentials: Credentials
659 :param lp: Loadparm context
660 :return: LDB handle for the created secrets database
662 if os.path.exists(path):
664 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
667 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
668 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
670 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
672 if credentials is not None and credentials.authentication_requested():
673 if credentials.get_bind_dn() is not None:
674 setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
675 "LDAPMANAGERDN": credentials.get_bind_dn(),
676 "LDAPMANAGERPASS_B64": b64encode(credentials.get_password())
679 setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
680 "LDAPADMINUSER": credentials.get_username(),
681 "LDAPADMINREALM": credentials.get_realm(),
682 "LDAPADMINPASS_B64": b64encode(credentials.get_password())
688 def setup_templatesdb(path, setup_path, session_info, lp):
689 """Setup the templates database.
691 :param path: Path to the database.
692 :param setup_path: Function for obtaining the path to setup files.
693 :param session_info: Session info
694 :param credentials: Credentials
695 :param lp: Loadparm context
697 templates_ldb = Ldb(url=path, session_info=session_info,
701 templates_ldb.erase()
702 # This should be 'except LdbError', but on a re-provision the assert in ldb.erase fires, and we need to catch that too
706 templates_ldb.load_ldif_file_add(setup_path("provision_templates_init.ldif"))
708 templates_ldb = Ldb(url=path, session_info=session_info,
711 templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif"))
714 def setup_registry(path, setup_path, session_info, lp):
715 """Setup the registry.
717 :param path: Path to the registry database
718 :param setup_path: Function that returns the path to a setup.
719 :param session_info: Session information
720 :param credentials: Credentials
721 :param lp: Loadparm context
723 reg = registry.Registry()
724 hive = registry.open_ldb(path, session_info=session_info,
726 reg.mount_hive(hive, registry.HKEY_LOCAL_MACHINE)
727 provision_reg = setup_path("provision.reg")
728 assert os.path.exists(provision_reg)
729 reg.diff_apply(provision_reg)
732 def setup_idmapdb(path, setup_path, session_info, lp):
733 """Setup the idmap database.
735 :param path: path to the idmap database
736 :param setup_path: Function that returns a path to a setup file
737 :param session_info: Session information
738 :param credentials: Credentials
739 :param lp: Loadparm context
741 if os.path.exists(path):
744 idmap_ldb = IDmapDB(path, session_info=session_info,
748 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
752 def setup_samdb_rootdse(samdb, setup_path, names):
753 """Setup the SamDB rootdse.
755 :param samdb: Sam Database handle
756 :param setup_path: Obtain setup path
758 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
759 "SCHEMADN": names.schemadn,
760 "NETBIOSNAME": names.netbiosname,
761 "DNSDOMAIN": names.dnsdomain,
762 "REALM": names.realm,
763 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
764 "DOMAINDN": names.domaindn,
765 "ROOTDN": names.rootdn,
766 "CONFIGDN": names.configdn,
767 "SERVERDN": names.serverdn,
771 def setup_self_join(samdb, names,
772 machinepass, dnspass,
773 domainsid, invocationid, setup_path,
774 policyguid, domainControllerFunctionality):
775 """Join a host to its own domain."""
776 assert isinstance(invocationid, str)
777 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
778 "CONFIGDN": names.configdn,
779 "SCHEMADN": names.schemadn,
780 "DOMAINDN": names.domaindn,
781 "SERVERDN": names.serverdn,
782 "INVOCATIONID": invocationid,
783 "NETBIOSNAME": names.netbiosname,
784 "DEFAULTSITE": names.sitename,
785 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
786 "MACHINEPASS_B64": b64encode(machinepass),
787 "DNSPASS_B64": b64encode(dnspass),
788 "REALM": names.realm,
789 "DOMAIN": names.domain,
790 "DNSDOMAIN": names.dnsdomain,
791 "SAMBA_VERSION_STRING": version,
792 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
794 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
795 "POLICYGUID": policyguid,
796 "DNSDOMAIN": names.dnsdomain,
797 "DOMAINSID": str(domainsid),
798 "DOMAINDN": names.domaindn})
800 # Setup fSMORoleOwner entries to point at the newly created DC entry
801 setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
802 "DOMAINDN": names.domaindn,
803 "CONFIGDN": names.configdn,
804 "SCHEMADN": names.schemadn,
805 "DEFAULTSITE": names.sitename,
806 "SERVERDN": names.serverdn
810 def setup_samdb(path, setup_path, session_info, credentials, lp,
812 domainsid, domainguid, policyguid,
813 fill, adminpass, krbtgtpass,
814 machinepass, invocationid, dnspass,
815 serverrole, schema=None, ldap_backend=None):
816 """Setup a complete SAM Database.
818 :note: This will wipe the main SAM database file!
821 domainFunctionality = DS_BEHAVIOR_WIN2008
822 forestFunctionality = DS_BEHAVIOR_WIN2008
823 domainControllerFunctionality = DS_BEHAVIOR_WIN2008
825 # Also wipes the database
826 setup_samdb_partitions(path, setup_path, message=message, lp=lp,
827 credentials=credentials, session_info=session_info,
829 ldap_backend=ldap_backend, serverrole=serverrole)
832 schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn)
834 # Load the database, but importantly, use Ldb not SamDB as we don't want to load the global schema
835 samdb = Ldb(session_info=session_info,
836 credentials=credentials, lp=lp)
838 message("Pre-loading the Samba 4 and AD schema")
840 # Load the schema from the one we computed earlier
841 samdb.set_schema_from_ldb(schema.ldb)
843 # And now we can connect to the DB - the schema won't be loaded from the DB
847 samdb.load_ldif_file_add(setup_path("provision_options.ldif"))
852 samdb.transaction_start()
854 message("Erasing data from partitions")
855 # Load the schema (again). This time it will force a reindex,
856 # and will therefore make the erase_partitions() below
857 # computationally sane
858 samdb.set_schema_from_ldb(schema.ldb)
859 samdb.erase_partitions()
861 # Set the domain functionality levels onto the database.
862 # Various module (the password_hash module in particular) need
863 # to know what level of AD we are emulating.
865 # These will be fixed into the database via the database
866 # modifictions below, but we need them set from the start.
867 samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
868 samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
869 samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
871 samdb.set_domain_sid(str(domainsid))
872 if serverrole == "domain controller":
873 samdb.set_invocation_id(invocationid)
875 message("Adding DomainDN: %s" % names.domaindn)
876 if serverrole == "domain controller":
877 domain_oc = "domainDNS"
879 domain_oc = "samba4LocalDomain"
881 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
882 "DOMAINDN": names.domaindn,
883 "DOMAIN_OC": domain_oc
886 message("Modifying DomainDN: " + names.domaindn + "")
887 if domainguid is not None:
888 domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
892 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
893 "LDAPTIME": timestring(int(time.time())),
894 "DOMAINSID": str(domainsid),
895 "SCHEMADN": names.schemadn,
896 "NETBIOSNAME": names.netbiosname,
897 "DEFAULTSITE": names.sitename,
898 "CONFIGDN": names.configdn,
899 "SERVERDN": names.serverdn,
900 "POLICYGUID": policyguid,
901 "DOMAINDN": names.domaindn,
902 "DOMAINGUID_MOD": domainguid_mod,
903 "DOMAIN_FUNCTIONALITY": str(domainFunctionality)
906 message("Adding configuration container")
907 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
908 "CONFIGDN": names.configdn,
910 message("Modifying configuration container")
911 setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
912 "CONFIGDN": names.configdn,
913 "SCHEMADN": names.schemadn,
916 # The LDIF here was created when the Schema object was constructed
917 message("Setting up sam.ldb schema")
918 samdb.add_ldif(schema.schema_dn_add)
919 samdb.modify_ldif(schema.schema_dn_modify)
920 samdb.write_prefixes_from_schema()
921 samdb.add_ldif(schema.schema_data)
922 setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
923 {"SCHEMADN": names.schemadn})
925 message("Setting up sam.ldb configuration data")
926 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
927 "CONFIGDN": names.configdn,
928 "NETBIOSNAME": names.netbiosname,
929 "DEFAULTSITE": names.sitename,
930 "DNSDOMAIN": names.dnsdomain,
931 "DOMAIN": names.domain,
932 "SCHEMADN": names.schemadn,
933 "DOMAINDN": names.domaindn,
934 "SERVERDN": names.serverdn,
935 "FOREST_FUNCTIONALALITY": str(forestFunctionality)
938 message("Setting up display specifiers")
939 setup_add_ldif(samdb, setup_path("display_specifiers.ldif"),
940 {"CONFIGDN": names.configdn})
942 message("Adding users container")
943 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
944 "DOMAINDN": names.domaindn})
945 message("Modifying users container")
946 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
947 "DOMAINDN": names.domaindn})
948 message("Adding computers container")
949 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
950 "DOMAINDN": names.domaindn})
951 message("Modifying computers container")
952 setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
953 "DOMAINDN": names.domaindn})
954 message("Setting up sam.ldb data")
955 setup_add_ldif(samdb, setup_path("provision.ldif"), {
956 "DOMAINDN": names.domaindn,
957 "NETBIOSNAME": names.netbiosname,
958 "DEFAULTSITE": names.sitename,
959 "CONFIGDN": names.configdn,
960 "SERVERDN": names.serverdn
963 if fill == FILL_FULL:
964 message("Setting up sam.ldb users and groups")
965 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
966 "DOMAINDN": names.domaindn,
967 "DOMAINSID": str(domainsid),
968 "CONFIGDN": names.configdn,
969 "ADMINPASS_B64": b64encode(adminpass),
970 "KRBTGTPASS_B64": b64encode(krbtgtpass),
973 if serverrole == "domain controller":
974 message("Setting up self join")
975 setup_self_join(samdb, names=names, invocationid=invocationid,
977 machinepass=machinepass,
978 domainsid=domainsid, policyguid=policyguid,
979 setup_path=setup_path, domainControllerFunctionality=domainControllerFunctionality)
982 samdb.transaction_cancel()
985 samdb.transaction_commit()
990 FILL_NT4SYNC = "NT4SYNC"
994 def provision(setup_dir, message, session_info,
995 credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL, realm=None,
996 rootdn=None, domaindn=None, schemadn=None, configdn=None,
998 domain=None, hostname=None, hostip=None, hostip6=None,
999 domainsid=None, adminpass=None, ldapadminpass=None,
1000 krbtgtpass=None, domainguid=None,
1001 policyguid=None, invocationid=None, machinepass=None,
1002 dnspass=None, root=None, nobody=None, users=None,
1003 wheel=None, backup=None, aci=None, serverrole=None,
1004 ldap_backend_extra_port=None, ldap_backend_type=None, sitename=None,
1005 ol_mmr_urls=None, ol_olc=None,
1006 setup_ds_path=None, slapd_path=None, nosync=False,
1007 ldap_dryrun_mode=False):
1010 :note: caution, this wipes all existing data!
1013 def setup_path(file):
1014 return os.path.join(setup_dir, file)
1016 if domainsid is None:
1017 domainsid = security.random_sid()
1019 if policyguid is None:
1020 policyguid = str(uuid.uuid4())
1021 if adminpass is None:
1022 adminpass = glue.generate_random_str(12)
1023 if krbtgtpass is None:
1024 krbtgtpass = glue.generate_random_str(12)
1025 if machinepass is None:
1026 machinepass = glue.generate_random_str(12)
1028 dnspass = glue.generate_random_str(12)
1029 if ldapadminpass is None:
1030 #Make a new, random password between Samba and it's LDAP server
1031 ldapadminpass=glue.generate_random_str(12)
1034 root_uid = findnss_uid([root or "root"])
1035 nobody_uid = findnss_uid([nobody or "nobody"])
1036 users_gid = findnss_gid([users or "users"])
1038 wheel_gid = findnss_gid(["wheel", "adm"])
1040 wheel_gid = findnss_gid([wheel])
1042 if targetdir is not None:
1043 if (not os.path.exists(os.path.join(targetdir, "etc"))):
1044 os.makedirs(os.path.join(targetdir, "etc"))
1045 smbconf = os.path.join(targetdir, "etc", "smb.conf")
1046 elif smbconf is None:
1047 smbconf = param.default_path()
1049 # only install a new smb.conf if there isn't one there already
1050 if not os.path.exists(smbconf):
1051 make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
1054 lp = param.LoadParm()
1057 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1058 dnsdomain=realm, serverrole=serverrole, sitename=sitename,
1059 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1062 paths = provision_paths_from_lp(lp, names.dnsdomain)
1066 hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1067 except socket.gaierror, (socket.EAI_NODATA, msg):
1072 hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1073 except socket.gaierror, (socket.EAI_NODATA, msg):
1076 if serverrole is None:
1077 serverrole = lp.get("server role")
1079 assert serverrole in ("domain controller", "member server", "standalone")
1080 if invocationid is None and serverrole == "domain controller":
1081 invocationid = str(uuid.uuid4())
1083 if not os.path.exists(paths.private_dir):
1084 os.mkdir(paths.private_dir)
1086 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1088 schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn)
1090 provision_backend = None
1091 if ldap_backend_type:
1092 # We only support an LDAP backend over ldapi://
1094 provision_backend = ProvisionBackend(paths=paths, setup_path=setup_path, lp=lp, credentials=credentials,
1096 message=message, hostname=hostname,
1097 root=root, schema=schema, ldap_backend_type=ldap_backend_type,
1098 ldapadminpass=ldapadminpass,
1099 ldap_backend_extra_port=ldap_backend_extra_port,
1100 ol_mmr_urls=ol_mmr_urls,
1101 slapd_path=slapd_path,
1102 setup_ds_path=setup_ds_path,
1103 ldap_dryrun_mode=ldap_dryrun_mode)
1105 # Now use the backend credentials to access the databases
1106 credentials = provision_backend.credentials
1108 # only install a new shares config db if there is none
1109 if not os.path.exists(paths.shareconf):
1110 message("Setting up share.ldb")
1111 share_ldb = Ldb(paths.shareconf, session_info=session_info,
1112 credentials=credentials, lp=lp)
1113 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1116 message("Setting up secrets.ldb")
1117 secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
1118 session_info=session_info,
1119 credentials=credentials, lp=lp)
1121 message("Setting up the registry")
1122 setup_registry(paths.hklm, setup_path, session_info,
1125 message("Setting up templates db")
1126 setup_templatesdb(paths.templates, setup_path, session_info=session_info,
1129 message("Setting up idmap db")
1130 idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1133 message("Setting up SAM db")
1134 samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info,
1135 credentials=credentials, lp=lp, names=names,
1137 domainsid=domainsid,
1138 schema=schema, domainguid=domainguid, policyguid=policyguid,
1140 adminpass=adminpass, krbtgtpass=krbtgtpass,
1141 invocationid=invocationid,
1142 machinepass=machinepass, dnspass=dnspass,
1143 serverrole=serverrole, ldap_backend=provision_backend)
1145 if serverrole == "domain controller":
1146 if paths.netlogon is None:
1147 message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1148 message("Please either remove %s or see the template at %s" %
1149 ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1150 assert(paths.netlogon is not None)
1152 if paths.sysvol is None:
1153 message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1154 message("Please either remove %s or see the template at %s" %
1155 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1156 assert(paths.sysvol is not None)
1158 policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1159 "{" + policyguid + "}")
1160 os.makedirs(policy_path, 0755)
1161 open(os.path.join(policy_path, "GPT.INI"), 'w').write("")
1162 os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1163 os.makedirs(os.path.join(policy_path, "User"), 0755)
1164 if not os.path.isdir(paths.netlogon):
1165 os.makedirs(paths.netlogon, 0755)
1167 if samdb_fill == FILL_FULL:
1168 setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1169 root_uid=root_uid, nobody_uid=nobody_uid,
1170 users_gid=users_gid, wheel_gid=wheel_gid)
1172 message("Setting up sam.ldb rootDSE marking as synchronized")
1173 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1175 # Only make a zone file on the first DC, it should be replicated with DNS replication
1176 if serverrole == "domain controller":
1177 secrets_ldb = Ldb(paths.secrets, session_info=session_info,
1178 credentials=credentials, lp=lp)
1179 secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1180 netbiosname=names.netbiosname, domainsid=domainsid,
1181 keytab_path=paths.keytab, samdb_url=paths.samdb,
1182 dns_keytab_path=paths.dns_keytab, dnspass=dnspass,
1183 machinepass=machinepass, dnsdomain=names.dnsdomain)
1185 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1186 assert isinstance(domainguid, str)
1187 hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1188 expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1189 scope=SCOPE_SUBTREE)
1190 assert isinstance(hostguid, str)
1192 create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1193 domaindn=names.domaindn, hostip=hostip,
1194 hostip6=hostip6, hostname=names.hostname,
1195 dnspass=dnspass, realm=names.realm,
1196 domainguid=domainguid, hostguid=hostguid)
1198 create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1199 dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1201 create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1202 dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1203 keytab_name=paths.dns_keytab)
1204 message("See %s for an example configuration include file for BIND" % paths.namedconf)
1205 message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1207 create_krb5_conf(paths.krb5conf, setup_path, dnsdomain=names.dnsdomain,
1208 hostname=names.hostname, realm=names.realm)
1209 message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1212 # if backend is openldap, terminate slapd after final provision and check its proper termination
1213 if provision_backend is not None and provision_backend.slapd is not None:
1214 if provision_backend.slapd.poll() is None:
1216 if hasattr(provision_backend.slapd, "terminate"):
1217 provision_backend.slapd.terminate()
1220 os.kill(provision_backend.slapd.pid, signal.SIGTERM)
1222 #and now wait for it to die
1223 provision_backend.slapd.communicate()
1225 # now display slapd_command_file.txt to show how slapd must be started next time
1226 message("Use later the following commandline to start slapd, then Samba:")
1227 slapd_command = "\'" + "\' \'".join(provision_backend.slapd_command) + "\'"
1228 message(slapd_command)
1229 message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
1231 setup_file(setup_path("ldap_backend_startup.sh"), paths.ldapdir + "/ldap_backend_startup.sh", {
1232 "SLAPD_COMMAND" : slapd_command})
1235 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1238 message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1240 message("Once the above files are installed, your Samba4 server will be ready to use")
1241 message("Server Role: %s" % serverrole)
1242 message("Hostname: %s" % names.hostname)
1243 message("NetBIOS Domain: %s" % names.domain)
1244 message("DNS Domain: %s" % names.dnsdomain)
1245 message("DOMAIN SID: %s" % str(domainsid))
1246 if samdb_fill == FILL_FULL:
1247 message("Admin password: %s" % adminpass)
1248 if provision_backend:
1249 if provision_backend.credentials.get_bind_dn() is not None:
1250 message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1252 message("LDAP Admin User: %s" % provision_backend.credentials.get_username())
1254 message("LDAP Admin Password: %s" % provision_backend.credentials.get_password())
1256 result = ProvisionResult()
1257 result.domaindn = domaindn
1258 result.paths = paths
1260 result.samdb = samdb
1265 def provision_become_dc(setup_dir=None,
1266 smbconf=None, targetdir=None, realm=None,
1267 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1269 domain=None, hostname=None, domainsid=None,
1270 adminpass=None, krbtgtpass=None, domainguid=None,
1271 policyguid=None, invocationid=None, machinepass=None,
1272 dnspass=None, root=None, nobody=None, users=None,
1273 wheel=None, backup=None, serverrole=None,
1274 ldap_backend=None, ldap_backend_type=None, sitename=None):
1277 """print a message if quiet is not set."""
1280 return provision(setup_dir, message, system_session(), None,
1281 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm,
1282 rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn, serverdn=serverdn,
1283 domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename)
1286 def setup_db_config(setup_path, dbdir):
1287 """Setup a Berkeley database.
1289 :param setup_path: Setup path function.
1290 :param dbdir: Database directory."""
1291 if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1292 os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1293 if not os.path.isdir(os.path.join(dbdir, "tmp")):
1294 os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1296 setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1297 {"LDAPDBDIR": dbdir})
1299 class ProvisionBackend(object):
1300 def __init__(self, paths=None, setup_path=None, lp=None, credentials=None,
1301 names=None, message=None,
1302 hostname=None, root=None,
1303 schema=None, ldapadminpass=None,
1304 ldap_backend_type=None, ldap_backend_extra_port=None,
1306 setup_ds_path=None, slapd_path=None,
1307 nosync=False, ldap_dryrun_mode=False):
1308 """Provision an LDAP backend for samba4
1310 This works for OpenLDAP and Fedora DS
1313 self.ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.ldapdir, "ldapi"), safe="")
1315 if not os.path.isdir(paths.ldapdir):
1316 os.makedirs(paths.ldapdir, 0700)
1318 if ldap_backend_type == "existing":
1319 #Check to see that this 'existing' LDAP backend in fact exists
1320 ldapi_db = Ldb(self.ldapi_uri, credentials=credentials)
1321 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1322 expression="(objectClass=OpenLDAProotDSE)")
1324 # If we have got here, then we must have a valid connection to the LDAP server, with valid credentials supplied
1325 # This caused them to be set into the long-term database later in the script.
1326 self.credentials = credentials
1327 self.ldap_backend_type = "openldap" #For now, assume existing backends at least emulate OpenLDAP
1330 # we will shortly start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via self.ldapi_uri
1331 # if another instance of slapd is already running
1333 ldapi_db = Ldb(self.ldapi_uri)
1334 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1335 expression="(objectClass=OpenLDAProotDSE)");
1337 f = open(paths.slapdpid, "r")
1340 message("Check for slapd Process with PID: " + str(p) + " and terminate it manually.")
1344 raise("Warning: Another slapd Instance seems already running on this host, listening to " + self.ldapi_uri + ". Please shut it down before you continue. ")
1349 # Try to print helpful messages when the user has not specified the path to slapd
1350 if slapd_path is None:
1351 raise("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
1352 if not os.path.exists(slapd_path):
1353 message (slapd_path)
1354 raise("Warning: Given Path to slapd does not exist!")
1356 schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1358 os.unlink(schemadb_path)
1363 # Put the LDIF of the schema into a database so we can search on
1364 # it to generate schema-dependent configurations in Fedora DS and
1366 os.path.join(paths.ldapdir, "schema-tmp.ldb")
1367 schema.ldb.connect(schemadb_path)
1368 schema.ldb.transaction_start()
1370 # These bits of LDIF are supplied when the Schema object is created
1371 schema.ldb.add_ldif(schema.schema_dn_add)
1372 schema.ldb.modify_ldif(schema.schema_dn_modify)
1373 schema.ldb.add_ldif(schema.schema_data)
1374 schema.ldb.transaction_commit()
1376 self.credentials = Credentials()
1377 self.credentials.guess(lp)
1378 #Kerberos to an ldapi:// backend makes no sense
1379 self.credentials.set_kerberos_state(DONT_USE_KERBEROS)
1380 self.ldap_backend_type = ldap_backend_type
1382 if ldap_backend_type == "fedora-ds":
1383 provision_fds_backend(self, paths=paths, setup_path=setup_path, names=names, message=message,
1384 hostname=hostname, ldapadminpass=ldapadminpass, root=root,
1385 schema=schema, ldap_backend_extra_port=ldap_backend_extra_port,
1386 setup_ds_path=setup_ds_path, slapd_path=slapd_path,
1387 nosync=nosync, ldap_dryrun_mode=ldap_dryrun_mode)
1389 elif ldap_backend_type == "openldap":
1390 provision_openldap_backend(self, paths=paths, setup_path=setup_path, names=names, message=message,
1391 hostname=hostname, ldapadminpass=ldapadminpass, root=root,
1392 schema=schema, ldap_backend_extra_port=ldap_backend_extra_port,
1393 ol_mmr_urls=ol_mmr_urls,
1394 slapd_path=slapd_path,
1395 nosync=nosync, ldap_dryrun_mode=ldap_dryrun_mode)
1397 raise("Unknown LDAP backend type selected")
1399 self.credentials.set_password(ldapadminpass)
1401 # Now start the slapd, so we can provision onto it. We keep the
1402 # subprocess context around, to kill this off at the successful
1404 self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False)
1406 while self.slapd.poll() is None:
1407 # Wait until the socket appears
1409 ldapi_db = Ldb(self.ldapi_uri, lp=lp, credentials=self.credentials)
1410 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1411 expression="(objectClass=OpenLDAProotDSE)")
1412 # If we have got here, then we must have a valid connection to the LDAP server!
1418 raise "slapd died before we could make a connection to it"
1421 def provision_openldap_backend(result, paths=None, setup_path=None, names=None, message=None,
1422 hostname=None, ldapadminpass=None, root=None,
1424 ldap_backend_extra_port=None,
1426 slapd_path=None, nosync=False,
1427 ldap_dryrun_mode=False):
1429 #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
1432 nosync_config = "dbnosync"
1435 attrs = ["linkID", "lDAPDisplayName"]
1436 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)
1438 memberof_config = "# Generated from Samba4 schema\n"
1439 refint_attributes = ""
1440 for i in range (0, len(res)):
1441 expression = "(&(objectclass=attributeSchema)(linkID=%d)(attributeSyntax=2.5.5.1))" % (int(res[i]["linkID"][0])+1)
1442 target = schema.ldb.searchone(basedn=names.schemadn,
1443 expression=expression,
1444 attribute="lDAPDisplayName",
1445 scope=SCOPE_SUBTREE)
1446 if target is not None:
1447 refint_attributes = refint_attributes + " " + res[i]["lDAPDisplayName"][0]
1449 memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1450 { "MEMBER_ATTR" : str(res[i]["lDAPDisplayName"][0]),
1451 "MEMBEROF_ATTR" : str(target) })
1453 refint_config = read_and_sub_file(setup_path("refint.conf"),
1454 { "LINK_ATTRS" : refint_attributes})
1456 res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1458 for i in range (0, len(res)):
1459 index_attr = res[i]["lDAPDisplayName"][0]
1460 if index_attr == "objectGUID":
1461 index_attr = "entryUUID"
1463 index_config += "index " + index_attr + " eq\n"
1465 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1467 mmr_replicator_acl = ""
1468 mmr_serverids_config = ""
1469 mmr_syncrepl_schema_config = ""
1470 mmr_syncrepl_config_config = ""
1471 mmr_syncrepl_user_config = ""
1474 if ol_mmr_urls is not None:
1475 # For now, make these equal
1476 mmr_pass = ldapadminpass
1478 url_list=filter(None,ol_mmr_urls.split(' '))
1479 if (len(url_list) == 1):
1480 url_list=filter(None,ol_mmr_urls.split(','))
1483 mmr_on_config = "MirrorMode On"
1484 mmr_replicator_acl = " by dn=cn=replicator,cn=samba read"
1486 for url in url_list:
1488 mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1489 { "SERVERID" : str(serverid),
1490 "LDAPSERVER" : url })
1493 mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1495 "MMRDN": names.schemadn,
1497 "MMR_PASSWORD": mmr_pass})
1500 mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1502 "MMRDN": names.configdn,
1504 "MMR_PASSWORD": mmr_pass})
1507 mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1509 "MMRDN": names.domaindn,
1511 "MMR_PASSWORD": mmr_pass })
1512 # OpenLDAP cn=config initialisation
1513 olc_syncrepl_config = ""
1515 # if mmr = yes, generate cn=config-replication directives
1516 # and olc_seed.lif for the other mmr-servers
1517 if ol_mmr_urls is not None:
1519 olc_serverids_config = ""
1520 olc_syncrepl_seed_config = ""
1521 olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
1523 for url in url_list:
1525 olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
1526 { "SERVERID" : str(serverid),
1527 "LDAPSERVER" : url })
1530 olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
1533 "MMR_PASSWORD": mmr_pass})
1535 olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1537 "LDAPSERVER" : url})
1539 setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif,
1540 {"OLC_SERVER_ID_CONF": olc_serverids_config,
1541 "OLC_PW": ldapadminpass,
1542 "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
1545 setup_file(setup_path("slapd.conf"), paths.slapdconf,
1546 {"DNSDOMAIN": names.dnsdomain,
1547 "LDAPDIR": paths.ldapdir,
1548 "DOMAINDN": names.domaindn,
1549 "CONFIGDN": names.configdn,
1550 "SCHEMADN": names.schemadn,
1551 "MEMBEROF_CONFIG": memberof_config,
1552 "MIRRORMODE": mmr_on_config,
1553 "REPLICATOR_ACL": mmr_replicator_acl,
1554 "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1555 "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1556 "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1557 "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1558 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
1559 "OLC_MMR_CONFIG": olc_mmr_config,
1560 "REFINT_CONFIG": refint_config,
1561 "INDEX_CONFIG": index_config,
1562 "NOSYNC": nosync_config})
1564 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1565 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1566 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1568 if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba", "cn=samba")):
1569 os.makedirs(os.path.join(paths.ldapdir, "db", "samba", "cn=samba"), 0700)
1571 setup_file(setup_path("cn=samba.ldif"),
1572 os.path.join(paths.ldapdir, "db", "samba", "cn=samba.ldif"),
1573 { "UUID": str(uuid.uuid4()),
1574 "LDAPTIME": timestring(int(time.time()))} )
1575 setup_file(setup_path("cn=samba-admin.ldif"),
1576 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=samba-admin.ldif"),
1577 {"LDAPADMINPASS_B64": b64encode(ldapadminpass),
1578 "UUID": str(uuid.uuid4()),
1579 "LDAPTIME": timestring(int(time.time()))} )
1581 if ol_mmr_urls is not None:
1582 setup_file(setup_path("cn=replicator.ldif"),
1583 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=replicator.ldif"),
1584 {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1585 "UUID": str(uuid.uuid4()),
1586 "LDAPTIME": timestring(int(time.time()))} )
1589 mapping = "schema-map-openldap-2.3"
1590 backend_schema = "backend-schema.schema"
1592 backend_schema_data = schema.ldb.convert_schema_to_openldap("openldap", open(setup_path(mapping), 'r').read())
1593 assert backend_schema_data is not None
1594 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1596 # now we generate the needed strings to start slapd automatically,
1597 # first ldapi_uri...
1598 if ldap_backend_extra_port is not None:
1599 # When we use MMR, we can't use 0.0.0.0 as it uses the name
1600 # specified there as part of it's clue as to it's own name,
1601 # and not to replicate to itself
1602 if ol_mmr_urls is None:
1603 server_port_string = "ldap://0.0.0.0:%d" % ldap_backend_extra_port
1605 server_port_string = "ldap://" + names.hostname + "." + names.dnsdomain +":%d" % ldap_backend_extra_port
1607 server_port_string = ""
1609 # Prepare the 'result' information - the commands to return in particular
1610 result.slapd_provision_command = [slapd_path]
1612 result.slapd_provision_command.append("-F" + paths.olcdir)
1614 result.slapd_provision_command.append("-h")
1616 # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands
1617 result.slapd_command = list(result.slapd_provision_command)
1619 result.slapd_provision_command.append(result.ldapi_uri)
1620 result.slapd_provision_command.append("-d0")
1622 uris = result.ldapi_uri
1623 if server_port_string is not "":
1624 uris = uris + " " + server_port_string
1626 result.slapd_command.append(uris)
1628 # Set the username - done here because Fedora DS still uses the admin DN and simple bind
1629 result.credentials.set_username("samba-admin")
1631 # If we were just looking for crashes up to this point, it's a
1632 # good time to exit before we realise we don't have OpenLDAP on
1634 if ldap_dryrun_mode:
1637 # Finally, convert the configuration into cn=config style!
1638 if not os.path.isdir(paths.olcdir):
1639 os.makedirs(paths.olcdir, 0770)
1641 retcode = subprocess.call([slapd_path, "-Ttest", "-f", paths.slapdconf, "-F", paths.olcdir], close_fds=True, shell=False)
1643 # We can't do this, as OpenLDAP is strange. It gives an error
1644 # output to the above, but does the conversion sucessfully...
1647 # raise("conversion from slapd.conf to cn=config failed")
1649 if not os.path.exists(os.path.join(paths.olcdir, "cn=config.ldif")):
1650 raise("conversion from slapd.conf to cn=config failed")
1652 # Don't confuse the admin by leaving the slapd.conf around
1653 os.remove(paths.slapdconf)
1656 def provision_fds_backend(result, paths=None, setup_path=None, names=None, message=None,
1657 hostname=None, ldapadminpass=None, root=None,
1659 ldap_backend_extra_port=None,
1663 ldap_dryrun_mode=False):
1665 if ldap_backend_extra_port is not None:
1666 serverport = "ServerPort=%d" % ldap_backend_extra_port
1670 setup_file(setup_path("fedorads.inf"), paths.fedoradsinf,
1672 "HOSTNAME": hostname,
1673 "DNSDOMAIN": names.dnsdomain,
1674 "LDAPDIR": paths.ldapdir,
1675 "DOMAINDN": names.domaindn,
1676 "LDAPMANAGERDN": names.ldapmanagerdn,
1677 "LDAPMANAGERPASS": ldapadminpass,
1678 "SERVERPORT": serverport})
1680 setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions,
1681 {"CONFIGDN": names.configdn,
1682 "SCHEMADN": names.schemadn,
1685 mapping = "schema-map-fedora-ds-1.0"
1686 backend_schema = "99_ad.ldif"
1688 # Build a schema file in Fedora DS format
1689 backend_schema_data = schema.ldb.convert_schema_to_openldap("fedora-ds", open(setup_path(mapping), 'r').read())
1690 assert backend_schema_data is not None
1691 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1693 result.credentials.set_bind_dn(names.ldapmanagerdn)
1695 # Destory the target directory, or else setup-ds.pl will complain
1696 fedora_ds_dir = os.path.join(paths.ldapdir, "slapd-samba4")
1697 shutil.rmtree(fedora_ds_dir, True)
1699 result.slapd_provision_command = [slapd_path, "-D", fedora_ds_dir, "-i", paths.slapdpid];
1700 #In the 'provision' command line, stay in the foreground so we can easily kill it
1701 result.slapd_provision_command.append("-d0")
1703 #the command for the final run is the normal script
1704 result.slapd_command = [os.path.join(paths.ldapdir, "slapd-samba4", "start-slapd")]
1706 # If we were just looking for crashes up to this point, it's a
1707 # good time to exit before we realise we don't have Fedora DS on
1708 if ldap_dryrun_mode:
1711 # Try to print helpful messages when the user has not specified the path to the setup-ds tool
1712 if setup_ds_path is None:
1713 raise("Warning: Fedora DS LDAP-Backend must be setup with path to setup-ds, e.g. --setup-ds-path=\"/usr/sbin/setup-ds.pl\"!")
1714 if not os.path.exists(setup_ds_path):
1715 message (setup_ds_path)
1716 raise("Warning: Given Path to slapd does not exist!")
1718 # Run the Fedora DS setup utility
1719 retcode = subprocess.call([setup_ds_path, "--silent", "--file", paths.fedoradsinf], close_fds=True, shell=False)
1721 raise("setup-ds failed")
1723 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1724 """Create a PHP LDAP admin configuration file.
1726 :param path: Path to write the configuration to.
1727 :param setup_path: Function to generate setup paths.
1729 setup_file(setup_path("phpldapadmin-config.php"), path,
1730 {"S4_LDAPI_URI": ldapi_uri})
1733 def create_zone_file(path, setup_path, dnsdomain, domaindn,
1734 hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid):
1735 """Write out a DNS zone file, from the info in the current database.
1737 :param path: Path of the new zone file.
1738 :param setup_path: Setup path function.
1739 :param dnsdomain: DNS Domain name
1740 :param domaindn: DN of the Domain
1741 :param hostip: Local IPv4 IP
1742 :param hostip6: Local IPv6 IP
1743 :param hostname: Local hostname
1744 :param dnspass: Password for DNS
1745 :param realm: Realm name
1746 :param domainguid: GUID of the domain.
1747 :param hostguid: GUID of the host.
1749 assert isinstance(domainguid, str)
1751 if hostip6 is not None:
1752 hostip6_base_line = " IN AAAA " + hostip6
1753 hostip6_host_line = hostname + " IN AAAA " + hostip6
1755 hostip6_base_line = ""
1756 hostip6_host_line = ""
1758 if hostip is not None:
1759 hostip_base_line = " IN A " + hostip
1760 hostip_host_line = hostname + " IN A " + hostip
1762 hostip_base_line = ""
1763 hostip_host_line = ""
1765 setup_file(setup_path("provision.zone"), path, {
1766 "DNSPASS_B64": b64encode(dnspass),
1767 "HOSTNAME": hostname,
1768 "DNSDOMAIN": dnsdomain,
1770 "HOSTIP_BASE_LINE": hostip_base_line,
1771 "HOSTIP_HOST_LINE": hostip_host_line,
1772 "DOMAINGUID": domainguid,
1773 "DATESTRING": time.strftime("%Y%m%d%H"),
1774 "DEFAULTSITE": DEFAULTSITE,
1775 "HOSTGUID": hostguid,
1776 "HOSTIP6_BASE_LINE": hostip6_base_line,
1777 "HOSTIP6_HOST_LINE": hostip6_host_line,
1781 def create_named_conf(path, setup_path, realm, dnsdomain,
1783 """Write out a file containing zone statements suitable for inclusion in a
1784 named.conf file (including GSS-TSIG configuration).
1786 :param path: Path of the new named.conf file.
1787 :param setup_path: Setup path function.
1788 :param realm: Realm name
1789 :param dnsdomain: DNS Domain name
1790 :param private_dir: Path to private directory
1791 :param keytab_name: File name of DNS keytab file
1794 setup_file(setup_path("named.conf"), path, {
1795 "DNSDOMAIN": dnsdomain,
1797 "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1798 "PRIVATE_DIR": private_dir
1801 def create_named_txt(path, setup_path, realm, dnsdomain,
1802 private_dir, keytab_name):
1803 """Write out a file containing zone statements suitable for inclusion in a
1804 named.conf file (including GSS-TSIG configuration).
1806 :param path: Path of the new named.conf file.
1807 :param setup_path: Setup path function.
1808 :param realm: Realm name
1809 :param dnsdomain: DNS Domain name
1810 :param private_dir: Path to private directory
1811 :param keytab_name: File name of DNS keytab file
1814 setup_file(setup_path("named.txt"), path, {
1815 "DNSDOMAIN": dnsdomain,
1817 "DNS_KEYTAB": keytab_name,
1818 "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1819 "PRIVATE_DIR": private_dir
1822 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1823 """Write out a file containing zone statements suitable for inclusion in a
1824 named.conf file (including GSS-TSIG configuration).
1826 :param path: Path of the new named.conf file.
1827 :param setup_path: Setup path function.
1828 :param dnsdomain: DNS Domain name
1829 :param hostname: Local hostname
1830 :param realm: Realm name
1833 setup_file(setup_path("krb5.conf"), path, {
1834 "DNSDOMAIN": dnsdomain,
1835 "HOSTNAME": hostname,