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 = ["resolve_oids",
598 "extended_dn_out_ldb"]
599 modules_list2 = ["show_deleted",
602 domaindn_ldb = "users.ldb"
603 configdn_ldb = "configuration.ldb"
604 schemadn_ldb = "schema.ldb"
605 if ldap_backend is not None:
606 domaindn_ldb = ldap_backend.ldapi_uri
607 configdn_ldb = ldap_backend.ldapi_uri
608 schemadn_ldb = ldap_backend.ldapi_uri
610 if ldap_backend.ldap_backend_type == "fedora-ds":
611 backend_modules = ["nsuniqueid", "paged_searches"]
612 # We can handle linked attributes here, as we don't have directory-side subtree operations
613 tdb_modules_list = ["linked_attributes", "extended_dn_out_dereference"]
614 elif ldap_backend.ldap_backend_type == "openldap":
615 backend_modules = ["entryuuid", "paged_searches"]
616 # OpenLDAP handles subtree renames, so we don't want to do any of these things
617 tdb_modules_list = ["extended_dn_out_dereference"]
619 elif serverrole == "domain controller":
620 tdb_modules_list.insert(0, "repl_meta_data")
623 backend_modules = ["objectguid"]
625 if tdb_modules_list is None:
626 tdb_modules_list_as_string = ""
628 tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
630 samdb.transaction_start()
632 message("Setting up sam.ldb partitions and settings")
633 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
634 "SCHEMADN": names.schemadn,
635 "SCHEMADN_LDB": schemadn_ldb,
636 "SCHEMADN_MOD2": ",objectguid",
637 "CONFIGDN": names.configdn,
638 "CONFIGDN_LDB": configdn_ldb,
639 "DOMAINDN": names.domaindn,
640 "DOMAINDN_LDB": domaindn_ldb,
641 "SCHEMADN_MOD": "schema_fsmo,instancetype",
642 "CONFIGDN_MOD": "naming_fsmo,instancetype",
643 "DOMAINDN_MOD": "pdc_fsmo,instancetype",
644 "MODULES_LIST": ",".join(modules_list),
645 "TDB_MODULES_LIST": tdb_modules_list_as_string,
646 "MODULES_LIST2": ",".join(modules_list2),
647 "BACKEND_MOD": ",".join(backend_modules),
650 samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
652 message("Setting up sam.ldb rootDSE")
653 setup_samdb_rootdse(samdb, setup_path, names)
656 samdb.transaction_cancel()
659 samdb.transaction_commit()
663 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain,
664 netbiosname, domainsid, keytab_path, samdb_url,
665 dns_keytab_path, dnspass, machinepass):
666 """Add DC-specific bits to a secrets database.
668 :param secretsdb: Ldb Handle to the secrets database
669 :param setup_path: Setup path function
670 :param machinepass: Machine password
672 setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), {
673 "MACHINEPASS_B64": b64encode(machinepass),
676 "DNSDOMAIN": dnsdomain,
677 "DOMAINSID": str(domainsid),
678 "SECRETS_KEYTAB": keytab_path,
679 "NETBIOSNAME": netbiosname,
680 "SAM_LDB": samdb_url,
681 "DNS_KEYTAB": dns_keytab_path,
682 "DNSPASS_B64": b64encode(dnspass),
686 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
687 """Setup the secrets database.
689 :param path: Path to the secrets database.
690 :param setup_path: Get the path to a setup file.
691 :param session_info: Session info.
692 :param credentials: Credentials
693 :param lp: Loadparm context
694 :return: LDB handle for the created secrets database
696 if os.path.exists(path):
698 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
701 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
702 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
704 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
706 if credentials is not None and credentials.authentication_requested():
707 if credentials.get_bind_dn() is not None:
708 setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
709 "LDAPMANAGERDN": credentials.get_bind_dn(),
710 "LDAPMANAGERPASS_B64": b64encode(credentials.get_password())
713 setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
714 "LDAPADMINUSER": credentials.get_username(),
715 "LDAPADMINREALM": credentials.get_realm(),
716 "LDAPADMINPASS_B64": b64encode(credentials.get_password())
721 def setup_registry(path, setup_path, session_info, lp):
722 """Setup the registry.
724 :param path: Path to the registry database
725 :param setup_path: Function that returns the path to a setup.
726 :param session_info: Session information
727 :param credentials: Credentials
728 :param lp: Loadparm context
730 reg = registry.Registry()
731 hive = registry.open_ldb(path, session_info=session_info,
733 reg.mount_hive(hive, registry.HKEY_LOCAL_MACHINE)
734 provision_reg = setup_path("provision.reg")
735 assert os.path.exists(provision_reg)
736 reg.diff_apply(provision_reg)
739 def setup_idmapdb(path, setup_path, session_info, lp):
740 """Setup the idmap database.
742 :param path: path to the idmap database
743 :param setup_path: Function that returns a path to a setup file
744 :param session_info: Session information
745 :param credentials: Credentials
746 :param lp: Loadparm context
748 if os.path.exists(path):
751 idmap_ldb = IDmapDB(path, session_info=session_info,
755 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
759 def setup_samdb_rootdse(samdb, setup_path, names):
760 """Setup the SamDB rootdse.
762 :param samdb: Sam Database handle
763 :param setup_path: Obtain setup path
765 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
766 "SCHEMADN": names.schemadn,
767 "NETBIOSNAME": names.netbiosname,
768 "DNSDOMAIN": names.dnsdomain,
769 "REALM": names.realm,
770 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
771 "DOMAINDN": names.domaindn,
772 "ROOTDN": names.rootdn,
773 "CONFIGDN": names.configdn,
774 "SERVERDN": names.serverdn,
778 def setup_self_join(samdb, names,
779 machinepass, dnspass,
780 domainsid, invocationid, setup_path,
781 policyguid, policyguid_dc, domainControllerFunctionality):
782 """Join a host to its own domain."""
783 assert isinstance(invocationid, str)
784 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
785 "CONFIGDN": names.configdn,
786 "SCHEMADN": names.schemadn,
787 "DOMAINDN": names.domaindn,
788 "SERVERDN": names.serverdn,
789 "INVOCATIONID": invocationid,
790 "NETBIOSNAME": names.netbiosname,
791 "DEFAULTSITE": names.sitename,
792 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
793 "MACHINEPASS_B64": b64encode(machinepass),
794 "DNSPASS_B64": b64encode(dnspass),
795 "REALM": names.realm,
796 "DOMAIN": names.domain,
797 "DNSDOMAIN": names.dnsdomain,
798 "SAMBA_VERSION_STRING": version,
799 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
801 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
802 "POLICYGUID": policyguid,
803 "POLICYGUID_DC": policyguid_dc,
804 "DNSDOMAIN": names.dnsdomain,
805 "DOMAINSID": str(domainsid),
806 "DOMAINDN": names.domaindn})
808 # add the NTDSGUID based SPNs
809 ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
810 names.ntdsguid = samdb.searchone(basedn=ntds_dn, attribute="objectGUID",
811 expression="", scope=SCOPE_BASE)
812 assert isinstance(names.ntdsguid, str)
814 # Setup fSMORoleOwner entries to point at the newly created DC entry
815 setup_modify_ldif(samdb, setup_path("provision_self_join_modify.ldif"), {
816 "DOMAIN": names.domain,
817 "DNSDOMAIN": names.dnsdomain,
818 "DOMAINDN": names.domaindn,
819 "CONFIGDN": names.configdn,
820 "SCHEMADN": names.schemadn,
821 "DEFAULTSITE": names.sitename,
822 "SERVERDN": names.serverdn,
823 "NETBIOSNAME": names.netbiosname,
824 "NTDSGUID": names.ntdsguid
828 def setup_samdb(path, setup_path, session_info, credentials, lp,
830 domainsid, domainguid, policyguid, policyguid_dc,
831 fill, adminpass, krbtgtpass,
832 machinepass, invocationid, dnspass,
833 serverrole, schema=None, ldap_backend=None):
834 """Setup a complete SAM Database.
836 :note: This will wipe the main SAM database file!
839 domainFunctionality = DS_DOMAIN_FUNCTION_2000
840 forestFunctionality = DS_DOMAIN_FUNCTION_2000
841 domainControllerFunctionality = DS_DC_FUNCTION_2008_R2
843 # Also wipes the database
844 setup_samdb_partitions(path, setup_path, message=message, lp=lp,
845 credentials=credentials, session_info=session_info,
847 ldap_backend=ldap_backend, serverrole=serverrole)
850 schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn,
851 sambadn=names.sambadn, ldap_backend_type=ldap_backend.ldap_backend_type)
853 # Load the database, but importantly, use Ldb not SamDB as we don't want to load the global schema
854 samdb = Ldb(session_info=session_info,
855 credentials=credentials, lp=lp)
857 message("Pre-loading the Samba 4 and AD schema")
859 # Load the schema from the one we computed earlier
860 samdb.set_schema_from_ldb(schema.ldb)
862 # And now we can connect to the DB - the schema won't be loaded from the DB
866 samdb.load_ldif_file_add(setup_path("provision_options.ldif"))
871 samdb.transaction_start()
873 message("Erasing data from partitions")
874 # Load the schema (again). This time it will force a reindex,
875 # and will therefore make the erase_partitions() below
876 # computationally sane
877 samdb.set_schema_from_ldb(schema.ldb)
878 samdb.erase_partitions()
880 # Set the domain functionality levels onto the database.
881 # Various module (the password_hash module in particular) need
882 # to know what level of AD we are emulating.
884 # These will be fixed into the database via the database
885 # modifictions below, but we need them set from the start.
886 samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
887 samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
888 samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
890 samdb.set_domain_sid(str(domainsid))
891 if serverrole == "domain controller":
892 samdb.set_invocation_id(invocationid)
894 message("Adding DomainDN: %s" % names.domaindn)
895 if serverrole == "domain controller":
896 domain_oc = "domainDNS"
898 domain_oc = "samba4LocalDomain"
900 #impersonate domain admin
901 admin_session_info = admin_session(lp, str(domainsid))
902 samdb.set_session_info(admin_session_info)
904 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
905 "DOMAINDN": names.domaindn,
906 "DOMAIN_OC": domain_oc
909 message("Modifying DomainDN: " + names.domaindn + "")
910 if domainguid is not None:
911 domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
915 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
916 "CREATTIME": str(int(time.time()) * 1e7), # seconds -> ticks
917 "DOMAINSID": str(domainsid),
918 "SCHEMADN": names.schemadn,
919 "NETBIOSNAME": names.netbiosname,
920 "DEFAULTSITE": names.sitename,
921 "CONFIGDN": names.configdn,
922 "SERVERDN": names.serverdn,
923 "POLICYGUID": policyguid,
924 "DOMAINDN": names.domaindn,
925 "DOMAINGUID_MOD": domainguid_mod,
926 "DOMAIN_FUNCTIONALITY": str(domainFunctionality),
927 "SAMBA_VERSION_STRING": version
930 message("Adding configuration container")
931 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
932 "CONFIGDN": names.configdn,
934 message("Modifying configuration container")
935 setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
936 "CONFIGDN": names.configdn,
937 "SCHEMADN": names.schemadn,
940 # The LDIF here was created when the Schema object was constructed
941 message("Setting up sam.ldb schema")
942 samdb.add_ldif(schema.schema_dn_add)
943 samdb.modify_ldif(schema.schema_dn_modify)
944 samdb.write_prefixes_from_schema()
945 samdb.add_ldif(schema.schema_data)
946 setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
947 {"SCHEMADN": names.schemadn})
949 message("Setting up sam.ldb configuration data")
950 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
951 "CONFIGDN": names.configdn,
952 "NETBIOSNAME": names.netbiosname,
953 "DEFAULTSITE": names.sitename,
954 "DNSDOMAIN": names.dnsdomain,
955 "DOMAIN": names.domain,
956 "SCHEMADN": names.schemadn,
957 "DOMAINDN": names.domaindn,
958 "SERVERDN": names.serverdn,
959 "FOREST_FUNCTIONALALITY": str(forestFunctionality)
962 message("Setting up display specifiers")
963 display_specifiers_ldif = read_ms_ldif(setup_path('display-specifiers/DisplaySpecifiers-Win2k8R2.txt'))
964 display_specifiers_ldif = substitute_var(display_specifiers_ldif, {"CONFIGDN": names.configdn})
965 check_all_substituted(display_specifiers_ldif)
966 samdb.add_ldif(display_specifiers_ldif)
968 message("Adding users container")
969 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
970 "DOMAINDN": names.domaindn})
971 message("Modifying users container")
972 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
973 "DOMAINDN": names.domaindn})
974 message("Adding computers container")
975 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
976 "DOMAINDN": names.domaindn})
977 message("Modifying computers container")
978 setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
979 "DOMAINDN": names.domaindn})
980 message("Setting up sam.ldb data")
981 setup_add_ldif(samdb, setup_path("provision.ldif"), {
982 "CREATTIME": str(int(time.time()) * 1e7), # seconds -> ticks
983 "DOMAINDN": names.domaindn,
984 "NETBIOSNAME": names.netbiosname,
985 "DEFAULTSITE": names.sitename,
986 "CONFIGDN": names.configdn,
987 "SERVERDN": names.serverdn,
988 "POLICYGUID_DC": policyguid_dc
991 if fill == FILL_FULL:
992 message("Setting up sam.ldb users and groups")
993 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
994 "DOMAINDN": names.domaindn,
995 "DOMAINSID": str(domainsid),
996 "CONFIGDN": names.configdn,
997 "ADMINPASS_B64": b64encode(adminpass),
998 "KRBTGTPASS_B64": b64encode(krbtgtpass),
1001 if serverrole == "domain controller":
1002 message("Setting up self join")
1003 setup_self_join(samdb, names=names, invocationid=invocationid,
1005 machinepass=machinepass,
1006 domainsid=domainsid, policyguid=policyguid,
1007 policyguid_dc=policyguid_dc,
1008 setup_path=setup_path,
1009 domainControllerFunctionality=domainControllerFunctionality)
1011 ntds_dn = "CN=NTDS Settings,CN=%s,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,%s" % (names.hostname, names.domaindn)
1012 names.ntdsguid = samdb.searchone(basedn=ntds_dn,
1013 attribute="objectGUID", expression="", scope=SCOPE_BASE)
1014 assert isinstance(names.ntdsguid, str)
1017 samdb.transaction_cancel()
1020 samdb.transaction_commit()
1025 FILL_NT4SYNC = "NT4SYNC"
1029 def provision(setup_dir, message, session_info,
1030 credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL,
1032 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1034 domain=None, hostname=None, hostip=None, hostip6=None,
1035 domainsid=None, adminpass=None, ldapadminpass=None,
1036 krbtgtpass=None, domainguid=None,
1037 policyguid=None, policyguid_dc=None, invocationid=None,
1039 dnspass=None, root=None, nobody=None, users=None,
1040 wheel=None, backup=None, aci=None, serverrole=None,
1041 ldap_backend_extra_port=None, ldap_backend_type=None,
1043 ol_mmr_urls=None, ol_olc=None,
1044 setup_ds_path=None, slapd_path=None, nosync=False,
1045 ldap_dryrun_mode=False):
1048 :note: caution, this wipes all existing data!
1051 def setup_path(file):
1052 return os.path.join(setup_dir, file)
1054 if domainsid is None:
1055 domainsid = security.random_sid()
1057 domainsid = security.dom_sid(domainsid)
1060 # create/adapt the group policy GUIDs
1061 if policyguid is None:
1062 policyguid = str(uuid.uuid4())
1063 policyguid = policyguid.upper()
1064 if policyguid_dc is None:
1065 policyguid_dc = str(uuid.uuid4())
1066 policyguid_dc = policyguid_dc.upper()
1068 if adminpass is None:
1069 adminpass = glue.generate_random_str(12)
1070 if krbtgtpass is None:
1071 krbtgtpass = glue.generate_random_str(12)
1072 if machinepass is None:
1073 machinepass = glue.generate_random_str(12)
1075 dnspass = glue.generate_random_str(12)
1076 if ldapadminpass is None:
1077 #Make a new, random password between Samba and it's LDAP server
1078 ldapadminpass=glue.generate_random_str(12)
1081 root_uid = findnss_uid([root or "root"])
1082 nobody_uid = findnss_uid([nobody or "nobody"])
1083 users_gid = findnss_gid([users or "users"])
1085 wheel_gid = findnss_gid(["wheel", "adm"])
1087 wheel_gid = findnss_gid([wheel])
1089 if targetdir is not None:
1090 if (not os.path.exists(os.path.join(targetdir, "etc"))):
1091 os.makedirs(os.path.join(targetdir, "etc"))
1092 smbconf = os.path.join(targetdir, "etc", "smb.conf")
1093 elif smbconf is None:
1094 smbconf = param.default_path()
1096 # only install a new smb.conf if there isn't one there already
1097 if not os.path.exists(smbconf):
1098 make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
1101 lp = param.LoadParm()
1104 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1105 dnsdomain=realm, serverrole=serverrole, sitename=sitename,
1106 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1109 paths = provision_paths_from_lp(lp, names.dnsdomain)
1113 hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1114 except socket.gaierror, (socket.EAI_NODATA, msg):
1119 hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1120 except socket.gaierror, (socket.EAI_NODATA, msg):
1123 if serverrole is None:
1124 serverrole = lp.get("server role")
1126 assert serverrole in ("domain controller", "member server", "standalone")
1127 if invocationid is None and serverrole == "domain controller":
1128 invocationid = str(uuid.uuid4())
1130 if not os.path.exists(paths.private_dir):
1131 os.mkdir(paths.private_dir)
1133 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1135 schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn,
1136 sambadn=names.sambadn, ldap_backend_type=ldap_backend_type)
1138 secrets_credentials = credentials
1139 provision_backend = None
1140 if ldap_backend_type:
1141 # We only support an LDAP backend over ldapi://
1143 provision_backend = ProvisionBackend(paths=paths, setup_path=setup_path,
1144 lp=lp, credentials=credentials,
1146 message=message, hostname=hostname,
1147 root=root, schema=schema,
1148 ldap_backend_type=ldap_backend_type,
1149 ldapadminpass=ldapadminpass,
1150 ldap_backend_extra_port=ldap_backend_extra_port,
1151 ol_mmr_urls=ol_mmr_urls,
1152 slapd_path=slapd_path,
1153 setup_ds_path=setup_ds_path,
1154 ldap_dryrun_mode=ldap_dryrun_mode)
1156 # Now use the backend credentials to access the databases
1157 credentials = provision_backend.credentials
1158 secrets_credentials = provision_backend.adminCredentials
1159 ldapi_url = provision_backend.ldapi_uri
1161 # only install a new shares config db if there is none
1162 if not os.path.exists(paths.shareconf):
1163 message("Setting up share.ldb")
1164 share_ldb = Ldb(paths.shareconf, session_info=session_info,
1165 credentials=credentials, lp=lp)
1166 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1169 message("Setting up secrets.ldb")
1170 secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
1171 session_info=session_info,
1172 credentials=secrets_credentials, lp=lp)
1174 message("Setting up the registry")
1175 setup_registry(paths.hklm, setup_path, session_info,
1178 message("Setting up idmap db")
1179 idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1182 message("Setting up SAM db")
1183 samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info,
1184 credentials=credentials, lp=lp, names=names,
1186 domainsid=domainsid,
1187 schema=schema, domainguid=domainguid,
1188 policyguid=policyguid, policyguid_dc=policyguid_dc,
1190 adminpass=adminpass, krbtgtpass=krbtgtpass,
1191 invocationid=invocationid,
1192 machinepass=machinepass, dnspass=dnspass,
1193 serverrole=serverrole, ldap_backend=provision_backend)
1195 if serverrole == "domain controller":
1196 if paths.netlogon is None:
1197 message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1198 message("Please either remove %s or see the template at %s" %
1199 ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1200 assert(paths.netlogon is not None)
1202 if paths.sysvol is None:
1203 message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1204 message("Please either remove %s or see the template at %s" %
1205 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1206 assert(paths.sysvol is not None)
1208 # Set up group policies (domain policy and domain controller policy)
1210 policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1211 "{" + policyguid + "}")
1212 os.makedirs(policy_path, 0755)
1213 open(os.path.join(policy_path, "GPT.INI"), 'w').write(
1214 "[General]\r\nVersion=65543")
1215 os.makedirs(os.path.join(policy_path, "MACHINE"), 0755)
1216 os.makedirs(os.path.join(policy_path, "USER"), 0755)
1218 policy_path_dc = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1219 "{" + policyguid_dc + "}")
1220 os.makedirs(policy_path_dc, 0755)
1221 open(os.path.join(policy_path_dc, "GPT.INI"), 'w').write(
1222 "[General]\r\nVersion=2")
1223 os.makedirs(os.path.join(policy_path_dc, "MACHINE"), 0755)
1224 os.makedirs(os.path.join(policy_path_dc, "USER"), 0755)
1226 if not os.path.isdir(paths.netlogon):
1227 os.makedirs(paths.netlogon, 0755)
1229 if samdb_fill == FILL_FULL:
1230 setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1231 root_uid=root_uid, nobody_uid=nobody_uid,
1232 users_gid=users_gid, wheel_gid=wheel_gid)
1234 message("Setting up sam.ldb rootDSE marking as synchronized")
1235 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1237 # Only make a zone file on the first DC, it should be replicated with DNS replication
1238 if serverrole == "domain controller":
1239 secrets_ldb = Ldb(paths.secrets, session_info=session_info,
1240 credentials=credentials, lp=lp)
1241 secretsdb_become_dc(secrets_ldb, setup_path, domain=domain,
1243 netbiosname=names.netbiosname,
1244 domainsid=domainsid,
1245 keytab_path=paths.keytab, samdb_url=paths.samdb,
1246 dns_keytab_path=paths.dns_keytab,
1247 dnspass=dnspass, machinepass=machinepass,
1248 dnsdomain=names.dnsdomain)
1250 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1251 assert isinstance(domainguid, str)
1253 create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1254 domaindn=names.domaindn, hostip=hostip,
1255 hostip6=hostip6, hostname=names.hostname,
1256 dnspass=dnspass, realm=names.realm,
1257 domainguid=domainguid, ntdsguid=names.ntdsguid)
1259 create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1260 dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1262 create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1263 dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1264 keytab_name=paths.dns_keytab)
1265 message("See %s for an example configuration include file for BIND" % paths.namedconf)
1266 message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1268 create_krb5_conf(paths.krb5conf, setup_path,
1269 dnsdomain=names.dnsdomain, hostname=names.hostname,
1271 message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1274 if provision_backend is not None:
1275 if ldap_backend_type == "fedora-ds":
1276 ldapi_db = Ldb(provision_backend.ldapi_uri, lp=lp, credentials=credentials)
1278 # delete default SASL mappings
1279 res = ldapi_db.search(expression="(!(cn=samba-admin mapping))", base="cn=mapping,cn=sasl,cn=config", scope=SCOPE_ONELEVEL, attrs=["dn"])
1281 # configure in-directory access control on Fedora DS via the aci attribute (over a direct ldapi:// socket)
1282 for i in range (0, len(res)):
1283 dn = str(res[i]["dn"])
1286 aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % names.sambadn
1289 m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci")
1291 m.dn = ldb.Dn(1, names.domaindn)
1294 m.dn = ldb.Dn(1, names.configdn)
1297 m.dn = ldb.Dn(1, names.schemadn)
1300 # if an LDAP backend is in use, terminate slapd after final provision and check its proper termination
1301 if provision_backend.slapd.poll() is None:
1303 if hasattr(provision_backend.slapd, "terminate"):
1304 provision_backend.slapd.terminate()
1306 # Older python versions don't have .terminate()
1308 os.kill(provision_backend.slapd.pid, signal.SIGTERM)
1310 #and now wait for it to die
1311 provision_backend.slapd.communicate()
1313 # now display slapd_command_file.txt to show how slapd must be started next time
1314 message("Use later the following commandline to start slapd, then Samba:")
1315 slapd_command = "\'" + "\' \'".join(provision_backend.slapd_command) + "\'"
1316 message(slapd_command)
1317 message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
1319 setup_file(setup_path("ldap_backend_startup.sh"), paths.ldapdir + "/ldap_backend_startup.sh", {
1320 "SLAPD_COMMAND" : slapd_command})
1323 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1326 message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1328 message("Once the above files are installed, your Samba4 server will be ready to use")
1329 message("Server Role: %s" % serverrole)
1330 message("Hostname: %s" % names.hostname)
1331 message("NetBIOS Domain: %s" % names.domain)
1332 message("DNS Domain: %s" % names.dnsdomain)
1333 message("DOMAIN SID: %s" % str(domainsid))
1334 if samdb_fill == FILL_FULL:
1335 message("Admin password: %s" % adminpass)
1336 if provision_backend:
1337 if provision_backend.credentials.get_bind_dn() is not None:
1338 message("LDAP Backend Admin DN: %s" % provision_backend.credentials.get_bind_dn())
1340 message("LDAP Admin User: %s" % provision_backend.credentials.get_username())
1342 message("LDAP Admin Password: %s" % provision_backend.credentials.get_password())
1344 result = ProvisionResult()
1345 result.domaindn = domaindn
1346 result.paths = paths
1348 result.samdb = samdb
1353 def provision_become_dc(setup_dir=None,
1354 smbconf=None, targetdir=None, realm=None,
1355 rootdn=None, domaindn=None, schemadn=None,
1356 configdn=None, serverdn=None,
1357 domain=None, hostname=None, domainsid=None,
1358 adminpass=None, krbtgtpass=None, domainguid=None,
1359 policyguid=None, policyguid_dc=None, invocationid=None,
1361 dnspass=None, root=None, nobody=None, users=None,
1362 wheel=None, backup=None, serverrole=None,
1363 ldap_backend=None, ldap_backend_type=None,
1364 sitename=None, debuglevel=1):
1367 """print a message if quiet is not set."""
1370 glue.set_debug_level(debuglevel)
1372 return provision(setup_dir, message, system_session(), None,
1373 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS,
1374 realm=realm, rootdn=rootdn, domaindn=domaindn, schemadn=schemadn,
1375 configdn=configdn, serverdn=serverdn, domain=domain,
1376 hostname=hostname, hostip="127.0.0.1", domainsid=domainsid,
1377 machinepass=machinepass, serverrole="domain controller",
1381 def setup_db_config(setup_path, dbdir):
1382 """Setup a Berkeley database.
1384 :param setup_path: Setup path function.
1385 :param dbdir: Database directory."""
1386 if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1387 os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1388 if not os.path.isdir(os.path.join(dbdir, "tmp")):
1389 os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1391 setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1392 {"LDAPDBDIR": dbdir})
1394 class ProvisionBackend(object):
1395 def __init__(self, paths=None, setup_path=None, lp=None, credentials=None,
1396 names=None, message=None,
1397 hostname=None, root=None,
1398 schema=None, ldapadminpass=None,
1399 ldap_backend_type=None, ldap_backend_extra_port=None,
1401 setup_ds_path=None, slapd_path=None,
1402 nosync=False, ldap_dryrun_mode=False):
1403 """Provision an LDAP backend for samba4
1405 This works for OpenLDAP and Fedora DS
1408 self.ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.ldapdir, "ldapi"), safe="")
1410 if not os.path.isdir(paths.ldapdir):
1411 os.makedirs(paths.ldapdir, 0700)
1413 if ldap_backend_type == "existing":
1414 #Check to see that this 'existing' LDAP backend in fact exists
1415 ldapi_db = Ldb(self.ldapi_uri, credentials=credentials)
1416 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1417 expression="(objectClass=OpenLDAProotDSE)")
1419 # If we have got here, then we must have a valid connection to the LDAP server, with valid credentials supplied
1420 # This caused them to be set into the long-term database later in the script.
1421 self.credentials = credentials
1422 self.ldap_backend_type = "openldap" #For now, assume existing backends at least emulate OpenLDAP
1425 # we will shortly start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via self.ldapi_uri
1426 # if another instance of slapd is already running
1428 ldapi_db = Ldb(self.ldapi_uri)
1429 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1430 expression="(objectClass=OpenLDAProotDSE)");
1432 f = open(paths.slapdpid, "r")
1435 message("Check for slapd Process with PID: " + str(p) + " and terminate it manually.")
1439 raise ProvisioningError("Warning: Another slapd Instance seems already running on this host, listening to " + self.ldapi_uri + ". Please shut it down before you continue. ")
1444 # Try to print helpful messages when the user has not specified the path to slapd
1445 if slapd_path is None:
1446 raise ProvisioningError("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
1447 if not os.path.exists(slapd_path):
1448 message (slapd_path)
1449 raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1451 schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1453 os.unlink(schemadb_path)
1458 # Put the LDIF of the schema into a database so we can search on
1459 # it to generate schema-dependent configurations in Fedora DS and
1461 os.path.join(paths.ldapdir, "schema-tmp.ldb")
1462 schema.ldb.connect(schemadb_path)
1463 schema.ldb.transaction_start()
1465 # These bits of LDIF are supplied when the Schema object is created
1466 schema.ldb.add_ldif(schema.schema_dn_add)
1467 schema.ldb.modify_ldif(schema.schema_dn_modify)
1468 schema.ldb.add_ldif(schema.schema_data)
1469 schema.ldb.transaction_commit()
1471 self.credentials = Credentials()
1472 self.credentials.guess(lp)
1473 #Kerberos to an ldapi:// backend makes no sense
1474 self.credentials.set_kerberos_state(DONT_USE_KERBEROS)
1476 self.adminCredentials = Credentials()
1477 self.adminCredentials.guess(lp)
1478 #Kerberos to an ldapi:// backend makes no sense
1479 self.adminCredentials.set_kerberos_state(DONT_USE_KERBEROS)
1481 self.ldap_backend_type = ldap_backend_type
1483 if ldap_backend_type == "fedora-ds":
1484 provision_fds_backend(self, paths=paths, setup_path=setup_path,
1485 names=names, message=message,
1487 ldapadminpass=ldapadminpass, root=root,
1489 ldap_backend_extra_port=ldap_backend_extra_port,
1490 setup_ds_path=setup_ds_path,
1491 slapd_path=slapd_path,
1493 ldap_dryrun_mode=ldap_dryrun_mode)
1495 elif ldap_backend_type == "openldap":
1496 provision_openldap_backend(self, paths=paths, setup_path=setup_path,
1497 names=names, message=message,
1499 ldapadminpass=ldapadminpass, root=root,
1501 ldap_backend_extra_port=ldap_backend_extra_port,
1502 ol_mmr_urls=ol_mmr_urls,
1503 slapd_path=slapd_path,
1505 ldap_dryrun_mode=ldap_dryrun_mode)
1507 raise ProvisioningError("Unknown LDAP backend type selected")
1509 self.credentials.set_password(ldapadminpass)
1510 self.adminCredentials.set_username("samba-admin")
1511 self.adminCredentials.set_password(ldapadminpass)
1513 # Now start the slapd, so we can provision onto it. We keep the
1514 # subprocess context around, to kill this off at the successful
1516 self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False)
1518 while self.slapd.poll() is None:
1519 # Wait until the socket appears
1521 ldapi_db = Ldb(self.ldapi_uri, lp=lp, credentials=self.credentials)
1522 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1523 expression="(objectClass=OpenLDAProotDSE)")
1524 # If we have got here, then we must have a valid connection to the LDAP server!
1530 raise ProvisioningError("slapd died before we could make a connection to it")
1533 def provision_openldap_backend(result, paths=None, setup_path=None, names=None,
1535 hostname=None, ldapadminpass=None, root=None,
1537 ldap_backend_extra_port=None,
1539 slapd_path=None, nosync=False,
1540 ldap_dryrun_mode=False):
1542 #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
1545 nosync_config = "dbnosync"
1547 lnkattr = get_linked_attributes(names.schemadn,schema.ldb)
1548 refint_attributes = ""
1549 memberof_config = "# Generated from Samba4 schema\n"
1550 for att in lnkattr.keys():
1551 if lnkattr[att] is not None:
1552 refint_attributes = refint_attributes + " " + att
1554 memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1555 { "MEMBER_ATTR" : att ,
1556 "MEMBEROF_ATTR" : lnkattr[att] })
1558 refint_config = read_and_sub_file(setup_path("refint.conf"),
1559 { "LINK_ATTRS" : refint_attributes})
1561 attrs = ["linkID", "lDAPDisplayName"]
1562 res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1564 for i in range (0, len(res)):
1565 index_attr = res[i]["lDAPDisplayName"][0]
1566 if index_attr == "objectGUID":
1567 index_attr = "entryUUID"
1569 index_config += "index " + index_attr + " eq\n"
1571 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1573 mmr_replicator_acl = ""
1574 mmr_serverids_config = ""
1575 mmr_syncrepl_schema_config = ""
1576 mmr_syncrepl_config_config = ""
1577 mmr_syncrepl_user_config = ""
1580 if ol_mmr_urls is not None:
1581 # For now, make these equal
1582 mmr_pass = ldapadminpass
1584 url_list=filter(None,ol_mmr_urls.split(' '))
1585 if (len(url_list) == 1):
1586 url_list=filter(None,ol_mmr_urls.split(','))
1589 mmr_on_config = "MirrorMode On"
1590 mmr_replicator_acl = " by dn=cn=replicator,cn=samba read"
1592 for url in url_list:
1594 mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1595 { "SERVERID" : str(serverid),
1596 "LDAPSERVER" : url })
1599 mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1601 "MMRDN": names.schemadn,
1603 "MMR_PASSWORD": mmr_pass})
1606 mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1608 "MMRDN": names.configdn,
1610 "MMR_PASSWORD": mmr_pass})
1613 mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1615 "MMRDN": names.domaindn,
1617 "MMR_PASSWORD": mmr_pass })
1618 # OpenLDAP cn=config initialisation
1619 olc_syncrepl_config = ""
1621 # if mmr = yes, generate cn=config-replication directives
1622 # and olc_seed.lif for the other mmr-servers
1623 if ol_mmr_urls is not None:
1625 olc_serverids_config = ""
1626 olc_syncrepl_seed_config = ""
1627 olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
1629 for url in url_list:
1631 olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
1632 { "SERVERID" : str(serverid),
1633 "LDAPSERVER" : url })
1636 olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
1639 "MMR_PASSWORD": mmr_pass})
1641 olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1643 "LDAPSERVER" : url})
1645 setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif,
1646 {"OLC_SERVER_ID_CONF": olc_serverids_config,
1647 "OLC_PW": ldapadminpass,
1648 "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
1651 setup_file(setup_path("slapd.conf"), paths.slapdconf,
1652 {"DNSDOMAIN": names.dnsdomain,
1653 "LDAPDIR": paths.ldapdir,
1654 "DOMAINDN": names.domaindn,
1655 "CONFIGDN": names.configdn,
1656 "SCHEMADN": names.schemadn,
1657 "MEMBEROF_CONFIG": memberof_config,
1658 "MIRRORMODE": mmr_on_config,
1659 "REPLICATOR_ACL": mmr_replicator_acl,
1660 "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1661 "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1662 "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1663 "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1664 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
1665 "OLC_MMR_CONFIG": olc_mmr_config,
1666 "REFINT_CONFIG": refint_config,
1667 "INDEX_CONFIG": index_config,
1668 "NOSYNC": nosync_config})
1670 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1671 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1672 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1674 if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba", "cn=samba")):
1675 os.makedirs(os.path.join(paths.ldapdir, "db", "samba", "cn=samba"), 0700)
1677 setup_file(setup_path("cn=samba.ldif"),
1678 os.path.join(paths.ldapdir, "db", "samba", "cn=samba.ldif"),
1679 { "UUID": str(uuid.uuid4()),
1680 "LDAPTIME": timestring(int(time.time()))} )
1681 setup_file(setup_path("cn=samba-admin.ldif"),
1682 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=samba-admin.ldif"),
1683 {"LDAPADMINPASS_B64": b64encode(ldapadminpass),
1684 "UUID": str(uuid.uuid4()),
1685 "LDAPTIME": timestring(int(time.time()))} )
1687 if ol_mmr_urls is not None:
1688 setup_file(setup_path("cn=replicator.ldif"),
1689 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=replicator.ldif"),
1690 {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1691 "UUID": str(uuid.uuid4()),
1692 "LDAPTIME": timestring(int(time.time()))} )
1695 mapping = "schema-map-openldap-2.3"
1696 backend_schema = "backend-schema.schema"
1698 backend_schema_data = schema.ldb.convert_schema_to_openldap("openldap", open(setup_path(mapping), 'r').read())
1699 assert backend_schema_data is not None
1700 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1702 # now we generate the needed strings to start slapd automatically,
1703 # first ldapi_uri...
1704 if ldap_backend_extra_port is not None:
1705 # When we use MMR, we can't use 0.0.0.0 as it uses the name
1706 # specified there as part of it's clue as to it's own name,
1707 # and not to replicate to itself
1708 if ol_mmr_urls is None:
1709 server_port_string = "ldap://0.0.0.0:%d" % ldap_backend_extra_port
1711 server_port_string = "ldap://" + names.hostname + "." + names.dnsdomain +":%d" % ldap_backend_extra_port
1713 server_port_string = ""
1715 # Prepare the 'result' information - the commands to return in particular
1716 result.slapd_provision_command = [slapd_path]
1718 result.slapd_provision_command.append("-F" + paths.olcdir)
1720 result.slapd_provision_command.append("-h")
1722 # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands
1723 result.slapd_command = list(result.slapd_provision_command)
1725 result.slapd_provision_command.append(result.ldapi_uri)
1726 result.slapd_provision_command.append("-d0")
1728 uris = result.ldapi_uri
1729 if server_port_string is not "":
1730 uris = uris + " " + server_port_string
1732 result.slapd_command.append(uris)
1734 # Set the username - done here because Fedora DS still uses the admin DN and simple bind
1735 result.credentials.set_username("samba-admin")
1737 # If we were just looking for crashes up to this point, it's a
1738 # good time to exit before we realise we don't have OpenLDAP on
1740 if ldap_dryrun_mode:
1743 # Finally, convert the configuration into cn=config style!
1744 if not os.path.isdir(paths.olcdir):
1745 os.makedirs(paths.olcdir, 0770)
1747 retcode = subprocess.call([slapd_path, "-Ttest", "-f", paths.slapdconf, "-F", paths.olcdir], close_fds=True, shell=False)
1749 # We can't do this, as OpenLDAP is strange. It gives an error
1750 # output to the above, but does the conversion sucessfully...
1753 # raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1755 if not os.path.exists(os.path.join(paths.olcdir, "cn=config.ldif")):
1756 raise ProvisioningError("conversion from slapd.conf to cn=config failed")
1758 # Don't confuse the admin by leaving the slapd.conf around
1759 os.remove(paths.slapdconf)
1762 def provision_fds_backend(result, paths=None, setup_path=None, names=None,
1764 hostname=None, ldapadminpass=None, root=None,
1766 ldap_backend_extra_port=None,
1770 ldap_dryrun_mode=False):
1772 if ldap_backend_extra_port is not None:
1773 serverport = "ServerPort=%d" % ldap_backend_extra_port
1777 setup_file(setup_path("fedorads.inf"), paths.fedoradsinf,
1779 "HOSTNAME": hostname,
1780 "DNSDOMAIN": names.dnsdomain,
1781 "LDAPDIR": paths.ldapdir,
1782 "DOMAINDN": names.domaindn,
1783 "LDAPMANAGERDN": names.ldapmanagerdn,
1784 "LDAPMANAGERPASS": ldapadminpass,
1785 "SERVERPORT": serverport})
1787 setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions,
1788 {"CONFIGDN": names.configdn,
1789 "SCHEMADN": names.schemadn,
1790 "SAMBADN": names.sambadn,
1793 setup_file(setup_path("fedorads-sasl.ldif"), paths.fedoradssasl,
1794 {"SAMBADN": names.sambadn,
1797 setup_file(setup_path("fedorads-samba.ldif"), paths.fedoradssamba,
1798 {"SAMBADN": names.sambadn,
1799 "LDAPADMINPASS": ldapadminpass
1802 mapping = "schema-map-fedora-ds-1.0"
1803 backend_schema = "99_ad.ldif"
1805 # Build a schema file in Fedora DS format
1806 backend_schema_data = schema.ldb.convert_schema_to_openldap("fedora-ds", open(setup_path(mapping), 'r').read())
1807 assert backend_schema_data is not None
1808 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1810 result.credentials.set_bind_dn(names.ldapmanagerdn)
1812 # Destory the target directory, or else setup-ds.pl will complain
1813 fedora_ds_dir = os.path.join(paths.ldapdir, "slapd-samba4")
1814 shutil.rmtree(fedora_ds_dir, True)
1816 result.slapd_provision_command = [slapd_path, "-D", fedora_ds_dir, "-i", paths.slapdpid];
1817 #In the 'provision' command line, stay in the foreground so we can easily kill it
1818 result.slapd_provision_command.append("-d0")
1820 #the command for the final run is the normal script
1821 result.slapd_command = [os.path.join(paths.ldapdir, "slapd-samba4", "start-slapd")]
1823 # If we were just looking for crashes up to this point, it's a
1824 # good time to exit before we realise we don't have Fedora DS on
1825 if ldap_dryrun_mode:
1828 # Try to print helpful messages when the user has not specified the path to the setup-ds tool
1829 if setup_ds_path is None:
1830 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\"!")
1831 if not os.path.exists(setup_ds_path):
1832 message (setup_ds_path)
1833 raise ProvisioningError("Warning: Given Path to slapd does not exist!")
1835 # Run the Fedora DS setup utility
1836 retcode = subprocess.call([setup_ds_path, "--silent", "--file", paths.fedoradsinf], close_fds=True, shell=False)
1838 raise ProvisioningError("setup-ds failed")
1841 retcode = subprocess.call([
1842 os.path.join(paths.ldapdir, "slapd-samba4", "ldif2db"), "-s", names.sambadn, "-i", paths.fedoradssamba],
1843 close_fds=True, shell=False)
1845 raise("ldib2db failed")
1847 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1848 """Create a PHP LDAP admin configuration file.
1850 :param path: Path to write the configuration to.
1851 :param setup_path: Function to generate setup paths.
1853 setup_file(setup_path("phpldapadmin-config.php"), path,
1854 {"S4_LDAPI_URI": ldapi_uri})
1857 def create_zone_file(path, setup_path, dnsdomain, domaindn,
1858 hostip, hostip6, hostname, dnspass, realm, domainguid,
1860 """Write out a DNS zone file, from the info in the current database.
1862 :param path: Path of the new zone file.
1863 :param setup_path: Setup path function.
1864 :param dnsdomain: DNS Domain name
1865 :param domaindn: DN of the Domain
1866 :param hostip: Local IPv4 IP
1867 :param hostip6: Local IPv6 IP
1868 :param hostname: Local hostname
1869 :param dnspass: Password for DNS
1870 :param realm: Realm name
1871 :param domainguid: GUID of the domain.
1872 :param ntdsguid: GUID of the hosts nTDSDSA record.
1874 assert isinstance(domainguid, str)
1876 if hostip6 is not None:
1877 hostip6_base_line = " IN AAAA " + hostip6
1878 hostip6_host_line = hostname + " IN AAAA " + hostip6
1880 hostip6_base_line = ""
1881 hostip6_host_line = ""
1883 if hostip is not None:
1884 hostip_base_line = " IN A " + hostip
1885 hostip_host_line = hostname + " IN A " + hostip
1887 hostip_base_line = ""
1888 hostip_host_line = ""
1890 setup_file(setup_path("provision.zone"), path, {
1891 "DNSPASS_B64": b64encode(dnspass),
1892 "HOSTNAME": hostname,
1893 "DNSDOMAIN": dnsdomain,
1895 "HOSTIP_BASE_LINE": hostip_base_line,
1896 "HOSTIP_HOST_LINE": hostip_host_line,
1897 "DOMAINGUID": domainguid,
1898 "DATESTRING": time.strftime("%Y%m%d%H"),
1899 "DEFAULTSITE": DEFAULTSITE,
1900 "NTDSGUID": ntdsguid,
1901 "HOSTIP6_BASE_LINE": hostip6_base_line,
1902 "HOSTIP6_HOST_LINE": hostip6_host_line,
1906 def create_named_conf(path, setup_path, realm, dnsdomain,
1908 """Write out a file containing zone statements suitable for inclusion in a
1909 named.conf file (including GSS-TSIG configuration).
1911 :param path: Path of the new named.conf file.
1912 :param setup_path: Setup path function.
1913 :param realm: Realm name
1914 :param dnsdomain: DNS Domain name
1915 :param private_dir: Path to private directory
1916 :param keytab_name: File name of DNS keytab file
1919 setup_file(setup_path("named.conf"), path, {
1920 "DNSDOMAIN": dnsdomain,
1922 "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1923 "PRIVATE_DIR": private_dir
1926 def create_named_txt(path, setup_path, realm, dnsdomain,
1927 private_dir, keytab_name):
1928 """Write out a file containing zone statements suitable for inclusion in a
1929 named.conf file (including GSS-TSIG configuration).
1931 :param path: Path of the new named.conf file.
1932 :param setup_path: Setup path function.
1933 :param realm: Realm name
1934 :param dnsdomain: DNS Domain name
1935 :param private_dir: Path to private directory
1936 :param keytab_name: File name of DNS keytab file
1939 setup_file(setup_path("named.txt"), path, {
1940 "DNSDOMAIN": dnsdomain,
1942 "DNS_KEYTAB": keytab_name,
1943 "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1944 "PRIVATE_DIR": private_dir
1947 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1948 """Write out a file containing zone statements suitable for inclusion in a
1949 named.conf file (including GSS-TSIG configuration).
1951 :param path: Path of the new named.conf file.
1952 :param setup_path: Setup path function.
1953 :param dnsdomain: DNS Domain name
1954 :param hostname: Local hostname
1955 :param realm: Realm name
1958 setup_file(setup_path("krb5.conf"), path, {
1959 "DNSDOMAIN": dnsdomain,
1960 "HOSTNAME": hostname,