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")
268 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
269 serverrole=None, rootdn=None, domaindn=None, configdn=None,
270 schemadn=None, sitename=None):
273 hostname = socket.gethostname().split(".")[0].lower()
275 netbiosname = hostname.upper()
276 if not valid_netbios_name(netbiosname):
277 raise InvalidNetbiosName(netbiosname)
279 hostname = hostname.lower()
281 if dnsdomain is None:
282 dnsdomain = lp.get("realm")
284 if serverrole is None:
285 serverrole = lp.get("server role")
287 assert dnsdomain is not None
288 realm = dnsdomain.upper()
290 if lp.get("realm").upper() != realm:
291 raise Exception("realm '%s' in %s must match chosen realm '%s'" %
292 (lp.get("realm"), smbconf, realm))
294 dnsdomain = dnsdomain.lower()
296 if (serverrole == "domain controller"):
298 domain = lp.get("workgroup")
300 domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
301 if lp.get("workgroup").upper() != domain.upper():
302 raise Error("workgroup '%s' in smb.conf must match chosen domain '%s'",
303 lp.get("workgroup"), domain)
307 domaindn = "CN=" + netbiosname
309 assert domain is not None
310 domain = domain.upper()
311 if not valid_netbios_name(domain):
312 raise InvalidNetbiosName(domain)
318 configdn = "CN=Configuration," + rootdn
320 schemadn = "CN=Schema," + configdn
325 names = ProvisionNames()
326 names.rootdn = rootdn
327 names.domaindn = domaindn
328 names.configdn = configdn
329 names.schemadn = schemadn
330 names.ldapmanagerdn = "CN=Manager," + rootdn
331 names.dnsdomain = dnsdomain
332 names.domain = domain
334 names.netbiosname = netbiosname
335 names.hostname = hostname
336 names.sitename = sitename
341 def load_or_make_smbconf(smbconf, setup_path, hostname, domain, realm,
342 serverrole, targetdir):
343 if targetdir is not None:
344 if not os.path.exists(targetdir):
346 if not os.path.exists(os.path.join(targetdir, "etc")):
347 os.mkdir(os.path.join(targetdir, "etc"))
349 smbconf = os.path.join(targetdir, "etc", "smb.conf")
351 # only install a new smb.conf if there isn't one there already
353 if not os.path.exists(smbconf):
355 hostname = socket.gethostname().split(".")[0].lower()
357 if serverrole is None:
358 serverrole = "standalone"
360 assert serverrole in ("domain controller", "member server", "standalone")
361 if serverrole == "domain controller":
363 elif serverrole == "member server":
364 smbconfsuffix = "member"
365 elif serverrole == "standalone":
366 smbconfsuffix = "standalone"
368 assert domain is not None
369 assert realm is not None
371 default_lp = param.LoadParm()
372 #Load non-existant file
373 default_lp.load(smbconf)
375 if targetdir is not None:
376 privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
377 lockdir_line = "lock dir = " + os.path.abspath(targetdir)
379 default_lp.set("lock dir", os.path.abspath(targetdir))
384 sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
385 netlogon = os.path.join(sysvol, realm.lower(), "scripts")
387 setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
389 "HOSTNAME": hostname,
392 "SERVERROLE": serverrole,
393 "NETLOGONPATH": netlogon,
394 "SYSVOLPATH": sysvol,
395 "PRIVATEDIR_LINE": privatedir_line,
396 "LOCKDIR_LINE": lockdir_line
399 lp = param.LoadParm()
404 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
405 users_gid, wheel_gid):
406 """setup reasonable name mappings for sam names to unix names.
408 :param samdb: SamDB object.
409 :param idmap: IDmap db object.
410 :param sid: The domain sid.
411 :param domaindn: The domain DN.
412 :param root_uid: uid of the UNIX root user.
413 :param nobody_uid: uid of the UNIX nobody user.
414 :param users_gid: gid of the UNIX users group.
415 :param wheel_gid: gid of the UNIX wheel group."""
416 # add some foreign sids if they are not present already
417 samdb.add_foreign(domaindn, "S-1-5-7", "Anonymous")
418 samdb.add_foreign(domaindn, "S-1-1-0", "World")
419 samdb.add_foreign(domaindn, "S-1-5-2", "Network")
420 samdb.add_foreign(domaindn, "S-1-5-18", "System")
421 samdb.add_foreign(domaindn, "S-1-5-11", "Authenticated Users")
423 idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
424 idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
426 idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
427 idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
430 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
432 serverrole, ldap_backend=None,
433 ldap_backend_type=None, erase=False):
434 """Setup the partitions for the SAM database.
436 Alternatively, provision() may call this, and then populate the database.
438 :note: This will wipe the Sam Database!
440 :note: This function always removes the local SAM LDB file. The erase
441 parameter controls whether to erase the existing data, which
442 may not be stored locally but in LDAP.
444 assert session_info is not None
446 samdb = SamDB(samdb_path, session_info=session_info,
447 credentials=credentials, lp=lp)
453 os.unlink(samdb_path)
455 samdb = SamDB(samdb_path, session_info=session_info,
456 credentials=credentials, lp=lp)
458 #Add modules to the list to activate them by default
459 #beware often order is important
461 # Some Known ordering constraints:
462 # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
463 # - objectclass must be before password_hash, because password_hash checks
464 # that the objectclass is of type person (filled in by objectclass
465 # module when expanding the objectclass list)
466 # - partition must be last
467 # - each partition has its own module list then
468 modules_list = ["rootdse",
484 modules_list2 = ["show_deleted",
487 domaindn_ldb = "users.ldb"
488 if ldap_backend is not None:
489 domaindn_ldb = ldap_backend
490 configdn_ldb = "configuration.ldb"
491 if ldap_backend is not None:
492 configdn_ldb = ldap_backend
493 schemadn_ldb = "schema.ldb"
494 if ldap_backend is not None:
495 schema_ldb = ldap_backend
496 schemadn_ldb = ldap_backend
498 if ldap_backend_type == "fedora-ds":
499 backend_modules = ["nsuniqueid", "paged_searches"]
500 # We can handle linked attributes here, as we don't have directory-side subtree operations
501 tdb_modules_list = ["linked_attributes"]
502 elif ldap_backend_type == "openldap":
503 backend_modules = ["normalise", "entryuuid", "paged_searches"]
504 # OpenLDAP handles subtree renames, so we don't want to do any of these things
505 tdb_modules_list = None
506 elif serverrole == "domain controller":
507 backend_modules = ["repl_meta_data"]
509 backend_modules = ["objectguid"]
511 if tdb_modules_list is None:
512 tdb_modules_list_as_string = ""
514 tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
516 samdb.transaction_start()
518 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
519 "SCHEMADN": names.schemadn,
520 "SCHEMADN_LDB": schemadn_ldb,
521 "SCHEMADN_MOD2": ",objectguid",
522 "CONFIGDN": names.configdn,
523 "CONFIGDN_LDB": configdn_ldb,
524 "DOMAINDN": names.domaindn,
525 "DOMAINDN_LDB": domaindn_ldb,
526 "SCHEMADN_MOD": "schema_fsmo,instancetype",
527 "CONFIGDN_MOD": "naming_fsmo,instancetype",
528 "DOMAINDN_MOD": "pdc_fsmo,password_hash,instancetype",
529 "MODULES_LIST": ",".join(modules_list),
530 "TDB_MODULES_LIST": tdb_modules_list_as_string,
531 "MODULES_LIST2": ",".join(modules_list2),
532 "BACKEND_MOD": ",".join(backend_modules),
536 samdb.transaction_cancel()
539 samdb.transaction_commit()
541 samdb = SamDB(samdb_path, session_info=session_info,
542 credentials=credentials, lp=lp)
544 samdb.transaction_start()
546 message("Setting up sam.ldb attributes")
547 samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
549 message("Setting up sam.ldb rootDSE")
550 setup_samdb_rootdse(samdb, setup_path, names.schemadn, names.domaindn,
551 names.hostname, names.dnsdomain, names.realm,
552 names.rootdn, names.configdn, names.netbiosname,
556 message("Erasing data from partitions")
557 samdb.erase_partitions()
560 samdb.transaction_cancel()
563 samdb.transaction_commit()
568 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain,
569 netbiosname, domainsid, keytab_path, samdb_url,
570 dns_keytab_path, dnspass, machinepass):
571 """Add DC-specific bits to a secrets database.
573 :param secretsdb: Ldb Handle to the secrets database
574 :param setup_path: Setup path function
575 :param machinepass: Machine password
577 setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), {
578 "MACHINEPASS_B64": b64encode(machinepass),
581 "DNSDOMAIN": dnsdomain,
582 "DOMAINSID": str(domainsid),
583 "SECRETS_KEYTAB": keytab_path,
584 "NETBIOSNAME": netbiosname,
585 "SAM_LDB": samdb_url,
586 "DNS_KEYTAB": dns_keytab_path,
587 "DNSPASS_B64": b64encode(dnspass),
591 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
592 """Setup the secrets database.
594 :param path: Path to the secrets database.
595 :param setup_path: Get the path to a setup file.
596 :param session_info: Session info.
597 :param credentials: Credentials
598 :param lp: Loadparm context
599 :return: LDB handle for the created secrets database
601 if os.path.exists(path):
603 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
606 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
607 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
609 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
613 def setup_templatesdb(path, setup_path, session_info, credentials, lp):
614 """Setup the templates database.
616 :param path: Path to the database.
617 :param setup_path: Function for obtaining the path to setup files.
618 :param session_info: Session info
619 :param credentials: Credentials
620 :param lp: Loadparm context
622 templates_ldb = SamDB(path, session_info=session_info,
623 credentials=credentials, lp=lp)
624 templates_ldb.erase()
625 templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif"))
628 def setup_registry(path, setup_path, session_info, credentials, lp):
629 """Setup the registry.
631 :param path: Path to the registry database
632 :param setup_path: Function that returns the path to a setup.
633 :param session_info: Session information
634 :param credentials: Credentials
635 :param lp: Loadparm context
637 reg = registry.Registry()
638 hive = registry.open_ldb(path, session_info=session_info,
639 credentials=credentials, lp_ctx=lp)
640 reg.mount_hive(hive, "HKEY_LOCAL_MACHINE")
641 provision_reg = setup_path("provision.reg")
642 assert os.path.exists(provision_reg)
643 reg.diff_apply(provision_reg)
646 def setup_idmapdb(path, setup_path, session_info, credentials, lp):
647 """Setup the idmap database.
649 :param path: path to the idmap database
650 :param setup_path: Function that returns a path to a setup file
651 :param session_info: Session information
652 :param credentials: Credentials
653 :param lp: Loadparm context
655 if os.path.exists(path):
658 idmap_ldb = IDmapDB(path, session_info=session_info,
659 credentials=credentials, lp=lp)
662 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
666 def setup_samdb_rootdse(samdb, setup_path, schemadn, domaindn, hostname,
667 dnsdomain, realm, rootdn, configdn, netbiosname,
669 """Setup the SamDB rootdse.
671 :param samdb: Sam Database handle
672 :param setup_path: Obtain setup path
674 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
675 "SCHEMADN": schemadn,
676 "NETBIOSNAME": netbiosname,
677 "DNSDOMAIN": dnsdomain,
678 "DEFAULTSITE": sitename,
680 "DNSNAME": "%s.%s" % (hostname, dnsdomain),
681 "DOMAINDN": domaindn,
683 "CONFIGDN": configdn,
684 "VERSION": samba.version(),
688 def setup_self_join(samdb, names,
689 machinepass, dnspass,
690 domainsid, invocationid, setup_path,
692 """Join a host to its own domain."""
693 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
694 "CONFIGDN": names.configdn,
695 "SCHEMADN": names.schemadn,
696 "DOMAINDN": names.domaindn,
697 "INVOCATIONID": invocationid,
698 "NETBIOSNAME": names.netbiosname,
699 "DEFAULTSITE": names.sitename,
700 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
701 "MACHINEPASS_B64": b64encode(machinepass),
702 "DNSPASS_B64": b64encode(dnspass),
703 "REALM": names.realm,
704 "DOMAIN": names.domain,
705 "DNSDOMAIN": names.dnsdomain})
706 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
707 "POLICYGUID": policyguid,
708 "DNSDOMAIN": names.dnsdomain,
709 "DOMAINSID": str(domainsid),
710 "DOMAINDN": names.domaindn})
713 def setup_samdb(path, setup_path, session_info, credentials, lp,
715 domainsid, aci, domainguid, policyguid,
716 fill, adminpass, krbtgtpass,
717 machinepass, invocationid, dnspass,
718 serverrole, ldap_backend=None,
719 ldap_backend_type=None):
720 """Setup a complete SAM Database.
722 :note: This will wipe the main SAM database file!
725 erase = (fill != FILL_DRS)
727 # Also wipes the database
728 setup_samdb_partitions(path, setup_path, message=message, lp=lp,
729 credentials=credentials, session_info=session_info,
731 ldap_backend=ldap_backend, serverrole=serverrole,
732 ldap_backend_type=ldap_backend_type, erase=erase)
734 samdb = SamDB(path, session_info=session_info,
735 credentials=credentials, lp=lp)
738 # We want to finish here, but setup the index before we do so
739 message("Setting up sam.ldb index")
740 samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
743 message("Pre-loading the Samba 4 and AD schema")
744 samdb = SamDB(path, session_info=session_info,
745 credentials=credentials, lp=lp)
746 samdb.set_domain_sid(domainsid)
747 if serverrole == "domain controller":
748 samdb.set_invocation_id(invocationid)
750 load_schema(setup_path, samdb, names.schemadn, names.netbiosname,
751 names.configdn, names.sitename)
753 samdb.transaction_start()
756 message("Adding DomainDN: %s (permitted to fail)" % names.domaindn)
757 if serverrole == "domain controller":
758 domain_oc = "domainDNS"
760 domain_oc = "samba4LocalDomain"
762 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
763 "DOMAINDN": names.domaindn,
765 "DOMAIN_OC": domain_oc
768 message("Modifying DomainDN: " + names.domaindn + "")
769 if domainguid is not None:
770 domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
774 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
775 "LDAPTIME": timestring(int(time.time())),
776 "DOMAINSID": str(domainsid),
777 "SCHEMADN": names.schemadn,
778 "NETBIOSNAME": names.netbiosname,
779 "DEFAULTSITE": names.sitename,
780 "CONFIGDN": names.configdn,
781 "POLICYGUID": policyguid,
782 "DOMAINDN": names.domaindn,
783 "DOMAINGUID_MOD": domainguid_mod,
786 message("Adding configuration container (permitted to fail)")
787 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
788 "CONFIGDN": names.configdn,
790 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb",
792 message("Modifying configuration container")
793 setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
794 "CONFIGDN": names.configdn,
795 "SCHEMADN": names.schemadn,
798 message("Adding schema container (permitted to fail)")
799 setup_add_ldif(samdb, setup_path("provision_schema_basedn.ldif"), {
800 "SCHEMADN": names.schemadn,
802 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
804 message("Modifying schema container")
805 setup_modify_ldif(samdb,
806 setup_path("provision_schema_basedn_modify.ldif"), {
807 "SCHEMADN": names.schemadn,
808 "NETBIOSNAME": names.netbiosname,
809 "DEFAULTSITE": names.sitename,
810 "CONFIGDN": names.configdn,
813 message("Setting up sam.ldb Samba4 schema")
814 setup_add_ldif(samdb, setup_path("schema_samba4.ldif"),
815 {"SCHEMADN": names.schemadn })
816 message("Setting up sam.ldb AD schema")
817 setup_add_ldif(samdb, setup_path("schema.ldif"),
818 {"SCHEMADN": names.schemadn})
820 message("Setting up sam.ldb configuration data")
821 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
822 "CONFIGDN": names.configdn,
823 "NETBIOSNAME": names.netbiosname,
824 "DEFAULTSITE": names.sitename,
825 "DNSDOMAIN": names.dnsdomain,
826 "DOMAIN": names.domain,
827 "SCHEMADN": names.schemadn,
828 "DOMAINDN": names.domaindn,
831 message("Setting up display specifiers")
832 setup_add_ldif(samdb, setup_path("display_specifiers.ldif"),
833 {"CONFIGDN": names.configdn})
835 message("Adding users container (permitted to fail)")
836 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
837 "DOMAINDN": names.domaindn})
838 message("Modifying users container")
839 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
840 "DOMAINDN": names.domaindn})
841 message("Adding computers container (permitted to fail)")
842 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
843 "DOMAINDN": names.domaindn})
844 message("Modifying computers container")
845 setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
846 "DOMAINDN": names.domaindn})
847 message("Setting up sam.ldb data")
848 setup_add_ldif(samdb, setup_path("provision.ldif"), {
849 "DOMAINDN": names.domaindn,
850 "NETBIOSNAME": names.netbiosname,
851 "DEFAULTSITE": names.sitename,
852 "CONFIGDN": names.configdn,
855 if fill == FILL_FULL:
856 message("Setting up sam.ldb users and groups")
857 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
858 "DOMAINDN": names.domaindn,
859 "DOMAINSID": str(domainsid),
860 "CONFIGDN": names.configdn,
861 "ADMINPASS_B64": b64encode(adminpass),
862 "KRBTGTPASS_B64": b64encode(krbtgtpass),
865 if serverrole == "domain controller":
866 message("Setting up self join")
867 setup_self_join(samdb, names=names, invocationid=invocationid,
869 machinepass=machinepass,
870 domainsid=domainsid, policyguid=policyguid,
871 setup_path=setup_path)
873 #We want to setup the index last, as adds are faster unindexed
874 message("Setting up sam.ldb index")
875 samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
877 samdb.transaction_cancel()
880 samdb.transaction_commit()
885 FILL_NT4SYNC = "NT4SYNC"
888 def provision(setup_dir, message, session_info,
889 credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL, realm=None,
890 rootdn=None, domaindn=None, schemadn=None, configdn=None,
891 domain=None, hostname=None, hostip=None, hostip6=None,
892 domainsid=None, adminpass=None, krbtgtpass=None, domainguid=None,
893 policyguid=None, invocationid=None, machinepass=None,
894 dnspass=None, root=None, nobody=None, nogroup=None, users=None,
895 wheel=None, backup=None, aci=None, serverrole=None,
896 ldap_backend=None, ldap_backend_type=None, sitename=None):
899 :note: caution, this wipes all existing data!
902 def setup_path(file):
903 return os.path.join(setup_dir, file)
905 if domainsid is None:
906 domainsid = security.random_sid()
908 domainsid = security.Sid(domainsid)
910 if policyguid is None:
911 policyguid = uuid.random()
912 if adminpass is None:
913 adminpass = misc.random_password(12)
914 if krbtgtpass is None:
915 krbtgtpass = misc.random_password(12)
916 if machinepass is None:
917 machinepass = misc.random_password(12)
919 dnspass = misc.random_password(12)
921 root_uid = findnss(pwd.getpwnam, ["root"])[2]
923 root_uid = findnss(pwd.getpwnam, [root])[2]
925 nobody_uid = findnss(pwd.getpwnam, ["nobody"])[2]
927 nobody_uid = findnss(pwd.getpwnam, [nobody])[2]
929 users_gid = findnss(grp.getgrnam, ["users"])[2]
931 users_gid = findnss(grp.getgrnam, [users])[2]
933 wheel_gid = findnss(grp.getgrnam, ["wheel", "adm"])[2]
935 wheel_gid = findnss(grp.getgrnam, [wheel])[2]
937 aci = "# no aci for local ldb"
939 lp = load_or_make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, targetdir)
941 names = guess_names(lp=lp, hostname=hostname, domain=domain,
942 dnsdomain=realm, serverrole=serverrole, sitename=sitename,
943 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn)
945 paths = provision_paths_from_lp(lp, names.dnsdomain)
948 hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
952 hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
953 except socket.gaierror: pass
955 if serverrole is None:
956 serverrole = lp.get("server role")
958 assert serverrole in ("domain controller", "member server", "standalone")
959 if invocationid is None and serverrole == "domain controller":
960 invocationid = uuid.random()
962 if not os.path.exists(paths.private_dir):
963 os.mkdir(paths.private_dir)
965 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
967 if ldap_backend is not None:
968 if ldap_backend == "ldapi":
969 # provision-backend will set this path suggested slapd command line / fedorads.inf
970 ldap_backend = "ldapi://" % urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
972 # only install a new shares config db if there is none
973 if not os.path.exists(paths.shareconf):
974 message("Setting up share.ldb")
975 share_ldb = Ldb(paths.shareconf, session_info=session_info,
976 credentials=credentials, lp=lp)
977 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
980 message("Setting up secrets.ldb")
981 secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
982 session_info=session_info,
983 credentials=credentials, lp=lp)
985 message("Setting up the registry")
986 setup_registry(paths.hklm, setup_path, session_info,
987 credentials=credentials, lp=lp)
989 message("Setting up templates db")
990 setup_templatesdb(paths.templates, setup_path, session_info=session_info,
991 credentials=credentials, lp=lp)
993 message("Setting up idmap db")
994 idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
995 credentials=credentials, lp=lp)
997 samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info,
998 credentials=credentials, lp=lp, names=names,
1000 domainsid=domainsid,
1001 aci=aci, domainguid=domainguid, policyguid=policyguid,
1003 adminpass=adminpass, krbtgtpass=krbtgtpass,
1004 invocationid=invocationid,
1005 machinepass=machinepass, dnspass=dnspass,
1006 serverrole=serverrole, ldap_backend=ldap_backend,
1007 ldap_backend_type=ldap_backend_type)
1009 if lp.get("server role") == "domain controller":
1010 policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1011 "{" + policyguid + "}")
1012 os.makedirs(policy_path, 0755)
1013 os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1014 os.makedirs(os.path.join(policy_path, "User"), 0755)
1015 if not os.path.isdir(paths.netlogon):
1016 os.makedirs(paths.netlogon, 0755)
1017 secrets_ldb = Ldb(paths.secrets, session_info=session_info,
1018 credentials=credentials, lp=lp)
1019 secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1020 netbiosname=names.netbiosname, domainsid=domainsid,
1021 keytab_path=paths.keytab, samdb_url=paths.samdb,
1022 dns_keytab_path=paths.dns_keytab, dnspass=dnspass,
1023 machinepass=machinepass, dnsdomain=names.dnsdomain)
1025 if samdb_fill == FILL_FULL:
1026 setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1027 root_uid=root_uid, nobody_uid=nobody_uid,
1028 users_gid=users_gid, wheel_gid=wheel_gid)
1030 message("Setting up sam.ldb rootDSE marking as synchronized")
1031 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1033 # Only make a zone file on the first DC, it should be replicated with DNS replication
1034 if serverrole == "domain controller":
1035 samdb = SamDB(paths.samdb, session_info=session_info,
1036 credentials=credentials, lp=lp)
1038 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1039 assert isinstance(domainguid, str)
1040 hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1041 expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1042 scope=SCOPE_SUBTREE)
1043 assert isinstance(hostguid, str)
1045 create_zone_file(paths.dns, setup_path, samdb,
1046 hostname=names.hostname, hostip=hostip,
1047 hostip6=hostip6, dnsdomain=names.dnsdomain,
1048 domaindn=names.domaindn, dnspass=dnspass, realm=names.realm,
1049 domainguid=domainguid, hostguid=hostguid)
1050 message("Please install the zone located in %s into your DNS server" % paths.dns)
1052 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1055 message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1057 message("Once the above files are installed, your server will be ready to use")
1058 message("Server Type: %s" % serverrole)
1059 message("Hostname: %s" % names.hostname)
1060 message("NetBIOS Domain: %s" % names.domain)
1061 message("DNS Domain: %s" % names.dnsdomain)
1062 message("DOMAIN SID: %s" % str(domainsid))
1063 message("Admin password: %s" % adminpass)
1065 result = ProvisionResult()
1066 result.domaindn = domaindn
1067 result.paths = paths
1069 result.samdb = samdb
1072 def provision_become_dc(setup_dir=None,
1073 smbconf=None, targetdir=None, realm=None,
1074 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1075 domain=None, hostname=None, domainsid=None,
1076 adminpass=None, krbtgtpass=None, domainguid=None,
1077 policyguid=None, invocationid=None, machinepass=None,
1078 dnspass=None, root=None, nobody=None, nogroup=None, users=None,
1079 wheel=None, backup=None, aci=None, serverrole=None,
1080 ldap_backend=None, ldap_backend_type=None, sitename=DEFAULTSITE):
1083 """print a message if quiet is not set."""
1086 provision(setup_dir, message, system_session(), None,
1087 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm,
1088 rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn,
1089 domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename);
1092 def setup_db_config(setup_path, file, dbdir):
1093 if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1094 os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700);
1095 if not os.path.isdir(os.path.join(dbdir, "tmp")):
1096 os.makedirs(os.path.join(dbdir, "tmp"), 0700);
1098 setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1099 {"LDAPDBDIR": dbdir})
1103 def provision_backend(setup_dir=None, message=None,
1104 smbconf=None, targetdir=None, realm=None,
1105 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1106 domain=None, hostname=None, adminpass=None, root=None, serverrole=None,
1107 ldap_backend_type=None):
1109 def setup_path(file):
1110 return os.path.join(setup_dir, file)
1112 if hostname is None:
1113 hostname = socket.gethostname().split(".")[0].lower()
1116 root = findnss(pwd.getpwnam, ["root"])[0]
1118 lp = load_or_make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, targetdir)
1120 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1121 dnsdomain=realm, serverrole=serverrole,
1122 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn)
1124 paths = provision_paths_from_lp(lp, names.dnsdomain)
1126 if not os.path.isdir(paths.ldapdir):
1127 os.makedirs(paths.ldapdir)
1128 schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1130 os.unlink(schemadb_path)
1134 schemadb = Ldb(schemadb_path, lp=lp)
1136 setup_add_ldif(schemadb, setup_path("provision_schema_basedn.ldif"),
1137 {"SCHEMADN": names.schemadn,
1139 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
1141 setup_modify_ldif(schemadb,
1142 setup_path("provision_schema_basedn_modify.ldif"), \
1143 {"SCHEMADN": names.schemadn,
1144 "NETBIOSNAME": names.netbiosname,
1145 "DEFAULTSITE": DEFAULTSITE,
1146 "CONFIGDN": names.configdn,
1149 setup_add_ldif(schemadb, setup_path("schema_samba4.ldif"),
1150 {"SCHEMADN": names.schemadn })
1151 setup_add_ldif(schemadb, setup_path("schema.ldif"),
1152 {"SCHEMADN": names.schemadn})
1154 if ldap_backend_type == "fedora-ds":
1155 setup_file(setup_path("fedora-ds.inf"), paths.fedoradsinf,
1157 "HOSTNAME": hostname,
1158 "DNSDOMAIN": names.dnsdomain,
1159 "LDAPDIR": paths.ldapdir,
1160 "DOMAINDN": names.domaindn,
1161 "LDAPMANAGERDN": names.ldapmanagerdn,
1162 "LDAPMANAGERPASS": adminpass,
1165 setup_file(setup_path("fedora-partitions.ldif"), paths.fedoradspartitions,
1166 {"CONFIGDN": names.configdn,
1167 "SCHEMADN": names.schemadn,
1170 setup_file(setup_path("fedora-partitions.ldif"), paths.fedoradspartitions,
1171 {"CONFIGDN": names.configdn,
1172 "SCHEMADN": names.schemadn,
1174 mapping = "schema-map-fedora-ds-1.0"
1175 backend_schema = "99_ad.ldif"
1176 elif ldap_backend_type == "openldap":
1177 attrs = ["linkID", "lDAPDisplayName"]
1178 res = schemadb.search(expression="(&(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1)))(objectclass=attributeSchema))", base=names.schemadn, scope=SCOPE_SUBTREE, attrs=attrs);
1180 memberof_config = "# Generated from schema in " + schemadb_path + "\n";
1181 refint_attributes = "";
1182 for i in range (0, len(res)):
1183 linkid = res[i]["linkID"][0]
1184 linkid = str(int(linkid) + 1)
1185 expression = "(&(objectclass=attributeSchema)(linkID=" + (linkid) + "))"
1186 target = schemadb.searchone(basedn=names.schemadn,
1187 expression=expression,
1188 attribute="lDAPDisplayName",
1189 scope=SCOPE_SUBTREE);
1190 if target is not None:
1191 refint_attributes = refint_attributes + " " + target + " " + res[i]["lDAPDisplayName"][0];
1192 memberof_config = memberof_config + """overlay memberof
1193 memberof-dangling error
1194 memberof-refint TRUE
1195 memberof-group-oc top
1196 memberof-member-ad """ + res[i]["lDAPDisplayName"][0] + """
1197 memberof-memberof-ad """ + target + """
1198 memberof-dangling-error 32
1202 memberof_config = memberof_config + """
1204 refint_attributes""" + refint_attributes + "\n";
1206 setup_file(setup_path("slapd.conf"), paths.slapdconf,
1207 {"DNSDOMAIN": names.dnsdomain,
1208 "LDAPDIR": paths.ldapdir,
1209 "DOMAINDN": names.domaindn,
1210 "CONFIGDN": names.configdn,
1211 "SCHEMADN": names.schemadn,
1212 "LDAPMANAGERDN": names.ldapmanagerdn,
1213 "LDAPMANAGERPASS": adminpass,
1214 "MEMBEROF_CONFIG": memberof_config})
1215 setup_file(setup_path("modules.conf"), paths.modulesconf,
1216 {"REALM": names.realm})
1218 setup_db_config(setup_path, file, os.path.join(paths.ldapdir, "db", "user"))
1219 setup_db_config(setup_path, file, os.path.join(paths.ldapdir, "db", "config"))
1220 setup_db_config(setup_path, file, os.path.join(paths.ldapdir, "db", "schema"))
1221 mapping = "schema-map-openldap-2.3"
1222 backend_schema = "backend-schema.schema"
1225 ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
1226 message("Start slapd with: slapd -f " + paths.ldapdir + "/slapd.conf -h " + ldapi_uri)
1229 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);
1231 os.system(schema_command)
1235 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1236 """Create a PHP LDAP admin configuration file.
1238 :param path: Path to write the configuration to.
1239 :param setup_path: Function to generate setup paths.
1241 setup_file(setup_path("phpldapadmin-config.php"), path,
1242 {"S4_LDAPI_URI": ldapi_uri})
1245 def create_zone_file(path, setup_path, samdb, dnsdomain, domaindn,
1246 hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid):
1247 """Write out a DNS zone file, from the info in the current database.
1249 :param path: Path of the new file.
1250 :param setup_path": Setup path function.
1251 :param samdb: SamDB object
1252 :param dnsdomain: DNS Domain name
1253 :param domaindn: DN of the Domain
1254 :param hostip: Local IPv4 IP
1255 :param hostip6: Local IPv6 IP
1256 :param hostname: Local hostname
1257 :param dnspass: Password for DNS
1258 :param realm: Realm name
1259 :param domainguid: GUID of the domain.
1260 :param hostguid: GUID of the host.
1262 assert isinstance(domainguid, str)
1264 hostip6_base_line = ""
1265 hostip6_host_line = ""
1267 if hostip6 is not None:
1268 hostip6_base_line = " IN AAAA " + hostip6
1269 hostip6_host_line = hostname + " IN AAAA " + hostip6
1271 setup_file(setup_path("provision.zone"), path, {
1272 "DNSPASS_B64": b64encode(dnspass),
1273 "HOSTNAME": hostname,
1274 "DNSDOMAIN": dnsdomain,
1277 "DOMAINGUID": domainguid,
1278 "DATESTRING": time.strftime("%Y%m%d%H"),
1279 "DEFAULTSITE": DEFAULTSITE,
1280 "HOSTGUID": hostguid,
1281 "HOSTIP6_BASE_LINE": hostip6_base_line,
1282 "HOSTIP6_HOST_LINE": hostip6_host_line,
1285 def load_schema(setup_path, samdb, schemadn, netbiosname, configdn, sitename):
1286 """Load schema for the SamDB.
1288 :param samdb: Load a schema into a SamDB.
1289 :param setup_path: Setup path function.
1290 :param schemadn: DN of the schema
1291 :param netbiosname: NetBIOS name of the host.
1292 :param configdn: DN of the configuration
1294 schema_data = open(setup_path("schema.ldif"), 'r').read()
1295 schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
1296 schema_data = substitute_var(schema_data, {"SCHEMADN": schemadn})
1297 head_data = open(setup_path("provision_schema_basedn_modify.ldif"), 'r').read()
1298 head_data = substitute_var(head_data, {
1299 "SCHEMADN": schemadn,
1300 "NETBIOSNAME": netbiosname,
1301 "CONFIGDN": configdn,
1302 "DEFAULTSITE":sitename
1304 samdb.attach_schema_from_ldif(head_data, schema_data)