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 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
492 serverrole, ldap_backend=None,
494 """Setup the partitions for the SAM database.
496 Alternatively, provision() may call this, and then populate the database.
498 :note: This will wipe the Sam Database!
500 :note: This function always removes the local SAM LDB file. The erase
501 parameter controls whether to erase the existing data, which
502 may not be stored locally but in LDAP.
504 assert session_info is not None
506 # We use options=["modules:"] to stop the modules loading - we
507 # just want to wipe and re-initialise the database, not start it up
510 samdb = Ldb(url=samdb_path, session_info=session_info,
511 credentials=credentials, lp=lp, options=["modules:"])
513 samdb.erase_except_schema_controlled()
515 os.unlink(samdb_path)
516 samdb = Ldb(url=samdb_path, session_info=session_info,
517 credentials=credentials, lp=lp, options=["modules:"])
519 samdb.erase_except_schema_controlled()
522 #Add modules to the list to activate them by default
523 #beware often order is important
525 # Some Known ordering constraints:
526 # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
527 # - objectclass must be before password_hash, because password_hash checks
528 # that the objectclass is of type person (filled in by objectclass
529 # module when expanding the objectclass list)
530 # - partition must be last
531 # - each partition has its own module list then
532 modules_list = ["rootdse",
550 "extended_dn_out_ldb"]
551 modules_list2 = ["show_deleted",
554 domaindn_ldb = "users.ldb"
555 configdn_ldb = "configuration.ldb"
556 schemadn_ldb = "schema.ldb"
557 if ldap_backend is not None:
558 domaindn_ldb = ldap_backend.ldapi_uri
559 configdn_ldb = ldap_backend.ldapi_uri
560 schemadn_ldb = ldap_backend.ldapi_uri
562 if ldap_backend.ldap_backend_type == "fedora-ds":
563 backend_modules = ["nsuniqueid", "paged_searches"]
564 # We can handle linked attributes here, as we don't have directory-side subtree operations
565 tdb_modules_list = ["linked_attributes", "extended_dn_out_dereference"]
566 elif ldap_backend.ldap_backend_type == "openldap":
567 backend_modules = ["entryuuid", "paged_searches"]
568 # OpenLDAP handles subtree renames, so we don't want to do any of these things
569 tdb_modules_list = ["extended_dn_out_dereference"]
571 elif serverrole == "domain controller":
572 backend_modules = ["repl_meta_data"]
574 backend_modules = ["objectguid"]
576 if tdb_modules_list is None:
577 tdb_modules_list_as_string = ""
579 tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
581 samdb.transaction_start()
583 message("Setting up sam.ldb partitions and settings")
584 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
585 "SCHEMADN": names.schemadn,
586 "SCHEMADN_LDB": schemadn_ldb,
587 "SCHEMADN_MOD2": ",objectguid",
588 "CONFIGDN": names.configdn,
589 "CONFIGDN_LDB": configdn_ldb,
590 "DOMAINDN": names.domaindn,
591 "DOMAINDN_LDB": domaindn_ldb,
592 "SCHEMADN_MOD": "schema_fsmo,instancetype",
593 "CONFIGDN_MOD": "naming_fsmo,instancetype",
594 "DOMAINDN_MOD": "pdc_fsmo,instancetype",
595 "MODULES_LIST": ",".join(modules_list),
596 "TDB_MODULES_LIST": tdb_modules_list_as_string,
597 "MODULES_LIST2": ",".join(modules_list2),
598 "BACKEND_MOD": ",".join(backend_modules),
601 samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
603 message("Setting up sam.ldb rootDSE")
604 setup_samdb_rootdse(samdb, setup_path, names)
607 samdb.transaction_cancel()
610 samdb.transaction_commit()
614 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain,
615 netbiosname, domainsid, keytab_path, samdb_url,
616 dns_keytab_path, dnspass, machinepass):
617 """Add DC-specific bits to a secrets database.
619 :param secretsdb: Ldb Handle to the secrets database
620 :param setup_path: Setup path function
621 :param machinepass: Machine password
623 setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), {
624 "MACHINEPASS_B64": b64encode(machinepass),
627 "DNSDOMAIN": dnsdomain,
628 "DOMAINSID": str(domainsid),
629 "SECRETS_KEYTAB": keytab_path,
630 "NETBIOSNAME": netbiosname,
631 "SAM_LDB": samdb_url,
632 "DNS_KEYTAB": dns_keytab_path,
633 "DNSPASS_B64": b64encode(dnspass),
637 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
638 """Setup the secrets database.
640 :param path: Path to the secrets database.
641 :param setup_path: Get the path to a setup file.
642 :param session_info: Session info.
643 :param credentials: Credentials
644 :param lp: Loadparm context
645 :return: LDB handle for the created secrets database
647 if os.path.exists(path):
649 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
652 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
653 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
655 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
657 if credentials is not None and credentials.authentication_requested():
658 if credentials.get_bind_dn() is not None:
659 setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
660 "LDAPMANAGERDN": credentials.get_bind_dn(),
661 "LDAPMANAGERPASS_B64": b64encode(credentials.get_password())
664 setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
665 "LDAPADMINUSER": credentials.get_username(),
666 "LDAPADMINREALM": credentials.get_realm(),
667 "LDAPADMINPASS_B64": b64encode(credentials.get_password())
673 def setup_templatesdb(path, setup_path, session_info, lp):
674 """Setup the templates database.
676 :param path: Path to the database.
677 :param setup_path: Function for obtaining the path to setup files.
678 :param session_info: Session info
679 :param credentials: Credentials
680 :param lp: Loadparm context
682 templates_ldb = Ldb(url=path, session_info=session_info,
686 templates_ldb.erase()
687 # This should be 'except LdbError', but on a re-provision the assert in ldb.erase fires, and we need to catch that too
691 templates_ldb.load_ldif_file_add(setup_path("provision_templates_init.ldif"))
693 templates_ldb = Ldb(url=path, session_info=session_info,
696 templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif"))
699 def setup_registry(path, setup_path, session_info, lp):
700 """Setup the registry.
702 :param path: Path to the registry database
703 :param setup_path: Function that returns the path to a setup.
704 :param session_info: Session information
705 :param credentials: Credentials
706 :param lp: Loadparm context
708 reg = registry.Registry()
709 hive = registry.open_ldb(path, session_info=session_info,
711 reg.mount_hive(hive, registry.HKEY_LOCAL_MACHINE)
712 provision_reg = setup_path("provision.reg")
713 assert os.path.exists(provision_reg)
714 reg.diff_apply(provision_reg)
717 def setup_idmapdb(path, setup_path, session_info, lp):
718 """Setup the idmap database.
720 :param path: path to the idmap database
721 :param setup_path: Function that returns a path to a setup file
722 :param session_info: Session information
723 :param credentials: Credentials
724 :param lp: Loadparm context
726 if os.path.exists(path):
729 idmap_ldb = IDmapDB(path, session_info=session_info,
733 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
737 def setup_samdb_rootdse(samdb, setup_path, names):
738 """Setup the SamDB rootdse.
740 :param samdb: Sam Database handle
741 :param setup_path: Obtain setup path
743 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
744 "SCHEMADN": names.schemadn,
745 "NETBIOSNAME": names.netbiosname,
746 "DNSDOMAIN": names.dnsdomain,
747 "REALM": names.realm,
748 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
749 "DOMAINDN": names.domaindn,
750 "ROOTDN": names.rootdn,
751 "CONFIGDN": names.configdn,
752 "SERVERDN": names.serverdn,
756 def setup_self_join(samdb, names,
757 machinepass, dnspass,
758 domainsid, invocationid, setup_path,
759 policyguid, domainControllerFunctionality):
760 """Join a host to its own domain."""
761 assert isinstance(invocationid, str)
762 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
763 "CONFIGDN": names.configdn,
764 "SCHEMADN": names.schemadn,
765 "DOMAINDN": names.domaindn,
766 "SERVERDN": names.serverdn,
767 "INVOCATIONID": invocationid,
768 "NETBIOSNAME": names.netbiosname,
769 "DEFAULTSITE": names.sitename,
770 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
771 "MACHINEPASS_B64": b64encode(machinepass),
772 "DNSPASS_B64": b64encode(dnspass),
773 "REALM": names.realm,
774 "DOMAIN": names.domain,
775 "DNSDOMAIN": names.dnsdomain,
776 "SAMBA_VERSION_STRING": version,
777 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
778 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
779 "POLICYGUID": policyguid,
780 "DNSDOMAIN": names.dnsdomain,
781 "DOMAINSID": str(domainsid),
782 "DOMAINDN": names.domaindn})
785 def setup_samdb(path, setup_path, session_info, credentials, lp,
787 domainsid, domainguid, policyguid,
788 fill, adminpass, krbtgtpass,
789 machinepass, invocationid, dnspass,
790 serverrole, schema=None, ldap_backend=None):
791 """Setup a complete SAM Database.
793 :note: This will wipe the main SAM database file!
796 domainFunctionality = DS_BEHAVIOR_WIN2008
797 forestFunctionality = DS_BEHAVIOR_WIN2008
798 domainControllerFunctionality = DS_BEHAVIOR_WIN2008
800 # Also wipes the database
801 setup_samdb_partitions(path, setup_path, message=message, lp=lp,
802 credentials=credentials, session_info=session_info,
804 ldap_backend=ldap_backend, serverrole=serverrole)
807 schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn)
809 # Load the database, but importantly, use Ldb not SamDB as we don't want to load the global schema
810 samdb = Ldb(session_info=session_info,
811 credentials=credentials, lp=lp)
813 message("Pre-loading the Samba 4 and AD schema")
815 # Load the schema from the one we computed earlier
816 samdb.set_schema_from_ldb(schema.ldb)
818 # And now we can connect to the DB - the schema won't be loaded from the DB
823 samdb.transaction_start()
825 message("Erasing data from partitions")
826 # Load the schema (again). This time it will force a reindex,
827 # and will therefore make the erase_partitions() below
828 # computationally sane
829 samdb.set_schema_from_ldb(schema.ldb)
830 samdb.erase_partitions()
832 # Set the domain functionality levels onto the database.
833 # Various module (the password_hash module in particular) need
834 # to know what level of AD we are emulating.
836 # These will be fixed into the database via the database
837 # modifictions below, but we need them set from the start.
838 samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
839 samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
840 samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
842 samdb.set_domain_sid(str(domainsid))
843 if serverrole == "domain controller":
844 samdb.set_invocation_id(invocationid)
846 message("Adding DomainDN: %s" % names.domaindn)
847 if serverrole == "domain controller":
848 domain_oc = "domainDNS"
850 domain_oc = "samba4LocalDomain"
852 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
853 "DOMAINDN": names.domaindn,
854 "DOMAIN_OC": domain_oc
857 message("Modifying DomainDN: " + names.domaindn + "")
858 if domainguid is not None:
859 domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
863 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
864 "LDAPTIME": timestring(int(time.time())),
865 "DOMAINSID": str(domainsid),
866 "SCHEMADN": names.schemadn,
867 "NETBIOSNAME": names.netbiosname,
868 "DEFAULTSITE": names.sitename,
869 "CONFIGDN": names.configdn,
870 "SERVERDN": names.serverdn,
871 "POLICYGUID": policyguid,
872 "DOMAINDN": names.domaindn,
873 "DOMAINGUID_MOD": domainguid_mod,
874 "DOMAIN_FUNCTIONALITY": str(domainFunctionality)
877 message("Adding configuration container")
878 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
879 "CONFIGDN": names.configdn,
881 message("Modifying configuration container")
882 setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
883 "CONFIGDN": names.configdn,
884 "SCHEMADN": names.schemadn,
887 # The LDIF here was created when the Schema object was constructed
888 message("Setting up sam.ldb schema")
889 samdb.add_ldif(schema.schema_dn_add)
890 samdb.modify_ldif(schema.schema_dn_modify)
891 samdb.add_ldif(schema.schema_data)
892 setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
893 {"SCHEMADN": names.schemadn})
895 message("Setting up sam.ldb configuration data")
896 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
897 "CONFIGDN": names.configdn,
898 "NETBIOSNAME": names.netbiosname,
899 "DEFAULTSITE": names.sitename,
900 "DNSDOMAIN": names.dnsdomain,
901 "DOMAIN": names.domain,
902 "SCHEMADN": names.schemadn,
903 "DOMAINDN": names.domaindn,
904 "SERVERDN": names.serverdn,
905 "FOREST_FUNCTIONALALITY": str(forestFunctionality)
908 message("Setting up display specifiers")
909 setup_add_ldif(samdb, setup_path("display_specifiers.ldif"),
910 {"CONFIGDN": names.configdn})
912 message("Adding users container")
913 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
914 "DOMAINDN": names.domaindn})
915 message("Modifying users container")
916 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
917 "DOMAINDN": names.domaindn})
918 message("Adding computers container")
919 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
920 "DOMAINDN": names.domaindn})
921 message("Modifying computers container")
922 setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
923 "DOMAINDN": names.domaindn})
924 message("Setting up sam.ldb data")
925 setup_add_ldif(samdb, setup_path("provision.ldif"), {
926 "DOMAINDN": names.domaindn,
927 "NETBIOSNAME": names.netbiosname,
928 "DEFAULTSITE": names.sitename,
929 "CONFIGDN": names.configdn,
930 "SERVERDN": names.serverdn
933 if fill == FILL_FULL:
934 message("Setting up sam.ldb users and groups")
935 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
936 "DOMAINDN": names.domaindn,
937 "DOMAINSID": str(domainsid),
938 "CONFIGDN": names.configdn,
939 "ADMINPASS_B64": b64encode(adminpass),
940 "KRBTGTPASS_B64": b64encode(krbtgtpass),
943 if serverrole == "domain controller":
944 message("Setting up self join")
945 setup_self_join(samdb, names=names, invocationid=invocationid,
947 machinepass=machinepass,
948 domainsid=domainsid, policyguid=policyguid,
949 setup_path=setup_path, domainControllerFunctionality=domainControllerFunctionality)
952 samdb.transaction_cancel()
955 samdb.transaction_commit()
960 FILL_NT4SYNC = "NT4SYNC"
964 def provision(setup_dir, message, session_info,
965 credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL, realm=None,
966 rootdn=None, domaindn=None, schemadn=None, configdn=None,
968 domain=None, hostname=None, hostip=None, hostip6=None,
969 domainsid=None, adminpass=None, ldapadminpass=None,
970 krbtgtpass=None, domainguid=None,
971 policyguid=None, invocationid=None, machinepass=None,
972 dnspass=None, root=None, nobody=None, users=None,
973 wheel=None, backup=None, aci=None, serverrole=None,
974 ldap_backend_extra_port=None, ldap_backend_type=None, sitename=None,
975 ol_mmr_urls=None, ol_olc=None,
976 setup_ds_path=None, slapd_path=None, nosync=False,
977 ldap_dryrun_mode=False):
980 :note: caution, this wipes all existing data!
983 def setup_path(file):
984 return os.path.join(setup_dir, file)
986 if domainsid is None:
987 domainsid = security.random_sid()
989 if policyguid is None:
990 policyguid = str(uuid.uuid4())
991 if adminpass is None:
992 adminpass = glue.generate_random_str(12)
993 if krbtgtpass is None:
994 krbtgtpass = glue.generate_random_str(12)
995 if machinepass is None:
996 machinepass = glue.generate_random_str(12)
998 dnspass = glue.generate_random_str(12)
999 if ldapadminpass is None:
1000 #Make a new, random password between Samba and it's LDAP server
1001 ldapadminpass=glue.generate_random_str(12)
1004 root_uid = findnss_uid([root or "root"])
1005 nobody_uid = findnss_uid([nobody or "nobody"])
1006 users_gid = findnss_gid([users or "users"])
1008 wheel_gid = findnss_gid(["wheel", "adm"])
1010 wheel_gid = findnss_gid([wheel])
1012 if targetdir is not None:
1013 if (not os.path.exists(os.path.join(targetdir, "etc"))):
1014 os.makedirs(os.path.join(targetdir, "etc"))
1015 smbconf = os.path.join(targetdir, "etc", "smb.conf")
1016 elif smbconf is None:
1017 smbconf = param.default_path()
1019 # only install a new smb.conf if there isn't one there already
1020 if not os.path.exists(smbconf):
1021 make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
1024 lp = param.LoadParm()
1027 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1028 dnsdomain=realm, serverrole=serverrole, sitename=sitename,
1029 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1032 paths = provision_paths_from_lp(lp, names.dnsdomain)
1036 hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1037 except socket.gaierror, (socket.EAI_NODATA, msg):
1042 hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1043 except socket.gaierror, (socket.EAI_NODATA, msg):
1046 if serverrole is None:
1047 serverrole = lp.get("server role")
1049 assert serverrole in ("domain controller", "member server", "standalone")
1050 if invocationid is None and serverrole == "domain controller":
1051 invocationid = str(uuid.uuid4())
1053 if not os.path.exists(paths.private_dir):
1054 os.mkdir(paths.private_dir)
1056 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1058 schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn)
1060 provision_backend = None
1061 if ldap_backend_type:
1062 # We only support an LDAP backend over ldapi://
1064 provision_backend = ProvisionBackend(paths=paths, setup_path=setup_path, lp=lp, credentials=credentials,
1066 message=message, hostname=hostname,
1067 root=root, schema=schema, ldap_backend_type=ldap_backend_type,
1068 ldapadminpass=ldapadminpass,
1069 ldap_backend_extra_port=ldap_backend_extra_port,
1070 ol_mmr_urls=ol_mmr_urls,
1071 slapd_path=slapd_path,
1072 setup_ds_path=setup_ds_path,
1073 ldap_dryrun_mode=ldap_dryrun_mode)
1075 # Now use the backend credentials to access the databases
1076 credentials = provision_backend.credentials
1078 # only install a new shares config db if there is none
1079 if not os.path.exists(paths.shareconf):
1080 message("Setting up share.ldb")
1081 share_ldb = Ldb(paths.shareconf, session_info=session_info,
1082 credentials=credentials, lp=lp)
1083 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1086 message("Setting up secrets.ldb")
1087 secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
1088 session_info=session_info,
1089 credentials=credentials, lp=lp)
1091 message("Setting up the registry")
1092 setup_registry(paths.hklm, setup_path, session_info,
1095 message("Setting up templates db")
1096 setup_templatesdb(paths.templates, setup_path, session_info=session_info,
1099 message("Setting up idmap db")
1100 idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1103 message("Setting up SAM db")
1104 samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info,
1105 credentials=credentials, lp=lp, names=names,
1107 domainsid=domainsid,
1108 schema=schema, domainguid=domainguid, policyguid=policyguid,
1110 adminpass=adminpass, krbtgtpass=krbtgtpass,
1111 invocationid=invocationid,
1112 machinepass=machinepass, dnspass=dnspass,
1113 serverrole=serverrole, ldap_backend=provision_backend)
1115 if serverrole == "domain controller":
1116 if paths.netlogon is None:
1117 message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1118 message("Please either remove %s or see the template at %s" %
1119 ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1120 assert(paths.netlogon is not None)
1122 if paths.sysvol is None:
1123 message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1124 message("Please either remove %s or see the template at %s" %
1125 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1126 assert(paths.sysvol is not None)
1128 policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1129 "{" + policyguid + "}")
1130 os.makedirs(policy_path, 0755)
1131 open(os.path.join(policy_path, "GPT.INI"), 'w').write("")
1132 os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1133 os.makedirs(os.path.join(policy_path, "User"), 0755)
1134 if not os.path.isdir(paths.netlogon):
1135 os.makedirs(paths.netlogon, 0755)
1137 if samdb_fill == FILL_FULL:
1138 setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1139 root_uid=root_uid, nobody_uid=nobody_uid,
1140 users_gid=users_gid, wheel_gid=wheel_gid)
1142 message("Setting up sam.ldb rootDSE marking as synchronized")
1143 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1145 # Only make a zone file on the first DC, it should be replicated with DNS replication
1146 if serverrole == "domain controller":
1147 secrets_ldb = Ldb(paths.secrets, session_info=session_info,
1148 credentials=credentials, lp=lp)
1149 secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1150 netbiosname=names.netbiosname, domainsid=domainsid,
1151 keytab_path=paths.keytab, samdb_url=paths.samdb,
1152 dns_keytab_path=paths.dns_keytab, dnspass=dnspass,
1153 machinepass=machinepass, dnsdomain=names.dnsdomain)
1155 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1156 assert isinstance(domainguid, str)
1157 hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1158 expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1159 scope=SCOPE_SUBTREE)
1160 assert isinstance(hostguid, str)
1162 create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1163 domaindn=names.domaindn, hostip=hostip,
1164 hostip6=hostip6, hostname=names.hostname,
1165 dnspass=dnspass, realm=names.realm,
1166 domainguid=domainguid, hostguid=hostguid)
1168 create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1169 dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1171 create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1172 dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1173 keytab_name=paths.dns_keytab)
1174 message("See %s for an example configuration include file for BIND" % paths.namedconf)
1175 message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1177 create_krb5_conf(paths.krb5conf, setup_path, dnsdomain=names.dnsdomain,
1178 hostname=names.hostname, realm=names.realm)
1179 message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1182 # if backend is openldap, terminate slapd after final provision and check its proper termination
1183 if provision_backend is not None and provision_backend.slapd is not None:
1184 if provision_backend.slapd.poll() is None:
1186 if hasattr(provision_backend.slapd, "terminate"):
1187 provision_backend.slapd.terminate()
1190 os.kill(provision_backend.slapd.pid, signal.SIGTERM)
1192 #and now wait for it to die
1193 provision_backend.slapd.communicate()
1195 # now display slapd_command_file.txt to show how slapd must be started next time
1196 message("Use later the following commandline to start slapd, then Samba:")
1197 slapd_command = "\'" + "\' \'".join(provision_backend.slapd_command) + "\'"
1198 message(slapd_command)
1199 message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
1201 setup_file(setup_path("ldap_backend_startup.sh"), paths.ldapdir + "/ldap_backend_startup.sh", {
1202 "SLAPD_COMMAND" : slapd_command})
1205 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1208 message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1210 message("Once the above files are installed, your Samba4 server will be ready to use")
1211 message("Server Role: %s" % serverrole)
1212 message("Hostname: %s" % names.hostname)
1213 message("NetBIOS Domain: %s" % names.domain)
1214 message("DNS Domain: %s" % names.dnsdomain)
1215 message("DOMAIN SID: %s" % str(domainsid))
1216 if samdb_fill == FILL_FULL:
1217 message("Admin password: %s" % adminpass)
1218 if provision_backend:
1219 if provision_backend.credentials.get_bind_dn() is not None:
1220 message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1222 message("LDAP Admin User: %s" % provision_backend.credentials.get_username())
1224 message("LDAP Admin Password: %s" % provision_backend.credentials.get_password())
1226 result = ProvisionResult()
1227 result.domaindn = domaindn
1228 result.paths = paths
1230 result.samdb = samdb
1235 def provision_become_dc(setup_dir=None,
1236 smbconf=None, targetdir=None, realm=None,
1237 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1239 domain=None, hostname=None, domainsid=None,
1240 adminpass=None, krbtgtpass=None, domainguid=None,
1241 policyguid=None, invocationid=None, machinepass=None,
1242 dnspass=None, root=None, nobody=None, users=None,
1243 wheel=None, backup=None, serverrole=None,
1244 ldap_backend=None, ldap_backend_type=None, sitename=None):
1247 """print a message if quiet is not set."""
1250 return provision(setup_dir, message, system_session(), None,
1251 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm,
1252 rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn, serverdn=serverdn,
1253 domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename)
1256 def setup_db_config(setup_path, dbdir):
1257 """Setup a Berkeley database.
1259 :param setup_path: Setup path function.
1260 :param dbdir: Database directory."""
1261 if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1262 os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1263 if not os.path.isdir(os.path.join(dbdir, "tmp")):
1264 os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1266 setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1267 {"LDAPDBDIR": dbdir})
1269 class ProvisionBackend(object):
1270 def __init__(self, paths=None, setup_path=None, lp=None, credentials=None,
1271 names=None, message=None,
1272 hostname=None, root=None,
1273 schema=None, ldapadminpass=None,
1274 ldap_backend_type=None, ldap_backend_extra_port=None,
1276 setup_ds_path=None, slapd_path=None,
1277 nosync=False, ldap_dryrun_mode=False):
1278 """Provision an LDAP backend for samba4
1280 This works for OpenLDAP and Fedora DS
1283 self.ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.ldapdir, "ldapi"), safe="")
1285 if not os.path.isdir(paths.ldapdir):
1286 os.makedirs(paths.ldapdir, 0700)
1288 if ldap_backend_type == "existing":
1289 #Check to see that this 'existing' LDAP backend in fact exists
1290 ldapi_db = Ldb(self.ldapi_uri, credentials=credentials)
1291 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1292 expression="(objectClass=OpenLDAProotDSE)")
1294 # If we have got here, then we must have a valid connection to the LDAP server, with valid credentials supplied
1295 # This caused them to be set into the long-term database later in the script.
1296 self.credentials = credentials
1297 self.ldap_backend_type = "openldap" #For now, assume existing backends at least emulate OpenLDAP
1300 # we will shortly start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via self.ldapi_uri
1301 # if another instance of slapd is already running
1303 ldapi_db = Ldb(self.ldapi_uri)
1304 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1305 expression="(objectClass=OpenLDAProotDSE)");
1307 f = open(paths.slapdpid, "r")
1310 message("Check for slapd Process with PID: " + str(p) + " and terminate it manually.")
1314 raise("Warning: Another slapd Instance seems already running on this host, listening to " + self.ldapi_uri + ". Please shut it down before you continue. ")
1319 # Try to print helpful messages when the user has not specified the path to slapd
1320 if slapd_path is None:
1321 raise("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
1322 if not os.path.exists(slapd_path):
1323 message (slapd_path)
1324 raise("Warning: Given Path to slapd does not exist!")
1326 schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1328 os.unlink(schemadb_path)
1333 # Put the LDIF of the schema into a database so we can search on
1334 # it to generate schema-dependent configurations in Fedora DS and
1336 os.path.join(paths.ldapdir, "schema-tmp.ldb")
1337 schema.ldb.connect(schemadb_path)
1338 schema.ldb.transaction_start()
1340 # These bits of LDIF are supplied when the Schema object is created
1341 schema.ldb.add_ldif(schema.schema_dn_add)
1342 schema.ldb.modify_ldif(schema.schema_dn_modify)
1343 schema.ldb.add_ldif(schema.schema_data)
1344 schema.ldb.transaction_commit()
1346 self.credentials = Credentials()
1347 self.credentials.guess(lp)
1348 #Kerberos to an ldapi:// backend makes no sense
1349 self.credentials.set_kerberos_state(DONT_USE_KERBEROS)
1350 self.ldap_backend_type = ldap_backend_type
1352 if ldap_backend_type == "fedora-ds":
1353 provision_fds_backend(self, paths=paths, setup_path=setup_path, names=names, message=message,
1354 hostname=hostname, ldapadminpass=ldapadminpass, root=root,
1355 schema=schema, ldap_backend_extra_port=ldap_backend_extra_port,
1356 setup_ds_path=setup_ds_path, slapd_path=slapd_path,
1357 nosync=nosync, ldap_dryrun_mode=ldap_dryrun_mode)
1359 elif ldap_backend_type == "openldap":
1360 provision_openldap_backend(self, paths=paths, setup_path=setup_path, names=names, message=message,
1361 hostname=hostname, ldapadminpass=ldapadminpass, root=root,
1362 schema=schema, ldap_backend_extra_port=ldap_backend_extra_port,
1363 ol_mmr_urls=ol_mmr_urls,
1364 slapd_path=slapd_path,
1365 nosync=nosync, ldap_dryrun_mode=ldap_dryrun_mode)
1367 raise("Unknown LDAP backend type selected")
1369 self.credentials.set_password(ldapadminpass)
1371 # Now start the slapd, so we can provision onto it. We keep the
1372 # subprocess context around, to kill this off at the successful
1374 self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False)
1376 while self.slapd.poll() is None:
1377 # Wait until the socket appears
1379 ldapi_db = Ldb(self.ldapi_uri, lp=lp, credentials=self.credentials)
1380 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1381 expression="(objectClass=OpenLDAProotDSE)")
1382 # If we have got here, then we must have a valid connection to the LDAP server!
1388 raise "slapd died before we could make a connection to it"
1391 def provision_openldap_backend(result, paths=None, setup_path=None, names=None, message=None,
1392 hostname=None, ldapadminpass=None, root=None,
1394 ldap_backend_extra_port=None,
1396 slapd_path=None, nosync=False,
1397 ldap_dryrun_mode=False):
1399 #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
1402 nosync_config = "dbnosync"
1405 attrs = ["linkID", "lDAPDisplayName"]
1406 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)
1408 memberof_config = "# Generated from Samba4 schema\n"
1409 refint_attributes = ""
1410 for i in range (0, len(res)):
1411 expression = "(&(objectclass=attributeSchema)(linkID=%d)(attributeSyntax=2.5.5.1))" % (int(res[i]["linkID"][0])+1)
1412 target = schema.ldb.searchone(basedn=names.schemadn,
1413 expression=expression,
1414 attribute="lDAPDisplayName",
1415 scope=SCOPE_SUBTREE)
1416 if target is not None:
1417 refint_attributes = refint_attributes + " " + res[i]["lDAPDisplayName"][0]
1419 memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1420 { "MEMBER_ATTR" : str(res[i]["lDAPDisplayName"][0]),
1421 "MEMBEROF_ATTR" : str(target) })
1423 refint_config = read_and_sub_file(setup_path("refint.conf"),
1424 { "LINK_ATTRS" : refint_attributes})
1426 res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1428 for i in range (0, len(res)):
1429 index_attr = res[i]["lDAPDisplayName"][0]
1430 if index_attr == "objectGUID":
1431 index_attr = "entryUUID"
1433 index_config += "index " + index_attr + " eq\n"
1435 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1437 mmr_replicator_acl = ""
1438 mmr_serverids_config = ""
1439 mmr_syncrepl_schema_config = ""
1440 mmr_syncrepl_config_config = ""
1441 mmr_syncrepl_user_config = ""
1444 if ol_mmr_urls is not None:
1445 # For now, make these equal
1446 mmr_pass = ldapadminpass
1448 url_list=filter(None,ol_mmr_urls.split(' '))
1449 if (len(url_list) == 1):
1450 url_list=filter(None,ol_mmr_urls.split(','))
1453 mmr_on_config = "MirrorMode On"
1454 mmr_replicator_acl = " by dn=cn=replicator,cn=samba read"
1456 for url in url_list:
1458 mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1459 { "SERVERID" : str(serverid),
1460 "LDAPSERVER" : url })
1463 mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1465 "MMRDN": names.schemadn,
1467 "MMR_PASSWORD": mmr_pass})
1470 mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1472 "MMRDN": names.configdn,
1474 "MMR_PASSWORD": mmr_pass})
1477 mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1479 "MMRDN": names.domaindn,
1481 "MMR_PASSWORD": mmr_pass })
1482 # OpenLDAP cn=config initialisation
1483 olc_syncrepl_config = ""
1485 # if mmr = yes, generate cn=config-replication directives
1486 # and olc_seed.lif for the other mmr-servers
1487 if ol_mmr_urls is not None:
1489 olc_serverids_config = ""
1490 olc_syncrepl_seed_config = ""
1491 olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
1493 for url in url_list:
1495 olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
1496 { "SERVERID" : str(serverid),
1497 "LDAPSERVER" : url })
1500 olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
1503 "MMR_PASSWORD": mmr_pass})
1505 olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1507 "LDAPSERVER" : url})
1509 setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif,
1510 {"OLC_SERVER_ID_CONF": olc_serverids_config,
1511 "OLC_PW": ldapadminpass,
1512 "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
1515 setup_file(setup_path("slapd.conf"), paths.slapdconf,
1516 {"DNSDOMAIN": names.dnsdomain,
1517 "LDAPDIR": paths.ldapdir,
1518 "DOMAINDN": names.domaindn,
1519 "CONFIGDN": names.configdn,
1520 "SCHEMADN": names.schemadn,
1521 "MEMBEROF_CONFIG": memberof_config,
1522 "MIRRORMODE": mmr_on_config,
1523 "REPLICATOR_ACL": mmr_replicator_acl,
1524 "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1525 "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1526 "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1527 "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1528 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
1529 "OLC_MMR_CONFIG": olc_mmr_config,
1530 "REFINT_CONFIG": refint_config,
1531 "INDEX_CONFIG": index_config,
1532 "NOSYNC": nosync_config})
1534 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1535 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1536 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1538 if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba", "cn=samba")):
1539 os.makedirs(os.path.join(paths.ldapdir, "db", "samba", "cn=samba"), 0700)
1541 setup_file(setup_path("cn=samba.ldif"),
1542 os.path.join(paths.ldapdir, "db", "samba", "cn=samba.ldif"),
1543 { "UUID": str(uuid.uuid4()),
1544 "LDAPTIME": timestring(int(time.time()))} )
1545 setup_file(setup_path("cn=samba-admin.ldif"),
1546 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=samba-admin.ldif"),
1547 {"LDAPADMINPASS_B64": b64encode(ldapadminpass),
1548 "UUID": str(uuid.uuid4()),
1549 "LDAPTIME": timestring(int(time.time()))} )
1551 if ol_mmr_urls is not None:
1552 setup_file(setup_path("cn=replicator.ldif"),
1553 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=replicator.ldif"),
1554 {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1555 "UUID": str(uuid.uuid4()),
1556 "LDAPTIME": timestring(int(time.time()))} )
1559 mapping = "schema-map-openldap-2.3"
1560 backend_schema = "backend-schema.schema"
1562 backend_schema_data = schema.ldb.convert_schema_to_openldap("openldap", open(setup_path(mapping), 'r').read())
1563 assert backend_schema_data is not None
1564 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1566 # now we generate the needed strings to start slapd automatically,
1567 # first ldapi_uri...
1568 if ldap_backend_extra_port is not None:
1569 # When we use MMR, we can't use 0.0.0.0 as it uses the name
1570 # specified there as part of it's clue as to it's own name,
1571 # and not to replicate to itself
1572 if ol_mmr_urls is None:
1573 server_port_string = "ldap://0.0.0.0:%d" % ldap_backend_extra_port
1575 server_port_string = "ldap://" + names.hostname + "." + names.dnsdomain +":%d" % ldap_backend_extra_port
1577 server_port_string = ""
1579 # Prepare the 'result' information - the commands to return in particular
1580 result.slapd_provision_command = [slapd_path]
1582 result.slapd_provision_command.append("-F" + paths.olcdir)
1584 result.slapd_provision_command.append("-h")
1586 # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands
1587 result.slapd_command = list(result.slapd_provision_command)
1589 result.slapd_provision_command.append(result.ldapi_uri)
1590 result.slapd_provision_command.append("-d0")
1592 uris = result.ldapi_uri
1593 if server_port_string is not "":
1594 uris = uris + " " + server_port_string
1596 result.slapd_command.append(uris)
1598 # Set the username - done here because Fedora DS still uses the admin DN and simple bind
1599 result.credentials.set_username("samba-admin")
1601 # If we were just looking for crashes up to this point, it's a
1602 # good time to exit before we realise we don't have OpenLDAP on
1604 if ldap_dryrun_mode:
1607 # Finally, convert the configuration into cn=config style!
1608 if not os.path.isdir(paths.olcdir):
1609 os.makedirs(paths.olcdir, 0770)
1611 retcode = subprocess.call([slapd_path, "-Ttest", "-f", paths.slapdconf, "-F", paths.olcdir], close_fds=True, shell=False)
1613 # We can't do this, as OpenLDAP is strange. It gives an error
1614 # output to the above, but does the conversion sucessfully...
1617 # raise("conversion from slapd.conf to cn=config failed")
1619 if not os.path.exists(os.path.join(paths.olcdir, "cn=config.ldif")):
1620 raise("conversion from slapd.conf to cn=config failed")
1622 # Don't confuse the admin by leaving the slapd.conf around
1623 os.remove(paths.slapdconf)
1626 def provision_fds_backend(result, paths=None, setup_path=None, names=None, message=None,
1627 hostname=None, ldapadminpass=None, root=None,
1629 ldap_backend_extra_port=None,
1633 ldap_dryrun_mode=False):
1635 if ldap_backend_extra_port is not None:
1636 serverport = "ServerPort=%d" % ldap_backend_extra_port
1640 setup_file(setup_path("fedorads.inf"), paths.fedoradsinf,
1642 "HOSTNAME": hostname,
1643 "DNSDOMAIN": names.dnsdomain,
1644 "LDAPDIR": paths.ldapdir,
1645 "DOMAINDN": names.domaindn,
1646 "LDAPMANAGERDN": names.ldapmanagerdn,
1647 "LDAPMANAGERPASS": ldapadminpass,
1648 "SERVERPORT": serverport})
1650 setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions,
1651 {"CONFIGDN": names.configdn,
1652 "SCHEMADN": names.schemadn,
1655 mapping = "schema-map-fedora-ds-1.0"
1656 backend_schema = "99_ad.ldif"
1658 # Build a schema file in Fedora DS format
1659 backend_schema_data = schema.ldb.convert_schema_to_openldap("fedora-ds", open(setup_path(mapping), 'r').read())
1660 assert backend_schema_data is not None
1661 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1663 result.credentials.set_bind_dn(names.ldapmanagerdn)
1665 # Destory the target directory, or else setup-ds.pl will complain
1666 fedora_ds_dir = os.path.join(paths.ldapdir, "slapd-samba4")
1667 shutil.rmtree(fedora_ds_dir, True)
1669 result.slapd_provision_command = [slapd_path, "-D", fedora_ds_dir, "-i", paths.slapdpid];
1670 #In the 'provision' command line, stay in the foreground so we can easily kill it
1671 result.slapd_provision_command.append("-d0")
1673 #the command for the final run is the normal script
1674 result.slapd_command = [os.path.join(paths.ldapdir, "slapd-samba4", "start-slapd")]
1676 # If we were just looking for crashes up to this point, it's a
1677 # good time to exit before we realise we don't have Fedora DS on
1678 if ldap_dryrun_mode:
1681 # Try to print helpful messages when the user has not specified the path to the setup-ds tool
1682 if setup_ds_path is None:
1683 raise("Warning: Fedora DS LDAP-Backend must be setup with path to setup-ds, e.g. --setup-ds-path=\"/usr/sbin/setup-ds.pl\"!")
1684 if not os.path.exists(setup_ds_path):
1685 message (setup_ds_path)
1686 raise("Warning: Given Path to slapd does not exist!")
1688 # Run the Fedora DS setup utility
1689 retcode = subprocess.call([setup_ds_path, "--silent", "--file", paths.fedoradsinf], close_fds=True, shell=False)
1691 raise("setup-ds failed")
1693 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1694 """Create a PHP LDAP admin configuration file.
1696 :param path: Path to write the configuration to.
1697 :param setup_path: Function to generate setup paths.
1699 setup_file(setup_path("phpldapadmin-config.php"), path,
1700 {"S4_LDAPI_URI": ldapi_uri})
1703 def create_zone_file(path, setup_path, dnsdomain, domaindn,
1704 hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid):
1705 """Write out a DNS zone file, from the info in the current database.
1707 :param path: Path of the new zone file.
1708 :param setup_path: Setup path function.
1709 :param dnsdomain: DNS Domain name
1710 :param domaindn: DN of the Domain
1711 :param hostip: Local IPv4 IP
1712 :param hostip6: Local IPv6 IP
1713 :param hostname: Local hostname
1714 :param dnspass: Password for DNS
1715 :param realm: Realm name
1716 :param domainguid: GUID of the domain.
1717 :param hostguid: GUID of the host.
1719 assert isinstance(domainguid, str)
1721 if hostip6 is not None:
1722 hostip6_base_line = " IN AAAA " + hostip6
1723 hostip6_host_line = hostname + " IN AAAA " + hostip6
1725 hostip6_base_line = ""
1726 hostip6_host_line = ""
1728 if hostip is not None:
1729 hostip_base_line = " IN A " + hostip
1730 hostip_host_line = hostname + " IN A " + hostip
1732 hostip_base_line = ""
1733 hostip_host_line = ""
1735 setup_file(setup_path("provision.zone"), path, {
1736 "DNSPASS_B64": b64encode(dnspass),
1737 "HOSTNAME": hostname,
1738 "DNSDOMAIN": dnsdomain,
1740 "HOSTIP_BASE_LINE": hostip_base_line,
1741 "HOSTIP_HOST_LINE": hostip_host_line,
1742 "DOMAINGUID": domainguid,
1743 "DATESTRING": time.strftime("%Y%m%d%H"),
1744 "DEFAULTSITE": DEFAULTSITE,
1745 "HOSTGUID": hostguid,
1746 "HOSTIP6_BASE_LINE": hostip6_base_line,
1747 "HOSTIP6_HOST_LINE": hostip6_host_line,
1751 def create_named_conf(path, setup_path, realm, dnsdomain,
1753 """Write out a file containing zone statements suitable for inclusion in a
1754 named.conf file (including GSS-TSIG configuration).
1756 :param path: Path of the new named.conf file.
1757 :param setup_path: Setup path function.
1758 :param realm: Realm name
1759 :param dnsdomain: DNS Domain name
1760 :param private_dir: Path to private directory
1761 :param keytab_name: File name of DNS keytab file
1764 setup_file(setup_path("named.conf"), path, {
1765 "DNSDOMAIN": dnsdomain,
1767 "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1768 "PRIVATE_DIR": private_dir
1771 def create_named_txt(path, setup_path, realm, dnsdomain,
1772 private_dir, keytab_name):
1773 """Write out a file containing zone statements suitable for inclusion in a
1774 named.conf file (including GSS-TSIG configuration).
1776 :param path: Path of the new named.conf file.
1777 :param setup_path: Setup path function.
1778 :param realm: Realm name
1779 :param dnsdomain: DNS Domain name
1780 :param private_dir: Path to private directory
1781 :param keytab_name: File name of DNS keytab file
1784 setup_file(setup_path("named.txt"), path, {
1785 "DNSDOMAIN": dnsdomain,
1787 "DNS_KEYTAB": keytab_name,
1788 "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1789 "PRIVATE_DIR": private_dir
1792 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1793 """Write out a file containing zone statements suitable for inclusion in a
1794 named.conf file (including GSS-TSIG configuration).
1796 :param path: Path of the new named.conf file.
1797 :param setup_path: Setup path function.
1798 :param dnsdomain: DNS Domain name
1799 :param hostname: Local hostname
1800 :param realm: Realm name
1803 setup_file(setup_path("krb5.conf"), path, {
1804 "DNSDOMAIN": dnsdomain,
1805 "HOSTNAME": hostname,