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 Exception("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 findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
129 findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
132 def open_ldb(session_info, credentials, lp, dbname):
133 """Open a LDB, thrashing it if it is corrupt.
135 :param session_info: auth session information
136 :param credentials: credentials
137 :param lp: Loadparm context
138 :param dbname: Path of the database to open.
139 :return: a Ldb object
141 assert session_info is not None
143 return Ldb(dbname, session_info=session_info, credentials=credentials,
148 return Ldb(dbname, session_info=session_info, credentials=credentials,
152 def setup_add_ldif(ldb, ldif_path, subst_vars=None):
153 """Setup a ldb in the private dir.
155 :param ldb: LDB file to import data into
156 :param ldif_path: Path of the LDIF file to load
157 :param subst_vars: Optional variables to subsitute in LDIF.
159 assert isinstance(ldif_path, str)
161 data = open(ldif_path, 'r').read()
162 if subst_vars is not None:
163 data = substitute_var(data, subst_vars)
165 check_all_substituted(data)
170 def setup_modify_ldif(ldb, ldif_path, substvars=None):
171 """Modify a ldb in the private dir.
173 :param ldb: LDB object.
174 :param ldif_path: LDIF file path.
175 :param substvars: Optional dictionary with substitution variables.
177 data = open(ldif_path, 'r').read()
178 if substvars is not None:
179 data = substitute_var(data, substvars)
181 check_all_substituted(data)
183 ldb.modify_ldif(data)
186 def setup_ldb(ldb, ldif_path, subst_vars):
187 """Import a LDIF a file into a LDB handle, optionally substituting variables.
189 :note: Either all LDIF data will be added or none (using transactions).
191 :param ldb: LDB file to import into.
192 :param ldif_path: Path to the LDIF file.
193 :param subst_vars: Dictionary with substitution variables.
195 assert ldb is not None
196 ldb.transaction_start()
198 setup_add_ldif(ldb, ldif_path, subst_vars)
200 ldb.transaction_cancel()
202 ldb.transaction_commit()
205 def setup_file(template, fname, substvars):
206 """Setup a file in the private dir.
208 :param template: Path of the template file.
209 :param fname: Path of the file to create.
210 :param substvars: Substitution variables.
214 if os.path.exists(f):
217 data = open(template, 'r').read()
219 data = substitute_var(data, substvars)
220 check_all_substituted(data)
222 open(f, 'w').write(data)
225 def provision_paths_from_lp(lp, dnsdomain):
226 """Set the default paths for provisioning.
228 :param lp: Loadparm context.
229 :param dnsdomain: DNS Domain name
231 paths = ProvisionPaths()
232 paths.private_dir = lp.get("private dir")
233 paths.keytab = "secrets.keytab"
234 paths.dns_keytab = "dns.keytab"
236 paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
237 paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb")
238 paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb")
239 paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
240 paths.templates = os.path.join(paths.private_dir, "templates.ldb")
241 paths.dns = os.path.join(paths.private_dir, dnsdomain + ".zone")
242 paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
243 paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
244 paths.phpldapadminconfig = os.path.join(paths.private_dir,
245 "phpldapadmin-config.php")
246 paths.ldapdir = os.path.join(paths.private_dir,
248 paths.slapdconf = os.path.join(paths.ldapdir,
250 paths.modulesconf = os.path.join(paths.ldapdir,
252 paths.memberofconf = os.path.join(paths.ldapdir,
254 paths.fedoradsinf = os.path.join(paths.ldapdir,
256 paths.fedoradspartitions = os.path.join(paths.ldapdir,
257 "fedorads-partitions.ldif")
258 paths.hklm = "hklm.ldb"
259 paths.hkcr = "hkcr.ldb"
260 paths.hkcu = "hkcu.ldb"
261 paths.hku = "hku.ldb"
262 paths.hkpd = "hkpd.ldb"
263 paths.hkpt = "hkpt.ldb"
265 paths.sysvol = lp.get("path", "sysvol")
267 paths.netlogon = lp.get("path", "netlogon")
272 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
273 serverrole=None, rootdn=None, domaindn=None, configdn=None,
274 schemadn=None, sitename=None):
277 hostname = socket.gethostname().split(".")[0].lower()
279 netbiosname = hostname.upper()
280 if not valid_netbios_name(netbiosname):
281 raise InvalidNetbiosName(netbiosname)
283 hostname = hostname.lower()
285 if dnsdomain is None:
286 dnsdomain = lp.get("realm")
288 if serverrole is None:
289 serverrole = lp.get("server role")
291 assert dnsdomain is not None
292 realm = dnsdomain.upper()
294 if lp.get("realm").upper() != realm:
295 raise Exception("realm '%s' must match chosen realm '%s'" %
296 (lp.get("realm"), realm))
298 dnsdomain = dnsdomain.lower()
300 if serverrole == "domain controller":
302 domain = lp.get("workgroup")
304 domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
305 if lp.get("workgroup").upper() != domain.upper():
306 raise Exception("workgroup '%s' in smb.conf must match chosen domain '%s'",
307 lp.get("workgroup"), domain)
311 domaindn = "CN=" + netbiosname
313 assert domain is not None
314 domain = domain.upper()
315 if not valid_netbios_name(domain):
316 raise InvalidNetbiosName(domain)
322 configdn = "CN=Configuration," + rootdn
324 schemadn = "CN=Schema," + configdn
329 names = ProvisionNames()
330 names.rootdn = rootdn
331 names.domaindn = domaindn
332 names.configdn = configdn
333 names.schemadn = schemadn
334 names.ldapmanagerdn = "CN=Manager," + rootdn
335 names.dnsdomain = dnsdomain
336 names.domain = domain
338 names.netbiosname = netbiosname
339 names.hostname = hostname
340 names.sitename = sitename
345 def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
348 hostname = socket.gethostname().split(".")[0].lower()
350 if serverrole is None:
351 serverrole = "standalone"
353 assert serverrole in ("domain controller", "member server", "standalone")
354 if serverrole == "domain controller":
356 elif serverrole == "member server":
357 smbconfsuffix = "member"
358 elif serverrole == "standalone":
359 smbconfsuffix = "standalone"
361 assert domain is not None
362 assert realm is not None
364 default_lp = param.LoadParm()
365 #Load non-existant file
366 default_lp.load(smbconf)
368 if targetdir is not None:
369 privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
370 lockdir_line = "lock dir = " + os.path.abspath(targetdir)
372 default_lp.set("lock dir", os.path.abspath(targetdir))
377 sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
378 netlogon = os.path.join(sysvol, realm.lower(), "scripts")
380 setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
382 "HOSTNAME": hostname,
385 "SERVERROLE": serverrole,
386 "NETLOGONPATH": netlogon,
387 "SYSVOLPATH": sysvol,
388 "PRIVATEDIR_LINE": privatedir_line,
389 "LOCKDIR_LINE": lockdir_line
393 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
394 users_gid, wheel_gid):
395 """setup reasonable name mappings for sam names to unix names.
397 :param samdb: SamDB object.
398 :param idmap: IDmap db object.
399 :param sid: The domain sid.
400 :param domaindn: The domain DN.
401 :param root_uid: uid of the UNIX root user.
402 :param nobody_uid: uid of the UNIX nobody user.
403 :param users_gid: gid of the UNIX users group.
404 :param wheel_gid: gid of the UNIX wheel group."""
405 # add some foreign sids if they are not present already
406 samdb.add_foreign(domaindn, "S-1-5-7", "Anonymous")
407 samdb.add_foreign(domaindn, "S-1-1-0", "World")
408 samdb.add_foreign(domaindn, "S-1-5-2", "Network")
409 samdb.add_foreign(domaindn, "S-1-5-18", "System")
410 samdb.add_foreign(domaindn, "S-1-5-11", "Authenticated Users")
412 idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
413 idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
415 idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
416 idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
419 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
421 serverrole, ldap_backend=None,
422 ldap_backend_type=None, erase=False):
423 """Setup the partitions for the SAM database.
425 Alternatively, provision() may call this, and then populate the database.
427 :note: This will wipe the Sam Database!
429 :note: This function always removes the local SAM LDB file. The erase
430 parameter controls whether to erase the existing data, which
431 may not be stored locally but in LDAP.
433 assert session_info is not None
435 samdb = SamDB(samdb_path, session_info=session_info,
436 credentials=credentials, lp=lp)
442 os.unlink(samdb_path)
444 samdb = SamDB(samdb_path, session_info=session_info,
445 credentials=credentials, lp=lp)
447 #Add modules to the list to activate them by default
448 #beware often order is important
450 # Some Known ordering constraints:
451 # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
452 # - objectclass must be before password_hash, because password_hash checks
453 # that the objectclass is of type person (filled in by objectclass
454 # module when expanding the objectclass list)
455 # - partition must be last
456 # - each partition has its own module list then
457 modules_list = ["rootdse",
473 modules_list2 = ["show_deleted",
476 domaindn_ldb = "users.ldb"
477 if ldap_backend is not None:
478 domaindn_ldb = ldap_backend
479 configdn_ldb = "configuration.ldb"
480 if ldap_backend is not None:
481 configdn_ldb = ldap_backend
482 schemadn_ldb = "schema.ldb"
483 if ldap_backend is not None:
484 schema_ldb = ldap_backend
485 schemadn_ldb = ldap_backend
487 if ldap_backend_type == "fedora-ds":
488 backend_modules = ["nsuniqueid", "paged_searches"]
489 # We can handle linked attributes here, as we don't have directory-side subtree operations
490 tdb_modules_list = ["linked_attributes"]
491 elif ldap_backend_type == "openldap":
492 backend_modules = ["normalise", "entryuuid", "paged_searches"]
493 # OpenLDAP handles subtree renames, so we don't want to do any of these things
494 tdb_modules_list = None
495 elif serverrole == "domain controller":
496 backend_modules = ["repl_meta_data"]
498 backend_modules = ["objectguid"]
500 if tdb_modules_list is None:
501 tdb_modules_list_as_string = ""
503 tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
505 samdb.transaction_start()
507 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
508 "SCHEMADN": names.schemadn,
509 "SCHEMADN_LDB": schemadn_ldb,
510 "SCHEMADN_MOD2": ",objectguid",
511 "CONFIGDN": names.configdn,
512 "CONFIGDN_LDB": configdn_ldb,
513 "DOMAINDN": names.domaindn,
514 "DOMAINDN_LDB": domaindn_ldb,
515 "SCHEMADN_MOD": "schema_fsmo,instancetype",
516 "CONFIGDN_MOD": "naming_fsmo,instancetype",
517 "DOMAINDN_MOD": "pdc_fsmo,password_hash,instancetype",
518 "MODULES_LIST": ",".join(modules_list),
519 "TDB_MODULES_LIST": tdb_modules_list_as_string,
520 "MODULES_LIST2": ",".join(modules_list2),
521 "BACKEND_MOD": ",".join(backend_modules),
525 samdb.transaction_cancel()
528 samdb.transaction_commit()
530 samdb = SamDB(samdb_path, session_info=session_info,
531 credentials=credentials, lp=lp)
533 samdb.transaction_start()
535 message("Setting up sam.ldb attributes")
536 samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
538 message("Setting up sam.ldb rootDSE")
539 setup_samdb_rootdse(samdb, setup_path, names.schemadn, names.domaindn,
540 names.hostname, names.dnsdomain, names.realm,
541 names.rootdn, names.configdn, names.netbiosname,
545 message("Erasing data from partitions")
546 samdb.erase_partitions()
549 samdb.transaction_cancel()
552 samdb.transaction_commit()
557 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain,
558 netbiosname, domainsid, keytab_path, samdb_url,
559 dns_keytab_path, dnspass, machinepass):
560 """Add DC-specific bits to a secrets database.
562 :param secretsdb: Ldb Handle to the secrets database
563 :param setup_path: Setup path function
564 :param machinepass: Machine password
566 setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), {
567 "MACHINEPASS_B64": b64encode(machinepass),
570 "DNSDOMAIN": dnsdomain,
571 "DOMAINSID": str(domainsid),
572 "SECRETS_KEYTAB": keytab_path,
573 "NETBIOSNAME": netbiosname,
574 "SAM_LDB": samdb_url,
575 "DNS_KEYTAB": dns_keytab_path,
576 "DNSPASS_B64": b64encode(dnspass),
580 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
581 """Setup the secrets database.
583 :param path: Path to the secrets database.
584 :param setup_path: Get the path to a setup file.
585 :param session_info: Session info.
586 :param credentials: Credentials
587 :param lp: Loadparm context
588 :return: LDB handle for the created secrets database
590 if os.path.exists(path):
592 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
595 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
596 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
598 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
602 def setup_templatesdb(path, setup_path, session_info, credentials, lp):
603 """Setup the templates database.
605 :param path: Path to the database.
606 :param setup_path: Function for obtaining the path to setup files.
607 :param session_info: Session info
608 :param credentials: Credentials
609 :param lp: Loadparm context
611 templates_ldb = SamDB(path, session_info=session_info,
612 credentials=credentials, lp=lp)
613 templates_ldb.erase()
614 templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif"))
617 def setup_registry(path, setup_path, session_info, credentials, lp):
618 """Setup the registry.
620 :param path: Path to the registry database
621 :param setup_path: Function that returns the path to a setup.
622 :param session_info: Session information
623 :param credentials: Credentials
624 :param lp: Loadparm context
626 reg = registry.Registry()
627 hive = registry.open_ldb(path, session_info=session_info,
628 credentials=credentials, lp_ctx=lp)
629 reg.mount_hive(hive, "HKEY_LOCAL_MACHINE")
630 provision_reg = setup_path("provision.reg")
631 assert os.path.exists(provision_reg)
632 reg.diff_apply(provision_reg)
635 def setup_idmapdb(path, setup_path, session_info, credentials, lp):
636 """Setup the idmap database.
638 :param path: path to the idmap database
639 :param setup_path: Function that returns a path to a setup file
640 :param session_info: Session information
641 :param credentials: Credentials
642 :param lp: Loadparm context
644 if os.path.exists(path):
647 idmap_ldb = IDmapDB(path, session_info=session_info,
648 credentials=credentials, lp=lp)
651 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
655 def setup_samdb_rootdse(samdb, setup_path, schemadn, domaindn, hostname,
656 dnsdomain, realm, rootdn, configdn, netbiosname,
658 """Setup the SamDB rootdse.
660 :param samdb: Sam Database handle
661 :param setup_path: Obtain setup path
663 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
664 "SCHEMADN": schemadn,
665 "NETBIOSNAME": netbiosname,
666 "DNSDOMAIN": dnsdomain,
667 "DEFAULTSITE": sitename,
669 "DNSNAME": "%s.%s" % (hostname, dnsdomain),
670 "DOMAINDN": domaindn,
672 "CONFIGDN": configdn,
673 "VERSION": samba.version(),
677 def setup_self_join(samdb, names,
678 machinepass, dnspass,
679 domainsid, invocationid, setup_path,
681 """Join a host to its own domain."""
682 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
683 "CONFIGDN": names.configdn,
684 "SCHEMADN": names.schemadn,
685 "DOMAINDN": names.domaindn,
686 "INVOCATIONID": invocationid,
687 "NETBIOSNAME": names.netbiosname,
688 "DEFAULTSITE": names.sitename,
689 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
690 "MACHINEPASS_B64": b64encode(machinepass),
691 "DNSPASS_B64": b64encode(dnspass),
692 "REALM": names.realm,
693 "DOMAIN": names.domain,
694 "DNSDOMAIN": names.dnsdomain})
695 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
696 "POLICYGUID": policyguid,
697 "DNSDOMAIN": names.dnsdomain,
698 "DOMAINSID": str(domainsid),
699 "DOMAINDN": names.domaindn})
702 def setup_samdb(path, setup_path, session_info, credentials, lp,
704 domainsid, aci, domainguid, policyguid,
705 fill, adminpass, krbtgtpass,
706 machinepass, invocationid, dnspass,
707 serverrole, ldap_backend=None,
708 ldap_backend_type=None):
709 """Setup a complete SAM Database.
711 :note: This will wipe the main SAM database file!
714 erase = (fill != FILL_DRS)
716 # Also wipes the database
717 setup_samdb_partitions(path, setup_path, message=message, lp=lp,
718 credentials=credentials, session_info=session_info,
720 ldap_backend=ldap_backend, serverrole=serverrole,
721 ldap_backend_type=ldap_backend_type, erase=erase)
723 samdb = SamDB(path, session_info=session_info,
724 credentials=credentials, lp=lp)
727 # We want to finish here, but setup the index before we do so
728 message("Setting up sam.ldb index")
729 samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
732 message("Pre-loading the Samba 4 and AD schema")
733 samdb = SamDB(path, session_info=session_info,
734 credentials=credentials, lp=lp)
735 samdb.set_domain_sid(domainsid)
736 if serverrole == "domain controller":
737 samdb.set_invocation_id(invocationid)
739 load_schema(setup_path, samdb, names.schemadn, names.netbiosname,
740 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,
879 realm=None, rootdn=None, domaindn=None, schemadn=None,
880 configdn=None, domain=None, hostname=None, hostip=None,
881 hostip6=None, domainsid=None, adminpass=None, krbtgtpass=None,
882 domainguid=None, policyguid=None, invocationid=None,
883 machinepass=None, dnspass=None, root=None, nobody=None,
884 nogroup=None, users=None, wheel=None, backup=None, aci=None,
885 serverrole=None, ldap_backend=None, ldap_backend_type=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)
910 root_uid = findnss_uid([root or "root"])
911 nobody_uid = findnss_uid([nobody or "nobody"])
912 users_gid = findnss_gid([users or "users"])
914 wheel_gid = findnss_gid(["wheel", "adm"])
916 wheel_gid = findnss_gid([wheel])
918 aci = "# no aci for local ldb"
921 os.makedirs(os.path.join(targetdir, "etc"))
922 smbconf = os.path.join(targetdir, "etc", "smb.conf")
924 # only install a new smb.conf if there isn't one there already
925 if not os.path.exists(smbconf):
926 make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
929 lp = param.LoadParm()
932 names = guess_names(lp=lp, hostname=hostname, domain=domain,
933 dnsdomain=realm, serverrole=serverrole,
934 sitename=sitename, rootdn=rootdn, domaindn=domaindn,
935 configdn=configdn, schemadn=schemadn)
937 paths = provision_paths_from_lp(lp, names.dnsdomain)
940 hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
944 hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
945 except socket.gaierror:
948 if serverrole is None:
949 serverrole = lp.get("server role")
951 assert serverrole in ("domain controller", "member server", "standalone")
952 if invocationid is None and serverrole == "domain controller":
953 invocationid = uuid.random()
955 if not os.path.exists(paths.private_dir):
956 os.mkdir(paths.private_dir)
958 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
960 if ldap_backend is not None:
961 if ldap_backend == "ldapi":
962 # provision-backend will set this path suggested slapd command line / fedorads.inf
963 ldap_backend = "ldapi://" % urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
965 # only install a new shares config db if there is none
966 if not os.path.exists(paths.shareconf):
967 message("Setting up share.ldb")
968 share_ldb = Ldb(paths.shareconf, session_info=session_info,
969 credentials=credentials, lp=lp)
970 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
973 message("Setting up secrets.ldb")
974 secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
975 session_info=session_info,
976 credentials=credentials, lp=lp)
978 message("Setting up the registry")
979 setup_registry(paths.hklm, setup_path, session_info,
980 credentials=credentials, lp=lp)
982 message("Setting up templates db")
983 setup_templatesdb(paths.templates, setup_path, session_info=session_info,
984 credentials=credentials, lp=lp)
986 message("Setting up idmap db")
987 idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
988 credentials=credentials, lp=lp)
990 samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info,
991 credentials=credentials, lp=lp, names=names,
994 aci=aci, domainguid=domainguid, policyguid=policyguid,
996 adminpass=adminpass, krbtgtpass=krbtgtpass,
997 invocationid=invocationid,
998 machinepass=machinepass, dnspass=dnspass,
999 serverrole=serverrole, ldap_backend=ldap_backend,
1000 ldap_backend_type=ldap_backend_type)
1002 if lp.get("server role") == "domain controller":
1003 policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1004 "{" + policyguid + "}")
1005 os.makedirs(policy_path, 0755)
1006 os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1007 os.makedirs(os.path.join(policy_path, "User"), 0755)
1008 if not os.path.isdir(paths.netlogon):
1009 os.makedirs(paths.netlogon, 0755)
1010 secrets_ldb = Ldb(paths.secrets, session_info=session_info,
1011 credentials=credentials, lp=lp)
1012 secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1013 netbiosname=names.netbiosname, domainsid=domainsid,
1014 keytab_path=paths.keytab, samdb_url=paths.samdb,
1015 dns_keytab_path=paths.dns_keytab, dnspass=dnspass,
1016 machinepass=machinepass, dnsdomain=names.dnsdomain)
1018 if samdb_fill == FILL_FULL:
1019 setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1020 root_uid=root_uid, nobody_uid=nobody_uid,
1021 users_gid=users_gid, wheel_gid=wheel_gid)
1023 message("Setting up sam.ldb rootDSE marking as synchronized")
1024 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1026 # Only make a zone file on the first DC, it should be replicated with DNS replication
1027 if serverrole == "domain controller":
1028 samdb = SamDB(paths.samdb, session_info=session_info,
1029 credentials=credentials, lp=lp)
1031 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1032 assert isinstance(domainguid, str)
1033 hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1034 expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1035 scope=SCOPE_SUBTREE)
1036 assert isinstance(hostguid, str)
1038 create_zone_file(paths.dns, setup_path, samdb,
1039 hostname=names.hostname, hostip=hostip,
1040 hostip6=hostip6, dnsdomain=names.dnsdomain,
1041 domaindn=names.domaindn, dnspass=dnspass, realm=names.realm,
1042 domainguid=domainguid, hostguid=hostguid)
1043 message("Please install the zone located in %s into your DNS server" % paths.dns)
1045 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1048 message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1050 message("Once the above files are installed, your server will be ready to use")
1051 message("Server Type: %s" % serverrole)
1052 message("Hostname: %s" % names.hostname)
1053 message("NetBIOS Domain: %s" % names.domain)
1054 message("DNS Domain: %s" % names.dnsdomain)
1055 message("DOMAIN SID: %s" % str(domainsid))
1056 message("Admin password: %s" % adminpass)
1058 result = ProvisionResult()
1059 result.domaindn = domaindn
1060 result.paths = paths
1062 result.samdb = samdb
1065 def provision_become_dc(setup_dir=None,
1066 smbconf=None, targetdir=None, realm=None,
1067 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1068 domain=None, hostname=None, domainsid=None,
1069 adminpass=None, krbtgtpass=None, domainguid=None,
1070 policyguid=None, invocationid=None, machinepass=None,
1071 dnspass=None, root=None, nobody=None, nogroup=None, users=None,
1072 wheel=None, backup=None, aci=None, serverrole=None,
1073 ldap_backend=None, ldap_backend_type=None, sitename=DEFAULTSITE):
1076 """print a message if quiet is not set."""
1079 provision(setup_dir, message, system_session(), None,
1080 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm,
1081 rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn,
1082 domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename);
1085 def setup_db_config(setup_path, file, dbdir):
1086 if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1087 os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700);
1088 if not os.path.isdir(os.path.join(dbdir, "tmp")):
1089 os.makedirs(os.path.join(dbdir, "tmp"), 0700);
1091 setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1092 {"LDAPDBDIR": dbdir})
1096 def provision_backend(setup_dir=None, message=None,
1097 smbconf=None, targetdir=None, realm=None,
1098 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1099 domain=None, hostname=None, adminpass=None, root=None, serverrole=None,
1100 ldap_backend_type=None):
1102 def setup_path(file):
1103 return os.path.join(setup_dir, file)
1105 if hostname is None:
1106 hostname = socket.gethostname().split(".")[0].lower()
1109 root = findnss(pwd.getpwnam, ["root"])[0]
1112 os.makedirs(os.path.join(targetdir, "etc"))
1113 smbconf = os.path.join(targetdir, "etc", "smb.conf")
1115 # only install a new smb.conf if there isn't one there already
1116 if not os.path.exists(smbconf):
1117 make_smbconf(smbconf, setup_path, hostname, domain, realm,
1118 serverrole, targetdir)
1120 lp = param.LoadParm()
1123 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1124 dnsdomain=realm, serverrole=serverrole,
1125 rootdn=rootdn, domaindn=domaindn, configdn=configdn,
1128 paths = provision_paths_from_lp(lp, names.dnsdomain)
1130 if not os.path.isdir(paths.ldapdir):
1131 os.makedirs(paths.ldapdir)
1132 schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1134 os.unlink(schemadb_path)
1138 schemadb = Ldb(schemadb_path, lp=lp)
1140 setup_add_ldif(schemadb, setup_path("provision_schema_basedn.ldif"),
1141 {"SCHEMADN": names.schemadn,
1143 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
1145 setup_modify_ldif(schemadb,
1146 setup_path("provision_schema_basedn_modify.ldif"), \
1147 {"SCHEMADN": names.schemadn,
1148 "NETBIOSNAME": names.netbiosname,
1149 "DEFAULTSITE": DEFAULTSITE,
1150 "CONFIGDN": names.configdn,
1153 setup_add_ldif(schemadb, setup_path("schema_samba4.ldif"),
1154 {"SCHEMADN": names.schemadn })
1155 setup_add_ldif(schemadb, setup_path("schema.ldif"),
1156 {"SCHEMADN": names.schemadn})
1158 if ldap_backend_type == "fedora-ds":
1159 setup_file(setup_path("fedora-ds.inf"), paths.fedoradsinf,
1161 "HOSTNAME": hostname,
1162 "DNSDOMAIN": names.dnsdomain,
1163 "LDAPDIR": paths.ldapdir,
1164 "DOMAINDN": names.domaindn,
1165 "LDAPMANAGERDN": names.ldapmanagerdn,
1166 "LDAPMANAGERPASS": adminpass,
1169 setup_file(setup_path("fedora-partitions.ldif"), paths.fedoradspartitions,
1170 {"CONFIGDN": names.configdn,
1171 "SCHEMADN": names.schemadn,
1174 setup_file(setup_path("fedora-partitions.ldif"), paths.fedoradspartitions,
1175 {"CONFIGDN": names.configdn,
1176 "SCHEMADN": names.schemadn,
1178 mapping = "schema-map-fedora-ds-1.0"
1179 backend_schema = "99_ad.ldif"
1180 elif ldap_backend_type == "openldap":
1181 attrs = ["linkID", "lDAPDisplayName"]
1182 res = schemadb.search(expression="(&(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1)))(objectclass=attributeSchema))", base=names.schemadn, scope=SCOPE_SUBTREE, attrs=attrs);
1184 memberof_config = "# Generated from schema in " + schemadb_path + "\n";
1185 refint_attributes = "";
1186 for i in range (0, len(res)):
1187 linkid = res[i]["linkID"][0]
1188 linkid = str(int(linkid) + 1)
1189 expression = "(&(objectclass=attributeSchema)(linkID=" + (linkid) + "))"
1190 target = schemadb.searchone(basedn=names.schemadn,
1191 expression=expression,
1192 attribute="lDAPDisplayName",
1193 scope=SCOPE_SUBTREE);
1194 if target is not None:
1195 refint_attributes = refint_attributes + " " + target + " " + res[i]["lDAPDisplayName"][0];
1196 memberof_config = memberof_config + """overlay memberof
1197 memberof-dangling error
1198 memberof-refint TRUE
1199 memberof-group-oc top
1200 memberof-member-ad """ + res[i]["lDAPDisplayName"][0] + """
1201 memberof-memberof-ad """ + target + """
1202 memberof-dangling-error 32
1206 memberof_config = memberof_config + """
1208 refint_attributes""" + refint_attributes + "\n";
1210 setup_file(setup_path("slapd.conf"), paths.slapdconf,
1211 {"DNSDOMAIN": names.dnsdomain,
1212 "LDAPDIR": paths.ldapdir,
1213 "DOMAINDN": names.domaindn,
1214 "CONFIGDN": names.configdn,
1215 "SCHEMADN": names.schemadn,
1216 "LDAPMANAGERDN": names.ldapmanagerdn,
1217 "LDAPMANAGERPASS": adminpass,
1218 "MEMBEROF_CONFIG": memberof_config})
1219 setup_file(setup_path("modules.conf"), paths.modulesconf,
1220 {"REALM": names.realm})
1222 setup_db_config(setup_path, file, os.path.join(paths.ldapdir, "db", "user"))
1223 setup_db_config(setup_path, file, os.path.join(paths.ldapdir, "db", "config"))
1224 setup_db_config(setup_path, file, os.path.join(paths.ldapdir, "db", "schema"))
1225 mapping = "schema-map-openldap-2.3"
1226 backend_schema = "backend-schema.schema"
1229 ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
1230 message("Start slapd with: slapd -f " + paths.ldapdir + "/slapd.conf -h " + ldapi_uri)
1233 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);
1235 os.system(schema_command)
1239 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1240 """Create a PHP LDAP admin configuration file.
1242 :param path: Path to write the configuration to.
1243 :param setup_path: Function to generate setup paths.
1245 setup_file(setup_path("phpldapadmin-config.php"), path,
1246 {"S4_LDAPI_URI": ldapi_uri})
1249 def create_zone_file(path, setup_path, samdb, dnsdomain, domaindn,
1250 hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid):
1251 """Write out a DNS zone file, from the info in the current database.
1253 :param path: Path of the new file.
1254 :param setup_path": Setup path function.
1255 :param samdb: SamDB object
1256 :param dnsdomain: DNS Domain name
1257 :param domaindn: DN of the Domain
1258 :param hostip: Local IPv4 IP
1259 :param hostip6: Local IPv6 IP
1260 :param hostname: Local hostname
1261 :param dnspass: Password for DNS
1262 :param realm: Realm name
1263 :param domainguid: GUID of the domain.
1264 :param hostguid: GUID of the host.
1266 assert isinstance(domainguid, str)
1268 hostip6_base_line = ""
1269 hostip6_host_line = ""
1271 if hostip6 is not None:
1272 hostip6_base_line = " IN AAAA " + hostip6
1273 hostip6_host_line = hostname + " IN AAAA " + hostip6
1275 setup_file(setup_path("provision.zone"), path, {
1276 "DNSPASS_B64": b64encode(dnspass),
1277 "HOSTNAME": hostname,
1278 "DNSDOMAIN": dnsdomain,
1281 "DOMAINGUID": domainguid,
1282 "DATESTRING": time.strftime("%Y%m%d%H"),
1283 "DEFAULTSITE": DEFAULTSITE,
1284 "HOSTGUID": hostguid,
1285 "HOSTIP6_BASE_LINE": hostip6_base_line,
1286 "HOSTIP6_HOST_LINE": hostip6_host_line,
1289 def load_schema(setup_path, samdb, schemadn, netbiosname, configdn, sitename):
1290 """Load schema for the SamDB.
1292 :param samdb: Load a schema into a SamDB.
1293 :param setup_path: Setup path function.
1294 :param schemadn: DN of the schema
1295 :param netbiosname: NetBIOS name of the host.
1296 :param configdn: DN of the configuration
1298 schema_data = open(setup_path("schema.ldif"), 'r').read()
1299 schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
1300 schema_data = substitute_var(schema_data, {"SCHEMADN": schemadn})
1301 head_data = open(setup_path("provision_schema_basedn_modify.ldif"), 'r').read()
1302 head_data = substitute_var(head_data, {
1303 "SCHEMADN": schemadn,
1304 "NETBIOSNAME": netbiosname,
1305 "CONFIGDN": configdn,
1306 "DEFAULTSITE":sitename
1308 samdb.attach_schema_from_ldif(head_data, schema_data)