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
43 from credentials import Credentials, DONT_USE_KERBEROS
44 from auth import system_session, admin_session
45 from samba import version, Ldb, substitute_var, valid_netbios_name, check_all_substituted, \
47 from samba.samdb import SamDB
48 from samba.idmap import IDmapDB
49 from samba.dcerpc import security
51 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, timestring
52 from ms_schema import read_ms_schema
53 from ms_display_specifiers import read_ms_ldif
54 from signal import SIGTERM
56 __docformat__ = "restructuredText"
59 class ProvisioningError(ValueError):
64 """Find the setup directory used by provision."""
65 dirname = os.path.dirname(__file__)
66 if "/site-packages/" in dirname:
67 prefix = "/".join(dirname[:dirname.index("/site-packages/")].split("/")[:-2])
68 for suffix in ["share/setup", "share/samba/setup", "setup"]:
69 ret = os.path.join(prefix, suffix)
70 if os.path.isdir(ret):
73 ret = os.path.join(dirname, "../../../setup")
74 if os.path.isdir(ret):
76 raise Exception("Unable to find setup directory.")
79 DEFAULTSITE = "Default-First-Site-Name"
81 class InvalidNetbiosName(Exception):
82 """A specified name was not a valid NetBIOS name."""
83 def __init__(self, name):
84 super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)
87 class ProvisionPaths(object):
100 self.dns_keytab = None
103 self.private_dir = None
105 self.slapdconf = None
106 self.modulesconf = None
107 self.memberofconf = None
108 self.fedoradsinf = None
109 self.fedoradspartitions = None
110 self.fedoradssasl = None
112 self.olmmrserveridsconf = None
113 self.olmmrsyncreplconf = None
116 self.olcseedldif = None
119 class ProvisionNames(object):
126 self.ldapmanagerdn = None
127 self.dnsdomain = None
129 self.netbiosname = None
136 class ProvisionResult(object):
143 class Schema(object):
144 def __init__(self, setup_path, schemadn=None,
145 serverdn=None, sambadn=None, ldap_backend_type=None):
146 """Load schema for the SamDB from the AD schema files and samba4_schema.ldif
148 :param samdb: Load a schema into a SamDB.
149 :param setup_path: Setup path function.
150 :param schemadn: DN of the schema
151 :param serverdn: DN of the server
153 Returns the schema data loaded, to avoid double-parsing when then needing to add it to the db
157 self.schema_data = read_ms_schema(setup_path('ad-schema/MS-AD_Schema_2K8_Attributes.txt'),
158 setup_path('ad-schema/MS-AD_Schema_2K8_Classes.txt'))
159 self.schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
160 self.schema_data = substitute_var(self.schema_data, {"SCHEMADN": schemadn})
161 check_all_substituted(self.schema_data)
163 self.schema_dn_modify = read_and_sub_file(setup_path("provision_schema_basedn_modify.ldif"),
164 {"SCHEMADN": schemadn,
165 "SERVERDN": serverdn,
167 self.schema_dn_add = read_and_sub_file(setup_path("provision_schema_basedn.ldif"),
168 {"SCHEMADN": schemadn
171 prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
172 prefixmap = b64encode(prefixmap)
174 # We don't actually add this ldif, just parse it
175 prefixmap_ldif = "dn: cn=schema\nprefixMap:: %s\n\n" % prefixmap
176 self.ldb.set_schema_from_ldif(prefixmap_ldif, self.schema_data)
179 # Return a hash with the forward attribute as a key and the back as the value
180 def get_linked_attributes(schemadn,schemaldb):
181 attrs = ["linkID", "lDAPDisplayName"]
182 res = schemaldb.search(expression="(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1))", base=schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
184 for i in range (0, len(res)):
185 expression = "(&(objectclass=attributeSchema)(linkID=%d)(attributeSyntax=2.5.5.1))" % (int(res[i]["linkID"][0])+1)
186 target = schemaldb.searchone(basedn=schemadn,
187 expression=expression,
188 attribute="lDAPDisplayName",
190 if target is not None:
191 attributes[str(res[i]["lDAPDisplayName"])]=str(target)
195 def get_dnsyntax_attributes(schemadn,schemaldb):
196 attrs = ["linkID", "lDAPDisplayName"]
197 res = schemaldb.search(expression="(&(!(linkID=*))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1))", base=schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
199 for i in range (0, len(res)):
200 attributes.append(str(res[i]["lDAPDisplayName"]))
205 def check_install(lp, session_info, credentials):
206 """Check whether the current install seems ok.
208 :param lp: Loadparm context
209 :param session_info: Session information
210 :param credentials: Credentials
212 if lp.get("realm") == "":
213 raise Exception("Realm empty")
214 ldb = Ldb(lp.get("sam database"), session_info=session_info,
215 credentials=credentials, lp=lp)
216 if len(ldb.search("(cn=Administrator)")) != 1:
217 raise ProvisioningError("No administrator account found")
220 def findnss(nssfn, names):
221 """Find a user or group from a list of possibilities.
223 :param nssfn: NSS Function to try (should raise KeyError if not found)
224 :param names: Names to check.
225 :return: Value return by first names list.
232 raise KeyError("Unable to find user/group %r" % names)
235 findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
236 findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
239 def read_and_sub_file(file, subst_vars):
240 """Read a file and sub in variables found in it
242 :param file: File to be read (typically from setup directory)
243 param subst_vars: Optional variables to subsitute in the file.
245 data = open(file, 'r').read()
246 if subst_vars is not None:
247 data = substitute_var(data, subst_vars)
248 check_all_substituted(data)
252 def setup_add_ldif(ldb, ldif_path, subst_vars=None):
253 """Setup a ldb in the private dir.
255 :param ldb: LDB file to import data into
256 :param ldif_path: Path of the LDIF file to load
257 :param subst_vars: Optional variables to subsitute in LDIF.
259 assert isinstance(ldif_path, str)
261 data = read_and_sub_file(ldif_path, subst_vars)
265 def setup_modify_ldif(ldb, ldif_path, subst_vars=None):
266 """Modify a ldb in the private dir.
268 :param ldb: LDB object.
269 :param ldif_path: LDIF file path.
270 :param subst_vars: Optional dictionary with substitution variables.
272 data = read_and_sub_file(ldif_path, subst_vars)
274 ldb.modify_ldif(data)
277 def setup_ldb(ldb, ldif_path, subst_vars):
278 """Import a LDIF a file into a LDB handle, optionally substituting variables.
280 :note: Either all LDIF data will be added or none (using transactions).
282 :param ldb: LDB file to import into.
283 :param ldif_path: Path to the LDIF file.
284 :param subst_vars: Dictionary with substitution variables.
286 assert ldb is not None
287 ldb.transaction_start()
289 setup_add_ldif(ldb, ldif_path, subst_vars)
291 ldb.transaction_cancel()
293 ldb.transaction_commit()
296 def setup_file(template, fname, subst_vars):
297 """Setup a file in the private dir.
299 :param template: Path of the template file.
300 :param fname: Path of the file to create.
301 :param subst_vars: Substitution variables.
305 if os.path.exists(f):
308 data = read_and_sub_file(template, subst_vars)
309 open(f, 'w').write(data)
312 def provision_paths_from_lp(lp, dnsdomain):
313 """Set the default paths for provisioning.
315 :param lp: Loadparm context.
316 :param dnsdomain: DNS Domain name
318 paths = ProvisionPaths()
319 paths.private_dir = lp.get("private dir")
320 paths.keytab = "secrets.keytab"
321 paths.dns_keytab = "dns.keytab"
323 paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
324 paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb")
325 paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb")
326 paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
327 paths.dns = os.path.join(paths.private_dir, dnsdomain + ".zone")
328 paths.namedconf = os.path.join(paths.private_dir, "named.conf")
329 paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
330 paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
331 paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
332 paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
333 paths.phpldapadminconfig = os.path.join(paths.private_dir,
334 "phpldapadmin-config.php")
335 paths.ldapdir = os.path.join(paths.private_dir,
337 paths.slapdconf = os.path.join(paths.ldapdir,
339 paths.slapdpid = os.path.join(paths.ldapdir,
341 paths.modulesconf = os.path.join(paths.ldapdir,
343 paths.memberofconf = os.path.join(paths.ldapdir,
345 paths.fedoradsinf = os.path.join(paths.ldapdir,
347 paths.fedoradspartitions = os.path.join(paths.ldapdir,
348 "fedorads-partitions.ldif")
349 paths.fedoradssasl = os.path.join(paths.ldapdir,
350 "fedorads-sasl.ldif")
351 paths.fedoradssamba = os.path.join(paths.ldapdir,
352 "fedorads-samba.ldif")
353 paths.olmmrserveridsconf = os.path.join(paths.ldapdir,
354 "mmr_serverids.conf")
355 paths.olmmrsyncreplconf = os.path.join(paths.ldapdir,
357 paths.olcdir = os.path.join(paths.ldapdir,
359 paths.olcseedldif = os.path.join(paths.ldapdir,
361 paths.hklm = "hklm.ldb"
362 paths.hkcr = "hkcr.ldb"
363 paths.hkcu = "hkcu.ldb"
364 paths.hku = "hku.ldb"
365 paths.hkpd = "hkpd.ldb"
366 paths.hkpt = "hkpt.ldb"
368 paths.sysvol = lp.get("path", "sysvol")
370 paths.netlogon = lp.get("path", "netlogon")
372 paths.smbconf = lp.configfile
377 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
378 serverrole=None, rootdn=None, domaindn=None, configdn=None,
379 schemadn=None, serverdn=None, sitename=None, sambadn=None):
380 """Guess configuration settings to use."""
383 hostname = socket.gethostname().split(".")[0].lower()
385 netbiosname = hostname.upper()
386 if not valid_netbios_name(netbiosname):
387 raise InvalidNetbiosName(netbiosname)
389 hostname = hostname.lower()
391 if dnsdomain is None:
392 dnsdomain = lp.get("realm")
394 if serverrole is None:
395 serverrole = lp.get("server role")
397 assert dnsdomain is not None
398 realm = dnsdomain.upper()
400 if lp.get("realm").upper() != realm:
401 raise Exception("realm '%s' in %s must match chosen realm '%s'" %
402 (lp.get("realm"), lp.configfile, realm))
404 dnsdomain = dnsdomain.lower()
406 if serverrole == "domain controller":
408 domain = lp.get("workgroup")
410 domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
411 if lp.get("workgroup").upper() != domain.upper():
412 raise Exception("workgroup '%s' in smb.conf must match chosen domain '%s'",
413 lp.get("workgroup"), domain)
417 domaindn = "CN=" + netbiosname
419 assert domain is not None
420 domain = domain.upper()
421 if not valid_netbios_name(domain):
422 raise InvalidNetbiosName(domain)
424 if netbiosname.upper() == realm.upper():
425 raise Exception("realm %s must not be equal to netbios domain name %s", realm, netbiosname)
427 if hostname.upper() == realm.upper():
428 raise Exception("realm %s must not be equal to hostname %s", realm, hostname)
430 if domain.upper() == realm.upper():
431 raise Exception("realm %s must not be equal to domain name %s", realm, domain)
437 configdn = "CN=Configuration," + rootdn
439 schemadn = "CN=Schema," + configdn
446 names = ProvisionNames()
447 names.rootdn = rootdn
448 names.domaindn = domaindn
449 names.configdn = configdn
450 names.schemadn = schemadn
451 names.sambadn = sambadn
452 names.ldapmanagerdn = "CN=Manager," + rootdn
453 names.dnsdomain = dnsdomain
454 names.domain = domain
456 names.netbiosname = netbiosname
457 names.hostname = hostname
458 names.sitename = sitename
459 names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (netbiosname, sitename, configdn)
464 def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
466 """Create a new smb.conf file based on a couple of basic settings.
468 assert smbconf is not None
470 hostname = socket.gethostname().split(".")[0].lower()
472 if serverrole is None:
473 serverrole = "standalone"
475 assert serverrole in ("domain controller", "member server", "standalone")
476 if serverrole == "domain controller":
478 elif serverrole == "member server":
479 smbconfsuffix = "member"
480 elif serverrole == "standalone":
481 smbconfsuffix = "standalone"
483 assert domain is not None
484 assert realm is not None
486 default_lp = param.LoadParm()
487 #Load non-existant file
488 if os.path.exists(smbconf):
489 default_lp.load(smbconf)
491 if targetdir is not None:
492 privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
493 lockdir_line = "lock dir = " + os.path.abspath(targetdir)
495 default_lp.set("lock dir", os.path.abspath(targetdir))
500 sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
501 netlogon = os.path.join(sysvol, realm.lower(), "scripts")
503 setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
505 "HOSTNAME": hostname,
508 "SERVERROLE": serverrole,
509 "NETLOGONPATH": netlogon,
510 "SYSVOLPATH": sysvol,
511 "PRIVATEDIR_LINE": privatedir_line,
512 "LOCKDIR_LINE": lockdir_line
516 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
517 users_gid, wheel_gid):
518 """setup reasonable name mappings for sam names to unix names.
520 :param samdb: SamDB object.
521 :param idmap: IDmap db object.
522 :param sid: The domain sid.
523 :param domaindn: The domain DN.
524 :param root_uid: uid of the UNIX root user.
525 :param nobody_uid: uid of the UNIX nobody user.
526 :param users_gid: gid of the UNIX users group.
527 :param wheel_gid: gid of the UNIX wheel group."""
529 idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
530 idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
532 idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
533 idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
535 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
537 serverrole, ldap_backend=None,
539 """Setup the partitions for the SAM database.
541 Alternatively, provision() may call this, and then populate the database.
543 :note: This will wipe the Sam Database!
545 :note: This function always removes the local SAM LDB file. The erase
546 parameter controls whether to erase the existing data, which
547 may not be stored locally but in LDAP.
549 assert session_info is not None
551 # We use options=["modules:"] to stop the modules loading - we
552 # just want to wipe and re-initialise the database, not start it up
555 samdb = Ldb(url=samdb_path, session_info=session_info,
556 credentials=credentials, lp=lp, options=["modules:"])
558 samdb.erase_except_schema_controlled()
560 os.unlink(samdb_path)
561 samdb = Ldb(url=samdb_path, session_info=session_info,
562 credentials=credentials, lp=lp, options=["modules:"])
564 samdb.erase_except_schema_controlled()
567 #Add modules to the list to activate them by default
568 #beware often order is important
570 # Some Known ordering constraints:
571 # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
572 # - objectclass must be before password_hash, because password_hash checks
573 # that the objectclass is of type person (filled in by objectclass
574 # module when expanding the objectclass list)
575 # - partition must be last
576 # - each partition has its own module list then
577 modules_list = ["rootdse",
596 "extended_dn_out_ldb"]
597 modules_list2 = ["show_deleted",
600 domaindn_ldb = "users.ldb"
601 configdn_ldb = "configuration.ldb"
602 schemadn_ldb = "schema.ldb"
603 if ldap_backend is not None:
604 domaindn_ldb = ldap_backend.ldapi_uri
605 configdn_ldb = ldap_backend.ldapi_uri
606 schemadn_ldb = ldap_backend.ldapi_uri
608 if ldap_backend.ldap_backend_type == "fedora-ds":
609 backend_modules = ["nsuniqueid", "paged_searches"]
610 # We can handle linked attributes here, as we don't have directory-side subtree operations
611 tdb_modules_list = ["linked_attributes", "extended_dn_out_dereference"]
612 elif ldap_backend.ldap_backend_type == "openldap":
613 backend_modules = ["entryuuid", "paged_searches"]
614 # OpenLDAP handles subtree renames, so we don't want to do any of these things
615 tdb_modules_list = ["extended_dn_out_dereference"]
617 elif serverrole == "domain controller":
618 tdb_modules_list.insert(0, "repl_meta_data")
621 backend_modules = ["objectguid"]
623 if tdb_modules_list is None:
624 tdb_modules_list_as_string = ""
626 tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
628 samdb.transaction_start()
630 message("Setting up sam.ldb partitions and settings")
631 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
632 "SCHEMADN": names.schemadn,
633 "SCHEMADN_LDB": schemadn_ldb,
634 "SCHEMADN_MOD2": ",objectguid",
635 "CONFIGDN": names.configdn,
636 "CONFIGDN_LDB": configdn_ldb,
637 "DOMAINDN": names.domaindn,
638 "DOMAINDN_LDB": domaindn_ldb,
639 "SCHEMADN_MOD": "schema_fsmo,instancetype",
640 "CONFIGDN_MOD": "naming_fsmo,instancetype",
641 "DOMAINDN_MOD": "pdc_fsmo,instancetype",
642 "MODULES_LIST": ",".join(modules_list),
643 "TDB_MODULES_LIST": tdb_modules_list_as_string,
644 "MODULES_LIST2": ",".join(modules_list2),
645 "BACKEND_MOD": ",".join(backend_modules),
648 samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
650 message("Setting up sam.ldb rootDSE")
651 setup_samdb_rootdse(samdb, setup_path, names)
654 samdb.transaction_cancel()
657 samdb.transaction_commit()
661 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain,
662 netbiosname, domainsid, keytab_path, samdb_url,
663 dns_keytab_path, dnspass, machinepass):
664 """Add DC-specific bits to a secrets database.
666 :param secretsdb: Ldb Handle to the secrets database
667 :param setup_path: Setup path function
668 :param machinepass: Machine password
670 setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), {
671 "MACHINEPASS_B64": b64encode(machinepass),
674 "DNSDOMAIN": dnsdomain,
675 "DOMAINSID": str(domainsid),
676 "SECRETS_KEYTAB": keytab_path,
677 "NETBIOSNAME": netbiosname,
678 "SAM_LDB": samdb_url,
679 "DNS_KEYTAB": dns_keytab_path,
680 "DNSPASS_B64": b64encode(dnspass),
684 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
685 """Setup the secrets database.
687 :param path: Path to the secrets database.
688 :param setup_path: Get the path to a setup file.
689 :param session_info: Session info.
690 :param credentials: Credentials
691 :param lp: Loadparm context
692 :return: LDB handle for the created secrets database
694 if os.path.exists(path):
696 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
699 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
700 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
702 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
704 if credentials is not None and credentials.authentication_requested():
705 if credentials.get_bind_dn() is not None:
706 setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
707 "LDAPMANAGERDN": credentials.get_bind_dn(),
708 "LDAPMANAGERPASS_B64": b64encode(credentials.get_password())
711 setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
712 "LDAPADMINUSER": credentials.get_username(),
713 "LDAPADMINREALM": credentials.get_realm(),
714 "LDAPADMINPASS_B64": b64encode(credentials.get_password())
719 def setup_registry(path, setup_path, session_info, lp):
720 """Setup the registry.
722 :param path: Path to the registry database
723 :param setup_path: Function that returns the path to a setup.
724 :param session_info: Session information
725 :param credentials: Credentials
726 :param lp: Loadparm context
728 reg = registry.Registry()
729 hive = registry.open_ldb(path, session_info=session_info,
731 reg.mount_hive(hive, registry.HKEY_LOCAL_MACHINE)
732 provision_reg = setup_path("provision.reg")
733 assert os.path.exists(provision_reg)
734 reg.diff_apply(provision_reg)
737 def setup_idmapdb(path, setup_path, session_info, lp):
738 """Setup the idmap database.
740 :param path: path to the idmap database
741 :param setup_path: Function that returns a path to a setup file
742 :param session_info: Session information
743 :param credentials: Credentials
744 :param lp: Loadparm context
746 if os.path.exists(path):
749 idmap_ldb = IDmapDB(path, session_info=session_info,
753 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
757 def setup_samdb_rootdse(samdb, setup_path, names):
758 """Setup the SamDB rootdse.
760 :param samdb: Sam Database handle
761 :param setup_path: Obtain setup path
763 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
764 "SCHEMADN": names.schemadn,
765 "NETBIOSNAME": names.netbiosname,
766 "DNSDOMAIN": names.dnsdomain,
767 "REALM": names.realm,
768 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
769 "DOMAINDN": names.domaindn,
770 "ROOTDN": names.rootdn,
771 "CONFIGDN": names.configdn,
772 "SERVERDN": names.serverdn,
776 def setup_self_join(samdb, names,
777 machinepass, dnspass,
778 domainsid, invocationid, setup_path,
779 policyguid, policyguid_dc, domainControllerFunctionality):
780 """Join a host to its own domain."""
781 assert isinstance(invocationid, str)
782 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
783 "CONFIGDN": names.configdn,
784 "SCHEMADN": names.schemadn,
785 "DOMAINDN": names.domaindn,
786 "SERVERDN": names.serverdn,
787 "INVOCATIONID": invocationid,
788 "NETBIOSNAME": names.netbiosname,
789 "DEFAULTSITE": names.sitename,
790 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
791 "MACHINEPASS_B64": b64encode(machinepass),
792 "DNSPASS_B64": b64encode(dnspass),
793 "REALM": names.realm,
794 "DOMAIN": names.domain,
795 "DNSDOMAIN": names.dnsdomain,
796 "SAMBA_VERSION_STRING": version,
797 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
799 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
800 "POLICYGUID": policyguid,
801 "POLICYGUID_DC": policyguid_dc,
802 "DNSDOMAIN": names.dnsdomain,
803 "DOMAINSID": str(domainsid),
804 "DOMAINDN": names.domaindn})
806 # add the NTDSGUID based SPNs
807 ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
808 names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
809 expression="", scope=SCOPE_BASE)
810 assert isinstance(names.ntdsguid, str)
812 # Setup fSMORoleOwner entries to point at the newly created DC entry
813 setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
814 "DOMAIN": names.domain,
815 "DNSDOMAIN": names.dnsdomain,
816 "DOMAINDN": names.domaindn,
817 "CONFIGDN": names.configdn,
818 "SCHEMADN": names.schemadn,
819 "DEFAULTSITE": names.sitename,
820 "SERVERDN": names.serverdn,
821 "NETBIOSNAME": names.netbiosname,
822 "NTDSGUID": names.ntdsguid
826 def setup_samdb(path, setup_path, session_info, credentials, lp,
828 domainsid, domainguid, policyguid, policyguid_dc,
829 fill, adminpass, krbtgtpass,
830 machinepass, invocationid, dnspass,
831 serverrole, schema=None, ldap_backend=None):
832 """Setup a complete SAM Database.
834 :note: This will wipe the main SAM database file!
837 domainFunctionality = DS_BEHAVIOR_WIN2008
838 forestFunctionality = DS_BEHAVIOR_WIN2008
839 domainControllerFunctionality = DS_BEHAVIOR_WIN2008
841 # Also wipes the database
842 setup_samdb_partitions(path, setup_path, message=message, lp=lp,
843 credentials=credentials, session_info=session_info,
845 ldap_backend=ldap_backend, serverrole=serverrole)
848 schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn,
849 sambadn=names.sambadn, ldap_backend_type=ldap_backend.ldap_backend_type)
851 # Load the database, but importantly, use Ldb not SamDB as we don't want to load the global schema
852 samdb = Ldb(session_info=session_info,
853 credentials=credentials, lp=lp)
855 message("Pre-loading the Samba 4 and AD schema")
857 # Load the schema from the one we computed earlier
858 samdb.set_schema_from_ldb(schema.ldb)
860 # And now we can connect to the DB - the schema won't be loaded from the DB
864 samdb.load_ldif_file_add(setup_path("provision_options.ldif"))
869 samdb.transaction_start()
871 message("Erasing data from partitions")
872 # Load the schema (again). This time it will force a reindex,
873 # and will therefore make the erase_partitions() below
874 # computationally sane
875 samdb.set_schema_from_ldb(schema.ldb)
876 samdb.erase_partitions()
878 # Set the domain functionality levels onto the database.
879 # Various module (the password_hash module in particular) need
880 # to know what level of AD we are emulating.
882 # These will be fixed into the database via the database
883 # modifictions below, but we need them set from the start.
884 samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
885 samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
886 samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
888 samdb.set_domain_sid(str(domainsid))
889 if serverrole == "domain controller":
890 samdb.set_invocation_id(invocationid)
892 message("Adding DomainDN: %s" % names.domaindn)
893 if serverrole == "domain controller":
894 domain_oc = "domainDNS"
896 domain_oc = "samba4LocalDomain"
898 #impersonate domain admin
899 admin_session_info = admin_session(lp, str(domainsid))
900 samdb.set_session_info(admin_session_info)
902 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
903 "DOMAINDN": names.domaindn,
904 "DOMAIN_OC": domain_oc
907 message("Modifying DomainDN: " + names.domaindn + "")
908 if domainguid is not None:
909 domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
913 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
914 "LDAPTIME": timestring(int(time.time())),
915 "DOMAINSID": str(domainsid),
916 "SCHEMADN": names.schemadn,
917 "NETBIOSNAME": names.netbiosname,
918 "DEFAULTSITE": names.sitename,
919 "CONFIGDN": names.configdn,
920 "SERVERDN": names.serverdn,
921 "POLICYGUID": policyguid,
922 "DOMAINDN": names.domaindn,
923 "DOMAINGUID_MOD": domainguid_mod,
924 "DOMAIN_FUNCTIONALITY": str(domainFunctionality)
927 message("Adding configuration container")
928 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
929 "CONFIGDN": names.configdn,
931 message("Modifying configuration container")
932 setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
933 "CONFIGDN": names.configdn,
934 "SCHEMADN": names.schemadn,
937 # The LDIF here was created when the Schema object was constructed
938 message("Setting up sam.ldb schema")
939 samdb.add_ldif(schema.schema_dn_add)
940 samdb.modify_ldif(schema.schema_dn_modify)
941 samdb.write_prefixes_from_schema()
942 samdb.add_ldif(schema.schema_data)
943 setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
944 {"SCHEMADN": names.schemadn})
946 message("Setting up sam.ldb configuration data")
947 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
948 "CONFIGDN": names.configdn,
949 "NETBIOSNAME": names.netbiosname,
950 "DEFAULTSITE": names.sitename,
951 "DNSDOMAIN": names.dnsdomain,
952 "DOMAIN": names.domain,
953 "SCHEMADN": names.schemadn,
954 "DOMAINDN": names.domaindn,
955 "SERVERDN": names.serverdn,
956 "FOREST_FUNCTIONALALITY": str(forestFunctionality)
959 message("Setting up display specifiers")
960 display_specifiers_ldif = read_ms_ldif(setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
961 display_specifiers_ldif = substitute_var(display_specifiers_ldif, {"CONFIGDN": names.configdn})
962 check_all_substituted(display_specifiers_ldif)
963 samdb.add_ldif(display_specifiers_ldif)
965 message("Adding users container")
966 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
967 "DOMAINDN": names.domaindn})
968 message("Modifying users container")
969 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
970 "DOMAINDN": names.domaindn})
971 message("Adding computers container")
972 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
973 "DOMAINDN": names.domaindn})
974 message("Modifying computers container")
975 setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
976 "DOMAINDN": names.domaindn})
977 message("Setting up sam.ldb data")
978 setup_add_ldif(samdb, setup_path("provision.ldif"), {
979 "DOMAINDN": names.domaindn,
980 "NETBIOSNAME": names.netbiosname,
981 "DEFAULTSITE": names.sitename,
982 "CONFIGDN": names.configdn,
983 "SERVERDN": names.serverdn,
984 "POLICYGUID_DC": policyguid_dc
987 if fill == FILL_FULL:
988 message("Setting up sam.ldb users and groups")
989 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
990 "DOMAINDN": names.domaindn,
991 "DOMAINSID": str(domainsid),
992 "CONFIGDN": names.configdn,
993 "ADMINPASS_B64": b64encode(adminpass),
994 "KRBTGTPASS_B64": b64encode(krbtgtpass),
997 if serverrole == "domain controller":
998 message("Setting up self join")
999 setup_self_join(samdb, names=names, invocationid=invocationid,
1001 machinepass=machinepass,
1002 domainsid=domainsid, policyguid=policyguid,
1003 policyguid_dc=policyguid_dc,
1004 setup_path=setup_path,
1005 domainControllerFunctionality=domainControllerFunctionality)
1006 # add the NTDSGUID based SPNs
1007 ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
1008 names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
1009 expression="", scope=SCOPE_BASE)
1010 assert isinstance(names.ntdsguid, str)
1013 samdb.transaction_cancel()
1016 samdb.transaction_commit()
1021 FILL_NT4SYNC = "NT4SYNC"
1025 def provision(setup_dir, message, session_info,
1026 credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL,
1028 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1030 domain=None, hostname=None, hostip=None, hostip6=None,
1031 domainsid=None, adminpass=None, ldapadminpass=None,
1032 krbtgtpass=None, domainguid=None,
1033 policyguid=None, policyguid_dc=None, invocationid=None,
1035 dnspass=None, root=None, nobody=None, users=None,
1036 wheel=None, backup=None, aci=None, serverrole=None,
1037 ldap_backend_extra_port=None, ldap_backend_type=None,
1039 ol_mmr_urls=None, ol_olc=None,
1040 setup_ds_path=None, slapd_path=None, nosync=False,
1041 ldap_dryrun_mode=False):
1044 :note: caution, this wipes all existing data!
1047 def setup_path(file):
1048 return os.path.join(setup_dir, file)
1050 if domainsid is None:
1051 domainsid = security.random_sid()
1053 # create/adapt the group policy GUIDs
1054 if policyguid is None:
1055 policyguid = str(uuid.uuid4())
1056 policyguid = policyguid.upper()
1057 if policyguid_dc is None:
1058 policyguid_dc = str(uuid.uuid4())
1059 policyguid_dc = policyguid_dc.upper()
1061 if adminpass is None:
1062 adminpass = glue.generate_random_str(12)
1063 if krbtgtpass is None:
1064 krbtgtpass = glue.generate_random_str(12)
1065 if machinepass is None:
1066 machinepass = glue.generate_random_str(12)
1068 dnspass = glue.generate_random_str(12)
1069 if ldapadminpass is None:
1070 #Make a new, random password between Samba and it's LDAP server
1071 ldapadminpass=glue.generate_random_str(12)
1074 root_uid = findnss_uid([root or "root"])
1075 nobody_uid = findnss_uid([nobody or "nobody"])
1076 users_gid = findnss_gid([users or "users"])
1078 wheel_gid = findnss_gid(["wheel", "adm"])
1080 wheel_gid = findnss_gid([wheel])
1082 if targetdir is not None:
1083 if (not os.path.exists(os.path.join(targetdir, "etc"))):
1084 os.makedirs(os.path.join(targetdir, "etc"))
1085 smbconf = os.path.join(targetdir, "etc", "smb.conf")
1086 elif smbconf is None:
1087 smbconf = param.default_path()
1089 # only install a new smb.conf if there isn't one there already
1090 if not os.path.exists(smbconf):
1091 make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
1094 lp = param.LoadParm()
1097 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1098 dnsdomain=realm, serverrole=serverrole, sitename=sitename,
1099 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1102 paths = provision_paths_from_lp(lp, names.dnsdomain)
1106 hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1107 except socket.gaierror, (socket.EAI_NODATA, msg):
1112 hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1113 except socket.gaierror, (socket.EAI_NODATA, msg):
1116 if serverrole is None:
1117 serverrole = lp.get("server role")
1119 assert serverrole in ("domain controller", "member server", "standalone")
1120 if invocationid is None and serverrole == "domain controller":
1121 invocationid = str(uuid.uuid4())
1123 if not os.path.exists(paths.private_dir):
1124 os.mkdir(paths.private_dir)
1126 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1128 schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn,
1129 sambadn=names.sambadn, ldap_backend_type=ldap_backend_type)
1131 secrets_credentials = credentials
1132 provision_backend = None
1133 if ldap_backend_type:
1134 # We only support an LDAP backend over ldapi://
1136 provision_backend = ProvisionBackend(paths=paths, setup_path=setup_path,
1137 lp=lp, credentials=credentials,
1139 message=message, hostname=hostname,
1140 root=root, schema=schema,
1141 ldap_backend_type=ldap_backend_type,
1142 ldapadminpass=ldapadminpass,
1143 ldap_backend_extra_port=ldap_backend_extra_port,
1144 ol_mmr_urls=ol_mmr_urls,
1145 slapd_path=slapd_path,
1146 setup_ds_path=setup_ds_path,
1147 ldap_dryrun_mode=ldap_dryrun_mode)
1149 # Now use the backend credentials to access the databases
1150 credentials = provision_backend.credentials
1151 secrets_credentials = provision_backend.adminCredentials
1152 ldapi_url = provision_backend.ldapi_uri
1154 # only install a new shares config db if there is none
1155 if not os.path.exists(paths.shareconf):
1156 message("Setting up share.ldb")
1157 share_ldb = Ldb(paths.shareconf, session_info=session_info,
1158 credentials=credentials, lp=lp)
1159 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1162 message("Setting up secrets.ldb")
1163 secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
1164 session_info=session_info,
1165 credentials=secrets_credentials, lp=lp)
1167 message("Setting up the registry")
1168 setup_registry(paths.hklm, setup_path, session_info,
1171 message("Setting up idmap db")
1172 idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1175 message("Setting up SAM db")
1176 samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info,
1177 credentials=credentials, lp=lp, names=names,
1179 domainsid=domainsid,
1180 schema=schema, domainguid=domainguid,
1181 policyguid=policyguid, policyguid_dc=policyguid_dc,
1183 adminpass=adminpass, krbtgtpass=krbtgtpass,
1184 invocationid=invocationid,
1185 machinepass=machinepass, dnspass=dnspass,
1186 serverrole=serverrole, ldap_backend=provision_backend)
1188 if serverrole == "domain controller":
1189 if paths.netlogon is None:
1190 message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1191 message("Please either remove %s or see the template at %s" %
1192 ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1193 assert(paths.netlogon is not None)
1195 if paths.sysvol is None:
1196 message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1197 message("Please either remove %s or see the template at %s" %
1198 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1199 assert(paths.sysvol is not None)
1201 # Set up group policies (domain policy and domain controller policy)
1203 policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1204 "{" + policyguid + "}")
1205 os.makedirs(policy_path, 0755)
1206 open(os.path.join(policy_path, "GPT.INI"), 'w').write(
1207 "[General]\r\nVersion=65544")
1208 os.makedirs(os.path.join(policy_path, "MACHINE"), 0755)
1209 os.makedirs(os.path.join(policy_path, "USER"), 0755)
1211 policy_path_dc = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1212 "{" + policyguid_dc + "}")
1213 os.makedirs(policy_path_dc, 0755)
1214 open(os.path.join(policy_path_dc, "GPT.INI"), 'w').write(
1215 "[General]\r\nVersion=2")
1216 os.makedirs(os.path.join(policy_path_dc, "MACHINE"), 0755)
1217 os.makedirs(os.path.join(policy_path_dc, "USER"), 0755)
1219 if not os.path.isdir(paths.netlogon):
1220 os.makedirs(paths.netlogon, 0755)
1222 if samdb_fill == FILL_FULL:
1223 setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1224 root_uid=root_uid, nobody_uid=nobody_uid,
1225 users_gid=users_gid, wheel_gid=wheel_gid)
1227 message("Setting up sam.ldb rootDSE marking as synchronized")
1228 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1230 # Only make a zone file on the first DC, it should be replicated with DNS replication
1231 if serverrole == "domain controller":
1232 secrets_ldb = Ldb(paths.secrets, session_info=session_info,
1233 credentials=credentials, lp=lp)
1234 secretsdb_become_dc(secrets_ldb, setup_path, domain=domain,
1236 netbiosname=names.netbiosname,
1237 domainsid=domainsid,
1238 keytab_path=paths.keytab, samdb_url=paths.samdb,
1239 dns_keytab_path=paths.dns_keytab,
1240 dnspass=dnspass, machinepass=machinepass,
1241 dnsdomain=names.dnsdomain)
1243 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1244 assert isinstance(domainguid, str)
1246 create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1247 domaindn=names.domaindn, hostip=hostip,
1248 hostip6=hostip6, hostname=names.hostname,
1249 dnspass=dnspass, realm=names.realm,
1250 domainguid=domainguid, ntdsguid=names.ntdsguid)
1252 create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1253 dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1255 create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1256 dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1257 keytab_name=paths.dns_keytab)
1258 message("See %s for an example configuration include file for BIND" % paths.namedconf)
1259 message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1261 create_krb5_conf(paths.krb5conf, setup_path,
1262 dnsdomain=names.dnsdomain, hostname=names.hostname,
1264 message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1267 if provision_backend is not None:
1268 if ldap_backend_type == "fedora-ds":
1269 ldapi_db = Ldb(provision_backend.ldapi_uri, lp=lp, credentials=credentials)
1271 # delete default SASL mappings
1272 res = ldapi_db.search(expression="(!(cn=samba-admin mapping))", base="cn=mapping,cn=sasl,cn=config", scope=SCOPE_ONELEVEL, attrs=["dn"])
1274 # configure in-directory access control on Fedora DS via the aci attribute (over a direct ldapi:// socket)
1275 for i in range (0, len(res)):
1276 dn = str(res[i]["dn"])
1279 aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % names.sambadn
1282 m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci")
1284 m.dn = ldb.Dn(1, names.domaindn)
1287 m.dn = ldb.Dn(1, names.configdn)
1290 m.dn = ldb.Dn(1, names.schemadn)
1293 # if an LDAP backend is in use, terminate slapd after final provision and check its proper termination
1294 if provision_backend.slapd.poll() is None:
1296 if hasattr(provision_backend.slapd, "terminate"):
1297 provision_backend.slapd.terminate()
1299 # Older python versions don't have .terminate()
1301 os.kill(provision_backend.slapd.pid, signal.SIGTERM)
1303 #and now wait for it to die
1304 provision_backend.slapd.communicate()
1306 # now display slapd_command_file.txt to show how slapd must be started next time
1307 message("Use later the following commandline to start slapd, then Samba:")
1308 slapd_command = "\'" + "\' \'".join(provision_backend.slapd_command) + "\'"
1309 message(slapd_command)
1310 message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
1312 setup_file(setup_path("ldap_backend_startup.sh"), paths.ldapdir + "/ldap_backend_startup.sh", {
1313 "SLAPD_COMMAND" : slapd_command})
1316 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1319 message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1321 message("Once the above files are installed, your Samba4 server will be ready to use")
1322 message("Server Role: %s" % serverrole)
1323 message("Hostname: %s" % names.hostname)
1324 message("NetBIOS Domain: %s" % names.domain)
1325 message("DNS Domain: %s" % names.dnsdomain)
1326 message("DOMAIN SID: %s" % str(domainsid))
1327 if samdb_fill == FILL_FULL:
1328 message("Admin password: %s" % adminpass)
1329 if provision_backend:
1330 if provision_backend.credentials.get_bind_dn() is not None:
1331 message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1333 message("LDAP Admin User: %s" % provision_backend.credentials.get_username())
1335 message("LDAP Admin Password: %s" % provision_backend.credentials.get_password())
1337 result = ProvisionResult()
1338 result.domaindn = domaindn
1339 result.paths = paths
1341 result.samdb = samdb
1346 def provision_become_dc(setup_dir=None,
1347 smbconf=None, targetdir=None, realm=None,
1348 rootdn=None, domaindn=None, schemadn=None,
1349 configdn=None, serverdn=None,
1350 domain=None, hostname=None, domainsid=None,
1351 adminpass=None, krbtgtpass=None, domainguid=None,
1352 policyguid=None, policyguid_dc=None, invocationid=None,
1354 dnspass=None, root=None, nobody=None, users=None,
1355 wheel=None, backup=None, serverrole=None,
1356 ldap_backend=None, ldap_backend_type=None,
1357 sitename=None, debuglevel=1):
1360 """print a message if quiet is not set."""
1363 glue.set_debug_level(debuglevel)
1365 return provision(setup_dir, message, system_session(), None,
1366 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1367 realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1368 configdn=configdn, serverdn=serverdn, domain=domain,
1369 hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
1370 machinepass=machinepass, serverrole="domain controller",
1374 def setup_db_config(setup_path, dbdir):
1375 """Setup a Berkeley database.
1377 :param setup_path: Setup path function.
1378 :param dbdir: Database directory."""
1379 if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1380 os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1381 if not os.path.isdir(os.path.join(dbdir, "tmp")):
1382 os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1384 setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1385 {"LDAPDBDIR": dbdir})
1387 class ProvisionBackend(object):
1388 def __init__(self, paths=None, setup_path=None, lp=None, credentials=None,
1389 names=None, message=None,
1390 hostname=None, root=None,
1391 schema=None, ldapadminpass=None,
1392 ldap_backend_type=None, ldap_backend_extra_port=None,
1394 setup_ds_path=None, slapd_path=None,
1395 nosync=False, ldap_dryrun_mode=False):
1396 """Provision an LDAP backend for samba4
1398 This works for OpenLDAP and Fedora DS
1401 self.ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.ldapdir, "ldapi"), safe="")
1403 if not os.path.isdir(paths.ldapdir):
1404 os.makedirs(paths.ldapdir, 0700)
1406 if ldap_backend_type == "existing":
1407 #Check to see that this 'existing' LDAP backend in fact exists
1408 ldapi_db = Ldb(self.ldapi_uri, credentials=credentials)
1409 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1410 expression="(objectClass=OpenLDAProotDSE)")
1412 # If we have got here, then we must have a valid connection to the LDAP server, with valid credentials supplied
1413 # This caused them to be set into the long-term database later in the script.
1414 self.credentials = credentials
1415 self.ldap_backend_type = "openldap" #For now, assume existing backends at least emulate OpenLDAP
1418 # we will shortly start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via self.ldapi_uri
1419 # if another instance of slapd is already running
1421 ldapi_db = Ldb(self.ldapi_uri)
1422 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1423 expression="(objectClass=OpenLDAProotDSE)");
1425 f = open(paths.slapdpid, "r")
1428 message("Check for slapd Process with PID: " + str(p) + " and terminate it manually.")
1432 raise ProvisioningError("Warning: Another slapd Instance seems already running on this host, listening to " + self.ldapi_uri + ". Please shut it down before you continue. ")
1437 # Try to print helpful messages when the user has not specified the path to slapd
1438 if slapd_path is None:
1439 raise ProvisioningError("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
1440 if not os.path.exists(slapd_path):
1441 message (slapd_path)
1442 raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1444 schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1446 os.unlink(schemadb_path)
1451 # Put the LDIF of the schema into a database so we can search on
1452 # it to generate schema-dependent configurations in Fedora DS and
1454 os.path.join(paths.ldapdir, "schema-tmp.ldb")
1455 schema.ldb.connect(schemadb_path)
1456 schema.ldb.transaction_start()
1458 # These bits of LDIF are supplied when the Schema object is created
1459 schema.ldb.add_ldif(schema.schema_dn_add)
1460 schema.ldb.modify_ldif(schema.schema_dn_modify)
1461 schema.ldb.add_ldif(schema.schema_data)
1462 schema.ldb.transaction_commit()
1464 self.credentials = Credentials()
1465 self.credentials.guess(lp)
1466 #Kerberos to an ldapi:// backend makes no sense
1467 self.credentials.set_kerberos_state(DONT_USE_KERBEROS)
1469 self.adminCredentials = Credentials()
1470 self.adminCredentials.guess(lp)
1471 #Kerberos to an ldapi:// backend makes no sense
1472 self.adminCredentials.set_kerberos_state(DONT_USE_KERBEROS)
1474 self.ldap_backend_type = ldap_backend_type
1476 if ldap_backend_type == "fedora-ds":
1477 provision_fds_backend(self, paths=paths, setup_path=setup_path,
1478 names=names, message=message,
1480 ldapadminpass=ldapadminpass, root=root,
1482 ldap_backend_extra_port=ldap_backend_extra_port,
1483 setup_ds_path=setup_ds_path,
1484 slapd_path=slapd_path,
1486 ldap_dryrun_mode=ldap_dryrun_mode)
1488 elif ldap_backend_type == "openldap":
1489 provision_openldap_backend(self, paths=paths, setup_path=setup_path,
1490 names=names, message=message,
1492 ldapadminpass=ldapadminpass, root=root,
1494 ldap_backend_extra_port=ldap_backend_extra_port,
1495 ol_mmr_urls=ol_mmr_urls,
1496 slapd_path=slapd_path,
1498 ldap_dryrun_mode=ldap_dryrun_mode)
1500 raise ProvisioningError("Unknown LDAP backend type selected")
1502 self.credentials.set_password(ldapadminpass)
1503 self.adminCredentials.set_username("samba-admin")
1504 self.adminCredentials.set_password(ldapadminpass)
1506 # Now start the slapd, so we can provision onto it. We keep the
1507 # subprocess context around, to kill this off at the successful
1509 self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False)
1511 while self.slapd.poll() is None:
1512 # Wait until the socket appears
1514 ldapi_db = Ldb(self.ldapi_uri, lp=lp, credentials=self.credentials)
1515 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1516 expression="(objectClass=OpenLDAProotDSE)")
1517 # If we have got here, then we must have a valid connection to the LDAP server!
1523 raise ProvisioningError("slapd died before we could make a connection to it")
1526 def provision_openldap_backend(result, paths=None, setup_path=None, names=None,
1528 hostname=None, ldapadminpass=None, root=None,
1530 ldap_backend_extra_port=None,
1532 slapd_path=None, nosync=False,
1533 ldap_dryrun_mode=False):
1535 #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
1538 nosync_config = "dbnosync"
1540 lnkattr = get_linked_attributes(names.schemadn,schema.ldb)
1541 refint_attributes = ""
1542 memberof_config = "# Generated from Samba4 schema\n"
1543 for att in lnkattr.keys():
1544 if lnkattr[att] is not None:
1545 refint_attributes = refint_attributes + " " + att
1547 memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1548 { "MEMBER_ATTR" : att ,
1549 "MEMBEROF_ATTR" : lnkattr[att] })
1551 refint_config = read_and_sub_file(setup_path("refint.conf"),
1552 { "LINK_ATTRS" : refint_attributes})
1554 attrs = ["linkID", "lDAPDisplayName"]
1555 res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1557 for i in range (0, len(res)):
1558 index_attr = res[i]["lDAPDisplayName"][0]
1559 if index_attr == "objectGUID":
1560 index_attr = "entryUUID"
1562 index_config += "index " + index_attr + " eq\n"
1564 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1566 mmr_replicator_acl = ""
1567 mmr_serverids_config = ""
1568 mmr_syncrepl_schema_config = ""
1569 mmr_syncrepl_config_config = ""
1570 mmr_syncrepl_user_config = ""
1573 if ol_mmr_urls is not None:
1574 # For now, make these equal
1575 mmr_pass = ldapadminpass
1577 url_list=filter(None,ol_mmr_urls.split(' '))
1578 if (len(url_list) == 1):
1579 url_list=filter(None,ol_mmr_urls.split(','))
1582 mmr_on_config = "MirrorMode On"
1583 mmr_replicator_acl = " by dn=cn=replicator,cn=samba read"
1585 for url in url_list:
1587 mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1588 { "SERVERID" : str(serverid),
1589 "LDAPSERVER" : url })
1592 mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1594 "MMRDN": names.schemadn,
1596 "MMR_PASSWORD": mmr_pass})
1599 mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1601 "MMRDN": names.configdn,
1603 "MMR_PASSWORD": mmr_pass})
1606 mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1608 "MMRDN": names.domaindn,
1610 "MMR_PASSWORD": mmr_pass })
1611 # OpenLDAP cn=config initialisation
1612 olc_syncrepl_config = ""
1614 # if mmr = yes, generate cn=config-replication directives
1615 # and olc_seed.lif for the other mmr-servers
1616 if ol_mmr_urls is not None:
1618 olc_serverids_config = ""
1619 olc_syncrepl_seed_config = ""
1620 olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
1622 for url in url_list:
1624 olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
1625 { "SERVERID" : str(serverid),
1626 "LDAPSERVER" : url })
1629 olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
1632 "MMR_PASSWORD": mmr_pass})
1634 olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1636 "LDAPSERVER" : url})
1638 setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif,
1639 {"OLC_SERVER_ID_CONF": olc_serverids_config,
1640 "OLC_PW": ldapadminpass,
1641 "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
1644 setup_file(setup_path("slapd.conf"), paths.slapdconf,
1645 {"DNSDOMAIN": names.dnsdomain,
1646 "LDAPDIR": paths.ldapdir,
1647 "DOMAINDN": names.domaindn,
1648 "CONFIGDN": names.configdn,
1649 "SCHEMADN": names.schemadn,
1650 "MEMBEROF_CONFIG": memberof_config,
1651 "MIRRORMODE": mmr_on_config,
1652 "REPLICATOR_ACL": mmr_replicator_acl,
1653 "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1654 "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1655 "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1656 "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1657 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
1658 "OLC_MMR_CONFIG": olc_mmr_config,
1659 "REFINT_CONFIG": refint_config,
1660 "INDEX_CONFIG": index_config,
1661 "NOSYNC": nosync_config})
1663 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1664 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1665 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1667 if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba", "cn=samba")):
1668 os.makedirs(os.path.join(paths.ldapdir, "db", "samba", "cn=samba"), 0700)
1670 setup_file(setup_path("cn=samba.ldif"),
1671 os.path.join(paths.ldapdir, "db", "samba", "cn=samba.ldif"),
1672 { "UUID": str(uuid.uuid4()),
1673 "LDAPTIME": timestring(int(time.time()))} )
1674 setup_file(setup_path("cn=samba-admin.ldif"),
1675 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=samba-admin.ldif"),
1676 {"LDAPADMINPASS_B64": b64encode(ldapadminpass),
1677 "UUID": str(uuid.uuid4()),
1678 "LDAPTIME": timestring(int(time.time()))} )
1680 if ol_mmr_urls is not None:
1681 setup_file(setup_path("cn=replicator.ldif"),
1682 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=replicator.ldif"),
1683 {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1684 "UUID": str(uuid.uuid4()),
1685 "LDAPTIME": timestring(int(time.time()))} )
1688 mapping = "schema-map-openldap-2.3"
1689 backend_schema = "backend-schema.schema"
1691 backend_schema_data = schema.ldb.convert_schema_to_openldap("openldap", open(setup_path(mapping), 'r').read())
1692 assert backend_schema_data is not None
1693 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1695 # now we generate the needed strings to start slapd automatically,
1696 # first ldapi_uri...
1697 if ldap_backend_extra_port is not None:
1698 # When we use MMR, we can't use 0.0.0.0 as it uses the name
1699 # specified there as part of it's clue as to it's own name,
1700 # and not to replicate to itself
1701 if ol_mmr_urls is None:
1702 server_port_string = "ldap://0.0.0.0:%d" % ldap_backend_extra_port
1704 server_port_string = "ldap://" + names.hostname + "." + names.dnsdomain +":%d" % ldap_backend_extra_port
1706 server_port_string = ""
1708 # Prepare the 'result' information - the commands to return in particular
1709 result.slapd_provision_command = [slapd_path]
1711 result.slapd_provision_command.append("-F" + paths.olcdir)
1713 result.slapd_provision_command.append("-h")
1715 # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands
1716 result.slapd_command = list(result.slapd_provision_command)
1718 result.slapd_provision_command.append(result.ldapi_uri)
1719 result.slapd_provision_command.append("-d0")
1721 uris = result.ldapi_uri
1722 if server_port_string is not "":
1723 uris = uris + " " + server_port_string
1725 result.slapd_command.append(uris)
1727 # Set the username - done here because Fedora DS still uses the admin DN and simple bind
1728 result.credentials.set_username("samba-admin")
1730 # If we were just looking for crashes up to this point, it's a
1731 # good time to exit before we realise we don't have OpenLDAP on
1733 if ldap_dryrun_mode:
1736 # Finally, convert the configuration into cn=config style!
1737 if not os.path.isdir(paths.olcdir):
1738 os.makedirs(paths.olcdir, 0770)
1740 retcode = subprocess.call([slapd_path, "-Ttest", "-f", paths.slapdconf, "-F", paths.olcdir], close_fds=True, shell=False)
1742 # We can't do this, as OpenLDAP is strange. It gives an error
1743 # output to the above, but does the conversion sucessfully...
1746 # raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1748 if not os.path.exists(os.path.join(paths.olcdir, "cn=config.ldif")):
1749 raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1751 # Don't confuse the admin by leaving the slapd.conf around
1752 os.remove(paths.slapdconf)
1755 def provision_fds_backend(result, paths=None, setup_path=None, names=None,
1757 hostname=None, ldapadminpass=None, root=None,
1759 ldap_backend_extra_port=None,
1763 ldap_dryrun_mode=False):
1765 if ldap_backend_extra_port is not None:
1766 serverport = "ServerPort=%d" % ldap_backend_extra_port
1770 setup_file(setup_path("fedorads.inf"), paths.fedoradsinf,
1772 "HOSTNAME": hostname,
1773 "DNSDOMAIN": names.dnsdomain,
1774 "LDAPDIR": paths.ldapdir,
1775 "DOMAINDN": names.domaindn,
1776 "LDAPMANAGERDN": names.ldapmanagerdn,
1777 "LDAPMANAGERPASS": ldapadminpass,
1778 "SERVERPORT": serverport})
1780 setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions,
1781 {"CONFIGDN": names.configdn,
1782 "SCHEMADN": names.schemadn,
1783 "SAMBADN": names.sambadn,
1786 setup_file(setup_path("fedorads-sasl.ldif"), paths.fedoradssasl,
1787 {"SAMBADN": names.sambadn,
1790 setup_file(setup_path("fedorads-samba.ldif"), paths.fedoradssamba,
1791 {"SAMBADN": names.sambadn,
1792 "LDAPADMINPASS": ldapadminpass
1795 mapping = "schema-map-fedora-ds-1.0"
1796 backend_schema = "99_ad.ldif"
1798 # Build a schema file in Fedora DS format
1799 backend_schema_data = schema.ldb.convert_schema_to_openldap("fedora-ds", open(setup_path(mapping), 'r').read())
1800 assert backend_schema_data is not None
1801 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1803 result.credentials.set_bind_dn(names.ldapmanagerdn)
1805 # Destory the target directory, or else setup-ds.pl will complain
1806 fedora_ds_dir = os.path.join(paths.ldapdir, "slapd-samba4")
1807 shutil.rmtree(fedora_ds_dir, True)
1809 result.slapd_provision_command = [slapd_path, "-D", fedora_ds_dir, "-i", paths.slapdpid];
1810 #In the 'provision' command line, stay in the foreground so we can easily kill it
1811 result.slapd_provision_command.append("-d0")
1813 #the command for the final run is the normal script
1814 result.slapd_command = [os.path.join(paths.ldapdir, "slapd-samba4", "start-slapd")]
1816 # If we were just looking for crashes up to this point, it's a
1817 # good time to exit before we realise we don't have Fedora DS on
1818 if ldap_dryrun_mode:
1821 # Try to print helpful messages when the user has not specified the path to the setup-ds tool
1822 if setup_ds_path is None:
1823 raise ProvisioningError("Warning: Fedora DS LDAP-Backend must be setup with path to setup-ds, e.g. --setup-ds-path=\"/usr/sbin/setup-ds.pl\"!")
1824 if not os.path.exists(setup_ds_path):
1825 message (setup_ds_path)
1826 raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1828 # Run the Fedora DS setup utility
1829 retcode = subprocess.call([setup_ds_path, "--silent", "--file", paths.fedoradsinf], close_fds=True, shell=False)
1831 raise ProvisioningError("setup-ds failed")
1834 retcode = subprocess.call([
1835 os.path.join(paths.ldapdir, "slapd-samba4", "ldif2db"), "-s", names.sambadn, "-i", paths.fedoradssamba],
1836 close_fds=True, shell=False)
1838 raise("ldib2db failed")
1840 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1841 """Create a PHP LDAP admin configuration file.
1843 :param path: Path to write the configuration to.
1844 :param setup_path: Function to generate setup paths.
1846 setup_file(setup_path("phpldapadmin-config.php"), path,
1847 {"S4_LDAPI_URI": ldapi_uri})
1850 def create_zone_file(path, setup_path, dnsdomain, domaindn,
1851 hostip, hostip6, hostname, dnspass, realm, domainguid,
1853 """Write out a DNS zone file, from the info in the current database.
1855 :param path: Path of the new zone file.
1856 :param setup_path: Setup path function.
1857 :param dnsdomain: DNS Domain name
1858 :param domaindn: DN of the Domain
1859 :param hostip: Local IPv4 IP
1860 :param hostip6: Local IPv6 IP
1861 :param hostname: Local hostname
1862 :param dnspass: Password for DNS
1863 :param realm: Realm name
1864 :param domainguid: GUID of the domain.
1865 :param ntdsguid: GUID of the hosts nTDSDSA record.
1867 assert isinstance(domainguid, str)
1869 if hostip6 is not None:
1870 hostip6_base_line = " IN AAAA " + hostip6
1871 hostip6_host_line = hostname + " IN AAAA " + hostip6
1873 hostip6_base_line = ""
1874 hostip6_host_line = ""
1876 if hostip is not None:
1877 hostip_base_line = " IN A " + hostip
1878 hostip_host_line = hostname + " IN A " + hostip
1880 hostip_base_line = ""
1881 hostip_host_line = ""
1883 setup_file(setup_path("provision.zone"), path, {
1884 "DNSPASS_B64": b64encode(dnspass),
1885 "HOSTNAME": hostname,
1886 "DNSDOMAIN": dnsdomain,
1888 "HOSTIP_BASE_LINE": hostip_base_line,
1889 "HOSTIP_HOST_LINE": hostip_host_line,
1890 "DOMAINGUID": domainguid,
1891 "DATESTRING": time.strftime("%Y%m%d%H"),
1892 "DEFAULTSITE": DEFAULTSITE,
1893 "NTDSGUID": ntdsguid,
1894 "HOSTIP6_BASE_LINE": hostip6_base_line,
1895 "HOSTIP6_HOST_LINE": hostip6_host_line,
1899 def create_named_conf(path, setup_path, realm, dnsdomain,
1901 """Write out a file containing zone statements suitable for inclusion in a
1902 named.conf file (including GSS-TSIG configuration).
1904 :param path: Path of the new named.conf file.
1905 :param setup_path: Setup path function.
1906 :param realm: Realm name
1907 :param dnsdomain: DNS Domain name
1908 :param private_dir: Path to private directory
1909 :param keytab_name: File name of DNS keytab file
1912 setup_file(setup_path("named.conf"), path, {
1913 "DNSDOMAIN": dnsdomain,
1915 "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1916 "PRIVATE_DIR": private_dir
1919 def create_named_txt(path, setup_path, realm, dnsdomain,
1920 private_dir, keytab_name):
1921 """Write out a file containing zone statements suitable for inclusion in a
1922 named.conf file (including GSS-TSIG configuration).
1924 :param path: Path of the new named.conf file.
1925 :param setup_path: Setup path function.
1926 :param realm: Realm name
1927 :param dnsdomain: DNS Domain name
1928 :param private_dir: Path to private directory
1929 :param keytab_name: File name of DNS keytab file
1932 setup_file(setup_path("named.txt"), path, {
1933 "DNSDOMAIN": dnsdomain,
1935 "DNS_KEYTAB": keytab_name,
1936 "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1937 "PRIVATE_DIR": private_dir
1940 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1941 """Write out a file containing zone statements suitable for inclusion in a
1942 named.conf file (including GSS-TSIG configuration).
1944 :param path: Path of the new named.conf file.
1945 :param setup_path: Setup path function.
1946 :param dnsdomain: DNS Domain name
1947 :param hostname: Local hostname
1948 :param realm: Realm name
1951 setup_file(setup_path("krb5.conf"), path, {
1952 "DNSDOMAIN": dnsdomain,
1953 "HOSTNAME": hostname,