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
31 from socket import gethostname, gethostbyname
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
40 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, \
41 LDB_ERR_NO_SUCH_OBJECT, timestring, CHANGETYPE_MODIFY, CHANGETYPE_NONE
43 """Functions for setting up a Samba configuration."""
45 DEFAULTSITE = "Default-First-Site-Name"
47 class InvalidNetbiosName(Exception):
48 def __init__(self, name):
49 super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)
65 self.dns_keytab = None
68 self.private_dir = None
71 self.modulesconf = None
72 self.memberofconf = None
73 self.fedoradsinf = None
74 self.fedoradspartitions = None
82 self.ldapmanagerdn = None
85 self.netbiosname = None
90 class ProvisionResult:
97 def check_install(lp, session_info, credentials):
98 """Check whether the current install seems ok.
100 :param lp: Loadparm context
101 :param session_info: Session information
102 :param credentials: Credentials
104 if lp.get("realm") == "":
105 raise Error("Realm empty")
106 ldb = Ldb(lp.get("sam database"), session_info=session_info,
107 credentials=credentials, lp=lp)
108 if len(ldb.search("(cn=Administrator)")) != 1:
109 raise "No administrator account found"
112 def findnss(nssfn, names):
113 """Find a user or group from a list of possibilities.
115 :param nssfn: NSS Function to try (should raise KeyError if not found)
116 :param names: Names to check.
117 :return: Value return by first names list.
124 raise KeyError("Unable to find user/group %r" % names)
127 def open_ldb(session_info, credentials, lp, dbname):
128 """Open a LDB, thrashing it if it is corrupt.
130 :param session_info: auth session information
131 :param credentials: credentials
132 :param lp: Loadparm context
133 :param dbname: Path of the database to open.
134 :return: a Ldb object
136 assert session_info is not None
138 return Ldb(dbname, session_info=session_info, credentials=credentials,
143 return Ldb(dbname, session_info=session_info, credentials=credentials,
147 def setup_add_ldif(ldb, ldif_path, subst_vars=None):
148 """Setup a ldb in the private dir.
150 :param ldb: LDB file to import data into
151 :param ldif_path: Path of the LDIF file to load
152 :param subst_vars: Optional variables to subsitute in LDIF.
154 assert isinstance(ldif_path, str)
156 data = open(ldif_path, 'r').read()
157 if subst_vars is not None:
158 data = substitute_var(data, subst_vars)
160 check_all_substituted(data)
165 def setup_modify_ldif(ldb, ldif_path, substvars=None):
166 """Modify a ldb in the private dir.
168 :param ldb: LDB object.
169 :param ldif_path: LDIF file path.
170 :param substvars: Optional dictionary with substitution variables.
172 data = open(ldif_path, 'r').read()
173 if substvars is not None:
174 data = substitute_var(data, substvars)
176 check_all_substituted(data)
178 ldb.modify_ldif(data)
181 def setup_ldb(ldb, ldif_path, subst_vars):
182 """Import a LDIF a file into a LDB handle, optionally substituting variables.
184 :note: Either all LDIF data will be added or none (using transactions).
186 :param ldb: LDB file to import into.
187 :param ldif_path: Path to the LDIF file.
188 :param subst_vars: Dictionary with substitution variables.
190 assert ldb is not None
191 ldb.transaction_start()
193 setup_add_ldif(ldb, ldif_path, subst_vars)
195 ldb.transaction_cancel()
197 ldb.transaction_commit()
200 def setup_file(template, fname, substvars):
201 """Setup a file in the private dir.
203 :param template: Path of the template file.
204 :param fname: Path of the file to create.
205 :param substvars: Substitution variables.
209 if os.path.exists(f):
212 data = open(template, 'r').read()
214 data = substitute_var(data, substvars)
215 check_all_substituted(data)
217 open(f, 'w').write(data)
220 def provision_paths_from_lp(lp, dnsdomain):
221 """Set the default paths for provisioning.
223 :param lp: Loadparm context.
224 :param dnsdomain: DNS Domain name
226 paths = ProvisionPaths()
227 paths.private_dir = lp.get("private dir")
228 paths.keytab = "secrets.keytab"
229 paths.dns_keytab = "dns.keytab"
231 paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
232 paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb")
233 paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb")
234 paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
235 paths.templates = os.path.join(paths.private_dir, "templates.ldb")
236 paths.dns = os.path.join(paths.private_dir, dnsdomain + ".zone")
237 paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
238 paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
239 paths.phpldapadminconfig = os.path.join(paths.private_dir,
240 "phpldapadmin-config.php")
241 paths.ldapdir = os.path.join(paths.private_dir,
243 paths.slapdconf = os.path.join(paths.ldapdir,
245 paths.modulesconf = os.path.join(paths.ldapdir,
247 paths.memberofconf = os.path.join(paths.ldapdir,
249 paths.fedoradsinf = os.path.join(paths.ldapdir,
251 paths.fedoradspartitions = os.path.join(paths.ldapdir,
252 "fedorads-partitions.ldif")
253 paths.hklm = "hklm.ldb"
254 paths.hkcr = "hkcr.ldb"
255 paths.hkcu = "hkcu.ldb"
256 paths.hku = "hku.ldb"
257 paths.hkpd = "hkpd.ldb"
258 paths.hkpt = "hkpt.ldb"
260 paths.sysvol = lp.get("path", "sysvol")
262 paths.netlogon = lp.get("path", "netlogon")
266 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, serverrole=None,
267 rootdn=None, domaindn=None, configdn=None, schemadn=None, sitename=None):
270 hostname = gethostname().split(".")[0].lower()
272 netbiosname = hostname.upper()
273 if not valid_netbios_name(netbiosname):
274 raise InvalidNetbiosName(netbiosname)
276 hostname = hostname.lower()
278 if dnsdomain is None:
279 dnsdomain = lp.get("realm")
281 if serverrole is None:
282 serverrole = lp.get("server role")
284 assert dnsdomain is not None
285 realm = dnsdomain.upper()
287 if lp.get("realm").upper() != realm:
288 raise Exception("realm '%s' in %s must match chosen realm '%s'" %
289 (lp.get("realm"), smbconf, realm))
291 dnsdomain = dnsdomain.lower()
293 if (serverrole == "domain controller"):
295 domain = lp.get("workgroup")
297 domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
298 if lp.get("workgroup").upper() != domain.upper():
299 raise Error("workgroup '%s' in smb.conf must match chosen domain '%s'",
300 lp.get("workgroup"), domain)
304 domaindn = "CN=" + netbiosname
306 assert domain is not None
307 domain = domain.upper()
308 if not valid_netbios_name(domain):
309 raise InvalidNetbiosName(domain)
315 configdn = "CN=Configuration," + rootdn
317 schemadn = "CN=Schema," + configdn
322 names = ProvisionNames()
323 names.rootdn = rootdn
324 names.domaindn = domaindn
325 names.configdn = configdn
326 names.schemadn = schemadn
327 names.ldapmanagerdn = "CN=Manager," + rootdn
328 names.dnsdomain = dnsdomain
329 names.domain = domain
331 names.netbiosname = netbiosname
332 names.hostname = hostname
333 names.sitename = sitename
338 def load_or_make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, targetdir):
339 if targetdir is not None:
340 if not os.path.exists(targetdir):
342 if not os.path.exists(os.path.join(targetdir, "etc")):
343 os.mkdir(os.path.join(targetdir, "etc"))
345 smbconf = os.path.join(targetdir, "etc", "smb.conf")
347 # only install a new smb.conf if there isn't one there already
349 if not os.path.exists(smbconf):
351 hostname = gethostname().split(".")[0].lower()
353 if serverrole is None:
354 serverrole = "standalone"
356 assert serverrole in ("domain controller", "member server", "standalone")
357 if serverrole == "domain controller":
359 elif serverrole == "member server":
360 smbconfsuffix = "member"
361 elif serverrole == "standalone":
362 smbconfsuffix = "standalone"
364 assert domain is not None
365 assert realm is not None
367 default_lp = param.LoadParm()
368 #Load non-existant file
369 default_lp.load(smbconf)
371 if targetdir is not None:
372 privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
373 lockdir_line = "lock dir = " + os.path.abspath(targetdir)
375 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
392 lp = param.LoadParm()
397 def setup_name_mappings(ldb, sid, domaindn, root, nobody, nogroup, users,
399 """setup reasonable name mappings for sam names to unix names.
401 :param ldb: SamDB object.
402 :param sid: The domain sid.
403 :param domaindn: The domain DN.
404 :param root: Name of the UNIX root user.
405 :param nobody: Name of the UNIX nobody user.
406 :param nogroup: Name of the unix nobody group.
407 :param users: Name of the unix users group.
408 :param wheel: Name of the wheel group (users that can become root).
409 :param backup: Name of the backup group."""
410 # add some foreign sids if they are not present already
411 ldb.add_foreign(domaindn, "S-1-5-7", "Anonymous")
412 ldb.add_foreign(domaindn, "S-1-1-0", "World")
413 ldb.add_foreign(domaindn, "S-1-5-2", "Network")
414 ldb.add_foreign(domaindn, "S-1-5-18", "System")
415 ldb.add_foreign(domaindn, "S-1-5-11", "Authenticated Users")
417 # some well known sids
418 ldb.setup_name_mapping(domaindn, "S-1-5-7", nobody)
419 ldb.setup_name_mapping(domaindn, "S-1-1-0", nogroup)
420 ldb.setup_name_mapping(domaindn, "S-1-5-2", nogroup)
421 ldb.setup_name_mapping(domaindn, "S-1-5-18", root)
422 ldb.setup_name_mapping(domaindn, "S-1-5-11", users)
423 ldb.setup_name_mapping(domaindn, "S-1-5-32-544", wheel)
424 ldb.setup_name_mapping(domaindn, "S-1-5-32-545", users)
425 ldb.setup_name_mapping(domaindn, "S-1-5-32-546", nogroup)
426 ldb.setup_name_mapping(domaindn, "S-1-5-32-551", backup)
428 # and some well known domain rids
429 ldb.setup_name_mapping(domaindn, sid + "-500", root)
430 ldb.setup_name_mapping(domaindn, sid + "-518", wheel)
431 ldb.setup_name_mapping(domaindn, sid + "-519", wheel)
432 ldb.setup_name_mapping(domaindn, sid + "-512", wheel)
433 ldb.setup_name_mapping(domaindn, sid + "-513", users)
434 ldb.setup_name_mapping(domaindn, sid + "-520", wheel)
437 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
439 serverrole, ldap_backend=None,
440 ldap_backend_type=None, erase=False):
441 """Setup the partitions for the SAM database.
443 Alternatively, provision() may call this, and then populate the database.
445 :note: This will wipe the Sam Database!
447 :note: This function always removes the local SAM LDB file. The erase
448 parameter controls whether to erase the existing data, which
449 may not be stored locally but in LDAP.
451 assert session_info is not None
453 samdb = SamDB(samdb_path, session_info=session_info,
454 credentials=credentials, lp=lp)
460 os.unlink(samdb_path)
462 samdb = SamDB(samdb_path, session_info=session_info,
463 credentials=credentials, lp=lp)
465 #Add modules to the list to activate them by default
466 #beware often order is important
468 # Some Known ordering constraints:
469 # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
470 # - objectclass must be before password_hash, because password_hash checks
471 # that the objectclass is of type person (filled in by objectclass
472 # module when expanding the objectclass list)
473 # - partition must be last
474 # - each partition has its own module list then
475 modules_list = ["rootdse",
491 modules_list2 = ["show_deleted",
494 domaindn_ldb = "users.ldb"
495 if ldap_backend is not None:
496 domaindn_ldb = ldap_backend
497 configdn_ldb = "configuration.ldb"
498 if ldap_backend is not None:
499 configdn_ldb = ldap_backend
500 schemadn_ldb = "schema.ldb"
501 if ldap_backend is not None:
502 schema_ldb = ldap_backend
503 schemadn_ldb = ldap_backend
505 if ldap_backend_type == "fedora-ds":
506 backend_modules = ["nsuniqueid", "paged_searches"]
507 # We can handle linked attributes here, as we don't have directory-side subtree operations
508 tdb_modules_list = ["linked_attributes"]
509 elif ldap_backend_type == "openldap":
510 backend_modules = ["normalise", "entryuuid", "paged_searches"]
511 # OpenLDAP handles subtree renames, so we don't want to do any of these things
512 tdb_modules_list = None
513 elif serverrole == "domain controller":
514 backend_modules = ["repl_meta_data"]
516 backend_modules = ["objectguid"]
518 if tdb_modules_list is None:
519 tdb_modules_list_as_string = ""
521 tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
523 samdb.transaction_start()
525 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
526 "SCHEMADN": names.schemadn,
527 "SCHEMADN_LDB": schemadn_ldb,
528 "SCHEMADN_MOD2": ",objectguid",
529 "CONFIGDN": names.configdn,
530 "CONFIGDN_LDB": configdn_ldb,
531 "DOMAINDN": names.domaindn,
532 "DOMAINDN_LDB": domaindn_ldb,
533 "SCHEMADN_MOD": "schema_fsmo,instancetype",
534 "CONFIGDN_MOD": "naming_fsmo,instancetype",
535 "DOMAINDN_MOD": "pdc_fsmo,password_hash,instancetype",
536 "MODULES_LIST": ",".join(modules_list),
537 "TDB_MODULES_LIST": tdb_modules_list_as_string,
538 "MODULES_LIST2": ",".join(modules_list2),
539 "BACKEND_MOD": ",".join(backend_modules),
543 samdb.transaction_cancel()
546 samdb.transaction_commit()
548 samdb = SamDB(samdb_path, session_info=session_info,
549 credentials=credentials, lp=lp)
551 samdb.transaction_start()
553 message("Setting up sam.ldb attributes")
554 samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
556 message("Setting up sam.ldb rootDSE")
557 setup_samdb_rootdse(samdb, setup_path, names.schemadn, names.domaindn, names.hostname,
558 names.dnsdomain, names.realm, names.rootdn, names.configdn, names.netbiosname,
562 message("Erasing data from partitions")
563 samdb.erase_partitions()
566 samdb.transaction_cancel()
569 samdb.transaction_commit()
574 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain,
575 netbiosname, domainsid, keytab_path, samdb_url,
576 dns_keytab_path, dnspass, machinepass):
577 """Add DC-specific bits to a secrets database.
579 :param secretsdb: Ldb Handle to the secrets database
580 :param setup_path: Setup path function
581 :param machinepass: Machine password
583 setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), {
584 "MACHINEPASS_B64": b64encode(machinepass),
587 "DNSDOMAIN": dnsdomain,
588 "DOMAINSID": str(domainsid),
589 "SECRETS_KEYTAB": keytab_path,
590 "NETBIOSNAME": netbiosname,
591 "SAM_LDB": samdb_url,
592 "DNS_KEYTAB": dns_keytab_path,
593 "DNSPASS_B64": b64encode(dnspass),
597 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
598 """Setup the secrets database.
600 :param path: Path to the secrets database.
601 :param setup_path: Get the path to a setup file.
602 :param session_info: Session info.
603 :param credentials: Credentials
604 :param lp: Loadparm context
605 :return: LDB handle for the created secrets database
607 if os.path.exists(path):
609 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
612 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
613 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
615 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
619 def setup_templatesdb(path, setup_path, session_info, credentials, lp):
620 """Setup the templates database.
622 :param path: Path to the database.
623 :param setup_path: Function for obtaining the path to setup files.
624 :param session_info: Session info
625 :param credentials: Credentials
626 :param lp: Loadparm context
628 templates_ldb = SamDB(path, session_info=session_info,
629 credentials=credentials, lp=lp)
630 templates_ldb.erase()
631 templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif"))
634 def setup_registry(path, setup_path, session_info, credentials, lp):
635 """Setup the registry.
637 :param path: Path to the registry database
638 :param setup_path: Function that returns the path to a setup.
639 :param session_info: Session information
640 :param credentials: Credentials
641 :param lp: Loadparm context
643 reg = registry.Registry()
644 hive = registry.open_ldb(path, session_info=session_info,
645 credentials=credentials, lp_ctx=lp)
646 reg.mount_hive(hive, "HKEY_LOCAL_MACHINE")
647 provision_reg = setup_path("provision.reg")
648 assert os.path.exists(provision_reg)
649 reg.diff_apply(provision_reg)
651 def setup_idmapdb(path, setup_path, session_info, credentials, lp):
652 """Setup the idmap database.
654 :param path: path to the idmap database
655 :param setup_path: Function that returns a path to a setup file
656 :param session_info: Session information
657 :param credentials: Credentials
658 :param lp: Loadparm context
660 if os.path.exists(path):
663 idmap_ldb = Ldb(path, session_info=session_info, credentials=credentials,
667 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
670 def setup_samdb_rootdse(samdb, setup_path, schemadn, domaindn, hostname,
671 dnsdomain, realm, rootdn, configdn, netbiosname,
673 """Setup the SamDB rootdse.
675 :param samdb: Sam Database handle
676 :param setup_path: Obtain setup path
678 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
679 "SCHEMADN": schemadn,
680 "NETBIOSNAME": netbiosname,
681 "DNSDOMAIN": dnsdomain,
682 "DEFAULTSITE": sitename,
684 "DNSNAME": "%s.%s" % (hostname, dnsdomain),
685 "DOMAINDN": domaindn,
687 "CONFIGDN": configdn,
688 "VERSION": samba.version(),
692 def setup_self_join(samdb, names,
693 machinepass, dnspass,
694 domainsid, invocationid, setup_path,
695 policyguid, hostguid=None):
696 """Join a host to its own domain."""
697 if hostguid is not None:
698 hostguid_add = "objectGUID: %s" % hostguid
702 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
703 "CONFIGDN": names.configdn,
704 "SCHEMADN": names.schemadn,
705 "DOMAINDN": names.domaindn,
706 "INVOCATIONID": invocationid,
707 "NETBIOSNAME": names.netbiosname,
708 "DEFAULTSITE": names.sitename,
709 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
710 "MACHINEPASS_B64": b64encode(machinepass),
711 "DNSPASS_B64": b64encode(dnspass),
712 "REALM": names.realm,
713 "DOMAIN": names.domain,
714 "HOSTGUID_ADD": hostguid_add,
715 "DNSDOMAIN": names.dnsdomain})
716 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
717 "POLICYGUID": policyguid,
718 "DNSDOMAIN": names.dnsdomain,
719 "DOMAINSID": str(domainsid),
720 "DOMAINDN": names.domaindn})
723 def setup_samdb(path, setup_path, session_info, credentials, lp,
725 domainsid, aci, domainguid, policyguid,
726 fill, adminpass, krbtgtpass,
727 machinepass, hostguid, invocationid, dnspass,
728 serverrole, ldap_backend=None,
729 ldap_backend_type=None):
730 """Setup a complete SAM Database.
732 :note: This will wipe the main SAM database file!
735 erase = (fill != FILL_DRS)
737 # Also wipes the database
738 setup_samdb_partitions(path, setup_path, message=message, lp=lp,
739 credentials=credentials, session_info=session_info,
741 ldap_backend=ldap_backend, serverrole=serverrole,
742 ldap_backend_type=ldap_backend_type, erase=erase)
744 samdb = SamDB(path, session_info=session_info,
745 credentials=credentials, lp=lp)
748 # We want to finish here, but setup the index before we do so
749 message("Setting up sam.ldb index")
750 samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
753 message("Pre-loading the Samba 4 and AD schema")
754 samdb = SamDB(path, session_info=session_info,
755 credentials=credentials, lp=lp)
756 samdb.set_domain_sid(domainsid)
757 if serverrole == "domain controller":
758 samdb.set_invocation_id(invocationid)
760 load_schema(setup_path, samdb, names.schemadn, names.netbiosname, names.configdn, names.sitename)
762 samdb.transaction_start()
765 message("Adding DomainDN: %s (permitted to fail)" % names.domaindn)
766 if serverrole == "domain controller":
767 domain_oc = "domainDNS"
769 domain_oc = "samba4LocalDomain"
771 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
772 "DOMAINDN": names.domaindn,
774 "DOMAIN_OC": domain_oc
777 message("Modifying DomainDN: " + names.domaindn + "")
778 if domainguid is not None:
779 domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
783 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
784 "LDAPTIME": timestring(int(time.time())),
785 "DOMAINSID": str(domainsid),
786 "SCHEMADN": names.schemadn,
787 "NETBIOSNAME": names.netbiosname,
788 "DEFAULTSITE": names.sitename,
789 "CONFIGDN": names.configdn,
790 "POLICYGUID": policyguid,
791 "DOMAINDN": names.domaindn,
792 "DOMAINGUID_MOD": domainguid_mod,
795 message("Adding configuration container (permitted to fail)")
796 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
797 "CONFIGDN": names.configdn,
799 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb",
801 message("Modifying configuration container")
802 setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
803 "CONFIGDN": names.configdn,
804 "SCHEMADN": names.schemadn,
807 message("Adding schema container (permitted to fail)")
808 setup_add_ldif(samdb, setup_path("provision_schema_basedn.ldif"), {
809 "SCHEMADN": names.schemadn,
811 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
813 message("Modifying schema container")
814 setup_modify_ldif(samdb,
815 setup_path("provision_schema_basedn_modify.ldif"), {
816 "SCHEMADN": names.schemadn,
817 "NETBIOSNAME": names.netbiosname,
818 "DEFAULTSITE": names.sitename,
819 "CONFIGDN": names.configdn,
822 message("Setting up sam.ldb Samba4 schema")
823 setup_add_ldif(samdb, setup_path("schema_samba4.ldif"),
824 {"SCHEMADN": names.schemadn })
825 message("Setting up sam.ldb AD schema")
826 setup_add_ldif(samdb, setup_path("schema.ldif"),
827 {"SCHEMADN": names.schemadn})
829 message("Setting up sam.ldb configuration data")
830 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
831 "CONFIGDN": names.configdn,
832 "NETBIOSNAME": names.netbiosname,
833 "DEFAULTSITE": names.sitename,
834 "DNSDOMAIN": names.dnsdomain,
835 "DOMAIN": names.domain,
836 "SCHEMADN": names.schemadn,
837 "DOMAINDN": names.domaindn,
840 message("Setting up display specifiers")
841 setup_add_ldif(samdb, setup_path("display_specifiers.ldif"),
842 {"CONFIGDN": names.configdn})
844 message("Adding users container (permitted to fail)")
845 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
846 "DOMAINDN": names.domaindn})
847 message("Modifying users container")
848 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
849 "DOMAINDN": names.domaindn})
850 message("Adding computers container (permitted to fail)")
851 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
852 "DOMAINDN": names.domaindn})
853 message("Modifying computers container")
854 setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
855 "DOMAINDN": names.domaindn})
856 message("Setting up sam.ldb data")
857 setup_add_ldif(samdb, setup_path("provision.ldif"), {
858 "DOMAINDN": names.domaindn,
859 "NETBIOSNAME": names.netbiosname,
860 "DEFAULTSITE": names.sitename,
861 "CONFIGDN": names.configdn,
864 if fill == FILL_FULL:
865 message("Setting up sam.ldb users and groups")
866 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
867 "DOMAINDN": names.domaindn,
868 "DOMAINSID": str(domainsid),
869 "CONFIGDN": names.configdn,
870 "ADMINPASS_B64": b64encode(adminpass),
871 "KRBTGTPASS_B64": b64encode(krbtgtpass),
874 if serverrole == "domain controller":
875 message("Setting up self join")
876 setup_self_join(samdb, names=names, invocationid=invocationid,
878 machinepass=machinepass,
879 domainsid=domainsid, policyguid=policyguid,
881 setup_path=setup_path)
883 #We want to setup the index last, as adds are faster unindexed
884 message("Setting up sam.ldb index")
885 samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
887 samdb.transaction_cancel()
890 samdb.transaction_commit()
895 FILL_NT4SYNC = "NT4SYNC"
898 def provision(setup_dir, message, session_info,
899 credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL, realm=None,
900 rootdn=None, domaindn=None, schemadn=None, configdn=None,
901 domain=None, hostname=None, hostip=None, domainsid=None,
902 hostguid=None, adminpass=None, krbtgtpass=None, domainguid=None,
903 policyguid=None, invocationid=None, machinepass=None,
904 dnspass=None, root=None, nobody=None, nogroup=None, users=None,
905 wheel=None, backup=None, aci=None, serverrole=None,
906 ldap_backend=None, ldap_backend_type=None, sitename=None):
909 :note: caution, this wipes all existing data!
912 def setup_path(file):
913 return os.path.join(setup_dir, file)
915 if domainsid is None:
916 domainsid = security.random_sid()
918 domainsid = security.Sid(domainsid)
920 if policyguid is None:
921 policyguid = uuid.random()
922 if adminpass is None:
923 adminpass = misc.random_password(12)
924 if krbtgtpass is None:
925 krbtgtpass = misc.random_password(12)
926 if machinepass is None:
927 machinepass = misc.random_password(12)
929 dnspass = misc.random_password(12)
931 root = findnss(pwd.getpwnam, ["root"])[0]
933 nobody = findnss(pwd.getpwnam, ["nobody"])[0]
935 nogroup = findnss(grp.getgrnam, ["nogroup", "nobody"])[0]
937 users = findnss(grp.getgrnam, ["users", "guest", "other", "unknown",
940 wheel = findnss(grp.getgrnam, ["wheel", "root", "staff", "adm"])[0]
942 backup = findnss(grp.getgrnam, ["backup", "wheel", "root", "staff"])[0]
944 aci = "# no aci for local ldb"
946 lp = load_or_make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, targetdir)
948 names = guess_names(lp=lp, hostname=hostname, domain=domain,
949 dnsdomain=realm, serverrole=serverrole, sitename=sitename,
950 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn)
952 paths = provision_paths_from_lp(lp, names.dnsdomain)
955 hostip = gethostbyname(names.hostname)
957 if serverrole is None:
958 serverrole = lp.get("server role")
960 assert serverrole in ("domain controller", "member server", "standalone")
961 if invocationid is None and serverrole == "domain controller":
962 invocationid = uuid.random()
964 if not os.path.exists(paths.private_dir):
965 os.mkdir(paths.private_dir)
967 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
969 if ldap_backend is not None:
970 if ldap_backend == "ldapi":
971 # provision-backend will set this path suggested slapd command line / fedorads.inf
972 ldap_backend = "ldapi://" % urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
974 message("set DOMAIN SID: %s" % str(domainsid))
975 message("Provisioning for %s in realm %s" % (names.domain, realm))
976 message("Using administrator password: %s" % adminpass)
978 # only install a new shares config db if there is none
979 if not os.path.exists(paths.shareconf):
980 message("Setting up share.ldb")
981 share_ldb = Ldb(paths.shareconf, session_info=session_info,
982 credentials=credentials, lp=lp)
983 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
986 message("Setting up secrets.ldb")
987 secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
988 session_info=session_info,
989 credentials=credentials, lp=lp)
991 message("Setting up the registry")
992 setup_registry(paths.hklm, setup_path, session_info,
993 credentials=credentials, lp=lp)
995 message("Setting up templates db")
996 setup_templatesdb(paths.templates, setup_path, session_info=session_info,
997 credentials=credentials, lp=lp)
999 message("Setting up idmap db")
1000 setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1001 credentials=credentials, lp=lp)
1003 samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info,
1004 credentials=credentials, lp=lp, names=names,
1006 domainsid=domainsid,
1007 aci=aci, domainguid=domainguid, policyguid=policyguid,
1009 adminpass=adminpass, krbtgtpass=krbtgtpass,
1010 hostguid=hostguid, invocationid=invocationid,
1011 machinepass=machinepass, dnspass=dnspass,
1012 serverrole=serverrole, ldap_backend=ldap_backend,
1013 ldap_backend_type=ldap_backend_type)
1015 if lp.get("server role") == "domain controller":
1016 policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1017 "{" + policyguid + "}")
1018 os.makedirs(policy_path, 0755)
1019 os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1020 os.makedirs(os.path.join(policy_path, "User"), 0755)
1021 if not os.path.isdir(paths.netlogon):
1022 os.makedirs(paths.netlogon, 0755)
1023 secrets_ldb = Ldb(paths.secrets, session_info=session_info,
1024 credentials=credentials, lp=lp)
1025 secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1026 netbiosname=names.netbiosname, domainsid=domainsid,
1027 keytab_path=paths.keytab, samdb_url=paths.samdb,
1028 dns_keytab_path=paths.dns_keytab, dnspass=dnspass,
1029 machinepass=machinepass, dnsdomain=names.dnsdomain)
1031 if samdb_fill == FILL_FULL:
1032 setup_name_mappings(samdb, str(domainsid), names.domaindn, root=root,
1033 nobody=nobody, nogroup=nogroup, wheel=wheel,
1034 users=users, backup=backup)
1036 message("Setting up sam.ldb rootDSE marking as synchronized")
1037 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1039 # Only make a zone file on the first DC, it should be replicated with DNS replication
1040 if serverrole == "domain controller":
1041 samdb = SamDB(paths.samdb, session_info=session_info,
1042 credentials=credentials, lp=lp)
1044 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1045 assert isinstance(domainguid, str)
1046 hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1047 expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1048 scope=SCOPE_SUBTREE)
1049 assert isinstance(hostguid, str)
1051 message("Setting up DNS zone: %s" % names.dnsdomain)
1052 create_zone_file(paths.dns, setup_path, samdb,
1053 hostname=names.hostname, hostip=hostip, dnsdomain=names.dnsdomain,
1054 domaindn=names.domaindn, dnspass=dnspass, realm=names.realm,
1055 domainguid=domainguid, hostguid=hostguid)
1056 message("Please install the zone located in %s into your DNS server" % paths.dns)
1058 message("Setting up phpLDAPadmin configuration")
1059 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1062 message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1064 result = ProvisionResult()
1065 result.domaindn = domaindn
1066 result.paths = paths
1068 result.samdb = samdb
1071 def provision_become_dc(setup_dir=None,
1072 smbconf=None, targetdir=None, realm=None,
1073 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1074 domain=None, hostname=None, domainsid=None,
1075 hostguid=None, adminpass=None, krbtgtpass=None, domainguid=None,
1076 policyguid=None, invocationid=None, machinepass=None,
1077 dnspass=None, root=None, nobody=None, nogroup=None, users=None,
1078 wheel=None, backup=None, aci=None, serverrole=None,
1079 ldap_backend=None, ldap_backend_type=None, sitename=DEFAULTSITE):
1082 """print a message if quiet is not set."""
1085 provision(setup_dir, message, system_session(), None,
1086 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm,
1087 rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn,
1088 domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename);
1091 def setup_db_config(setup_path, file, dbdir):
1092 if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1093 os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700);
1094 if not os.path.isdir(os.path.join(dbdir, "tmp")):
1095 os.makedirs(os.path.join(dbdir, "tmp"), 0700);
1097 setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1098 {"LDAPDBDIR": dbdir})
1102 def provision_backend(setup_dir=None, message=None,
1103 smbconf=None, targetdir=None, realm=None,
1104 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1105 domain=None, hostname=None, adminpass=None, root=None, serverrole=None,
1106 ldap_backend_type=None):
1108 def setup_path(file):
1109 return os.path.join(setup_dir, file)
1111 if hostname is None:
1112 hostname = gethostname().split(".")[0].lower()
1115 root = findnss(pwd.getpwnam, ["root"])[0]
1117 lp = load_or_make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, targetdir)
1119 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1120 dnsdomain=realm, serverrole=serverrole,
1121 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn)
1123 paths = provision_paths_from_lp(lp, names.dnsdomain)
1125 if not os.path.isdir(paths.ldapdir):
1126 os.makedirs(paths.ldapdir)
1127 schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1129 os.unlink(schemadb_path)
1133 schemadb = Ldb(schemadb_path, lp=lp)
1135 setup_add_ldif(schemadb, setup_path("provision_schema_basedn.ldif"),
1136 {"SCHEMADN": names.schemadn,
1138 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
1140 setup_modify_ldif(schemadb,
1141 setup_path("provision_schema_basedn_modify.ldif"), \
1142 {"SCHEMADN": names.schemadn,
1143 "NETBIOSNAME": names.netbiosname,
1144 "DEFAULTSITE": DEFAULTSITE,
1145 "CONFIGDN": names.configdn,
1148 setup_add_ldif(schemadb, setup_path("schema_samba4.ldif"),
1149 {"SCHEMADN": names.schemadn })
1150 setup_add_ldif(schemadb, setup_path("schema.ldif"),
1151 {"SCHEMADN": names.schemadn})
1153 if ldap_backend_type == "fedora-ds":
1154 setup_file(setup_path("fedora-ds.inf"), paths.fedoradsinf,
1156 "HOSTNAME": hostname,
1157 "DNSDOMAIN": names.dnsdomain,
1158 "LDAPDIR": paths.ldapdir,
1159 "DOMAINDN": names.domaindn,
1160 "LDAPMANAGERDN": names.ldapmanagerdn,
1161 "LDAPMANAGERPASS": adminpass,
1164 setup_file(setup_path("fedora-partitions.ldif"), paths.fedoradspartitions,
1165 {"CONFIGDN": names.configdn,
1166 "SCHEMADN": names.schemadn,
1169 setup_file(setup_path("fedora-partitions.ldif"), paths.fedoradspartitions,
1170 {"CONFIGDN": names.configdn,
1171 "SCHEMADN": names.schemadn,
1173 mapping = "schema-map-fedora-ds-1.0"
1174 backend_schema = "99_ad.ldif"
1175 elif ldap_backend_type == "openldap":
1176 setup_file(setup_path("slapd.conf"), paths.slapdconf,
1177 {"DNSDOMAIN": names.dnsdomain,
1178 "LDAPDIR": paths.ldapdir,
1179 "DOMAINDN": names.domaindn,
1180 "CONFIGDN": names.configdn,
1181 "SCHEMADN": names.schemadn,
1182 "LDAPMANAGERDN": names.ldapmanagerdn,
1183 "LDAPMANAGERPASS": adminpass})
1184 setup_file(setup_path("modules.conf"), paths.modulesconf,
1185 {"REALM": names.realm})
1187 setup_db_config(setup_path, file, os.path.join(paths.ldapdir, "db", "user"))
1188 setup_db_config(setup_path, file, os.path.join(paths.ldapdir, "db", "config"))
1189 setup_db_config(setup_path, file, os.path.join(paths.ldapdir, "db", "schema"))
1190 mapping = "schema-map-openldap-2.3"
1191 backend_schema = "backend-schema.schema"
1193 attrs = ["linkID", "lDAPDisplayName"]
1194 res = schemadb.search(expression="(&(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1)))(objectclass=attributeSchema))", base=names.schemadn, scope=SCOPE_SUBTREE, attrs=attrs);
1196 memberof_config = "# This is a generated file, do not edit!\n";
1197 refint_attributes = "";
1198 for i in range (0, len(res)):
1199 linkid = res[i]["linkID"][0]
1200 linkid = str(int(linkid) + 1)
1201 expression = "(&(objectclass=attributeSchema)(linkID=" + (linkid) + "))"
1202 target = schemadb.searchone(basedn=names.schemadn,
1203 expression=expression,
1204 attribute="lDAPDisplayName",
1205 scope=SCOPE_SUBTREE);
1206 if target is not None:
1207 refint_attributes = refint_attributes + " " + target + " " + res[i]["lDAPDisplayName"][0];
1208 memberof_config = memberof_config + """overlay memberof
1209 memberof-dangling error
1210 memberof-refint TRUE
1211 memberof-group-oc top
1212 memberof-member-ad """ + res[i]["lDAPDisplayName"][0] + """
1213 memberof-memberof-ad """ + target + """
1214 memberof-dangling-error 32
1218 memberof_config = memberof_config + """
1220 refint_attributes""" + refint_attributes + "\n";
1222 if os.path.exists(paths.memberofconf):
1223 os.unlink(paths.memberof.conf)
1225 open(paths.memberofconf, 'w').write(memberof_config)
1227 ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
1228 message("Start slapd with: slapd -f " + paths.ldapdir + "/slapd.conf -h " + ldapi_uri)
1231 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);
1233 os.system(schema_command)
1237 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1238 """Create a PHP LDAP admin configuration file.
1240 :param path: Path to write the configuration to.
1241 :param setup_path: Function to generate setup paths.
1243 setup_file(setup_path("phpldapadmin-config.php"), path,
1244 {"S4_LDAPI_URI": ldapi_uri})
1247 def create_zone_file(path, setup_path, samdb, dnsdomain, domaindn,
1248 hostip, hostname, dnspass, realm, domainguid, hostguid):
1249 """Write out a DNS zone file, from the info in the current database.
1251 :param path: Path of the new file.
1252 :param setup_path": Setup path function.
1253 :param samdb: SamDB object
1254 :param dnsdomain: DNS Domain name
1255 :param domaindn: DN of the Domain
1256 :param hostip: Local IP
1257 :param hostname: Local hostname
1258 :param dnspass: Password for DNS
1259 :param realm: Realm name
1260 :param domainguid: GUID of the domain.
1261 :param hostguid: GUID of the host.
1263 assert isinstance(domainguid, str)
1265 setup_file(setup_path("provision.zone"), path, {
1266 "DNSPASS_B64": b64encode(dnspass),
1267 "HOSTNAME": hostname,
1268 "DNSDOMAIN": dnsdomain,
1271 "DOMAINGUID": domainguid,
1272 "DATESTRING": time.strftime("%Y%m%d%H"),
1273 "DEFAULTSITE": DEFAULTSITE,
1274 "HOSTGUID": hostguid,
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)