s4-openldap: Restored openldap-related options to the provision script
[Samba.git] / python / samba / provision / backend.py
blob93c38f78bb921a95801397437a1ef14023aa706b
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, credentials, slapd_command_escaped, ldapdir):
67 self.credentials = credentials
68 self.slapd_command_escaped = slapd_command_escaped
69 self.ldapdir = ldapdir
71 def report_logger(self, logger):
72 if self.credentials.get_bind_dn() is not None:
73 logger.info("LDAP Backend Admin DN: %s" %
74 self.credentials.get_bind_dn())
75 else:
76 logger.info("LDAP Admin User: %s" %
77 self.credentials.get_username())
79 if self.slapd_command_escaped is not None:
80 # now display slapd_command_file.txt to show how slapd must be
81 # started next time
82 logger.info(
83 "Use later the following commandline to start slapd, then Samba:")
84 logger.info(self.slapd_command_escaped)
85 logger.info(
86 "This slapd-Commandline is also stored under: %s/ldap_backend_startup.sh",
87 self.ldapdir)
90 class ProvisionBackend(object):
92 def __init__(self, backend_type, paths=None, lp=None,
93 credentials=None, names=None, logger=None):
94 """Provision a backend for samba4"""
95 self.paths = paths
96 self.lp = lp
97 self.credentials = credentials
98 self.names = names
99 self.logger = logger
101 self.type = backend_type
103 # Set a default - the code for "existing" below replaces this
104 self.ldap_backend_type = backend_type
106 def init(self):
107 """Initialize the backend."""
108 raise NotImplementedError(self.init)
110 def start(self):
111 """Start the backend."""
112 raise NotImplementedError(self.start)
114 def shutdown(self):
115 """Shutdown the backend."""
116 raise NotImplementedError(self.shutdown)
118 def post_setup(self):
119 """Post setup.
121 :return: A BackendResult or None
123 raise NotImplementedError(self.post_setup)
126 class LDBBackend(ProvisionBackend):
128 def init(self):
129 self.credentials = None
130 self.secrets_credentials = None
132 # Wipe the old sam.ldb databases away
133 shutil.rmtree(self.paths.samdb + ".d", True)
135 def start(self):
136 pass
138 def shutdown(self):
139 pass
141 def post_setup(self):
142 pass
145 class ExistingBackend(ProvisionBackend):
147 def __init__(self, backend_type, paths=None, lp=None,
148 credentials=None, names=None, logger=None, ldapi_uri=None):
150 super(ExistingBackend, self).__init__(backend_type=backend_type,
151 paths=paths, lp=lp,
152 credentials=credentials, names=names, logger=logger,
153 ldap_backend_forced_uri=ldapi_uri)
155 def init(self):
156 # Check to see that this 'existing' LDAP backend in fact exists
157 ldapi_db = Ldb(self.ldapi_uri)
158 ldapi_db.search(base="", scope=SCOPE_BASE,
159 expression="(objectClass=OpenLDAProotDSE)")
161 # If we have got here, then we must have a valid connection to the LDAP
162 # server, with valid credentials supplied This caused them to be set
163 # into the long-term database later in the script.
164 self.secrets_credentials = self.credentials
167 # For now, assume existing backends at least emulate OpenLDAP
168 self.ldap_backend_type = "openldap"
171 class LDAPBackend(ProvisionBackend):
173 def __init__(self, backend_type, paths=None, lp=None,
174 credentials=None, names=None, logger=None, domainsid=None,
175 schema=None, hostname=None, ldapadminpass=None,
176 slapd_path=None, ldap_backend_extra_port=None,
177 ldap_backend_forced_uri=None, ldap_dryrun_mode=False):
179 super(LDAPBackend, self).__init__(backend_type=backend_type,
180 paths=paths, lp=lp,
181 credentials=credentials, names=names, logger=logger)
183 self.domainsid = domainsid
184 self.schema = schema
185 self.hostname = hostname
187 self.ldapdir = os.path.join(paths.private_dir, "ldap")
188 self.ldapadminpass = ldapadminpass
190 self.slapd_path = slapd_path
191 self.slapd_command = None
192 self.slapd_command_escaped = None
193 self.slapd_pid = os.path.join(self.ldapdir, "slapd.pid")
195 self.ldap_backend_extra_port = ldap_backend_extra_port
196 self.ldap_dryrun_mode = ldap_dryrun_mode
198 if ldap_backend_forced_uri is not None:
199 self.ldap_uri = ldap_backend_forced_uri
200 else:
201 self.ldap_uri = "ldapi://%s" % urllib.quote(
202 os.path.join(self.ldapdir, "ldapi"), safe="")
204 if not os.path.exists(self.ldapdir):
205 os.mkdir(self.ldapdir)
207 def init(self):
208 from samba.provision import ProvisioningError
209 # we will shortly start slapd with ldapi for final provisioning. first
210 # check with ldapsearch -> rootDSE via self.ldap_uri if another
211 # instance of slapd is already running
212 try:
213 ldapi_db = Ldb(self.ldap_uri)
214 ldapi_db.search(base="", scope=SCOPE_BASE,
215 expression="(objectClass=OpenLDAProotDSE)")
216 try:
217 f = open(self.slapd_pid, "r")
218 except IOError, err:
219 if err != errno.ENOENT:
220 raise
221 else:
222 try:
223 p = f.read()
224 finally:
225 f.close()
226 self.logger.info("Check for slapd process with PID: %s and terminate it manually." % p)
227 raise SlapdAlreadyRunning(self.ldap_uri)
228 except LdbError:
229 # XXX: We should never be catching all Ldb errors
230 pass
232 # Try to print helpful messages when the user has not specified the
233 # path to slapd
234 if self.slapd_path is None:
235 raise ProvisioningError("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
236 if not os.path.exists(self.slapd_path):
237 self.logger.warning("Path (%s) to slapd does not exist!",
238 self.slapd_path)
240 if not os.path.isdir(self.ldapdir):
241 os.makedirs(self.ldapdir, 0700)
243 # Put the LDIF of the schema into a database so we can search on
244 # it to generate schema-dependent configurations in Fedora DS and
245 # OpenLDAP
246 schemadb_path = os.path.join(self.ldapdir, "schema-tmp.ldb")
247 try:
248 os.unlink(schemadb_path)
249 except OSError:
250 pass
252 self.schema.write_to_tmp_ldb(schemadb_path)
254 self.credentials = Credentials()
255 self.credentials.guess(self.lp)
256 # Kerberos to an ldapi:// backend makes no sense
257 self.credentials.set_kerberos_state(DONT_USE_KERBEROS)
258 self.credentials.set_password(self.ldapadminpass)
259 self.credentials.set_forced_sasl_mech("EXTERNAL")
261 self.secrets_credentials = Credentials()
262 self.secrets_credentials.guess(self.lp)
263 # Kerberos to an ldapi:// backend makes no sense
264 self.secrets_credentials.set_kerberos_state(DONT_USE_KERBEROS)
265 self.secrets_credentials.set_username("samba-admin")
266 self.secrets_credentials.set_password(self.ldapadminpass)
267 self.secrets_credentials.set_forced_sasl_mech("EXTERNAL")
269 self.provision()
271 def provision(self):
272 pass
274 def start(self):
275 from samba.provision import ProvisioningError
276 self.slapd_command_escaped = "\'" + "\' \'".join(self.slapd_command) + "\'"
277 ldap_backend_script = os.path.join(self.ldapdir, "ldap_backend_startup.sh")
278 f = open(ldap_backend_script, 'w')
279 try:
280 f.write("#!/bin/sh\n" + self.slapd_command_escaped + " $@\n")
281 finally:
282 f.close()
284 os.chmod(ldap_backend_script, 0755)
286 # Now start the slapd, so we can provision onto it. We keep the
287 # subprocess context around, to kill this off at the successful
288 # end of the script
289 self.slapd = subprocess.Popen(self.slapd_provision_command,
290 close_fds=True, shell=False)
292 count = 0
293 while self.slapd.poll() is None:
294 # Wait until the socket appears
295 try:
296 time.sleep(1)
297 ldapi_db = Ldb(self.ldap_uri, lp=self.lp, credentials=self.credentials)
298 ldapi_db.search(base="", scope=SCOPE_BASE,
299 expression="(objectClass=OpenLDAProotDSE)")
300 # If we have got here, then we must have a valid connection to
301 # the LDAP server!
302 return
303 except LdbError:
304 count = count + 1
306 if count > 15:
307 self.logger.error("Could not connect to slapd started with: %s" % "\'" + "\' \'".join(self.slapd_provision_command) + "\'")
308 raise ProvisioningError("slapd never accepted a connection within 15 seconds of starting")
310 self.logger.error("Could not start slapd with: %s" % "\'" + "\' \'".join(self.slapd_provision_command) + "\'")
311 raise ProvisioningError("slapd died before we could make a connection to it")
313 def shutdown(self):
314 # if an LDAP backend is in use, terminate slapd after final provision
315 # and check its proper termination
316 if self.slapd.poll() is None:
317 # Kill the slapd
318 if getattr(self.slapd, "terminate", None) is not None:
319 self.slapd.terminate()
320 else:
321 # Older python versions don't have .terminate()
322 import signal
323 os.kill(self.slapd.pid, signal.SIGTERM)
325 # and now wait for it to die
326 self.slapd.communicate()
328 def post_setup(self):
329 return LDAPBackendResult(self.credentials, self.slapd_command_escaped,
330 self.ldapdir)
333 class OpenLDAPBackend(LDAPBackend):
335 def __init__(self, backend_type, paths=None, lp=None,
336 credentials=None, names=None, logger=None, domainsid=None,
337 schema=None, hostname=None, ldapadminpass=None, slapd_path=None,
338 ldap_backend_extra_port=None, ldap_dryrun_mode=False,
339 ol_mmr_urls=None, nosync=False, ldap_backend_forced_uri=None):
340 from samba.provision import setup_path
341 super(OpenLDAPBackend, self).__init__( backend_type=backend_type,
342 paths=paths, lp=lp,
343 credentials=credentials, names=names, logger=logger,
344 domainsid=domainsid, schema=schema, hostname=hostname,
345 ldapadminpass=ldapadminpass, slapd_path=slapd_path,
346 ldap_backend_extra_port=ldap_backend_extra_port,
347 ldap_backend_forced_uri=ldap_backend_forced_uri,
348 ldap_dryrun_mode=ldap_dryrun_mode)
350 self.ol_mmr_urls = ol_mmr_urls
351 self.nosync = nosync
353 self.slapdconf = os.path.join(self.ldapdir, "slapd.conf")
354 self.modulesconf = os.path.join(self.ldapdir, "modules.conf")
355 self.memberofconf = os.path.join(self.ldapdir, "memberof.conf")
356 self.olmmrserveridsconf = os.path.join(self.ldapdir, "mmr_serverids.conf")
357 self.olmmrsyncreplconf = os.path.join(self.ldapdir, "mmr_syncrepl.conf")
358 self.olcdir = os.path.join(self.ldapdir, "slapd.d")
359 self.olcseedldif = os.path.join(self.ldapdir, "olc_seed.ldif")
361 self.schema = Schema(self.domainsid,
362 schemadn=self.names.schemadn, files=[
363 setup_path("schema_samba4.ldif")])
365 def setup_db_dir(self, dbdir):
366 """Create a database directory.
368 :param dbdir: Database directory.
370 if not os.path.exists(dbdir):
371 os.makedirs(dbdir, 0700)
373 def provision(self):
374 from samba.provision import ProvisioningError, setup_path
375 # Wipe the directories so we can start
376 shutil.rmtree(os.path.join(self.ldapdir, "db"), True)
378 # Allow the test scripts to turn off fsync() for OpenLDAP as for TDB
379 # and LDB
380 nosync_config = ""
381 if self.nosync:
382 nosync_config = "dbnosync"
384 lnkattr = self.schema.linked_attributes()
385 refint_attributes = ""
386 memberof_config = "# Generated from Samba4 schema\n"
387 for att in lnkattr.keys():
388 if lnkattr[att] is not None:
389 refint_attributes = refint_attributes + " " + att
391 memberof_config += read_and_sub_file(
392 setup_path("memberof.conf"), {
393 "MEMBER_ATTR": att,
394 "MEMBEROF_ATTR" : lnkattr[att] })
396 refint_config = read_and_sub_file(setup_path("refint.conf"),
397 { "LINK_ATTRS" : refint_attributes})
399 attrs = ["linkID", "lDAPDisplayName"]
400 res = self.schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=self.names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
401 index_config = ""
402 for i in range (0, len(res)):
403 index_attr = res[i]["lDAPDisplayName"][0]
404 if index_attr == "objectGUID":
405 index_attr = "entryUUID"
407 index_config += "index " + index_attr + " eq\n"
409 # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
410 mmr_on_config = ""
411 mmr_replicator_acl = ""
412 mmr_serverids_config = ""
413 mmr_syncrepl_schema_config = ""
414 mmr_syncrepl_config_config = ""
415 mmr_syncrepl_domaindns_config = ""
416 mmr_syncrepl_forestdns_config = ""
417 mmr_syncrepl_user_config = ""
418 mmr_pass = ""
420 if self.ol_mmr_urls is not None:
421 # For now, make these equal
422 mmr_pass = self.ldapadminpass
424 url_list = filter(None,self.ol_mmr_urls.split(','))
425 for url in url_list:
426 self.logger.info("Using LDAP-URL: "+url)
427 if len(url_list) == 1:
428 raise ProvisioningError("At least 2 LDAP-URLs needed for MMR!")
430 mmr_on_config = "MirrorMode On"
431 mmr_replicator_acl = " by dn=cn=replicator,cn=samba read"
432 serverid = 0
433 for url in url_list:
434 serverid = serverid + 1
435 mmr_serverids_config += read_and_sub_file(
436 setup_path("mmr_serverids.conf"), {
437 "SERVERID": str(serverid),
438 "LDAPSERVER": url })
439 rid = serverid * 10
440 rid = rid + 1
441 mmr_syncrepl_schema_config += read_and_sub_file(
442 setup_path("mmr_syncrepl.conf"), {
443 "RID" : str(rid),
444 "MMRDN": self.names.schemadn,
445 "LDAPSERVER" : url,
446 "MMR_PASSWORD": mmr_pass})
448 rid = rid + 1
449 mmr_syncrepl_config_config += read_and_sub_file(
450 setup_path("mmr_syncrepl.conf"), {
451 "RID" : str(rid),
452 "MMRDN": self.names.configdn,
453 "LDAPSERVER" : url,
454 "MMR_PASSWORD": mmr_pass})
456 rid = rid + 1
457 mmr_syncrepl_domaindns_config += read_and_sub_file(
458 setup_path("mmr_syncrepl.conf"), {
459 "RID" : str(rid),
460 "MMRDN": "dc=DomainDNSZones," + self.names.domaindn,
461 "LDAPSERVER" : url,
462 "MMR_PASSWORD": mmr_pass})
464 rid = rid + 1
465 mmr_syncrepl_forestdns_config += read_and_sub_file(
466 setup_path("mmr_syncrepl.conf"), {
467 "RID" : str(rid),
468 "MMRDN": "dc=ForestDNSZones," + self.names.domaindn,
469 "LDAPSERVER" : url,
470 "MMR_PASSWORD": mmr_pass})
472 rid = rid + 1
473 mmr_syncrepl_user_config += read_and_sub_file(
474 setup_path("mmr_syncrepl.conf"), {
475 "RID" : str(rid),
476 "MMRDN": self.names.domaindn,
477 "LDAPSERVER" : url,
478 "MMR_PASSWORD": mmr_pass })
479 # OpenLDAP cn=config initialisation
480 olc_syncrepl_config = ""
481 olc_mmr_config = ""
482 # if mmr = yes, generate cn=config-replication directives
483 # and olc_seed.lif for the other mmr-servers
484 if self.ol_mmr_urls is not None:
485 serverid = 0
486 olc_serverids_config = ""
487 olc_syncrepl_seed_config = ""
488 olc_mmr_config += read_and_sub_file(
489 setup_path("olc_mmr.conf"), {})
490 rid = 500
491 for url in url_list:
492 serverid = serverid + 1
493 olc_serverids_config += read_and_sub_file(
494 setup_path("olc_serverid.conf"), {
495 "SERVERID" : str(serverid), "LDAPSERVER" : url })
497 rid = rid + 1
498 olc_syncrepl_config += read_and_sub_file(
499 setup_path("olc_syncrepl.conf"), {
500 "RID" : str(rid), "LDAPSERVER" : url,
501 "MMR_PASSWORD": mmr_pass})
503 olc_syncrepl_seed_config += read_and_sub_file(
504 setup_path("olc_syncrepl_seed.conf"), {
505 "RID" : str(rid), "LDAPSERVER" : url})
507 setup_file(setup_path("olc_seed.ldif"), self.olcseedldif,
508 {"OLC_SERVER_ID_CONF": olc_serverids_config,
509 "OLC_PW": self.ldapadminpass,
510 "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
511 # end olc
513 setup_file(setup_path("slapd.conf"), self.slapdconf,
514 {"DNSDOMAIN": self.names.dnsdomain,
515 "LDAPDIR": self.ldapdir,
516 "DOMAINDN": self.names.domaindn,
517 "CONFIGDN": self.names.configdn,
518 "SCHEMADN": self.names.schemadn,
519 "MEMBEROF_CONFIG": memberof_config,
520 "MIRRORMODE": mmr_on_config,
521 "REPLICATOR_ACL": mmr_replicator_acl,
522 "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
523 "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
524 "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
525 "MMR_SYNCREPL_DOMAINDNS_CONFIG": mmr_syncrepl_domaindns_config,
526 "MMR_SYNCREPL_FORESTDNS_CONFIG": mmr_syncrepl_forestdns_config,
527 "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
528 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
529 "OLC_MMR_CONFIG": olc_mmr_config,
530 "REFINT_CONFIG": refint_config,
531 "INDEX_CONFIG": index_config,
532 "ADMIN_UID": str(os.getuid()),
533 "NOSYNC": nosync_config,})
535 self.setup_db_dir(os.path.join(self.ldapdir, "db", "forestdns"))
536 self.setup_db_dir(os.path.join(self.ldapdir, "db", "domaindns"))
537 self.setup_db_dir(os.path.join(self.ldapdir, "db", "user"))
538 self.setup_db_dir(os.path.join(self.ldapdir, "db", "config"))
539 self.setup_db_dir(os.path.join(self.ldapdir, "db", "schema"))
540 self.setup_db_dir(os.path.join(self.ldapdir, "db", "samba"))
542 if self.ol_mmr_urls is not None:
543 mmr = ""
544 else:
545 mmr = "#"
547 cn_samba = read_and_sub_file(
548 setup_path("cn=samba.ldif"),
549 { "LDAPADMINPASS": self.ldapadminpass,
550 "MMR_PASSWORD": mmr_pass,
551 "MMR": mmr })
553 mapping = "schema-map-openldap-2.3"
554 backend_schema = "backend-schema.schema"
556 f = open(setup_path(mapping), 'r')
557 try:
558 backend_schema_data = self.schema.convert_to_openldap(
559 "openldap", f.read())
560 finally:
561 f.close()
562 assert backend_schema_data is not None
563 f = open(os.path.join(self.ldapdir, backend_schema), 'w')
564 try:
565 f.write(backend_schema_data)
566 finally:
567 f.close()
569 # now we generate the needed strings to start slapd automatically,
570 if self.ldap_backend_extra_port is not None:
571 # When we use MMR, we can't use 0.0.0.0 as it uses the name
572 # specified there as part of it's clue as to it's own name,
573 # and not to replicate to itself
574 if self.ol_mmr_urls is None:
575 server_port_string = "ldap://0.0.0.0:%d" % self.ldap_backend_extra_port
576 else:
577 server_port_string = "ldap://%s.%s:%d" (self.names.hostname,
578 self.names.dnsdomain, self.ldap_backend_extra_port)
579 else:
580 server_port_string = ""
582 # Prepare the 'result' information - the commands to return in
583 # particular
584 self.slapd_provision_command = [self.slapd_path, "-F" + self.olcdir,
585 "-h"]
587 # copy this command so we have two version, one with -d0 and only
588 # ldapi (or the forced ldap_uri), and one with all the listen commands
589 self.slapd_command = list(self.slapd_provision_command)
591 self.slapd_provision_command.extend([self.ldap_uri, "-d0"])
592 uris = self.ldap_uri
593 if server_port_string is not "":
594 uris = uris + " " + server_port_string
596 self.slapd_command.append(uris)
598 # Set the username - done here because Fedora DS still uses the admin
599 # DN and simple bind
600 self.credentials.set_username("samba-admin")
602 # Wipe the old sam.ldb databases away
603 shutil.rmtree(self.olcdir, True)
604 os.makedirs(self.olcdir, 0770)
606 # If we were just looking for crashes up to this point, it's a
607 # good time to exit before we realise we don't have OpenLDAP on
608 # this system
609 if self.ldap_dryrun_mode:
610 sys.exit(0)
612 slapd_cmd = [self.slapd_path, "-Ttest", "-n", "0", "-f",
613 self.slapdconf, "-F", self.olcdir]
614 retcode = subprocess.call(slapd_cmd, close_fds=True, shell=False)
616 if retcode != 0:
617 self.logger.error("conversion from slapd.conf to cn=config failed slapd started with: %s" % "\'" + "\' \'".join(slapd_cmd) + "\'")
618 raise ProvisioningError("conversion from slapd.conf to cn=config failed")
620 if not os.path.exists(os.path.join(self.olcdir, "cn=config.ldif")):
621 raise ProvisioningError("conversion from slapd.conf to cn=config failed")
623 # Don't confuse the admin by leaving the slapd.conf around
624 os.remove(self.slapdconf)
626 cn_samba_cmd = [self.slapd_path, "-Tadd", "-b", "cn=samba", "-F", self.olcdir]
627 p = subprocess.Popen(cn_samba_cmd, stdin=subprocess.PIPE, shell=False)
628 p.stdin.write(cn_samba)
629 p.communicate()
632 class FDSBackend(LDAPBackend):
634 def __init__(self, backend_type, paths=None, lp=None,
635 credentials=None, names=None, logger=None, domainsid=None,
636 schema=None, hostname=None, ldapadminpass=None, slapd_path=None,
637 ldap_backend_extra_port=None, ldap_dryrun_mode=False, root=None,
638 setup_ds_path=None):
640 from samba.provision import setup_path
642 super(FDSBackend, self).__init__(backend_type=backend_type,
643 paths=paths, lp=lp,
644 credentials=credentials, names=names, logger=logger,
645 domainsid=domainsid, schema=schema, hostname=hostname,
646 ldapadminpass=ldapadminpass, slapd_path=slapd_path,
647 ldap_backend_extra_port=ldap_backend_extra_port,
648 ldap_backend_forced_uri=ldap_backend_forced_uri,
649 ldap_dryrun_mode=ldap_dryrun_mode)
651 self.root = root
652 self.setup_ds_path = setup_ds_path
653 self.ldap_instance = self.names.netbiosname.lower()
655 self.sambadn = "CN=Samba"
657 self.fedoradsinf = os.path.join(self.ldapdir, "fedorads.inf")
658 self.partitions_ldif = os.path.join(self.ldapdir,
659 "fedorads-partitions.ldif")
660 self.sasl_ldif = os.path.join(self.ldapdir, "fedorads-sasl.ldif")
661 self.dna_ldif = os.path.join(self.ldapdir, "fedorads-dna.ldif")
662 self.pam_ldif = os.path.join(self.ldapdir, "fedorads-pam.ldif")
663 self.refint_ldif = os.path.join(self.ldapdir, "fedorads-refint.ldif")
664 self.linked_attrs_ldif = os.path.join(self.ldapdir,
665 "fedorads-linked-attributes.ldif")
666 self.index_ldif = os.path.join(self.ldapdir, "fedorads-index.ldif")
667 self.samba_ldif = os.path.join(self.ldapdir, "fedorads-samba.ldif")
669 self.samba3_schema = setup_path(
670 "../../examples/LDAP/samba.schema")
671 self.samba3_ldif = os.path.join(self.ldapdir, "samba3.ldif")
673 self.retcode = subprocess.call(["bin/oLschema2ldif",
674 "-I", self.samba3_schema,
675 "-O", self.samba3_ldif,
676 "-b", self.names.domaindn],
677 close_fds=True, shell=False)
679 if self.retcode != 0:
680 raise Exception("Unable to convert Samba 3 schema.")
682 self.schema = Schema(
683 self.domainsid,
684 schemadn=self.names.schemadn,
685 files=[setup_path("schema_samba4.ldif"), self.samba3_ldif],
686 additional_prefixmap=["1000:1.3.6.1.4.1.7165.2.1",
687 "1001:1.3.6.1.4.1.7165.2.2"])
689 def provision(self):
690 from samba.provision import ProvisioningError, setup_path
691 if self.ldap_backend_extra_port is not None:
692 serverport = "ServerPort=%d" % self.ldap_backend_extra_port
693 else:
694 serverport = ""
696 setup_file(setup_path("fedorads.inf"), self.fedoradsinf,
697 {"ROOT": self.root,
698 "HOSTNAME": self.hostname,
699 "DNSDOMAIN": self.names.dnsdomain,
700 "LDAPDIR": self.ldapdir,
701 "DOMAINDN": self.names.domaindn,
702 "LDAP_INSTANCE": self.ldap_instance,
703 "LDAPMANAGERDN": self.names.ldapmanagerdn,
704 "LDAPMANAGERPASS": self.ldapadminpass,
705 "SERVERPORT": serverport})
707 setup_file(setup_path("fedorads-partitions.ldif"),
708 self.partitions_ldif,
709 {"CONFIGDN": self.names.configdn,
710 "SCHEMADN": self.names.schemadn,
711 "SAMBADN": self.sambadn,
714 setup_file(setup_path("fedorads-sasl.ldif"), self.sasl_ldif,
715 {"SAMBADN": self.sambadn,
718 setup_file(setup_path("fedorads-dna.ldif"), self.dna_ldif,
719 {"DOMAINDN": self.names.domaindn,
720 "SAMBADN": self.sambadn,
721 "DOMAINSID": str(self.domainsid),
724 setup_file(setup_path("fedorads-pam.ldif"), self.pam_ldif)
726 lnkattr = self.schema.linked_attributes()
728 f = open(setup_path("fedorads-refint-delete.ldif"), 'r')
729 try:
730 refint_config = f.read()
731 finally:
732 f.close()
733 memberof_config = ""
734 index_config = ""
735 argnum = 3
737 for attr in lnkattr.keys():
738 if lnkattr[attr] is not None:
739 refint_config += read_and_sub_file(
740 setup_path("fedorads-refint-add.ldif"),
741 { "ARG_NUMBER" : str(argnum),
742 "LINK_ATTR" : attr })
743 memberof_config += read_and_sub_file(
744 setup_path("fedorads-linked-attributes.ldif"),
745 { "MEMBER_ATTR" : attr,
746 "MEMBEROF_ATTR" : lnkattr[attr] })
747 index_config += read_and_sub_file(
748 setup_path("fedorads-index.ldif"), { "ATTR" : attr })
749 argnum += 1
751 f = open(self.refint_ldif, 'w')
752 try:
753 f.write(refint_config)
754 finally:
755 f.close()
756 f = open(self.linked_attrs_ldif, 'w')
757 try:
758 f.write(memberof_config)
759 finally:
760 f.close()
762 attrs = ["lDAPDisplayName"]
763 res = self.schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=self.names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
765 for i in range (0, len(res)):
766 attr = res[i]["lDAPDisplayName"][0]
768 if attr == "objectGUID":
769 attr = "nsUniqueId"
771 index_config += read_and_sub_file(
772 setup_path("fedorads-index.ldif"), { "ATTR" : attr })
774 f = open(self.index_ldif, 'w')
775 try:
776 f.write(index_config)
777 finally:
778 f.close()
780 setup_file(setup_path("fedorads-samba.ldif"), self.samba_ldif, {
781 "SAMBADN": self.sambadn,
782 "LDAPADMINPASS": self.ldapadminpass
785 mapping = "schema-map-fedora-ds-1.0"
786 backend_schema = "99_ad.ldif"
788 # Build a schema file in Fedora DS format
789 f = open(setup_path(mapping), 'r')
790 try:
791 backend_schema_data = self.schema.convert_to_openldap("fedora-ds",
792 f.read())
793 finally:
794 f.close()
795 assert backend_schema_data is not None
796 f = open(os.path.join(self.ldapdir, backend_schema), 'w')
797 try:
798 f.write(backend_schema_data)
799 finally:
800 f.close()
802 self.credentials.set_bind_dn(self.names.ldapmanagerdn)
804 # Destory the target directory, or else setup-ds.pl will complain
805 fedora_ds_dir = os.path.join(self.ldapdir,
806 "slapd-" + self.ldap_instance)
807 shutil.rmtree(fedora_ds_dir, True)
809 self.slapd_provision_command = [self.slapd_path, "-D", fedora_ds_dir,
810 "-i", self.slapd_pid]
811 # In the 'provision' command line, stay in the foreground so we can
812 # easily kill it
813 self.slapd_provision_command.append("-d0")
815 #the command for the final run is the normal script
816 self.slapd_command = [os.path.join(self.ldapdir,
817 "slapd-" + self.ldap_instance, "start-slapd")]
819 # If we were just looking for crashes up to this point, it's a
820 # good time to exit before we realise we don't have Fedora DS on
821 if self.ldap_dryrun_mode:
822 sys.exit(0)
824 # Try to print helpful messages when the user has not specified the
825 # path to the setup-ds tool
826 if self.setup_ds_path is None:
827 raise ProvisioningError("Fedora DS LDAP-Backend must be setup with path to setup-ds, e.g. --setup-ds-path=\"/usr/sbin/setup-ds.pl\"!")
828 if not os.path.exists(self.setup_ds_path):
829 self.logger.warning("Path (%s) to slapd does not exist!",
830 self.setup_ds_path)
832 # Run the Fedora DS setup utility
833 retcode = subprocess.call([self.setup_ds_path, "--silent", "--file",
834 self.fedoradsinf], close_fds=True, shell=False)
835 if retcode != 0:
836 raise ProvisioningError("setup-ds failed")
838 # Load samba-admin
839 retcode = subprocess.call([
840 os.path.join(self.ldapdir, "slapd-" + self.ldap_instance, "ldif2db"), "-s", self.sambadn, "-i", self.samba_ldif],
841 close_fds=True, shell=False)
842 if retcode != 0:
843 raise ProvisioningError("ldif2db failed")
845 def post_setup(self):
846 ldapi_db = Ldb(self.ldap_uri, credentials=self.credentials)
848 # configure in-directory access control on Fedora DS via the aci
849 # attribute (over a direct ldapi:// socket)
850 aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % self.sambadn
852 m = ldb.Message()
853 m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci")
855 for dnstring in (self.names.domaindn, self.names.configdn,
856 self.names.schemadn):
857 m.dn = ldb.Dn(ldapi_db, dnstring)
858 ldapi_db.modify(m)
859 return LDAPBackendResult(self.credentials, self.slapd_command_escaped,
860 self.ldapdir)