gpfs: Introduce wrapper for gpfs_quotactl
[Samba.git] / python / samba / provision / backend.py
blob17572efc76bfa349305021f6e05a03f61ab4c514
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 (LDB and LDAP backends)."""
28 from base64 import b64encode
29 import errno
30 import ldb
31 import os
32 import sys
33 import uuid
34 import time
35 import shutil
36 import subprocess
37 import urllib
39 from ldb import SCOPE_BASE, SCOPE_ONELEVEL, LdbError, timestring
41 from samba import Ldb, read_and_sub_file, setup_file
42 from samba.credentials import Credentials, DONT_USE_KERBEROS
43 from samba.schema import Schema
46 class SlapdAlreadyRunning(Exception):
48 def __init__(self, uri):
49 self.ldapi_uri = uri
50 super(SlapdAlreadyRunning, self).__init__("Another slapd Instance "
51 "seems already running on this host, listening to %s." %
52 self.ldapi_uri)
55 class BackendResult(object):
57 def report_logger(self, logger):
58 """Rerport this result to a particular logger.
60 """
61 raise NotImplementedError(self.report_logger)
64 class LDAPBackendResult(BackendResult):
66 def __init__(self, slapd_command_escaped, ldapdir):
67 self.slapd_command_escaped = slapd_command_escaped
68 self.ldapdir = ldapdir
70 def report_logger(self, logger):
71 if self.slapd_command_escaped is not None:
72 # now display slapd_command_file.txt to show how slapd must be
73 # started next time
74 logger.info(
75 "Use later the following commandline to start slapd, then Samba:")
76 logger.info(self.slapd_command_escaped)
77 logger.info(
78 "This slapd-Commandline is also stored under: %s/ldap_backend_startup.sh",
79 self.ldapdir)
82 class ProvisionBackend(object):
84 def __init__(self, backend_type, paths=None, lp=None,
85 names=None, logger=None):
86 """Provision a backend for samba4"""
87 self.paths = paths
88 self.lp = lp
89 self.credentials = None
90 self.names = names
91 self.logger = logger
93 self.type = backend_type
95 # Set a default - the code for "existing" below replaces this
96 self.ldap_backend_type = backend_type
98 def init(self):
99 """Initialize the backend."""
100 raise NotImplementedError(self.init)
102 def start(self):
103 """Start the backend."""
104 raise NotImplementedError(self.start)
106 def shutdown(self):
107 """Shutdown the backend."""
108 raise NotImplementedError(self.shutdown)
110 def post_setup(self):
111 """Post setup.
113 :return: A BackendResult or None
115 raise NotImplementedError(self.post_setup)
118 class LDBBackend(ProvisionBackend):
120 def init(self):
121 self.credentials = None
123 # Wipe the old sam.ldb databases away
124 shutil.rmtree(self.paths.samdb + ".d", True)
126 def start(self):
127 pass
129 def shutdown(self):
130 pass
132 def post_setup(self):
133 pass
136 class ExistingBackend(ProvisionBackend):
138 def __init__(self, backend_type, paths=None, lp=None,
139 names=None, logger=None, ldapi_uri=None):
141 super(ExistingBackend, self).__init__(backend_type=backend_type,
142 paths=paths, lp=lp,
143 names=names, logger=logger,
144 ldap_backend_forced_uri=ldapi_uri)
146 def init(self):
147 # Check to see that this 'existing' LDAP backend in fact exists
148 ldapi_db = Ldb(self.ldapi_uri)
149 ldapi_db.search(base="", scope=SCOPE_BASE,
150 expression="(objectClass=OpenLDAProotDSE)")
152 # For now, assume existing backends at least emulate OpenLDAP
153 self.ldap_backend_type = "openldap"
156 class LDAPBackend(ProvisionBackend):
158 def __init__(self, backend_type, paths=None, lp=None,
159 names=None, logger=None, domainsid=None,
160 schema=None, hostname=None, ldapadminpass=None,
161 slapd_path=None, ldap_backend_extra_port=None,
162 ldap_backend_forced_uri=None, ldap_dryrun_mode=False):
164 super(LDAPBackend, self).__init__(backend_type=backend_type,
165 paths=paths, lp=lp,
166 names=names, logger=logger)
168 self.domainsid = domainsid
169 self.schema = schema
170 self.hostname = hostname
172 self.ldapdir = os.path.join(paths.private_dir, "ldap")
173 self.ldapadminpass = ldapadminpass
175 self.slapd_path = slapd_path
176 self.slapd_command = None
177 self.slapd_command_escaped = None
178 self.slapd_pid = os.path.join(self.ldapdir, "slapd.pid")
180 self.ldap_backend_extra_port = ldap_backend_extra_port
181 self.ldap_dryrun_mode = ldap_dryrun_mode
183 if ldap_backend_forced_uri is not None:
184 self.ldap_uri = ldap_backend_forced_uri
185 else:
186 self.ldap_uri = "ldapi://%s" % urllib.quote(
187 os.path.join(self.ldapdir, "ldapi"), safe="")
189 if not os.path.exists(self.ldapdir):
190 os.mkdir(self.ldapdir)
192 def init(self):
193 from samba.provision import ProvisioningError
194 # we will shortly start slapd with ldapi for final provisioning. first
195 # check with ldapsearch -> rootDSE via self.ldap_uri if another
196 # instance of slapd is already running
197 try:
198 ldapi_db = Ldb(self.ldap_uri)
199 ldapi_db.search(base="", scope=SCOPE_BASE,
200 expression="(objectClass=OpenLDAProotDSE)")
201 try:
202 f = open(self.slapd_pid, "r")
203 except IOError, err:
204 if err != errno.ENOENT:
205 raise
206 else:
207 try:
208 p = f.read()
209 finally:
210 f.close()
211 self.logger.info("Check for slapd process with PID: %s and terminate it manually." % p)
212 raise SlapdAlreadyRunning(self.ldap_uri)
213 except LdbError:
214 # XXX: We should never be catching all Ldb errors
215 pass
217 # Try to print helpful messages when the user has not specified the
218 # path to slapd
219 if self.slapd_path is None:
220 raise ProvisioningError("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
221 if not os.path.exists(self.slapd_path):
222 self.logger.warning("Path (%s) to slapd does not exist!",
223 self.slapd_path)
225 if not os.path.isdir(self.ldapdir):
226 os.makedirs(self.ldapdir, 0700)
228 # Put the LDIF of the schema into a database so we can search on
229 # it to generate schema-dependent configurations in Fedora DS and
230 # OpenLDAP
231 schemadb_path = os.path.join(self.ldapdir, "schema-tmp.ldb")
232 try:
233 os.unlink(schemadb_path)
234 except OSError:
235 pass
237 self.schema.write_to_tmp_ldb(schemadb_path)
239 self.credentials = Credentials()
240 self.credentials.guess(self.lp)
241 # Kerberos to an ldapi:// backend makes no sense (we also force EXTERNAL)
242 self.credentials.set_kerberos_state(DONT_USE_KERBEROS)
243 self.credentials.set_username("samba-admin")
244 self.credentials.set_password(self.ldapadminpass)
245 self.credentials.set_forced_sasl_mech("EXTERNAL")
247 self.provision()
249 def provision(self):
250 pass
252 def start(self):
253 from samba.provision import ProvisioningError
254 self.slapd_command_escaped = "\'" + "\' \'".join(self.slapd_command) + "\'"
255 ldap_backend_script = os.path.join(self.ldapdir, "ldap_backend_startup.sh")
256 f = open(ldap_backend_script, 'w')
257 try:
258 f.write("#!/bin/sh\n" + self.slapd_command_escaped + " $@\n")
259 finally:
260 f.close()
262 os.chmod(ldap_backend_script, 0755)
264 # Now start the slapd, so we can provision onto it. We keep the
265 # subprocess context around, to kill this off at the successful
266 # end of the script
267 self.slapd = subprocess.Popen(self.slapd_provision_command,
268 close_fds=True, shell=False)
270 count = 0
271 while self.slapd.poll() is None:
272 # Wait until the socket appears
273 try:
274 time.sleep(1)
275 ldapi_db = Ldb(self.ldap_uri, lp=self.lp, credentials=self.credentials)
276 ldapi_db.search(base="", scope=SCOPE_BASE,
277 expression="(objectClass=OpenLDAProotDSE)")
278 # If we have got here, then we must have a valid connection to
279 # the LDAP server!
280 return
281 except LdbError:
282 count = count + 1
284 if count > 15:
285 self.logger.error("Could not connect to slapd started with: %s" % "\'" + "\' \'".join(self.slapd_provision_command) + "\'")
286 raise ProvisioningError("slapd never accepted a connection within 15 seconds of starting")
288 self.logger.error("Could not start slapd with: %s" % "\'" + "\' \'".join(self.slapd_provision_command) + "\'")
289 raise ProvisioningError("slapd died before we could make a connection to it")
291 def shutdown(self):
292 # if an LDAP backend is in use, terminate slapd after final provision
293 # and check its proper termination
294 if self.slapd.poll() is None:
295 # Kill the slapd
296 if getattr(self.slapd, "terminate", None) is not None:
297 self.slapd.terminate()
298 else:
299 # Older python versions don't have .terminate()
300 import signal
301 os.kill(self.slapd.pid, signal.SIGTERM)
303 # and now wait for it to die
304 self.slapd.communicate()
306 def post_setup(self):
307 return LDAPBackendResult(self.slapd_command_escaped,
308 self.ldapdir)
311 class OpenLDAPBackend(LDAPBackend):
313 def __init__(self, backend_type, paths=None, lp=None,
314 credentials=None, names=None, logger=None, domainsid=None,
315 schema=None, hostname=None, ldapadminpass=None, slapd_path=None,
316 ldap_backend_extra_port=None, ldap_dryrun_mode=False,
317 ol_mmr_urls=None, nosync=False, ldap_backend_forced_uri=None):
318 from samba.provision import setup_path
319 super(OpenLDAPBackend, self).__init__( backend_type=backend_type,
320 paths=paths, lp=lp,
321 names=names, logger=logger,
322 domainsid=domainsid, schema=schema, hostname=hostname,
323 ldapadminpass=ldapadminpass, slapd_path=slapd_path,
324 ldap_backend_extra_port=ldap_backend_extra_port,
325 ldap_backend_forced_uri=ldap_backend_forced_uri,
326 ldap_dryrun_mode=ldap_dryrun_mode)
328 self.ol_mmr_urls = ol_mmr_urls
329 self.nosync = nosync
331 self.slapdconf = os.path.join(self.ldapdir, "slapd.conf")
332 self.modulesconf = os.path.join(self.ldapdir, "modules.conf")
333 self.memberofconf = os.path.join(self.ldapdir, "memberof.conf")
334 self.olmmrserveridsconf = os.path.join(self.ldapdir, "mmr_serverids.conf")
335 self.olmmrsyncreplconf = os.path.join(self.ldapdir, "mmr_syncrepl.conf")
336 self.olcdir = os.path.join(self.ldapdir, "slapd.d")
337 self.olcseedldif = os.path.join(self.ldapdir, "olc_seed.ldif")
339 self.schema = Schema(self.domainsid,
340 schemadn=self.names.schemadn, files=[
341 setup_path("schema_samba4.ldif")])
343 def setup_db_dir(self, dbdir):
344 """Create a database directory.
346 :param dbdir: Database directory.
348 if not os.path.exists(dbdir):
349 os.makedirs(dbdir, 0700)
351 def provision(self):
352 from samba.provision import ProvisioningError, setup_path
353 # Wipe the directories so we can start
354 shutil.rmtree(os.path.join(self.ldapdir, "db"), True)
356 # Allow the test scripts to turn off fsync() for OpenLDAP as for TDB
357 # and LDB
358 nosync_config = ""
359 if self.nosync:
360 nosync_config = "dbnosync"
362 lnkattr = self.schema.linked_attributes()
363 refint_attributes = ""
364 memberof_config = "# Generated from Samba4 schema\n"
365 for att in lnkattr.keys():
366 if lnkattr[att] is not None:
367 refint_attributes = refint_attributes + " " + att
369 memberof_config += read_and_sub_file(
370 setup_path("memberof.conf"), {
371 "MEMBER_ATTR": att,
372 "MEMBEROF_ATTR": lnkattr[att]})
374 refint_config = read_and_sub_file(
375 setup_path("refint.conf"), {"LINK_ATTRS": refint_attributes})
377 attrs = ["linkID", "lDAPDisplayName"]
378 res = self.schema.ldb.search(
379 expression="(&(objectclass=attributeSchema)"
380 "(searchFlags:1.2.840.113556.1.4.803:=1))",
381 base=self.names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
382 index_config = ""
383 for i in range(0, len(res)):
384 index_attr = res[i]["lDAPDisplayName"][0]
385 if index_attr == "objectGUID":
386 index_attr = "entryUUID"
388 index_config += "index " + index_attr + " eq\n"
390 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
391 mmr_on_config = ""
392 mmr_replicator_acl = ""
393 mmr_serverids_config = ""
394 mmr_syncrepl_schema_config = ""
395 mmr_syncrepl_config_config = ""
396 mmr_syncrepl_domaindns_config = ""
397 mmr_syncrepl_forestdns_config = ""
398 mmr_syncrepl_user_config = ""
399 mmr_pass = ""
401 if self.ol_mmr_urls is not None:
402 # For now, make these equal
403 mmr_pass = self.ldapadminpass
405 url_list = filter(None,self.ol_mmr_urls.split(','))
406 for url in url_list:
407 self.logger.info("Using LDAP-URL: "+url)
408 if len(url_list) == 1:
409 raise ProvisioningError("At least 2 LDAP-URLs needed for MMR!")
411 mmr_on_config = "MirrorMode On"
412 mmr_replicator_acl = " by dn=cn=replicator,cn=samba read"
413 serverid = 0
414 for url in url_list:
415 serverid = serverid + 1
416 mmr_serverids_config += read_and_sub_file(
417 setup_path("mmr_serverids.conf"), {
418 "SERVERID": str(serverid),
419 "LDAPSERVER": url })
420 rid = serverid * 10
421 rid = rid + 1
422 mmr_syncrepl_schema_config += read_and_sub_file(
423 setup_path("mmr_syncrepl.conf"), {
424 "RID" : str(rid),
425 "MMRDN": self.names.schemadn,
426 "LDAPSERVER" : url,
427 "MMR_PASSWORD": mmr_pass})
429 rid = rid + 1
430 mmr_syncrepl_config_config += read_and_sub_file(
431 setup_path("mmr_syncrepl.conf"), {
432 "RID" : str(rid),
433 "MMRDN": self.names.configdn,
434 "LDAPSERVER" : url,
435 "MMR_PASSWORD": mmr_pass})
437 rid = rid + 1
438 mmr_syncrepl_domaindns_config += read_and_sub_file(
439 setup_path("mmr_syncrepl.conf"), {
440 "RID" : str(rid),
441 "MMRDN": "dc=DomainDNSZones," + self.names.domaindn,
442 "LDAPSERVER" : url,
443 "MMR_PASSWORD": mmr_pass})
445 rid = rid + 1
446 mmr_syncrepl_forestdns_config += read_and_sub_file(
447 setup_path("mmr_syncrepl.conf"), {
448 "RID" : str(rid),
449 "MMRDN": "dc=ForestDNSZones," + self.names.domaindn,
450 "LDAPSERVER" : url,
451 "MMR_PASSWORD": mmr_pass})
453 rid = rid + 1
454 mmr_syncrepl_user_config += read_and_sub_file(
455 setup_path("mmr_syncrepl.conf"), {
456 "RID" : str(rid),
457 "MMRDN": self.names.domaindn,
458 "LDAPSERVER" : url,
459 "MMR_PASSWORD": mmr_pass })
460 # OpenLDAP cn=config initialisation
461 olc_syncrepl_config = ""
462 olc_mmr_config = ""
463 # if mmr = yes, generate cn=config-replication directives
464 # and olc_seed.lif for the other mmr-servers
465 if self.ol_mmr_urls is not None:
466 serverid = 0
467 olc_serverids_config = ""
468 olc_syncrepl_seed_config = ""
469 olc_mmr_config += read_and_sub_file(
470 setup_path("olc_mmr.conf"), {})
471 rid = 500
472 for url in url_list:
473 serverid = serverid + 1
474 olc_serverids_config += read_and_sub_file(
475 setup_path("olc_serverid.conf"), {
476 "SERVERID" : str(serverid), "LDAPSERVER" : url })
478 rid = rid + 1
479 olc_syncrepl_config += read_and_sub_file(
480 setup_path("olc_syncrepl.conf"), {
481 "RID" : str(rid), "LDAPSERVER" : url,
482 "MMR_PASSWORD": mmr_pass})
484 olc_syncrepl_seed_config += read_and_sub_file(
485 setup_path("olc_syncrepl_seed.conf"), {
486 "RID" : str(rid), "LDAPSERVER" : url})
488 setup_file(setup_path("olc_seed.ldif"), self.olcseedldif,
489 {"OLC_SERVER_ID_CONF": olc_serverids_config,
490 "OLC_PW": self.ldapadminpass,
491 "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
492 # end olc
494 setup_file(setup_path("slapd.conf"), self.slapdconf,
495 {"DNSDOMAIN": self.names.dnsdomain,
496 "LDAPDIR": self.ldapdir,
497 "DOMAINDN": self.names.domaindn,
498 "CONFIGDN": self.names.configdn,
499 "SCHEMADN": self.names.schemadn,
500 "MEMBEROF_CONFIG": memberof_config,
501 "MIRRORMODE": mmr_on_config,
502 "REPLICATOR_ACL": mmr_replicator_acl,
503 "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
504 "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
505 "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
506 "MMR_SYNCREPL_DOMAINDNS_CONFIG": mmr_syncrepl_domaindns_config,
507 "MMR_SYNCREPL_FORESTDNS_CONFIG": mmr_syncrepl_forestdns_config,
508 "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
509 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
510 "OLC_MMR_CONFIG": olc_mmr_config,
511 "REFINT_CONFIG": refint_config,
512 "INDEX_CONFIG": index_config,
513 "ADMIN_UID": str(os.getuid()),
514 "NOSYNC": nosync_config,})
516 self.setup_db_dir(os.path.join(self.ldapdir, "db", "forestdns"))
517 self.setup_db_dir(os.path.join(self.ldapdir, "db", "domaindns"))
518 self.setup_db_dir(os.path.join(self.ldapdir, "db", "user"))
519 self.setup_db_dir(os.path.join(self.ldapdir, "db", "config"))
520 self.setup_db_dir(os.path.join(self.ldapdir, "db", "schema"))
521 self.setup_db_dir(os.path.join(self.ldapdir, "db", "samba"))
523 if self.ol_mmr_urls is not None:
524 mmr = ""
525 else:
526 mmr = "#"
528 cn_samba = read_and_sub_file(
529 setup_path("cn=samba.ldif"),
530 { "LDAPADMINPASS": self.ldapadminpass,
531 "MMR_PASSWORD": mmr_pass,
532 "MMR": mmr })
534 mapping = "schema-map-openldap-2.3"
535 backend_schema = "backend-schema.schema"
537 f = open(setup_path(mapping), 'r')
538 try:
539 backend_schema_data = self.schema.convert_to_openldap(
540 "openldap", f.read())
541 finally:
542 f.close()
543 assert backend_schema_data is not None
544 f = open(os.path.join(self.ldapdir, backend_schema), 'w')
545 try:
546 f.write(backend_schema_data)
547 finally:
548 f.close()
550 # now we generate the needed strings to start slapd automatically,
551 if self.ldap_backend_extra_port is not None:
552 # When we use MMR, we can't use 0.0.0.0 as it uses the name
553 # specified there as part of it's clue as to it's own name,
554 # and not to replicate to itself
555 if self.ol_mmr_urls is None:
556 server_port_string = "ldap://0.0.0.0:%d" % self.ldap_backend_extra_port
557 else:
558 server_port_string = "ldap://%s.%s:%d" (self.names.hostname,
559 self.names.dnsdomain, self.ldap_backend_extra_port)
560 else:
561 server_port_string = ""
563 # Prepare the 'result' information - the commands to return in
564 # particular
565 self.slapd_provision_command = [self.slapd_path, "-F" + self.olcdir,
566 "-h"]
568 # copy this command so we have two version, one with -d0 and only
569 # ldapi (or the forced ldap_uri), and one with all the listen commands
570 self.slapd_command = list(self.slapd_provision_command)
572 self.slapd_provision_command.extend([self.ldap_uri, "-d0"])
573 uris = self.ldap_uri
574 if server_port_string is not "":
575 uris = uris + " " + server_port_string
577 self.slapd_command.append(uris)
579 # Wipe the old sam.ldb databases away
580 shutil.rmtree(self.olcdir, True)
581 os.makedirs(self.olcdir, 0770)
583 # If we were just looking for crashes up to this point, it's a
584 # good time to exit before we realise we don't have OpenLDAP on
585 # this system
586 if self.ldap_dryrun_mode:
587 sys.exit(0)
589 slapd_cmd = [self.slapd_path, "-Ttest", "-n", "0", "-f",
590 self.slapdconf, "-F", self.olcdir]
591 retcode = subprocess.call(slapd_cmd, close_fds=True, shell=False)
593 if retcode != 0:
594 self.logger.error("conversion from slapd.conf to cn=config failed slapd started with: %s" % "\'" + "\' \'".join(slapd_cmd) + "\'")
595 raise ProvisioningError("conversion from slapd.conf to cn=config failed")
597 if not os.path.exists(os.path.join(self.olcdir, "cn=config.ldif")):
598 raise ProvisioningError("conversion from slapd.conf to cn=config failed")
600 # Don't confuse the admin by leaving the slapd.conf around
601 os.remove(self.slapdconf)
603 cn_samba_cmd = [self.slapd_path, "-Tadd", "-b", "cn=samba", "-F", self.olcdir]
604 p = subprocess.Popen(cn_samba_cmd, stdin=subprocess.PIPE, shell=False)
605 p.stdin.write(cn_samba)
606 p.communicate()
609 class FDSBackend(LDAPBackend):
611 def __init__(self, backend_type, paths=None, lp=None,
612 names=None, logger=None, domainsid=None,
613 schema=None, hostname=None, ldapadminpass=None, slapd_path=None,
614 ldap_backend_extra_port=None, ldap_dryrun_mode=False, root=None,
615 setup_ds_path=None):
617 from samba.provision import setup_path
619 super(FDSBackend, self).__init__(backend_type=backend_type,
620 paths=paths, lp=lp,
621 names=names, logger=logger,
622 domainsid=domainsid, schema=schema, hostname=hostname,
623 ldapadminpass=ldapadminpass, slapd_path=slapd_path,
624 ldap_backend_extra_port=ldap_backend_extra_port,
625 ldap_backend_forced_uri=ldap_backend_forced_uri,
626 ldap_dryrun_mode=ldap_dryrun_mode)
628 self.root = root
629 self.setup_ds_path = setup_ds_path
630 self.ldap_instance = self.names.netbiosname.lower()
632 self.sambadn = "CN=Samba"
634 self.fedoradsinf = os.path.join(self.ldapdir, "fedorads.inf")
635 self.partitions_ldif = os.path.join(self.ldapdir,
636 "fedorads-partitions.ldif")
637 self.sasl_ldif = os.path.join(self.ldapdir, "fedorads-sasl.ldif")
638 self.dna_ldif = os.path.join(self.ldapdir, "fedorads-dna.ldif")
639 self.pam_ldif = os.path.join(self.ldapdir, "fedorads-pam.ldif")
640 self.refint_ldif = os.path.join(self.ldapdir, "fedorads-refint.ldif")
641 self.linked_attrs_ldif = os.path.join(self.ldapdir,
642 "fedorads-linked-attributes.ldif")
643 self.index_ldif = os.path.join(self.ldapdir, "fedorads-index.ldif")
644 self.samba_ldif = os.path.join(self.ldapdir, "fedorads-samba.ldif")
646 self.samba3_schema = setup_path(
647 "../../examples/LDAP/samba.schema")
648 self.samba3_ldif = os.path.join(self.ldapdir, "samba3.ldif")
650 self.retcode = subprocess.call(["bin/oLschema2ldif",
651 "-I", self.samba3_schema,
652 "-O", self.samba3_ldif,
653 "-b", self.names.domaindn],
654 close_fds=True, shell=False)
656 if self.retcode != 0:
657 raise Exception("Unable to convert Samba 3 schema.")
659 self.schema = Schema(
660 self.domainsid,
661 schemadn=self.names.schemadn,
662 files=[setup_path("schema_samba4.ldif"), self.samba3_ldif],
663 additional_prefixmap=["1000:1.3.6.1.4.1.7165.2.1",
664 "1001:1.3.6.1.4.1.7165.2.2"])
666 def provision(self):
667 from samba.provision import ProvisioningError, setup_path
668 if self.ldap_backend_extra_port is not None:
669 serverport = "ServerPort=%d" % self.ldap_backend_extra_port
670 else:
671 serverport = ""
673 setup_file(setup_path("fedorads.inf"), self.fedoradsinf,
674 {"ROOT": self.root,
675 "HOSTNAME": self.hostname,
676 "DNSDOMAIN": self.names.dnsdomain,
677 "LDAPDIR": self.ldapdir,
678 "DOMAINDN": self.names.domaindn,
679 "LDAP_INSTANCE": self.ldap_instance,
680 "LDAPMANAGERDN": self.names.ldapmanagerdn,
681 "LDAPMANAGERPASS": self.ldapadminpass,
682 "SERVERPORT": serverport})
684 setup_file(setup_path("fedorads-partitions.ldif"),
685 self.partitions_ldif,
686 {"CONFIGDN": self.names.configdn,
687 "SCHEMADN": self.names.schemadn,
688 "SAMBADN": self.sambadn,
691 setup_file(setup_path("fedorads-sasl.ldif"), self.sasl_ldif,
692 {"SAMBADN": self.sambadn,
695 setup_file(setup_path("fedorads-dna.ldif"), self.dna_ldif,
696 {"DOMAINDN": self.names.domaindn,
697 "SAMBADN": self.sambadn,
698 "DOMAINSID": str(self.domainsid),
701 setup_file(setup_path("fedorads-pam.ldif"), self.pam_ldif)
703 lnkattr = self.schema.linked_attributes()
705 f = open(setup_path("fedorads-refint-delete.ldif"), 'r')
706 try:
707 refint_config = f.read()
708 finally:
709 f.close()
710 memberof_config = ""
711 index_config = ""
712 argnum = 3
714 for attr in lnkattr.keys():
715 if lnkattr[attr] is not None:
716 refint_config += read_and_sub_file(
717 setup_path("fedorads-refint-add.ldif"),
718 { "ARG_NUMBER" : str(argnum),
719 "LINK_ATTR" : attr })
720 memberof_config += read_and_sub_file(
721 setup_path("fedorads-linked-attributes.ldif"),
722 { "MEMBER_ATTR" : attr,
723 "MEMBEROF_ATTR" : lnkattr[attr] })
724 index_config += read_and_sub_file(
725 setup_path("fedorads-index.ldif"), { "ATTR" : attr })
726 argnum += 1
728 f = open(self.refint_ldif, 'w')
729 try:
730 f.write(refint_config)
731 finally:
732 f.close()
733 f = open(self.linked_attrs_ldif, 'w')
734 try:
735 f.write(memberof_config)
736 finally:
737 f.close()
739 attrs = ["lDAPDisplayName"]
740 res = self.schema.ldb.search(
741 expression="(&(objectclass=attributeSchema)"
742 "(searchFlags:1.2.840.113556.1.4.803:=1))",
743 base=self.names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
745 for i in range(0, len(res)):
746 attr = res[i]["lDAPDisplayName"][0]
748 if attr == "objectGUID":
749 attr = "nsUniqueId"
751 index_config += read_and_sub_file(
752 setup_path("fedorads-index.ldif"), { "ATTR" : attr })
754 f = open(self.index_ldif, 'w')
755 try:
756 f.write(index_config)
757 finally:
758 f.close()
760 setup_file(setup_path("fedorads-samba.ldif"), self.samba_ldif, {
761 "SAMBADN": self.sambadn,
762 "LDAPADMINPASS": self.ldapadminpass
765 mapping = "schema-map-fedora-ds-1.0"
766 backend_schema = "99_ad.ldif"
768 # Build a schema file in Fedora DS format
769 f = open(setup_path(mapping), 'r')
770 try:
771 backend_schema_data = self.schema.convert_to_openldap("fedora-ds",
772 f.read())
773 finally:
774 f.close()
775 assert backend_schema_data is not None
776 f = open(os.path.join(self.ldapdir, backend_schema), 'w')
777 try:
778 f.write(backend_schema_data)
779 finally:
780 f.close()
782 self.credentials.set_bind_dn(self.names.ldapmanagerdn)
784 # Destory the target directory, or else setup-ds.pl will complain
785 fedora_ds_dir = os.path.join(self.ldapdir,
786 "slapd-" + self.ldap_instance)
787 shutil.rmtree(fedora_ds_dir, True)
789 self.slapd_provision_command = [self.slapd_path, "-D", fedora_ds_dir,
790 "-i", self.slapd_pid]
791 # In the 'provision' command line, stay in the foreground so we can
792 # easily kill it
793 self.slapd_provision_command.append("-d0")
795 #the command for the final run is the normal script
796 self.slapd_command = [os.path.join(self.ldapdir,
797 "slapd-" + self.ldap_instance, "start-slapd")]
799 # If we were just looking for crashes up to this point, it's a
800 # good time to exit before we realise we don't have Fedora DS on
801 if self.ldap_dryrun_mode:
802 sys.exit(0)
804 # Try to print helpful messages when the user has not specified the
805 # path to the setup-ds tool
806 if self.setup_ds_path is None:
807 raise ProvisioningError("Fedora DS LDAP-Backend must be setup with path to setup-ds, e.g. --setup-ds-path=\"/usr/sbin/setup-ds.pl\"!")
808 if not os.path.exists(self.setup_ds_path):
809 self.logger.warning("Path (%s) to slapd does not exist!",
810 self.setup_ds_path)
812 # Run the Fedora DS setup utility
813 retcode = subprocess.call([self.setup_ds_path, "--silent", "--file",
814 self.fedoradsinf], close_fds=True, shell=False)
815 if retcode != 0:
816 raise ProvisioningError("setup-ds failed")
818 # Load samba-admin
819 retcode = subprocess.call([
820 os.path.join(self.ldapdir, "slapd-" + self.ldap_instance, "ldif2db"), "-s", self.sambadn, "-i", self.samba_ldif],
821 close_fds=True, shell=False)
822 if retcode != 0:
823 raise ProvisioningError("ldif2db failed")
825 def post_setup(self):
826 ldapi_db = Ldb(self.ldap_uri, credentials=self.credentials)
828 # configure in-directory access control on Fedora DS via the aci
829 # attribute (over a direct ldapi:// socket)
830 aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % self.sambadn
832 m = ldb.Message()
833 m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci")
835 for dnstring in (self.names.domaindn, self.names.configdn,
836 self.names.schemadn):
837 m.dn = ldb.Dn(ldapi_db, dnstring)
838 ldapi_db.modify(m)
839 return LDAPBackendResult(self.credentials, self.slapd_command_escaped,
840 self.ldapdir)