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-2009
7 # Copyright (C) Oliver Liebel <oliver@itc.li> 2008-2009
9 # Based on the original in EJS:
10 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
12 # This program is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 3 of the License, or
15 # (at your option) any later version.
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
22 # You should have received a copy of the GNU General Public License
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 """Functions for setting up a Samba configuration."""
28 from base64 import b64encode
43 from credentials import Credentials, DONT_USE_KERBEROS
44 from auth import system_session, admin_session
45 from samba import version, Ldb, substitute_var, valid_netbios_name
46 from samba import check_all_substituted
47 from samba import DS_DOMAIN_FUNCTION_2008_R2, DS_DC_FUNCTION_2008_R2
48 from samba.samdb import SamDB
49 from samba.idmap import IDmapDB
50 from samba.dcerpc import security
52 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, timestring
53 from ms_schema import read_ms_schema
54 from ms_display_specifiers import read_ms_ldif
55 from signal import SIGTERM
57 __docformat__ = "restructuredText"
60 class ProvisioningError(ValueError):
65 """Find the setup directory used by provision."""
66 dirname = os.path.dirname(__file__)
67 if "/site-packages/" in dirname:
68 prefix = "/".join(dirname[:dirname.index("/site-packages/")].split("/")[:-2])
69 for suffix in ["share/setup", "share/samba/setup", "setup"]:
70 ret = os.path.join(prefix, suffix)
71 if os.path.isdir(ret):
74 ret = os.path.join(dirname, "../../../setup")
75 if os.path.isdir(ret):
77 raise Exception("Unable to find setup directory.")
80 DEFAULTSITE = "Default-First-Site-Name"
82 class InvalidNetbiosName(Exception):
83 """A specified name was not a valid NetBIOS name."""
84 def __init__(self, name):
85 super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)
88 class ProvisionPaths(object):
101 self.dns_keytab = None
104 self.private_dir = None
106 self.slapdconf = None
107 self.modulesconf = None
108 self.memberofconf = None
109 self.fedoradsinf = None
110 self.fedoradspartitions = None
111 self.fedoradssasl = None
113 self.olmmrserveridsconf = None
114 self.olmmrsyncreplconf = None
117 self.olcseedldif = None
120 class ProvisionNames(object):
127 self.ldapmanagerdn = None
128 self.dnsdomain = None
130 self.netbiosname = None
137 class ProvisionResult(object):
144 class Schema(object):
145 def __init__(self, setup_path, schemadn=None,
146 serverdn=None, sambadn=None, ldap_backend_type=None):
147 """Load schema for the SamDB from the AD schema files and samba4_schema.ldif
149 :param samdb: Load a schema into a SamDB.
150 :param setup_path: Setup path function.
151 :param schemadn: DN of the schema
152 :param serverdn: DN of the server
154 Returns the schema data loaded, to avoid double-parsing when then needing to add it to the db
158 self.schema_data = read_ms_schema(setup_path('ad-schema/MS-AD_Schema_2K8_Attributes.txt'),
159 setup_path('ad-schema/MS-AD_Schema_2K8_Classes.txt'))
160 self.schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
161 self.schema_data = substitute_var(self.schema_data, {"SCHEMADN": schemadn})
162 check_all_substituted(self.schema_data)
164 self.schema_dn_modify = read_and_sub_file(setup_path("provision_schema_basedn_modify.ldif"),
165 {"SCHEMADN": schemadn,
166 "SERVERDN": serverdn,
168 self.schema_dn_add = read_and_sub_file(setup_path("provision_schema_basedn.ldif"),
169 {"SCHEMADN": schemadn
172 prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
173 prefixmap = b64encode(prefixmap)
175 # We don't actually add this ldif, just parse it
176 prefixmap_ldif = "dn: cn=schema\nprefixMap:: %s\n\n" % prefixmap
177 self.ldb.set_schema_from_ldif(prefixmap_ldif, self.schema_data)
180 # Return a hash with the forward attribute as a key and the back as the value
181 def get_linked_attributes(schemadn,schemaldb):
182 attrs = ["linkID", "lDAPDisplayName"]
183 res = schemaldb.search(expression="(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1))", base=schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
185 for i in range (0, len(res)):
186 expression = "(&(objectclass=attributeSchema)(linkID=%d)(attributeSyntax=2.5.5.1))" % (int(res[i]["linkID"][0])+1)
187 target = schemaldb.searchone(basedn=schemadn,
188 expression=expression,
189 attribute="lDAPDisplayName",
191 if target is not None:
192 attributes[str(res[i]["lDAPDisplayName"])]=str(target)
196 def get_dnsyntax_attributes(schemadn,schemaldb):
197 attrs = ["linkID", "lDAPDisplayName"]
198 res = schemaldb.search(expression="(&(!(linkID=*))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1))", base=schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
200 for i in range (0, len(res)):
201 attributes.append(str(res[i]["lDAPDisplayName"]))
206 def check_install(lp, session_info, credentials):
207 """Check whether the current install seems ok.
209 :param lp: Loadparm context
210 :param session_info: Session information
211 :param credentials: Credentials
213 if lp.get("realm") == "":
214 raise Exception("Realm empty")
215 ldb = Ldb(lp.get("sam database"), session_info=session_info,
216 credentials=credentials, lp=lp)
217 if len(ldb.search("(cn=Administrator)")) != 1:
218 raise ProvisioningError("No administrator account found")
221 def findnss(nssfn, names):
222 """Find a user or group from a list of possibilities.
224 :param nssfn: NSS Function to try (should raise KeyError if not found)
225 :param names: Names to check.
226 :return: Value return by first names list.
233 raise KeyError("Unable to find user/group %r" % names)
236 findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
237 findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
240 def read_and_sub_file(file, subst_vars):
241 """Read a file and sub in variables found in it
243 :param file: File to be read (typically from setup directory)
244 param subst_vars: Optional variables to subsitute in the file.
246 data = open(file, 'r').read()
247 if subst_vars is not None:
248 data = substitute_var(data, subst_vars)
249 check_all_substituted(data)
253 def setup_add_ldif(ldb, ldif_path, subst_vars=None):
254 """Setup a ldb in the private dir.
256 :param ldb: LDB file to import data into
257 :param ldif_path: Path of the LDIF file to load
258 :param subst_vars: Optional variables to subsitute in LDIF.
260 assert isinstance(ldif_path, str)
262 data = read_and_sub_file(ldif_path, subst_vars)
266 def setup_modify_ldif(ldb, ldif_path, subst_vars=None):
267 """Modify a ldb in the private dir.
269 :param ldb: LDB object.
270 :param ldif_path: LDIF file path.
271 :param subst_vars: Optional dictionary with substitution variables.
273 data = read_and_sub_file(ldif_path, subst_vars)
275 ldb.modify_ldif(data)
278 def setup_ldb(ldb, ldif_path, subst_vars):
279 """Import a LDIF a file into a LDB handle, optionally substituting variables.
281 :note: Either all LDIF data will be added or none (using transactions).
283 :param ldb: LDB file to import into.
284 :param ldif_path: Path to the LDIF file.
285 :param subst_vars: Dictionary with substitution variables.
287 assert ldb is not None
288 ldb.transaction_start()
290 setup_add_ldif(ldb, ldif_path, subst_vars)
292 ldb.transaction_cancel()
294 ldb.transaction_commit()
297 def setup_file(template, fname, subst_vars):
298 """Setup a file in the private dir.
300 :param template: Path of the template file.
301 :param fname: Path of the file to create.
302 :param subst_vars: Substitution variables.
306 if os.path.exists(f):
309 data = read_and_sub_file(template, subst_vars)
310 open(f, 'w').write(data)
313 def provision_paths_from_lp(lp, dnsdomain):
314 """Set the default paths for provisioning.
316 :param lp: Loadparm context.
317 :param dnsdomain: DNS Domain name
319 paths = ProvisionPaths()
320 paths.private_dir = lp.get("private dir")
321 paths.keytab = "secrets.keytab"
322 paths.dns_keytab = "dns.keytab"
324 paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
325 paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb")
326 paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb")
327 paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
328 paths.dns = os.path.join(paths.private_dir, dnsdomain + ".zone")
329 paths.namedconf = os.path.join(paths.private_dir, "named.conf")
330 paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
331 paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
332 paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
333 paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
334 paths.phpldapadminconfig = os.path.join(paths.private_dir,
335 "phpldapadmin-config.php")
336 paths.ldapdir = os.path.join(paths.private_dir,
338 paths.slapdconf = os.path.join(paths.ldapdir,
340 paths.slapdpid = os.path.join(paths.ldapdir,
342 paths.modulesconf = os.path.join(paths.ldapdir,
344 paths.memberofconf = os.path.join(paths.ldapdir,
346 paths.fedoradsinf = os.path.join(paths.ldapdir,
348 paths.fedoradspartitions = os.path.join(paths.ldapdir,
349 "fedorads-partitions.ldif")
350 paths.fedoradssasl = os.path.join(paths.ldapdir,
351 "fedorads-sasl.ldif")
352 paths.fedoradssamba = os.path.join(paths.ldapdir,
353 "fedorads-samba.ldif")
354 paths.olmmrserveridsconf = os.path.join(paths.ldapdir,
355 "mmr_serverids.conf")
356 paths.olmmrsyncreplconf = os.path.join(paths.ldapdir,
358 paths.olcdir = os.path.join(paths.ldapdir,
360 paths.olcseedldif = os.path.join(paths.ldapdir,
362 paths.hklm = "hklm.ldb"
363 paths.hkcr = "hkcr.ldb"
364 paths.hkcu = "hkcu.ldb"
365 paths.hku = "hku.ldb"
366 paths.hkpd = "hkpd.ldb"
367 paths.hkpt = "hkpt.ldb"
369 paths.sysvol = lp.get("path", "sysvol")
371 paths.netlogon = lp.get("path", "netlogon")
373 paths.smbconf = lp.configfile
378 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None,
379 serverrole=None, rootdn=None, domaindn=None, configdn=None,
380 schemadn=None, serverdn=None, sitename=None, sambadn=None):
381 """Guess configuration settings to use."""
384 hostname = socket.gethostname().split(".")[0].lower()
386 netbiosname = hostname.upper()
387 if not valid_netbios_name(netbiosname):
388 raise InvalidNetbiosName(netbiosname)
390 hostname = hostname.lower()
392 if dnsdomain is None:
393 dnsdomain = lp.get("realm")
395 if serverrole is None:
396 serverrole = lp.get("server role")
398 assert dnsdomain is not None
399 realm = dnsdomain.upper()
401 if lp.get("realm").upper() != realm:
402 raise Exception("realm '%s' in %s must match chosen realm '%s'" %
403 (lp.get("realm"), lp.configfile, realm))
405 dnsdomain = dnsdomain.lower()
407 if serverrole == "domain controller":
409 domain = lp.get("workgroup")
411 domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
412 if lp.get("workgroup").upper() != domain.upper():
413 raise Exception("workgroup '%s' in smb.conf must match chosen domain '%s'",
414 lp.get("workgroup"), domain)
418 domaindn = "CN=" + netbiosname
420 assert domain is not None
421 domain = domain.upper()
422 if not valid_netbios_name(domain):
423 raise InvalidNetbiosName(domain)
425 if netbiosname.upper() == realm.upper():
426 raise Exception("realm %s must not be equal to netbios domain name %s", realm, netbiosname)
428 if hostname.upper() == realm.upper():
429 raise Exception("realm %s must not be equal to hostname %s", realm, hostname)
431 if domain.upper() == realm.upper():
432 raise Exception("realm %s must not be equal to domain name %s", realm, domain)
438 configdn = "CN=Configuration," + rootdn
440 schemadn = "CN=Schema," + configdn
447 names = ProvisionNames()
448 names.rootdn = rootdn
449 names.domaindn = domaindn
450 names.configdn = configdn
451 names.schemadn = schemadn
452 names.sambadn = sambadn
453 names.ldapmanagerdn = "CN=Manager," + rootdn
454 names.dnsdomain = dnsdomain
455 names.domain = domain
457 names.netbiosname = netbiosname
458 names.hostname = hostname
459 names.sitename = sitename
460 names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (netbiosname, sitename, configdn)
465 def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
467 """Create a new smb.conf file based on a couple of basic settings.
469 assert smbconf is not None
471 hostname = socket.gethostname().split(".")[0].lower()
473 if serverrole is None:
474 serverrole = "standalone"
476 assert serverrole in ("domain controller", "member server", "standalone")
477 if serverrole == "domain controller":
479 elif serverrole == "member server":
480 smbconfsuffix = "member"
481 elif serverrole == "standalone":
482 smbconfsuffix = "standalone"
484 assert domain is not None
485 assert realm is not None
487 default_lp = param.LoadParm()
488 #Load non-existant file
489 if os.path.exists(smbconf):
490 default_lp.load(smbconf)
492 if targetdir is not None:
493 privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
494 lockdir_line = "lock dir = " + os.path.abspath(targetdir)
496 default_lp.set("lock dir", os.path.abspath(targetdir))
501 sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
502 netlogon = os.path.join(sysvol, realm.lower(), "scripts")
504 setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
506 "HOSTNAME": hostname,
509 "SERVERROLE": serverrole,
510 "NETLOGONPATH": netlogon,
511 "SYSVOLPATH": sysvol,
512 "PRIVATEDIR_LINE": privatedir_line,
513 "LOCKDIR_LINE": lockdir_line
517 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
518 users_gid, wheel_gid):
519 """setup reasonable name mappings for sam names to unix names.
521 :param samdb: SamDB object.
522 :param idmap: IDmap db object.
523 :param sid: The domain sid.
524 :param domaindn: The domain DN.
525 :param root_uid: uid of the UNIX root user.
526 :param nobody_uid: uid of the UNIX nobody user.
527 :param users_gid: gid of the UNIX users group.
528 :param wheel_gid: gid of the UNIX wheel group."""
530 idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
531 idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
533 idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
534 idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
536 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
538 serverrole, ldap_backend=None,
540 """Setup the partitions for the SAM database.
542 Alternatively, provision() may call this, and then populate the database.
544 :note: This will wipe the Sam Database!
546 :note: This function always removes the local SAM LDB file. The erase
547 parameter controls whether to erase the existing data, which
548 may not be stored locally but in LDAP.
550 assert session_info is not None
552 # We use options=["modules:"] to stop the modules loading - we
553 # just want to wipe and re-initialise the database, not start it up
556 samdb = Ldb(url=samdb_path, session_info=session_info,
557 credentials=credentials, lp=lp, options=["modules:"])
559 samdb.erase_except_schema_controlled()
561 os.unlink(samdb_path)
562 samdb = Ldb(url=samdb_path, session_info=session_info,
563 credentials=credentials, lp=lp, options=["modules:"])
565 samdb.erase_except_schema_controlled()
568 #Add modules to the list to activate them by default
569 #beware often order is important
571 # Some Known ordering constraints:
572 # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
573 # - objectclass must be before password_hash, because password_hash checks
574 # that the objectclass is of type person (filled in by objectclass
575 # module when expanding the objectclass list)
576 # - partition must be last
577 # - each partition has its own module list then
578 modules_list = ["rootdse",
597 "extended_dn_out_ldb"]
598 modules_list2 = ["show_deleted",
601 domaindn_ldb = "users.ldb"
602 configdn_ldb = "configuration.ldb"
603 schemadn_ldb = "schema.ldb"
604 if ldap_backend is not None:
605 domaindn_ldb = ldap_backend.ldapi_uri
606 configdn_ldb = ldap_backend.ldapi_uri
607 schemadn_ldb = ldap_backend.ldapi_uri
609 if ldap_backend.ldap_backend_type == "fedora-ds":
610 backend_modules = ["nsuniqueid", "paged_searches"]
611 # We can handle linked attributes here, as we don't have directory-side subtree operations
612 tdb_modules_list = ["linked_attributes", "extended_dn_out_dereference"]
613 elif ldap_backend.ldap_backend_type == "openldap":
614 backend_modules = ["entryuuid", "paged_searches"]
615 # OpenLDAP handles subtree renames, so we don't want to do any of these things
616 tdb_modules_list = ["extended_dn_out_dereference"]
618 elif serverrole == "domain controller":
619 tdb_modules_list.insert(0, "repl_meta_data")
622 backend_modules = ["objectguid"]
624 if tdb_modules_list is None:
625 tdb_modules_list_as_string = ""
627 tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
629 samdb.transaction_start()
631 message("Setting up sam.ldb partitions and settings")
632 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
633 "SCHEMADN": names.schemadn,
634 "SCHEMADN_LDB": schemadn_ldb,
635 "SCHEMADN_MOD2": ",objectguid",
636 "CONFIGDN": names.configdn,
637 "CONFIGDN_LDB": configdn_ldb,
638 "DOMAINDN": names.domaindn,
639 "DOMAINDN_LDB": domaindn_ldb,
640 "SCHEMADN_MOD": "schema_fsmo,instancetype",
641 "CONFIGDN_MOD": "naming_fsmo,instancetype",
642 "DOMAINDN_MOD": "pdc_fsmo,instancetype",
643 "MODULES_LIST": ",".join(modules_list),
644 "TDB_MODULES_LIST": tdb_modules_list_as_string,
645 "MODULES_LIST2": ",".join(modules_list2),
646 "BACKEND_MOD": ",".join(backend_modules),
649 samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
651 message("Setting up sam.ldb rootDSE")
652 setup_samdb_rootdse(samdb, setup_path, names)
655 samdb.transaction_cancel()
658 samdb.transaction_commit()
662 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain,
663 netbiosname, domainsid, keytab_path, samdb_url,
664 dns_keytab_path, dnspass, machinepass):
665 """Add DC-specific bits to a secrets database.
667 :param secretsdb: Ldb Handle to the secrets database
668 :param setup_path: Setup path function
669 :param machinepass: Machine password
671 setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), {
672 "MACHINEPASS_B64": b64encode(machinepass),
675 "DNSDOMAIN": dnsdomain,
676 "DOMAINSID": str(domainsid),
677 "SECRETS_KEYTAB": keytab_path,
678 "NETBIOSNAME": netbiosname,
679 "SAM_LDB": samdb_url,
680 "DNS_KEYTAB": dns_keytab_path,
681 "DNSPASS_B64": b64encode(dnspass),
685 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
686 """Setup the secrets database.
688 :param path: Path to the secrets database.
689 :param setup_path: Get the path to a setup file.
690 :param session_info: Session info.
691 :param credentials: Credentials
692 :param lp: Loadparm context
693 :return: LDB handle for the created secrets database
695 if os.path.exists(path):
697 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
700 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
701 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
703 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
705 if credentials is not None and credentials.authentication_requested():
706 if credentials.get_bind_dn() is not None:
707 setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
708 "LDAPMANAGERDN": credentials.get_bind_dn(),
709 "LDAPMANAGERPASS_B64": b64encode(credentials.get_password())
712 setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
713 "LDAPADMINUSER": credentials.get_username(),
714 "LDAPADMINREALM": credentials.get_realm(),
715 "LDAPADMINPASS_B64": b64encode(credentials.get_password())
720 def setup_registry(path, setup_path, session_info, lp):
721 """Setup the registry.
723 :param path: Path to the registry database
724 :param setup_path: Function that returns the path to a setup.
725 :param session_info: Session information
726 :param credentials: Credentials
727 :param lp: Loadparm context
729 reg = registry.Registry()
730 hive = registry.open_ldb(path, session_info=session_info,
732 reg.mount_hive(hive, registry.HKEY_LOCAL_MACHINE)
733 provision_reg = setup_path("provision.reg")
734 assert os.path.exists(provision_reg)
735 reg.diff_apply(provision_reg)
738 def setup_idmapdb(path, setup_path, session_info, lp):
739 """Setup the idmap database.
741 :param path: path to the idmap database
742 :param setup_path: Function that returns a path to a setup file
743 :param session_info: Session information
744 :param credentials: Credentials
745 :param lp: Loadparm context
747 if os.path.exists(path):
750 idmap_ldb = IDmapDB(path, session_info=session_info,
754 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
758 def setup_samdb_rootdse(samdb, setup_path, names):
759 """Setup the SamDB rootdse.
761 :param samdb: Sam Database handle
762 :param setup_path: Obtain setup path
764 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
765 "SCHEMADN": names.schemadn,
766 "NETBIOSNAME": names.netbiosname,
767 "DNSDOMAIN": names.dnsdomain,
768 "REALM": names.realm,
769 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
770 "DOMAINDN": names.domaindn,
771 "ROOTDN": names.rootdn,
772 "CONFIGDN": names.configdn,
773 "SERVERDN": names.serverdn,
777 def setup_self_join(samdb, names,
778 machinepass, dnspass,
779 domainsid, invocationid, setup_path,
780 policyguid, policyguid_dc, domainControllerFunctionality):
781 """Join a host to its own domain."""
782 assert isinstance(invocationid, str)
783 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
784 "CONFIGDN": names.configdn,
785 "SCHEMADN": names.schemadn,
786 "DOMAINDN": names.domaindn,
787 "SERVERDN": names.serverdn,
788 "INVOCATIONID": invocationid,
789 "NETBIOSNAME": names.netbiosname,
790 "DEFAULTSITE": names.sitename,
791 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
792 "MACHINEPASS_B64": b64encode(machinepass),
793 "DNSPASS_B64": b64encode(dnspass),
794 "REALM": names.realm,
795 "DOMAIN": names.domain,
796 "DNSDOMAIN": names.dnsdomain,
797 "SAMBA_VERSION_STRING": version,
798 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
800 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
801 "POLICYGUID": policyguid,
802 "POLICYGUID_DC": policyguid_dc,
803 "DNSDOMAIN": names.dnsdomain,
804 "DOMAINSID": str(domainsid),
805 "DOMAINDN": names.domaindn})
807 # add the NTDSGUID based SPNs
808 ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
809 names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
810 expression="", scope=SCOPE_BASE)
811 assert isinstance(names.ntdsguid, str)
813 # Setup fSMORoleOwner entries to point at the newly created DC entry
814 setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
815 "DOMAIN": names.domain,
816 "DNSDOMAIN": names.dnsdomain,
817 "DOMAINDN": names.domaindn,
818 "CONFIGDN": names.configdn,
819 "SCHEMADN": names.schemadn,
820 "DEFAULTSITE": names.sitename,
821 "SERVERDN": names.serverdn,
822 "NETBIOSNAME": names.netbiosname,
823 "NTDSGUID": names.ntdsguid
827 def setup_samdb(path, setup_path, session_info, credentials, lp,
829 domainsid, domainguid, policyguid, policyguid_dc,
830 fill, adminpass, krbtgtpass,
831 machinepass, invocationid, dnspass,
832 serverrole, schema=None, ldap_backend=None):
833 """Setup a complete SAM Database.
835 :note: This will wipe the main SAM database file!
838 domainFunctionality = DS_DOMAIN_FUNCTION_2008_R2
839 forestFunctionality = DS_DOMAIN_FUNCTION_2008_R2
840 domainControllerFunctionality = DS_DC_FUNCTION_2008_R2
842 # Also wipes the database
843 setup_samdb_partitions(path, setup_path, message=message, lp=lp,
844 credentials=credentials, session_info=session_info,
846 ldap_backend=ldap_backend, serverrole=serverrole)
849 schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn,
850 sambadn=names.sambadn, ldap_backend_type=ldap_backend.ldap_backend_type)
852 # Load the database, but importantly, use Ldb not SamDB as we don't want to load the global schema
853 samdb = Ldb(session_info=session_info,
854 credentials=credentials, lp=lp)
856 message("Pre-loading the Samba 4 and AD schema")
858 # Load the schema from the one we computed earlier
859 samdb.set_schema_from_ldb(schema.ldb)
861 # And now we can connect to the DB - the schema won't be loaded from the DB
865 samdb.load_ldif_file_add(setup_path("provision_options.ldif"))
870 samdb.transaction_start()
872 message("Erasing data from partitions")
873 # Load the schema (again). This time it will force a reindex,
874 # and will therefore make the erase_partitions() below
875 # computationally sane
876 samdb.set_schema_from_ldb(schema.ldb)
877 samdb.erase_partitions()
879 # Set the domain functionality levels onto the database.
880 # Various module (the password_hash module in particular) need
881 # to know what level of AD we are emulating.
883 # These will be fixed into the database via the database
884 # modifictions below, but we need them set from the start.
885 samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
886 samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
887 samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
889 samdb.set_domain_sid(str(domainsid))
890 if serverrole == "domain controller":
891 samdb.set_invocation_id(invocationid)
893 message("Adding DomainDN: %s" % names.domaindn)
894 if serverrole == "domain controller":
895 domain_oc = "domainDNS"
897 domain_oc = "samba4LocalDomain"
899 #impersonate domain admin
900 admin_session_info = admin_session(lp, str(domainsid))
901 samdb.set_session_info(admin_session_info)
903 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
904 "DOMAINDN": names.domaindn,
905 "DOMAIN_OC": domain_oc
908 message("Modifying DomainDN: " + names.domaindn + "")
909 if domainguid is not None:
910 domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
914 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
915 "CREATTIME": str(int(time.time()) * 1e7), # seconds -> ticks
916 "DOMAINSID": str(domainsid),
917 "SCHEMADN": names.schemadn,
918 "NETBIOSNAME": names.netbiosname,
919 "DEFAULTSITE": names.sitename,
920 "CONFIGDN": names.configdn,
921 "SERVERDN": names.serverdn,
922 "POLICYGUID": policyguid,
923 "DOMAINDN": names.domaindn,
924 "DOMAINGUID_MOD": domainguid_mod,
925 "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
926 "SAMBA_VERSION_STRING": version
929 message("Adding configuration container")
930 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
931 "CONFIGDN": names.configdn,
933 message("Modifying configuration container")
934 setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
935 "CONFIGDN": names.configdn,
936 "SCHEMADN": names.schemadn,
939 # The LDIF here was created when the Schema object was constructed
940 message("Setting up sam.ldb schema")
941 samdb.add_ldif(schema.schema_dn_add)
942 samdb.modify_ldif(schema.schema_dn_modify)
943 samdb.write_prefixes_from_schema()
944 samdb.add_ldif(schema.schema_data)
945 setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
946 {"SCHEMADN": names.schemadn})
948 message("Setting up sam.ldb configuration data")
949 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
950 "CONFIGDN": names.configdn,
951 "NETBIOSNAME": names.netbiosname,
952 "DEFAULTSITE": names.sitename,
953 "DNSDOMAIN": names.dnsdomain,
954 "DOMAIN": names.domain,
955 "SCHEMADN": names.schemadn,
956 "DOMAINDN": names.domaindn,
957 "SERVERDN": names.serverdn,
958 "FOREST_FUNCTIONALALITY": str(forestFunctionality)
961 message("Setting up display specifiers")
962 display_specifiers_ldif = read_ms_ldif(setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
963 display_specifiers_ldif = substitute_var(display_specifiers_ldif, {"CONFIGDN": names.configdn})
964 check_all_substituted(display_specifiers_ldif)
965 samdb.add_ldif(display_specifiers_ldif)
967 message("Adding users container")
968 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
969 "DOMAINDN": names.domaindn})
970 message("Modifying users container")
971 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
972 "DOMAINDN": names.domaindn})
973 message("Adding computers container")
974 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
975 "DOMAINDN": names.domaindn})
976 message("Modifying computers container")
977 setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
978 "DOMAINDN": names.domaindn})
979 message("Setting up sam.ldb data")
980 setup_add_ldif(samdb, setup_path("provision.ldif"), {
981 "DOMAINDN": names.domaindn,
982 "NETBIOSNAME": names.netbiosname,
983 "DEFAULTSITE": names.sitename,
984 "CONFIGDN": names.configdn,
985 "SERVERDN": names.serverdn,
986 "POLICYGUID_DC": policyguid_dc
989 if fill == FILL_FULL:
990 message("Setting up sam.ldb users and groups")
991 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
992 "DOMAINDN": names.domaindn,
993 "DOMAINSID": str(domainsid),
994 "CONFIGDN": names.configdn,
995 "ADMINPASS_B64": b64encode(adminpass),
996 "KRBTGTPASS_B64": b64encode(krbtgtpass),
999 if serverrole == "domain controller":
1000 message("Setting up self join")
1001 setup_self_join(samdb, names=names, invocationid=invocationid,
1003 machinepass=machinepass,
1004 domainsid=domainsid, policyguid=policyguid,
1005 policyguid_dc=policyguid_dc,
1006 setup_path=setup_path,
1007 domainControllerFunctionality=domainControllerFunctionality)
1008 # add the NTDSGUID based SPNs
1009 ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
1010 names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
1011 expression="", scope=SCOPE_BASE)
1012 assert isinstance(names.ntdsguid, str)
1015 samdb.transaction_cancel()
1018 samdb.transaction_commit()
1023 FILL_NT4SYNC = "NT4SYNC"
1027 def provision(setup_dir, message, session_info,
1028 credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL,
1030 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1032 domain=None, hostname=None, hostip=None, hostip6=None,
1033 domainsid=None, adminpass=None, ldapadminpass=None,
1034 krbtgtpass=None, domainguid=None,
1035 policyguid=None, policyguid_dc=None, invocationid=None,
1037 dnspass=None, root=None, nobody=None, users=None,
1038 wheel=None, backup=None, aci=None, serverrole=None,
1039 ldap_backend_extra_port=None, ldap_backend_type=None,
1041 ol_mmr_urls=None, ol_olc=None,
1042 setup_ds_path=None, slapd_path=None, nosync=False,
1043 ldap_dryrun_mode=False):
1046 :note: caution, this wipes all existing data!
1049 def setup_path(file):
1050 return os.path.join(setup_dir, file)
1052 if domainsid is None:
1053 domainsid = security.random_sid()
1055 # create/adapt the group policy GUIDs
1056 if policyguid is None:
1057 policyguid = str(uuid.uuid4())
1058 policyguid = policyguid.upper()
1059 if policyguid_dc is None:
1060 policyguid_dc = str(uuid.uuid4())
1061 policyguid_dc = policyguid_dc.upper()
1063 if adminpass is None:
1064 adminpass = glue.generate_random_str(12)
1065 if krbtgtpass is None:
1066 krbtgtpass = glue.generate_random_str(12)
1067 if machinepass is None:
1068 machinepass = glue.generate_random_str(12)
1070 dnspass = glue.generate_random_str(12)
1071 if ldapadminpass is None:
1072 #Make a new, random password between Samba and it's LDAP server
1073 ldapadminpass=glue.generate_random_str(12)
1076 root_uid = findnss_uid([root or "root"])
1077 nobody_uid = findnss_uid([nobody or "nobody"])
1078 users_gid = findnss_gid([users or "users"])
1080 wheel_gid = findnss_gid(["wheel", "adm"])
1082 wheel_gid = findnss_gid([wheel])
1084 if targetdir is not None:
1085 if (not os.path.exists(os.path.join(targetdir, "etc"))):
1086 os.makedirs(os.path.join(targetdir, "etc"))
1087 smbconf = os.path.join(targetdir, "etc", "smb.conf")
1088 elif smbconf is None:
1089 smbconf = param.default_path()
1091 # only install a new smb.conf if there isn't one there already
1092 if not os.path.exists(smbconf):
1093 make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
1096 lp = param.LoadParm()
1099 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1100 dnsdomain=realm, serverrole=serverrole, sitename=sitename,
1101 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1104 paths = provision_paths_from_lp(lp, names.dnsdomain)
1108 hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1109 except socket.gaierror, (socket.EAI_NODATA, msg):
1114 hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1115 except socket.gaierror, (socket.EAI_NODATA, msg):
1118 if serverrole is None:
1119 serverrole = lp.get("server role")
1121 assert serverrole in ("domain controller", "member server", "standalone")
1122 if invocationid is None and serverrole == "domain controller":
1123 invocationid = str(uuid.uuid4())
1125 if not os.path.exists(paths.private_dir):
1126 os.mkdir(paths.private_dir)
1128 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1130 schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn,
1131 sambadn=names.sambadn, ldap_backend_type=ldap_backend_type)
1133 secrets_credentials = credentials
1134 provision_backend = None
1135 if ldap_backend_type:
1136 # We only support an LDAP backend over ldapi://
1138 provision_backend = ProvisionBackend(paths=paths, setup_path=setup_path,
1139 lp=lp, credentials=credentials,
1141 message=message, hostname=hostname,
1142 root=root, schema=schema,
1143 ldap_backend_type=ldap_backend_type,
1144 ldapadminpass=ldapadminpass,
1145 ldap_backend_extra_port=ldap_backend_extra_port,
1146 ol_mmr_urls=ol_mmr_urls,
1147 slapd_path=slapd_path,
1148 setup_ds_path=setup_ds_path,
1149 ldap_dryrun_mode=ldap_dryrun_mode)
1151 # Now use the backend credentials to access the databases
1152 credentials = provision_backend.credentials
1153 secrets_credentials = provision_backend.adminCredentials
1154 ldapi_url = provision_backend.ldapi_uri
1156 # only install a new shares config db if there is none
1157 if not os.path.exists(paths.shareconf):
1158 message("Setting up share.ldb")
1159 share_ldb = Ldb(paths.shareconf, session_info=session_info,
1160 credentials=credentials, lp=lp)
1161 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1164 message("Setting up secrets.ldb")
1165 secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
1166 session_info=session_info,
1167 credentials=secrets_credentials, lp=lp)
1169 message("Setting up the registry")
1170 setup_registry(paths.hklm, setup_path, session_info,
1173 message("Setting up idmap db")
1174 idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1177 message("Setting up SAM db")
1178 samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info,
1179 credentials=credentials, lp=lp, names=names,
1181 domainsid=domainsid,
1182 schema=schema, domainguid=domainguid,
1183 policyguid=policyguid, policyguid_dc=policyguid_dc,
1185 adminpass=adminpass, krbtgtpass=krbtgtpass,
1186 invocationid=invocationid,
1187 machinepass=machinepass, dnspass=dnspass,
1188 serverrole=serverrole, ldap_backend=provision_backend)
1190 if serverrole == "domain controller":
1191 if paths.netlogon is None:
1192 message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1193 message("Please either remove %s or see the template at %s" %
1194 ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1195 assert(paths.netlogon is not None)
1197 if paths.sysvol is None:
1198 message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1199 message("Please either remove %s or see the template at %s" %
1200 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1201 assert(paths.sysvol is not None)
1203 # Set up group policies (domain policy and domain controller policy)
1205 policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1206 "{" + policyguid + "}")
1207 os.makedirs(policy_path, 0755)
1208 open(os.path.join(policy_path, "GPT.INI"), 'w').write(
1209 "[General]\r\nVersion=65543")
1210 os.makedirs(os.path.join(policy_path, "MACHINE"), 0755)
1211 os.makedirs(os.path.join(policy_path, "USER"), 0755)
1213 policy_path_dc = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1214 "{" + policyguid_dc + "}")
1215 os.makedirs(policy_path_dc, 0755)
1216 open(os.path.join(policy_path_dc, "GPT.INI"), 'w').write(
1217 "[General]\r\nVersion=2")
1218 os.makedirs(os.path.join(policy_path_dc, "MACHINE"), 0755)
1219 os.makedirs(os.path.join(policy_path_dc, "USER"), 0755)
1221 if not os.path.isdir(paths.netlogon):
1222 os.makedirs(paths.netlogon, 0755)
1224 if samdb_fill == FILL_FULL:
1225 setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1226 root_uid=root_uid, nobody_uid=nobody_uid,
1227 users_gid=users_gid, wheel_gid=wheel_gid)
1229 message("Setting up sam.ldb rootDSE marking as synchronized")
1230 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1232 # Only make a zone file on the first DC, it should be replicated with DNS replication
1233 if serverrole == "domain controller":
1234 secrets_ldb = Ldb(paths.secrets, session_info=session_info,
1235 credentials=credentials, lp=lp)
1236 secretsdb_become_dc(secrets_ldb, setup_path, domain=domain,
1238 netbiosname=names.netbiosname,
1239 domainsid=domainsid,
1240 keytab_path=paths.keytab, samdb_url=paths.samdb,
1241 dns_keytab_path=paths.dns_keytab,
1242 dnspass=dnspass, machinepass=machinepass,
1243 dnsdomain=names.dnsdomain)
1245 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1246 assert isinstance(domainguid, str)
1248 create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1249 domaindn=names.domaindn, hostip=hostip,
1250 hostip6=hostip6, hostname=names.hostname,
1251 dnspass=dnspass, realm=names.realm,
1252 domainguid=domainguid, ntdsguid=names.ntdsguid)
1254 create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1255 dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1257 create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1258 dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1259 keytab_name=paths.dns_keytab)
1260 message("See %s for an example configuration include file for BIND" % paths.namedconf)
1261 message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1263 create_krb5_conf(paths.krb5conf, setup_path,
1264 dnsdomain=names.dnsdomain, hostname=names.hostname,
1266 message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1269 if provision_backend is not None:
1270 if ldap_backend_type == "fedora-ds":
1271 ldapi_db = Ldb(provision_backend.ldapi_uri, lp=lp, credentials=credentials)
1273 # delete default SASL mappings
1274 res = ldapi_db.search(expression="(!(cn=samba-admin mapping))", base="cn=mapping,cn=sasl,cn=config", scope=SCOPE_ONELEVEL, attrs=["dn"])
1276 # configure in-directory access control on Fedora DS via the aci attribute (over a direct ldapi:// socket)
1277 for i in range (0, len(res)):
1278 dn = str(res[i]["dn"])
1281 aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % names.sambadn
1284 m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci")
1286 m.dn = ldb.Dn(1, names.domaindn)
1289 m.dn = ldb.Dn(1, names.configdn)
1292 m.dn = ldb.Dn(1, names.schemadn)
1295 # if an LDAP backend is in use, terminate slapd after final provision and check its proper termination
1296 if provision_backend.slapd.poll() is None:
1298 if hasattr(provision_backend.slapd, "terminate"):
1299 provision_backend.slapd.terminate()
1301 # Older python versions don't have .terminate()
1303 os.kill(provision_backend.slapd.pid, signal.SIGTERM)
1305 #and now wait for it to die
1306 provision_backend.slapd.communicate()
1308 # now display slapd_command_file.txt to show how slapd must be started next time
1309 message("Use later the following commandline to start slapd, then Samba:")
1310 slapd_command = "\'" + "\' \'".join(provision_backend.slapd_command) + "\'"
1311 message(slapd_command)
1312 message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
1314 setup_file(setup_path("ldap_backend_startup.sh"), paths.ldapdir + "/ldap_backend_startup.sh", {
1315 "SLAPD_COMMAND" : slapd_command})
1318 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1321 message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1323 message("Once the above files are installed, your Samba4 server will be ready to use")
1324 message("Server Role: %s" % serverrole)
1325 message("Hostname: %s" % names.hostname)
1326 message("NetBIOS Domain: %s" % names.domain)
1327 message("DNS Domain: %s" % names.dnsdomain)
1328 message("DOMAIN SID: %s" % str(domainsid))
1329 if samdb_fill == FILL_FULL:
1330 message("Admin password: %s" % adminpass)
1331 if provision_backend:
1332 if provision_backend.credentials.get_bind_dn() is not None:
1333 message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1335 message("LDAP Admin User: %s" % provision_backend.credentials.get_username())
1337 message("LDAP Admin Password: %s" % provision_backend.credentials.get_password())
1339 result = ProvisionResult()
1340 result.domaindn = domaindn
1341 result.paths = paths
1343 result.samdb = samdb
1348 def provision_become_dc(setup_dir=None,
1349 smbconf=None, targetdir=None, realm=None,
1350 rootdn=None, domaindn=None, schemadn=None,
1351 configdn=None, serverdn=None,
1352 domain=None, hostname=None, domainsid=None,
1353 adminpass=None, krbtgtpass=None, domainguid=None,
1354 policyguid=None, policyguid_dc=None, invocationid=None,
1356 dnspass=None, root=None, nobody=None, users=None,
1357 wheel=None, backup=None, serverrole=None,
1358 ldap_backend=None, ldap_backend_type=None,
1359 sitename=None, debuglevel=1):
1362 """print a message if quiet is not set."""
1365 glue.set_debug_level(debuglevel)
1367 return provision(setup_dir, message, system_session(), None,
1368 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1369 realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1370 configdn=configdn, serverdn=serverdn, domain=domain,
1371 hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
1372 machinepass=machinepass, serverrole="domain controller",
1376 def setup_db_config(setup_path, dbdir):
1377 """Setup a Berkeley database.
1379 :param setup_path: Setup path function.
1380 :param dbdir: Database directory."""
1381 if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1382 os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1383 if not os.path.isdir(os.path.join(dbdir, "tmp")):
1384 os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1386 setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1387 {"LDAPDBDIR": dbdir})
1389 class ProvisionBackend(object):
1390 def __init__(self, paths=None, setup_path=None, lp=None, credentials=None,
1391 names=None, message=None,
1392 hostname=None, root=None,
1393 schema=None, ldapadminpass=None,
1394 ldap_backend_type=None, ldap_backend_extra_port=None,
1396 setup_ds_path=None, slapd_path=None,
1397 nosync=False, ldap_dryrun_mode=False):
1398 """Provision an LDAP backend for samba4
1400 This works for OpenLDAP and Fedora DS
1403 self.ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.ldapdir, "ldapi"), safe="")
1405 if not os.path.isdir(paths.ldapdir):
1406 os.makedirs(paths.ldapdir, 0700)
1408 if ldap_backend_type == "existing":
1409 #Check to see that this 'existing' LDAP backend in fact exists
1410 ldapi_db = Ldb(self.ldapi_uri, credentials=credentials)
1411 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1412 expression="(objectClass=OpenLDAProotDSE)")
1414 # If we have got here, then we must have a valid connection to the LDAP server, with valid credentials supplied
1415 # This caused them to be set into the long-term database later in the script.
1416 self.credentials = credentials
1417 self.ldap_backend_type = "openldap" #For now, assume existing backends at least emulate OpenLDAP
1420 # we will shortly start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via self.ldapi_uri
1421 # if another instance of slapd is already running
1423 ldapi_db = Ldb(self.ldapi_uri)
1424 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1425 expression="(objectClass=OpenLDAProotDSE)");
1427 f = open(paths.slapdpid, "r")
1430 message("Check for slapd Process with PID: " + str(p) + " and terminate it manually.")
1434 raise ProvisioningError("Warning: Another slapd Instance seems already running on this host, listening to " + self.ldapi_uri + ". Please shut it down before you continue. ")
1439 # Try to print helpful messages when the user has not specified the path to slapd
1440 if slapd_path is None:
1441 raise ProvisioningError("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
1442 if not os.path.exists(slapd_path):
1443 message (slapd_path)
1444 raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1446 schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1448 os.unlink(schemadb_path)
1453 # Put the LDIF of the schema into a database so we can search on
1454 # it to generate schema-dependent configurations in Fedora DS and
1456 os.path.join(paths.ldapdir, "schema-tmp.ldb")
1457 schema.ldb.connect(schemadb_path)
1458 schema.ldb.transaction_start()
1460 # These bits of LDIF are supplied when the Schema object is created
1461 schema.ldb.add_ldif(schema.schema_dn_add)
1462 schema.ldb.modify_ldif(schema.schema_dn_modify)
1463 schema.ldb.add_ldif(schema.schema_data)
1464 schema.ldb.transaction_commit()
1466 self.credentials = Credentials()
1467 self.credentials.guess(lp)
1468 #Kerberos to an ldapi:// backend makes no sense
1469 self.credentials.set_kerberos_state(DONT_USE_KERBEROS)
1471 self.adminCredentials = Credentials()
1472 self.adminCredentials.guess(lp)
1473 #Kerberos to an ldapi:// backend makes no sense
1474 self.adminCredentials.set_kerberos_state(DONT_USE_KERBEROS)
1476 self.ldap_backend_type = ldap_backend_type
1478 if ldap_backend_type == "fedora-ds":
1479 provision_fds_backend(self, paths=paths, setup_path=setup_path,
1480 names=names, message=message,
1482 ldapadminpass=ldapadminpass, root=root,
1484 ldap_backend_extra_port=ldap_backend_extra_port,
1485 setup_ds_path=setup_ds_path,
1486 slapd_path=slapd_path,
1488 ldap_dryrun_mode=ldap_dryrun_mode)
1490 elif ldap_backend_type == "openldap":
1491 provision_openldap_backend(self, paths=paths, setup_path=setup_path,
1492 names=names, message=message,
1494 ldapadminpass=ldapadminpass, root=root,
1496 ldap_backend_extra_port=ldap_backend_extra_port,
1497 ol_mmr_urls=ol_mmr_urls,
1498 slapd_path=slapd_path,
1500 ldap_dryrun_mode=ldap_dryrun_mode)
1502 raise ProvisioningError("Unknown LDAP backend type selected")
1504 self.credentials.set_password(ldapadminpass)
1505 self.adminCredentials.set_username("samba-admin")
1506 self.adminCredentials.set_password(ldapadminpass)
1508 # Now start the slapd, so we can provision onto it. We keep the
1509 # subprocess context around, to kill this off at the successful
1511 self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False)
1513 while self.slapd.poll() is None:
1514 # Wait until the socket appears
1516 ldapi_db = Ldb(self.ldapi_uri, lp=lp, credentials=self.credentials)
1517 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1518 expression="(objectClass=OpenLDAProotDSE)")
1519 # If we have got here, then we must have a valid connection to the LDAP server!
1525 raise ProvisioningError("slapd died before we could make a connection to it")
1528 def provision_openldap_backend(result, paths=None, setup_path=None, names=None,
1530 hostname=None, ldapadminpass=None, root=None,
1532 ldap_backend_extra_port=None,
1534 slapd_path=None, nosync=False,
1535 ldap_dryrun_mode=False):
1537 #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
1540 nosync_config = "dbnosync"
1542 lnkattr = get_linked_attributes(names.schemadn,schema.ldb)
1543 refint_attributes = ""
1544 memberof_config = "# Generated from Samba4 schema\n"
1545 for att in lnkattr.keys():
1546 if lnkattr[att] is not None:
1547 refint_attributes = refint_attributes + " " + att
1549 memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1550 { "MEMBER_ATTR" : att ,
1551 "MEMBEROF_ATTR" : lnkattr[att] })
1553 refint_config = read_and_sub_file(setup_path("refint.conf"),
1554 { "LINK_ATTRS" : refint_attributes})
1556 attrs = ["linkID", "lDAPDisplayName"]
1557 res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1559 for i in range (0, len(res)):
1560 index_attr = res[i]["lDAPDisplayName"][0]
1561 if index_attr == "objectGUID":
1562 index_attr = "entryUUID"
1564 index_config += "index " + index_attr + " eq\n"
1566 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1568 mmr_replicator_acl = ""
1569 mmr_serverids_config = ""
1570 mmr_syncrepl_schema_config = ""
1571 mmr_syncrepl_config_config = ""
1572 mmr_syncrepl_user_config = ""
1575 if ol_mmr_urls is not None:
1576 # For now, make these equal
1577 mmr_pass = ldapadminpass
1579 url_list=filter(None,ol_mmr_urls.split(' '))
1580 if (len(url_list) == 1):
1581 url_list=filter(None,ol_mmr_urls.split(','))
1584 mmr_on_config = "MirrorMode On"
1585 mmr_replicator_acl = " by dn=cn=replicator,cn=samba read"
1587 for url in url_list:
1589 mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1590 { "SERVERID" : str(serverid),
1591 "LDAPSERVER" : url })
1594 mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1596 "MMRDN": names.schemadn,
1598 "MMR_PASSWORD": mmr_pass})
1601 mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1603 "MMRDN": names.configdn,
1605 "MMR_PASSWORD": mmr_pass})
1608 mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1610 "MMRDN": names.domaindn,
1612 "MMR_PASSWORD": mmr_pass })
1613 # OpenLDAP cn=config initialisation
1614 olc_syncrepl_config = ""
1616 # if mmr = yes, generate cn=config-replication directives
1617 # and olc_seed.lif for the other mmr-servers
1618 if ol_mmr_urls is not None:
1620 olc_serverids_config = ""
1621 olc_syncrepl_seed_config = ""
1622 olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
1624 for url in url_list:
1626 olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
1627 { "SERVERID" : str(serverid),
1628 "LDAPSERVER" : url })
1631 olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
1634 "MMR_PASSWORD": mmr_pass})
1636 olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1638 "LDAPSERVER" : url})
1640 setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif,
1641 {"OLC_SERVER_ID_CONF": olc_serverids_config,
1642 "OLC_PW": ldapadminpass,
1643 "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
1646 setup_file(setup_path("slapd.conf"), paths.slapdconf,
1647 {"DNSDOMAIN": names.dnsdomain,
1648 "LDAPDIR": paths.ldapdir,
1649 "DOMAINDN": names.domaindn,
1650 "CONFIGDN": names.configdn,
1651 "SCHEMADN": names.schemadn,
1652 "MEMBEROF_CONFIG": memberof_config,
1653 "MIRRORMODE": mmr_on_config,
1654 "REPLICATOR_ACL": mmr_replicator_acl,
1655 "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1656 "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1657 "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1658 "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1659 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
1660 "OLC_MMR_CONFIG": olc_mmr_config,
1661 "REFINT_CONFIG": refint_config,
1662 "INDEX_CONFIG": index_config,
1663 "NOSYNC": nosync_config})
1665 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1666 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1667 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1669 if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba", "cn=samba")):
1670 os.makedirs(os.path.join(paths.ldapdir, "db", "samba", "cn=samba"), 0700)
1672 setup_file(setup_path("cn=samba.ldif"),
1673 os.path.join(paths.ldapdir, "db", "samba", "cn=samba.ldif"),
1674 { "UUID": str(uuid.uuid4()),
1675 "LDAPTIME": timestring(int(time.time()))} )
1676 setup_file(setup_path("cn=samba-admin.ldif"),
1677 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=samba-admin.ldif"),
1678 {"LDAPADMINPASS_B64": b64encode(ldapadminpass),
1679 "UUID": str(uuid.uuid4()),
1680 "LDAPTIME": timestring(int(time.time()))} )
1682 if ol_mmr_urls is not None:
1683 setup_file(setup_path("cn=replicator.ldif"),
1684 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=replicator.ldif"),
1685 {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1686 "UUID": str(uuid.uuid4()),
1687 "LDAPTIME": timestring(int(time.time()))} )
1690 mapping = "schema-map-openldap-2.3"
1691 backend_schema = "backend-schema.schema"
1693 backend_schema_data = schema.ldb.convert_schema_to_openldap("openldap", open(setup_path(mapping), 'r').read())
1694 assert backend_schema_data is not None
1695 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1697 # now we generate the needed strings to start slapd automatically,
1698 # first ldapi_uri...
1699 if ldap_backend_extra_port is not None:
1700 # When we use MMR, we can't use 0.0.0.0 as it uses the name
1701 # specified there as part of it's clue as to it's own name,
1702 # and not to replicate to itself
1703 if ol_mmr_urls is None:
1704 server_port_string = "ldap://0.0.0.0:%d" % ldap_backend_extra_port
1706 server_port_string = "ldap://" + names.hostname + "." + names.dnsdomain +":%d" % ldap_backend_extra_port
1708 server_port_string = ""
1710 # Prepare the 'result' information - the commands to return in particular
1711 result.slapd_provision_command = [slapd_path]
1713 result.slapd_provision_command.append("-F" + paths.olcdir)
1715 result.slapd_provision_command.append("-h")
1717 # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands
1718 result.slapd_command = list(result.slapd_provision_command)
1720 result.slapd_provision_command.append(result.ldapi_uri)
1721 result.slapd_provision_command.append("-d0")
1723 uris = result.ldapi_uri
1724 if server_port_string is not "":
1725 uris = uris + " " + server_port_string
1727 result.slapd_command.append(uris)
1729 # Set the username - done here because Fedora DS still uses the admin DN and simple bind
1730 result.credentials.set_username("samba-admin")
1732 # If we were just looking for crashes up to this point, it's a
1733 # good time to exit before we realise we don't have OpenLDAP on
1735 if ldap_dryrun_mode:
1738 # Finally, convert the configuration into cn=config style!
1739 if not os.path.isdir(paths.olcdir):
1740 os.makedirs(paths.olcdir, 0770)
1742 retcode = subprocess.call([slapd_path, "-Ttest", "-f", paths.slapdconf, "-F", paths.olcdir], close_fds=True, shell=False)
1744 # We can't do this, as OpenLDAP is strange. It gives an error
1745 # output to the above, but does the conversion sucessfully...
1748 # raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1750 if not os.path.exists(os.path.join(paths.olcdir, "cn=config.ldif")):
1751 raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1753 # Don't confuse the admin by leaving the slapd.conf around
1754 os.remove(paths.slapdconf)
1757 def provision_fds_backend(result, paths=None, setup_path=None, names=None,
1759 hostname=None, ldapadminpass=None, root=None,
1761 ldap_backend_extra_port=None,
1765 ldap_dryrun_mode=False):
1767 if ldap_backend_extra_port is not None:
1768 serverport = "ServerPort=%d" % ldap_backend_extra_port
1772 setup_file(setup_path("fedorads.inf"), paths.fedoradsinf,
1774 "HOSTNAME": hostname,
1775 "DNSDOMAIN": names.dnsdomain,
1776 "LDAPDIR": paths.ldapdir,
1777 "DOMAINDN": names.domaindn,
1778 "LDAPMANAGERDN": names.ldapmanagerdn,
1779 "LDAPMANAGERPASS": ldapadminpass,
1780 "SERVERPORT": serverport})
1782 setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions,
1783 {"CONFIGDN": names.configdn,
1784 "SCHEMADN": names.schemadn,
1785 "SAMBADN": names.sambadn,
1788 setup_file(setup_path("fedorads-sasl.ldif"), paths.fedoradssasl,
1789 {"SAMBADN": names.sambadn,
1792 setup_file(setup_path("fedorads-samba.ldif"), paths.fedoradssamba,
1793 {"SAMBADN": names.sambadn,
1794 "LDAPADMINPASS": ldapadminpass
1797 mapping = "schema-map-fedora-ds-1.0"
1798 backend_schema = "99_ad.ldif"
1800 # Build a schema file in Fedora DS format
1801 backend_schema_data = schema.ldb.convert_schema_to_openldap("fedora-ds", open(setup_path(mapping), 'r').read())
1802 assert backend_schema_data is not None
1803 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1805 result.credentials.set_bind_dn(names.ldapmanagerdn)
1807 # Destory the target directory, or else setup-ds.pl will complain
1808 fedora_ds_dir = os.path.join(paths.ldapdir, "slapd-samba4")
1809 shutil.rmtree(fedora_ds_dir, True)
1811 result.slapd_provision_command = [slapd_path, "-D", fedora_ds_dir, "-i", paths.slapdpid];
1812 #In the 'provision' command line, stay in the foreground so we can easily kill it
1813 result.slapd_provision_command.append("-d0")
1815 #the command for the final run is the normal script
1816 result.slapd_command = [os.path.join(paths.ldapdir, "slapd-samba4", "start-slapd")]
1818 # If we were just looking for crashes up to this point, it's a
1819 # good time to exit before we realise we don't have Fedora DS on
1820 if ldap_dryrun_mode:
1823 # Try to print helpful messages when the user has not specified the path to the setup-ds tool
1824 if setup_ds_path is None:
1825 raise ProvisioningError("Warning: Fedora DS LDAP-Backend must be setup with path to setup-ds, e.g. --setup-ds-path=\"/usr/sbin/setup-ds.pl\"!")
1826 if not os.path.exists(setup_ds_path):
1827 message (setup_ds_path)
1828 raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1830 # Run the Fedora DS setup utility
1831 retcode = subprocess.call([setup_ds_path, "--silent", "--file", paths.fedoradsinf], close_fds=True, shell=False)
1833 raise ProvisioningError("setup-ds failed")
1836 retcode = subprocess.call([
1837 os.path.join(paths.ldapdir, "slapd-samba4", "ldif2db"), "-s", names.sambadn, "-i", paths.fedoradssamba],
1838 close_fds=True, shell=False)
1840 raise("ldib2db failed")
1842 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1843 """Create a PHP LDAP admin configuration file.
1845 :param path: Path to write the configuration to.
1846 :param setup_path: Function to generate setup paths.
1848 setup_file(setup_path("phpldapadmin-config.php"), path,
1849 {"S4_LDAPI_URI": ldapi_uri})
1852 def create_zone_file(path, setup_path, dnsdomain, domaindn,
1853 hostip, hostip6, hostname, dnspass, realm, domainguid,
1855 """Write out a DNS zone file, from the info in the current database.
1857 :param path: Path of the new zone file.
1858 :param setup_path: Setup path function.
1859 :param dnsdomain: DNS Domain name
1860 :param domaindn: DN of the Domain
1861 :param hostip: Local IPv4 IP
1862 :param hostip6: Local IPv6 IP
1863 :param hostname: Local hostname
1864 :param dnspass: Password for DNS
1865 :param realm: Realm name
1866 :param domainguid: GUID of the domain.
1867 :param ntdsguid: GUID of the hosts nTDSDSA record.
1869 assert isinstance(domainguid, str)
1871 if hostip6 is not None:
1872 hostip6_base_line = " IN AAAA " + hostip6
1873 hostip6_host_line = hostname + " IN AAAA " + hostip6
1875 hostip6_base_line = ""
1876 hostip6_host_line = ""
1878 if hostip is not None:
1879 hostip_base_line = " IN A " + hostip
1880 hostip_host_line = hostname + " IN A " + hostip
1882 hostip_base_line = ""
1883 hostip_host_line = ""
1885 setup_file(setup_path("provision.zone"), path, {
1886 "DNSPASS_B64": b64encode(dnspass),
1887 "HOSTNAME": hostname,
1888 "DNSDOMAIN": dnsdomain,
1890 "HOSTIP_BASE_LINE": hostip_base_line,
1891 "HOSTIP_HOST_LINE": hostip_host_line,
1892 "DOMAINGUID": domainguid,
1893 "DATESTRING": time.strftime("%Y%m%d%H"),
1894 "DEFAULTSITE": DEFAULTSITE,
1895 "NTDSGUID": ntdsguid,
1896 "HOSTIP6_BASE_LINE": hostip6_base_line,
1897 "HOSTIP6_HOST_LINE": hostip6_host_line,
1901 def create_named_conf(path, setup_path, realm, dnsdomain,
1903 """Write out a file containing zone statements suitable for inclusion in a
1904 named.conf file (including GSS-TSIG configuration).
1906 :param path: Path of the new named.conf file.
1907 :param setup_path: Setup path function.
1908 :param realm: Realm name
1909 :param dnsdomain: DNS Domain name
1910 :param private_dir: Path to private directory
1911 :param keytab_name: File name of DNS keytab file
1914 setup_file(setup_path("named.conf"), path, {
1915 "DNSDOMAIN": dnsdomain,
1917 "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1918 "PRIVATE_DIR": private_dir
1921 def create_named_txt(path, setup_path, realm, dnsdomain,
1922 private_dir, keytab_name):
1923 """Write out a file containing zone statements suitable for inclusion in a
1924 named.conf file (including GSS-TSIG configuration).
1926 :param path: Path of the new named.conf file.
1927 :param setup_path: Setup path function.
1928 :param realm: Realm name
1929 :param dnsdomain: DNS Domain name
1930 :param private_dir: Path to private directory
1931 :param keytab_name: File name of DNS keytab file
1934 setup_file(setup_path("named.txt"), path, {
1935 "DNSDOMAIN": dnsdomain,
1937 "DNS_KEYTAB": keytab_name,
1938 "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1939 "PRIVATE_DIR": private_dir
1942 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1943 """Write out a file containing zone statements suitable for inclusion in a
1944 named.conf file (including GSS-TSIG configuration).
1946 :param path: Path of the new named.conf file.
1947 :param setup_path: Setup path function.
1948 :param dnsdomain: DNS Domain name
1949 :param hostname: Local hostname
1950 :param realm: Realm name
1953 setup_file(setup_path("krb5.conf"), path, {
1954 "DNSDOMAIN": dnsdomain,
1955 "HOSTNAME": hostname,