2 # Unix SMB/CIFS implementation.
3 # backend code for provisioning a Samba4 server
5 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
6 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008
8 # Based on the original in EJS:
9 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 3 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 from base64 import b64encode
35 from auth import system_session
36 from samba import Ldb, substitute_var, valid_netbios_name, check_all_substituted
37 from samba.samdb import SamDB
38 from samba.idmap import IDmapDB
41 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, \
42 LDB_ERR_NO_SUCH_OBJECT, timestring, CHANGETYPE_MODIFY, CHANGETYPE_NONE
44 """Functions for setting up a Samba configuration."""
46 DEFAULTSITE = "Default-First-Site-Name"
48 class InvalidNetbiosName(Exception):
49 def __init__(self, name):
50 super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)
66 self.dns_keytab = None
69 self.private_dir = None
72 self.modulesconf = None
73 self.memberofconf = None
74 self.fedoradsinf = None
75 self.fedoradspartitions = None
83 self.ldapmanagerdn = None
86 self.netbiosname = None
91 class ProvisionResult:
98 def check_install(lp, session_info, credentials):
99 """Check whether the current install seems ok.
101 :param lp: Loadparm context
102 :param session_info: Session information
103 :param credentials: Credentials
105 if lp.get("realm") == "":
106 raise Error("Realm empty")
107 ldb = Ldb(lp.get("sam database"), session_info=session_info,
108 credentials=credentials, lp=lp)
109 if len(ldb.search("(cn=Administrator)")) != 1:
110 raise "No administrator account found"
113 def findnss(nssfn, names):
114 """Find a user or group from a list of possibilities.
116 :param nssfn: NSS Function to try (should raise KeyError if not found)
117 :param names: Names to check.
118 :return: Value return by first names list.
125 raise KeyError("Unable to find user/group %r" % names)
128 def open_ldb(session_info, credentials, lp, dbname):
129 """Open a LDB, thrashing it if it is corrupt.
131 :param session_info: auth session information
132 :param credentials: credentials
133 :param lp: Loadparm context
134 :param dbname: Path of the database to open.
135 :return: a Ldb object
137 assert session_info is not None
139 return Ldb(dbname, session_info=session_info, credentials=credentials,
144 return Ldb(dbname, session_info=session_info, credentials=credentials,
148 def setup_add_ldif(ldb, ldif_path, subst_vars=None):
149 """Setup a ldb in the private dir.
151 :param ldb: LDB file to import data into
152 :param ldif_path: Path of the LDIF file to load
153 :param subst_vars: Optional variables to subsitute in LDIF.
155 assert isinstance(ldif_path, str)
157 data = open(ldif_path, 'r').read()
158 if subst_vars is not None:
159 data = substitute_var(data, subst_vars)
161 check_all_substituted(data)
166 def setup_modify_ldif(ldb, ldif_path, substvars=None):
167 """Modify a ldb in the private dir.
169 :param ldb: LDB object.
170 :param ldif_path: LDIF file path.
171 :param substvars: Optional dictionary with substitution variables.
173 data = open(ldif_path, 'r').read()
174 if substvars is not None:
175 data = substitute_var(data, substvars)
177 check_all_substituted(data)
179 ldb.modify_ldif(data)
182 def setup_ldb(ldb, ldif_path, subst_vars):
183 """Import a LDIF a file into a LDB handle, optionally substituting variables.
185 :note: Either all LDIF data will be added or none (using transactions).
187 :param ldb: LDB file to import into.
188 :param ldif_path: Path to the LDIF file.
189 :param subst_vars: Dictionary with substitution variables.
191 assert ldb is not None
192 ldb.transaction_start()
194 setup_add_ldif(ldb, ldif_path, subst_vars)
196 ldb.transaction_cancel()
198 ldb.transaction_commit()
201 def setup_file(template, fname, substvars):
202 """Setup a file in the private dir.
204 :param template: Path of the template file.
205 :param fname: Path of the file to create.
206 :param substvars: Substitution variables.
210 if os.path.exists(f):
213 data = open(template, 'r').read()
215 data = substitute_var(data, substvars)
216 check_all_substituted(data)
218 open(f, 'w').write(data)
221 def provision_paths_from_lp(lp, dnsdomain):
222 """Set the default paths for provisioning.
224 :param lp: Loadparm context.
225 :param dnsdomain: DNS Domain name
227 paths = ProvisionPaths()
228 paths.private_dir = lp.get("private dir")
229 paths.keytab = "secrets.keytab"
230 paths.dns_keytab = "dns.keytab"
232 paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
233 paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb")
234 paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb")
235 paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
236 paths.templates = os.path.join(paths.private_dir, "templates.ldb")
237 paths.dns = os.path.join(paths.private_dir, dnsdomain + ".zone")
238 paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
239 paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
240 paths.phpldapadminconfig = os.path.join(paths.private_dir,
241 "phpldapadmin-config.php")
242 paths.ldapdir = os.path.join(paths.private_dir,
244 paths.slapdconf = os.path.join(paths.ldapdir,
246 paths.modulesconf = os.path.join(paths.ldapdir,
248 paths.memberofconf = os.path.join(paths.ldapdir,
250 paths.fedoradsinf = os.path.join(paths.ldapdir,
252 paths.fedoradspartitions = os.path.join(paths.ldapdir,
253 "fedorads-partitions.ldif")
254 paths.hklm = "hklm.ldb"
255 paths.hkcr = "hkcr.ldb"
256 paths.hkcu = "hkcu.ldb"
257 paths.hku = "hku.ldb"
258 paths.hkpd = "hkpd.ldb"
259 paths.hkpt = "hkpt.ldb"
261 paths.sysvol = lp.get("path", "sysvol")
263 paths.netlogon = lp.get("path", "netlogon")
268 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, serverrole=None,
269 rootdn=None, domaindn=None, configdn=None, schemadn=None, serverdn=None,
271 """Guess configuration settings to use."""
274 hostname = socket.gethostname().split(".")[0].lower()
276 netbiosname = hostname.upper()
277 if not valid_netbios_name(netbiosname):
278 raise InvalidNetbiosName(netbiosname)
280 hostname = hostname.lower()
282 if dnsdomain is None:
283 dnsdomain = lp.get("realm")
285 if serverrole is None:
286 serverrole = lp.get("server role")
288 assert dnsdomain is not None
289 realm = dnsdomain.upper()
291 if lp.get("realm").upper() != realm:
292 raise Exception("realm '%s' in %s must match chosen realm '%s'" %
293 (lp.get("realm"), lp.configfile(), realm))
295 dnsdomain = dnsdomain.lower()
297 if (serverrole == "domain controller"):
299 domain = lp.get("workgroup")
301 domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
302 if lp.get("workgroup").upper() != domain.upper():
303 raise Error("workgroup '%s' in smb.conf must match chosen domain '%s'",
304 lp.get("workgroup"), domain)
308 domaindn = "CN=" + netbiosname
310 assert domain is not None
311 domain = domain.upper()
312 if not valid_netbios_name(domain):
313 raise InvalidNetbiosName(domain)
319 configdn = "CN=Configuration," + rootdn
321 schemadn = "CN=Schema," + configdn
326 names = ProvisionNames()
327 names.rootdn = rootdn
328 names.domaindn = domaindn
329 names.configdn = configdn
330 names.schemadn = schemadn
331 names.ldapmanagerdn = "CN=Manager," + rootdn
332 names.dnsdomain = dnsdomain
333 names.domain = domain
335 names.netbiosname = netbiosname
336 names.hostname = hostname
337 names.sitename = sitename
338 names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (netbiosname, sitename, configdn)
343 def load_or_make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, targetdir):
344 if targetdir is not None:
345 if not os.path.exists(targetdir):
347 if not os.path.exists(os.path.join(targetdir, "etc")):
348 os.mkdir(os.path.join(targetdir, "etc"))
350 smbconf = os.path.join(targetdir, "etc", "smb.conf")
352 # only install a new smb.conf if there isn't one there already
354 if not os.path.exists(smbconf):
356 hostname = socket.gethostname().split(".")[0].lower()
358 if serverrole is None:
359 serverrole = "standalone"
361 assert serverrole in ("domain controller", "member server", "standalone")
362 if serverrole == "domain controller":
364 elif serverrole == "member server":
365 smbconfsuffix = "member"
366 elif serverrole == "standalone":
367 smbconfsuffix = "standalone"
369 assert domain is not None
370 assert realm is not None
372 default_lp = param.LoadParm()
373 #Load non-existant file
374 default_lp.load(smbconf)
376 if targetdir is not None:
377 privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
378 lockdir_line = "lock dir = " + os.path.abspath(targetdir)
380 default_lp.set("lock dir", os.path.abspath(targetdir))
385 sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
386 netlogon = os.path.join(sysvol, realm.lower(), "scripts")
388 setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
390 "HOSTNAME": hostname,
393 "SERVERROLE": serverrole,
394 "NETLOGONPATH": netlogon,
395 "SYSVOLPATH": sysvol,
396 "PRIVATEDIR_LINE": privatedir_line,
397 "LOCKDIR_LINE": lockdir_line
400 lp = param.LoadParm()
406 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
407 users_gid, wheel_gid):
408 """setup reasonable name mappings for sam names to unix names.
410 :param samdb: SamDB object.
411 :param idmap: IDmap db object.
412 :param sid: The domain sid.
413 :param domaindn: The domain DN.
414 :param root_uid: uid of the UNIX root user.
415 :param nobody_uid: uid of the UNIX nobody user.
416 :param users_gid: gid of the UNIX users group.
417 :param wheel_gid: gid of the UNIX wheel group."""
418 # add some foreign sids if they are not present already
419 samdb.add_foreign(domaindn, "S-1-5-7", "Anonymous")
420 samdb.add_foreign(domaindn, "S-1-1-0", "World")
421 samdb.add_foreign(domaindn, "S-1-5-2", "Network")
422 samdb.add_foreign(domaindn, "S-1-5-18", "System")
423 samdb.add_foreign(domaindn, "S-1-5-11", "Authenticated Users")
425 idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
426 idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
428 idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
429 idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
432 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
434 serverrole, ldap_backend=None,
435 ldap_backend_type=None, erase=False):
436 """Setup the partitions for the SAM database.
438 Alternatively, provision() may call this, and then populate the database.
440 :note: This will wipe the Sam Database!
442 :note: This function always removes the local SAM LDB file. The erase
443 parameter controls whether to erase the existing data, which
444 may not be stored locally but in LDAP.
446 assert session_info is not None
448 samdb = SamDB(samdb_path, session_info=session_info,
449 credentials=credentials, lp=lp)
455 os.unlink(samdb_path)
457 samdb = SamDB(samdb_path, session_info=session_info,
458 credentials=credentials, lp=lp)
460 #Add modules to the list to activate them by default
461 #beware often order is important
463 # Some Known ordering constraints:
464 # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
465 # - objectclass must be before password_hash, because password_hash checks
466 # that the objectclass is of type person (filled in by objectclass
467 # module when expanding the objectclass list)
468 # - partition must be last
469 # - each partition has its own module list then
470 modules_list = ["rootdse",
486 modules_list2 = ["show_deleted",
489 domaindn_ldb = "users.ldb"
490 if ldap_backend is not None:
491 domaindn_ldb = ldap_backend
492 configdn_ldb = "configuration.ldb"
493 if ldap_backend is not None:
494 configdn_ldb = ldap_backend
495 schemadn_ldb = "schema.ldb"
496 if ldap_backend is not None:
497 schema_ldb = ldap_backend
498 schemadn_ldb = ldap_backend
500 if ldap_backend_type == "fedora-ds":
501 backend_modules = ["nsuniqueid", "paged_searches"]
502 # We can handle linked attributes here, as we don't have directory-side subtree operations
503 tdb_modules_list = ["linked_attributes"]
504 elif ldap_backend_type == "openldap":
505 backend_modules = ["normalise", "entryuuid", "paged_searches"]
506 # OpenLDAP handles subtree renames, so we don't want to do any of these things
507 tdb_modules_list = None
508 elif serverrole == "domain controller":
509 backend_modules = ["repl_meta_data"]
511 backend_modules = ["objectguid"]
513 if tdb_modules_list is None:
514 tdb_modules_list_as_string = ""
516 tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
518 samdb.transaction_start()
520 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
521 "SCHEMADN": names.schemadn,
522 "SCHEMADN_LDB": schemadn_ldb,
523 "SCHEMADN_MOD2": ",objectguid",
524 "CONFIGDN": names.configdn,
525 "CONFIGDN_LDB": configdn_ldb,
526 "DOMAINDN": names.domaindn,
527 "DOMAINDN_LDB": domaindn_ldb,
528 "SCHEMADN_MOD": "schema_fsmo,instancetype",
529 "CONFIGDN_MOD": "naming_fsmo,instancetype",
530 "DOMAINDN_MOD": "pdc_fsmo,password_hash,instancetype",
531 "MODULES_LIST": ",".join(modules_list),
532 "TDB_MODULES_LIST": tdb_modules_list_as_string,
533 "MODULES_LIST2": ",".join(modules_list2),
534 "BACKEND_MOD": ",".join(backend_modules),
538 samdb.transaction_cancel()
541 samdb.transaction_commit()
543 samdb = SamDB(samdb_path, session_info=session_info,
544 credentials=credentials, lp=lp)
546 samdb.transaction_start()
548 message("Setting up sam.ldb attributes")
549 samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
551 message("Setting up sam.ldb rootDSE")
552 setup_samdb_rootdse(samdb, setup_path, names)
555 message("Erasing data from partitions")
556 samdb.erase_partitions()
559 samdb.transaction_cancel()
562 samdb.transaction_commit()
567 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain,
568 netbiosname, domainsid, keytab_path, samdb_url,
569 dns_keytab_path, dnspass, machinepass):
570 """Add DC-specific bits to a secrets database.
572 :param secretsdb: Ldb Handle to the secrets database
573 :param setup_path: Setup path function
574 :param machinepass: Machine password
576 setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), {
577 "MACHINEPASS_B64": b64encode(machinepass),
580 "DNSDOMAIN": dnsdomain,
581 "DOMAINSID": str(domainsid),
582 "SECRETS_KEYTAB": keytab_path,
583 "NETBIOSNAME": netbiosname,
584 "SAM_LDB": samdb_url,
585 "DNS_KEYTAB": dns_keytab_path,
586 "DNSPASS_B64": b64encode(dnspass),
590 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
591 """Setup the secrets database.
593 :param path: Path to the secrets database.
594 :param setup_path: Get the path to a setup file.
595 :param session_info: Session info.
596 :param credentials: Credentials
597 :param lp: Loadparm context
598 :return: LDB handle for the created secrets database
600 if os.path.exists(path):
602 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
605 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
606 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
608 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
612 def setup_templatesdb(path, setup_path, session_info, credentials, lp):
613 """Setup the templates database.
615 :param path: Path to the database.
616 :param setup_path: Function for obtaining the path to setup files.
617 :param session_info: Session info
618 :param credentials: Credentials
619 :param lp: Loadparm context
621 templates_ldb = SamDB(path, session_info=session_info,
622 credentials=credentials, lp=lp)
623 templates_ldb.erase()
624 templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif"))
627 def setup_registry(path, setup_path, session_info, credentials, lp):
628 """Setup the registry.
630 :param path: Path to the registry database
631 :param setup_path: Function that returns the path to a setup.
632 :param session_info: Session information
633 :param credentials: Credentials
634 :param lp: Loadparm context
636 reg = registry.Registry()
637 hive = registry.open_ldb(path, session_info=session_info,
638 credentials=credentials, lp_ctx=lp)
639 reg.mount_hive(hive, "HKEY_LOCAL_MACHINE")
640 provision_reg = setup_path("provision.reg")
641 assert os.path.exists(provision_reg)
642 reg.diff_apply(provision_reg)
645 def setup_idmapdb(path, setup_path, session_info, credentials, lp):
646 """Setup the idmap database.
648 :param path: path to the idmap database
649 :param setup_path: Function that returns a path to a setup file
650 :param session_info: Session information
651 :param credentials: Credentials
652 :param lp: Loadparm context
654 if os.path.exists(path):
657 idmap_ldb = IDmapDB(path, session_info=session_info,
658 credentials=credentials, lp=lp)
661 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
665 def setup_samdb_rootdse(samdb, setup_path, names):
666 """Setup the SamDB rootdse.
668 :param samdb: Sam Database handle
669 :param setup_path: Obtain setup path
671 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
672 "SCHEMADN": names.schemadn,
673 "NETBIOSNAME": names.netbiosname,
674 "DNSDOMAIN": names.dnsdomain,
675 "REALM": names.realm,
676 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
677 "DOMAINDN": names.domaindn,
678 "ROOTDN": names.rootdn,
679 "CONFIGDN": names.configdn,
680 "SERVERDN": names.serverdn,
684 def setup_self_join(samdb, names,
685 machinepass, dnspass,
686 domainsid, invocationid, setup_path,
688 """Join a host to its own domain."""
689 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
690 "CONFIGDN": names.configdn,
691 "SCHEMADN": names.schemadn,
692 "DOMAINDN": names.domaindn,
693 "SERVERDN": names.serverdn,
694 "INVOCATIONID": invocationid,
695 "NETBIOSNAME": names.netbiosname,
696 "DEFAULTSITE": names.sitename,
697 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
698 "MACHINEPASS_B64": b64encode(machinepass),
699 "DNSPASS_B64": b64encode(dnspass),
700 "REALM": names.realm,
701 "DOMAIN": names.domain,
702 "DNSDOMAIN": names.dnsdomain})
703 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
704 "POLICYGUID": policyguid,
705 "DNSDOMAIN": names.dnsdomain,
706 "DOMAINSID": str(domainsid),
707 "DOMAINDN": names.domaindn})
710 def setup_samdb(path, setup_path, session_info, credentials, lp,
712 domainsid, aci, domainguid, policyguid,
713 fill, adminpass, krbtgtpass,
714 machinepass, invocationid, dnspass,
715 serverrole, ldap_backend=None,
716 ldap_backend_type=None):
717 """Setup a complete SAM Database.
719 :note: This will wipe the main SAM database file!
722 erase = (fill != FILL_DRS)
724 # Also wipes the database
725 setup_samdb_partitions(path, setup_path, message=message, lp=lp,
726 credentials=credentials, session_info=session_info,
728 ldap_backend=ldap_backend, serverrole=serverrole,
729 ldap_backend_type=ldap_backend_type, erase=erase)
731 samdb = SamDB(path, session_info=session_info,
732 credentials=credentials, lp=lp)
735 # We want to finish here, but setup the index before we do so
736 message("Setting up sam.ldb index")
737 samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
740 message("Pre-loading the Samba 4 and AD schema")
741 samdb = SamDB(path, session_info=session_info,
742 credentials=credentials, lp=lp)
743 samdb.set_domain_sid(domainsid)
744 if serverrole == "domain controller":
745 samdb.set_invocation_id(invocationid)
747 load_schema(setup_path, samdb, names.schemadn, names.netbiosname, names.configdn, names.sitename)
749 samdb.transaction_start()
752 message("Adding DomainDN: %s (permitted to fail)" % names.domaindn)
753 if serverrole == "domain controller":
754 domain_oc = "domainDNS"
756 domain_oc = "samba4LocalDomain"
758 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
759 "DOMAINDN": names.domaindn,
761 "DOMAIN_OC": domain_oc
764 message("Modifying DomainDN: " + names.domaindn + "")
765 if domainguid is not None:
766 domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
770 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
771 "LDAPTIME": timestring(int(time.time())),
772 "DOMAINSID": str(domainsid),
773 "SCHEMADN": names.schemadn,
774 "NETBIOSNAME": names.netbiosname,
775 "DEFAULTSITE": names.sitename,
776 "CONFIGDN": names.configdn,
777 "SERVERDN": names.serverdn,
778 "POLICYGUID": policyguid,
779 "DOMAINDN": names.domaindn,
780 "DOMAINGUID_MOD": domainguid_mod,
783 message("Adding configuration container (permitted to fail)")
784 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
785 "CONFIGDN": names.configdn,
787 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb",
789 message("Modifying configuration container")
790 setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
791 "CONFIGDN": names.configdn,
792 "SCHEMADN": names.schemadn,
795 message("Adding schema container (permitted to fail)")
796 setup_add_ldif(samdb, setup_path("provision_schema_basedn.ldif"), {
797 "SCHEMADN": names.schemadn,
799 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
801 message("Modifying schema container")
802 setup_modify_ldif(samdb,
803 setup_path("provision_schema_basedn_modify.ldif"), {
804 "SCHEMADN": names.schemadn,
805 "NETBIOSNAME": names.netbiosname,
806 "DEFAULTSITE": names.sitename,
807 "CONFIGDN": names.configdn,
808 "SERVERDN": names.serverdn
811 message("Setting up sam.ldb Samba4 schema")
812 setup_add_ldif(samdb, setup_path("schema_samba4.ldif"),
813 {"SCHEMADN": names.schemadn })
814 message("Setting up sam.ldb AD schema")
815 setup_add_ldif(samdb, setup_path("schema.ldif"),
816 {"SCHEMADN": names.schemadn})
818 message("Setting up sam.ldb configuration data")
819 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
820 "CONFIGDN": names.configdn,
821 "NETBIOSNAME": names.netbiosname,
822 "DEFAULTSITE": names.sitename,
823 "DNSDOMAIN": names.dnsdomain,
824 "DOMAIN": names.domain,
825 "SCHEMADN": names.schemadn,
826 "DOMAINDN": names.domaindn,
827 "SERVERDN": names.serverdn
830 message("Setting up display specifiers")
831 setup_add_ldif(samdb, setup_path("display_specifiers.ldif"),
832 {"CONFIGDN": names.configdn})
834 message("Adding users container (permitted to fail)")
835 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
836 "DOMAINDN": names.domaindn})
837 message("Modifying users container")
838 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
839 "DOMAINDN": names.domaindn})
840 message("Adding computers container (permitted to fail)")
841 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
842 "DOMAINDN": names.domaindn})
843 message("Modifying computers container")
844 setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
845 "DOMAINDN": names.domaindn})
846 message("Setting up sam.ldb data")
847 setup_add_ldif(samdb, setup_path("provision.ldif"), {
848 "DOMAINDN": names.domaindn,
849 "NETBIOSNAME": names.netbiosname,
850 "DEFAULTSITE": names.sitename,
851 "CONFIGDN": names.configdn,
852 "SERVERDN": names.serverdn
855 if fill == FILL_FULL:
856 message("Setting up sam.ldb users and groups")
857 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
858 "DOMAINDN": names.domaindn,
859 "DOMAINSID": str(domainsid),
860 "CONFIGDN": names.configdn,
861 "ADMINPASS_B64": b64encode(adminpass),
862 "KRBTGTPASS_B64": b64encode(krbtgtpass),
865 if serverrole == "domain controller":
866 message("Setting up self join")
867 setup_self_join(samdb, names=names, invocationid=invocationid,
869 machinepass=machinepass,
870 domainsid=domainsid, policyguid=policyguid,
871 setup_path=setup_path)
873 #We want to setup the index last, as adds are faster unindexed
874 message("Setting up sam.ldb index")
875 samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
877 samdb.transaction_cancel()
880 samdb.transaction_commit()
885 FILL_NT4SYNC = "NT4SYNC"
888 def provision(setup_dir, message, session_info,
889 credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL, realm=None,
890 rootdn=None, domaindn=None, schemadn=None, configdn=None,
892 domain=None, hostname=None, hostip=None, hostip6=None,
893 domainsid=None, adminpass=None, krbtgtpass=None, domainguid=None,
894 policyguid=None, invocationid=None, machinepass=None,
895 dnspass=None, root=None, nobody=None, nogroup=None, users=None,
896 wheel=None, backup=None, aci=None, serverrole=None,
897 ldap_backend=None, ldap_backend_type=None, sitename=None):
900 :note: caution, this wipes all existing data!
903 def setup_path(file):
904 return os.path.join(setup_dir, file)
906 if domainsid is None:
907 domainsid = security.random_sid()
909 domainsid = security.Sid(domainsid)
911 if policyguid is None:
912 policyguid = uuid.random()
913 if adminpass is None:
914 adminpass = misc.random_password(12)
915 if krbtgtpass is None:
916 krbtgtpass = misc.random_password(12)
917 if machinepass is None:
918 machinepass = misc.random_password(12)
920 dnspass = misc.random_password(12)
922 root_uid = findnss(pwd.getpwnam, ["root"])[2]
924 root_uid = findnss(pwd.getpwnam, [root])[2]
926 nobody_uid = findnss(pwd.getpwnam, ["nobody"])[2]
928 nobody_uid = findnss(pwd.getpwnam, [nobody])[2]
930 users_gid = findnss(grp.getgrnam, ["users"])[2]
932 users_gid = findnss(grp.getgrnam, [users])[2]
934 wheel_gid = findnss(grp.getgrnam, ["wheel", "adm"])[2]
936 wheel_gid = findnss(grp.getgrnam, [wheel])[2]
938 aci = "# no aci for local ldb"
940 lp = load_or_make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, targetdir)
942 names = guess_names(lp=lp, hostname=hostname, domain=domain,
943 dnsdomain=realm, serverrole=serverrole, sitename=sitename,
944 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
947 paths = provision_paths_from_lp(lp, names.dnsdomain)
950 hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
954 hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
955 except socket.gaierror: pass
957 if serverrole is None:
958 serverrole = lp.get("server role")
960 assert serverrole in ("domain controller", "member server", "standalone")
961 if invocationid is None and serverrole == "domain controller":
962 invocationid = uuid.random()
964 if not os.path.exists(paths.private_dir):
965 os.mkdir(paths.private_dir)
967 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
969 if ldap_backend is not None:
970 if ldap_backend == "ldapi":
971 # provision-backend will set this path suggested slapd command line / fedorads.inf
972 ldap_backend = "ldapi://" % urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
974 # only install a new shares config db if there is none
975 if not os.path.exists(paths.shareconf):
976 message("Setting up share.ldb")
977 share_ldb = Ldb(paths.shareconf, session_info=session_info,
978 credentials=credentials, lp=lp)
979 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
982 message("Setting up secrets.ldb")
983 secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
984 session_info=session_info,
985 credentials=credentials, lp=lp)
987 message("Setting up the registry")
988 setup_registry(paths.hklm, setup_path, session_info,
989 credentials=credentials, lp=lp)
991 message("Setting up templates db")
992 setup_templatesdb(paths.templates, setup_path, session_info=session_info,
993 credentials=credentials, lp=lp)
995 message("Setting up idmap db")
996 idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
997 credentials=credentials, lp=lp)
999 samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info,
1000 credentials=credentials, lp=lp, names=names,
1002 domainsid=domainsid,
1003 aci=aci, domainguid=domainguid, policyguid=policyguid,
1005 adminpass=adminpass, krbtgtpass=krbtgtpass,
1006 invocationid=invocationid,
1007 machinepass=machinepass, dnspass=dnspass,
1008 serverrole=serverrole, ldap_backend=ldap_backend,
1009 ldap_backend_type=ldap_backend_type)
1011 if lp.get("server role") == "domain controller":
1012 policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1013 "{" + policyguid + "}")
1014 os.makedirs(policy_path, 0755)
1015 os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1016 os.makedirs(os.path.join(policy_path, "User"), 0755)
1017 if not os.path.isdir(paths.netlogon):
1018 os.makedirs(paths.netlogon, 0755)
1020 if samdb_fill == FILL_FULL:
1021 setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1022 root_uid=root_uid, nobody_uid=nobody_uid,
1023 users_gid=users_gid, wheel_gid=wheel_gid)
1025 message("Setting up sam.ldb rootDSE marking as synchronized")
1026 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1028 # Only make a zone file on the first DC, it should be replicated with DNS replication
1029 if serverrole == "domain controller":
1030 secrets_ldb = Ldb(paths.secrets, session_info=session_info,
1031 credentials=credentials, lp=lp)
1032 secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1033 netbiosname=names.netbiosname, domainsid=domainsid,
1034 keytab_path=paths.keytab, samdb_url=paths.samdb,
1035 dns_keytab_path=paths.dns_keytab, dnspass=dnspass,
1036 machinepass=machinepass, dnsdomain=names.dnsdomain)
1038 samdb = SamDB(paths.samdb, session_info=session_info,
1039 credentials=credentials, lp=lp)
1041 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1042 assert isinstance(domainguid, str)
1043 hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1044 expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1045 scope=SCOPE_SUBTREE)
1046 assert isinstance(hostguid, str)
1048 create_zone_file(paths.dns, setup_path, samdb,
1049 hostname=names.hostname, hostip=hostip,
1050 hostip6=hostip6, dnsdomain=names.dnsdomain,
1051 domaindn=names.domaindn, dnspass=dnspass, realm=names.realm,
1052 domainguid=domainguid, hostguid=hostguid)
1053 message("Please install the zone located in %s into your DNS server" % paths.dns)
1055 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1058 message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1060 message("Once the above files are installed, your Samba4 server will be ready to use")
1061 message("Server Role: %s" % serverrole)
1062 message("Hostname: %s" % names.hostname)
1063 message("NetBIOS Domain: %s" % names.domain)
1064 message("DNS Domain: %s" % names.dnsdomain)
1065 message("DOMAIN SID: %s" % str(domainsid))
1066 message("Admin password: %s" % adminpass)
1068 result = ProvisionResult()
1069 result.domaindn = domaindn
1070 result.paths = paths
1072 result.samdb = samdb
1076 def provision_become_dc(setup_dir=None,
1077 smbconf=None, targetdir=None, realm=None,
1078 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1080 domain=None, hostname=None, domainsid=None,
1081 adminpass=None, krbtgtpass=None, domainguid=None,
1082 policyguid=None, invocationid=None, machinepass=None,
1083 dnspass=None, root=None, nobody=None, nogroup=None, users=None,
1084 wheel=None, backup=None, aci=None, serverrole=None,
1085 ldap_backend=None, ldap_backend_type=None, sitename=None):
1088 """print a message if quiet is not set."""
1091 provision(setup_dir, message, system_session(), None,
1092 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm,
1093 rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn, serverdn=serverdn,
1094 domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename);
1097 def setup_db_config(setup_path, dbdir):
1098 """Setup a Berkeley database.
1100 :param setup_path: Setup path function.
1101 :param dbdir: Database directory."""
1102 if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1103 os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700);
1104 if not os.path.isdir(os.path.join(dbdir, "tmp")):
1105 os.makedirs(os.path.join(dbdir, "tmp"), 0700);
1107 setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1108 {"LDAPDBDIR": dbdir})
1112 def provision_backend(setup_dir=None, message=None,
1113 smbconf=None, targetdir=None, realm=None,
1114 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1115 domain=None, hostname=None, adminpass=None, root=None, serverrole=None,
1116 ldap_backend_type=None, ldap_backend_port=None):
1118 def setup_path(file):
1119 return os.path.join(setup_dir, file)
1121 if hostname is None:
1122 hostname = socket.gethostname().split(".")[0].lower()
1125 root = findnss(pwd.getpwnam, ["root"])[0]
1127 lp = load_or_make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, targetdir)
1129 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1130 dnsdomain=realm, serverrole=serverrole,
1131 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn)
1133 paths = provision_paths_from_lp(lp, names.dnsdomain)
1135 if not os.path.isdir(paths.ldapdir):
1136 os.makedirs(paths.ldapdir)
1137 schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1139 os.unlink(schemadb_path)
1143 schemadb = Ldb(schemadb_path, lp=lp)
1145 setup_add_ldif(schemadb, setup_path("provision_schema_basedn.ldif"),
1146 {"SCHEMADN": names.schemadn,
1148 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
1150 setup_modify_ldif(schemadb,
1151 setup_path("provision_schema_basedn_modify.ldif"), \
1152 {"SCHEMADN": names.schemadn,
1153 "NETBIOSNAME": names.netbiosname,
1154 "DEFAULTSITE": DEFAULTSITE,
1155 "CONFIGDN": names.configdn,
1158 setup_add_ldif(schemadb, setup_path("schema_samba4.ldif"),
1159 {"SCHEMADN": names.schemadn })
1160 setup_add_ldif(schemadb, setup_path("schema.ldif"),
1161 {"SCHEMADN": names.schemadn})
1163 if ldap_backend_type == "fedora-ds":
1164 if ldap_backend_port is not None:
1165 serverport = "ServerPort=%d" % ldap_backend_port
1169 setup_file(setup_path("fedorads.inf"), paths.fedoradsinf,
1171 "HOSTNAME": hostname,
1172 "DNSDOMAIN": names.dnsdomain,
1173 "LDAPDIR": paths.ldapdir,
1174 "DOMAINDN": names.domaindn,
1175 "LDAPMANAGERDN": names.ldapmanagerdn,
1176 "LDAPMANAGERPASS": adminpass,
1177 "SERVERPORT": serverport})
1179 setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions,
1180 {"CONFIGDN": names.configdn,
1181 "SCHEMADN": names.schemadn,
1184 mapping = "schema-map-fedora-ds-1.0"
1185 backend_schema = "99_ad.ldif"
1187 slapdcommand="Initailise Fedora DS with: setup-ds.pl --file=%s" % paths.fedoradsinf
1189 elif ldap_backend_type == "openldap":
1190 attrs = ["linkID", "lDAPDisplayName"]
1191 res = schemadb.search(expression="(&(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1)))(objectclass=attributeSchema))", base=names.schemadn, scope=SCOPE_SUBTREE, attrs=attrs);
1193 memberof_config = "# Generated from schema in " + schemadb_path + "\n";
1194 refint_attributes = "";
1195 for i in range (0, len(res)):
1196 linkid = res[i]["linkID"][0]
1197 linkid = str(int(linkid) + 1)
1198 expression = "(&(objectclass=attributeSchema)(linkID=" + (linkid) + "))"
1199 target = schemadb.searchone(basedn=names.schemadn,
1200 expression=expression,
1201 attribute="lDAPDisplayName",
1202 scope=SCOPE_SUBTREE);
1203 if target is not None:
1204 refint_attributes = refint_attributes + " " + target + " " + res[i]["lDAPDisplayName"][0];
1205 memberof_config = memberof_config + """overlay memberof
1206 memberof-dangling error
1207 memberof-refint TRUE
1208 memberof-group-oc top
1209 memberof-member-ad """ + res[i]["lDAPDisplayName"][0] + """
1210 memberof-memberof-ad """ + target + """
1211 memberof-dangling-error 32
1215 memberof_config = memberof_config + """
1217 refint_attributes""" + refint_attributes + "\n";
1219 setup_file(setup_path("slapd.conf"), paths.slapdconf,
1220 {"DNSDOMAIN": names.dnsdomain,
1221 "LDAPDIR": paths.ldapdir,
1222 "DOMAINDN": names.domaindn,
1223 "CONFIGDN": names.configdn,
1224 "SCHEMADN": names.schemadn,
1225 "LDAPMANAGERDN": names.ldapmanagerdn,
1226 "LDAPMANAGERPASS": adminpass,
1227 "MEMBEROF_CONFIG": memberof_config})
1228 setup_file(setup_path("modules.conf"), paths.modulesconf,
1229 {"REALM": names.realm})
1231 setup_db_config(setup_path, file, os.path.join(paths.ldapdir, "user"))
1232 setup_db_config(setup_path, file, os.path.join(paths.ldapdir, "config"))
1233 setup_db_config(setup_path, file, os.path.join(paths.ldapdir, "schema"))
1234 mapping = "schema-map-openldap-2.3"
1235 backend_schema = "backend-schema.schema"
1237 ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
1238 if ldap_backend_port is not None:
1239 server_port_string = " -h ldap://0.0.0.0:%d" % ldap_backend_port
1241 server_port_string = ""
1242 slapdcommand="Start slapd with: slapd -f " + paths.ldapdir + "/slapd.conf -h " + ldapi_uri + server_port_string
1244 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);
1246 os.system(schema_command)
1249 message("Your %s Backend for Samba4 is now configured, and is ready to be started" % ( ldap_backend_type) )
1250 message("Server Role: %s" % serverrole)
1251 message("Hostname: %s" % names.hostname)
1252 message("DNS Domain: %s" % names.dnsdomain)
1253 message("Base DN: %s" % names.domaindn)
1254 message("LDAP admin DN: %s" % names.ldapmanagerdn)
1255 message("LDAP admin password: %s" % adminpass)
1256 message(slapdcommand)
1259 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1260 """Create a PHP LDAP admin configuration file.
1262 :param path: Path to write the configuration to.
1263 :param setup_path: Function to generate setup paths.
1265 setup_file(setup_path("phpldapadmin-config.php"), path,
1266 {"S4_LDAPI_URI": ldapi_uri})
1269 def create_zone_file(path, setup_path, samdb, dnsdomain, domaindn,
1270 hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid):
1271 """Write out a DNS zone file, from the info in the current database.
1273 :param path: Path of the new file.
1274 :param setup_path": Setup path function.
1275 :param samdb: SamDB object
1276 :param dnsdomain: DNS Domain name
1277 :param domaindn: DN of the Domain
1278 :param hostip: Local IPv4 IP
1279 :param hostip6: Local IPv6 IP
1280 :param hostname: Local hostname
1281 :param dnspass: Password for DNS
1282 :param realm: Realm name
1283 :param domainguid: GUID of the domain.
1284 :param hostguid: GUID of the host.
1286 assert isinstance(domainguid, str)
1288 hostip6_base_line = ""
1289 hostip6_host_line = ""
1291 if hostip6 is not None:
1292 hostip6_base_line = " IN AAAA " + hostip6
1293 hostip6_host_line = hostname + " IN AAAA " + hostip6
1295 setup_file(setup_path("provision.zone"), path, {
1296 "DNSPASS_B64": b64encode(dnspass),
1297 "HOSTNAME": hostname,
1298 "DNSDOMAIN": dnsdomain,
1301 "DOMAINGUID": domainguid,
1302 "DATESTRING": time.strftime("%Y%m%d%H"),
1303 "DEFAULTSITE": DEFAULTSITE,
1304 "HOSTGUID": hostguid,
1305 "HOSTIP6_BASE_LINE": hostip6_base_line,
1306 "HOSTIP6_HOST_LINE": hostip6_host_line,
1309 def load_schema(setup_path, samdb, schemadn, netbiosname, configdn, sitename):
1310 """Load schema for the SamDB.
1312 :param samdb: Load a schema into a SamDB.
1313 :param setup_path: Setup path function.
1314 :param schemadn: DN of the schema
1315 :param netbiosname: NetBIOS name of the host.
1316 :param configdn: DN of the configuration
1318 schema_data = open(setup_path("schema.ldif"), 'r').read()
1319 schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
1320 schema_data = substitute_var(schema_data, {"SCHEMADN": schemadn})
1321 head_data = open(setup_path("provision_schema_basedn_modify.ldif"), 'r').read()
1322 head_data = substitute_var(head_data, {
1323 "SCHEMADN": schemadn,
1324 "NETBIOSNAME": netbiosname,
1325 "CONFIGDN": configdn,
1326 "DEFAULTSITE":sitename
1328 samdb.attach_schema_from_ldif(head_data, schema_data)