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
92 class ProvisionResult:
99 def check_install(lp, session_info, credentials):
100 """Check whether the current install seems ok.
102 :param lp: Loadparm context
103 :param session_info: Session information
104 :param credentials: Credentials
106 if lp.get("realm") == "":
107 raise Error("Realm empty")
108 ldb = Ldb(lp.get("sam database"), session_info=session_info,
109 credentials=credentials, lp=lp)
110 if len(ldb.search("(cn=Administrator)")) != 1:
111 raise "No administrator account found"
114 def findnss(nssfn, names):
115 """Find a user or group from a list of possibilities.
117 :param nssfn: NSS Function to try (should raise KeyError if not found)
118 :param names: Names to check.
119 :return: Value return by first names list.
126 raise KeyError("Unable to find user/group %r" % names)
129 def open_ldb(session_info, credentials, lp, dbname):
130 """Open a LDB, thrashing it if it is corrupt.
132 :param session_info: auth session information
133 :param credentials: credentials
134 :param lp: Loadparm context
135 :param dbname: Path of the database to open.
136 :return: a Ldb object
138 assert session_info is not None
140 return Ldb(dbname, session_info=session_info, credentials=credentials,
145 return Ldb(dbname, session_info=session_info, credentials=credentials,
149 def setup_add_ldif(ldb, ldif_path, subst_vars=None):
150 """Setup a ldb in the private dir.
152 :param ldb: LDB file to import data into
153 :param ldif_path: Path of the LDIF file to load
154 :param subst_vars: Optional variables to subsitute in LDIF.
156 assert isinstance(ldif_path, str)
158 data = open(ldif_path, 'r').read()
159 if subst_vars is not None:
160 data = substitute_var(data, subst_vars)
162 check_all_substituted(data)
167 def setup_modify_ldif(ldb, ldif_path, substvars=None):
168 """Modify a ldb in the private dir.
170 :param ldb: LDB object.
171 :param ldif_path: LDIF file path.
172 :param substvars: Optional dictionary with substitution variables.
174 data = open(ldif_path, 'r').read()
175 if substvars is not None:
176 data = substitute_var(data, substvars)
178 check_all_substituted(data)
180 ldb.modify_ldif(data)
183 def setup_ldb(ldb, ldif_path, subst_vars):
184 """Import a LDIF a file into a LDB handle, optionally substituting variables.
186 :note: Either all LDIF data will be added or none (using transactions).
188 :param ldb: LDB file to import into.
189 :param ldif_path: Path to the LDIF file.
190 :param subst_vars: Dictionary with substitution variables.
192 assert ldb is not None
193 ldb.transaction_start()
195 setup_add_ldif(ldb, ldif_path, subst_vars)
197 ldb.transaction_cancel()
199 ldb.transaction_commit()
202 def setup_file(template, fname, substvars):
203 """Setup a file in the private dir.
205 :param template: Path of the template file.
206 :param fname: Path of the file to create.
207 :param substvars: Substitution variables.
211 if os.path.exists(f):
214 data = open(template, 'r').read()
216 data = substitute_var(data, substvars)
217 check_all_substituted(data)
219 open(f, 'w').write(data)
222 def provision_paths_from_lp(lp, dnsdomain):
223 """Set the default paths for provisioning.
225 :param lp: Loadparm context.
226 :param dnsdomain: DNS Domain name
228 paths = ProvisionPaths()
229 paths.private_dir = lp.get("private dir")
230 paths.keytab = "secrets.keytab"
231 paths.dns_keytab = "dns.keytab"
233 paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
234 paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb")
235 paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb")
236 paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
237 paths.templates = os.path.join(paths.private_dir, "templates.ldb")
238 paths.dns = os.path.join(paths.private_dir, dnsdomain + ".zone")
239 paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
240 paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
241 paths.phpldapadminconfig = os.path.join(paths.private_dir,
242 "phpldapadmin-config.php")
243 paths.ldapdir = os.path.join(paths.private_dir,
245 paths.slapdconf = os.path.join(paths.ldapdir,
247 paths.modulesconf = os.path.join(paths.ldapdir,
249 paths.memberofconf = os.path.join(paths.ldapdir,
251 paths.fedoradsinf = os.path.join(paths.ldapdir,
253 paths.fedoradspartitions = os.path.join(paths.ldapdir,
254 "fedorads-partitions.ldif")
255 paths.hklm = "hklm.ldb"
256 paths.hkcr = "hkcr.ldb"
257 paths.hkcu = "hkcu.ldb"
258 paths.hku = "hku.ldb"
259 paths.hkpd = "hkpd.ldb"
260 paths.hkpt = "hkpt.ldb"
262 paths.sysvol = lp.get("path", "sysvol")
264 paths.netlogon = lp.get("path", "netlogon")
266 paths.smbconf = lp.configfile()
271 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, serverrole=None,
272 rootdn=None, domaindn=None, configdn=None, schemadn=None, serverdn=None,
274 """Guess configuration settings to use."""
277 hostname = socket.gethostname().split(".")[0].lower()
279 netbiosname = hostname.upper()
280 if not valid_netbios_name(netbiosname):
281 raise InvalidNetbiosName(netbiosname)
283 hostname = hostname.lower()
285 if dnsdomain is None:
286 dnsdomain = lp.get("realm")
288 if serverrole is None:
289 serverrole = lp.get("server role")
291 assert dnsdomain is not None
292 realm = dnsdomain.upper()
294 if lp.get("realm").upper() != realm:
295 raise Exception("realm '%s' in %s must match chosen realm '%s'" %
296 (lp.get("realm"), lp.configfile(), realm))
298 dnsdomain = dnsdomain.lower()
300 if (serverrole == "domain controller"):
302 domain = lp.get("workgroup")
304 domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
305 if lp.get("workgroup").upper() != domain.upper():
306 raise Error("workgroup '%s' in smb.conf must match chosen domain '%s'",
307 lp.get("workgroup"), domain)
311 domaindn = "CN=" + netbiosname
313 assert domain is not None
314 domain = domain.upper()
315 if not valid_netbios_name(domain):
316 raise InvalidNetbiosName(domain)
322 configdn = "CN=Configuration," + rootdn
324 schemadn = "CN=Schema," + configdn
329 names = ProvisionNames()
330 names.rootdn = rootdn
331 names.domaindn = domaindn
332 names.configdn = configdn
333 names.schemadn = schemadn
334 names.ldapmanagerdn = "CN=Manager," + rootdn
335 names.dnsdomain = dnsdomain
336 names.domain = domain
338 names.netbiosname = netbiosname
339 names.hostname = hostname
340 names.sitename = sitename
341 names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (netbiosname, sitename, configdn)
346 def load_or_make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, targetdir):
347 if targetdir is not None:
348 if not os.path.exists(targetdir):
350 if not os.path.exists(os.path.join(targetdir, "etc")):
351 os.mkdir(os.path.join(targetdir, "etc"))
353 smbconf = os.path.join(targetdir, "etc", "smb.conf")
355 # only install a new smb.conf if there isn't one there already
357 if not os.path.exists(smbconf):
359 hostname = socket.gethostname().split(".")[0].lower()
361 if serverrole is None:
362 serverrole = "standalone"
364 assert serverrole in ("domain controller", "member server", "standalone")
365 if serverrole == "domain controller":
367 elif serverrole == "member server":
368 smbconfsuffix = "member"
369 elif serverrole == "standalone":
370 smbconfsuffix = "standalone"
372 assert domain is not None
373 assert realm is not None
375 default_lp = param.LoadParm()
376 #Load non-existant file
377 default_lp.load(smbconf)
379 if targetdir is not None:
380 privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
381 lockdir_line = "lock dir = " + os.path.abspath(targetdir)
383 default_lp.set("lock dir", os.path.abspath(targetdir))
388 sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
389 netlogon = os.path.join(sysvol, realm.lower(), "scripts")
391 setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
393 "HOSTNAME": hostname,
396 "SERVERROLE": serverrole,
397 "NETLOGONPATH": netlogon,
398 "SYSVOLPATH": sysvol,
399 "PRIVATEDIR_LINE": privatedir_line,
400 "LOCKDIR_LINE": lockdir_line
403 lp = param.LoadParm()
409 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
410 users_gid, wheel_gid):
411 """setup reasonable name mappings for sam names to unix names.
413 :param samdb: SamDB object.
414 :param idmap: IDmap db object.
415 :param sid: The domain sid.
416 :param domaindn: The domain DN.
417 :param root_uid: uid of the UNIX root user.
418 :param nobody_uid: uid of the UNIX nobody user.
419 :param users_gid: gid of the UNIX users group.
420 :param wheel_gid: gid of the UNIX wheel group."""
421 # add some foreign sids if they are not present already
422 samdb.add_foreign(domaindn, "S-1-5-7", "Anonymous")
423 samdb.add_foreign(domaindn, "S-1-1-0", "World")
424 samdb.add_foreign(domaindn, "S-1-5-2", "Network")
425 samdb.add_foreign(domaindn, "S-1-5-18", "System")
426 samdb.add_foreign(domaindn, "S-1-5-11", "Authenticated Users")
428 idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
429 idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
431 idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
432 idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
435 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
437 serverrole, ldap_backend=None,
438 ldap_backend_type=None, erase=False):
439 """Setup the partitions for the SAM database.
441 Alternatively, provision() may call this, and then populate the database.
443 :note: This will wipe the Sam Database!
445 :note: This function always removes the local SAM LDB file. The erase
446 parameter controls whether to erase the existing data, which
447 may not be stored locally but in LDAP.
449 assert session_info is not None
451 samdb = SamDB(samdb_path, session_info=session_info,
452 credentials=credentials, lp=lp)
458 os.unlink(samdb_path)
460 samdb = SamDB(samdb_path, session_info=session_info,
461 credentials=credentials, lp=lp)
463 #Add modules to the list to activate them by default
464 #beware often order is important
466 # Some Known ordering constraints:
467 # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
468 # - objectclass must be before password_hash, because password_hash checks
469 # that the objectclass is of type person (filled in by objectclass
470 # module when expanding the objectclass list)
471 # - partition must be last
472 # - each partition has its own module list then
473 modules_list = ["rootdse",
489 modules_list2 = ["show_deleted",
492 domaindn_ldb = "users.ldb"
493 if ldap_backend is not None:
494 domaindn_ldb = ldap_backend
495 configdn_ldb = "configuration.ldb"
496 if ldap_backend is not None:
497 configdn_ldb = ldap_backend
498 schemadn_ldb = "schema.ldb"
499 if ldap_backend is not None:
500 schema_ldb = ldap_backend
501 schemadn_ldb = ldap_backend
503 if ldap_backend_type == "fedora-ds":
504 backend_modules = ["nsuniqueid", "paged_searches"]
505 # We can handle linked attributes here, as we don't have directory-side subtree operations
506 tdb_modules_list = ["linked_attributes"]
507 elif ldap_backend_type == "openldap":
508 backend_modules = ["normalise", "entryuuid", "paged_searches"]
509 # OpenLDAP handles subtree renames, so we don't want to do any of these things
510 tdb_modules_list = None
511 elif serverrole == "domain controller":
512 backend_modules = ["repl_meta_data"]
514 backend_modules = ["objectguid"]
516 if tdb_modules_list is None:
517 tdb_modules_list_as_string = ""
519 tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
521 samdb.transaction_start()
523 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
524 "SCHEMADN": names.schemadn,
525 "SCHEMADN_LDB": schemadn_ldb,
526 "SCHEMADN_MOD2": ",objectguid",
527 "CONFIGDN": names.configdn,
528 "CONFIGDN_LDB": configdn_ldb,
529 "DOMAINDN": names.domaindn,
530 "DOMAINDN_LDB": domaindn_ldb,
531 "SCHEMADN_MOD": "schema_fsmo,instancetype",
532 "CONFIGDN_MOD": "naming_fsmo,instancetype",
533 "DOMAINDN_MOD": "pdc_fsmo,password_hash,instancetype",
534 "MODULES_LIST": ",".join(modules_list),
535 "TDB_MODULES_LIST": tdb_modules_list_as_string,
536 "MODULES_LIST2": ",".join(modules_list2),
537 "BACKEND_MOD": ",".join(backend_modules),
541 samdb.transaction_cancel()
544 samdb.transaction_commit()
546 samdb = SamDB(samdb_path, session_info=session_info,
547 credentials=credentials, lp=lp)
549 samdb.transaction_start()
551 message("Setting up sam.ldb attributes")
552 samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
554 message("Setting up sam.ldb rootDSE")
555 setup_samdb_rootdse(samdb, setup_path, names)
558 message("Erasing data from partitions")
559 samdb.erase_partitions()
562 samdb.transaction_cancel()
565 samdb.transaction_commit()
570 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain,
571 netbiosname, domainsid, keytab_path, samdb_url,
572 dns_keytab_path, dnspass, machinepass):
573 """Add DC-specific bits to a secrets database.
575 :param secretsdb: Ldb Handle to the secrets database
576 :param setup_path: Setup path function
577 :param machinepass: Machine password
579 setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), {
580 "MACHINEPASS_B64": b64encode(machinepass),
583 "DNSDOMAIN": dnsdomain,
584 "DOMAINSID": str(domainsid),
585 "SECRETS_KEYTAB": keytab_path,
586 "NETBIOSNAME": netbiosname,
587 "SAM_LDB": samdb_url,
588 "DNS_KEYTAB": dns_keytab_path,
589 "DNSPASS_B64": b64encode(dnspass),
593 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
594 """Setup the secrets database.
596 :param path: Path to the secrets database.
597 :param setup_path: Get the path to a setup file.
598 :param session_info: Session info.
599 :param credentials: Credentials
600 :param lp: Loadparm context
601 :return: LDB handle for the created secrets database
603 if os.path.exists(path):
605 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
608 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
609 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
611 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
615 def setup_templatesdb(path, setup_path, session_info, credentials, lp):
616 """Setup the templates database.
618 :param path: Path to the database.
619 :param setup_path: Function for obtaining the path to setup files.
620 :param session_info: Session info
621 :param credentials: Credentials
622 :param lp: Loadparm context
624 templates_ldb = SamDB(path, session_info=session_info,
625 credentials=credentials, lp=lp)
626 templates_ldb.erase()
627 templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif"))
630 def setup_registry(path, setup_path, session_info, credentials, lp):
631 """Setup the registry.
633 :param path: Path to the registry database
634 :param setup_path: Function that returns the path to a setup.
635 :param session_info: Session information
636 :param credentials: Credentials
637 :param lp: Loadparm context
639 reg = registry.Registry()
640 hive = registry.open_ldb(path, session_info=session_info,
641 credentials=credentials, lp_ctx=lp)
642 reg.mount_hive(hive, "HKEY_LOCAL_MACHINE")
643 provision_reg = setup_path("provision.reg")
644 assert os.path.exists(provision_reg)
645 reg.diff_apply(provision_reg)
648 def setup_idmapdb(path, setup_path, session_info, credentials, lp):
649 """Setup the idmap database.
651 :param path: path to the idmap database
652 :param setup_path: Function that returns a path to a setup file
653 :param session_info: Session information
654 :param credentials: Credentials
655 :param lp: Loadparm context
657 if os.path.exists(path):
660 idmap_ldb = IDmapDB(path, session_info=session_info,
661 credentials=credentials, lp=lp)
664 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
668 def setup_samdb_rootdse(samdb, setup_path, names):
669 """Setup the SamDB rootdse.
671 :param samdb: Sam Database handle
672 :param setup_path: Obtain setup path
674 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
675 "SCHEMADN": names.schemadn,
676 "NETBIOSNAME": names.netbiosname,
677 "DNSDOMAIN": names.dnsdomain,
678 "REALM": names.realm,
679 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
680 "DOMAINDN": names.domaindn,
681 "ROOTDN": names.rootdn,
682 "CONFIGDN": names.configdn,
683 "SERVERDN": names.serverdn,
687 def setup_self_join(samdb, names,
688 machinepass, dnspass,
689 domainsid, invocationid, setup_path,
691 """Join a host to its own domain."""
692 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
693 "CONFIGDN": names.configdn,
694 "SCHEMADN": names.schemadn,
695 "DOMAINDN": names.domaindn,
696 "SERVERDN": names.serverdn,
697 "INVOCATIONID": invocationid,
698 "NETBIOSNAME": names.netbiosname,
699 "DEFAULTSITE": names.sitename,
700 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
701 "MACHINEPASS_B64": b64encode(machinepass),
702 "DNSPASS_B64": b64encode(dnspass),
703 "REALM": names.realm,
704 "DOMAIN": names.domain,
705 "DNSDOMAIN": names.dnsdomain})
706 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
707 "POLICYGUID": policyguid,
708 "DNSDOMAIN": names.dnsdomain,
709 "DOMAINSID": str(domainsid),
710 "DOMAINDN": names.domaindn})
713 def setup_samdb(path, setup_path, session_info, credentials, lp,
715 domainsid, aci, domainguid, policyguid,
716 fill, adminpass, krbtgtpass,
717 machinepass, invocationid, dnspass,
718 serverrole, ldap_backend=None,
719 ldap_backend_type=None):
720 """Setup a complete SAM Database.
722 :note: This will wipe the main SAM database file!
725 erase = (fill != FILL_DRS)
727 # Also wipes the database
728 setup_samdb_partitions(path, setup_path, message=message, lp=lp,
729 credentials=credentials, session_info=session_info,
731 ldap_backend=ldap_backend, serverrole=serverrole,
732 ldap_backend_type=ldap_backend_type, erase=erase)
734 samdb = SamDB(path, session_info=session_info,
735 credentials=credentials, lp=lp)
738 # We want to finish here, but setup the index before we do so
739 message("Setting up sam.ldb index")
740 samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
743 message("Pre-loading the Samba 4 and AD schema")
744 samdb.set_domain_sid(domainsid)
745 if serverrole == "domain controller":
746 samdb.set_invocation_id(invocationid)
748 load_schema(setup_path, samdb, names.schemadn, names.netbiosname, names.configdn, names.sitename)
750 samdb.transaction_start()
753 message("Adding DomainDN: %s (permitted to fail)" % names.domaindn)
754 if serverrole == "domain controller":
755 domain_oc = "domainDNS"
757 domain_oc = "samba4LocalDomain"
759 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
760 "DOMAINDN": names.domaindn,
762 "DOMAIN_OC": domain_oc
765 message("Modifying DomainDN: " + names.domaindn + "")
766 if domainguid is not None:
767 domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
771 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
772 "LDAPTIME": timestring(int(time.time())),
773 "DOMAINSID": str(domainsid),
774 "SCHEMADN": names.schemadn,
775 "NETBIOSNAME": names.netbiosname,
776 "DEFAULTSITE": names.sitename,
777 "CONFIGDN": names.configdn,
778 "SERVERDN": names.serverdn,
779 "POLICYGUID": policyguid,
780 "DOMAINDN": names.domaindn,
781 "DOMAINGUID_MOD": domainguid_mod,
784 message("Adding configuration container (permitted to fail)")
785 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
786 "CONFIGDN": names.configdn,
788 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb",
790 message("Modifying configuration container")
791 setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
792 "CONFIGDN": names.configdn,
793 "SCHEMADN": names.schemadn,
796 message("Adding schema container (permitted to fail)")
797 setup_add_ldif(samdb, setup_path("provision_schema_basedn.ldif"), {
798 "SCHEMADN": names.schemadn,
800 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
802 message("Modifying schema container")
803 setup_modify_ldif(samdb,
804 setup_path("provision_schema_basedn_modify.ldif"), {
805 "SCHEMADN": names.schemadn,
806 "NETBIOSNAME": names.netbiosname,
807 "DEFAULTSITE": names.sitename,
808 "CONFIGDN": names.configdn,
809 "SERVERDN": names.serverdn
812 message("Setting up sam.ldb Samba4 schema")
813 setup_add_ldif(samdb, setup_path("schema_samba4.ldif"),
814 {"SCHEMADN": names.schemadn })
815 message("Setting up sam.ldb AD schema")
816 setup_add_ldif(samdb, setup_path("schema.ldif"),
817 {"SCHEMADN": names.schemadn})
819 message("Setting up sam.ldb configuration data")
820 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
821 "CONFIGDN": names.configdn,
822 "NETBIOSNAME": names.netbiosname,
823 "DEFAULTSITE": names.sitename,
824 "DNSDOMAIN": names.dnsdomain,
825 "DOMAIN": names.domain,
826 "SCHEMADN": names.schemadn,
827 "DOMAINDN": names.domaindn,
828 "SERVERDN": names.serverdn
831 message("Setting up display specifiers")
832 setup_add_ldif(samdb, setup_path("display_specifiers.ldif"),
833 {"CONFIGDN": names.configdn})
835 message("Adding users container (permitted to fail)")
836 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
837 "DOMAINDN": names.domaindn})
838 message("Modifying users container")
839 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
840 "DOMAINDN": names.domaindn})
841 message("Adding computers container (permitted to fail)")
842 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
843 "DOMAINDN": names.domaindn})
844 message("Modifying computers container")
845 setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
846 "DOMAINDN": names.domaindn})
847 message("Setting up sam.ldb data")
848 setup_add_ldif(samdb, setup_path("provision.ldif"), {
849 "DOMAINDN": names.domaindn,
850 "NETBIOSNAME": names.netbiosname,
851 "DEFAULTSITE": names.sitename,
852 "CONFIGDN": names.configdn,
853 "SERVERDN": names.serverdn
856 if fill == FILL_FULL:
857 message("Setting up sam.ldb users and groups")
858 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
859 "DOMAINDN": names.domaindn,
860 "DOMAINSID": str(domainsid),
861 "CONFIGDN": names.configdn,
862 "ADMINPASS_B64": b64encode(adminpass),
863 "KRBTGTPASS_B64": b64encode(krbtgtpass),
866 if serverrole == "domain controller":
867 message("Setting up self join")
868 setup_self_join(samdb, names=names, invocationid=invocationid,
870 machinepass=machinepass,
871 domainsid=domainsid, policyguid=policyguid,
872 setup_path=setup_path)
874 #We want to setup the index last, as adds are faster unindexed
875 message("Setting up sam.ldb index")
876 samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
878 samdb.transaction_cancel()
881 samdb.transaction_commit()
886 FILL_NT4SYNC = "NT4SYNC"
889 def provision(setup_dir, message, session_info,
890 credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL, realm=None,
891 rootdn=None, domaindn=None, schemadn=None, configdn=None,
893 domain=None, hostname=None, hostip=None, hostip6=None,
894 domainsid=None, adminpass=None, krbtgtpass=None, domainguid=None,
895 policyguid=None, invocationid=None, machinepass=None,
896 dnspass=None, root=None, nobody=None, nogroup=None, users=None,
897 wheel=None, backup=None, aci=None, serverrole=None,
898 ldap_backend=None, ldap_backend_type=None, sitename=None):
901 :note: caution, this wipes all existing data!
904 def setup_path(file):
905 return os.path.join(setup_dir, file)
907 if domainsid is None:
908 domainsid = security.random_sid()
910 domainsid = security.Sid(domainsid)
912 if policyguid is None:
913 policyguid = uuid.random()
914 if adminpass is None:
915 adminpass = misc.random_password(12)
916 if krbtgtpass is None:
917 krbtgtpass = misc.random_password(12)
918 if machinepass is None:
919 machinepass = misc.random_password(12)
921 dnspass = misc.random_password(12)
923 root_uid = findnss(pwd.getpwnam, ["root"])[2]
925 root_uid = findnss(pwd.getpwnam, [root])[2]
927 nobody_uid = findnss(pwd.getpwnam, ["nobody"])[2]
929 nobody_uid = findnss(pwd.getpwnam, [nobody])[2]
931 users_gid = findnss(grp.getgrnam, ["users"])[2]
933 users_gid = findnss(grp.getgrnam, [users])[2]
935 wheel_gid = findnss(grp.getgrnam, ["wheel", "adm"])[2]
937 wheel_gid = findnss(grp.getgrnam, [wheel])[2]
939 aci = "# no aci for local ldb"
941 lp = load_or_make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, targetdir)
943 names = guess_names(lp=lp, hostname=hostname, domain=domain,
944 dnsdomain=realm, serverrole=serverrole, sitename=sitename,
945 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
948 paths = provision_paths_from_lp(lp, names.dnsdomain)
951 hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
955 hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
956 except socket.gaierror: pass
958 if serverrole is None:
959 serverrole = lp.get("server role")
961 assert serverrole in ("domain controller", "member server", "standalone")
962 if invocationid is None and serverrole == "domain controller":
963 invocationid = uuid.random()
965 if not os.path.exists(paths.private_dir):
966 os.mkdir(paths.private_dir)
968 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
970 if ldap_backend is not None:
971 if ldap_backend == "ldapi":
972 # provision-backend will set this path suggested slapd command line / fedorads.inf
973 ldap_backend = "ldapi://" % urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
975 # only install a new shares config db if there is none
976 if not os.path.exists(paths.shareconf):
977 message("Setting up share.ldb")
978 share_ldb = Ldb(paths.shareconf, session_info=session_info,
979 credentials=credentials, lp=lp)
980 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
983 message("Setting up secrets.ldb")
984 secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
985 session_info=session_info,
986 credentials=credentials, lp=lp)
988 message("Setting up the registry")
989 setup_registry(paths.hklm, setup_path, session_info,
990 credentials=credentials, lp=lp)
992 message("Setting up templates db")
993 setup_templatesdb(paths.templates, setup_path, session_info=session_info,
994 credentials=credentials, lp=lp)
996 message("Setting up idmap db")
997 idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
998 credentials=credentials, lp=lp)
1000 samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info,
1001 credentials=credentials, lp=lp, names=names,
1003 domainsid=domainsid,
1004 aci=aci, domainguid=domainguid, policyguid=policyguid,
1006 adminpass=adminpass, krbtgtpass=krbtgtpass,
1007 invocationid=invocationid,
1008 machinepass=machinepass, dnspass=dnspass,
1009 serverrole=serverrole, ldap_backend=ldap_backend,
1010 ldap_backend_type=ldap_backend_type)
1012 if lp.get("server role") == "domain controller":
1013 if paths.netlogon is None:
1014 message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1015 message("Please either remove %s or see the template at %s" %
1016 ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1017 assert(paths.netlogon is not None)
1019 if paths.sysvol is None:
1020 message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1021 message("Please either remove %s or see the template at %s" %
1022 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1023 assert(paths.sysvol is not None)
1025 policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1026 "{" + policyguid + "}")
1027 os.makedirs(policy_path, 0755)
1028 os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1029 os.makedirs(os.path.join(policy_path, "User"), 0755)
1030 if not os.path.isdir(paths.netlogon):
1031 os.makedirs(paths.netlogon, 0755)
1033 if samdb_fill == FILL_FULL:
1034 setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1035 root_uid=root_uid, nobody_uid=nobody_uid,
1036 users_gid=users_gid, wheel_gid=wheel_gid)
1038 message("Setting up sam.ldb rootDSE marking as synchronized")
1039 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1041 # Only make a zone file on the first DC, it should be replicated with DNS replication
1042 if serverrole == "domain controller":
1043 secrets_ldb = Ldb(paths.secrets, session_info=session_info,
1044 credentials=credentials, lp=lp)
1045 secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1046 netbiosname=names.netbiosname, domainsid=domainsid,
1047 keytab_path=paths.keytab, samdb_url=paths.samdb,
1048 dns_keytab_path=paths.dns_keytab, dnspass=dnspass,
1049 machinepass=machinepass, dnsdomain=names.dnsdomain)
1051 samdb = SamDB(paths.samdb, session_info=session_info,
1052 credentials=credentials, lp=lp)
1054 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1055 assert isinstance(domainguid, str)
1056 hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1057 expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1058 scope=SCOPE_SUBTREE)
1059 assert isinstance(hostguid, str)
1061 create_zone_file(paths.dns, setup_path, samdb,
1062 hostname=names.hostname, hostip=hostip,
1063 hostip6=hostip6, dnsdomain=names.dnsdomain,
1064 domaindn=names.domaindn, dnspass=dnspass, realm=names.realm,
1065 domainguid=domainguid, hostguid=hostguid)
1066 message("Please install the zone located in %s into your DNS server" % paths.dns)
1068 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1071 message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1073 message("Once the above files are installed, your Samba4 server will be ready to use")
1074 message("Server Role: %s" % serverrole)
1075 message("Hostname: %s" % names.hostname)
1076 message("NetBIOS Domain: %s" % names.domain)
1077 message("DNS Domain: %s" % names.dnsdomain)
1078 message("DOMAIN SID: %s" % str(domainsid))
1079 message("Admin password: %s" % adminpass)
1081 result = ProvisionResult()
1082 result.domaindn = domaindn
1083 result.paths = paths
1085 result.samdb = samdb
1089 def provision_become_dc(setup_dir=None,
1090 smbconf=None, targetdir=None, realm=None,
1091 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1093 domain=None, hostname=None, domainsid=None,
1094 adminpass=None, krbtgtpass=None, domainguid=None,
1095 policyguid=None, invocationid=None, machinepass=None,
1096 dnspass=None, root=None, nobody=None, nogroup=None, users=None,
1097 wheel=None, backup=None, aci=None, serverrole=None,
1098 ldap_backend=None, ldap_backend_type=None, sitename=None):
1101 """print a message if quiet is not set."""
1104 return provision(setup_dir, message, system_session(), None,
1105 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm,
1106 rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn, serverdn=serverdn,
1107 domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename);
1110 def setup_db_config(setup_path, dbdir):
1111 """Setup a Berkeley database.
1113 :param setup_path: Setup path function.
1114 :param dbdir: Database directory."""
1115 if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1116 os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700);
1117 if not os.path.isdir(os.path.join(dbdir, "tmp")):
1118 os.makedirs(os.path.join(dbdir, "tmp"), 0700);
1120 setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1121 {"LDAPDBDIR": dbdir})
1125 def provision_backend(setup_dir=None, message=None,
1126 smbconf=None, targetdir=None, realm=None,
1127 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1128 domain=None, hostname=None, adminpass=None, root=None, serverrole=None,
1129 ldap_backend_type=None, ldap_backend_port=None):
1131 def setup_path(file):
1132 return os.path.join(setup_dir, file)
1134 if hostname is None:
1135 hostname = socket.gethostname().split(".")[0].lower()
1138 root = findnss(pwd.getpwnam, ["root"])[0]
1140 lp = load_or_make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, targetdir)
1142 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1143 dnsdomain=realm, serverrole=serverrole,
1144 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn)
1146 paths = provision_paths_from_lp(lp, names.dnsdomain)
1148 if not os.path.isdir(paths.ldapdir):
1149 os.makedirs(paths.ldapdir)
1150 schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1152 os.unlink(schemadb_path)
1156 schemadb = Ldb(schemadb_path, lp=lp)
1158 setup_add_ldif(schemadb, setup_path("provision_schema_basedn.ldif"),
1159 {"SCHEMADN": names.schemadn,
1161 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
1163 setup_modify_ldif(schemadb,
1164 setup_path("provision_schema_basedn_modify.ldif"), \
1165 {"SCHEMADN": names.schemadn,
1166 "NETBIOSNAME": names.netbiosname,
1167 "DEFAULTSITE": DEFAULTSITE,
1168 "CONFIGDN": names.configdn,
1169 "SERVERDN": names.serverdn
1172 setup_add_ldif(schemadb, setup_path("schema_samba4.ldif"),
1173 {"SCHEMADN": names.schemadn })
1174 setup_add_ldif(schemadb, setup_path("schema.ldif"),
1175 {"SCHEMADN": names.schemadn})
1177 if ldap_backend_type == "fedora-ds":
1178 if ldap_backend_port is not None:
1179 serverport = "ServerPort=%d" % ldap_backend_port
1183 setup_file(setup_path("fedorads.inf"), paths.fedoradsinf,
1185 "HOSTNAME": hostname,
1186 "DNSDOMAIN": names.dnsdomain,
1187 "LDAPDIR": paths.ldapdir,
1188 "DOMAINDN": names.domaindn,
1189 "LDAPMANAGERDN": names.ldapmanagerdn,
1190 "LDAPMANAGERPASS": adminpass,
1191 "SERVERPORT": serverport})
1193 setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions,
1194 {"CONFIGDN": names.configdn,
1195 "SCHEMADN": names.schemadn,
1198 mapping = "schema-map-fedora-ds-1.0"
1199 backend_schema = "99_ad.ldif"
1201 slapdcommand="Initailise Fedora DS with: setup-ds.pl --file=%s" % paths.fedoradsinf
1203 elif ldap_backend_type == "openldap":
1204 attrs = ["linkID", "lDAPDisplayName"]
1205 res = schemadb.search(expression="(&(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1)))(objectclass=attributeSchema))", base=names.schemadn, scope=SCOPE_SUBTREE, attrs=attrs);
1207 memberof_config = "# Generated from schema in " + schemadb_path + "\n";
1208 refint_attributes = "";
1209 for i in range (0, len(res)):
1210 linkid = res[i]["linkID"][0]
1211 linkid = str(int(linkid) + 1)
1212 expression = "(&(objectclass=attributeSchema)(linkID=" + (linkid) + "))"
1213 target = schemadb.searchone(basedn=names.schemadn,
1214 expression=expression,
1215 attribute="lDAPDisplayName",
1216 scope=SCOPE_SUBTREE);
1217 if target is not None:
1218 refint_attributes = refint_attributes + " " + target + " " + res[i]["lDAPDisplayName"][0];
1219 memberof_config = memberof_config + """overlay memberof
1220 memberof-dangling error
1221 memberof-refint TRUE
1222 memberof-group-oc top
1223 memberof-member-ad """ + res[i]["lDAPDisplayName"][0] + """
1224 memberof-memberof-ad """ + target + """
1225 memberof-dangling-error 32
1229 memberof_config = memberof_config + """
1231 refint_attributes""" + refint_attributes + "\n";
1233 setup_file(setup_path("slapd.conf"), paths.slapdconf,
1234 {"DNSDOMAIN": names.dnsdomain,
1235 "LDAPDIR": paths.ldapdir,
1236 "DOMAINDN": names.domaindn,
1237 "CONFIGDN": names.configdn,
1238 "SCHEMADN": names.schemadn,
1239 "LDAPMANAGERDN": names.ldapmanagerdn,
1240 "LDAPMANAGERPASS": adminpass,
1241 "MEMBEROF_CONFIG": memberof_config})
1242 setup_file(setup_path("modules.conf"), paths.modulesconf,
1243 {"REALM": names.realm})
1245 setup_db_config(setup_path, os.path.join(paths.ldapdir, os.path.join("db", "user")))
1246 setup_db_config(setup_path, os.path.join(paths.ldapdir, os.path.join("db", "config")))
1247 setup_db_config(setup_path, os.path.join(paths.ldapdir, os.path.join("db", "schema")))
1248 mapping = "schema-map-openldap-2.3"
1249 backend_schema = "backend-schema.schema"
1251 ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
1252 if ldap_backend_port is not None:
1253 server_port_string = " -h ldap://0.0.0.0:%d" % ldap_backend_port
1255 server_port_string = ""
1256 slapdcommand="Start slapd with: slapd -f " + paths.ldapdir + "/slapd.conf -h " + ldapi_uri + server_port_string
1258 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);
1260 os.system(schema_command)
1263 message("Your %s Backend for Samba4 is now configured, and is ready to be started" % ( ldap_backend_type) )
1264 message("Server Role: %s" % serverrole)
1265 message("Hostname: %s" % names.hostname)
1266 message("DNS Domain: %s" % names.dnsdomain)
1267 message("Base DN: %s" % names.domaindn)
1268 message("LDAP admin DN: %s" % names.ldapmanagerdn)
1269 message("LDAP admin password: %s" % adminpass)
1270 message(slapdcommand)
1273 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1274 """Create a PHP LDAP admin configuration file.
1276 :param path: Path to write the configuration to.
1277 :param setup_path: Function to generate setup paths.
1279 setup_file(setup_path("phpldapadmin-config.php"), path,
1280 {"S4_LDAPI_URI": ldapi_uri})
1283 def create_zone_file(path, setup_path, samdb, dnsdomain, domaindn,
1284 hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid):
1285 """Write out a DNS zone file, from the info in the current database.
1287 :param path: Path of the new file.
1288 :param setup_path": Setup path function.
1289 :param samdb: SamDB object
1290 :param dnsdomain: DNS Domain name
1291 :param domaindn: DN of the Domain
1292 :param hostip: Local IPv4 IP
1293 :param hostip6: Local IPv6 IP
1294 :param hostname: Local hostname
1295 :param dnspass: Password for DNS
1296 :param realm: Realm name
1297 :param domainguid: GUID of the domain.
1298 :param hostguid: GUID of the host.
1300 assert isinstance(domainguid, str)
1302 hostip6_base_line = ""
1303 hostip6_host_line = ""
1305 if hostip6 is not None:
1306 hostip6_base_line = " IN AAAA " + hostip6
1307 hostip6_host_line = hostname + " IN AAAA " + hostip6
1309 setup_file(setup_path("provision.zone"), path, {
1310 "DNSPASS_B64": b64encode(dnspass),
1311 "HOSTNAME": hostname,
1312 "DNSDOMAIN": dnsdomain,
1315 "DOMAINGUID": domainguid,
1316 "DATESTRING": time.strftime("%Y%m%d%H"),
1317 "DEFAULTSITE": DEFAULTSITE,
1318 "HOSTGUID": hostguid,
1319 "HOSTIP6_BASE_LINE": hostip6_base_line,
1320 "HOSTIP6_HOST_LINE": hostip6_host_line,
1323 def load_schema(setup_path, samdb, schemadn, netbiosname, configdn, sitename):
1324 """Load schema for the SamDB.
1326 :param samdb: Load a schema into a SamDB.
1327 :param setup_path: Setup path function.
1328 :param schemadn: DN of the schema
1329 :param netbiosname: NetBIOS name of the host.
1330 :param configdn: DN of the configuration
1332 schema_data = open(setup_path("schema.ldif"), 'r').read()
1333 schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
1334 schema_data = substitute_var(schema_data, {"SCHEMADN": schemadn})
1335 head_data = open(setup_path("provision_schema_basedn_modify.ldif"), 'r').read()
1336 head_data = substitute_var(head_data, {
1337 "SCHEMADN": schemadn,
1338 "NETBIOSNAME": netbiosname,
1339 "CONFIGDN": configdn,
1340 "DEFAULTSITE":sitename
1342 samdb.attach_schema_from_ldif(head_data, schema_data)