s4: Re-add --ldapadminpass as an option to provision
[Samba/kamenim.git] / source4 / scripting / python / samba / provision.py
blobbcb6566998eca1e8da20b0512b51679907c9eb93
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
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
29 import os
30 import sys
31 import pwd
32 import grp
33 import time
34 import uuid, glue
35 import socket
36 import param
37 import registry
38 import samba
39 import subprocess
41 import shutil
42 from credentials import Credentials
43 from auth import system_session
44 from samba import version, Ldb, substitute_var, valid_netbios_name, check_all_substituted, \
45 DS_BEHAVIOR_WIN2008
46 from samba.samdb import SamDB
47 from samba.idmap import IDmapDB
48 from samba.dcerpc import security
49 import urllib
50 from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError, timestring
51 from ms_schema import read_ms_schema
52 from signal import SIGTERM
54 __docformat__ = "restructuredText"
57 def find_setup_dir():
58 """Find the setup directory used by provision."""
59 dirname = os.path.dirname(__file__)
60 if "/site-packages/" in dirname:
61 prefix = "/".join(dirname[:dirname.index("/site-packages/")].split("/")[:-2])
62 for suffix in ["share/setup", "share/samba/setup", "setup"]:
63 ret = os.path.join(prefix, suffix)
64 if os.path.isdir(ret):
65 return ret
66 # In source tree
67 ret = os.path.join(dirname, "../../../setup")
68 if os.path.isdir(ret):
69 return ret
70 raise Exception("Unable to find setup directory.")
73 DEFAULTSITE = "Default-First-Site-Name"
75 class InvalidNetbiosName(Exception):
76 """A specified name was not a valid NetBIOS name."""
77 def __init__(self, name):
78 super(InvalidNetbiosName, self).__init__("The name '%r' is not a valid NetBIOS name" % name)
81 class ProvisionPaths(object):
82 def __init__(self):
83 self.shareconf = None
84 self.hklm = None
85 self.hkcu = None
86 self.hkcr = None
87 self.hku = None
88 self.hkpd = None
89 self.hkpt = None
90 self.samdb = None
91 self.idmapdb = None
92 self.secrets = None
93 self.keytab = None
94 self.dns_keytab = None
95 self.dns = None
96 self.winsdb = None
97 self.private_dir = None
98 self.ldapdir = None
99 self.slapdconf = None
100 self.modulesconf = None
101 self.memberofconf = None
102 self.fedoradsinf = None
103 self.fedoradspartitions = None
104 self.olmmron = None
105 self.olmmrserveridsconf = None
106 self.olmmrsyncreplconf = None
107 self.olcdir = None
108 self.olslapd = None
109 self.olcseedldif = None
112 class ProvisionNames(object):
113 def __init__(self):
114 self.rootdn = None
115 self.domaindn = None
116 self.configdn = None
117 self.schemadn = None
118 self.ldapmanagerdn = None
119 self.dnsdomain = None
120 self.realm = None
121 self.netbiosname = None
122 self.domain = None
123 self.hostname = None
124 self.sitename = None
125 self.smbconf = None
128 class ProvisionResult(object):
129 def __init__(self):
130 self.paths = None
131 self.domaindn = None
132 self.lp = None
133 self.samdb = None
135 class Schema(object):
136 def __init__(self, setup_path, schemadn=None,
137 serverdn=None):
138 """Load schema for the SamDB from the AD schema files and samba4_schema.ldif
140 :param samdb: Load a schema into a SamDB.
141 :param setup_path: Setup path function.
142 :param schemadn: DN of the schema
143 :param serverdn: DN of the server
145 Returns the schema data loaded, to avoid double-parsing when then needing to add it to the db
148 self.ldb = Ldb()
149 self.schema_data = read_ms_schema(setup_path('ad-schema/MS-AD_Schema_2K8_Attributes.txt'),
150 setup_path('ad-schema/MS-AD_Schema_2K8_Classes.txt'))
151 self.schema_data += open(setup_path("schema_samba4.ldif"), 'r').read()
152 self.schema_data = substitute_var(self.schema_data, {"SCHEMADN": schemadn})
153 check_all_substituted(self.schema_data)
154 prefixmap = open(setup_path("prefixMap.txt"), 'r').read()
155 prefixmap = b64encode(prefixmap)
157 self.schema_dn_modify = read_and_sub_file(setup_path("provision_schema_basedn_modify.ldif"),
158 {"SCHEMADN": schemadn,
159 "PREFIXMAP_B64": prefixmap,
160 "SERVERDN": serverdn,
162 self.schema_dn_add = read_and_sub_file(setup_path("provision_schema_basedn.ldif"),
163 {"SCHEMADN": schemadn
165 self.ldb.set_schema_from_ldif(self.schema_dn_modify, self.schema_data)
168 def check_install(lp, session_info, credentials):
169 """Check whether the current install seems ok.
171 :param lp: Loadparm context
172 :param session_info: Session information
173 :param credentials: Credentials
175 if lp.get("realm") == "":
176 raise Exception("Realm empty")
177 ldb = Ldb(lp.get("sam database"), session_info=session_info,
178 credentials=credentials, lp=lp)
179 if len(ldb.search("(cn=Administrator)")) != 1:
180 raise "No administrator account found"
183 def findnss(nssfn, names):
184 """Find a user or group from a list of possibilities.
186 :param nssfn: NSS Function to try (should raise KeyError if not found)
187 :param names: Names to check.
188 :return: Value return by first names list.
190 for name in names:
191 try:
192 return nssfn(name)
193 except KeyError:
194 pass
195 raise KeyError("Unable to find user/group %r" % names)
198 findnss_uid = lambda names: findnss(pwd.getpwnam, names)[2]
199 findnss_gid = lambda names: findnss(grp.getgrnam, names)[2]
202 def read_and_sub_file(file, subst_vars):
203 """Read a file and sub in variables found in it
205 :param file: File to be read (typically from setup directory)
206 param subst_vars: Optional variables to subsitute in the file.
208 data = open(file, 'r').read()
209 if subst_vars is not None:
210 data = substitute_var(data, subst_vars)
211 check_all_substituted(data)
212 return data
215 def setup_add_ldif(ldb, ldif_path, subst_vars=None):
216 """Setup a ldb in the private dir.
218 :param ldb: LDB file to import data into
219 :param ldif_path: Path of the LDIF file to load
220 :param subst_vars: Optional variables to subsitute in LDIF.
222 assert isinstance(ldif_path, str)
224 data = read_and_sub_file(ldif_path, subst_vars)
225 ldb.add_ldif(data)
228 def setup_modify_ldif(ldb, ldif_path, subst_vars=None):
229 """Modify a ldb in the private dir.
231 :param ldb: LDB object.
232 :param ldif_path: LDIF file path.
233 :param subst_vars: Optional dictionary with substitution variables.
235 data = read_and_sub_file(ldif_path, subst_vars)
237 ldb.modify_ldif(data)
240 def setup_ldb(ldb, ldif_path, subst_vars):
241 """Import a LDIF a file into a LDB handle, optionally substituting variables.
243 :note: Either all LDIF data will be added or none (using transactions).
245 :param ldb: LDB file to import into.
246 :param ldif_path: Path to the LDIF file.
247 :param subst_vars: Dictionary with substitution variables.
249 assert ldb is not None
250 ldb.transaction_start()
251 try:
252 setup_add_ldif(ldb, ldif_path, subst_vars)
253 except:
254 ldb.transaction_cancel()
255 raise
256 ldb.transaction_commit()
259 def setup_file(template, fname, subst_vars):
260 """Setup a file in the private dir.
262 :param template: Path of the template file.
263 :param fname: Path of the file to create.
264 :param subst_vars: Substitution variables.
266 f = fname
268 if os.path.exists(f):
269 os.unlink(f)
271 data = read_and_sub_file(template, subst_vars)
272 open(f, 'w').write(data)
275 def provision_paths_from_lp(lp, dnsdomain):
276 """Set the default paths for provisioning.
278 :param lp: Loadparm context.
279 :param dnsdomain: DNS Domain name
281 paths = ProvisionPaths()
282 paths.private_dir = lp.get("private dir")
283 paths.keytab = "secrets.keytab"
284 paths.dns_keytab = "dns.keytab"
286 paths.shareconf = os.path.join(paths.private_dir, "share.ldb")
287 paths.samdb = os.path.join(paths.private_dir, lp.get("sam database") or "samdb.ldb")
288 paths.idmapdb = os.path.join(paths.private_dir, lp.get("idmap database") or "idmap.ldb")
289 paths.secrets = os.path.join(paths.private_dir, lp.get("secrets database") or "secrets.ldb")
290 paths.templates = os.path.join(paths.private_dir, "templates.ldb")
291 paths.dns = os.path.join(paths.private_dir, dnsdomain + ".zone")
292 paths.namedconf = os.path.join(paths.private_dir, "named.conf")
293 paths.namedtxt = os.path.join(paths.private_dir, "named.txt")
294 paths.krb5conf = os.path.join(paths.private_dir, "krb5.conf")
295 paths.winsdb = os.path.join(paths.private_dir, "wins.ldb")
296 paths.s4_ldapi_path = os.path.join(paths.private_dir, "ldapi")
297 paths.phpldapadminconfig = os.path.join(paths.private_dir,
298 "phpldapadmin-config.php")
299 paths.ldapdir = os.path.join(paths.private_dir,
300 "ldap")
301 paths.slapdconf = os.path.join(paths.ldapdir,
302 "slapd.conf")
303 paths.slapdpid = os.path.join(paths.ldapdir,
304 "slapd.pid")
305 paths.modulesconf = os.path.join(paths.ldapdir,
306 "modules.conf")
307 paths.memberofconf = os.path.join(paths.ldapdir,
308 "memberof.conf")
309 paths.fedoradsinf = os.path.join(paths.ldapdir,
310 "fedorads.inf")
311 paths.fedoradspartitions = os.path.join(paths.ldapdir,
312 "fedorads-partitions.ldif")
313 paths.olmmrserveridsconf = os.path.join(paths.ldapdir,
314 "mmr_serverids.conf")
315 paths.olmmrsyncreplconf = os.path.join(paths.ldapdir,
316 "mmr_syncrepl.conf")
317 paths.olcdir = os.path.join(paths.ldapdir,
318 "slapd.d")
319 paths.olcseedldif = os.path.join(paths.ldapdir,
320 "olc_seed.ldif")
321 paths.hklm = "hklm.ldb"
322 paths.hkcr = "hkcr.ldb"
323 paths.hkcu = "hkcu.ldb"
324 paths.hku = "hku.ldb"
325 paths.hkpd = "hkpd.ldb"
326 paths.hkpt = "hkpt.ldb"
328 paths.sysvol = lp.get("path", "sysvol")
330 paths.netlogon = lp.get("path", "netlogon")
332 paths.smbconf = lp.configfile
334 return paths
337 def guess_names(lp=None, hostname=None, domain=None, dnsdomain=None, serverrole=None,
338 rootdn=None, domaindn=None, configdn=None, schemadn=None, serverdn=None,
339 sitename=None):
340 """Guess configuration settings to use."""
342 if hostname is None:
343 hostname = socket.gethostname().split(".")[0].lower()
345 netbiosname = hostname.upper()
346 if not valid_netbios_name(netbiosname):
347 raise InvalidNetbiosName(netbiosname)
349 hostname = hostname.lower()
351 if dnsdomain is None:
352 dnsdomain = lp.get("realm")
354 if serverrole is None:
355 serverrole = lp.get("server role")
357 assert dnsdomain is not None
358 realm = dnsdomain.upper()
360 if lp.get("realm").upper() != realm:
361 raise Exception("realm '%s' in %s must match chosen realm '%s'" %
362 (lp.get("realm"), lp.configfile, realm))
364 dnsdomain = dnsdomain.lower()
366 if serverrole == "domain controller":
367 if domain is None:
368 domain = lp.get("workgroup")
369 if domaindn is None:
370 domaindn = "DC=" + dnsdomain.replace(".", ",DC=")
371 if lp.get("workgroup").upper() != domain.upper():
372 raise Exception("workgroup '%s' in smb.conf must match chosen domain '%s'",
373 lp.get("workgroup"), domain)
374 else:
375 domain = netbiosname
376 if domaindn is None:
377 domaindn = "CN=" + netbiosname
379 assert domain is not None
380 domain = domain.upper()
381 if not valid_netbios_name(domain):
382 raise InvalidNetbiosName(domain)
384 if rootdn is None:
385 rootdn = domaindn
387 if configdn is None:
388 configdn = "CN=Configuration," + rootdn
389 if schemadn is None:
390 schemadn = "CN=Schema," + configdn
392 if sitename is None:
393 sitename=DEFAULTSITE
395 names = ProvisionNames()
396 names.rootdn = rootdn
397 names.domaindn = domaindn
398 names.configdn = configdn
399 names.schemadn = schemadn
400 names.ldapmanagerdn = "CN=Manager," + rootdn
401 names.dnsdomain = dnsdomain
402 names.domain = domain
403 names.realm = realm
404 names.netbiosname = netbiosname
405 names.hostname = hostname
406 names.sitename = sitename
407 names.serverdn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (netbiosname, sitename, configdn)
409 return names
412 def make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
413 targetdir):
414 """Create a new smb.conf file based on a couple of basic settings.
416 assert smbconf is not None
417 if hostname is None:
418 hostname = socket.gethostname().split(".")[0].lower()
420 if serverrole is None:
421 serverrole = "standalone"
423 assert serverrole in ("domain controller", "member server", "standalone")
424 if serverrole == "domain controller":
425 smbconfsuffix = "dc"
426 elif serverrole == "member server":
427 smbconfsuffix = "member"
428 elif serverrole == "standalone":
429 smbconfsuffix = "standalone"
431 assert domain is not None
432 assert realm is not None
434 default_lp = param.LoadParm()
435 #Load non-existant file
436 if os.path.exists(smbconf):
437 default_lp.load(smbconf)
439 if targetdir is not None:
440 privatedir_line = "private dir = " + os.path.abspath(os.path.join(targetdir, "private"))
441 lockdir_line = "lock dir = " + os.path.abspath(targetdir)
443 default_lp.set("lock dir", os.path.abspath(targetdir))
444 else:
445 privatedir_line = ""
446 lockdir_line = ""
448 sysvol = os.path.join(default_lp.get("lock dir"), "sysvol")
449 netlogon = os.path.join(sysvol, realm.lower(), "scripts")
451 setup_file(setup_path("provision.smb.conf.%s" % smbconfsuffix),
452 smbconf, {
453 "HOSTNAME": hostname,
454 "DOMAIN": domain,
455 "REALM": realm,
456 "SERVERROLE": serverrole,
457 "NETLOGONPATH": netlogon,
458 "SYSVOLPATH": sysvol,
459 "PRIVATEDIR_LINE": privatedir_line,
460 "LOCKDIR_LINE": lockdir_line
464 def setup_name_mappings(samdb, idmap, sid, domaindn, root_uid, nobody_uid,
465 users_gid, wheel_gid):
466 """setup reasonable name mappings for sam names to unix names.
468 :param samdb: SamDB object.
469 :param idmap: IDmap db object.
470 :param sid: The domain sid.
471 :param domaindn: The domain DN.
472 :param root_uid: uid of the UNIX root user.
473 :param nobody_uid: uid of the UNIX nobody user.
474 :param users_gid: gid of the UNIX users group.
475 :param wheel_gid: gid of the UNIX wheel group."""
477 def add_foreign(self, domaindn, sid, desc):
478 """Add a foreign security principle."""
479 add = """
480 dn: CN=%s,CN=ForeignSecurityPrincipals,%s
481 objectClass: top
482 objectClass: foreignSecurityPrincipal
483 description: %s
484 """ % (sid, domaindn, desc)
485 # deliberately ignore errors from this, as the records may
486 # already exist
487 for msg in self.parse_ldif(add):
488 self.add(msg[1])
490 # add some foreign sids
491 add_foreign(samdb, domaindn, "S-1-5-7", "Anonymous")
492 add_foreign(samdb, domaindn, "S-1-1-0", "World")
493 add_foreign(samdb, domaindn, "S-1-5-2", "Network")
494 add_foreign(samdb, domaindn, "S-1-5-18", "System")
495 add_foreign(samdb, domaindn, "S-1-5-11", "Authenticated Users")
498 idmap.setup_name_mapping("S-1-5-7", idmap.TYPE_UID, nobody_uid)
499 idmap.setup_name_mapping("S-1-5-32-544", idmap.TYPE_GID, wheel_gid)
501 idmap.setup_name_mapping(sid + "-500", idmap.TYPE_UID, root_uid)
502 idmap.setup_name_mapping(sid + "-513", idmap.TYPE_GID, users_gid)
505 def setup_samdb_partitions(samdb_path, setup_path, message, lp, session_info,
506 credentials, names,
507 serverrole, ldap_backend=None,
508 erase=False):
509 """Setup the partitions for the SAM database.
511 Alternatively, provision() may call this, and then populate the database.
513 :note: This will wipe the Sam Database!
515 :note: This function always removes the local SAM LDB file. The erase
516 parameter controls whether to erase the existing data, which
517 may not be stored locally but in LDAP.
519 assert session_info is not None
521 # We use options=["modules:"] to stop the modules loading - we
522 # just want to wipe and re-initialise the database, not start it up
524 try:
525 samdb = Ldb(url=samdb_path, session_info=session_info,
526 credentials=credentials, lp=lp, options=["modules:"])
527 # Wipes the database
528 samdb.erase()
529 except LdbError:
530 os.unlink(samdb_path)
531 samdb = Ldb(url=samdb_path, session_info=session_info,
532 credentials=credentials, lp=lp, options=["modules:"])
533 # Wipes the database
534 samdb.erase()
537 #Add modules to the list to activate them by default
538 #beware often order is important
540 # Some Known ordering constraints:
541 # - rootdse must be first, as it makes redirects from "" -> cn=rootdse
542 # - objectclass must be before password_hash, because password_hash checks
543 # that the objectclass is of type person (filled in by objectclass
544 # module when expanding the objectclass list)
545 # - partition must be last
546 # - each partition has its own module list then
547 modules_list = ["rootdse",
548 "paged_results",
549 "ranged_results",
550 "anr",
551 "server_sort",
552 "asq",
553 "extended_dn_store",
554 "extended_dn_in",
555 "rdn_name",
556 "objectclass",
557 "samldb",
558 "kludge_acl",
559 "password_hash",
560 "operational"]
561 tdb_modules_list = [
562 "subtree_rename",
563 "subtree_delete",
564 "linked_attributes",
565 "extended_dn_out_ldb"]
566 modules_list2 = ["show_deleted",
567 "partition"]
569 domaindn_ldb = "users.ldb"
570 configdn_ldb = "configuration.ldb"
571 schemadn_ldb = "schema.ldb"
572 if ldap_backend is not None:
573 domaindn_ldb = ldap_backend.ldapi_uri
574 configdn_ldb = ldap_backend.ldapi_uri
575 schemadn_ldb = ldap_backend.ldapi_uri
577 if ldap_backend.ldap_backend_type == "fedora-ds":
578 backend_modules = ["nsuniqueid", "paged_searches"]
579 # We can handle linked attributes here, as we don't have directory-side subtree operations
580 tdb_modules_list = ["linked_attributes", "extended_dn_out_dereference"]
581 elif ldap_backend.ldap_backend_type == "openldap":
582 backend_modules = ["entryuuid", "paged_searches"]
583 # OpenLDAP handles subtree renames, so we don't want to do any of these things
584 tdb_modules_list = ["extended_dn_out_dereference"]
586 elif serverrole == "domain controller":
587 backend_modules = ["repl_meta_data"]
588 else:
589 backend_modules = ["objectguid"]
591 if tdb_modules_list is None:
592 tdb_modules_list_as_string = ""
593 else:
594 tdb_modules_list_as_string = ","+",".join(tdb_modules_list)
596 samdb.transaction_start()
597 try:
598 message("Setting up sam.ldb partitions and settings")
599 setup_add_ldif(samdb, setup_path("provision_partitions.ldif"), {
600 "SCHEMADN": names.schemadn,
601 "SCHEMADN_LDB": schemadn_ldb,
602 "SCHEMADN_MOD2": ",objectguid",
603 "CONFIGDN": names.configdn,
604 "CONFIGDN_LDB": configdn_ldb,
605 "DOMAINDN": names.domaindn,
606 "DOMAINDN_LDB": domaindn_ldb,
607 "SCHEMADN_MOD": "schema_fsmo,instancetype",
608 "CONFIGDN_MOD": "naming_fsmo,instancetype",
609 "DOMAINDN_MOD": "pdc_fsmo,instancetype",
610 "MODULES_LIST": ",".join(modules_list),
611 "TDB_MODULES_LIST": tdb_modules_list_as_string,
612 "MODULES_LIST2": ",".join(modules_list2),
613 "BACKEND_MOD": ",".join(backend_modules),
616 samdb.load_ldif_file_add(setup_path("provision_init.ldif"))
618 message("Setting up sam.ldb rootDSE")
619 setup_samdb_rootdse(samdb, setup_path, names)
621 except:
622 samdb.transaction_cancel()
623 raise
625 samdb.transaction_commit()
629 def secretsdb_become_dc(secretsdb, setup_path, domain, realm, dnsdomain,
630 netbiosname, domainsid, keytab_path, samdb_url,
631 dns_keytab_path, dnspass, machinepass):
632 """Add DC-specific bits to a secrets database.
634 :param secretsdb: Ldb Handle to the secrets database
635 :param setup_path: Setup path function
636 :param machinepass: Machine password
638 setup_ldb(secretsdb, setup_path("secrets_dc.ldif"), {
639 "MACHINEPASS_B64": b64encode(machinepass),
640 "DOMAIN": domain,
641 "REALM": realm,
642 "DNSDOMAIN": dnsdomain,
643 "DOMAINSID": str(domainsid),
644 "SECRETS_KEYTAB": keytab_path,
645 "NETBIOSNAME": netbiosname,
646 "SAM_LDB": samdb_url,
647 "DNS_KEYTAB": dns_keytab_path,
648 "DNSPASS_B64": b64encode(dnspass),
652 def setup_secretsdb(path, setup_path, session_info, credentials, lp):
653 """Setup the secrets database.
655 :param path: Path to the secrets database.
656 :param setup_path: Get the path to a setup file.
657 :param session_info: Session info.
658 :param credentials: Credentials
659 :param lp: Loadparm context
660 :return: LDB handle for the created secrets database
662 if os.path.exists(path):
663 os.unlink(path)
664 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
665 lp=lp)
666 secrets_ldb.erase()
667 secrets_ldb.load_ldif_file_add(setup_path("secrets_init.ldif"))
668 secrets_ldb = Ldb(path, session_info=session_info, credentials=credentials,
669 lp=lp)
670 secrets_ldb.load_ldif_file_add(setup_path("secrets.ldif"))
672 if credentials is not None and credentials.authentication_requested():
673 if credentials.get_bind_dn() is not None:
674 setup_add_ldif(secrets_ldb, setup_path("secrets_simple_ldap.ldif"), {
675 "LDAPMANAGERDN": credentials.get_bind_dn(),
676 "LDAPMANAGERPASS_B64": b64encode(credentials.get_password())
678 else:
679 setup_add_ldif(secrets_ldb, setup_path("secrets_sasl_ldap.ldif"), {
680 "LDAPADMINUSER": credentials.get_username(),
681 "LDAPADMINREALM": credentials.get_realm(),
682 "LDAPADMINPASS_B64": b64encode(credentials.get_password())
685 return secrets_ldb
688 def setup_templatesdb(path, setup_path, session_info, lp):
689 """Setup the templates database.
691 :param path: Path to the database.
692 :param setup_path: Function for obtaining the path to setup files.
693 :param session_info: Session info
694 :param credentials: Credentials
695 :param lp: Loadparm context
697 templates_ldb = Ldb(url=path, session_info=session_info,
698 lp=lp)
699 # Wipes the database
700 try:
701 templates_ldb.erase()
702 # This should be 'except LdbError', but on a re-provision the assert in ldb.erase fires, and we need to catch that too
703 except:
704 os.unlink(path)
706 templates_ldb.load_ldif_file_add(setup_path("provision_templates_init.ldif"))
708 templates_ldb = Ldb(url=path, session_info=session_info,
709 lp=lp)
711 templates_ldb.load_ldif_file_add(setup_path("provision_templates.ldif"))
714 def setup_registry(path, setup_path, session_info, lp):
715 """Setup the registry.
717 :param path: Path to the registry database
718 :param setup_path: Function that returns the path to a setup.
719 :param session_info: Session information
720 :param credentials: Credentials
721 :param lp: Loadparm context
723 reg = registry.Registry()
724 hive = registry.open_ldb(path, session_info=session_info,
725 lp_ctx=lp)
726 reg.mount_hive(hive, registry.HKEY_LOCAL_MACHINE)
727 provision_reg = setup_path("provision.reg")
728 assert os.path.exists(provision_reg)
729 reg.diff_apply(provision_reg)
732 def setup_idmapdb(path, setup_path, session_info, lp):
733 """Setup the idmap database.
735 :param path: path to the idmap database
736 :param setup_path: Function that returns a path to a setup file
737 :param session_info: Session information
738 :param credentials: Credentials
739 :param lp: Loadparm context
741 if os.path.exists(path):
742 os.unlink(path)
744 idmap_ldb = IDmapDB(path, session_info=session_info,
745 lp=lp)
747 idmap_ldb.erase()
748 idmap_ldb.load_ldif_file_add(setup_path("idmap_init.ldif"))
749 return idmap_ldb
752 def setup_samdb_rootdse(samdb, setup_path, names):
753 """Setup the SamDB rootdse.
755 :param samdb: Sam Database handle
756 :param setup_path: Obtain setup path
758 setup_add_ldif(samdb, setup_path("provision_rootdse_add.ldif"), {
759 "SCHEMADN": names.schemadn,
760 "NETBIOSNAME": names.netbiosname,
761 "DNSDOMAIN": names.dnsdomain,
762 "REALM": names.realm,
763 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
764 "DOMAINDN": names.domaindn,
765 "ROOTDN": names.rootdn,
766 "CONFIGDN": names.configdn,
767 "SERVERDN": names.serverdn,
771 def setup_self_join(samdb, names,
772 machinepass, dnspass,
773 domainsid, invocationid, setup_path,
774 policyguid, domainControllerFunctionality):
775 """Join a host to its own domain."""
776 assert isinstance(invocationid, str)
777 setup_add_ldif(samdb, setup_path("provision_self_join.ldif"), {
778 "CONFIGDN": names.configdn,
779 "SCHEMADN": names.schemadn,
780 "DOMAINDN": names.domaindn,
781 "SERVERDN": names.serverdn,
782 "INVOCATIONID": invocationid,
783 "NETBIOSNAME": names.netbiosname,
784 "DEFAULTSITE": names.sitename,
785 "DNSNAME": "%s.%s" % (names.hostname, names.dnsdomain),
786 "MACHINEPASS_B64": b64encode(machinepass),
787 "DNSPASS_B64": b64encode(dnspass),
788 "REALM": names.realm,
789 "DOMAIN": names.domain,
790 "DNSDOMAIN": names.dnsdomain,
791 "SAMBA_VERSION_STRING": version,
792 "DOMAIN_CONTROLLER_FUNCTIONALITY": str(domainControllerFunctionality)})
793 setup_add_ldif(samdb, setup_path("provision_group_policy.ldif"), {
794 "POLICYGUID": policyguid,
795 "DNSDOMAIN": names.dnsdomain,
796 "DOMAINSID": str(domainsid),
797 "DOMAINDN": names.domaindn})
800 def setup_samdb(path, setup_path, session_info, credentials, lp,
801 names, message,
802 domainsid, domainguid, policyguid,
803 fill, adminpass, krbtgtpass,
804 machinepass, invocationid, dnspass,
805 serverrole, schema=None, ldap_backend=None):
806 """Setup a complete SAM Database.
808 :note: This will wipe the main SAM database file!
811 domainFunctionality = DS_BEHAVIOR_WIN2008
812 forestFunctionality = DS_BEHAVIOR_WIN2008
813 domainControllerFunctionality = DS_BEHAVIOR_WIN2008
815 # Also wipes the database
816 setup_samdb_partitions(path, setup_path, message=message, lp=lp,
817 credentials=credentials, session_info=session_info,
818 names=names,
819 ldap_backend=ldap_backend, serverrole=serverrole)
821 # Load the database, but importantly, use Ldb not SamDB as we don't want to load the global schema
822 samdb = Ldb(session_info=session_info,
823 credentials=credentials, lp=lp)
825 message("Pre-loading the Samba 4 and AD schema")
827 # Load the schema from the one we computed earlier
828 samdb.set_schema_from_ldb(schema.ldb)
830 # And now we can connect to the DB - the schema won't be loaded from the DB
831 samdb.connect(path)
832 if fill == FILL_DRS:
833 return samdb
835 samdb.transaction_start()
836 try:
837 message("Erasing data from partitions")
838 # Load the schema (again). This time it will force a reindex, to make the below computationally sane
839 samdb.set_schema_from_ldb(schema.ldb)
840 samdb.erase_partitions()
842 samdb.set_opaque_integer("domainFunctionality", domainFunctionality)
843 samdb.set_opaque_integer("forestFunctionality", forestFunctionality)
844 samdb.set_opaque_integer("domainControllerFunctionality", domainControllerFunctionality)
846 samdb.set_domain_sid(str(domainsid))
847 if serverrole == "domain controller":
848 samdb.set_invocation_id(invocationid)
850 message("Adding DomainDN: %s" % names.domaindn)
851 if serverrole == "domain controller":
852 domain_oc = "domainDNS"
853 else:
854 domain_oc = "samba4LocalDomain"
856 setup_add_ldif(samdb, setup_path("provision_basedn.ldif"), {
857 "DOMAINDN": names.domaindn,
858 "DOMAIN_OC": domain_oc
861 message("Modifying DomainDN: " + names.domaindn + "")
862 if domainguid is not None:
863 domainguid_mod = "replace: objectGUID\nobjectGUID: %s\n-" % domainguid
864 else:
865 domainguid_mod = ""
867 setup_modify_ldif(samdb, setup_path("provision_basedn_modify.ldif"), {
868 "LDAPTIME": timestring(int(time.time())),
869 "DOMAINSID": str(domainsid),
870 "SCHEMADN": names.schemadn,
871 "NETBIOSNAME": names.netbiosname,
872 "DEFAULTSITE": names.sitename,
873 "CONFIGDN": names.configdn,
874 "SERVERDN": names.serverdn,
875 "POLICYGUID": policyguid,
876 "DOMAINDN": names.domaindn,
877 "DOMAINGUID_MOD": domainguid_mod,
878 "DOMAIN_FUNCTIONALITY": str(domainFunctionality)
881 message("Adding configuration container")
882 setup_add_ldif(samdb, setup_path("provision_configuration_basedn.ldif"), {
883 "CONFIGDN": names.configdn,
885 message("Modifying configuration container")
886 setup_modify_ldif(samdb, setup_path("provision_configuration_basedn_modify.ldif"), {
887 "CONFIGDN": names.configdn,
888 "SCHEMADN": names.schemadn,
891 message("Setting up sam.ldb schema")
892 samdb.add_ldif(schema.schema_dn_add)
893 samdb.modify_ldif(schema.schema_dn_modify)
894 samdb.add_ldif(schema.schema_data)
895 setup_add_ldif(samdb, setup_path("aggregate_schema.ldif"),
896 {"SCHEMADN": names.schemadn})
898 message("Setting up sam.ldb configuration data")
899 setup_add_ldif(samdb, setup_path("provision_configuration.ldif"), {
900 "CONFIGDN": names.configdn,
901 "NETBIOSNAME": names.netbiosname,
902 "DEFAULTSITE": names.sitename,
903 "DNSDOMAIN": names.dnsdomain,
904 "DOMAIN": names.domain,
905 "SCHEMADN": names.schemadn,
906 "DOMAINDN": names.domaindn,
907 "SERVERDN": names.serverdn,
908 "FOREST_FUNCTIONALALITY": str(forestFunctionality)
911 message("Setting up display specifiers")
912 setup_add_ldif(samdb, setup_path("display_specifiers.ldif"),
913 {"CONFIGDN": names.configdn})
915 message("Adding users container")
916 setup_add_ldif(samdb, setup_path("provision_users_add.ldif"), {
917 "DOMAINDN": names.domaindn})
918 message("Modifying users container")
919 setup_modify_ldif(samdb, setup_path("provision_users_modify.ldif"), {
920 "DOMAINDN": names.domaindn})
921 message("Adding computers container")
922 setup_add_ldif(samdb, setup_path("provision_computers_add.ldif"), {
923 "DOMAINDN": names.domaindn})
924 message("Modifying computers container")
925 setup_modify_ldif(samdb, setup_path("provision_computers_modify.ldif"), {
926 "DOMAINDN": names.domaindn})
927 message("Setting up sam.ldb data")
928 setup_add_ldif(samdb, setup_path("provision.ldif"), {
929 "DOMAINDN": names.domaindn,
930 "NETBIOSNAME": names.netbiosname,
931 "DEFAULTSITE": names.sitename,
932 "CONFIGDN": names.configdn,
933 "SERVERDN": names.serverdn
936 if fill == FILL_FULL:
937 message("Setting up sam.ldb users and groups")
938 setup_add_ldif(samdb, setup_path("provision_users.ldif"), {
939 "DOMAINDN": names.domaindn,
940 "DOMAINSID": str(domainsid),
941 "CONFIGDN": names.configdn,
942 "ADMINPASS_B64": b64encode(adminpass),
943 "KRBTGTPASS_B64": b64encode(krbtgtpass),
946 if serverrole == "domain controller":
947 message("Setting up self join")
948 setup_self_join(samdb, names=names, invocationid=invocationid,
949 dnspass=dnspass,
950 machinepass=machinepass,
951 domainsid=domainsid, policyguid=policyguid,
952 setup_path=setup_path, domainControllerFunctionality=domainControllerFunctionality)
954 except:
955 samdb.transaction_cancel()
956 raise
958 samdb.transaction_commit()
959 return samdb
962 FILL_FULL = "FULL"
963 FILL_NT4SYNC = "NT4SYNC"
964 FILL_DRS = "DRS"
967 def provision(setup_dir, message, session_info,
968 credentials, smbconf=None, targetdir=None, samdb_fill=FILL_FULL, realm=None,
969 rootdn=None, domaindn=None, schemadn=None, configdn=None,
970 serverdn=None,
971 domain=None, hostname=None, hostip=None, hostip6=None,
972 domainsid=None, adminpass=None, ldapadminpass=None,
973 krbtgtpass=None, domainguid=None,
974 policyguid=None, invocationid=None, machinepass=None,
975 dnspass=None, root=None, nobody=None, users=None,
976 wheel=None, backup=None, aci=None, serverrole=None,
977 ldap_backend_extra_port=None, ldap_backend_type=None, sitename=None,
978 ol_mmr_urls=None, ol_olc=None,
979 setup_ds_path=None, slapd_path=None, nosync=False,
980 ldap_dryrun_mode=False):
981 """Provision samba4
983 :note: caution, this wipes all existing data!
986 def setup_path(file):
987 return os.path.join(setup_dir, file)
989 if domainsid is None:
990 domainsid = security.random_sid()
992 if policyguid is None:
993 policyguid = str(uuid.uuid4())
994 if adminpass is None:
995 adminpass = glue.generate_random_str(12)
996 if krbtgtpass is None:
997 krbtgtpass = glue.generate_random_str(12)
998 if machinepass is None:
999 machinepass = glue.generate_random_str(12)
1000 if dnspass is None:
1001 dnspass = glue.generate_random_str(12)
1002 if ldapadminpass is None:
1003 #Make a new, random password between Samba and it's LDAP server
1004 ldapadminpass=glue.generate_random_str(12)
1007 root_uid = findnss_uid([root or "root"])
1008 nobody_uid = findnss_uid([nobody or "nobody"])
1009 users_gid = findnss_gid([users or "users"])
1010 if wheel is None:
1011 wheel_gid = findnss_gid(["wheel", "adm"])
1012 else:
1013 wheel_gid = findnss_gid([wheel])
1015 if targetdir is not None:
1016 if (not os.path.exists(os.path.join(targetdir, "etc"))):
1017 os.makedirs(os.path.join(targetdir, "etc"))
1018 smbconf = os.path.join(targetdir, "etc", "smb.conf")
1019 elif smbconf is None:
1020 smbconf = param.default_path()
1022 # only install a new smb.conf if there isn't one there already
1023 if not os.path.exists(smbconf):
1024 make_smbconf(smbconf, setup_path, hostname, domain, realm, serverrole,
1025 targetdir)
1027 lp = param.LoadParm()
1028 lp.load(smbconf)
1030 names = guess_names(lp=lp, hostname=hostname, domain=domain,
1031 dnsdomain=realm, serverrole=serverrole, sitename=sitename,
1032 rootdn=rootdn, domaindn=domaindn, configdn=configdn, schemadn=schemadn,
1033 serverdn=serverdn)
1035 paths = provision_paths_from_lp(lp, names.dnsdomain)
1037 if hostip is None:
1038 try:
1039 hostip = socket.getaddrinfo(names.hostname, None, socket.AF_INET, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1040 except socket.gaierror, (socket.EAI_NODATA, msg):
1041 hostip = None
1043 if hostip6 is None:
1044 try:
1045 hostip6 = socket.getaddrinfo(names.hostname, None, socket.AF_INET6, socket.AI_CANONNAME, socket.IPPROTO_IP)[0][-1][0]
1046 except socket.gaierror, (socket.EAI_NODATA, msg):
1047 hostip6 = None
1049 if serverrole is None:
1050 serverrole = lp.get("server role")
1052 assert serverrole in ("domain controller", "member server", "standalone")
1053 if invocationid is None and serverrole == "domain controller":
1054 invocationid = str(uuid.uuid4())
1056 if not os.path.exists(paths.private_dir):
1057 os.mkdir(paths.private_dir)
1059 ldapi_url = "ldapi://%s" % urllib.quote(paths.s4_ldapi_path, safe="")
1061 schema = Schema(setup_path, schemadn=names.schemadn, serverdn=names.serverdn)
1063 provision_backend = None
1064 if ldap_backend_type:
1065 # We only support an LDAP backend over ldapi://
1067 provision_backend = ProvisionBackend(paths=paths, setup_path=setup_path, lp=lp, credentials=credentials,
1068 names=names,
1069 message=message, hostname=hostname,
1070 root=root, schema=schema, ldap_backend_type=ldap_backend_type,
1071 ldapadminpass=ldapadminpass,
1072 ldap_backend_extra_port=ldap_backend_extra_port,
1073 ol_mmr_urls=ol_mmr_urls,
1074 slapd_path=slapd_path,
1075 setup_ds_path=setup_ds_path,
1076 ldap_dryrun_mode=ldap_dryrun_mode)
1078 # Now use the backend credentials to access the databases
1079 credentials = provision_backend.credentials
1081 # only install a new shares config db if there is none
1082 if not os.path.exists(paths.shareconf):
1083 message("Setting up share.ldb")
1084 share_ldb = Ldb(paths.shareconf, session_info=session_info,
1085 credentials=credentials, lp=lp)
1086 share_ldb.load_ldif_file_add(setup_path("share.ldif"))
1089 message("Setting up secrets.ldb")
1090 secrets_ldb = setup_secretsdb(paths.secrets, setup_path,
1091 session_info=session_info,
1092 credentials=credentials, lp=lp)
1094 message("Setting up the registry")
1095 setup_registry(paths.hklm, setup_path, session_info,
1096 lp=lp)
1098 message("Setting up templates db")
1099 setup_templatesdb(paths.templates, setup_path, session_info=session_info,
1100 lp=lp)
1102 message("Setting up idmap db")
1103 idmap = setup_idmapdb(paths.idmapdb, setup_path, session_info=session_info,
1104 lp=lp)
1106 message("Setting up SAM db")
1107 samdb = setup_samdb(paths.samdb, setup_path, session_info=session_info,
1108 credentials=credentials, lp=lp, names=names,
1109 message=message,
1110 domainsid=domainsid,
1111 schema=schema, domainguid=domainguid, policyguid=policyguid,
1112 fill=samdb_fill,
1113 adminpass=adminpass, krbtgtpass=krbtgtpass,
1114 invocationid=invocationid,
1115 machinepass=machinepass, dnspass=dnspass,
1116 serverrole=serverrole, ldap_backend=provision_backend)
1118 if serverrole == "domain controller":
1119 if paths.netlogon is None:
1120 message("Existing smb.conf does not have a [netlogon] share, but you are configuring a DC.")
1121 message("Please either remove %s or see the template at %s" %
1122 ( paths.smbconf, setup_path("provision.smb.conf.dc")))
1123 assert(paths.netlogon is not None)
1125 if paths.sysvol is None:
1126 message("Existing smb.conf does not have a [sysvol] share, but you are configuring a DC.")
1127 message("Please either remove %s or see the template at %s" %
1128 (paths.smbconf, setup_path("provision.smb.conf.dc")))
1129 assert(paths.sysvol is not None)
1131 policy_path = os.path.join(paths.sysvol, names.dnsdomain, "Policies",
1132 "{" + policyguid + "}")
1133 os.makedirs(policy_path, 0755)
1134 open(os.path.join(policy_path, "GPT.INI"), 'w').write("")
1135 os.makedirs(os.path.join(policy_path, "Machine"), 0755)
1136 os.makedirs(os.path.join(policy_path, "User"), 0755)
1137 if not os.path.isdir(paths.netlogon):
1138 os.makedirs(paths.netlogon, 0755)
1140 if samdb_fill == FILL_FULL:
1141 setup_name_mappings(samdb, idmap, str(domainsid), names.domaindn,
1142 root_uid=root_uid, nobody_uid=nobody_uid,
1143 users_gid=users_gid, wheel_gid=wheel_gid)
1145 message("Setting up sam.ldb rootDSE marking as synchronized")
1146 setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"))
1148 # Only make a zone file on the first DC, it should be replicated with DNS replication
1149 if serverrole == "domain controller":
1150 secrets_ldb = Ldb(paths.secrets, session_info=session_info,
1151 credentials=credentials, lp=lp)
1152 secretsdb_become_dc(secrets_ldb, setup_path, domain=domain, realm=names.realm,
1153 netbiosname=names.netbiosname, domainsid=domainsid,
1154 keytab_path=paths.keytab, samdb_url=paths.samdb,
1155 dns_keytab_path=paths.dns_keytab, dnspass=dnspass,
1156 machinepass=machinepass, dnsdomain=names.dnsdomain)
1158 domainguid = samdb.searchone(basedn=domaindn, attribute="objectGUID")
1159 assert isinstance(domainguid, str)
1160 hostguid = samdb.searchone(basedn=domaindn, attribute="objectGUID",
1161 expression="(&(objectClass=computer)(cn=%s))" % names.hostname,
1162 scope=SCOPE_SUBTREE)
1163 assert isinstance(hostguid, str)
1165 create_zone_file(paths.dns, setup_path, dnsdomain=names.dnsdomain,
1166 domaindn=names.domaindn, hostip=hostip,
1167 hostip6=hostip6, hostname=names.hostname,
1168 dnspass=dnspass, realm=names.realm,
1169 domainguid=domainguid, hostguid=hostguid)
1171 create_named_conf(paths.namedconf, setup_path, realm=names.realm,
1172 dnsdomain=names.dnsdomain, private_dir=paths.private_dir)
1174 create_named_txt(paths.namedtxt, setup_path, realm=names.realm,
1175 dnsdomain=names.dnsdomain, private_dir=paths.private_dir,
1176 keytab_name=paths.dns_keytab)
1177 message("See %s for an example configuration include file for BIND" % paths.namedconf)
1178 message("and %s for further documentation required for secure DNS updates" % paths.namedtxt)
1180 create_krb5_conf(paths.krb5conf, setup_path, dnsdomain=names.dnsdomain,
1181 hostname=names.hostname, realm=names.realm)
1182 message("A Kerberos configuration suitable for Samba 4 has been generated at %s" % paths.krb5conf)
1185 # if backend is openldap, terminate slapd after final provision and check its proper termination
1186 if provision_backend is not None and provision_backend.slapd is not None:
1187 if provision_backend.slapd.poll() is None:
1188 #Kill the slapd
1189 if hasattr(provision_backend.slapd, "terminate"):
1190 provision_backend.slapd.terminate()
1191 else:
1192 import signal
1193 os.kill(provision_backend.slapd.pid, signal.SIGTERM)
1195 #and now wait for it to die
1196 provision_backend.slapd.communicate()
1198 # now display slapd_command_file.txt to show how slapd must be started next time
1199 message("Use later the following commandline to start slapd, then Samba:")
1200 slapd_command = "\'" + "\' \'".join(provision_backend.slapd_command) + "\'"
1201 message(slapd_command)
1202 message("This slapd-Commandline is also stored under: " + paths.ldapdir + "/ldap_backend_startup.sh")
1204 setup_file(setup_path("ldap_backend_startup.sh"), paths.ldapdir + "/ldap_backend_startup.sh", {
1205 "SLAPD_COMMAND" : slapd_command})
1207 create_phpldapadmin_config(paths.phpldapadminconfig, setup_path,
1208 ldapi_url)
1210 message("Please install the phpLDAPadmin configuration located at %s into /etc/phpldapadmin/config.php" % paths.phpldapadminconfig)
1212 message("Once the above files are installed, your Samba4 server will be ready to use")
1213 message("Server Role: %s" % serverrole)
1214 message("Hostname: %s" % names.hostname)
1215 message("NetBIOS Domain: %s" % names.domain)
1216 message("DNS Domain: %s" % names.dnsdomain)
1217 message("DOMAIN SID: %s" % str(domainsid))
1218 if samdb_fill == FILL_FULL:
1219 message("Admin password: %s" % adminpass)
1221 result = ProvisionResult()
1222 result.domaindn = domaindn
1223 result.paths = paths
1224 result.lp = lp
1225 result.samdb = samdb
1226 return result
1230 def provision_become_dc(setup_dir=None,
1231 smbconf=None, targetdir=None, realm=None,
1232 rootdn=None, domaindn=None, schemadn=None, configdn=None,
1233 serverdn=None,
1234 domain=None, hostname=None, domainsid=None,
1235 adminpass=None, krbtgtpass=None, domainguid=None,
1236 policyguid=None, invocationid=None, machinepass=None,
1237 dnspass=None, root=None, nobody=None, users=None,
1238 wheel=None, backup=None, serverrole=None,
1239 ldap_backend=None, ldap_backend_type=None, sitename=None):
1241 def message(text):
1242 """print a message if quiet is not set."""
1243 print text
1245 return provision(setup_dir, message, system_session(), None,
1246 smbconf=smbconf, targetdir=targetdir, samdb_fill=FILL_DRS, realm=realm,
1247 rootdn=rootdn, domaindn=domaindn, schemadn=schemadn, configdn=configdn, serverdn=serverdn,
1248 domain=domain, hostname=hostname, hostip="127.0.0.1", domainsid=domainsid, machinepass=machinepass, serverrole="domain controller", sitename=sitename)
1251 def setup_db_config(setup_path, dbdir):
1252 """Setup a Berkeley database.
1254 :param setup_path: Setup path function.
1255 :param dbdir: Database directory."""
1256 if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
1257 os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
1258 if not os.path.isdir(os.path.join(dbdir, "tmp")):
1259 os.makedirs(os.path.join(dbdir, "tmp"), 0700)
1261 setup_file(setup_path("DB_CONFIG"), os.path.join(dbdir, "DB_CONFIG"),
1262 {"LDAPDBDIR": dbdir})
1264 class ProvisionBackend(object):
1265 def __init__(self, paths=None, setup_path=None, lp=None, credentials=None,
1266 names=None, message=None,
1267 hostname=None, root=None,
1268 schema=None, ldapadminpass=None,
1269 ldap_backend_type=None, ldap_backend_extra_port=None,
1270 ol_mmr_urls=None,
1271 setup_ds_path=None, slapd_path=None,
1272 nosync=False, ldap_dryrun_mode=False):
1273 """Provision an LDAP backend for samba4
1275 This works for OpenLDAP and Fedora DS
1278 self.ldapi_uri = "ldapi://" + urllib.quote(os.path.join(paths.ldapdir, "ldapi"), safe="")
1280 if not os.path.isdir(paths.ldapdir):
1281 os.makedirs(paths.ldapdir, 0700)
1283 if ldap_backend_type == "existing":
1284 #Check to see that this 'existing' LDAP backend in fact exists
1285 ldapi_db = Ldb(self.ldapi_uri, credentials=credentials)
1286 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1287 expression="(objectClass=OpenLDAProotDSE)")
1289 # If we have got here, then we must have a valid connection to the LDAP server, with valid credentials supplied
1290 # This caused them to be set into the long-term database later in the script.
1291 self.credentials = credentials
1292 self.ldap_backend_type = "openldap" #For now, assume existing backends at least emulate OpenLDAP
1293 return
1295 # we will shortly start slapd with ldapi for final provisioning. first check with ldapsearch -> rootDSE via self.ldapi_uri
1296 # if another instance of slapd is already running
1297 try:
1298 ldapi_db = Ldb(self.ldapi_uri)
1299 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1300 expression="(objectClass=OpenLDAProotDSE)");
1301 try:
1302 f = open(paths.slapdpid, "r")
1303 p = f.read()
1304 f.close()
1305 message("Check for slapd Process with PID: " + str(p) + " and terminate it manually.")
1306 except:
1307 pass
1309 raise("Warning: Another slapd Instance seems already running on this host, listening to " + ldapi_uri + ". Please shut it down before you continue. ")
1311 except LdbError, e:
1312 pass
1314 # Try to print helpful messages when the user has not specified the path to slapd
1315 if slapd_path is None:
1316 raise("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
1317 if not os.path.exists(slapd_path):
1318 message (slapd_path)
1319 raise("Warning: Given Path to slapd does not exist!")
1321 schemadb_path = os.path.join(paths.ldapdir, "schema-tmp.ldb")
1322 try:
1323 os.unlink(schemadb_path)
1324 except OSError:
1325 pass
1328 # Put the LDIF of the schema into a database so we can search on
1329 # it to generate schema-dependent configurations in Fedora DS and
1330 # OpenLDAP
1331 os.path.join(paths.ldapdir, "schema-tmp.ldb")
1332 schema.ldb.connect(schemadb_path)
1333 schema.ldb.transaction_start()
1335 # These bits of LDIF are supplied when the Schema object is created
1336 schema.ldb.add_ldif(schema.schema_dn_add)
1337 schema.ldb.modify_ldif(schema.schema_dn_modify)
1338 schema.ldb.add_ldif(schema.schema_data)
1339 schema.ldb.transaction_commit()
1341 self.credentials = Credentials()
1342 self.credentials.guess(lp)
1343 self.ldap_backend_type = ldap_backend_type
1345 if ldap_backend_type == "fedora-ds":
1346 provision_fds_backend(self, paths=paths, setup_path=setup_path, names=names, message=message,
1347 hostname=hostname, ldapadminpass=ldapadminpass, root=root,
1348 schema=schema, ldap_backend_extra_port=ldap_backend_extra_port,
1349 setup_ds_path=setup_ds_path, slapd_path=slapd_path,
1350 nosync=nosync, ldap_dryrun_mode=ldap_dryrun_mode)
1352 elif ldap_backend_type == "openldap":
1353 provision_openldap_backend(self, paths=paths, setup_path=setup_path, names=names, message=message,
1354 hostname=hostname, ldapadminpass=ldapadminpass, root=root,
1355 schema=schema, ldap_backend_extra_port=ldap_backend_extra_port,
1356 ol_mmr_urls=ol_mmr_urls,
1357 slapd_path=slapd_path,
1358 nosync=nosync, ldap_dryrun_mode=ldap_dryrun_mode)
1359 else:
1360 raise("Unknown LDAP backend type selected")
1362 self.credentials.set_password(ldapadminpass)
1364 # Now start the slapd, so we can provision onto it. We keep the
1365 # subprocess context around, to kill this off at the successful
1366 # end of the script
1367 self.slapd = subprocess.Popen(self.slapd_provision_command, close_fds=True, shell=False)
1369 while self.slapd.poll() is None:
1370 # Wait until the socket appears
1371 try:
1372 ldapi_db = Ldb(self.ldapi_uri, lp=lp, credentials=self.credentials)
1373 search_ol_rootdse = ldapi_db.search(base="", scope=SCOPE_BASE,
1374 expression="(objectClass=OpenLDAProotDSE)")
1375 # If we have got here, then we must have a valid connection to the LDAP server!
1376 return
1377 except LdbError, e:
1378 time.sleep(1)
1379 pass
1381 raise "slapd died before we could make a connection to it"
1384 def provision_openldap_backend(result, paths=None, setup_path=None, names=None, message=None,
1385 hostname=None, ldapadminpass=None, root=None,
1386 schema=None,
1387 ldap_backend_extra_port=None,
1388 ol_mmr_urls=None,
1389 slapd_path=None, nosync=False,
1390 ldap_dryrun_mode=False):
1392 #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
1393 nosync_config = ""
1394 if nosync:
1395 nosync_config = "dbnosync"
1398 attrs = ["linkID", "lDAPDisplayName"]
1399 res = schema.ldb.search(expression="(&(linkID=*)(!(linkID:1.2.840.113556.1.4.803:=1))(objectclass=attributeSchema)(attributeSyntax=2.5.5.1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1401 memberof_config = "# Generated from Samba4 schema\n"
1402 refint_attributes = ""
1403 for i in range (0, len(res)):
1404 expression = "(&(objectclass=attributeSchema)(linkID=%d)(attributeSyntax=2.5.5.1))" % (int(res[i]["linkID"][0])+1)
1405 target = schema.ldb.searchone(basedn=names.schemadn,
1406 expression=expression,
1407 attribute="lDAPDisplayName",
1408 scope=SCOPE_SUBTREE)
1409 if target is not None:
1410 refint_attributes = refint_attributes + " " + res[i]["lDAPDisplayName"][0]
1412 memberof_config += read_and_sub_file(setup_path("memberof.conf"),
1413 { "MEMBER_ATTR" : str(res[i]["lDAPDisplayName"][0]),
1414 "MEMBEROF_ATTR" : str(target) })
1416 refint_config = read_and_sub_file(setup_path("refint.conf"),
1417 { "LINK_ATTRS" : refint_attributes})
1419 res = schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
1420 index_config = ""
1421 for i in range (0, len(res)):
1422 index_attr = res[i]["lDAPDisplayName"][0]
1423 if index_attr == "objectGUID":
1424 index_attr = "entryUUID"
1426 index_config += "index " + index_attr + " eq\n"
1428 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
1429 mmr_on_config = ""
1430 mmr_replicator_acl = ""
1431 mmr_serverids_config = ""
1432 mmr_syncrepl_schema_config = ""
1433 mmr_syncrepl_config_config = ""
1434 mmr_syncrepl_user_config = ""
1437 if ol_mmr_urls is not None:
1438 # For now, make these equal
1439 mmr_pass = ldapadminpass
1441 url_list=filter(None,ol_mmr_urls.split(' '))
1442 if (len(url_list) == 1):
1443 url_list=filter(None,ol_mmr_urls.split(','))
1446 mmr_on_config = "MirrorMode On"
1447 mmr_replicator_acl = " by dn=cn=replicator,cn=samba read"
1448 serverid=0
1449 for url in url_list:
1450 serverid=serverid+1
1451 mmr_serverids_config += read_and_sub_file(setup_path("mmr_serverids.conf"),
1452 { "SERVERID" : str(serverid),
1453 "LDAPSERVER" : url })
1454 rid=serverid*10
1455 rid=rid+1
1456 mmr_syncrepl_schema_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1457 { "RID" : str(rid),
1458 "MMRDN": names.schemadn,
1459 "LDAPSERVER" : url,
1460 "MMR_PASSWORD": mmr_pass})
1462 rid=rid+1
1463 mmr_syncrepl_config_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1464 { "RID" : str(rid),
1465 "MMRDN": names.configdn,
1466 "LDAPSERVER" : url,
1467 "MMR_PASSWORD": mmr_pass})
1469 rid=rid+1
1470 mmr_syncrepl_user_config += read_and_sub_file(setup_path("mmr_syncrepl.conf"),
1471 { "RID" : str(rid),
1472 "MMRDN": names.domaindn,
1473 "LDAPSERVER" : url,
1474 "MMR_PASSWORD": mmr_pass })
1475 # OpenLDAP cn=config initialisation
1476 olc_syncrepl_config = ""
1477 olc_mmr_config = ""
1478 # if mmr = yes, generate cn=config-replication directives
1479 # and olc_seed.lif for the other mmr-servers
1480 if ol_mmr_urls is not None:
1481 serverid=0
1482 olc_serverids_config = ""
1483 olc_syncrepl_seed_config = ""
1484 olc_mmr_config += read_and_sub_file(setup_path("olc_mmr.conf"),{})
1485 rid=1000
1486 for url in url_list:
1487 serverid=serverid+1
1488 olc_serverids_config += read_and_sub_file(setup_path("olc_serverid.conf"),
1489 { "SERVERID" : str(serverid),
1490 "LDAPSERVER" : url })
1492 rid=rid+1
1493 olc_syncrepl_config += read_and_sub_file(setup_path("olc_syncrepl.conf"),
1494 { "RID" : str(rid),
1495 "LDAPSERVER" : url,
1496 "MMR_PASSWORD": mmr_pass})
1498 olc_syncrepl_seed_config += read_and_sub_file(setup_path("olc_syncrepl_seed.conf"),
1499 { "RID" : str(rid),
1500 "LDAPSERVER" : url})
1502 setup_file(setup_path("olc_seed.ldif"), paths.olcseedldif,
1503 {"OLC_SERVER_ID_CONF": olc_serverids_config,
1504 "OLC_PW": ldapadminpass,
1505 "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
1506 # end olc
1508 setup_file(setup_path("slapd.conf"), paths.slapdconf,
1509 {"DNSDOMAIN": names.dnsdomain,
1510 "LDAPDIR": paths.ldapdir,
1511 "DOMAINDN": names.domaindn,
1512 "CONFIGDN": names.configdn,
1513 "SCHEMADN": names.schemadn,
1514 "MEMBEROF_CONFIG": memberof_config,
1515 "MIRRORMODE": mmr_on_config,
1516 "REPLICATOR_ACL": mmr_replicator_acl,
1517 "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
1518 "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
1519 "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
1520 "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
1521 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
1522 "OLC_MMR_CONFIG": olc_mmr_config,
1523 "REFINT_CONFIG": refint_config,
1524 "INDEX_CONFIG": index_config,
1525 "NOSYNC": nosync_config})
1527 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "user"))
1528 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "config"))
1529 setup_db_config(setup_path, os.path.join(paths.ldapdir, "db", "schema"))
1531 if not os.path.exists(os.path.join(paths.ldapdir, "db", "samba", "cn=samba")):
1532 os.makedirs(os.path.join(paths.ldapdir, "db", "samba", "cn=samba"), 0700)
1534 setup_file(setup_path("cn=samba.ldif"),
1535 os.path.join(paths.ldapdir, "db", "samba", "cn=samba.ldif"),
1536 { "UUID": str(uuid.uuid4()),
1537 "LDAPTIME": timestring(int(time.time()))} )
1538 setup_file(setup_path("cn=samba-admin.ldif"),
1539 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=samba-admin.ldif"),
1540 {"LDAPADMINPASS_B64": b64encode(ldapadminpass),
1541 "UUID": str(uuid.uuid4()),
1542 "LDAPTIME": timestring(int(time.time()))} )
1544 if ol_mmr_urls is not None:
1545 setup_file(setup_path("cn=replicator.ldif"),
1546 os.path.join(paths.ldapdir, "db", "samba", "cn=samba", "cn=replicator.ldif"),
1547 {"MMR_PASSWORD_B64": b64encode(mmr_pass),
1548 "UUID": str(uuid.uuid4()),
1549 "LDAPTIME": timestring(int(time.time()))} )
1552 mapping = "schema-map-openldap-2.3"
1553 backend_schema = "backend-schema.schema"
1555 backend_schema_data = schema.ldb.convert_schema_to_openldap("openldap", open(setup_path(mapping), 'r').read())
1556 assert backend_schema_data is not None
1557 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1559 # now we generate the needed strings to start slapd automatically,
1560 # first ldapi_uri...
1561 if ldap_backend_extra_port is not None:
1562 # When we use MMR, we can't use 0.0.0.0 as it uses the name
1563 # specified there as part of it's clue as to it's own name,
1564 # and not to replicate to itself
1565 if ol_mmr_urls is None:
1566 server_port_string = "ldap://0.0.0.0:%d" % ldap_backend_extra_port
1567 else:
1568 server_port_string = "ldap://" + names.hostname + "." + names.dnsdomain +":%d" % ldap_backend_extra_port
1569 else:
1570 server_port_string = ""
1572 # Prepare the 'result' information - the commands to return in particular
1573 result.slapd_provision_command = [slapd_path]
1575 result.slapd_provision_command.append("-F" + paths.olcdir)
1577 result.slapd_provision_command.append("-h")
1579 # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands
1580 result.slapd_command = list(result.slapd_provision_command)
1582 result.slapd_provision_command.append(result.ldapi_uri)
1583 result.slapd_provision_command.append("-d0")
1585 uris = result.ldapi_uri
1586 if server_port_string is not "":
1587 uris = uris + " " + server_port_string
1589 result.slapd_command.append(uris)
1591 # Set the username - done here because Fedora DS still uses the admin DN and simple bind
1592 result.credentials.set_username("samba-admin")
1594 # If we were just looking for crashes up to this point, it's a
1595 # good time to exit before we realise we don't have OpenLDAP on
1596 # this system
1597 if ldap_dryrun_mode:
1598 sys.exit(0)
1600 # Finally, convert the configuration into cn=config style!
1601 if not os.path.isdir(paths.olcdir):
1602 os.makedirs(paths.olcdir, 0770)
1604 retcode = subprocess.call([slapd_path, "-Ttest", "-f", paths.slapdconf, "-F", paths.olcdir], close_fds=True, shell=False)
1606 # We can't do this, as OpenLDAP is strange. It gives an error
1607 # output to the above, but does the conversion sucessfully...
1609 # if retcode != 0:
1610 # raise("conversion from slapd.conf to cn=config failed")
1612 if not os.path.exists(os.path.join(paths.olcdir, "cn=config.ldif")):
1613 raise("conversion from slapd.conf to cn=config failed")
1615 # Don't confuse the admin by leaving the slapd.conf around
1616 os.remove(paths.slapdconf)
1619 def provision_fds_backend(result, paths=None, setup_path=None, names=None, message=None,
1620 hostname=None, ldapadminpass=None, root=None,
1621 schema=None,
1622 ldap_backend_extra_port=None,
1623 setup_ds_path=None,
1624 slapd_path=None,
1625 nosync=False,
1626 ldap_dryrun_mode=False):
1628 if ldap_backend_extra_port is not None:
1629 serverport = "ServerPort=%d" % ldap_backend_extra_port
1630 else:
1631 serverport = ""
1633 setup_file(setup_path("fedorads.inf"), paths.fedoradsinf,
1634 {"ROOT": root,
1635 "HOSTNAME": hostname,
1636 "DNSDOMAIN": names.dnsdomain,
1637 "LDAPDIR": paths.ldapdir,
1638 "DOMAINDN": names.domaindn,
1639 "LDAPMANAGERDN": names.ldapmanagerdn,
1640 "LDAPMANAGERPASS": ldapadminpass,
1641 "SERVERPORT": serverport})
1643 setup_file(setup_path("fedorads-partitions.ldif"), paths.fedoradspartitions,
1644 {"CONFIGDN": names.configdn,
1645 "SCHEMADN": names.schemadn,
1648 mapping = "schema-map-fedora-ds-1.0"
1649 backend_schema = "99_ad.ldif"
1651 # Build a schema file in Fedora DS format
1652 backend_schema_data = schema.ldb.convert_schema_to_openldap("fedora-ds", open(setup_path(mapping), 'r').read())
1653 assert backend_schema_data is not None
1654 open(os.path.join(paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
1656 result.credentials.set_bind_dn(names.ldapmanagerdn)
1658 # Destory the target directory, or else setup-ds.pl will complain
1659 fedora_ds_dir = os.path.join(paths.ldapdir, "slapd-samba4")
1660 shutil.rmtree(fedora_ds_dir, True)
1662 result.slapd_provision_command = [slapd_path, "-D", fedora_ds_dir, "-i", paths.slapdpid];
1663 #In the 'provision' command line, stay in the foreground so we can easily kill it
1664 result.slapd_provision_command.append("-d0")
1666 #the command for the final run is the normal script
1667 result.slapd_command = [os.path.join(paths.ldapdir, "slapd-samba4", "start-slapd")]
1669 # If we were just looking for crashes up to this point, it's a
1670 # good time to exit before we realise we don't have Fedora DS on
1671 if ldap_dryrun_mode:
1672 sys.exit(0)
1674 # Try to print helpful messages when the user has not specified the path to the setup-ds tool
1675 if setup_ds_path is None:
1676 raise("Warning: Fedora DS LDAP-Backend must be setup with path to setup-ds, e.g. --setup-ds-path=\"/usr/sbin/setup-ds.pl\"!")
1677 if not os.path.exists(setup_ds_path):
1678 message (setup_ds_path)
1679 raise("Warning: Given Path to slapd does not exist!")
1681 # Run the Fedora DS setup utility
1682 retcode = subprocess.call([setup_ds_path, "--silent", "--file", paths.fedoradsinf], close_fds=True, shell=False)
1683 if retcode != 0:
1684 raise("setup-ds failed")
1686 def create_phpldapadmin_config(path, setup_path, ldapi_uri):
1687 """Create a PHP LDAP admin configuration file.
1689 :param path: Path to write the configuration to.
1690 :param setup_path: Function to generate setup paths.
1692 setup_file(setup_path("phpldapadmin-config.php"), path,
1693 {"S4_LDAPI_URI": ldapi_uri})
1696 def create_zone_file(path, setup_path, dnsdomain, domaindn,
1697 hostip, hostip6, hostname, dnspass, realm, domainguid, hostguid):
1698 """Write out a DNS zone file, from the info in the current database.
1700 :param path: Path of the new zone file.
1701 :param setup_path: Setup path function.
1702 :param dnsdomain: DNS Domain name
1703 :param domaindn: DN of the Domain
1704 :param hostip: Local IPv4 IP
1705 :param hostip6: Local IPv6 IP
1706 :param hostname: Local hostname
1707 :param dnspass: Password for DNS
1708 :param realm: Realm name
1709 :param domainguid: GUID of the domain.
1710 :param hostguid: GUID of the host.
1712 assert isinstance(domainguid, str)
1714 if hostip6 is not None:
1715 hostip6_base_line = " IN AAAA " + hostip6
1716 hostip6_host_line = hostname + " IN AAAA " + hostip6
1717 else:
1718 hostip6_base_line = ""
1719 hostip6_host_line = ""
1721 if hostip is not None:
1722 hostip_base_line = " IN A " + hostip
1723 hostip_host_line = hostname + " IN A " + hostip
1724 else:
1725 hostip_base_line = ""
1726 hostip_host_line = ""
1728 setup_file(setup_path("provision.zone"), path, {
1729 "DNSPASS_B64": b64encode(dnspass),
1730 "HOSTNAME": hostname,
1731 "DNSDOMAIN": dnsdomain,
1732 "REALM": realm,
1733 "HOSTIP_BASE_LINE": hostip_base_line,
1734 "HOSTIP_HOST_LINE": hostip_host_line,
1735 "DOMAINGUID": domainguid,
1736 "DATESTRING": time.strftime("%Y%m%d%H"),
1737 "DEFAULTSITE": DEFAULTSITE,
1738 "HOSTGUID": hostguid,
1739 "HOSTIP6_BASE_LINE": hostip6_base_line,
1740 "HOSTIP6_HOST_LINE": hostip6_host_line,
1744 def create_named_conf(path, setup_path, realm, dnsdomain,
1745 private_dir):
1746 """Write out a file containing zone statements suitable for inclusion in a
1747 named.conf file (including GSS-TSIG configuration).
1749 :param path: Path of the new named.conf file.
1750 :param setup_path: Setup path function.
1751 :param realm: Realm name
1752 :param dnsdomain: DNS Domain name
1753 :param private_dir: Path to private directory
1754 :param keytab_name: File name of DNS keytab file
1757 setup_file(setup_path("named.conf"), path, {
1758 "DNSDOMAIN": dnsdomain,
1759 "REALM": realm,
1760 "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
1761 "PRIVATE_DIR": private_dir
1764 def create_named_txt(path, setup_path, realm, dnsdomain,
1765 private_dir, keytab_name):
1766 """Write out a file containing zone statements suitable for inclusion in a
1767 named.conf file (including GSS-TSIG configuration).
1769 :param path: Path of the new named.conf file.
1770 :param setup_path: Setup path function.
1771 :param realm: Realm name
1772 :param dnsdomain: DNS Domain name
1773 :param private_dir: Path to private directory
1774 :param keytab_name: File name of DNS keytab file
1777 setup_file(setup_path("named.txt"), path, {
1778 "DNSDOMAIN": dnsdomain,
1779 "REALM": realm,
1780 "DNS_KEYTAB": keytab_name,
1781 "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
1782 "PRIVATE_DIR": private_dir
1785 def create_krb5_conf(path, setup_path, dnsdomain, hostname, realm):
1786 """Write out a file containing zone statements suitable for inclusion in a
1787 named.conf file (including GSS-TSIG configuration).
1789 :param path: Path of the new named.conf file.
1790 :param setup_path: Setup path function.
1791 :param dnsdomain: DNS Domain name
1792 :param hostname: Local hostname
1793 :param realm: Realm name
1796 setup_file(setup_path("krb5.conf"), path, {
1797 "DNSDOMAIN": dnsdomain,
1798 "HOSTNAME": hostname,
1799 "REALM": realm,