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_2000, 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_2000
839 forestFunctionality = DS_DOMAIN_FUNCTION_2000
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 "CREATTIME": str(int(time.time()) * 1e7), # seconds -> ticks
982 "DOMAINDN": names.domaindn,
983 "NETBIOSNAME": names.netbiosname,
984 "DEFAULTSITE": names.sitename,
985 "CONFIGDN": names.configdn,
986 "SERVERDN": names.serverdn,
987 "POLICYGUID_DC": policyguid_dc
990 if fill == FILL_FULL:
991 message("Setting up sam.ldb users and groups")
992 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
993 "DOMAINDN": names.domaindn,
994 "DOMAINSID": str(domainsid),
995 "CONFIGDN": names.configdn,
996 "ADMINPASS_B64": b64encode(adminpass),
997 "KRBTGTPASS_B64": b64encode(krbtgtpass),
1000 if serverrole == "domain controller":
1001 message("Setting up self join")
1002 setup_self_join(samdb, names=names, invocationid=invocationid,
1004 machinepass=machinepass,
1005 domainsid=domainsid, policyguid=policyguid,
1006 policyguid_dc=policyguid_dc,
1007 setup_path=setup_path,
1008 domainControllerFunctionality=domainControllerFunctionality)
1010 ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
1011 names.ntdsguid = samdb.searchone(basedn=ntds_dn,
1012 attribute="objectGUID", expression="", scope=SCOPE_BASE)
1013 assert isinstance(names.ntdsguid, str)
1016 samdb.transaction_cancel()
1019 samdb.transaction_commit()
1024 FILL_NT4SYNC = "NT4SYNC"
1028 def provision(setup_dir, message, session_info,
1029 credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL,
1031 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1033 domain=None, hostname=None, hostip=None, hostip6=None,
1034 domainsid=None, adminpass=None, ldapadminpass=None,
1035 krbtgtpass=None, domainguid=None,
1036 policyguid=None, policyguid_dc=None, invocationid=None,
1038 dnspass=None, root=None, nobody=None, users=None,
1039 wheel=None, backup=None, aci=None, serverrole=None,
1040 ldap_backend_extra_port=None, ldap_backend_type=None,
1042 ol_mmr_urls=None, ol_olc=None,
1043 setup_ds_path=None, slapd_path=None, nosync=False,
1044 ldap_dryrun_mode=False):
1047 :note: caution, this wipes all existing data!
1050 def setup_path(file):
1051 return os.path.join(setup_dir, file)
1053 if domainsid is None:
1054 domainsid = security.random_sid()
1056 # create/adapt the group policy GUIDs
1057 if policyguid is None:
1058 policyguid = str(uuid.uuid4())
1059 policyguid = policyguid.upper()
1060 if policyguid_dc is None:
1061 policyguid_dc = str(uuid.uuid4())
1062 policyguid_dc = policyguid_dc.upper()
1064 if adminpass is None:
1065 adminpass = glue.generate_random_str(12)
1066 if krbtgtpass is None:
1067 krbtgtpass = glue.generate_random_str(12)
1068 if machinepass is None:
1069 machinepass = glue.generate_random_str(12)
1071 dnspass = glue.generate_random_str(12)
1072 if ldapadminpass is None:
1073 #Make a new, random password between Samba and it's LDAP server
1074 ldapadminpass=glue.generate_random_str(12)
1077 root_uid = findnss_uid([root or "root"])
1078 nobody_uid = findnss_uid([nobody or "nobody"])
1079 users_gid = findnss_gid([users or "users"])
1081 wheel_gid = findnss_gid(["wheel", "adm"])
1083 wheel_gid = findnss_gid([wheel])
1085 if targetdir is not None:
1086 if (not os.path.exists(os.path.join(targetdir, "etc"))):
1087 os.makedirs(os.path.join(targetdir, "etc"))
1088 smbconf = os.path.join(targetdir, "etc", "smb.conf")
1089 elif smbconf is None:
1090 smbconf = param.default_path()
1092 # only install a new smb.conf if there isn't one there already
1093 if not os.path.exists(smbconf):
1094 make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
1097 lp = param.LoadParm()
1100 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1101 dnsdomain=realm, serverrole=serverrole, sitename=sitename,
1102 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1105 paths = provision_paths_from_lp(lp, names.dnsdomain)
1109 hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1110 except socket.gaierror, (socket.EAI_NODATA, msg):
1115 hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1116 except socket.gaierror, (socket.EAI_NODATA, msg):
1119 if serverrole is None:
1120 serverrole = lp.get("server role")
1122 assert serverrole in ("domain controller", "member server", "standalone")
1123 if invocationid is None and serverrole == "domain controller":
1124 invocationid = str(uuid.uuid4())
1126 if not os.path.exists(paths.private_dir):
1127 os.mkdir(paths.private_dir)
1129 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1131 schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn,
1132 sambadn=names.sambadn, ldap_backend_type=ldap_backend_type)
1134 secrets_credentials = credentials
1135 provision_backend = None
1136 if ldap_backend_type:
1137 # We only support an LDAP backend over ldapi://
1139 provision_backend = ProvisionBackend(paths=paths, setup_path=setup_path,
1140 lp=lp, credentials=credentials,
1142 message=message, hostname=hostname,
1143 root=root, schema=schema,
1144 ldap_backend_type=ldap_backend_type,
1145 ldapadminpass=ldapadminpass,
1146 ldap_backend_extra_port=ldap_backend_extra_port,
1147 ol_mmr_urls=ol_mmr_urls,
1148 slapd_path=slapd_path,
1149 setup_ds_path=setup_ds_path,
1150 ldap_dryrun_mode=ldap_dryrun_mode)
1152 # Now use the backend credentials to access the databases
1153 credentials = provision_backend.credentials
1154 secrets_credentials = provision_backend.adminCredentials
1155 ldapi_url = provision_backend.ldapi_uri
1157 # only install a new shares config db if there is none
1158 if not os.path.exists(paths.shareconf):
1159 message("Setting up share.ldb")
1160 share_ldb = Ldb(paths.shareconf, session_info=session_info,
1161 credentials=credentials, lp=lp)
1162 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1165 message("Setting up secrets.ldb")
1166 secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
1167 session_info=session_info,
1168 credentials=secrets_credentials, lp=lp)
1170 message("Setting up the registry")
1171 setup_registry(paths.hklm, setup_path, session_info,
1174 message("Setting up idmap db")
1175 idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1178 message("Setting up SAM db")
1179 samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info,
1180 credentials=credentials, lp=lp, names=names,
1182 domainsid=domainsid,
1183 schema=schema, domainguid=domainguid,
1184 policyguid=policyguid, policyguid_dc=policyguid_dc,
1186 adminpass=adminpass, krbtgtpass=krbtgtpass,
1187 invocationid=invocationid,
1188 machinepass=machinepass, dnspass=dnspass,
1189 serverrole=serverrole, ldap_backend=provision_backend)
1191 if serverrole == "domain controller":
1192 if paths.netlogon is None:
1193 message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1194 message("Please either remove %s or see the template at %s" %
1195 ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1196 assert(paths.netlogon is not None)
1198 if paths.sysvol is None:
1199 message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1200 message("Please either remove %s or see the template at %s" %
1201 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1202 assert(paths.sysvol is not None)
1204 # Set up group policies (domain policy and domain controller policy)
1206 policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1207 "{" + policyguid + "}")
1208 os.makedirs(policy_path, 0755)
1209 open(os.path.join(policy_path, "GPT.INI"), 'w').write(
1210 "[General]\r\nVersion=65543")
1211 os.makedirs(os.path.join(policy_path, "MACHINE"), 0755)
1212 os.makedirs(os.path.join(policy_path, "USER"), 0755)
1214 policy_path_dc = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1215 "{" + policyguid_dc + "}")
1216 os.makedirs(policy_path_dc, 0755)
1217 open(os.path.join(policy_path_dc, "GPT.INI"), 'w').write(
1218 "[General]\r\nVersion=2")
1219 os.makedirs(os.path.join(policy_path_dc, "MACHINE"), 0755)
1220 os.makedirs(os.path.join(policy_path_dc, "USER"), 0755)
1222 if not os.path.isdir(paths.netlogon):
1223 os.makedirs(paths.netlogon, 0755)
1225 if samdb_fill == FILL_FULL:
1226 setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1227 root_uid=root_uid, nobody_uid=nobody_uid,
1228 users_gid=users_gid, wheel_gid=wheel_gid)
1230 message("Setting up sam.ldb rootDSE marking as synchronized")
1231 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1233 # Only make a zone file on the first DC, it should be replicated with DNS replication
1234 if serverrole == "domain controller":
1235 secrets_ldb = Ldb(paths.secrets, session_info=session_info,
1236 credentials=credentials, lp=lp)
1237 secretsdb_become_dc(secrets_ldb, setup_path, domain=domain,
1239 netbiosname=names.netbiosname,
1240 domainsid=domainsid,
1241 keytab_path=paths.keytab, samdb_url=paths.samdb,
1242 dns_keytab_path=paths.dns_keytab,
1243 dnspass=dnspass, machinepass=machinepass,
1244 dnsdomain=names.dnsdomain)
1246 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1247 assert isinstance(domainguid, str)
1249 create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1250 domaindn=names.domaindn, hostip=hostip,
1251 hostip6=hostip6, hostname=names.hostname,
1252 dnspass=dnspass, realm=names.realm,
1253 domainguid=domainguid, ntdsguid=names.ntdsguid)
1255 create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1256 dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1258 create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1259 dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1260 keytab_name=paths.dns_keytab)
1261 message("See %s for an example configuration include file for BIND" % paths.namedconf)
1262 message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1264 create_krb5_conf(paths.krb5conf, setup_path,
1265 dnsdomain=names.dnsdomain, hostname=names.hostname,
1267 message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1270 if provision_backend is not None:
1271 if ldap_backend_type == "fedora-ds":
1272 ldapi_db = Ldb(provision_backend.ldapi_uri, lp=lp, credentials=credentials)
1274 # delete default SASL mappings
1275 res = ldapi_db.search(expression="(!(cn=samba-admin mapping))", base="cn=mapping,cn=sasl,cn=config", scope=SCOPE_ONELEVEL, attrs=["dn"])
1277 # configure in-directory access control on Fedora DS via the aci attribute (over a direct ldapi:// socket)
1278 for i in range (0, len(res)):
1279 dn = str(res[i]["dn"])
1282 aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % names.sambadn
1285 m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci")
1287 m.dn = ldb.Dn(1, names.domaindn)
1290 m.dn = ldb.Dn(1, names.configdn)
1293 m.dn = ldb.Dn(1, names.schemadn)
1296 # if an LDAP backend is in use, terminate slapd after final provision and check its proper termination
1297 if provision_backend.slapd.poll() is None:
1299 if hasattr(provision_backend.slapd, "terminate"):
1300 provision_backend.slapd.terminate()
1302 # Older python versions don't have .terminate()
1304 os.kill(provision_backend.slapd.pid, signal.SIGTERM)
1306 #and now wait for it to die
1307 provision_backend.slapd.communicate()
1309 # now display slapd_command_file.txt to show how slapd must be started next time
1310 message("Use later the following commandline to start slapd, then Samba:")
1311 slapd_command = "\'" + "\' \'".join(provision_backend.slapd_command) + "\'"
1312 message(slapd_command)
1313 message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
1315 setup_file(setup_path("ldap_backend_startup.sh"), paths.ldapdir + "/ldap_backend_startup.sh", {
1316 "SLAPD_COMMAND" : slapd_command})
1319 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1322 message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1324 message("Once the above files are installed, your Samba4 server will be ready to use")
1325 message("Server Role: %s" % serverrole)
1326 message("Hostname: %s" % names.hostname)
1327 message("NetBIOS Domain: %s" % names.domain)
1328 message("DNS Domain: %s" % names.dnsdomain)
1329 message("DOMAIN SID: %s" % str(domainsid))
1330 if samdb_fill == FILL_FULL:
1331 message("Admin password: %s" % adminpass)
1332 if provision_backend:
1333 if provision_backend.credentials.get_bind_dn() is not None:
1334 message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1336 message("LDAP Admin User: %s" % provision_backend.credentials.get_username())
1338 message("LDAP Admin Password: %s" % provision_backend.credentials.get_password())
1340 result = ProvisionResult()
1341 result.domaindn = domaindn
1342 result.paths = paths
1344 result.samdb = samdb
1349 def provision_become_dc(setup_dir=None,
1350 smbconf=None, targetdir=None, realm=None,
1351 rootdn=None, domaindn=None, schemadn=None,
1352 configdn=None, serverdn=None,
1353 domain=None, hostname=None, domainsid=None,
1354 adminpass=None, krbtgtpass=None, domainguid=None,
1355 policyguid=None, policyguid_dc=None, invocationid=None,
1357 dnspass=None, root=None, nobody=None, users=None,
1358 wheel=None, backup=None, serverrole=None,
1359 ldap_backend=None, ldap_backend_type=None,
1360 sitename=None, debuglevel=1):
1363 """print a message if quiet is not set."""
1366 glue.set_debug_level(debuglevel)
1368 return provision(setup_dir, message, system_session(), None,
1369 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1370 realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1371 configdn=configdn, serverdn=serverdn, domain=domain,
1372 hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
1373 machinepass=machinepass, serverrole="domain controller",
1377 def setup_db_config(setup_path, dbdir):
1378 """Setup a Berkeley database.
1380 :param setup_path: Setup path function.
1381 :param dbdir: Database directory."""
1382 if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1383 os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1384 if not os.path.isdir(os.path.join(dbdir, "tmp")):
1385 os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1387 setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1388 {"LDAPDBDIR": dbdir})
1390 class ProvisionBackend(object):
1391 def __init__(self, paths=None, setup_path=None, lp=None, credentials=None,
1392 names=None, message=None,
1393 hostname=None, root=None,
1394 schema=None, ldapadminpass=None,
1395 ldap_backend_type=None, ldap_backend_extra_port=None,
1397 setup_ds_path=None, slapd_path=None,
1398 nosync=False, ldap_dryrun_mode=False):
1399 """Provision an LDAP backend for samba4
1401 This works for OpenLDAP and Fedora DS
1404 self.ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.ldapdir, "ldapi"), safe="")
1406 if not os.path.isdir(paths.ldapdir):
1407 os.makedirs(paths.ldapdir, 0700)
1409 if ldap_backend_type == "existing":
1410 #Check to see that this 'existing' LDAP backend in fact exists
1411 ldapi_db = Ldb(self.ldapi_uri, credentials=credentials)
1412 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1413 expression="(objectClass=OpenLDAProotDSE)")
1415 # If we have got here, then we must have a valid connection to the LDAP server, with valid credentials supplied
1416 # This caused them to be set into the long-term database later in the script.
1417 self.credentials = credentials
1418 self.ldap_backend_type = "openldap" #For now, assume existing backends at least emulate OpenLDAP
1421 # we will shortly start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via self.ldapi_uri
1422 # if another instance of slapd is already running
1424 ldapi_db = Ldb(self.ldapi_uri)
1425 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1426 expression="(objectClass=OpenLDAProotDSE)");
1428 f = open(paths.slapdpid, "r")
1431 message("Check for slapd Process with PID: " + str(p) + " and terminate it manually.")
1435 raise ProvisioningError("Warning: Another slapd Instance seems already running on this host, listening to " + self.ldapi_uri + ". Please shut it down before you continue. ")
1440 # Try to print helpful messages when the user has not specified the path to slapd
1441 if slapd_path is None:
1442 raise ProvisioningError("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
1443 if not os.path.exists(slapd_path):
1444 message (slapd_path)
1445 raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1447 schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1449 os.unlink(schemadb_path)
1454 # Put the LDIF of the schema into a database so we can search on
1455 # it to generate schema-dependent configurations in Fedora DS and
1457 os.path.join(paths.ldapdir, "schema-tmp.ldb")
1458 schema.ldb.connect(schemadb_path)
1459 schema.ldb.transaction_start()
1461 # These bits of LDIF are supplied when the Schema object is created
1462 schema.ldb.add_ldif(schema.schema_dn_add)
1463 schema.ldb.modify_ldif(schema.schema_dn_modify)
1464 schema.ldb.add_ldif(schema.schema_data)
1465 schema.ldb.transaction_commit()
1467 self.credentials = Credentials()
1468 self.credentials.guess(lp)
1469 #Kerberos to an ldapi:// backend makes no sense
1470 self.credentials.set_kerberos_state(DONT_USE_KERBEROS)
1472 self.adminCredentials = Credentials()
1473 self.adminCredentials.guess(lp)
1474 #Kerberos to an ldapi:// backend makes no sense
1475 self.adminCredentials.set_kerberos_state(DONT_USE_KERBEROS)
1477 self.ldap_backend_type = ldap_backend_type
1479 if ldap_backend_type == "fedora-ds":
1480 provision_fds_backend(self, paths=paths, setup_path=setup_path,
1481 names=names, message=message,
1483 ldapadminpass=ldapadminpass, root=root,
1485 ldap_backend_extra_port=ldap_backend_extra_port,
1486 setup_ds_path=setup_ds_path,
1487 slapd_path=slapd_path,
1489 ldap_dryrun_mode=ldap_dryrun_mode)
1491 elif ldap_backend_type == "openldap":
1492 provision_openldap_backend(self, paths=paths, setup_path=setup_path,
1493 names=names, message=message,
1495 ldapadminpass=ldapadminpass, root=root,
1497 ldap_backend_extra_port=ldap_backend_extra_port,
1498 ol_mmr_urls=ol_mmr_urls,
1499 slapd_path=slapd_path,
1501 ldap_dryrun_mode=ldap_dryrun_mode)
1503 raise ProvisioningError("Unknown LDAP backend type selected")
1505 self.credentials.set_password(ldapadminpass)
1506 self.adminCredentials.set_username("samba-admin")
1507 self.adminCredentials.set_password(ldapadminpass)
1509 # Now start the slapd, so we can provision onto it. We keep the
1510 # subprocess context around, to kill this off at the successful
1512 self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False)
1514 while self.slapd.poll() is None:
1515 # Wait until the socket appears
1517 ldapi_db = Ldb(self.ldapi_uri, lp=lp, credentials=self.credentials)
1518 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1519 expression="(objectClass=OpenLDAProotDSE)")
1520 # If we have got here, then we must have a valid connection to the LDAP server!
1526 raise ProvisioningError("slapd died before we could make a connection to it")
1529 def provision_openldap_backend(result, paths=None, setup_path=None, names=None,
1531 hostname=None, ldapadminpass=None, root=None,
1533 ldap_backend_extra_port=None,
1535 slapd_path=None, nosync=False,
1536 ldap_dryrun_mode=False):
1538 #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
1541 nosync_config = "dbnosync"
1543 lnkattr = get_linked_attributes(names.schemadn,schema.ldb)
1544 refint_attributes = ""
1545 memberof_config = "# Generated from Samba4 schema\n"
1546 for att in lnkattr.keys():
1547 if lnkattr[att] is not None:
1548 refint_attributes = refint_attributes + " " + att
1550 memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1551 { "MEMBER_ATTR" : att ,
1552 "MEMBEROF_ATTR" : lnkattr[att] })
1554 refint_config = read_and_sub_file(setup_path("refint.conf"),
1555 { "LINK_ATTRS" : refint_attributes})
1557 attrs = ["linkID", "lDAPDisplayName"]
1558 res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1560 for i in range (0, len(res)):
1561 index_attr = res[i]["lDAPDisplayName"][0]
1562 if index_attr == "objectGUID":
1563 index_attr = "entryUUID"
1565 index_config += "index " + index_attr + " eq\n"
1567 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1569 mmr_replicator_acl = ""
1570 mmr_serverids_config = ""
1571 mmr_syncrepl_schema_config = ""
1572 mmr_syncrepl_config_config = ""
1573 mmr_syncrepl_user_config = ""
1576 if ol_mmr_urls is not None:
1577 # For now, make these equal
1578 mmr_pass = ldapadminpass
1580 url_list=filter(None,ol_mmr_urls.split(' '))
1581 if (len(url_list) == 1):
1582 url_list=filter(None,ol_mmr_urls.split(','))
1585 mmr_on_config = "MirrorMode On"
1586 mmr_replicator_acl = " by dn=cn=replicator,cn=samba read"
1588 for url in url_list:
1590 mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1591 { "SERVERID" : str(serverid),
1592 "LDAPSERVER" : url })
1595 mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1597 "MMRDN": names.schemadn,
1599 "MMR_PASSWORD": mmr_pass})
1602 mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1604 "MMRDN": names.configdn,
1606 "MMR_PASSWORD": mmr_pass})
1609 mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1611 "MMRDN": names.domaindn,
1613 "MMR_PASSWORD": mmr_pass })
1614 # OpenLDAP cn=config initialisation
1615 olc_syncrepl_config = ""
1617 # if mmr = yes, generate cn=config-replication directives
1618 # and olc_seed.lif for the other mmr-servers
1619 if ol_mmr_urls is not None:
1621 olc_serverids_config = ""
1622 olc_syncrepl_seed_config = ""
1623 olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
1625 for url in url_list:
1627 olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
1628 { "SERVERID" : str(serverid),
1629 "LDAPSERVER" : url })
1632 olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
1635 "MMR_PASSWORD": mmr_pass})
1637 olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1639 "LDAPSERVER" : url})
1641 setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif,
1642 {"OLC_SERVER_ID_CONF": olc_serverids_config,
1643 "OLC_PW": ldapadminpass,
1644 "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
1647 setup_file(setup_path("slapd.conf"), paths.slapdconf,
1648 {"DNSDOMAIN": names.dnsdomain,
1649 "LDAPDIR": paths.ldapdir,
1650 "DOMAINDN": names.domaindn,
1651 "CONFIGDN": names.configdn,
1652 "SCHEMADN": names.schemadn,
1653 "MEMBEROF_CONFIG": memberof_config,
1654 "MIRRORMODE": mmr_on_config,
1655 "REPLICATOR_ACL": mmr_replicator_acl,
1656 "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1657 "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1658 "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1659 "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1660 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
1661 "OLC_MMR_CONFIG": olc_mmr_config,
1662 "REFINT_CONFIG": refint_config,
1663 "INDEX_CONFIG": index_config,
1664 "NOSYNC": nosync_config})
1666 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1667 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1668 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1670 if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba", "cn=samba")):
1671 os.makedirs(os.path.join(paths.ldapdir, "db", "samba", "cn=samba"), 0700)
1673 setup_file(setup_path("cn=samba.ldif"),
1674 os.path.join(paths.ldapdir, "db", "samba", "cn=samba.ldif"),
1675 { "UUID": str(uuid.uuid4()),
1676 "LDAPTIME": timestring(int(time.time()))} )
1677 setup_file(setup_path("cn=samba-admin.ldif"),
1678 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=samba-admin.ldif"),
1679 {"LDAPADMINPASS_B64": b64encode(ldapadminpass),
1680 "UUID": str(uuid.uuid4()),
1681 "LDAPTIME": timestring(int(time.time()))} )
1683 if ol_mmr_urls is not None:
1684 setup_file(setup_path("cn=replicator.ldif"),
1685 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=replicator.ldif"),
1686 {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1687 "UUID": str(uuid.uuid4()),
1688 "LDAPTIME": timestring(int(time.time()))} )
1691 mapping = "schema-map-openldap-2.3"
1692 backend_schema = "backend-schema.schema"
1694 backend_schema_data = schema.ldb.convert_schema_to_openldap("openldap", open(setup_path(mapping), 'r').read())
1695 assert backend_schema_data is not None
1696 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1698 # now we generate the needed strings to start slapd automatically,
1699 # first ldapi_uri...
1700 if ldap_backend_extra_port is not None:
1701 # When we use MMR, we can't use 0.0.0.0 as it uses the name
1702 # specified there as part of it's clue as to it's own name,
1703 # and not to replicate to itself
1704 if ol_mmr_urls is None:
1705 server_port_string = "ldap://0.0.0.0:%d" % ldap_backend_extra_port
1707 server_port_string = "ldap://" + names.hostname + "." + names.dnsdomain +":%d" % ldap_backend_extra_port
1709 server_port_string = ""
1711 # Prepare the 'result' information - the commands to return in particular
1712 result.slapd_provision_command = [slapd_path]
1714 result.slapd_provision_command.append("-F" + paths.olcdir)
1716 result.slapd_provision_command.append("-h")
1718 # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands
1719 result.slapd_command = list(result.slapd_provision_command)
1721 result.slapd_provision_command.append(result.ldapi_uri)
1722 result.slapd_provision_command.append("-d0")
1724 uris = result.ldapi_uri
1725 if server_port_string is not "":
1726 uris = uris + " " + server_port_string
1728 result.slapd_command.append(uris)
1730 # Set the username - done here because Fedora DS still uses the admin DN and simple bind
1731 result.credentials.set_username("samba-admin")
1733 # If we were just looking for crashes up to this point, it's a
1734 # good time to exit before we realise we don't have OpenLDAP on
1736 if ldap_dryrun_mode:
1739 # Finally, convert the configuration into cn=config style!
1740 if not os.path.isdir(paths.olcdir):
1741 os.makedirs(paths.olcdir, 0770)
1743 retcode = subprocess.call([slapd_path, "-Ttest", "-f", paths.slapdconf, "-F", paths.olcdir], close_fds=True, shell=False)
1745 # We can't do this, as OpenLDAP is strange. It gives an error
1746 # output to the above, but does the conversion sucessfully...
1749 # raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1751 if not os.path.exists(os.path.join(paths.olcdir, "cn=config.ldif")):
1752 raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1754 # Don't confuse the admin by leaving the slapd.conf around
1755 os.remove(paths.slapdconf)
1758 def provision_fds_backend(result, paths=None, setup_path=None, names=None,
1760 hostname=None, ldapadminpass=None, root=None,
1762 ldap_backend_extra_port=None,
1766 ldap_dryrun_mode=False):
1768 if ldap_backend_extra_port is not None:
1769 serverport = "ServerPort=%d" % ldap_backend_extra_port
1773 setup_file(setup_path("fedorads.inf"), paths.fedoradsinf,
1775 "HOSTNAME": hostname,
1776 "DNSDOMAIN": names.dnsdomain,
1777 "LDAPDIR": paths.ldapdir,
1778 "DOMAINDN": names.domaindn,
1779 "LDAPMANAGERDN": names.ldapmanagerdn,
1780 "LDAPMANAGERPASS": ldapadminpass,
1781 "SERVERPORT": serverport})
1783 setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions,
1784 {"CONFIGDN": names.configdn,
1785 "SCHEMADN": names.schemadn,
1786 "SAMBADN": names.sambadn,
1789 setup_file(setup_path("fedorads-sasl.ldif"), paths.fedoradssasl,
1790 {"SAMBADN": names.sambadn,
1793 setup_file(setup_path("fedorads-samba.ldif"), paths.fedoradssamba,
1794 {"SAMBADN": names.sambadn,
1795 "LDAPADMINPASS": ldapadminpass
1798 mapping = "schema-map-fedora-ds-1.0"
1799 backend_schema = "99_ad.ldif"
1801 # Build a schema file in Fedora DS format
1802 backend_schema_data = schema.ldb.convert_schema_to_openldap("fedora-ds", open(setup_path(mapping), 'r').read())
1803 assert backend_schema_data is not None
1804 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1806 result.credentials.set_bind_dn(names.ldapmanagerdn)
1808 # Destory the target directory, or else setup-ds.pl will complain
1809 fedora_ds_dir = os.path.join(paths.ldapdir, "slapd-samba4")
1810 shutil.rmtree(fedora_ds_dir, True)
1812 result.slapd_provision_command = [slapd_path, "-D", fedora_ds_dir, "-i", paths.slapdpid];
1813 #In the 'provision' command line, stay in the foreground so we can easily kill it
1814 result.slapd_provision_command.append("-d0")
1816 #the command for the final run is the normal script
1817 result.slapd_command = [os.path.join(paths.ldapdir, "slapd-samba4", "start-slapd")]
1819 # If we were just looking for crashes up to this point, it's a
1820 # good time to exit before we realise we don't have Fedora DS on
1821 if ldap_dryrun_mode:
1824 # Try to print helpful messages when the user has not specified the path to the setup-ds tool
1825 if setup_ds_path is None:
1826 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\"!")
1827 if not os.path.exists(setup_ds_path):
1828 message (setup_ds_path)
1829 raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1831 # Run the Fedora DS setup utility
1832 retcode = subprocess.call([setup_ds_path, "--silent", "--file", paths.fedoradsinf], close_fds=True, shell=False)
1834 raise ProvisioningError("setup-ds failed")
1837 retcode = subprocess.call([
1838 os.path.join(paths.ldapdir, "slapd-samba4", "ldif2db"), "-s", names.sambadn, "-i", paths.fedoradssamba],
1839 close_fds=True, shell=False)
1841 raise("ldib2db failed")
1843 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1844 """Create a PHP LDAP admin configuration file.
1846 :param path: Path to write the configuration to.
1847 :param setup_path: Function to generate setup paths.
1849 setup_file(setup_path("phpldapadmin-config.php"), path,
1850 {"S4_LDAPI_URI": ldapi_uri})
1853 def create_zone_file(path, setup_path, dnsdomain, domaindn,
1854 hostip, hostip6, hostname, dnspass, realm, domainguid,
1856 """Write out a DNS zone file, from the info in the current database.
1858 :param path: Path of the new zone file.
1859 :param setup_path: Setup path function.
1860 :param dnsdomain: DNS Domain name
1861 :param domaindn: DN of the Domain
1862 :param hostip: Local IPv4 IP
1863 :param hostip6: Local IPv6 IP
1864 :param hostname: Local hostname
1865 :param dnspass: Password for DNS
1866 :param realm: Realm name
1867 :param domainguid: GUID of the domain.
1868 :param ntdsguid: GUID of the hosts nTDSDSA record.
1870 assert isinstance(domainguid, str)
1872 if hostip6 is not None:
1873 hostip6_base_line = " IN AAAA " + hostip6
1874 hostip6_host_line = hostname + " IN AAAA " + hostip6
1876 hostip6_base_line = ""
1877 hostip6_host_line = ""
1879 if hostip is not None:
1880 hostip_base_line = " IN A " + hostip
1881 hostip_host_line = hostname + " IN A " + hostip
1883 hostip_base_line = ""
1884 hostip_host_line = ""
1886 setup_file(setup_path("provision.zone"), path, {
1887 "DNSPASS_B64": b64encode(dnspass),
1888 "HOSTNAME": hostname,
1889 "DNSDOMAIN": dnsdomain,
1891 "HOSTIP_BASE_LINE": hostip_base_line,
1892 "HOSTIP_HOST_LINE": hostip_host_line,
1893 "DOMAINGUID": domainguid,
1894 "DATESTRING": time.strftime("%Y%m%d%H"),
1895 "DEFAULTSITE": DEFAULTSITE,
1896 "NTDSGUID": ntdsguid,
1897 "HOSTIP6_BASE_LINE": hostip6_base_line,
1898 "HOSTIP6_HOST_LINE": hostip6_host_line,
1902 def create_named_conf(path, setup_path, realm, dnsdomain,
1904 """Write out a file containing zone statements suitable for inclusion in a
1905 named.conf file (including GSS-TSIG configuration).
1907 :param path: Path of the new named.conf file.
1908 :param setup_path: Setup path function.
1909 :param realm: Realm name
1910 :param dnsdomain: DNS Domain name
1911 :param private_dir: Path to private directory
1912 :param keytab_name: File name of DNS keytab file
1915 setup_file(setup_path("named.conf"), path, {
1916 "DNSDOMAIN": dnsdomain,
1918 "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1919 "PRIVATE_DIR": private_dir
1922 def create_named_txt(path, setup_path, realm, dnsdomain,
1923 private_dir, keytab_name):
1924 """Write out a file containing zone statements suitable for inclusion in a
1925 named.conf file (including GSS-TSIG configuration).
1927 :param path: Path of the new named.conf file.
1928 :param setup_path: Setup path function.
1929 :param realm: Realm name
1930 :param dnsdomain: DNS Domain name
1931 :param private_dir: Path to private directory
1932 :param keytab_name: File name of DNS keytab file
1935 setup_file(setup_path("named.txt"), path, {
1936 "DNSDOMAIN": dnsdomain,
1938 "DNS_KEYTAB": keytab_name,
1939 "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1940 "PRIVATE_DIR": private_dir
1943 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1944 """Write out a file containing zone statements suitable for inclusion in a
1945 named.conf file (including GSS-TSIG configuration).
1947 :param path: Path of the new named.conf file.
1948 :param setup_path: Setup path function.
1949 :param dnsdomain: DNS Domain name
1950 :param hostname: Local hostname
1951 :param realm: Realm name
1954 setup_file(setup_path("krb5.conf"), path, {
1955 "DNSDOMAIN": dnsdomain,
1956 "HOSTNAME": hostname,