2 # Unix SMB/CIFS implementation.
3 # backend code for provisioning a Samba4 server
5 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
6 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008
8 # Based on the original in EJS:
9 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 from base64 import b64encode
35 from auth import system_session
36 from samba import Ldb, substitute_var, valid_netbios_name, check_all_substituted
37 from samba.samdb import SamDB
38 from samba.idmap import IDmapDB
41 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, \
42 LDB_ERR_NO_SUCH_OBJECT, timestring, CHANGETYPE_MODIFY, CHANGETYPE_NONE
44 """Functions for setting up a Samba configuration."""
46 DEFAULTSITE = "Default-First-Site-Name"
48 class InvalidNetbiosName(Exception):
49 def __init__(self, name):
50 super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)
66 self.dns_keytab = None
69 self.private_dir = None
72 self.modulesconf = None
73 self.memberofconf = None
74 self.fedoradsinf = None
75 self.fedoradspartitions = None
83 self.ldapmanagerdn = None
86 self.netbiosname = None
91 class ProvisionResult:
98 def check_install(lp, session_info, credentials):
99 """Check whether the current install seems ok.
101 :param lp: Loadparm context
102 :param session_info: Session information
103 :param credentials: Credentials
105 if lp.get("realm") == "":
106 raise Exception("Realm empty")
107 ldb = Ldb(lp.get("sam database"), session_info=session_info,
108 credentials=credentials, lp=lp)
109 if len(ldb.search("(cn=Administrator)")) != 1:
110 raise "No administrator account found"
113 def findnss(nssfn, names):
114 """Find a user or group from a list of possibilities.
116 :param nssfn: NSS Function to try (should raise KeyError if not found)
117 :param names: Names to check.
118 :return: Value return by first names list.
125 raise KeyError("Unable to find user/group %r" % names)
128 def open_ldb(session_info, credentials, lp, dbname):
129 """Open a LDB, thrashing it if it is corrupt.
131 :param session_info: auth session information
132 :param credentials: credentials
133 :param lp: Loadparm context
134 :param dbname: Path of the database to open.
135 :return: a Ldb object
137 assert session_info is not None
139 return Ldb(dbname, session_info=session_info, credentials=credentials,
144 return Ldb(dbname, session_info=session_info, credentials=credentials,
148 def setup_add_ldif(ldb, ldif_path, subst_vars=None):
149 """Setup a ldb in the private dir.
151 :param ldb: LDB file to import data into
152 :param ldif_path: Path of the LDIF file to load
153 :param subst_vars: Optional variables to subsitute in LDIF.
155 assert isinstance(ldif_path, str)
157 data = open(ldif_path, 'r').read()
158 if subst_vars is not None:
159 data = substitute_var(data, subst_vars)
161 check_all_substituted(data)
166 def setup_modify_ldif(ldb, ldif_path, substvars=None):
167 """Modify a ldb in the private dir.
169 :param ldb: LDB object.
170 :param ldif_path: LDIF file path.
171 :param substvars: Optional dictionary with substitution variables.
173 data = open(ldif_path, 'r').read()
174 if substvars is not None:
175 data = substitute_var(data, substvars)
177 check_all_substituted(data)
179 ldb.modify_ldif(data)
182 def setup_ldb(ldb, ldif_path, subst_vars):
183 """Import a LDIF a file into a LDB handle, optionally substituting variables.
185 :note: Either all LDIF data will be added or none (using transactions).
187 :param ldb: LDB file to import into.
188 :param ldif_path: Path to the LDIF file.
189 :param subst_vars: Dictionary with substitution variables.
191 assert ldb is not None
192 ldb.transaction_start()
194 setup_add_ldif(ldb, ldif_path, subst_vars)
196 ldb.transaction_cancel()
198 ldb.transaction_commit()
201 def setup_file(template, fname, substvars):
202 """Setup a file in the private dir.
204 :param template: Path of the template file.
205 :param fname: Path of the file to create.
206 :param substvars: Substitution variables.
210 if os.path.exists(f):
213 data = open(template, 'r').read()
215 data = substitute_var(data, substvars)
216 check_all_substituted(data)
218 open(f, 'w').write(data)
221 def provision_paths_from_lp(lp, dnsdomain):
222 """Set the default paths for provisioning.
224 :param lp: Loadparm context.
225 :param dnsdomain: DNS Domain name
227 paths = ProvisionPaths()
228 paths.private_dir = lp.get("private dir")
229 paths.keytab = "secrets.keytab"
230 paths.dns_keytab = "dns.keytab"
232 paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
233 paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb")
234 paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb")
235 paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
236 paths.templates = os.path.join(paths.private_dir, "templates.ldb")
237 paths.dns = os.path.join(paths.private_dir, dnsdomain + ".zone")
238 paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
239 paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
240 paths.phpldapadminconfig = os.path.join(paths.private_dir,
241 "phpldapadmin-config.php")
242 paths.ldapdir = os.path.join(paths.private_dir,
244 paths.slapdconf = os.path.join(paths.ldapdir,
246 paths.modulesconf = os.path.join(paths.ldapdir,
248 paths.memberofconf = os.path.join(paths.ldapdir,
250 paths.fedoradsinf = os.path.join(paths.ldapdir,
252 paths.fedoradspartitions = os.path.join(paths.ldapdir,
253 "fedorads-partitions.ldif")
254 paths.hklm = "hklm.ldb"
255 paths.hkcr = "hkcr.ldb"
256 paths.hkcu = "hkcu.ldb"
257 paths.hku = "hku.ldb"
258 paths.hkpd = "hkpd.ldb"
259 paths.hkpt = "hkpt.ldb"
261 paths.sysvol = lp.get("path", "sysvol")
263 paths.netlogon = lp.get("path", "netlogon")
268 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
269 serverrole=None, rootdn=None, domaindn=None, configdn=None,
270 schemadn=None, sitename=None):
273 hostname = socket.gethostname().split(".")[0].lower()
275 netbiosname = hostname.upper()
276 if not valid_netbios_name(netbiosname):
277 raise InvalidNetbiosName(netbiosname)
279 hostname = hostname.lower()
281 if dnsdomain is None:
282 dnsdomain = lp.get("realm")
284 if serverrole is None:
285 serverrole = lp.get("server role")
287 assert dnsdomain is not None
288 realm = dnsdomain.upper()
290 if lp.get("realm").upper() != realm:
291 raise Exception("realm '%s' must match chosen realm '%s'" %
292 (lp.get("realm"), realm))
294 dnsdomain = dnsdomain.lower()
296 if (serverrole == "domain controller"):
298 domain = lp.get("workgroup")
300 domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
301 if lp.get("workgroup").upper() != domain.upper():
302 raise Exception("workgroup '%s' in smb.conf must match chosen domain '%s'",
303 lp.get("workgroup"), domain)
307 domaindn = "CN=" + netbiosname
309 assert domain is not None
310 domain = domain.upper()
311 if not valid_netbios_name(domain):
312 raise InvalidNetbiosName(domain)
318 configdn = "CN=Configuration," + rootdn
320 schemadn = "CN=Schema," + configdn
325 names = ProvisionNames()
326 names.rootdn = rootdn
327 names.domaindn = domaindn
328 names.configdn = configdn
329 names.schemadn = schemadn
330 names.ldapmanagerdn = "CN=Manager," + rootdn
331 names.dnsdomain = dnsdomain
332 names.domain = domain
334 names.netbiosname = netbiosname
335 names.hostname = hostname
336 names.sitename = sitename
341 def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
344 hostname = socket.gethostname().split(".")[0].lower()
346 if serverrole is None:
347 serverrole = "standalone"
349 assert serverrole in ("domain controller", "member server", "standalone")
350 if serverrole == "domain controller":
352 elif serverrole == "member server":
353 smbconfsuffix = "member"
354 elif serverrole == "standalone":
355 smbconfsuffix = "standalone"
357 assert domain is not None
358 assert realm is not None
360 default_lp = param.LoadParm()
361 #Load non-existant file
362 default_lp.load(smbconf)
364 if targetdir is not None:
365 privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
366 lockdir_line = "lock dir = " + os.path.abspath(targetdir)
368 default_lp.set("lock dir", os.path.abspath(targetdir))
373 sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
374 netlogon = os.path.join(sysvol, realm.lower(), "scripts")
376 setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
378 "HOSTNAME": hostname,
381 "SERVERROLE": serverrole,
382 "NETLOGONPATH": netlogon,
383 "SYSVOLPATH": sysvol,
384 "PRIVATEDIR_LINE": privatedir_line,
385 "LOCKDIR_LINE": lockdir_line
388 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
389 users_gid, wheel_gid):
390 """setup reasonable name mappings for sam names to unix names.
392 :param samdb: SamDB object.
393 :param idmap: IDmap db object.
394 :param sid: The domain sid.
395 :param domaindn: The domain DN.
396 :param root_uid: uid of the UNIX root user.
397 :param nobody_uid: uid of the UNIX nobody user.
398 :param users_gid: gid of the UNIX users group.
399 :param wheel_gid: gid of the UNIX wheel group."""
400 # add some foreign sids if they are not present already
401 samdb.add_foreign(domaindn, "S-1-5-7", "Anonymous")
402 samdb.add_foreign(domaindn, "S-1-1-0", "World")
403 samdb.add_foreign(domaindn, "S-1-5-2", "Network")
404 samdb.add_foreign(domaindn, "S-1-5-18", "System")
405 samdb.add_foreign(domaindn, "S-1-5-11", "Authenticated Users")
407 idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
408 idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
410 idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
411 idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
414 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
416 serverrole, ldap_backend=None,
417 ldap_backend_type=None, erase=False):
418 """Setup the partitions for the SAM database.
420 Alternatively, provision() may call this, and then populate the database.
422 :note: This will wipe the Sam Database!
424 :note: This function always removes the local SAM LDB file. The erase
425 parameter controls whether to erase the existing data, which
426 may not be stored locally but in LDAP.
428 assert session_info is not None
430 samdb = SamDB(samdb_path, session_info=session_info,
431 credentials=credentials, lp=lp)
437 os.unlink(samdb_path)
439 samdb = SamDB(samdb_path, session_info=session_info,
440 credentials=credentials, lp=lp)
442 #Add modules to the list to activate them by default
443 #beware often order is important
445 # Some Known ordering constraints:
446 # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
447 # - objectclass must be before password_hash, because password_hash checks
448 # that the objectclass is of type person (filled in by objectclass
449 # module when expanding the objectclass list)
450 # - partition must be last
451 # - each partition has its own module list then
452 modules_list = ["rootdse",
468 modules_list2 = ["show_deleted",
471 domaindn_ldb = "users.ldb"
472 if ldap_backend is not None:
473 domaindn_ldb = ldap_backend
474 configdn_ldb = "configuration.ldb"
475 if ldap_backend is not None:
476 configdn_ldb = ldap_backend
477 schemadn_ldb = "schema.ldb"
478 if ldap_backend is not None:
479 schema_ldb = ldap_backend
480 schemadn_ldb = ldap_backend
482 if ldap_backend_type == "fedora-ds":
483 backend_modules = ["nsuniqueid", "paged_searches"]
484 # We can handle linked attributes here, as we don't have directory-side subtree operations
485 tdb_modules_list = ["linked_attributes"]
486 elif ldap_backend_type == "openldap":
487 backend_modules = ["normalise", "entryuuid", "paged_searches"]
488 # OpenLDAP handles subtree renames, so we don't want to do any of these things
489 tdb_modules_list = None
490 elif serverrole == "domain controller":
491 backend_modules = ["repl_meta_data"]
493 backend_modules = ["objectguid"]
495 if tdb_modules_list is None:
496 tdb_modules_list_as_string = ""
498 tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
500 samdb.transaction_start()
502 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
503 "SCHEMADN": names.schemadn,
504 "SCHEMADN_LDB": schemadn_ldb,
505 "SCHEMADN_MOD2": ",objectguid",
506 "CONFIGDN": names.configdn,
507 "CONFIGDN_LDB": configdn_ldb,
508 "DOMAINDN": names.domaindn,
509 "DOMAINDN_LDB": domaindn_ldb,
510 "SCHEMADN_MOD": "schema_fsmo,instancetype",
511 "CONFIGDN_MOD": "naming_fsmo,instancetype",
512 "DOMAINDN_MOD": "pdc_fsmo,password_hash,instancetype",
513 "MODULES_LIST": ",".join(modules_list),
514 "TDB_MODULES_LIST": tdb_modules_list_as_string,
515 "MODULES_LIST2": ",".join(modules_list2),
516 "BACKEND_MOD": ",".join(backend_modules),
520 samdb.transaction_cancel()
523 samdb.transaction_commit()
525 samdb = SamDB(samdb_path, session_info=session_info,
526 credentials=credentials, lp=lp)
528 samdb.transaction_start()
530 message("Setting up sam.ldb attributes")
531 samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
533 message("Setting up sam.ldb rootDSE")
534 setup_samdb_rootdse(samdb, setup_path, names.schemadn, names.domaindn,
535 names.hostname, names.dnsdomain, names.realm,
536 names.rootdn, names.configdn, names.netbiosname,
540 message("Erasing data from partitions")
541 samdb.erase_partitions()
544 samdb.transaction_cancel()
547 samdb.transaction_commit()
552 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain,
553 netbiosname, domainsid, keytab_path, samdb_url,
554 dns_keytab_path, dnspass, machinepass):
555 """Add DC-specific bits to a secrets database.
557 :param secretsdb: Ldb Handle to the secrets database
558 :param setup_path: Setup path function
559 :param machinepass: Machine password
561 setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), {
562 "MACHINEPASS_B64": b64encode(machinepass),
565 "DNSDOMAIN": dnsdomain,
566 "DOMAINSID": str(domainsid),
567 "SECRETS_KEYTAB": keytab_path,
568 "NETBIOSNAME": netbiosname,
569 "SAM_LDB": samdb_url,
570 "DNS_KEYTAB": dns_keytab_path,
571 "DNSPASS_B64": b64encode(dnspass),
575 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
576 """Setup the secrets database.
578 :param path: Path to the secrets database.
579 :param setup_path: Get the path to a setup file.
580 :param session_info: Session info.
581 :param credentials: Credentials
582 :param lp: Loadparm context
583 :return: LDB handle for the created secrets database
585 if os.path.exists(path):
587 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
590 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
591 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
593 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
597 def setup_templatesdb(path, setup_path, session_info, credentials, lp):
598 """Setup the templates database.
600 :param path: Path to the database.
601 :param setup_path: Function for obtaining the path to setup files.
602 :param session_info: Session info
603 :param credentials: Credentials
604 :param lp: Loadparm context
606 templates_ldb = SamDB(path, session_info=session_info,
607 credentials=credentials, lp=lp)
608 templates_ldb.erase()
609 templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif"))
612 def setup_registry(path, setup_path, session_info, credentials, lp):
613 """Setup the registry.
615 :param path: Path to the registry database
616 :param setup_path: Function that returns the path to a setup.
617 :param session_info: Session information
618 :param credentials: Credentials
619 :param lp: Loadparm context
621 reg = registry.Registry()
622 hive = registry.open_ldb(path, session_info=session_info,
623 credentials=credentials, lp_ctx=lp)
624 reg.mount_hive(hive, "HKEY_LOCAL_MACHINE")
625 provision_reg = setup_path("provision.reg")
626 assert os.path.exists(provision_reg)
627 reg.diff_apply(provision_reg)
630 def setup_idmapdb(path, setup_path, session_info, credentials, lp):
631 """Setup the idmap database.
633 :param path: path to the idmap database
634 :param setup_path: Function that returns a path to a setup file
635 :param session_info: Session information
636 :param credentials: Credentials
637 :param lp: Loadparm context
639 if os.path.exists(path):
642 idmap_ldb = IDmapDB(path, session_info=session_info,
643 credentials=credentials, lp=lp)
646 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
650 def setup_samdb_rootdse(samdb, setup_path, schemadn, domaindn, hostname,
651 dnsdomain, realm, rootdn, configdn, netbiosname,
653 """Setup the SamDB rootdse.
655 :param samdb: Sam Database handle
656 :param setup_path: Obtain setup path
658 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
659 "SCHEMADN": schemadn,
660 "NETBIOSNAME": netbiosname,
661 "DNSDOMAIN": dnsdomain,
662 "DEFAULTSITE": sitename,
664 "DNSNAME": "%s.%s" % (hostname, dnsdomain),
665 "DOMAINDN": domaindn,
667 "CONFIGDN": configdn,
668 "VERSION": samba.version(),
672 def setup_self_join(samdb, names,
673 machinepass, dnspass,
674 domainsid, invocationid, setup_path,
676 """Join a host to its own domain."""
677 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
678 "CONFIGDN": names.configdn,
679 "SCHEMADN": names.schemadn,
680 "DOMAINDN": names.domaindn,
681 "INVOCATIONID": invocationid,
682 "NETBIOSNAME": names.netbiosname,
683 "DEFAULTSITE": names.sitename,
684 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
685 "MACHINEPASS_B64": b64encode(machinepass),
686 "DNSPASS_B64": b64encode(dnspass),
687 "REALM": names.realm,
688 "DOMAIN": names.domain,
689 "DNSDOMAIN": names.dnsdomain})
690 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
691 "POLICYGUID": policyguid,
692 "DNSDOMAIN": names.dnsdomain,
693 "DOMAINSID": str(domainsid),
694 "DOMAINDN": names.domaindn})
697 def setup_samdb(path, setup_path, session_info, credentials, lp,
699 domainsid, aci, domainguid, policyguid,
700 fill, adminpass, krbtgtpass,
701 machinepass, invocationid, dnspass,
702 serverrole, ldap_backend=None,
703 ldap_backend_type=None):
704 """Setup a complete SAM Database.
706 :note: This will wipe the main SAM database file!
709 erase = (fill != FILL_DRS)
711 # Also wipes the database
712 setup_samdb_partitions(path, setup_path, message=message, lp=lp,
713 credentials=credentials, session_info=session_info,
715 ldap_backend=ldap_backend, serverrole=serverrole,
716 ldap_backend_type=ldap_backend_type, erase=erase)
718 samdb = SamDB(path, session_info=session_info,
719 credentials=credentials, lp=lp)
722 # We want to finish here, but setup the index before we do so
723 message("Setting up sam.ldb index")
724 samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
727 message("Pre-loading the Samba 4 and AD schema")
728 samdb = SamDB(path, session_info=session_info,
729 credentials=credentials, lp=lp)
730 samdb.set_domain_sid(domainsid)
731 if serverrole == "domain controller":
732 samdb.set_invocation_id(invocationid)
734 load_schema(setup_path, samdb, names.schemadn, names.netbiosname,
735 names.configdn, names.sitename)
737 samdb.transaction_start()
740 message("Adding DomainDN: %s (permitted to fail)" % names.domaindn)
741 if serverrole == "domain controller":
742 domain_oc = "domainDNS"
744 domain_oc = "samba4LocalDomain"
746 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
747 "DOMAINDN": names.domaindn,
749 "DOMAIN_OC": domain_oc
752 message("Modifying DomainDN: " + names.domaindn + "")
753 if domainguid is not None:
754 domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
758 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
759 "LDAPTIME": timestring(int(time.time())),
760 "DOMAINSID": str(domainsid),
761 "SCHEMADN": names.schemadn,
762 "NETBIOSNAME": names.netbiosname,
763 "DEFAULTSITE": names.sitename,
764 "CONFIGDN": names.configdn,
765 "POLICYGUID": policyguid,
766 "DOMAINDN": names.domaindn,
767 "DOMAINGUID_MOD": domainguid_mod,
770 message("Adding configuration container (permitted to fail)")
771 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
772 "CONFIGDN": names.configdn,
774 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb",
776 message("Modifying configuration container")
777 setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
778 "CONFIGDN": names.configdn,
779 "SCHEMADN": names.schemadn,
782 message("Adding schema container (permitted to fail)")
783 setup_add_ldif(samdb, setup_path("provision_schema_basedn.ldif"), {
784 "SCHEMADN": names.schemadn,
786 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
788 message("Modifying schema container")
789 setup_modify_ldif(samdb,
790 setup_path("provision_schema_basedn_modify.ldif"), {
791 "SCHEMADN": names.schemadn,
792 "NETBIOSNAME": names.netbiosname,
793 "DEFAULTSITE": names.sitename,
794 "CONFIGDN": names.configdn,
797 message("Setting up sam.ldb Samba4 schema")
798 setup_add_ldif(samdb, setup_path("schema_samba4.ldif"),
799 {"SCHEMADN": names.schemadn })
800 message("Setting up sam.ldb AD schema")
801 setup_add_ldif(samdb, setup_path("schema.ldif"),
802 {"SCHEMADN": names.schemadn})
804 message("Setting up sam.ldb configuration data")
805 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
806 "CONFIGDN": names.configdn,
807 "NETBIOSNAME": names.netbiosname,
808 "DEFAULTSITE": names.sitename,
809 "DNSDOMAIN": names.dnsdomain,
810 "DOMAIN": names.domain,
811 "SCHEMADN": names.schemadn,
812 "DOMAINDN": names.domaindn,
815 message("Setting up display specifiers")
816 setup_add_ldif(samdb, setup_path("display_specifiers.ldif"),
817 {"CONFIGDN": names.configdn})
819 message("Adding users container (permitted to fail)")
820 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
821 "DOMAINDN": names.domaindn})
822 message("Modifying users container")
823 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
824 "DOMAINDN": names.domaindn})
825 message("Adding computers container (permitted to fail)")
826 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
827 "DOMAINDN": names.domaindn})
828 message("Modifying computers container")
829 setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
830 "DOMAINDN": names.domaindn})
831 message("Setting up sam.ldb data")
832 setup_add_ldif(samdb, setup_path("provision.ldif"), {
833 "DOMAINDN": names.domaindn,
834 "NETBIOSNAME": names.netbiosname,
835 "DEFAULTSITE": names.sitename,
836 "CONFIGDN": names.configdn,
839 if fill == FILL_FULL:
840 message("Setting up sam.ldb users and groups")
841 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
842 "DOMAINDN": names.domaindn,
843 "DOMAINSID": str(domainsid),
844 "CONFIGDN": names.configdn,
845 "ADMINPASS_B64": b64encode(adminpass),
846 "KRBTGTPASS_B64": b64encode(krbtgtpass),
849 if serverrole == "domain controller":
850 message("Setting up self join")
851 setup_self_join(samdb, names=names, invocationid=invocationid,
853 machinepass=machinepass,
854 domainsid=domainsid, policyguid=policyguid,
855 setup_path=setup_path)
857 #We want to setup the index last, as adds are faster unindexed
858 message("Setting up sam.ldb index")
859 samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
861 samdb.transaction_cancel()
864 samdb.transaction_commit()
869 FILL_NT4SYNC = "NT4SYNC"
872 def provision(setup_dir, message, session_info,
873 credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL,
874 realm=None, rootdn=None, domaindn=None, schemadn=None,
875 configdn=None, domain=None, hostname=None, hostip=None,
876 hostip6=None, domainsid=None, adminpass=None, krbtgtpass=None,
877 domainguid=None, policyguid=None, invocationid=None,
878 machinepass=None, dnspass=None, root=None, nobody=None,
879 nogroup=None, users=None, wheel=None, backup=None, aci=None,
880 serverrole=None, ldap_backend=None, ldap_backend_type=None,
884 :note: caution, this wipes all existing data!
887 def setup_path(file):
888 return os.path.join(setup_dir, file)
890 if domainsid is None:
891 domainsid = security.random_sid()
893 domainsid = security.Sid(domainsid)
895 if policyguid is None:
896 policyguid = uuid.random()
897 if adminpass is None:
898 adminpass = misc.random_password(12)
899 if krbtgtpass is None:
900 krbtgtpass = misc.random_password(12)
901 if machinepass is None:
902 machinepass = misc.random_password(12)
904 dnspass = misc.random_password(12)
905 root_uid = findnss(pwd.getpwnam, [root or "root"])[2]
906 nobody_uid = findnss(pwd.getpwnam, [nobody or "nobody"])[2]
907 users_gid = findnss(grp.getgrnam, [users or "users"])[2]
909 wheel_gid = findnss(grp.getgrnam, ["wheel", "adm"])[2]
911 wheel_gid = findnss(grp.getgrnam, [wheel])[2]
913 aci = "# no aci for local ldb"
916 os.makedirs(os.path.join(targetdir, "etc"))
917 smbconf = os.path.join(targetdir, "etc", "smb.conf")
919 # only install a new smb.conf if there isn't one there already
920 if not os.path.exists(smbconf):
921 make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
924 lp = param.LoadParm()
927 names = guess_names(lp=lp, hostname=hostname, domain=domain,
928 dnsdomain=realm, serverrole=serverrole, sitename=sitename,
929 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn)
931 paths = provision_paths_from_lp(lp, names.dnsdomain)
934 hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
938 hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
939 except socket.gaierror: pass
941 if serverrole is None:
942 serverrole = lp.get("server role")
944 assert serverrole in ("domain controller", "member server", "standalone")
945 if invocationid is None and serverrole == "domain controller":
946 invocationid = uuid.random()
948 if not os.path.exists(paths.private_dir):
949 os.mkdir(paths.private_dir)
951 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
953 if ldap_backend is not None:
954 if ldap_backend == "ldapi":
955 # provision-backend will set this path suggested slapd command line / fedorads.inf
956 ldap_backend = "ldapi://" % urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
958 # only install a new shares config db if there is none
959 if not os.path.exists(paths.shareconf):
960 message("Setting up share.ldb")
961 share_ldb = Ldb(paths.shareconf, session_info=session_info,
962 credentials=credentials, lp=lp)
963 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
966 message("Setting up secrets.ldb")
967 secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
968 session_info=session_info,
969 credentials=credentials, lp=lp)
971 message("Setting up the registry")
972 setup_registry(paths.hklm, setup_path, session_info,
973 credentials=credentials, lp=lp)
975 message("Setting up templates db")
976 setup_templatesdb(paths.templates, setup_path, session_info=session_info,
977 credentials=credentials, lp=lp)
979 message("Setting up idmap db")
980 idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
981 credentials=credentials, lp=lp)
983 samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info,
984 credentials=credentials, lp=lp, names=names,
987 aci=aci, domainguid=domainguid, policyguid=policyguid,
989 adminpass=adminpass, krbtgtpass=krbtgtpass,
990 invocationid=invocationid,
991 machinepass=machinepass, dnspass=dnspass,
992 serverrole=serverrole, ldap_backend=ldap_backend,
993 ldap_backend_type=ldap_backend_type)
995 if lp.get("server role") == "domain controller":
996 policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
997 "{" + policyguid + "}")
998 os.makedirs(policy_path, 0755)
999 os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1000 os.makedirs(os.path.join(policy_path, "User"), 0755)
1001 if not os.path.isdir(paths.netlogon):
1002 os.makedirs(paths.netlogon, 0755)
1003 secrets_ldb = Ldb(paths.secrets, session_info=session_info,
1004 credentials=credentials, lp=lp)
1005 secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1006 netbiosname=names.netbiosname, domainsid=domainsid,
1007 keytab_path=paths.keytab, samdb_url=paths.samdb,
1008 dns_keytab_path=paths.dns_keytab, dnspass=dnspass,
1009 machinepass=machinepass, dnsdomain=names.dnsdomain)
1011 if samdb_fill == FILL_FULL:
1012 setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1013 root_uid=root_uid, nobody_uid=nobody_uid,
1014 users_gid=users_gid, wheel_gid=wheel_gid)
1016 message("Setting up sam.ldb rootDSE marking as synchronized")
1017 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1019 # Only make a zone file on the first DC, it should be replicated with DNS replication
1020 if serverrole == "domain controller":
1021 samdb = SamDB(paths.samdb, session_info=session_info,
1022 credentials=credentials, lp=lp)
1024 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1025 assert isinstance(domainguid, str)
1026 hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1027 expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1028 scope=SCOPE_SUBTREE)
1029 assert isinstance(hostguid, str)
1031 create_zone_file(paths.dns, setup_path, samdb,
1032 hostname=names.hostname, hostip=hostip,
1033 hostip6=hostip6, dnsdomain=names.dnsdomain,
1034 domaindn=names.domaindn, dnspass=dnspass, realm=names.realm,
1035 domainguid=domainguid, hostguid=hostguid)
1036 message("Please install the zone located in %s into your DNS server" % paths.dns)
1038 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1041 message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1043 message("Once the above files are installed, your server will be ready to use")
1044 message("Server Type: %s" % serverrole)
1045 message("Hostname: %s" % names.hostname)
1046 message("NetBIOS Domain: %s" % names.domain)
1047 message("DNS Domain: %s" % names.dnsdomain)
1048 message("DOMAIN SID: %s" % str(domainsid))
1049 message("Admin password: %s" % adminpass)
1051 result = ProvisionResult()
1052 result.domaindn = domaindn
1053 result.paths = paths
1055 result.samdb = samdb
1058 def provision_become_dc(setup_dir=None,
1059 smbconf=None, targetdir=None, realm=None,
1060 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1061 domain=None, hostname=None, domainsid=None,
1062 adminpass=None, krbtgtpass=None, domainguid=None,
1063 policyguid=None, invocationid=None, machinepass=None,
1064 dnspass=None, root=None, nobody=None, nogroup=None, users=None,
1065 wheel=None, backup=None, aci=None, serverrole=None,
1066 ldap_backend=None, ldap_backend_type=None, sitename=DEFAULTSITE):
1069 """print a message if quiet is not set."""
1072 provision(setup_dir, message, system_session(), None,
1073 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm,
1074 rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn,
1075 domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename);
1078 def setup_db_config(setup_path, file, dbdir):
1079 if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1080 os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700);
1081 if not os.path.isdir(os.path.join(dbdir, "tmp")):
1082 os.makedirs(os.path.join(dbdir, "tmp"), 0700);
1084 setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1085 {"LDAPDBDIR": dbdir})
1089 def provision_backend(setup_dir=None, message=None,
1090 smbconf=None, targetdir=None, realm=None,
1091 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1092 domain=None, hostname=None, adminpass=None, root=None, serverrole=None,
1093 ldap_backend_type=None):
1095 def setup_path(file):
1096 return os.path.join(setup_dir, file)
1098 if hostname is None:
1099 hostname = socket.gethostname().split(".")[0].lower()
1102 root = findnss(pwd.getpwnam, ["root"])[0]
1105 os.makedirs(os.path.join(targetdir, "etc"))
1106 smbconf = os.path.join(targetdir, "etc", "smb.conf")
1108 # only install a new smb.conf if there isn't one there already
1109 if not os.path.exists(smbconf):
1110 make_smbconf(smbconf, setup_path, hostname, domain, realm,
1111 serverrole, targetdir)
1113 lp = param.LoadParm()
1116 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1117 dnsdomain=realm, serverrole=serverrole,
1118 rootdn=rootdn, domaindn=domaindn, configdn=configdn,
1121 paths = provision_paths_from_lp(lp, names.dnsdomain)
1123 if not os.path.isdir(paths.ldapdir):
1124 os.makedirs(paths.ldapdir)
1125 schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1127 os.unlink(schemadb_path)
1131 schemadb = Ldb(schemadb_path, lp=lp)
1133 setup_add_ldif(schemadb, setup_path("provision_schema_basedn.ldif"),
1134 {"SCHEMADN": names.schemadn,
1136 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
1138 setup_modify_ldif(schemadb,
1139 setup_path("provision_schema_basedn_modify.ldif"), \
1140 {"SCHEMADN": names.schemadn,
1141 "NETBIOSNAME": names.netbiosname,
1142 "DEFAULTSITE": DEFAULTSITE,
1143 "CONFIGDN": names.configdn,
1146 setup_add_ldif(schemadb, setup_path("schema_samba4.ldif"),
1147 {"SCHEMADN": names.schemadn })
1148 setup_add_ldif(schemadb, setup_path("schema.ldif"),
1149 {"SCHEMADN": names.schemadn})
1151 if ldap_backend_type == "fedora-ds":
1152 setup_file(setup_path("fedora-ds.inf"), paths.fedoradsinf,
1154 "HOSTNAME": hostname,
1155 "DNSDOMAIN": names.dnsdomain,
1156 "LDAPDIR": paths.ldapdir,
1157 "DOMAINDN": names.domaindn,
1158 "LDAPMANAGERDN": names.ldapmanagerdn,
1159 "LDAPMANAGERPASS": adminpass,
1162 setup_file(setup_path("fedora-partitions.ldif"), paths.fedoradspartitions,
1163 {"CONFIGDN": names.configdn,
1164 "SCHEMADN": names.schemadn,
1167 setup_file(setup_path("fedora-partitions.ldif"), paths.fedoradspartitions,
1168 {"CONFIGDN": names.configdn,
1169 "SCHEMADN": names.schemadn,
1171 mapping = "schema-map-fedora-ds-1.0"
1172 backend_schema = "99_ad.ldif"
1173 elif ldap_backend_type == "openldap":
1174 attrs = ["linkID", "lDAPDisplayName"]
1175 res = schemadb.search(expression="(&(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1)))(objectclass=attributeSchema))", base=names.schemadn, scope=SCOPE_SUBTREE, attrs=attrs);
1177 memberof_config = "# Generated from schema in " + schemadb_path + "\n";
1178 refint_attributes = "";
1179 for i in range (0, len(res)):
1180 linkid = res[i]["linkID"][0]
1181 linkid = str(int(linkid) + 1)
1182 expression = "(&(objectclass=attributeSchema)(linkID=" + (linkid) + "))"
1183 target = schemadb.searchone(basedn=names.schemadn,
1184 expression=expression,
1185 attribute="lDAPDisplayName",
1186 scope=SCOPE_SUBTREE);
1187 if target is not None:
1188 refint_attributes = refint_attributes + " " + target + " " + res[i]["lDAPDisplayName"][0];
1189 memberof_config = memberof_config + """overlay memberof
1190 memberof-dangling error
1191 memberof-refint TRUE
1192 memberof-group-oc top
1193 memberof-member-ad """ + res[i]["lDAPDisplayName"][0] + """
1194 memberof-memberof-ad """ + target + """
1195 memberof-dangling-error 32
1199 memberof_config = memberof_config + """
1201 refint_attributes""" + refint_attributes + "\n";
1203 setup_file(setup_path("slapd.conf"), paths.slapdconf,
1204 {"DNSDOMAIN": names.dnsdomain,
1205 "LDAPDIR": paths.ldapdir,
1206 "DOMAINDN": names.domaindn,
1207 "CONFIGDN": names.configdn,
1208 "SCHEMADN": names.schemadn,
1209 "LDAPMANAGERDN": names.ldapmanagerdn,
1210 "LDAPMANAGERPASS": adminpass,
1211 "MEMBEROF_CONFIG": memberof_config})
1212 setup_file(setup_path("modules.conf"), paths.modulesconf,
1213 {"REALM": names.realm})
1215 setup_db_config(setup_path, file, os.path.join(paths.ldapdir, "db", "user"))
1216 setup_db_config(setup_path, file, os.path.join(paths.ldapdir, "db", "config"))
1217 setup_db_config(setup_path, file, os.path.join(paths.ldapdir, "db", "schema"))
1218 mapping = "schema-map-openldap-2.3"
1219 backend_schema = "backend-schema.schema"
1222 ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
1223 message("Start slapd with: slapd -f " + paths.ldapdir + "/slapd.conf -h " + ldapi_uri)
1226 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);
1228 os.system(schema_command)
1232 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1233 """Create a PHP LDAP admin configuration file.
1235 :param path: Path to write the configuration to.
1236 :param setup_path: Function to generate setup paths.
1238 setup_file(setup_path("phpldapadmin-config.php"), path,
1239 {"S4_LDAPI_URI": ldapi_uri})
1242 def create_zone_file(path, setup_path, samdb, dnsdomain, domaindn,
1243 hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid):
1244 """Write out a DNS zone file, from the info in the current database.
1246 :param path: Path of the new file.
1247 :param setup_path": Setup path function.
1248 :param samdb: SamDB object
1249 :param dnsdomain: DNS Domain name
1250 :param domaindn: DN of the Domain
1251 :param hostip: Local IPv4 IP
1252 :param hostip6: Local IPv6 IP
1253 :param hostname: Local hostname
1254 :param dnspass: Password for DNS
1255 :param realm: Realm name
1256 :param domainguid: GUID of the domain.
1257 :param hostguid: GUID of the host.
1259 assert isinstance(domainguid, str)
1261 hostip6_base_line = ""
1262 hostip6_host_line = ""
1264 if hostip6 is not None:
1265 hostip6_base_line = " IN AAAA " + hostip6
1266 hostip6_host_line = hostname + " IN AAAA " + hostip6
1268 setup_file(setup_path("provision.zone"), path, {
1269 "DNSPASS_B64": b64encode(dnspass),
1270 "HOSTNAME": hostname,
1271 "DNSDOMAIN": dnsdomain,
1274 "DOMAINGUID": domainguid,
1275 "DATESTRING": time.strftime("%Y%m%d%H"),
1276 "DEFAULTSITE": DEFAULTSITE,
1277 "HOSTGUID": hostguid,
1278 "HOSTIP6_BASE_LINE": hostip6_base_line,
1279 "HOSTIP6_HOST_LINE": hostip6_host_line,
1282 def load_schema(setup_path, samdb, schemadn, netbiosname, configdn, sitename):
1283 """Load schema for the SamDB.
1285 :param samdb: Load a schema into a SamDB.
1286 :param setup_path: Setup path function.
1287 :param schemadn: DN of the schema
1288 :param netbiosname: NetBIOS name of the host.
1289 :param configdn: DN of the configuration
1291 schema_data = open(setup_path("schema.ldif"), 'r').read()
1292 schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
1293 schema_data = substitute_var(schema_data, {"SCHEMADN": schemadn})
1294 head_data = open(setup_path("provision_schema_basedn_modify.ldif"), 'r').read()
1295 head_data = substitute_var(head_data, {
1296 "SCHEMADN": schemadn,
1297 "NETBIOSNAME": netbiosname,
1298 "CONFIGDN": configdn,
1299 "DEFAULTSITE":sitename
1301 samdb.attach_schema_from_ldif(head_data, schema_data)