2 # Unix SMB/CIFS implementation.
3 # backend code for provisioning a Samba4 server
5 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
6 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008
8 # Based on the original in EJS:
9 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 from base64 import b64encode
35 from auth import system_session
36 from samba import Ldb, substitute_var, valid_netbios_name, check_all_substituted
37 from samba.samdb import SamDB
38 from samba.idmap import IDmapDB
41 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, \
42 LDB_ERR_NO_SUCH_OBJECT, timestring, CHANGETYPE_MODIFY, CHANGETYPE_NONE
44 """Functions for setting up a Samba configuration."""
46 DEFAULTSITE = "Default-First-Site-Name"
48 class InvalidNetbiosName(Exception):
49 def __init__(self, name):
50 super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)
66 self.dns_keytab = None
69 self.private_dir = None
72 self.modulesconf = None
73 self.memberofconf = None
74 self.fedoradsinf = None
75 self.fedoradspartitions = None
83 self.ldapmanagerdn = None
86 self.netbiosname = None
91 class ProvisionResult:
98 def check_install(lp, session_info, credentials):
99 """Check whether the current install seems ok.
101 :param lp: Loadparm context
102 :param session_info: Session information
103 :param credentials: Credentials
105 if lp.get("realm") == "":
106 raise Error("Realm empty")
107 ldb = Ldb(lp.get("sam database"), session_info=session_info,
108 credentials=credentials, lp=lp)
109 if len(ldb.search("(cn=Administrator)")) != 1:
110 raise "No administrator account found"
113 def findnss(nssfn, names):
114 """Find a user or group from a list of possibilities.
116 :param nssfn: NSS Function to try (should raise KeyError if not found)
117 :param names: Names to check.
118 :return: Value return by first names list.
125 raise KeyError("Unable to find user/group %r" % names)
128 def open_ldb(session_info, credentials, lp, dbname):
129 """Open a LDB, thrashing it if it is corrupt.
131 :param session_info: auth session information
132 :param credentials: credentials
133 :param lp: Loadparm context
134 :param dbname: Path of the database to open.
135 :return: a Ldb object
137 assert session_info is not None
139 return Ldb(dbname, session_info=session_info, credentials=credentials,
144 return Ldb(dbname, session_info=session_info, credentials=credentials,
148 def setup_add_ldif(ldb, ldif_path, subst_vars=None):
149 """Setup a ldb in the private dir.
151 :param ldb: LDB file to import data into
152 :param ldif_path: Path of the LDIF file to load
153 :param subst_vars: Optional variables to subsitute in LDIF.
155 assert isinstance(ldif_path, str)
157 data = open(ldif_path, 'r').read()
158 if subst_vars is not None:
159 data = substitute_var(data, subst_vars)
161 check_all_substituted(data)
166 def setup_modify_ldif(ldb, ldif_path, substvars=None):
167 """Modify a ldb in the private dir.
169 :param ldb: LDB object.
170 :param ldif_path: LDIF file path.
171 :param substvars: Optional dictionary with substitution variables.
173 data = open(ldif_path, 'r').read()
174 if substvars is not None:
175 data = substitute_var(data, substvars)
177 check_all_substituted(data)
179 ldb.modify_ldif(data)
182 def setup_ldb(ldb, ldif_path, subst_vars):
183 """Import a LDIF a file into a LDB handle, optionally substituting variables.
185 :note: Either all LDIF data will be added or none (using transactions).
187 :param ldb: LDB file to import into.
188 :param ldif_path: Path to the LDIF file.
189 :param subst_vars: Dictionary with substitution variables.
191 assert ldb is not None
192 ldb.transaction_start()
194 setup_add_ldif(ldb, ldif_path, subst_vars)
196 ldb.transaction_cancel()
198 ldb.transaction_commit()
201 def setup_file(template, fname, substvars):
202 """Setup a file in the private dir.
204 :param template: Path of the template file.
205 :param fname: Path of the file to create.
206 :param substvars: Substitution variables.
210 if os.path.exists(f):
213 data = open(template, 'r').read()
215 data = substitute_var(data, substvars)
216 check_all_substituted(data)
218 open(f, 'w').write(data)
221 def provision_paths_from_lp(lp, dnsdomain):
222 """Set the default paths for provisioning.
224 :param lp: Loadparm context.
225 :param dnsdomain: DNS Domain name
227 paths = ProvisionPaths()
228 paths.private_dir = lp.get("private dir")
229 paths.keytab = "secrets.keytab"
230 paths.dns_keytab = "dns.keytab"
232 paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
233 paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb")
234 paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb")
235 paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
236 paths.templates = os.path.join(paths.private_dir, "templates.ldb")
237 paths.dns = os.path.join(paths.private_dir, dnsdomain + ".zone")
238 paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
239 paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
240 paths.phpldapadminconfig = os.path.join(paths.private_dir,
241 "phpldapadmin-config.php")
242 paths.ldapdir = os.path.join(paths.private_dir,
244 paths.slapdconf = os.path.join(paths.ldapdir,
246 paths.modulesconf = os.path.join(paths.ldapdir,
248 paths.memberofconf = os.path.join(paths.ldapdir,
250 paths.fedoradsinf = os.path.join(paths.ldapdir,
252 paths.fedoradspartitions = os.path.join(paths.ldapdir,
253 "fedorads-partitions.ldif")
254 paths.hklm = "hklm.ldb"
255 paths.hkcr = "hkcr.ldb"
256 paths.hkcu = "hkcu.ldb"
257 paths.hku = "hku.ldb"
258 paths.hkpd = "hkpd.ldb"
259 paths.hkpt = "hkpt.ldb"
261 paths.sysvol = lp.get("path", "sysvol")
263 paths.netlogon = lp.get("path", "netlogon")
267 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, serverrole=None,
268 rootdn=None, domaindn=None, configdn=None, schemadn=None, sitename=None):
271 hostname = socket.gethostname().split(".")[0].lower()
273 netbiosname = hostname.upper()
274 if not valid_netbios_name(netbiosname):
275 raise InvalidNetbiosName(netbiosname)
277 hostname = hostname.lower()
279 if dnsdomain is None:
280 dnsdomain = lp.get("realm")
282 if serverrole is None:
283 serverrole = lp.get("server role")
285 assert dnsdomain is not None
286 realm = dnsdomain.upper()
288 if lp.get("realm").upper() != realm:
289 raise Exception("realm '%s' in %s must match chosen realm '%s'" %
290 (lp.get("realm"), smbconf, realm))
292 dnsdomain = dnsdomain.lower()
294 if (serverrole == "domain controller"):
296 domain = lp.get("workgroup")
298 domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
299 if lp.get("workgroup").upper() != domain.upper():
300 raise Error("workgroup '%s' in smb.conf must match chosen domain '%s'",
301 lp.get("workgroup"), domain)
305 domaindn = "CN=" + netbiosname
307 assert domain is not None
308 domain = domain.upper()
309 if not valid_netbios_name(domain):
310 raise InvalidNetbiosName(domain)
316 configdn = "CN=Configuration," + rootdn
318 schemadn = "CN=Schema," + configdn
323 names = ProvisionNames()
324 names.rootdn = rootdn
325 names.domaindn = domaindn
326 names.configdn = configdn
327 names.schemadn = schemadn
328 names.ldapmanagerdn = "CN=Manager," + rootdn
329 names.dnsdomain = dnsdomain
330 names.domain = domain
332 names.netbiosname = netbiosname
333 names.hostname = hostname
334 names.sitename = sitename
339 def load_or_make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, targetdir):
340 if targetdir is not None:
341 if not os.path.exists(targetdir):
343 if not os.path.exists(os.path.join(targetdir, "etc")):
344 os.mkdir(os.path.join(targetdir, "etc"))
346 smbconf = os.path.join(targetdir, "etc", "smb.conf")
348 # only install a new smb.conf if there isn't one there already
350 if not os.path.exists(smbconf):
352 hostname = socket.gethostname().split(".")[0].lower()
354 if serverrole is None:
355 serverrole = "standalone"
357 assert serverrole in ("domain controller", "member server", "standalone")
358 if serverrole == "domain controller":
360 elif serverrole == "member server":
361 smbconfsuffix = "member"
362 elif serverrole == "standalone":
363 smbconfsuffix = "standalone"
365 assert domain is not None
366 assert realm is not None
368 default_lp = param.LoadParm()
369 #Load non-existant file
370 default_lp.load(smbconf)
372 if targetdir is not None:
373 privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
374 lockdir_line = "lock dir = " + os.path.abspath(targetdir)
376 default_lp.set("lock dir", os.path.abspath(targetdir))
381 sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
382 netlogon = os.path.join(sysvol, realm.lower(), "scripts")
384 setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
386 "HOSTNAME": hostname,
389 "SERVERROLE": serverrole,
390 "NETLOGONPATH": netlogon,
391 "SYSVOLPATH": sysvol,
392 "PRIVATEDIR_LINE": privatedir_line,
393 "LOCKDIR_LINE": lockdir_line
396 lp = param.LoadParm()
401 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
402 users_gid, wheel_gid, backup_gid):
403 """setup reasonable name mappings for sam names to unix names.
405 :param samdb: SamDB object.
406 :param idmap: IDmap db object.
407 :param sid: The domain sid.
408 :param domaindn: The domain DN.
409 :param root_uid: uid of the UNIX root user.
410 :param nobody_uid: uid of the UNIX nobody user.
411 :param users_gid: gid of the UNIX users group.
412 :param wheel_gid: gid of the UNIX wheel group.
413 :param backup_gid: gid of the UNIX backup group."""
414 # add some foreign sids if they are not present already
415 samdb.add_foreign(domaindn, "S-1-5-7", "Anonymous")
416 samdb.add_foreign(domaindn, "S-1-1-0", "World")
417 samdb.add_foreign(domaindn, "S-1-5-2", "Network")
418 samdb.add_foreign(domaindn, "S-1-5-18", "System")
419 samdb.add_foreign(domaindn, "S-1-5-11", "Authenticated Users")
421 idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
422 idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
423 idmap.setup_name_mapping("S-1-5-32-551", idmap.TYPE_GID, backup_gid)
425 idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
426 idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
428 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
430 serverrole, ldap_backend=None,
431 ldap_backend_type=None, erase=False):
432 """Setup the partitions for the SAM database.
434 Alternatively, provision() may call this, and then populate the database.
436 :note: This will wipe the Sam Database!
438 :note: This function always removes the local SAM LDB file. The erase
439 parameter controls whether to erase the existing data, which
440 may not be stored locally but in LDAP.
442 assert session_info is not None
444 samdb = SamDB(samdb_path, session_info=session_info,
445 credentials=credentials, lp=lp)
451 os.unlink(samdb_path)
453 samdb = SamDB(samdb_path, session_info=session_info,
454 credentials=credentials, lp=lp)
456 #Add modules to the list to activate them by default
457 #beware often order is important
459 # Some Known ordering constraints:
460 # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
461 # - objectclass must be before password_hash, because password_hash checks
462 # that the objectclass is of type person (filled in by objectclass
463 # module when expanding the objectclass list)
464 # - partition must be last
465 # - each partition has its own module list then
466 modules_list = ["rootdse",
482 modules_list2 = ["show_deleted",
485 domaindn_ldb = "users.ldb"
486 if ldap_backend is not None:
487 domaindn_ldb = ldap_backend
488 configdn_ldb = "configuration.ldb"
489 if ldap_backend is not None:
490 configdn_ldb = ldap_backend
491 schemadn_ldb = "schema.ldb"
492 if ldap_backend is not None:
493 schema_ldb = ldap_backend
494 schemadn_ldb = ldap_backend
496 if ldap_backend_type == "fedora-ds":
497 backend_modules = ["nsuniqueid", "paged_searches"]
498 # We can handle linked attributes here, as we don't have directory-side subtree operations
499 tdb_modules_list = ["linked_attributes"]
500 elif ldap_backend_type == "openldap":
501 backend_modules = ["normalise", "entryuuid", "paged_searches"]
502 # OpenLDAP handles subtree renames, so we don't want to do any of these things
503 tdb_modules_list = None
504 elif serverrole == "domain controller":
505 backend_modules = ["repl_meta_data"]
507 backend_modules = ["objectguid"]
509 if tdb_modules_list is None:
510 tdb_modules_list_as_string = ""
512 tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
514 samdb.transaction_start()
516 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
517 "SCHEMADN": names.schemadn,
518 "SCHEMADN_LDB": schemadn_ldb,
519 "SCHEMADN_MOD2": ",objectguid",
520 "CONFIGDN": names.configdn,
521 "CONFIGDN_LDB": configdn_ldb,
522 "DOMAINDN": names.domaindn,
523 "DOMAINDN_LDB": domaindn_ldb,
524 "SCHEMADN_MOD": "schema_fsmo,instancetype",
525 "CONFIGDN_MOD": "naming_fsmo,instancetype",
526 "DOMAINDN_MOD": "pdc_fsmo,password_hash,instancetype",
527 "MODULES_LIST": ",".join(modules_list),
528 "TDB_MODULES_LIST": tdb_modules_list_as_string,
529 "MODULES_LIST2": ",".join(modules_list2),
530 "BACKEND_MOD": ",".join(backend_modules),
534 samdb.transaction_cancel()
537 samdb.transaction_commit()
539 samdb = SamDB(samdb_path, session_info=session_info,
540 credentials=credentials, lp=lp)
542 samdb.transaction_start()
544 message("Setting up sam.ldb attributes")
545 samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
547 message("Setting up sam.ldb rootDSE")
548 setup_samdb_rootdse(samdb, setup_path, names.schemadn, names.domaindn, names.hostname,
549 names.dnsdomain, names.realm, names.rootdn, names.configdn, names.netbiosname,
553 message("Erasing data from partitions")
554 samdb.erase_partitions()
557 samdb.transaction_cancel()
560 samdb.transaction_commit()
565 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain,
566 netbiosname, domainsid, keytab_path, samdb_url,
567 dns_keytab_path, dnspass, machinepass):
568 """Add DC-specific bits to a secrets database.
570 :param secretsdb: Ldb Handle to the secrets database
571 :param setup_path: Setup path function
572 :param machinepass: Machine password
574 setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), {
575 "MACHINEPASS_B64": b64encode(machinepass),
578 "DNSDOMAIN": dnsdomain,
579 "DOMAINSID": str(domainsid),
580 "SECRETS_KEYTAB": keytab_path,
581 "NETBIOSNAME": netbiosname,
582 "SAM_LDB": samdb_url,
583 "DNS_KEYTAB": dns_keytab_path,
584 "DNSPASS_B64": b64encode(dnspass),
588 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
589 """Setup the secrets database.
591 :param path: Path to the secrets database.
592 :param setup_path: Get the path to a setup file.
593 :param session_info: Session info.
594 :param credentials: Credentials
595 :param lp: Loadparm context
596 :return: LDB handle for the created secrets database
598 if os.path.exists(path):
600 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
603 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
604 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
606 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
610 def setup_templatesdb(path, setup_path, session_info, credentials, lp):
611 """Setup the templates database.
613 :param path: Path to the database.
614 :param setup_path: Function for obtaining the path to setup files.
615 :param session_info: Session info
616 :param credentials: Credentials
617 :param lp: Loadparm context
619 templates_ldb = SamDB(path, session_info=session_info,
620 credentials=credentials, lp=lp)
621 templates_ldb.erase()
622 templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif"))
625 def setup_registry(path, setup_path, session_info, credentials, lp):
626 """Setup the registry.
628 :param path: Path to the registry database
629 :param setup_path: Function that returns the path to a setup.
630 :param session_info: Session information
631 :param credentials: Credentials
632 :param lp: Loadparm context
634 reg = registry.Registry()
635 hive = registry.open_ldb(path, session_info=session_info,
636 credentials=credentials, lp_ctx=lp)
637 reg.mount_hive(hive, "HKEY_LOCAL_MACHINE")
638 provision_reg = setup_path("provision.reg")
639 assert os.path.exists(provision_reg)
640 reg.diff_apply(provision_reg)
642 def setup_idmapdb(path, setup_path, session_info, credentials, lp):
643 """Setup the idmap database.
645 :param path: path to the idmap database
646 :param setup_path: Function that returns a path to a setup file
647 :param session_info: Session information
648 :param credentials: Credentials
649 :param lp: Loadparm context
651 if os.path.exists(path):
654 idmap_ldb = IDmapDB(path, session_info=session_info,
655 credentials=credentials, lp=lp)
658 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
661 def setup_samdb_rootdse(samdb, setup_path, schemadn, domaindn, hostname,
662 dnsdomain, realm, rootdn, configdn, netbiosname,
664 """Setup the SamDB rootdse.
666 :param samdb: Sam Database handle
667 :param setup_path: Obtain setup path
669 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
670 "SCHEMADN": schemadn,
671 "NETBIOSNAME": netbiosname,
672 "DNSDOMAIN": dnsdomain,
673 "DEFAULTSITE": sitename,
675 "DNSNAME": "%s.%s" % (hostname, dnsdomain),
676 "DOMAINDN": domaindn,
678 "CONFIGDN": configdn,
679 "VERSION": samba.version(),
683 def setup_self_join(samdb, names,
684 machinepass, dnspass,
685 domainsid, invocationid, setup_path,
687 """Join a host to its own domain."""
688 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
689 "CONFIGDN": names.configdn,
690 "SCHEMADN": names.schemadn,
691 "DOMAINDN": names.domaindn,
692 "INVOCATIONID": invocationid,
693 "NETBIOSNAME": names.netbiosname,
694 "DEFAULTSITE": names.sitename,
695 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
696 "MACHINEPASS_B64": b64encode(machinepass),
697 "DNSPASS_B64": b64encode(dnspass),
698 "REALM": names.realm,
699 "DOMAIN": names.domain,
700 "DNSDOMAIN": names.dnsdomain})
701 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
702 "POLICYGUID": policyguid,
703 "DNSDOMAIN": names.dnsdomain,
704 "DOMAINSID": str(domainsid),
705 "DOMAINDN": names.domaindn})
708 def setup_samdb(path, setup_path, session_info, credentials, lp,
710 domainsid, aci, domainguid, policyguid,
711 fill, adminpass, krbtgtpass,
712 machinepass, invocationid, dnspass,
713 serverrole, ldap_backend=None,
714 ldap_backend_type=None):
715 """Setup a complete SAM Database.
717 :note: This will wipe the main SAM database file!
720 erase = (fill != FILL_DRS)
722 # Also wipes the database
723 setup_samdb_partitions(path, setup_path, message=message, lp=lp,
724 credentials=credentials, session_info=session_info,
726 ldap_backend=ldap_backend, serverrole=serverrole,
727 ldap_backend_type=ldap_backend_type, erase=erase)
729 samdb = SamDB(path, session_info=session_info,
730 credentials=credentials, lp=lp)
733 # We want to finish here, but setup the index before we do so
734 message("Setting up sam.ldb index")
735 samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
738 message("Pre-loading the Samba 4 and AD schema")
739 samdb = SamDB(path, session_info=session_info,
740 credentials=credentials, lp=lp)
741 samdb.set_domain_sid(domainsid)
742 if serverrole == "domain controller":
743 samdb.set_invocation_id(invocationid)
745 load_schema(setup_path, samdb, names.schemadn, names.netbiosname, names.configdn, names.sitename)
747 samdb.transaction_start()
750 message("Adding DomainDN: %s (permitted to fail)" % names.domaindn)
751 if serverrole == "domain controller":
752 domain_oc = "domainDNS"
754 domain_oc = "samba4LocalDomain"
756 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
757 "DOMAINDN": names.domaindn,
759 "DOMAIN_OC": domain_oc
762 message("Modifying DomainDN: " + names.domaindn + "")
763 if domainguid is not None:
764 domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
768 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
769 "LDAPTIME": timestring(int(time.time())),
770 "DOMAINSID": str(domainsid),
771 "SCHEMADN": names.schemadn,
772 "NETBIOSNAME": names.netbiosname,
773 "DEFAULTSITE": names.sitename,
774 "CONFIGDN": names.configdn,
775 "POLICYGUID": policyguid,
776 "DOMAINDN": names.domaindn,
777 "DOMAINGUID_MOD": domainguid_mod,
780 message("Adding configuration container (permitted to fail)")
781 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
782 "CONFIGDN": names.configdn,
784 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb",
786 message("Modifying configuration container")
787 setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
788 "CONFIGDN": names.configdn,
789 "SCHEMADN": names.schemadn,
792 message("Adding schema container (permitted to fail)")
793 setup_add_ldif(samdb, setup_path("provision_schema_basedn.ldif"), {
794 "SCHEMADN": names.schemadn,
796 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
798 message("Modifying schema container")
799 setup_modify_ldif(samdb,
800 setup_path("provision_schema_basedn_modify.ldif"), {
801 "SCHEMADN": names.schemadn,
802 "NETBIOSNAME": names.netbiosname,
803 "DEFAULTSITE": names.sitename,
804 "CONFIGDN": names.configdn,
807 message("Setting up sam.ldb Samba4 schema")
808 setup_add_ldif(samdb, setup_path("schema_samba4.ldif"),
809 {"SCHEMADN": names.schemadn })
810 message("Setting up sam.ldb AD schema")
811 setup_add_ldif(samdb, setup_path("schema.ldif"),
812 {"SCHEMADN": names.schemadn})
814 message("Setting up sam.ldb configuration data")
815 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
816 "CONFIGDN": names.configdn,
817 "NETBIOSNAME": names.netbiosname,
818 "DEFAULTSITE": names.sitename,
819 "DNSDOMAIN": names.dnsdomain,
820 "DOMAIN": names.domain,
821 "SCHEMADN": names.schemadn,
822 "DOMAINDN": names.domaindn,
825 message("Setting up display specifiers")
826 setup_add_ldif(samdb, setup_path("display_specifiers.ldif"),
827 {"CONFIGDN": names.configdn})
829 message("Adding users container (permitted to fail)")
830 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
831 "DOMAINDN": names.domaindn})
832 message("Modifying users container")
833 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
834 "DOMAINDN": names.domaindn})
835 message("Adding computers container (permitted to fail)")
836 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
837 "DOMAINDN": names.domaindn})
838 message("Modifying computers container")
839 setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
840 "DOMAINDN": names.domaindn})
841 message("Setting up sam.ldb data")
842 setup_add_ldif(samdb, setup_path("provision.ldif"), {
843 "DOMAINDN": names.domaindn,
844 "NETBIOSNAME": names.netbiosname,
845 "DEFAULTSITE": names.sitename,
846 "CONFIGDN": names.configdn,
849 if fill == FILL_FULL:
850 message("Setting up sam.ldb users and groups")
851 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
852 "DOMAINDN": names.domaindn,
853 "DOMAINSID": str(domainsid),
854 "CONFIGDN": names.configdn,
855 "ADMINPASS_B64": b64encode(adminpass),
856 "KRBTGTPASS_B64": b64encode(krbtgtpass),
859 if serverrole == "domain controller":
860 message("Setting up self join")
861 setup_self_join(samdb, names=names, invocationid=invocationid,
863 machinepass=machinepass,
864 domainsid=domainsid, policyguid=policyguid,
865 setup_path=setup_path)
867 #We want to setup the index last, as adds are faster unindexed
868 message("Setting up sam.ldb index")
869 samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
871 samdb.transaction_cancel()
874 samdb.transaction_commit()
879 FILL_NT4SYNC = "NT4SYNC"
882 def provision(setup_dir, message, session_info,
883 credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL, realm=None,
884 rootdn=None, domaindn=None, schemadn=None, configdn=None,
885 domain=None, hostname=None, hostip=None, hostip6=None,
886 domainsid=None, adminpass=None, krbtgtpass=None, domainguid=None,
887 policyguid=None, invocationid=None, machinepass=None,
888 dnspass=None, root=None, nobody=None, nogroup=None, users=None,
889 wheel=None, backup=None, aci=None, serverrole=None,
890 ldap_backend=None, ldap_backend_type=None, sitename=None):
893 :note: caution, this wipes all existing data!
896 def setup_path(file):
897 return os.path.join(setup_dir, file)
899 if domainsid is None:
900 domainsid = security.random_sid()
902 domainsid = security.Sid(domainsid)
904 if policyguid is None:
905 policyguid = uuid.random()
906 if adminpass is None:
907 adminpass = misc.random_password(12)
908 if krbtgtpass is None:
909 krbtgtpass = misc.random_password(12)
910 if machinepass is None:
911 machinepass = misc.random_password(12)
913 dnspass = misc.random_password(12)
915 root_uid = findnss(pwd.getpwnam, ["root"])[2]
917 root_uid = findnss(pwd.getpwnam, [root])[2]
919 nobody_uid = findnss(pwd.getpwnam, ["nobody"])[2]
921 nobody_uid = findnss(pwd.getpwnam, [nobody])[2]
923 users_gid = findnss(grp.getgrnam, ["users"])[2]
925 users_gid = findnss(grp.getgrnam, [users])[2]
927 wheel_gid = findnss(grp.getgrnam, ["wheel", "adm"])[2]
929 wheel_gid = findnss(grp.getgrnam, [wheel])[2]
931 backup_gid = findnss(grp.getgrnam, ["backup", "staff"])[2]
933 backup_gid = findnss(grp.getgrnam, [backup])[2]
935 aci = "# no aci for local ldb"
937 lp = load_or_make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, targetdir)
939 names = guess_names(lp=lp, hostname=hostname, domain=domain,
940 dnsdomain=realm, serverrole=serverrole, sitename=sitename,
941 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn)
943 paths = provision_paths_from_lp(lp, names.dnsdomain)
946 hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
950 hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
951 except socket.gaierror: pass
953 if serverrole is None:
954 serverrole = lp.get("server role")
956 assert serverrole in ("domain controller", "member server", "standalone")
957 if invocationid is None and serverrole == "domain controller":
958 invocationid = uuid.random()
960 if not os.path.exists(paths.private_dir):
961 os.mkdir(paths.private_dir)
963 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
965 if ldap_backend is not None:
966 if ldap_backend == "ldapi":
967 # provision-backend will set this path suggested slapd command line / fedorads.inf
968 ldap_backend = "ldapi://" % urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
970 # only install a new shares config db if there is none
971 if not os.path.exists(paths.shareconf):
972 message("Setting up share.ldb")
973 share_ldb = Ldb(paths.shareconf, session_info=session_info,
974 credentials=credentials, lp=lp)
975 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
978 message("Setting up secrets.ldb")
979 secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
980 session_info=session_info,
981 credentials=credentials, lp=lp)
983 message("Setting up the registry")
984 setup_registry(paths.hklm, setup_path, session_info,
985 credentials=credentials, lp=lp)
987 message("Setting up templates db")
988 setup_templatesdb(paths.templates, setup_path, session_info=session_info,
989 credentials=credentials, lp=lp)
991 message("Setting up idmap db")
992 idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
993 credentials=credentials, lp=lp)
995 samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info,
996 credentials=credentials, lp=lp, names=names,
999 aci=aci, domainguid=domainguid, policyguid=policyguid,
1001 adminpass=adminpass, krbtgtpass=krbtgtpass,
1002 invocationid=invocationid,
1003 machinepass=machinepass, dnspass=dnspass,
1004 serverrole=serverrole, ldap_backend=ldap_backend,
1005 ldap_backend_type=ldap_backend_type)
1007 if lp.get("server role") == "domain controller":
1008 policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1009 "{" + policyguid + "}")
1010 os.makedirs(policy_path, 0755)
1011 os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1012 os.makedirs(os.path.join(policy_path, "User"), 0755)
1013 if not os.path.isdir(paths.netlogon):
1014 os.makedirs(paths.netlogon, 0755)
1015 secrets_ldb = Ldb(paths.secrets, session_info=session_info,
1016 credentials=credentials, lp=lp)
1017 secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1018 netbiosname=names.netbiosname, domainsid=domainsid,
1019 keytab_path=paths.keytab, samdb_url=paths.samdb,
1020 dns_keytab_path=paths.dns_keytab, dnspass=dnspass,
1021 machinepass=machinepass, dnsdomain=names.dnsdomain)
1023 if samdb_fill == FILL_FULL:
1024 setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1025 root_uid=root_uid, nobody_uid=nobody_uid,
1026 users_gid=users_gid, wheel_gid=wheel_gid,
1027 backup_gid=backup_gid)
1029 message("Setting up sam.ldb rootDSE marking as synchronized")
1030 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1032 # Only make a zone file on the first DC, it should be replicated with DNS replication
1033 if serverrole == "domain controller":
1034 samdb = SamDB(paths.samdb, session_info=session_info,
1035 credentials=credentials, lp=lp)
1037 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1038 assert isinstance(domainguid, str)
1039 hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1040 expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1041 scope=SCOPE_SUBTREE)
1042 assert isinstance(hostguid, str)
1044 create_zone_file(paths.dns, setup_path, samdb,
1045 hostname=names.hostname, hostip=hostip,
1046 hostip6=hostip6, dnsdomain=names.dnsdomain,
1047 domaindn=names.domaindn, dnspass=dnspass, realm=names.realm,
1048 domainguid=domainguid, hostguid=hostguid)
1049 message("Please install the zone located in %s into your DNS server" % paths.dns)
1051 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1054 message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1056 message("Once the above files are installed, your server will be ready to use")
1057 message("Server Type: %s" % serverrole)
1058 message("Hostname: %s" % names.hostname)
1059 message("NetBIOS Domain: %s" % names.domain)
1060 message("DNS Domain: %s" % names.dnsdomain)
1061 message("DOMAIN SID: %s" % str(domainsid))
1062 message("Admin password: %s" % adminpass)
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 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 = socket.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 attrs = ["linkID", "lDAPDisplayName"]
1177 res = schemadb.search(expression="(&(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1)))(objectclass=attributeSchema))", base=names.schemadn, scope=SCOPE_SUBTREE, attrs=attrs);
1179 memberof_config = "# Generated from schema in " + schemadb_path + "\n";
1180 refint_attributes = "";
1181 for i in range (0, len(res)):
1182 linkid = res[i]["linkID"][0]
1183 linkid = str(int(linkid) + 1)
1184 expression = "(&(objectclass=attributeSchema)(linkID=" + (linkid) + "))"
1185 target = schemadb.searchone(basedn=names.schemadn,
1186 expression=expression,
1187 attribute="lDAPDisplayName",
1188 scope=SCOPE_SUBTREE);
1189 if target is not None:
1190 refint_attributes = refint_attributes + " " + target + " " + res[i]["lDAPDisplayName"][0];
1191 memberof_config = memberof_config + """overlay memberof
1192 memberof-dangling error
1193 memberof-refint TRUE
1194 memberof-group-oc top
1195 memberof-member-ad """ + res[i]["lDAPDisplayName"][0] + """
1196 memberof-memberof-ad """ + target + """
1197 memberof-dangling-error 32
1201 memberof_config = memberof_config + """
1203 refint_attributes""" + refint_attributes + "\n";
1205 setup_file(setup_path("slapd.conf"), paths.slapdconf,
1206 {"DNSDOMAIN": names.dnsdomain,
1207 "LDAPDIR": paths.ldapdir,
1208 "DOMAINDN": names.domaindn,
1209 "CONFIGDN": names.configdn,
1210 "SCHEMADN": names.schemadn,
1211 "LDAPMANAGERDN": names.ldapmanagerdn,
1212 "LDAPMANAGERPASS": adminpass,
1213 "MEMBEROF_CONFIG": memberof_config})
1214 setup_file(setup_path("modules.conf"), paths.modulesconf,
1215 {"REALM": names.realm})
1217 setup_db_config(setup_path, file, os.path.join(paths.ldapdir, "db", "user"))
1218 setup_db_config(setup_path, file, os.path.join(paths.ldapdir, "db", "config"))
1219 setup_db_config(setup_path, file, os.path.join(paths.ldapdir, "db", "schema"))
1220 mapping = "schema-map-openldap-2.3"
1221 backend_schema = "backend-schema.schema"
1224 ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
1225 message("Start slapd with: slapd -f " + paths.ldapdir + "/slapd.conf -h " + ldapi_uri)
1228 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);
1230 os.system(schema_command)
1234 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1235 """Create a PHP LDAP admin configuration file.
1237 :param path: Path to write the configuration to.
1238 :param setup_path: Function to generate setup paths.
1240 setup_file(setup_path("phpldapadmin-config.php"), path,
1241 {"S4_LDAPI_URI": ldapi_uri})
1244 def create_zone_file(path, setup_path, samdb, dnsdomain, domaindn,
1245 hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid):
1246 """Write out a DNS zone file, from the info in the current database.
1248 :param path: Path of the new file.
1249 :param setup_path": Setup path function.
1250 :param samdb: SamDB object
1251 :param dnsdomain: DNS Domain name
1252 :param domaindn: DN of the Domain
1253 :param hostip: Local IPv4 IP
1254 :param hostip6: Local IPv6 IP
1255 :param hostname: Local hostname
1256 :param dnspass: Password for DNS
1257 :param realm: Realm name
1258 :param domainguid: GUID of the domain.
1259 :param hostguid: GUID of the host.
1261 assert isinstance(domainguid, str)
1263 hostip6_base_line = ""
1264 hostip6_host_line = ""
1266 if hostip6 is not None:
1267 hostip6_base_line = " IN AAAA " + hostip6
1268 hostip6_host_line = hostname + " IN AAAA " + hostip6
1270 setup_file(setup_path("provision.zone"), path, {
1271 "DNSPASS_B64": b64encode(dnspass),
1272 "HOSTNAME": hostname,
1273 "DNSDOMAIN": dnsdomain,
1276 "DOMAINGUID": domainguid,
1277 "DATESTRING": time.strftime("%Y%m%d%H"),
1278 "DEFAULTSITE": DEFAULTSITE,
1279 "HOSTGUID": hostguid,
1280 "HOSTIP6_BASE_LINE": hostip6_base_line,
1281 "HOSTIP6_HOST_LINE": hostip6_host_line,
1284 def load_schema(setup_path, samdb, schemadn, netbiosname, configdn, sitename):
1285 """Load schema for the SamDB.
1287 :param samdb: Load a schema into a SamDB.
1288 :param setup_path: Setup path function.
1289 :param schemadn: DN of the schema
1290 :param netbiosname: NetBIOS name of the host.
1291 :param configdn: DN of the configuration
1293 schema_data = open(setup_path("schema.ldif"), 'r').read()
1294 schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
1295 schema_data = substitute_var(schema_data, {"SCHEMADN": schemadn})
1296 head_data = open(setup_path("provision_schema_basedn_modify.ldif"), 'r').read()
1297 head_data = substitute_var(head_data, {
1298 "SCHEMADN": schemadn,
1299 "NETBIOSNAME": netbiosname,
1300 "CONFIGDN": configdn,
1301 "DEFAULTSITE":sitename
1303 samdb.attach_schema_from_ldif(head_data, schema_data)