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
8 # Based on the original in EJS:
9 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 from base64 import b64encode
35 from auth import system_session
36 from samba import Ldb, substitute_var, valid_netbios_name, check_all_substituted
37 from samba.samdb import SamDB
38 from samba.idmap import IDmapDB
41 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, \
42 LDB_ERR_NO_SUCH_OBJECT, timestring, CHANGETYPE_MODIFY, CHANGETYPE_NONE
44 """Functions for setting up a Samba configuration."""
46 DEFAULTSITE = "Default-First-Site-Name"
48 class InvalidNetbiosName(Exception):
49 def __init__(self, name):
50 super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)
66 self.dns_keytab = None
69 self.private_dir = None
72 self.modulesconf = None
73 self.memberofconf = None
74 self.fedoradsinf = None
75 self.fedoradspartitions = None
83 self.ldapmanagerdn = None
86 self.netbiosname = None
91 class ProvisionResult:
98 def check_install(lp, session_info, credentials):
99 """Check whether the current install seems ok.
101 :param lp: Loadparm context
102 :param session_info: Session information
103 :param credentials: Credentials
105 if lp.get("realm") == "":
106 raise Error("Realm empty")
107 ldb = Ldb(lp.get("sam database"), session_info=session_info,
108 credentials=credentials, lp=lp)
109 if len(ldb.search("(cn=Administrator)")) != 1:
110 raise "No administrator account found"
113 def findnss(nssfn, names):
114 """Find a user or group from a list of possibilities.
116 :param nssfn: NSS Function to try (should raise KeyError if not found)
117 :param names: Names to check.
118 :return: Value return by first names list.
125 raise KeyError("Unable to find user/group %r" % names)
128 def open_ldb(session_info, credentials, lp, dbname):
129 """Open a LDB, thrashing it if it is corrupt.
131 :param session_info: auth session information
132 :param credentials: credentials
133 :param lp: Loadparm context
134 :param dbname: Path of the database to open.
135 :return: a Ldb object
137 assert session_info is not None
139 return Ldb(dbname, session_info=session_info, credentials=credentials,
144 return Ldb(dbname, session_info=session_info, credentials=credentials,
148 def setup_add_ldif(ldb, ldif_path, subst_vars=None):
149 """Setup a ldb in the private dir.
151 :param ldb: LDB file to import data into
152 :param ldif_path: Path of the LDIF file to load
153 :param subst_vars: Optional variables to subsitute in LDIF.
155 assert isinstance(ldif_path, str)
157 data = open(ldif_path, 'r').read()
158 if subst_vars is not None:
159 data = substitute_var(data, subst_vars)
161 check_all_substituted(data)
166 def setup_modify_ldif(ldb, ldif_path, substvars=None):
167 """Modify a ldb in the private dir.
169 :param ldb: LDB object.
170 :param ldif_path: LDIF file path.
171 :param substvars: Optional dictionary with substitution variables.
173 data = open(ldif_path, 'r').read()
174 if substvars is not None:
175 data = substitute_var(data, substvars)
177 check_all_substituted(data)
179 ldb.modify_ldif(data)
182 def setup_ldb(ldb, ldif_path, subst_vars):
183 """Import a LDIF a file into a LDB handle, optionally substituting variables.
185 :note: Either all LDIF data will be added or none (using transactions).
187 :param ldb: LDB file to import into.
188 :param ldif_path: Path to the LDIF file.
189 :param subst_vars: Dictionary with substitution variables.
191 assert ldb is not None
192 ldb.transaction_start()
194 setup_add_ldif(ldb, ldif_path, subst_vars)
196 ldb.transaction_cancel()
198 ldb.transaction_commit()
201 def setup_file(template, fname, substvars):
202 """Setup a file in the private dir.
204 :param template: Path of the template file.
205 :param fname: Path of the file to create.
206 :param substvars: Substitution variables.
210 if os.path.exists(f):
213 data = open(template, 'r').read()
215 data = substitute_var(data, substvars)
216 check_all_substituted(data)
218 open(f, 'w').write(data)
221 def provision_paths_from_lp(lp, dnsdomain):
222 """Set the default paths for provisioning.
224 :param lp: Loadparm context.
225 :param dnsdomain: DNS Domain name
227 paths = ProvisionPaths()
228 paths.private_dir = lp.get("private dir")
229 paths.keytab = "secrets.keytab"
230 paths.dns_keytab = "dns.keytab"
232 paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
233 paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb")
234 paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb")
235 paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
236 paths.templates = os.path.join(paths.private_dir, "templates.ldb")
237 paths.dns = os.path.join(paths.private_dir, dnsdomain + ".zone")
238 paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
239 paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
240 paths.phpldapadminconfig = os.path.join(paths.private_dir,
241 "phpldapadmin-config.php")
242 paths.ldapdir = os.path.join(paths.private_dir,
244 paths.slapdconf = os.path.join(paths.ldapdir,
246 paths.modulesconf = os.path.join(paths.ldapdir,
248 paths.memberofconf = os.path.join(paths.ldapdir,
250 paths.fedoradsinf = os.path.join(paths.ldapdir,
252 paths.fedoradspartitions = os.path.join(paths.ldapdir,
253 "fedorads-partitions.ldif")
254 paths.hklm = "hklm.ldb"
255 paths.hkcr = "hkcr.ldb"
256 paths.hkcu = "hkcu.ldb"
257 paths.hku = "hku.ldb"
258 paths.hkpd = "hkpd.ldb"
259 paths.hkpt = "hkpt.ldb"
261 paths.sysvol = lp.get("path", "sysvol")
263 paths.netlogon = lp.get("path", "netlogon")
267 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, serverrole=None,
268 rootdn=None, domaindn=None, configdn=None, schemadn=None, serverdn=None,
272 hostname = socket.gethostname().split(".")[0].lower()
274 netbiosname = hostname.upper()
275 if not valid_netbios_name(netbiosname):
276 raise InvalidNetbiosName(netbiosname)
278 hostname = hostname.lower()
280 if dnsdomain is None:
281 dnsdomain = lp.get("realm")
283 if serverrole is None:
284 serverrole = lp.get("server role")
286 assert dnsdomain is not None
287 realm = dnsdomain.upper()
289 if lp.get("realm").upper() != realm:
290 raise Exception("realm '%s' in %s must match chosen realm '%s'" %
291 (lp.get("realm"), smbconf, realm))
293 dnsdomain = dnsdomain.lower()
295 if (serverrole == "domain controller"):
297 domain = lp.get("workgroup")
299 domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
300 if lp.get("workgroup").upper() != domain.upper():
301 raise Error("workgroup '%s' in smb.conf must match chosen domain '%s'",
302 lp.get("workgroup"), domain)
306 domaindn = "CN=" + netbiosname
308 assert domain is not None
309 domain = domain.upper()
310 if not valid_netbios_name(domain):
311 raise InvalidNetbiosName(domain)
317 configdn = "CN=Configuration," + rootdn
319 schemadn = "CN=Schema," + configdn
324 names = ProvisionNames()
325 names.rootdn = rootdn
326 names.domaindn = domaindn
327 names.configdn = configdn
328 names.schemadn = schemadn
329 names.ldapmanagerdn = "CN=Manager," + rootdn
330 names.dnsdomain = dnsdomain
331 names.domain = domain
333 names.netbiosname = netbiosname
334 names.hostname = hostname
335 names.sitename = sitename
336 names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (netbiosname, sitename, configdn)
341 def load_or_make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, targetdir):
342 if targetdir is not None:
343 if not os.path.exists(targetdir):
345 if not os.path.exists(os.path.join(targetdir, "etc")):
346 os.mkdir(os.path.join(targetdir, "etc"))
348 smbconf = os.path.join(targetdir, "etc", "smb.conf")
350 # only install a new smb.conf if there isn't one there already
352 if not os.path.exists(smbconf):
354 hostname = socket.gethostname().split(".")[0].lower()
356 if serverrole is None:
357 serverrole = "standalone"
359 assert serverrole in ("domain controller", "member server", "standalone")
360 if serverrole == "domain controller":
362 elif serverrole == "member server":
363 smbconfsuffix = "member"
364 elif serverrole == "standalone":
365 smbconfsuffix = "standalone"
367 assert domain is not None
368 assert realm is not None
370 default_lp = param.LoadParm()
371 #Load non-existant file
372 default_lp.load(smbconf)
374 if targetdir is not None:
375 privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
376 lockdir_line = "lock dir = " + os.path.abspath(targetdir)
378 default_lp.set("lock dir", os.path.abspath(targetdir))
383 sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
384 netlogon = os.path.join(sysvol, realm.lower(), "scripts")
386 setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
388 "HOSTNAME": hostname,
391 "SERVERROLE": serverrole,
392 "NETLOGONPATH": netlogon,
393 "SYSVOLPATH": sysvol,
394 "PRIVATEDIR_LINE": privatedir_line,
395 "LOCKDIR_LINE": lockdir_line
398 lp = param.LoadParm()
403 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
404 users_gid, wheel_gid):
405 """setup reasonable name mappings for sam names to unix names.
407 :param samdb: SamDB object.
408 :param idmap: IDmap db object.
409 :param sid: The domain sid.
410 :param domaindn: The domain DN.
411 :param root_uid: uid of the UNIX root user.
412 :param nobody_uid: uid of the UNIX nobody user.
413 :param users_gid: gid of the UNIX users group.
414 :param wheel_gid: gid of the UNIX wheel group."""
415 # add some foreign sids if they are not present already
416 samdb.add_foreign(domaindn, "S-1-5-7", "Anonymous")
417 samdb.add_foreign(domaindn, "S-1-1-0", "World")
418 samdb.add_foreign(domaindn, "S-1-5-2", "Network")
419 samdb.add_foreign(domaindn, "S-1-5-18", "System")
420 samdb.add_foreign(domaindn, "S-1-5-11", "Authenticated Users")
422 idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
423 idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
425 idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
426 idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
428 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
430 serverrole, ldap_backend=None,
431 ldap_backend_type=None, erase=False):
432 """Setup the partitions for the SAM database.
434 Alternatively, provision() may call this, and then populate the database.
436 :note: This will wipe the Sam Database!
438 :note: This function always removes the local SAM LDB file. The erase
439 parameter controls whether to erase the existing data, which
440 may not be stored locally but in LDAP.
442 assert session_info is not None
444 samdb = SamDB(samdb_path, session_info=session_info,
445 credentials=credentials, lp=lp)
451 os.unlink(samdb_path)
453 samdb = SamDB(samdb_path, session_info=session_info,
454 credentials=credentials, lp=lp)
456 #Add modules to the list to activate them by default
457 #beware often order is important
459 # Some Known ordering constraints:
460 # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
461 # - objectclass must be before password_hash, because password_hash checks
462 # that the objectclass is of type person (filled in by objectclass
463 # module when expanding the objectclass list)
464 # - partition must be last
465 # - each partition has its own module list then
466 modules_list = ["rootdse",
482 modules_list2 = ["show_deleted",
485 domaindn_ldb = "users.ldb"
486 if ldap_backend is not None:
487 domaindn_ldb = ldap_backend
488 configdn_ldb = "configuration.ldb"
489 if ldap_backend is not None:
490 configdn_ldb = ldap_backend
491 schemadn_ldb = "schema.ldb"
492 if ldap_backend is not None:
493 schema_ldb = ldap_backend
494 schemadn_ldb = ldap_backend
496 if ldap_backend_type == "fedora-ds":
497 backend_modules = ["nsuniqueid", "paged_searches"]
498 # We can handle linked attributes here, as we don't have directory-side subtree operations
499 tdb_modules_list = ["linked_attributes"]
500 elif ldap_backend_type == "openldap":
501 backend_modules = ["normalise", "entryuuid", "paged_searches"]
502 # OpenLDAP handles subtree renames, so we don't want to do any of these things
503 tdb_modules_list = None
504 elif serverrole == "domain controller":
505 backend_modules = ["repl_meta_data"]
507 backend_modules = ["objectguid"]
509 if tdb_modules_list is None:
510 tdb_modules_list_as_string = ""
512 tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
514 samdb.transaction_start()
516 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
517 "SCHEMADN": names.schemadn,
518 "SCHEMADN_LDB": schemadn_ldb,
519 "SCHEMADN_MOD2": ",objectguid",
520 "CONFIGDN": names.configdn,
521 "CONFIGDN_LDB": configdn_ldb,
522 "DOMAINDN": names.domaindn,
523 "DOMAINDN_LDB": domaindn_ldb,
524 "SCHEMADN_MOD": "schema_fsmo,instancetype",
525 "CONFIGDN_MOD": "naming_fsmo,instancetype",
526 "DOMAINDN_MOD": "pdc_fsmo,password_hash,instancetype",
527 "MODULES_LIST": ",".join(modules_list),
528 "TDB_MODULES_LIST": tdb_modules_list_as_string,
529 "MODULES_LIST2": ",".join(modules_list2),
530 "BACKEND_MOD": ",".join(backend_modules),
534 samdb.transaction_cancel()
537 samdb.transaction_commit()
539 samdb = SamDB(samdb_path, session_info=session_info,
540 credentials=credentials, lp=lp)
542 samdb.transaction_start()
544 message("Setting up sam.ldb attributes")
545 samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
547 message("Setting up sam.ldb rootDSE")
548 setup_samdb_rootdse(samdb, setup_path, names)
551 message("Erasing data from partitions")
552 samdb.erase_partitions()
555 samdb.transaction_cancel()
558 samdb.transaction_commit()
563 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain,
564 netbiosname, domainsid, keytab_path, samdb_url,
565 dns_keytab_path, dnspass, machinepass):
566 """Add DC-specific bits to a secrets database.
568 :param secretsdb: Ldb Handle to the secrets database
569 :param setup_path: Setup path function
570 :param machinepass: Machine password
572 setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), {
573 "MACHINEPASS_B64": b64encode(machinepass),
576 "DNSDOMAIN": dnsdomain,
577 "DOMAINSID": str(domainsid),
578 "SECRETS_KEYTAB": keytab_path,
579 "NETBIOSNAME": netbiosname,
580 "SAM_LDB": samdb_url,
581 "DNS_KEYTAB": dns_keytab_path,
582 "DNSPASS_B64": b64encode(dnspass),
586 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
587 """Setup the secrets database.
589 :param path: Path to the secrets database.
590 :param setup_path: Get the path to a setup file.
591 :param session_info: Session info.
592 :param credentials: Credentials
593 :param lp: Loadparm context
594 :return: LDB handle for the created secrets database
596 if os.path.exists(path):
598 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
601 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
602 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
604 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
608 def setup_templatesdb(path, setup_path, session_info, credentials, lp):
609 """Setup the templates database.
611 :param path: Path to the database.
612 :param setup_path: Function for obtaining the path to setup files.
613 :param session_info: Session info
614 :param credentials: Credentials
615 :param lp: Loadparm context
617 templates_ldb = SamDB(path, session_info=session_info,
618 credentials=credentials, lp=lp)
619 templates_ldb.erase()
620 templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif"))
623 def setup_registry(path, setup_path, session_info, credentials, lp):
624 """Setup the registry.
626 :param path: Path to the registry database
627 :param setup_path: Function that returns the path to a setup.
628 :param session_info: Session information
629 :param credentials: Credentials
630 :param lp: Loadparm context
632 reg = registry.Registry()
633 hive = registry.open_ldb(path, session_info=session_info,
634 credentials=credentials, lp_ctx=lp)
635 reg.mount_hive(hive, "HKEY_LOCAL_MACHINE")
636 provision_reg = setup_path("provision.reg")
637 assert os.path.exists(provision_reg)
638 reg.diff_apply(provision_reg)
640 def setup_idmapdb(path, setup_path, session_info, credentials, lp):
641 """Setup the idmap database.
643 :param path: path to the idmap database
644 :param setup_path: Function that returns a path to a setup file
645 :param session_info: Session information
646 :param credentials: Credentials
647 :param lp: Loadparm context
649 if os.path.exists(path):
652 idmap_ldb = IDmapDB(path, session_info=session_info,
653 credentials=credentials, lp=lp)
656 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
659 def setup_samdb_rootdse(samdb, setup_path, names):
660 """Setup the SamDB rootdse.
662 :param samdb: Sam Database handle
663 :param setup_path: Obtain setup path
665 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
666 "SCHEMADN": names.schemadn,
667 "NETBIOSNAME": names.netbiosname,
668 "DNSDOMAIN": names.dnsdomain,
669 "REALM": names.realm,
670 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
671 "DOMAINDN": names.domaindn,
672 "ROOTDN": names.rootdn,
673 "CONFIGDN": names.configdn,
674 "SERVERDN": names.serverdn,
678 def setup_self_join(samdb, names,
679 machinepass, dnspass,
680 domainsid, invocationid, setup_path,
682 """Join a host to its own domain."""
683 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
684 "CONFIGDN": names.configdn,
685 "SCHEMADN": names.schemadn,
686 "DOMAINDN": names.domaindn,
687 "INVOCATIONID": invocationid,
688 "NETBIOSNAME": names.netbiosname,
689 "DEFAULTSITE": names.sitename,
690 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
691 "MACHINEPASS_B64": b64encode(machinepass),
692 "DNSPASS_B64": b64encode(dnspass),
693 "REALM": names.realm,
694 "DOMAIN": names.domain,
695 "DNSDOMAIN": names.dnsdomain})
696 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
697 "POLICYGUID": policyguid,
698 "DNSDOMAIN": names.dnsdomain,
699 "DOMAINSID": str(domainsid),
700 "DOMAINDN": names.domaindn})
703 def setup_samdb(path, setup_path, session_info, credentials, lp,
705 domainsid, aci, domainguid, policyguid,
706 fill, adminpass, krbtgtpass,
707 machinepass, invocationid, dnspass,
708 serverrole, ldap_backend=None,
709 ldap_backend_type=None):
710 """Setup a complete SAM Database.
712 :note: This will wipe the main SAM database file!
715 erase = (fill != FILL_DRS)
717 # Also wipes the database
718 setup_samdb_partitions(path, setup_path, message=message, lp=lp,
719 credentials=credentials, session_info=session_info,
721 ldap_backend=ldap_backend, serverrole=serverrole,
722 ldap_backend_type=ldap_backend_type, erase=erase)
724 samdb = SamDB(path, session_info=session_info,
725 credentials=credentials, lp=lp)
728 # We want to finish here, but setup the index before we do so
729 message("Setting up sam.ldb index")
730 samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
733 message("Pre-loading the Samba 4 and AD schema")
734 samdb = SamDB(path, session_info=session_info,
735 credentials=credentials, lp=lp)
736 samdb.set_domain_sid(domainsid)
737 if serverrole == "domain controller":
738 samdb.set_invocation_id(invocationid)
740 load_schema(setup_path, samdb, names.schemadn, names.netbiosname, names.configdn, names.sitename)
742 samdb.transaction_start()
745 message("Adding DomainDN: %s (permitted to fail)" % names.domaindn)
746 if serverrole == "domain controller":
747 domain_oc = "domainDNS"
749 domain_oc = "samba4LocalDomain"
751 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
752 "DOMAINDN": names.domaindn,
754 "DOMAIN_OC": domain_oc
757 message("Modifying DomainDN: " + names.domaindn + "")
758 if domainguid is not None:
759 domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
763 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
764 "LDAPTIME": timestring(int(time.time())),
765 "DOMAINSID": str(domainsid),
766 "SCHEMADN": names.schemadn,
767 "NETBIOSNAME": names.netbiosname,
768 "DEFAULTSITE": names.sitename,
769 "CONFIGDN": names.configdn,
770 "POLICYGUID": policyguid,
771 "DOMAINDN": names.domaindn,
772 "DOMAINGUID_MOD": domainguid_mod,
775 message("Adding configuration container (permitted to fail)")
776 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
777 "CONFIGDN": names.configdn,
779 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb",
781 message("Modifying configuration container")
782 setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
783 "CONFIGDN": names.configdn,
784 "SCHEMADN": names.schemadn,
787 message("Adding schema container (permitted to fail)")
788 setup_add_ldif(samdb, setup_path("provision_schema_basedn.ldif"), {
789 "SCHEMADN": names.schemadn,
791 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
793 message("Modifying schema container")
794 setup_modify_ldif(samdb,
795 setup_path("provision_schema_basedn_modify.ldif"), {
796 "SCHEMADN": names.schemadn,
797 "NETBIOSNAME": names.netbiosname,
798 "DEFAULTSITE": names.sitename,
799 "CONFIGDN": names.configdn,
802 message("Setting up sam.ldb Samba4 schema")
803 setup_add_ldif(samdb, setup_path("schema_samba4.ldif"),
804 {"SCHEMADN": names.schemadn })
805 message("Setting up sam.ldb AD schema")
806 setup_add_ldif(samdb, setup_path("schema.ldif"),
807 {"SCHEMADN": names.schemadn})
809 message("Setting up sam.ldb configuration data")
810 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
811 "CONFIGDN": names.configdn,
812 "NETBIOSNAME": names.netbiosname,
813 "DEFAULTSITE": names.sitename,
814 "DNSDOMAIN": names.dnsdomain,
815 "DOMAIN": names.domain,
816 "SCHEMADN": names.schemadn,
817 "DOMAINDN": names.domaindn,
820 message("Setting up display specifiers")
821 setup_add_ldif(samdb, setup_path("display_specifiers.ldif"),
822 {"CONFIGDN": names.configdn})
824 message("Adding users container (permitted to fail)")
825 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
826 "DOMAINDN": names.domaindn})
827 message("Modifying users container")
828 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
829 "DOMAINDN": names.domaindn})
830 message("Adding computers container (permitted to fail)")
831 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
832 "DOMAINDN": names.domaindn})
833 message("Modifying computers container")
834 setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
835 "DOMAINDN": names.domaindn})
836 message("Setting up sam.ldb data")
837 setup_add_ldif(samdb, setup_path("provision.ldif"), {
838 "DOMAINDN": names.domaindn,
839 "NETBIOSNAME": names.netbiosname,
840 "DEFAULTSITE": names.sitename,
841 "CONFIGDN": names.configdn,
844 if fill == FILL_FULL:
845 message("Setting up sam.ldb users and groups")
846 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
847 "DOMAINDN": names.domaindn,
848 "DOMAINSID": str(domainsid),
849 "CONFIGDN": names.configdn,
850 "ADMINPASS_B64": b64encode(adminpass),
851 "KRBTGTPASS_B64": b64encode(krbtgtpass),
854 if serverrole == "domain controller":
855 message("Setting up self join")
856 setup_self_join(samdb, names=names, invocationid=invocationid,
858 machinepass=machinepass,
859 domainsid=domainsid, policyguid=policyguid,
860 setup_path=setup_path)
862 #We want to setup the index last, as adds are faster unindexed
863 message("Setting up sam.ldb index")
864 samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
866 samdb.transaction_cancel()
869 samdb.transaction_commit()
874 FILL_NT4SYNC = "NT4SYNC"
877 def provision(setup_dir, message, session_info,
878 credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL, realm=None,
879 rootdn=None, domaindn=None, schemadn=None, configdn=None,
881 domain=None, hostname=None, hostip=None, hostip6=None,
882 domainsid=None, adminpass=None, krbtgtpass=None, domainguid=None,
883 policyguid=None, invocationid=None, machinepass=None,
884 dnspass=None, root=None, nobody=None, nogroup=None, users=None,
885 wheel=None, backup=None, aci=None, serverrole=None,
886 ldap_backend=None, ldap_backend_type=None, sitename=None):
889 :note: caution, this wipes all existing data!
892 def setup_path(file):
893 return os.path.join(setup_dir, file)
895 if domainsid is None:
896 domainsid = security.random_sid()
898 domainsid = security.Sid(domainsid)
900 if policyguid is None:
901 policyguid = uuid.random()
902 if adminpass is None:
903 adminpass = misc.random_password(12)
904 if krbtgtpass is None:
905 krbtgtpass = misc.random_password(12)
906 if machinepass is None:
907 machinepass = misc.random_password(12)
909 dnspass = misc.random_password(12)
911 root_uid = findnss(pwd.getpwnam, ["root"])[2]
913 root_uid = findnss(pwd.getpwnam, [root])[2]
915 nobody_uid = findnss(pwd.getpwnam, ["nobody"])[2]
917 nobody_uid = findnss(pwd.getpwnam, [nobody])[2]
919 users_gid = findnss(grp.getgrnam, ["users"])[2]
921 users_gid = findnss(grp.getgrnam, [users])[2]
923 wheel_gid = findnss(grp.getgrnam, ["wheel", "adm"])[2]
925 wheel_gid = findnss(grp.getgrnam, [wheel])[2]
927 aci = "# no aci for local ldb"
929 lp = load_or_make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, targetdir)
931 names = guess_names(lp=lp, hostname=hostname, domain=domain,
932 dnsdomain=realm, serverrole=serverrole, sitename=sitename,
933 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
936 paths = provision_paths_from_lp(lp, names.dnsdomain)
939 hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
943 hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
944 except socket.gaierror: pass
946 if serverrole is None:
947 serverrole = lp.get("server role")
949 assert serverrole in ("domain controller", "member server", "standalone")
950 if invocationid is None and serverrole == "domain controller":
951 invocationid = uuid.random()
953 if not os.path.exists(paths.private_dir):
954 os.mkdir(paths.private_dir)
956 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
958 if ldap_backend is not None:
959 if ldap_backend == "ldapi":
960 # provision-backend will set this path suggested slapd command line / fedorads.inf
961 ldap_backend = "ldapi://" % urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
963 # only install a new shares config db if there is none
964 if not os.path.exists(paths.shareconf):
965 message("Setting up share.ldb")
966 share_ldb = Ldb(paths.shareconf, session_info=session_info,
967 credentials=credentials, lp=lp)
968 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
971 message("Setting up secrets.ldb")
972 secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
973 session_info=session_info,
974 credentials=credentials, lp=lp)
976 message("Setting up the registry")
977 setup_registry(paths.hklm, setup_path, session_info,
978 credentials=credentials, lp=lp)
980 message("Setting up templates db")
981 setup_templatesdb(paths.templates, setup_path, session_info=session_info,
982 credentials=credentials, lp=lp)
984 message("Setting up idmap db")
985 idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
986 credentials=credentials, lp=lp)
988 samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info,
989 credentials=credentials, lp=lp, names=names,
992 aci=aci, domainguid=domainguid, policyguid=policyguid,
994 adminpass=adminpass, krbtgtpass=krbtgtpass,
995 invocationid=invocationid,
996 machinepass=machinepass, dnspass=dnspass,
997 serverrole=serverrole, ldap_backend=ldap_backend,
998 ldap_backend_type=ldap_backend_type)
1000 if lp.get("server role") == "domain controller":
1001 policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1002 "{" + policyguid + "}")
1003 os.makedirs(policy_path, 0755)
1004 os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1005 os.makedirs(os.path.join(policy_path, "User"), 0755)
1006 if not os.path.isdir(paths.netlogon):
1007 os.makedirs(paths.netlogon, 0755)
1008 secrets_ldb = Ldb(paths.secrets, session_info=session_info,
1009 credentials=credentials, lp=lp)
1010 secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1011 netbiosname=names.netbiosname, domainsid=domainsid,
1012 keytab_path=paths.keytab, samdb_url=paths.samdb,
1013 dns_keytab_path=paths.dns_keytab, dnspass=dnspass,
1014 machinepass=machinepass, dnsdomain=names.dnsdomain)
1016 if samdb_fill == FILL_FULL:
1017 setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1018 root_uid=root_uid, nobody_uid=nobody_uid,
1019 users_gid=users_gid, wheel_gid=wheel_gid)
1021 message("Setting up sam.ldb rootDSE marking as synchronized")
1022 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1024 # Only make a zone file on the first DC, it should be replicated with DNS replication
1025 if serverrole == "domain controller":
1026 samdb = SamDB(paths.samdb, session_info=session_info,
1027 credentials=credentials, lp=lp)
1029 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1030 assert isinstance(domainguid, str)
1031 hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1032 expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1033 scope=SCOPE_SUBTREE)
1034 assert isinstance(hostguid, str)
1036 create_zone_file(paths.dns, setup_path, samdb,
1037 hostname=names.hostname, hostip=hostip,
1038 hostip6=hostip6, dnsdomain=names.dnsdomain,
1039 domaindn=names.domaindn, dnspass=dnspass, realm=names.realm,
1040 domainguid=domainguid, hostguid=hostguid)
1041 message("Please install the zone located in %s into your DNS server" % paths.dns)
1043 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1046 message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1048 message("Once the above files are installed, your server will be ready to use")
1049 message("Server Type: %s" % serverrole)
1050 message("Hostname: %s" % names.hostname)
1051 message("NetBIOS Domain: %s" % names.domain)
1052 message("DNS Domain: %s" % names.dnsdomain)
1053 message("DOMAIN SID: %s" % str(domainsid))
1054 message("Admin password: %s" % adminpass)
1056 result = ProvisionResult()
1057 result.domaindn = domaindn
1058 result.paths = paths
1060 result.samdb = samdb
1063 def provision_become_dc(setup_dir=None,
1064 smbconf=None, targetdir=None, realm=None,
1065 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1067 domain=None, hostname=None, domainsid=None,
1068 adminpass=None, krbtgtpass=None, domainguid=None,
1069 policyguid=None, invocationid=None, machinepass=None,
1070 dnspass=None, root=None, nobody=None, nogroup=None, users=None,
1071 wheel=None, backup=None, aci=None, serverrole=None,
1072 ldap_backend=None, ldap_backend_type=None, sitename=None):
1075 """print a message if quiet is not set."""
1078 provision(setup_dir, message, system_session(), None,
1079 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm,
1080 rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn, serverdn=serverdn,
1081 domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename);
1084 def setup_db_config(setup_path, file, dbdir):
1085 if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1086 os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700);
1087 if not os.path.isdir(os.path.join(dbdir, "tmp")):
1088 os.makedirs(os.path.join(dbdir, "tmp"), 0700);
1090 setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1091 {"LDAPDBDIR": dbdir})
1095 def provision_backend(setup_dir=None, message=None,
1096 smbconf=None, targetdir=None, realm=None,
1097 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1098 domain=None, hostname=None, adminpass=None, root=None, serverrole=None,
1099 ldap_backend_type=None):
1101 def setup_path(file):
1102 return os.path.join(setup_dir, file)
1104 if hostname is None:
1105 hostname = socket.gethostname().split(".")[0].lower()
1108 root = findnss(pwd.getpwnam, ["root"])[0]
1110 lp = load_or_make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, targetdir)
1112 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1113 dnsdomain=realm, serverrole=serverrole,
1114 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn)
1116 paths = provision_paths_from_lp(lp, names.dnsdomain)
1118 if not os.path.isdir(paths.ldapdir):
1119 os.makedirs(paths.ldapdir)
1120 schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1122 os.unlink(schemadb_path)
1126 schemadb = Ldb(schemadb_path, lp=lp)
1128 setup_add_ldif(schemadb, setup_path("provision_schema_basedn.ldif"),
1129 {"SCHEMADN": names.schemadn,
1131 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
1133 setup_modify_ldif(schemadb,
1134 setup_path("provision_schema_basedn_modify.ldif"), \
1135 {"SCHEMADN": names.schemadn,
1136 "NETBIOSNAME": names.netbiosname,
1137 "DEFAULTSITE": DEFAULTSITE,
1138 "CONFIGDN": names.configdn,
1141 setup_add_ldif(schemadb, setup_path("schema_samba4.ldif"),
1142 {"SCHEMADN": names.schemadn })
1143 setup_add_ldif(schemadb, setup_path("schema.ldif"),
1144 {"SCHEMADN": names.schemadn})
1146 if ldap_backend_type == "fedora-ds":
1147 setup_file(setup_path("fedora-ds.inf"), paths.fedoradsinf,
1149 "HOSTNAME": hostname,
1150 "DNSDOMAIN": names.dnsdomain,
1151 "LDAPDIR": paths.ldapdir,
1152 "DOMAINDN": names.domaindn,
1153 "LDAPMANAGERDN": names.ldapmanagerdn,
1154 "LDAPMANAGERPASS": adminpass,
1157 setup_file(setup_path("fedora-partitions.ldif"), paths.fedoradspartitions,
1158 {"CONFIGDN": names.configdn,
1159 "SCHEMADN": names.schemadn,
1162 setup_file(setup_path("fedora-partitions.ldif"), paths.fedoradspartitions,
1163 {"CONFIGDN": names.configdn,
1164 "SCHEMADN": names.schemadn,
1166 mapping = "schema-map-fedora-ds-1.0"
1167 backend_schema = "99_ad.ldif"
1168 elif ldap_backend_type == "openldap":
1169 attrs = ["linkID", "lDAPDisplayName"]
1170 res = schemadb.search(expression="(&(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1)))(objectclass=attributeSchema))", base=names.schemadn, scope=SCOPE_SUBTREE, attrs=attrs);
1172 memberof_config = "# Generated from schema in " + schemadb_path + "\n";
1173 refint_attributes = "";
1174 for i in range (0, len(res)):
1175 linkid = res[i]["linkID"][0]
1176 linkid = str(int(linkid) + 1)
1177 expression = "(&(objectclass=attributeSchema)(linkID=" + (linkid) + "))"
1178 target = schemadb.searchone(basedn=names.schemadn,
1179 expression=expression,
1180 attribute="lDAPDisplayName",
1181 scope=SCOPE_SUBTREE);
1182 if target is not None:
1183 refint_attributes = refint_attributes + " " + target + " " + res[i]["lDAPDisplayName"][0];
1184 memberof_config = memberof_config + """overlay memberof
1185 memberof-dangling error
1186 memberof-refint TRUE
1187 memberof-group-oc top
1188 memberof-member-ad """ + res[i]["lDAPDisplayName"][0] + """
1189 memberof-memberof-ad """ + target + """
1190 memberof-dangling-error 32
1194 memberof_config = memberof_config + """
1196 refint_attributes""" + refint_attributes + "\n";
1198 setup_file(setup_path("slapd.conf"), paths.slapdconf,
1199 {"DNSDOMAIN": names.dnsdomain,
1200 "LDAPDIR": paths.ldapdir,
1201 "DOMAINDN": names.domaindn,
1202 "CONFIGDN": names.configdn,
1203 "SCHEMADN": names.schemadn,
1204 "LDAPMANAGERDN": names.ldapmanagerdn,
1205 "LDAPMANAGERPASS": adminpass,
1206 "MEMBEROF_CONFIG": memberof_config})
1207 setup_file(setup_path("modules.conf"), paths.modulesconf,
1208 {"REALM": names.realm})
1210 setup_db_config(setup_path, file, os.path.join(paths.ldapdir, "db", "user"))
1211 setup_db_config(setup_path, file, os.path.join(paths.ldapdir, "db", "config"))
1212 setup_db_config(setup_path, file, os.path.join(paths.ldapdir, "db", "schema"))
1213 mapping = "schema-map-openldap-2.3"
1214 backend_schema = "backend-schema.schema"
1217 ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
1218 message("Start slapd with: slapd -f " + paths.ldapdir + "/slapd.conf -h " + ldapi_uri)
1221 schema_command = "bin/ad2oLschema --option=convert:target=" + ldap_backend_type + " -I " + setup_path(mapping) + " -H tdb://" + schemadb_path + " -O " + os.path.join(paths.ldapdir, backend_schema);
1223 os.system(schema_command)
1227 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1228 """Create a PHP LDAP admin configuration file.
1230 :param path: Path to write the configuration to.
1231 :param setup_path: Function to generate setup paths.
1233 setup_file(setup_path("phpldapadmin-config.php"), path,
1234 {"S4_LDAPI_URI": ldapi_uri})
1237 def create_zone_file(path, setup_path, samdb, dnsdomain, domaindn,
1238 hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid):
1239 """Write out a DNS zone file, from the info in the current database.
1241 :param path: Path of the new file.
1242 :param setup_path": Setup path function.
1243 :param samdb: SamDB object
1244 :param dnsdomain: DNS Domain name
1245 :param domaindn: DN of the Domain
1246 :param hostip: Local IPv4 IP
1247 :param hostip6: Local IPv6 IP
1248 :param hostname: Local hostname
1249 :param dnspass: Password for DNS
1250 :param realm: Realm name
1251 :param domainguid: GUID of the domain.
1252 :param hostguid: GUID of the host.
1254 assert isinstance(domainguid, str)
1256 hostip6_base_line = ""
1257 hostip6_host_line = ""
1259 if hostip6 is not None:
1260 hostip6_base_line = " IN AAAA " + hostip6
1261 hostip6_host_line = hostname + " IN AAAA " + hostip6
1263 setup_file(setup_path("provision.zone"), path, {
1264 "DNSPASS_B64": b64encode(dnspass),
1265 "HOSTNAME": hostname,
1266 "DNSDOMAIN": dnsdomain,
1269 "DOMAINGUID": domainguid,
1270 "DATESTRING": time.strftime("%Y%m%d%H"),
1271 "DEFAULTSITE": DEFAULTSITE,
1272 "HOSTGUID": hostguid,
1273 "HOSTIP6_BASE_LINE": hostip6_base_line,
1274 "HOSTIP6_HOST_LINE": hostip6_host_line,
1277 def load_schema(setup_path, samdb, schemadn, netbiosname, configdn, sitename):
1278 """Load schema for the SamDB.
1280 :param samdb: Load a schema into a SamDB.
1281 :param setup_path: Setup path function.
1282 :param schemadn: DN of the schema
1283 :param netbiosname: NetBIOS name of the host.
1284 :param configdn: DN of the configuration
1286 schema_data = open(setup_path("schema.ldif"), 'r').read()
1287 schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
1288 schema_data = substitute_var(schema_data, {"SCHEMADN": schemadn})
1289 head_data = open(setup_path("provision_schema_basedn_modify.ldif"), 'r').read()
1290 head_data = substitute_var(head_data, {
1291 "SCHEMADN": schemadn,
1292 "NETBIOSNAME": netbiosname,
1293 "CONFIGDN": configdn,
1294 "DEFAULTSITE":sitename
1296 samdb.attach_schema_from_ldif(head_data, schema_data)