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.namedconf = os.path.join(paths.private_dir, "named.conf")
240 paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
241 paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
242 paths.phpldapadminconfig = os.path.join(paths.private_dir,
243 "phpldapadmin-config.php")
244 paths.ldapdir = os.path.join(paths.private_dir,
246 paths.slapdconf = os.path.join(paths.ldapdir,
248 paths.modulesconf = os.path.join(paths.ldapdir,
250 paths.memberofconf = os.path.join(paths.ldapdir,
252 paths.fedoradsinf = os.path.join(paths.ldapdir,
254 paths.fedoradspartitions = os.path.join(paths.ldapdir,
255 "fedorads-partitions.ldif")
256 paths.hklm = "hklm.ldb"
257 paths.hkcr = "hkcr.ldb"
258 paths.hkcu = "hkcu.ldb"
259 paths.hku = "hku.ldb"
260 paths.hkpd = "hkpd.ldb"
261 paths.hkpt = "hkpt.ldb"
263 paths.sysvol = lp.get("path", "sysvol")
265 paths.netlogon = lp.get("path", "netlogon")
267 paths.smbconf = lp.configfile()
272 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, serverrole=None,
273 rootdn=None, domaindn=None, configdn=None, schemadn=None, serverdn=None,
275 """Guess configuration settings to use."""
278 hostname = socket.gethostname().split(".")[0].lower()
280 netbiosname = hostname.upper()
281 if not valid_netbios_name(netbiosname):
282 raise InvalidNetbiosName(netbiosname)
284 hostname = hostname.lower()
286 if dnsdomain is None:
287 dnsdomain = lp.get("realm")
289 if serverrole is None:
290 serverrole = lp.get("server role")
292 assert dnsdomain is not None
293 realm = dnsdomain.upper()
295 if lp.get("realm").upper() != realm:
296 raise Exception("realm '%s' in %s must match chosen realm '%s'" %
297 (lp.get("realm"), lp.configfile(), realm))
299 dnsdomain = dnsdomain.lower()
301 if (serverrole == "domain controller"):
303 domain = lp.get("workgroup")
305 domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
306 if lp.get("workgroup").upper() != domain.upper():
307 raise Error("workgroup '%s' in smb.conf must match chosen domain '%s'",
308 lp.get("workgroup"), domain)
312 domaindn = "CN=" + netbiosname
314 assert domain is not None
315 domain = domain.upper()
316 if not valid_netbios_name(domain):
317 raise InvalidNetbiosName(domain)
323 configdn = "CN=Configuration," + rootdn
325 schemadn = "CN=Schema," + configdn
330 names = ProvisionNames()
331 names.rootdn = rootdn
332 names.domaindn = domaindn
333 names.configdn = configdn
334 names.schemadn = schemadn
335 names.ldapmanagerdn = "CN=Manager," + rootdn
336 names.dnsdomain = dnsdomain
337 names.domain = domain
339 names.netbiosname = netbiosname
340 names.hostname = hostname
341 names.sitename = sitename
342 names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (netbiosname, sitename, configdn)
347 def load_or_make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, targetdir):
348 if targetdir is not None:
349 if not os.path.exists(targetdir):
351 if not os.path.exists(os.path.join(targetdir, "etc")):
352 os.mkdir(os.path.join(targetdir, "etc"))
354 smbconf = os.path.join(targetdir, "etc", "smb.conf")
356 # only install a new smb.conf if there isn't one there already
358 if not os.path.exists(smbconf):
360 hostname = socket.gethostname().split(".")[0].lower()
362 if serverrole is None:
363 serverrole = "standalone"
365 assert serverrole in ("domain controller", "member server", "standalone")
366 if serverrole == "domain controller":
368 elif serverrole == "member server":
369 smbconfsuffix = "member"
370 elif serverrole == "standalone":
371 smbconfsuffix = "standalone"
373 assert domain is not None
374 assert realm is not None
376 default_lp = param.LoadParm()
377 #Load non-existant file
378 default_lp.load(smbconf)
380 if targetdir is not None:
381 privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
382 lockdir_line = "lock dir = " + os.path.abspath(targetdir)
384 default_lp.set("lock dir", os.path.abspath(targetdir))
389 sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
390 netlogon = os.path.join(sysvol, realm.lower(), "scripts")
392 setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
394 "HOSTNAME": hostname,
397 "SERVERROLE": serverrole,
398 "NETLOGONPATH": netlogon,
399 "SYSVOLPATH": sysvol,
400 "PRIVATEDIR_LINE": privatedir_line,
401 "LOCKDIR_LINE": lockdir_line
404 lp = param.LoadParm()
410 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
411 users_gid, wheel_gid):
412 """setup reasonable name mappings for sam names to unix names.
414 :param samdb: SamDB object.
415 :param idmap: IDmap db object.
416 :param sid: The domain sid.
417 :param domaindn: The domain DN.
418 :param root_uid: uid of the UNIX root user.
419 :param nobody_uid: uid of the UNIX nobody user.
420 :param users_gid: gid of the UNIX users group.
421 :param wheel_gid: gid of the UNIX wheel group."""
422 # add some foreign sids if they are not present already
423 samdb.add_foreign(domaindn, "S-1-5-7", "Anonymous")
424 samdb.add_foreign(domaindn, "S-1-1-0", "World")
425 samdb.add_foreign(domaindn, "S-1-5-2", "Network")
426 samdb.add_foreign(domaindn, "S-1-5-18", "System")
427 samdb.add_foreign(domaindn, "S-1-5-11", "Authenticated Users")
429 idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
430 idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
432 idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
433 idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
436 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
438 serverrole, ldap_backend=None,
439 ldap_backend_type=None, erase=False):
440 """Setup the partitions for the SAM database.
442 Alternatively, provision() may call this, and then populate the database.
444 :note: This will wipe the Sam Database!
446 :note: This function always removes the local SAM LDB file. The erase
447 parameter controls whether to erase the existing data, which
448 may not be stored locally but in LDAP.
450 assert session_info is not None
452 samdb = SamDB(samdb_path, session_info=session_info,
453 credentials=credentials, lp=lp)
459 os.unlink(samdb_path)
461 samdb = SamDB(samdb_path, session_info=session_info,
462 credentials=credentials, lp=lp)
464 #Add modules to the list to activate them by default
465 #beware often order is important
467 # Some Known ordering constraints:
468 # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
469 # - objectclass must be before password_hash, because password_hash checks
470 # that the objectclass is of type person (filled in by objectclass
471 # module when expanding the objectclass list)
472 # - partition must be last
473 # - each partition has its own module list then
474 modules_list = ["rootdse",
490 modules_list2 = ["show_deleted",
493 domaindn_ldb = "users.ldb"
494 if ldap_backend is not None:
495 domaindn_ldb = ldap_backend
496 configdn_ldb = "configuration.ldb"
497 if ldap_backend is not None:
498 configdn_ldb = ldap_backend
499 schemadn_ldb = "schema.ldb"
500 if ldap_backend is not None:
501 schema_ldb = ldap_backend
502 schemadn_ldb = ldap_backend
504 if ldap_backend_type == "fedora-ds":
505 backend_modules = ["nsuniqueid", "paged_searches"]
506 # We can handle linked attributes here, as we don't have directory-side subtree operations
507 tdb_modules_list = ["linked_attributes"]
508 elif ldap_backend_type == "openldap":
509 backend_modules = ["normalise", "entryuuid", "paged_searches"]
510 # OpenLDAP handles subtree renames, so we don't want to do any of these things
511 tdb_modules_list = None
512 elif serverrole == "domain controller":
513 backend_modules = ["repl_meta_data"]
515 backend_modules = ["objectguid"]
517 if tdb_modules_list is None:
518 tdb_modules_list_as_string = ""
520 tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
522 samdb.transaction_start()
524 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
525 "SCHEMADN": names.schemadn,
526 "SCHEMADN_LDB": schemadn_ldb,
527 "SCHEMADN_MOD2": ",objectguid",
528 "CONFIGDN": names.configdn,
529 "CONFIGDN_LDB": configdn_ldb,
530 "DOMAINDN": names.domaindn,
531 "DOMAINDN_LDB": domaindn_ldb,
532 "SCHEMADN_MOD": "schema_fsmo,instancetype",
533 "CONFIGDN_MOD": "naming_fsmo,instancetype",
534 "DOMAINDN_MOD": "pdc_fsmo,password_hash,instancetype",
535 "MODULES_LIST": ",".join(modules_list),
536 "TDB_MODULES_LIST": tdb_modules_list_as_string,
537 "MODULES_LIST2": ",".join(modules_list2),
538 "BACKEND_MOD": ",".join(backend_modules),
542 samdb.transaction_cancel()
545 samdb.transaction_commit()
547 samdb = SamDB(samdb_path, session_info=session_info,
548 credentials=credentials, lp=lp)
550 samdb.transaction_start()
552 message("Setting up sam.ldb attributes")
553 samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
555 message("Setting up sam.ldb rootDSE")
556 setup_samdb_rootdse(samdb, setup_path, names)
559 message("Erasing data from partitions")
560 samdb.erase_partitions()
563 samdb.transaction_cancel()
566 samdb.transaction_commit()
571 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain,
572 netbiosname, domainsid, keytab_path, samdb_url,
573 dns_keytab_path, dnspass, machinepass):
574 """Add DC-specific bits to a secrets database.
576 :param secretsdb: Ldb Handle to the secrets database
577 :param setup_path: Setup path function
578 :param machinepass: Machine password
580 setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), {
581 "MACHINEPASS_B64": b64encode(machinepass),
584 "DNSDOMAIN": dnsdomain,
585 "DOMAINSID": str(domainsid),
586 "SECRETS_KEYTAB": keytab_path,
587 "NETBIOSNAME": netbiosname,
588 "SAM_LDB": samdb_url,
589 "DNS_KEYTAB": dns_keytab_path,
590 "DNSPASS_B64": b64encode(dnspass),
594 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
595 """Setup the secrets database.
597 :param path: Path to the secrets database.
598 :param setup_path: Get the path to a setup file.
599 :param session_info: Session info.
600 :param credentials: Credentials
601 :param lp: Loadparm context
602 :return: LDB handle for the created secrets database
604 if os.path.exists(path):
606 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
609 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
610 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
612 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
616 def setup_templatesdb(path, setup_path, session_info, credentials, lp):
617 """Setup the templates database.
619 :param path: Path to the database.
620 :param setup_path: Function for obtaining the path to setup files.
621 :param session_info: Session info
622 :param credentials: Credentials
623 :param lp: Loadparm context
625 templates_ldb = SamDB(path, session_info=session_info,
626 credentials=credentials, lp=lp)
627 templates_ldb.erase()
628 templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif"))
631 def setup_registry(path, setup_path, session_info, credentials, lp):
632 """Setup the registry.
634 :param path: Path to the registry database
635 :param setup_path: Function that returns the path to a setup.
636 :param session_info: Session information
637 :param credentials: Credentials
638 :param lp: Loadparm context
640 reg = registry.Registry()
641 hive = registry.open_ldb(path, session_info=session_info,
642 credentials=credentials, lp_ctx=lp)
643 reg.mount_hive(hive, "HKEY_LOCAL_MACHINE")
644 provision_reg = setup_path("provision.reg")
645 assert os.path.exists(provision_reg)
646 reg.diff_apply(provision_reg)
649 def setup_idmapdb(path, setup_path, session_info, credentials, lp):
650 """Setup the idmap database.
652 :param path: path to the idmap database
653 :param setup_path: Function that returns a path to a setup file
654 :param session_info: Session information
655 :param credentials: Credentials
656 :param lp: Loadparm context
658 if os.path.exists(path):
661 idmap_ldb = IDmapDB(path, session_info=session_info,
662 credentials=credentials, lp=lp)
665 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
669 def setup_samdb_rootdse(samdb, setup_path, names):
670 """Setup the SamDB rootdse.
672 :param samdb: Sam Database handle
673 :param setup_path: Obtain setup path
675 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
676 "SCHEMADN": names.schemadn,
677 "NETBIOSNAME": names.netbiosname,
678 "DNSDOMAIN": names.dnsdomain,
679 "REALM": names.realm,
680 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
681 "DOMAINDN": names.domaindn,
682 "ROOTDN": names.rootdn,
683 "CONFIGDN": names.configdn,
684 "SERVERDN": names.serverdn,
688 def setup_self_join(samdb, names,
689 machinepass, dnspass,
690 domainsid, invocationid, setup_path,
692 """Join a host to its own domain."""
693 assert isinstance(invocationid, str)
694 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
695 "CONFIGDN": names.configdn,
696 "SCHEMADN": names.schemadn,
697 "DOMAINDN": names.domaindn,
698 "SERVERDN": names.serverdn,
699 "INVOCATIONID": invocationid,
700 "NETBIOSNAME": names.netbiosname,
701 "DEFAULTSITE": names.sitename,
702 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
703 "MACHINEPASS_B64": b64encode(machinepass),
704 "DNSPASS_B64": b64encode(dnspass),
705 "REALM": names.realm,
706 "DOMAIN": names.domain,
707 "DNSDOMAIN": names.dnsdomain})
708 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
709 "POLICYGUID": policyguid,
710 "DNSDOMAIN": names.dnsdomain,
711 "DOMAINSID": str(domainsid),
712 "DOMAINDN": names.domaindn})
715 def setup_samdb(path, setup_path, session_info, credentials, lp,
717 domainsid, aci, domainguid, policyguid,
718 fill, adminpass, krbtgtpass,
719 machinepass, invocationid, dnspass,
720 serverrole, ldap_backend=None,
721 ldap_backend_type=None):
722 """Setup a complete SAM Database.
724 :note: This will wipe the main SAM database file!
727 erase = (fill != FILL_DRS)
729 # Also wipes the database
730 setup_samdb_partitions(path, setup_path, message=message, lp=lp,
731 credentials=credentials, session_info=session_info,
733 ldap_backend=ldap_backend, serverrole=serverrole,
734 ldap_backend_type=ldap_backend_type, erase=erase)
736 samdb = SamDB(path, session_info=session_info,
737 credentials=credentials, lp=lp)
740 # We want to finish here, but setup the index before we do so
741 message("Setting up sam.ldb index")
742 samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
745 message("Pre-loading the Samba 4 and AD schema")
746 samdb.set_domain_sid(domainsid)
747 if serverrole == "domain controller":
748 samdb.set_invocation_id(invocationid)
750 load_schema(setup_path, samdb, names.schemadn, names.netbiosname, names.configdn, names.sitename)
752 samdb.transaction_start()
755 message("Adding DomainDN: %s (permitted to fail)" % names.domaindn)
756 if serverrole == "domain controller":
757 domain_oc = "domainDNS"
759 domain_oc = "samba4LocalDomain"
761 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
762 "DOMAINDN": names.domaindn,
764 "DOMAIN_OC": domain_oc
767 message("Modifying DomainDN: " + names.domaindn + "")
768 if domainguid is not None:
769 domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
773 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
774 "LDAPTIME": timestring(int(time.time())),
775 "DOMAINSID": str(domainsid),
776 "SCHEMADN": names.schemadn,
777 "NETBIOSNAME": names.netbiosname,
778 "DEFAULTSITE": names.sitename,
779 "CONFIGDN": names.configdn,
780 "SERVERDN": names.serverdn,
781 "POLICYGUID": policyguid,
782 "DOMAINDN": names.domaindn,
783 "DOMAINGUID_MOD": domainguid_mod,
786 message("Adding configuration container (permitted to fail)")
787 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
788 "CONFIGDN": names.configdn,
790 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb",
792 message("Modifying configuration container")
793 setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
794 "CONFIGDN": names.configdn,
795 "SCHEMADN": names.schemadn,
798 message("Adding schema container (permitted to fail)")
799 setup_add_ldif(samdb, setup_path("provision_schema_basedn.ldif"), {
800 "SCHEMADN": names.schemadn,
802 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
804 message("Modifying schema container")
805 setup_modify_ldif(samdb,
806 setup_path("provision_schema_basedn_modify.ldif"), {
807 "SCHEMADN": names.schemadn,
808 "NETBIOSNAME": names.netbiosname,
809 "DEFAULTSITE": names.sitename,
810 "CONFIGDN": names.configdn,
811 "SERVERDN": names.serverdn
814 message("Setting up sam.ldb Samba4 schema")
815 setup_add_ldif(samdb, setup_path("schema_samba4.ldif"),
816 {"SCHEMADN": names.schemadn })
817 message("Setting up sam.ldb AD schema")
818 setup_add_ldif(samdb, setup_path("schema.ldif"),
819 {"SCHEMADN": names.schemadn})
821 message("Setting up sam.ldb configuration data")
822 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
823 "CONFIGDN": names.configdn,
824 "NETBIOSNAME": names.netbiosname,
825 "DEFAULTSITE": names.sitename,
826 "DNSDOMAIN": names.dnsdomain,
827 "DOMAIN": names.domain,
828 "SCHEMADN": names.schemadn,
829 "DOMAINDN": names.domaindn,
830 "SERVERDN": names.serverdn
833 message("Setting up display specifiers")
834 setup_add_ldif(samdb, setup_path("display_specifiers.ldif"),
835 {"CONFIGDN": names.configdn})
837 message("Adding users container (permitted to fail)")
838 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
839 "DOMAINDN": names.domaindn})
840 message("Modifying users container")
841 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
842 "DOMAINDN": names.domaindn})
843 message("Adding computers container (permitted to fail)")
844 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
845 "DOMAINDN": names.domaindn})
846 message("Modifying computers container")
847 setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
848 "DOMAINDN": names.domaindn})
849 message("Setting up sam.ldb data")
850 setup_add_ldif(samdb, setup_path("provision.ldif"), {
851 "DOMAINDN": names.domaindn,
852 "NETBIOSNAME": names.netbiosname,
853 "DEFAULTSITE": names.sitename,
854 "CONFIGDN": names.configdn,
855 "SERVERDN": names.serverdn
858 if fill == FILL_FULL:
859 message("Setting up sam.ldb users and groups")
860 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
861 "DOMAINDN": names.domaindn,
862 "DOMAINSID": str(domainsid),
863 "CONFIGDN": names.configdn,
864 "ADMINPASS_B64": b64encode(adminpass),
865 "KRBTGTPASS_B64": b64encode(krbtgtpass),
868 if serverrole == "domain controller":
869 message("Setting up self join")
870 setup_self_join(samdb, names=names, invocationid=invocationid,
872 machinepass=machinepass,
873 domainsid=domainsid, policyguid=policyguid,
874 setup_path=setup_path)
876 #We want to setup the index last, as adds are faster unindexed
877 message("Setting up sam.ldb index")
878 samdb.load_ldif_file_add(setup_path("provision_index.ldif"))
880 samdb.transaction_cancel()
883 samdb.transaction_commit()
888 FILL_NT4SYNC = "NT4SYNC"
891 def provision(setup_dir, message, session_info,
892 credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL, realm=None,
893 rootdn=None, domaindn=None, schemadn=None, configdn=None,
895 domain=None, hostname=None, hostip=None, hostip6=None,
896 domainsid=None, adminpass=None, krbtgtpass=None, domainguid=None,
897 policyguid=None, invocationid=None, machinepass=None,
898 dnspass=None, root=None, nobody=None, nogroup=None, users=None,
899 wheel=None, backup=None, aci=None, serverrole=None,
900 ldap_backend=None, ldap_backend_type=None, sitename=None):
903 :note: caution, this wipes all existing data!
906 def setup_path(file):
907 return os.path.join(setup_dir, file)
909 if domainsid is None:
910 domainsid = security.random_sid()
912 domainsid = security.Sid(domainsid)
914 if policyguid is None:
915 policyguid = str(uuid.uuid4())
916 if adminpass is None:
917 adminpass = misc.random_password(12)
918 if krbtgtpass is None:
919 krbtgtpass = misc.random_password(12)
920 if machinepass is None:
921 machinepass = misc.random_password(12)
923 dnspass = misc.random_password(12)
925 root_uid = findnss(pwd.getpwnam, ["root"])[2]
927 root_uid = findnss(pwd.getpwnam, [root])[2]
929 nobody_uid = findnss(pwd.getpwnam, ["nobody"])[2]
931 nobody_uid = findnss(pwd.getpwnam, [nobody])[2]
933 users_gid = findnss(grp.getgrnam, ["users"])[2]
935 users_gid = findnss(grp.getgrnam, [users])[2]
937 wheel_gid = findnss(grp.getgrnam, ["wheel", "adm"])[2]
939 wheel_gid = findnss(grp.getgrnam, [wheel])[2]
941 aci = "# no aci for local ldb"
943 lp = load_or_make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, targetdir)
945 names = guess_names(lp=lp, hostname=hostname, domain=domain,
946 dnsdomain=realm, serverrole=serverrole, sitename=sitename,
947 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
950 paths = provision_paths_from_lp(lp, names.dnsdomain)
953 hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
957 hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
958 except socket.gaierror: pass
960 if serverrole is None:
961 serverrole = lp.get("server role")
963 assert serverrole in ("domain controller", "member server", "standalone")
964 if invocationid is None and serverrole == "domain controller":
965 invocationid = str(uuid.uuid4())
967 if not os.path.exists(paths.private_dir):
968 os.mkdir(paths.private_dir)
970 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
972 if ldap_backend is not None:
973 if ldap_backend == "ldapi":
974 # provision-backend will set this path suggested slapd command line / fedorads.inf
975 ldap_backend = "ldapi://" % urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
977 # only install a new shares config db if there is none
978 if not os.path.exists(paths.shareconf):
979 message("Setting up share.ldb")
980 share_ldb = Ldb(paths.shareconf, session_info=session_info,
981 credentials=credentials, lp=lp)
982 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
985 message("Setting up secrets.ldb")
986 secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
987 session_info=session_info,
988 credentials=credentials, lp=lp)
990 message("Setting up the registry")
991 setup_registry(paths.hklm, setup_path, session_info,
992 credentials=credentials, lp=lp)
994 message("Setting up templates db")
995 setup_templatesdb(paths.templates, setup_path, session_info=session_info,
996 credentials=credentials, lp=lp)
998 message("Setting up idmap db")
999 idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1000 credentials=credentials, lp=lp)
1002 samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info,
1003 credentials=credentials, lp=lp, names=names,
1005 domainsid=domainsid,
1006 aci=aci, domainguid=domainguid, policyguid=policyguid,
1008 adminpass=adminpass, krbtgtpass=krbtgtpass,
1009 invocationid=invocationid,
1010 machinepass=machinepass, dnspass=dnspass,
1011 serverrole=serverrole, ldap_backend=ldap_backend,
1012 ldap_backend_type=ldap_backend_type)
1014 if lp.get("server role") == "domain controller":
1015 if paths.netlogon is None:
1016 message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1017 message("Please either remove %s or see the template at %s" %
1018 ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1019 assert(paths.netlogon is not None)
1021 if paths.sysvol is None:
1022 message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1023 message("Please either remove %s or see the template at %s" %
1024 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1025 assert(paths.sysvol is not None)
1027 policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1028 "{" + policyguid + "}")
1029 os.makedirs(policy_path, 0755)
1030 os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1031 os.makedirs(os.path.join(policy_path, "User"), 0755)
1032 if not os.path.isdir(paths.netlogon):
1033 os.makedirs(paths.netlogon, 0755)
1035 if samdb_fill == FILL_FULL:
1036 setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1037 root_uid=root_uid, nobody_uid=nobody_uid,
1038 users_gid=users_gid, wheel_gid=wheel_gid)
1040 message("Setting up sam.ldb rootDSE marking as synchronized")
1041 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1043 # Only make a zone file on the first DC, it should be replicated with DNS replication
1044 if serverrole == "domain controller":
1045 secrets_ldb = Ldb(paths.secrets, session_info=session_info,
1046 credentials=credentials, lp=lp)
1047 secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1048 netbiosname=names.netbiosname, domainsid=domainsid,
1049 keytab_path=paths.keytab, samdb_url=paths.samdb,
1050 dns_keytab_path=paths.dns_keytab, dnspass=dnspass,
1051 machinepass=machinepass, dnsdomain=names.dnsdomain)
1053 samdb = SamDB(paths.samdb, session_info=session_info,
1054 credentials=credentials, lp=lp)
1056 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1057 assert isinstance(domainguid, str)
1058 hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1059 expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1060 scope=SCOPE_SUBTREE)
1061 assert isinstance(hostguid, str)
1063 create_zone_file(paths.dns, paths.namedconf, setup_path, samdb,
1064 hostname=names.hostname, hostip=hostip,
1065 hostip6=hostip6, dnsdomain=names.dnsdomain,
1066 domaindn=names.domaindn, dnspass=dnspass, realm=names.realm,
1067 domainguid=domainguid, hostguid=hostguid,
1068 private_dir=paths.private_dir, keytab_name=paths.dns_keytab)
1069 message("Please install the zone located in %s into your DNS server" % paths.dns)
1070 message("See %s if you want to use secure GSS-TSIG updates" % paths.namedconf)
1072 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1075 message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1077 message("Once the above files are installed, your Samba4 server will be ready to use")
1078 message("Server Role: %s" % serverrole)
1079 message("Hostname: %s" % names.hostname)
1080 message("NetBIOS Domain: %s" % names.domain)
1081 message("DNS Domain: %s" % names.dnsdomain)
1082 message("DOMAIN SID: %s" % str(domainsid))
1083 message("Admin password: %s" % adminpass)
1085 result = ProvisionResult()
1086 result.domaindn = domaindn
1087 result.paths = paths
1089 result.samdb = samdb
1093 def provision_become_dc(setup_dir=None,
1094 smbconf=None, targetdir=None, realm=None,
1095 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1097 domain=None, hostname=None, domainsid=None,
1098 adminpass=None, krbtgtpass=None, domainguid=None,
1099 policyguid=None, invocationid=None, machinepass=None,
1100 dnspass=None, root=None, nobody=None, nogroup=None, users=None,
1101 wheel=None, backup=None, aci=None, serverrole=None,
1102 ldap_backend=None, ldap_backend_type=None, sitename=None):
1105 """print a message if quiet is not set."""
1108 return provision(setup_dir, message, system_session(), None,
1109 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm,
1110 rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn, serverdn=serverdn,
1111 domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename);
1114 def setup_db_config(setup_path, dbdir):
1115 """Setup a Berkeley database.
1117 :param setup_path: Setup path function.
1118 :param dbdir: Database directory."""
1119 if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1120 os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700);
1121 if not os.path.isdir(os.path.join(dbdir, "tmp")):
1122 os.makedirs(os.path.join(dbdir, "tmp"), 0700);
1124 setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1125 {"LDAPDBDIR": dbdir})
1129 def provision_backend(setup_dir=None, message=None,
1130 smbconf=None, targetdir=None, realm=None,
1131 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1132 domain=None, hostname=None, adminpass=None, root=None, serverrole=None,
1133 ldap_backend_type=None, ldap_backend_port=None):
1135 def setup_path(file):
1136 return os.path.join(setup_dir, file)
1138 if hostname is None:
1139 hostname = socket.gethostname().split(".")[0].lower()
1142 root = findnss(pwd.getpwnam, ["root"])[0]
1144 lp = load_or_make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole, targetdir)
1146 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1147 dnsdomain=realm, serverrole=serverrole,
1148 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn)
1150 paths = provision_paths_from_lp(lp, names.dnsdomain)
1152 if not os.path.isdir(paths.ldapdir):
1153 os.makedirs(paths.ldapdir)
1154 schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1156 os.unlink(schemadb_path)
1160 schemadb = Ldb(schemadb_path, lp=lp)
1162 setup_add_ldif(schemadb, setup_path("provision_schema_basedn.ldif"),
1163 {"SCHEMADN": names.schemadn,
1165 "EXTENSIBLEOBJECT": "# no objectClass: extensibleObject for local ldb"
1167 setup_modify_ldif(schemadb,
1168 setup_path("provision_schema_basedn_modify.ldif"), \
1169 {"SCHEMADN": names.schemadn,
1170 "NETBIOSNAME": names.netbiosname,
1171 "DEFAULTSITE": DEFAULTSITE,
1172 "CONFIGDN": names.configdn,
1173 "SERVERDN": names.serverdn
1176 setup_add_ldif(schemadb, setup_path("schema_samba4.ldif"),
1177 {"SCHEMADN": names.schemadn })
1178 setup_add_ldif(schemadb, setup_path("schema.ldif"),
1179 {"SCHEMADN": names.schemadn})
1181 if ldap_backend_type == "fedora-ds":
1182 if ldap_backend_port is not None:
1183 serverport = "ServerPort=%d" % ldap_backend_port
1187 setup_file(setup_path("fedorads.inf"), paths.fedoradsinf,
1189 "HOSTNAME": hostname,
1190 "DNSDOMAIN": names.dnsdomain,
1191 "LDAPDIR": paths.ldapdir,
1192 "DOMAINDN": names.domaindn,
1193 "LDAPMANAGERDN": names.ldapmanagerdn,
1194 "LDAPMANAGERPASS": adminpass,
1195 "SERVERPORT": serverport})
1197 setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions,
1198 {"CONFIGDN": names.configdn,
1199 "SCHEMADN": names.schemadn,
1202 mapping = "schema-map-fedora-ds-1.0"
1203 backend_schema = "99_ad.ldif"
1205 slapdcommand="Initailise Fedora DS with: setup-ds.pl --file=%s" % paths.fedoradsinf
1207 elif ldap_backend_type == "openldap":
1208 attrs = ["linkID", "lDAPDisplayName"]
1209 res = schemadb.search(expression="(&(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1)))(objectclass=attributeSchema))", base=names.schemadn, scope=SCOPE_SUBTREE, attrs=attrs);
1211 memberof_config = "# Generated from schema in " + schemadb_path + "\n";
1212 refint_attributes = "";
1213 for i in range (0, len(res)):
1214 linkid = res[i]["linkID"][0]
1215 linkid = str(int(linkid) + 1)
1216 expression = "(&(objectclass=attributeSchema)(linkID=" + (linkid) + "))"
1217 target = schemadb.searchone(basedn=names.schemadn,
1218 expression=expression,
1219 attribute="lDAPDisplayName",
1220 scope=SCOPE_SUBTREE);
1221 if target is not None:
1222 refint_attributes = refint_attributes + " " + target + " " + res[i]["lDAPDisplayName"][0];
1223 memberof_config = memberof_config + """overlay memberof
1224 memberof-dangling error
1225 memberof-refint TRUE
1226 memberof-group-oc top
1227 memberof-member-ad """ + res[i]["lDAPDisplayName"][0] + """
1228 memberof-memberof-ad """ + target + """
1229 memberof-dangling-error 32
1233 memberof_config = memberof_config + """
1235 refint_attributes""" + refint_attributes + "\n";
1237 setup_file(setup_path("slapd.conf"), paths.slapdconf,
1238 {"DNSDOMAIN": names.dnsdomain,
1239 "LDAPDIR": paths.ldapdir,
1240 "DOMAINDN": names.domaindn,
1241 "CONFIGDN": names.configdn,
1242 "SCHEMADN": names.schemadn,
1243 "LDAPMANAGERDN": names.ldapmanagerdn,
1244 "LDAPMANAGERPASS": adminpass,
1245 "MEMBEROF_CONFIG": memberof_config})
1246 setup_file(setup_path("modules.conf"), paths.modulesconf,
1247 {"REALM": names.realm})
1249 setup_db_config(setup_path, os.path.join(paths.ldapdir, os.path.join("db", "user")))
1250 setup_db_config(setup_path, os.path.join(paths.ldapdir, os.path.join("db", "config")))
1251 setup_db_config(setup_path, os.path.join(paths.ldapdir, os.path.join("db", "schema")))
1252 mapping = "schema-map-openldap-2.3"
1253 backend_schema = "backend-schema.schema"
1255 ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.private_dir, "ldap", "ldapi"), safe="")
1256 if ldap_backend_port is not None:
1257 server_port_string = " -h ldap://0.0.0.0:%d" % ldap_backend_port
1259 server_port_string = ""
1260 slapdcommand="Start slapd with: slapd -f " + paths.ldapdir + "/slapd.conf -h " + ldapi_uri + server_port_string
1262 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);
1264 os.system(schema_command)
1267 message("Your %s Backend for Samba4 is now configured, and is ready to be started" % ( ldap_backend_type) )
1268 message("Server Role: %s" % serverrole)
1269 message("Hostname: %s" % names.hostname)
1270 message("DNS Domain: %s" % names.dnsdomain)
1271 message("Base DN: %s" % names.domaindn)
1272 message("LDAP admin DN: %s" % names.ldapmanagerdn)
1273 message("LDAP admin password: %s" % adminpass)
1274 message(slapdcommand)
1277 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1278 """Create a PHP LDAP admin configuration file.
1280 :param path: Path to write the configuration to.
1281 :param setup_path: Function to generate setup paths.
1283 setup_file(setup_path("phpldapadmin-config.php"), path,
1284 {"S4_LDAPI_URI": ldapi_uri})
1287 def create_zone_file(path_zone, path_conf, setup_path, samdb, dnsdomain, domaindn,
1288 hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid,
1289 private_dir, keytab_name):
1290 """Write out a DNS zone file, from the info in the current database.
1292 Also writes a file with stubs appropriate for a DNS configuration file
1293 (including GSS-TSIG configuration), and details as to some of the other
1294 configuration changes that may be necessary.
1296 :param path_zone: Path of the new zone file.
1297 :param path_conf: Path of the config stubs file.
1298 :param setup_path: Setup path function.
1299 :param samdb: SamDB object
1300 :param dnsdomain: DNS Domain name
1301 :param domaindn: DN of the Domain
1302 :param hostip: Local IPv4 IP
1303 :param hostip6: Local IPv6 IP
1304 :param hostname: Local hostname
1305 :param dnspass: Password for DNS
1306 :param realm: Realm name
1307 :param domainguid: GUID of the domain.
1308 :param hostguid: GUID of the host.
1310 assert isinstance(domainguid, str)
1312 hostip6_base_line = ""
1313 hostip6_host_line = ""
1315 if hostip6 is not None:
1316 hostip6_base_line = " IN AAAA " + hostip6
1317 hostip6_host_line = hostname + " IN AAAA " + hostip6
1319 setup_file(setup_path("provision.zone"), path_zone, {
1320 "DNSPASS_B64": b64encode(dnspass),
1321 "HOSTNAME": hostname,
1322 "DNSDOMAIN": dnsdomain,
1325 "DOMAINGUID": domainguid,
1326 "DATESTRING": time.strftime("%Y%m%d%H"),
1327 "DEFAULTSITE": DEFAULTSITE,
1328 "HOSTGUID": hostguid,
1329 "HOSTIP6_BASE_LINE": hostip6_base_line,
1330 "HOSTIP6_HOST_LINE": hostip6_host_line,
1333 setup_file(setup_path("named.conf"), path_conf, {
1334 "DNSDOMAIN": dnsdomain,
1336 "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1337 "HOSTNAME": hostname,
1338 "DNS_KEYTAB": keytab_name,
1339 "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1342 def load_schema(setup_path, samdb, schemadn, netbiosname, configdn, sitename):
1343 """Load schema for the SamDB.
1345 :param samdb: Load a schema into a SamDB.
1346 :param setup_path: Setup path function.
1347 :param schemadn: DN of the schema
1348 :param netbiosname: NetBIOS name of the host.
1349 :param configdn: DN of the configuration
1351 schema_data = open(setup_path("schema.ldif"), 'r').read()
1352 schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
1353 schema_data = substitute_var(schema_data, {"SCHEMADN": schemadn})
1354 head_data = open(setup_path("provision_schema_basedn_modify.ldif"), 'r').read()
1355 head_data = substitute_var(head_data, {
1356 "SCHEMADN": schemadn,
1357 "NETBIOSNAME": netbiosname,
1358 "CONFIGDN": configdn,
1359 "DEFAULTSITE":sitename
1361 samdb.attach_schema_from_ldif(head_data, schema_data)