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
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 = socket.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 = socket.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))
380 sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
381 netlogon = os.path.join(sysvol, realm.lower(), "scripts")
383 setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
385 "HOSTNAME": hostname,
388 "SERVERROLE": serverrole,
389 "NETLOGONPATH": netlogon,
390 "SYSVOLPATH": sysvol,
391 "PRIVATEDIR_LINE": privatedir_line,
392 "LOCKDIR_LINE": lockdir_line
395 lp = param.LoadParm()
400 def setup_name_mappings(ldb, sid, domaindn, root, nobody, nogroup, users,
402 """setup reasonable name mappings for sam names to unix names.
404 :param ldb: SamDB object.
405 :param sid: The domain sid.
406 :param domaindn: The domain DN.
407 :param root: Name of the UNIX root user.
408 :param nobody: Name of the UNIX nobody user.
409 :param nogroup: Name of the unix nobody group.
410 :param users: Name of the unix users group.
411 :param wheel: Name of the wheel group (users that can become root).
412 :param backup: Name of the backup group."""
413 # add some foreign sids if they are not present already
414 ldb.add_foreign(domaindn, "S-1-5-7", "Anonymous")
415 ldb.add_foreign(domaindn, "S-1-1-0", "World")
416 ldb.add_foreign(domaindn, "S-1-5-2", "Network")
417 ldb.add_foreign(domaindn, "S-1-5-18", "System")
418 ldb.add_foreign(domaindn, "S-1-5-11", "Authenticated Users")
420 # some well known sids
421 ldb.setup_name_mapping(domaindn, "S-1-5-7", nobody)
422 ldb.setup_name_mapping(domaindn, "S-1-1-0", nogroup)
423 ldb.setup_name_mapping(domaindn, "S-1-5-2", nogroup)
424 ldb.setup_name_mapping(domaindn, "S-1-5-18", root)
425 ldb.setup_name_mapping(domaindn, "S-1-5-11", users)
426 ldb.setup_name_mapping(domaindn, "S-1-5-32-544", wheel)
427 ldb.setup_name_mapping(domaindn, "S-1-5-32-545", users)
428 ldb.setup_name_mapping(domaindn, "S-1-5-32-546", nogroup)
429 ldb.setup_name_mapping(domaindn, "S-1-5-32-551", backup)
431 # and some well known domain rids
432 ldb.setup_name_mapping(domaindn, sid + "-500", root)
433 ldb.setup_name_mapping(domaindn, sid + "-518", wheel)
434 ldb.setup_name_mapping(domaindn, sid + "-519", wheel)
435 ldb.setup_name_mapping(domaindn, sid + "-512", wheel)
436 ldb.setup_name_mapping(domaindn, sid + "-513", users)
437 ldb.setup_name_mapping(domaindn, sid + "-520", wheel)
440 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
442 serverrole, ldap_backend=None,
443 ldap_backend_type=None, erase=False):
444 """Setup the partitions for the SAM database.
446 Alternatively, provision() may call this, and then populate the database.
448 :note: This will wipe the Sam Database!
450 :note: This function always removes the local SAM LDB file. The erase
451 parameter controls whether to erase the existing data, which
452 may not be stored locally but in LDAP.
454 assert session_info is not None
456 samdb = SamDB(samdb_path, session_info=session_info,
457 credentials=credentials, lp=lp)
463 os.unlink(samdb_path)
465 samdb = SamDB(samdb_path, session_info=session_info,
466 credentials=credentials, lp=lp)
468 #Add modules to the list to activate them by default
469 #beware often order is important
471 # Some Known ordering constraints:
472 # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
473 # - objectclass must be before password_hash, because password_hash checks
474 # that the objectclass is of type person (filled in by objectclass
475 # module when expanding the objectclass list)
476 # - partition must be last
477 # - each partition has its own module list then
478 modules_list = ["rootdse",
494 modules_list2 = ["show_deleted",
497 domaindn_ldb = "users.ldb"
498 if ldap_backend is not None:
499 domaindn_ldb = ldap_backend
500 configdn_ldb = "configuration.ldb"
501 if ldap_backend is not None:
502 configdn_ldb = ldap_backend
503 schemadn_ldb = "schema.ldb"
504 if ldap_backend is not None:
505 schema_ldb = ldap_backend
506 schemadn_ldb = ldap_backend
508 if ldap_backend_type == "fedora-ds":
509 backend_modules = ["nsuniqueid", "paged_searches"]
510 # We can handle linked attributes here, as we don't have directory-side subtree operations
511 tdb_modules_list = ["linked_attributes"]
512 elif ldap_backend_type == "openldap":
513 backend_modules = ["normalise", "entryuuid", "paged_searches"]
514 # OpenLDAP handles subtree renames, so we don't want to do any of these things
515 tdb_modules_list = None
516 elif serverrole == "domain controller":
517 backend_modules = ["repl_meta_data"]
519 backend_modules = ["objectguid"]
521 if tdb_modules_list is None:
522 tdb_modules_list_as_string = ""
524 tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
526 samdb.transaction_start()
528 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
529 "SCHEMADN": names.schemadn,
530 "SCHEMADN_LDB": schemadn_ldb,
531 "SCHEMADN_MOD2": ",objectguid",
532 "CONFIGDN": names.configdn,
533 "CONFIGDN_LDB": configdn_ldb,
534 "DOMAINDN": names.domaindn,
535 "DOMAINDN_LDB": domaindn_ldb,
536 "SCHEMADN_MOD": "schema_fsmo,instancetype",
537 "CONFIGDN_MOD": "naming_fsmo,instancetype",
538 "DOMAINDN_MOD": "pdc_fsmo,password_hash,instancetype",
539 "MODULES_LIST": ",".join(modules_list),
540 "TDB_MODULES_LIST": tdb_modules_list_as_string,
541 "MODULES_LIST2": ",".join(modules_list2),
542 "BACKEND_MOD": ",".join(backend_modules),
546 samdb.transaction_cancel()
549 samdb.transaction_commit()
551 samdb = SamDB(samdb_path, session_info=session_info,
552 credentials=credentials, lp=lp)
554 samdb.transaction_start()
556 message("Setting up sam.ldb attributes")
557 samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
559 message("Setting up sam.ldb rootDSE")
560 setup_samdb_rootdse(samdb, setup_path, names.schemadn, names.domaindn, names.hostname,
561 names.dnsdomain, names.realm, names.rootdn, names.configdn, names.netbiosname,
565 message("Erasing data from partitions")
566 samdb.erase_partitions()
569 samdb.transaction_cancel()
572 samdb.transaction_commit()
577 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain,
578 netbiosname, domainsid, keytab_path, samdb_url,
579 dns_keytab_path, dnspass, machinepass):
580 """Add DC-specific bits to a secrets database.
582 :param secretsdb: Ldb Handle to the secrets database
583 :param setup_path: Setup path function
584 :param machinepass: Machine password
586 setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), {
587 "MACHINEPASS_B64": b64encode(machinepass),
590 "DNSDOMAIN": dnsdomain,
591 "DOMAINSID": str(domainsid),
592 "SECRETS_KEYTAB": keytab_path,
593 "NETBIOSNAME": netbiosname,
594 "SAM_LDB": samdb_url,
595 "DNS_KEYTAB": dns_keytab_path,
596 "DNSPASS_B64": b64encode(dnspass),
600 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
601 """Setup the secrets database.
603 :param path: Path to the secrets database.
604 :param setup_path: Get the path to a setup file.
605 :param session_info: Session info.
606 :param credentials: Credentials
607 :param lp: Loadparm context
608 :return: LDB handle for the created secrets database
610 if os.path.exists(path):
612 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
615 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
616 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
618 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
622 def setup_templatesdb(path, setup_path, session_info, credentials, lp):
623 """Setup the templates database.
625 :param path: Path to the database.
626 :param setup_path: Function for obtaining the path to setup files.
627 :param session_info: Session info
628 :param credentials: Credentials
629 :param lp: Loadparm context
631 templates_ldb = SamDB(path, session_info=session_info,
632 credentials=credentials, lp=lp)
633 templates_ldb.erase()
634 templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif"))
637 def setup_registry(path, setup_path, session_info, credentials, lp):
638 """Setup the registry.
640 :param path: Path to the registry database
641 :param setup_path: Function that returns the path to a setup.
642 :param session_info: Session information
643 :param credentials: Credentials
644 :param lp: Loadparm context
646 reg = registry.Registry()
647 hive = registry.open_ldb(path, session_info=session_info,
648 credentials=credentials, lp_ctx=lp)
649 reg.mount_hive(hive, "HKEY_LOCAL_MACHINE")
650 provision_reg = setup_path("provision.reg")
651 assert os.path.exists(provision_reg)
652 reg.diff_apply(provision_reg)
654 def setup_idmapdb(path, setup_path, session_info, credentials, lp):
655 """Setup the idmap database.
657 :param path: path to the idmap database
658 :param setup_path: Function that returns a path to a setup file
659 :param session_info: Session information
660 :param credentials: Credentials
661 :param lp: Loadparm context
663 if os.path.exists(path):
666 idmap_ldb = Ldb(path, session_info=session_info, credentials=credentials,
670 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
673 def setup_samdb_rootdse(samdb, setup_path, schemadn, domaindn, hostname,
674 dnsdomain, realm, rootdn, configdn, netbiosname,
676 """Setup the SamDB rootdse.
678 :param samdb: Sam Database handle
679 :param setup_path: Obtain setup path
681 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
682 "SCHEMADN": schemadn,
683 "NETBIOSNAME": netbiosname,
684 "DNSDOMAIN": dnsdomain,
685 "DEFAULTSITE": sitename,
687 "DNSNAME": "%s.%s" % (hostname, dnsdomain),
688 "DOMAINDN": domaindn,
690 "CONFIGDN": configdn,
691 "VERSION": samba.version(),
695 def setup_self_join(samdb, names,
696 machinepass, dnspass,
697 domainsid, invocationid, setup_path,
698 policyguid, hostguid=None):
699 """Join a host to its own domain."""
700 if hostguid is not None:
701 hostguid_add = "objectGUID: %s" % hostguid
705 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
706 "CONFIGDN": names.configdn,
707 "SCHEMADN": names.schemadn,
708 "DOMAINDN": names.domaindn,
709 "INVOCATIONID": invocationid,
710 "NETBIOSNAME": names.netbiosname,
711 "DEFAULTSITE": names.sitename,
712 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
713 "MACHINEPASS_B64": b64encode(machinepass),
714 "DNSPASS_B64": b64encode(dnspass),
715 "REALM": names.realm,
716 "DOMAIN": names.domain,
717 "HOSTGUID_ADD": hostguid_add,
718 "DNSDOMAIN": names.dnsdomain})
719 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
720 "POLICYGUID": policyguid,
721 "DNSDOMAIN": names.dnsdomain,
722 "DOMAINSID": str(domainsid),
723 "DOMAINDN": names.domaindn})
726 def setup_samdb(path, setup_path, session_info, credentials, lp,
728 domainsid, aci, domainguid, policyguid,
729 fill, adminpass, krbtgtpass,
730 machinepass, hostguid, invocationid, dnspass,
731 serverrole, ldap_backend=None,
732 ldap_backend_type=None):
733 """Setup a complete SAM Database.
735 :note: This will wipe the main SAM database file!
738 erase = (fill != FILL_DRS)
740 # Also wipes the database
741 setup_samdb_partitions(path, setup_path, message=message, lp=lp,
742 credentials=credentials, session_info=session_info,
744 ldap_backend=ldap_backend, serverrole=serverrole,
745 ldap_backend_type=ldap_backend_type, erase=erase)
747 samdb = SamDB(path, session_info=session_info,
748 credentials=credentials, lp=lp)
751 # We want to finish here, but setup the index before we do so
752 message("Setting up sam.ldb index")
753 samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
756 message("Pre-loading the Samba 4 and AD schema")
757 samdb = SamDB(path, session_info=session_info,
758 credentials=credentials, lp=lp)
759 samdb.set_domain_sid(domainsid)
760 if serverrole == "domain controller":
761 samdb.set_invocation_id(invocationid)
763 load_schema(setup_path, samdb, names.schemadn, names.netbiosname, names.configdn, names.sitename)
765 samdb.transaction_start()
768 message("Adding DomainDN: %s (permitted to fail)" % names.domaindn)
769 if serverrole == "domain controller":
770 domain_oc = "domainDNS"
772 domain_oc = "samba4LocalDomain"
774 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
775 "DOMAINDN": names.domaindn,
777 "DOMAIN_OC": domain_oc
780 message("Modifying DomainDN: " + names.domaindn + "")
781 if domainguid is not None:
782 domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
786 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
787 "LDAPTIME": timestring(int(time.time())),
788 "DOMAINSID": str(domainsid),
789 "SCHEMADN": names.schemadn,
790 "NETBIOSNAME": names.netbiosname,
791 "DEFAULTSITE": names.sitename,
792 "CONFIGDN": names.configdn,
793 "POLICYGUID": policyguid,
794 "DOMAINDN": names.domaindn,
795 "DOMAINGUID_MOD": domainguid_mod,
798 message("Adding configuration container (permitted to fail)")
799 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
800 "CONFIGDN": names.configdn,
802 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb",
804 message("Modifying configuration container")
805 setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
806 "CONFIGDN": names.configdn,
807 "SCHEMADN": names.schemadn,
810 message("Adding schema container (permitted to fail)")
811 setup_add_ldif(samdb, setup_path("provision_schema_basedn.ldif"), {
812 "SCHEMADN": names.schemadn,
814 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
816 message("Modifying schema container")
817 setup_modify_ldif(samdb,
818 setup_path("provision_schema_basedn_modify.ldif"), {
819 "SCHEMADN": names.schemadn,
820 "NETBIOSNAME": names.netbiosname,
821 "DEFAULTSITE": names.sitename,
822 "CONFIGDN": names.configdn,
825 message("Setting up sam.ldb Samba4 schema")
826 setup_add_ldif(samdb, setup_path("schema_samba4.ldif"),
827 {"SCHEMADN": names.schemadn })
828 message("Setting up sam.ldb AD schema")
829 setup_add_ldif(samdb, setup_path("schema.ldif"),
830 {"SCHEMADN": names.schemadn})
832 message("Setting up sam.ldb configuration data")
833 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
834 "CONFIGDN": names.configdn,
835 "NETBIOSNAME": names.netbiosname,
836 "DEFAULTSITE": names.sitename,
837 "DNSDOMAIN": names.dnsdomain,
838 "DOMAIN": names.domain,
839 "SCHEMADN": names.schemadn,
840 "DOMAINDN": names.domaindn,
843 message("Setting up display specifiers")
844 setup_add_ldif(samdb, setup_path("display_specifiers.ldif"),
845 {"CONFIGDN": names.configdn})
847 message("Adding users container (permitted to fail)")
848 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
849 "DOMAINDN": names.domaindn})
850 message("Modifying users container")
851 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
852 "DOMAINDN": names.domaindn})
853 message("Adding computers container (permitted to fail)")
854 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
855 "DOMAINDN": names.domaindn})
856 message("Modifying computers container")
857 setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
858 "DOMAINDN": names.domaindn})
859 message("Setting up sam.ldb data")
860 setup_add_ldif(samdb, setup_path("provision.ldif"), {
861 "DOMAINDN": names.domaindn,
862 "NETBIOSNAME": names.netbiosname,
863 "DEFAULTSITE": names.sitename,
864 "CONFIGDN": names.configdn,
867 if fill == FILL_FULL:
868 message("Setting up sam.ldb users and groups")
869 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
870 "DOMAINDN": names.domaindn,
871 "DOMAINSID": str(domainsid),
872 "CONFIGDN": names.configdn,
873 "ADMINPASS_B64": b64encode(adminpass),
874 "KRBTGTPASS_B64": b64encode(krbtgtpass),
877 if serverrole == "domain controller":
878 message("Setting up self join")
879 setup_self_join(samdb, names=names, invocationid=invocationid,
881 machinepass=machinepass,
882 domainsid=domainsid, policyguid=policyguid,
884 setup_path=setup_path)
886 #We want to setup the index last, as adds are faster unindexed
887 message("Setting up sam.ldb index")
888 samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
890 samdb.transaction_cancel()
893 samdb.transaction_commit()
898 FILL_NT4SYNC = "NT4SYNC"
901 def provision(setup_dir, message, session_info,
902 credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL, realm=None,
903 rootdn=None, domaindn=None, schemadn=None, configdn=None,
904 domain=None, hostname=None, hostip=None, hostip6=None, domainsid=None,
905 hostguid=None, adminpass=None, krbtgtpass=None, domainguid=None,
906 policyguid=None, invocationid=None, machinepass=None,
907 dnspass=None, root=None, nobody=None, nogroup=None, users=None,
908 wheel=None, backup=None, aci=None, serverrole=None,
909 ldap_backend=None, ldap_backend_type=None, sitename=None):
912 :note: caution, this wipes all existing data!
915 def setup_path(file):
916 return os.path.join(setup_dir, file)
918 if domainsid is None:
919 domainsid = security.random_sid()
921 domainsid = security.Sid(domainsid)
923 if policyguid is None:
924 policyguid = uuid.random()
925 if adminpass is None:
926 adminpass = misc.random_password(12)
927 if krbtgtpass is None:
928 krbtgtpass = misc.random_password(12)
929 if machinepass is None:
930 machinepass = misc.random_password(12)
932 dnspass = misc.random_password(12)
934 root = findnss(pwd.getpwnam, ["root"])[0]
936 nobody = findnss(pwd.getpwnam, ["nobody"])[0]
938 nogroup = findnss(grp.getgrnam, ["nogroup", "nobody"])[0]
940 users = findnss(grp.getgrnam, ["users", "guest", "other", "unknown",
943 wheel = findnss(grp.getgrnam, ["wheel", "root", "staff", "adm"])[0]
945 backup = findnss(grp.getgrnam, ["backup", "wheel", "root", "staff"])[0]
947 aci = "# no aci for local ldb"
949 lp = load_or_make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, targetdir)
951 names = guess_names(lp=lp, hostname=hostname, domain=domain,
952 dnsdomain=realm, serverrole=serverrole, sitename=sitename,
953 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn)
955 paths = provision_paths_from_lp(lp, names.dnsdomain)
958 hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
962 hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
963 except socket.gaierror: pass
965 if serverrole is None:
966 serverrole = lp.get("server role")
968 assert serverrole in ("domain controller", "member server", "standalone")
969 if invocationid is None and serverrole == "domain controller":
970 invocationid = uuid.random()
972 if not os.path.exists(paths.private_dir):
973 os.mkdir(paths.private_dir)
975 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
977 if ldap_backend is not None:
978 if ldap_backend == "ldapi":
979 # provision-backend will set this path suggested slapd command line / fedorads.inf
980 ldap_backend = "ldapi://" % urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
982 # only install a new shares config db if there is none
983 if not os.path.exists(paths.shareconf):
984 message("Setting up share.ldb")
985 share_ldb = Ldb(paths.shareconf, session_info=session_info,
986 credentials=credentials, lp=lp)
987 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
990 message("Setting up secrets.ldb")
991 secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
992 session_info=session_info,
993 credentials=credentials, lp=lp)
995 message("Setting up the registry")
996 setup_registry(paths.hklm, setup_path, session_info,
997 credentials=credentials, lp=lp)
999 message("Setting up templates db")
1000 setup_templatesdb(paths.templates, setup_path, session_info=session_info,
1001 credentials=credentials, lp=lp)
1003 message("Setting up idmap db")
1004 setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1005 credentials=credentials, lp=lp)
1007 samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info,
1008 credentials=credentials, lp=lp, names=names,
1010 domainsid=domainsid,
1011 aci=aci, domainguid=domainguid, policyguid=policyguid,
1013 adminpass=adminpass, krbtgtpass=krbtgtpass,
1014 hostguid=hostguid, invocationid=invocationid,
1015 machinepass=machinepass, dnspass=dnspass,
1016 serverrole=serverrole, ldap_backend=ldap_backend,
1017 ldap_backend_type=ldap_backend_type)
1019 if lp.get("server role") == "domain controller":
1020 policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1021 "{" + policyguid + "}")
1022 os.makedirs(policy_path, 0755)
1023 os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1024 os.makedirs(os.path.join(policy_path, "User"), 0755)
1025 if not os.path.isdir(paths.netlogon):
1026 os.makedirs(paths.netlogon, 0755)
1027 secrets_ldb = Ldb(paths.secrets, session_info=session_info,
1028 credentials=credentials, lp=lp)
1029 secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1030 netbiosname=names.netbiosname, domainsid=domainsid,
1031 keytab_path=paths.keytab, samdb_url=paths.samdb,
1032 dns_keytab_path=paths.dns_keytab, dnspass=dnspass,
1033 machinepass=machinepass, dnsdomain=names.dnsdomain)
1035 if samdb_fill == FILL_FULL:
1036 setup_name_mappings(samdb, str(domainsid), names.domaindn, root=root,
1037 nobody=nobody, nogroup=nogroup, wheel=wheel,
1038 users=users, backup=backup)
1040 message("Compleating sam.ldb setup by marking as synchronized")
1041 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1043 # Only make a zone file on the first DC, it should be replicated with DNS replication
1044 if serverrole == "domain controller":
1045 samdb = SamDB(paths.samdb, session_info=session_info,
1046 credentials=credentials, lp=lp)
1048 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1049 assert isinstance(domainguid, str)
1050 hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1051 expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1052 scope=SCOPE_SUBTREE)
1053 assert isinstance(hostguid, str)
1055 create_zone_file(paths.dns, setup_path, samdb,
1056 hostname=names.hostname, hostip=hostip,
1057 hostip6=hostip6, dnsdomain=names.dnsdomain,
1058 domaindn=names.domaindn, dnspass=dnspass, realm=names.realm,
1059 domainguid=domainguid, hostguid=hostguid)
1060 message("Please install the zone located in %s into your DNS server" % paths.dns)
1062 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1065 message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1067 message("Once the above files are installed, your server will be ready to use")
1068 message("Server Type: %s" % serverrole)
1069 message("Hostname: %s" % names.hostname)
1070 message("NetBIOS Domain: %s" % names.domain)
1071 message("DNS Domain: %s" % names.dnsdomain)
1072 message("DOMAIN SID: %s" % str(domainsid))
1073 message("Admin password: %s" % adminpass)
1075 result = ProvisionResult()
1076 result.domaindn = domaindn
1077 result.paths = paths
1079 result.samdb = samdb
1082 def provision_become_dc(setup_dir=None,
1083 smbconf=None, targetdir=None, realm=None,
1084 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1085 domain=None, hostname=None, domainsid=None,
1086 hostguid=None, adminpass=None, krbtgtpass=None, domainguid=None,
1087 policyguid=None, invocationid=None, machinepass=None,
1088 dnspass=None, root=None, nobody=None, nogroup=None, users=None,
1089 wheel=None, backup=None, aci=None, serverrole=None,
1090 ldap_backend=None, ldap_backend_type=None, sitename=DEFAULTSITE):
1093 """print a message if quiet is not set."""
1096 provision(setup_dir, message, system_session(), None,
1097 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm,
1098 rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn,
1099 domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename);
1102 def setup_db_config(setup_path, file, dbdir):
1103 if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1104 os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700);
1105 if not os.path.isdir(os.path.join(dbdir, "tmp")):
1106 os.makedirs(os.path.join(dbdir, "tmp"), 0700);
1108 setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1109 {"LDAPDBDIR": dbdir})
1113 def provision_backend(setup_dir=None, message=None,
1114 smbconf=None, targetdir=None, realm=None,
1115 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1116 domain=None, hostname=None, adminpass=None, root=None, serverrole=None,
1117 ldap_backend_type=None):
1119 def setup_path(file):
1120 return os.path.join(setup_dir, file)
1122 if hostname is None:
1123 hostname = socket.gethostname().split(".")[0].lower()
1126 root = findnss(pwd.getpwnam, ["root"])[0]
1128 lp = load_or_make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, targetdir)
1130 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1131 dnsdomain=realm, serverrole=serverrole,
1132 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn)
1134 paths = provision_paths_from_lp(lp, names.dnsdomain)
1136 if not os.path.isdir(paths.ldapdir):
1137 os.makedirs(paths.ldapdir)
1138 schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1140 os.unlink(schemadb_path)
1144 schemadb = Ldb(schemadb_path, lp=lp)
1146 setup_add_ldif(schemadb, setup_path("provision_schema_basedn.ldif"),
1147 {"SCHEMADN": names.schemadn,
1149 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
1151 setup_modify_ldif(schemadb,
1152 setup_path("provision_schema_basedn_modify.ldif"), \
1153 {"SCHEMADN": names.schemadn,
1154 "NETBIOSNAME": names.netbiosname,
1155 "DEFAULTSITE": DEFAULTSITE,
1156 "CONFIGDN": names.configdn,
1159 setup_add_ldif(schemadb, setup_path("schema_samba4.ldif"),
1160 {"SCHEMADN": names.schemadn })
1161 setup_add_ldif(schemadb, setup_path("schema.ldif"),
1162 {"SCHEMADN": names.schemadn})
1164 if ldap_backend_type == "fedora-ds":
1165 setup_file(setup_path("fedora-ds.inf"), paths.fedoradsinf,
1167 "HOSTNAME": hostname,
1168 "DNSDOMAIN": names.dnsdomain,
1169 "LDAPDIR": paths.ldapdir,
1170 "DOMAINDN": names.domaindn,
1171 "LDAPMANAGERDN": names.ldapmanagerdn,
1172 "LDAPMANAGERPASS": adminpass,
1175 setup_file(setup_path("fedora-partitions.ldif"), paths.fedoradspartitions,
1176 {"CONFIGDN": names.configdn,
1177 "SCHEMADN": names.schemadn,
1180 setup_file(setup_path("fedora-partitions.ldif"), paths.fedoradspartitions,
1181 {"CONFIGDN": names.configdn,
1182 "SCHEMADN": names.schemadn,
1184 mapping = "schema-map-fedora-ds-1.0"
1185 backend_schema = "99_ad.ldif"
1186 elif ldap_backend_type == "openldap":
1187 attrs = ["linkID", "lDAPDisplayName"]
1188 res = schemadb.search(expression="(&(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1)))(objectclass=attributeSchema))", base=names.schemadn, scope=SCOPE_SUBTREE, attrs=attrs);
1190 memberof_config = "# Generated from schema in " + schemadb_path + "\n";
1191 refint_attributes = "";
1192 for i in range (0, len(res)):
1193 linkid = res[i]["linkID"][0]
1194 linkid = str(int(linkid) + 1)
1195 expression = "(&(objectclass=attributeSchema)(linkID=" + (linkid) + "))"
1196 target = schemadb.searchone(basedn=names.schemadn,
1197 expression=expression,
1198 attribute="lDAPDisplayName",
1199 scope=SCOPE_SUBTREE);
1200 if target is not None:
1201 refint_attributes = refint_attributes + " " + target + " " + res[i]["lDAPDisplayName"][0];
1202 memberof_config = memberof_config + """overlay memberof
1203 memberof-dangling error
1204 memberof-refint TRUE
1205 memberof-group-oc top
1206 memberof-member-ad """ + res[i]["lDAPDisplayName"][0] + """
1207 memberof-memberof-ad """ + target + """
1208 memberof-dangling-error 32
1212 memberof_config = memberof_config + """
1214 refint_attributes""" + refint_attributes + "\n";
1216 setup_file(setup_path("slapd.conf"), paths.slapdconf,
1217 {"DNSDOMAIN": names.dnsdomain,
1218 "LDAPDIR": paths.ldapdir,
1219 "DOMAINDN": names.domaindn,
1220 "CONFIGDN": names.configdn,
1221 "SCHEMADN": names.schemadn,
1222 "LDAPMANAGERDN": names.ldapmanagerdn,
1223 "LDAPMANAGERPASS": adminpass,
1224 "MEMBEROF_CONFIG": memberof_config})
1225 setup_file(setup_path("modules.conf"), paths.modulesconf,
1226 {"REALM": names.realm})
1228 setup_db_config(setup_path, file, os.path.join(paths.ldapdir, "db", "user"))
1229 setup_db_config(setup_path, file, os.path.join(paths.ldapdir, "db", "config"))
1230 setup_db_config(setup_path, file, os.path.join(paths.ldapdir, "db", "schema"))
1231 mapping = "schema-map-openldap-2.3"
1232 backend_schema = "backend-schema.schema"
1235 ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
1236 message("Start slapd with: slapd -f " + paths.ldapdir + "/slapd.conf -h " + ldapi_uri)
1239 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);
1241 os.system(schema_command)
1245 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1246 """Create a PHP LDAP admin configuration file.
1248 :param path: Path to write the configuration to.
1249 :param setup_path: Function to generate setup paths.
1251 setup_file(setup_path("phpldapadmin-config.php"), path,
1252 {"S4_LDAPI_URI": ldapi_uri})
1255 def create_zone_file(path, setup_path, samdb, dnsdomain, domaindn,
1256 hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid):
1257 """Write out a DNS zone file, from the info in the current database.
1259 :param path: Path of the new file.
1260 :param setup_path": Setup path function.
1261 :param samdb: SamDB object
1262 :param dnsdomain: DNS Domain name
1263 :param domaindn: DN of the Domain
1264 :param hostip: Local IPv4 IP
1265 :param hostip6: Local IPv6 IP
1266 :param hostname: Local hostname
1267 :param dnspass: Password for DNS
1268 :param realm: Realm name
1269 :param domainguid: GUID of the domain.
1270 :param hostguid: GUID of the host.
1272 assert isinstance(domainguid, str)
1274 hostip6_base_line = ""
1275 hostip6_host_line = ""
1277 if hostip6 is not None:
1278 hostip6_base_line = " IN AAAA " + hostip6
1279 hostip6_host_line = hostname + " IN AAAA " + hostip6
1281 setup_file(setup_path("provision.zone"), path, {
1282 "DNSPASS_B64": b64encode(dnspass),
1283 "HOSTNAME": hostname,
1284 "DNSDOMAIN": dnsdomain,
1287 "DOMAINGUID": domainguid,
1288 "DATESTRING": time.strftime("%Y%m%d%H"),
1289 "DEFAULTSITE": DEFAULTSITE,
1290 "HOSTGUID": hostguid,
1291 "HOSTIP6_BASE_LINE": hostip6_base_line,
1292 "HOSTIP6_HOST_LINE": hostip6_host_line,
1295 def load_schema(setup_path, samdb, schemadn, netbiosname, configdn, sitename):
1296 """Load schema for the SamDB.
1298 :param samdb: Load a schema into a SamDB.
1299 :param setup_path: Setup path function.
1300 :param schemadn: DN of the schema
1301 :param netbiosname: NetBIOS name of the host.
1302 :param configdn: DN of the configuration
1304 schema_data = open(setup_path("schema.ldif"), 'r').read()
1305 schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
1306 schema_data = substitute_var(schema_data, {"SCHEMADN": schemadn})
1307 head_data = open(setup_path("provision_schema_basedn_modify.ldif"), 'r').read()
1308 head_data = substitute_var(head_data, {
1309 "SCHEMADN": schemadn,
1310 "NETBIOSNAME": netbiosname,
1311 "CONFIGDN": configdn,
1312 "DEFAULTSITE":sitename
1314 samdb.attach_schema_from_ldif(head_data, schema_data)