selftest: Cover the important non-Samba invalidation of the NT ACL
[Samba/gebeck_regimport.git] / source4 / scripting / python / samba / provision / backend.py
blobf88b0db89c52f3128204a259415a7b9f2e307316
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, credentials=self.credentials)
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
166 # For now, assume existing backends at least emulate OpenLDAP
167 self.ldap_backend_type = "openldap"
170 class LDAPBackend(ProvisionBackend):
172 def __init__(self, backend_type, paths=None, lp=None,
173 credentials=None, names=None, logger=None, domainsid=None,
174 schema=None, hostname=None, ldapadminpass=None,
175 slapd_path=None, ldap_backend_extra_port=None,
176 ldap_backend_forced_uri=None, ldap_dryrun_mode=True):
178 super(LDAPBackend, self).__init__(backend_type=backend_type,
179 paths=paths, lp=lp,
180 credentials=credentials, names=names, logger=logger)
182 self.domainsid = domainsid
183 self.schema = schema
184 self.hostname = hostname
186 self.ldapdir = os.path.join(paths.private_dir, "ldap")
187 self.ldapadminpass = ldapadminpass
189 self.slapd_path = slapd_path
190 self.slapd_command = None
191 self.slapd_command_escaped = None
192 self.slapd_pid = os.path.join(self.ldapdir, "slapd.pid")
194 self.ldap_backend_extra_port = ldap_backend_extra_port
195 self.ldap_dryrun_mode = ldap_dryrun_mode
197 if ldap_backend_forced_uri is not None:
198 self.ldap_uri = ldap_backend_forced_uri
199 else:
200 self.ldap_uri = "ldapi://%s" % urllib.quote(
201 os.path.join(self.ldapdir, "ldapi"), safe="")
203 if not os.path.exists(self.ldapdir):
204 os.mkdir(self.ldapdir)
206 def init(self):
207 from samba.provision import ProvisioningError
208 # we will shortly start slapd with ldapi for final provisioning. first
209 # check with ldapsearch -> rootDSE via self.ldap_uri if another
210 # instance of slapd is already running
211 try:
212 ldapi_db = Ldb(self.ldap_uri)
213 ldapi_db.search(base="", scope=SCOPE_BASE,
214 expression="(objectClass=OpenLDAProotDSE)")
215 try:
216 f = open(self.slapd_pid, "r")
217 except IOError, err:
218 if err != errno.ENOENT:
219 raise
220 else:
221 try:
222 p = f.read()
223 finally:
224 f.close()
225 self.logger.info("Check for slapd process with PID: %s and terminate it manually." % p)
226 raise SlapdAlreadyRunning(self.ldap_uri)
227 except LdbError:
228 # XXX: We should never be catching all Ldb errors
229 pass
231 # Try to print helpful messages when the user has not specified the
232 # path to slapd
233 if self.slapd_path is None:
234 raise ProvisioningError("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
235 if not os.path.exists(self.slapd_path):
236 self.logger.warning("Path (%s) to slapd does not exist!",
237 self.slapd_path)
239 if not os.path.isdir(self.ldapdir):
240 os.makedirs(self.ldapdir, 0700)
242 # Put the LDIF of the schema into a database so we can search on
243 # it to generate schema-dependent configurations in Fedora DS and
244 # OpenLDAP
245 schemadb_path = os.path.join(self.ldapdir, "schema-tmp.ldb")
246 try:
247 os.unlink(schemadb_path)
248 except OSError:
249 pass
251 self.schema.write_to_tmp_ldb(schemadb_path)
253 self.credentials = Credentials()
254 self.credentials.guess(self.lp)
255 # Kerberos to an ldapi:// backend makes no sense
256 self.credentials.set_kerberos_state(DONT_USE_KERBEROS)
257 self.credentials.set_password(self.ldapadminpass)
259 self.secrets_credentials = Credentials()
260 self.secrets_credentials.guess(self.lp)
261 # Kerberos to an ldapi:// backend makes no sense
262 self.secrets_credentials.set_kerberos_state(DONT_USE_KERBEROS)
263 self.secrets_credentials.set_username("samba-admin")
264 self.secrets_credentials.set_password(self.ldapadminpass)
266 self.provision()
268 def provision(self):
269 pass
271 def start(self):
272 from samba.provision import ProvisioningError
273 self.slapd_command_escaped = "\'" + "\' \'".join(self.slapd_command) + "\'"
274 f = open(os.path.join(self.ldapdir, "ldap_backend_startup.sh"), 'w')
275 try:
276 f.write("#!/bin/sh\n" + self.slapd_command_escaped + "\n")
277 finally:
278 f.close()
280 # Now start the slapd, so we can provision onto it. We keep the
281 # subprocess context around, to kill this off at the successful
282 # end of the script
283 self.slapd = subprocess.Popen(self.slapd_provision_command,
284 close_fds=True, shell=False)
286 count = 0
287 while self.slapd.poll() is None:
288 # Wait until the socket appears
289 try:
290 ldapi_db = Ldb(self.ldap_uri, lp=self.lp, credentials=self.credentials)
291 ldapi_db.search(base="", scope=SCOPE_BASE,
292 expression="(objectClass=OpenLDAProotDSE)")
293 # If we have got here, then we must have a valid connection to
294 # the LDAP server!
295 return
296 except LdbError:
297 time.sleep(1)
298 count = count + 1
300 if count > 15:
301 self.logger.error("Could not connect to slapd started with: %s" % "\'" + "\' \'".join(self.slapd_provision_command) + "\'")
302 raise ProvisioningError("slapd never accepted a connection within 15 seconds of starting")
304 self.logger.error("Could not start slapd with: %s" % "\'" + "\' \'".join(self.slapd_provision_command) + "\'")
305 raise ProvisioningError("slapd died before we could make a connection to it")
307 def shutdown(self):
308 # if an LDAP backend is in use, terminate slapd after final provision
309 # and check its proper termination
310 if self.slapd.poll() is None:
311 # Kill the slapd
312 if getattr(self.slapd, "terminate", None) is not None:
313 self.slapd.terminate()
314 else:
315 # Older python versions don't have .terminate()
316 import signal
317 os.kill(self.slapd.pid, signal.SIGTERM)
319 # and now wait for it to die
320 self.slapd.communicate()
322 def post_setup(self):
323 return LDAPBackendResult(self.credentials, self.slapd_command_escaped,
324 self.ldapdir)
327 class OpenLDAPBackend(LDAPBackend):
329 def __init__(self, backend_type, paths=None, lp=None,
330 credentials=None, names=None, logger=None, domainsid=None,
331 schema=None, hostname=None, ldapadminpass=None, slapd_path=None,
332 ldap_backend_extra_port=None, ldap_dryrun_mode=True,
333 ol_mmr_urls=None, nosync=False, ldap_backend_forced_uri=None):
334 from samba.provision import setup_path
335 super(OpenLDAPBackend, self).__init__( backend_type=backend_type,
336 paths=paths, lp=lp,
337 credentials=credentials, names=names, logger=logger,
338 domainsid=domainsid, schema=schema, hostname=hostname,
339 ldapadminpass=ldapadminpass, slapd_path=slapd_path,
340 ldap_backend_extra_port=ldap_backend_extra_port,
341 ldap_backend_forced_uri=ldap_backend_forced_uri,
342 ldap_dryrun_mode=ldap_dryrun_mode)
344 self.ol_mmr_urls = ol_mmr_urls
345 self.nosync = nosync
347 self.slapdconf = os.path.join(self.ldapdir, "slapd.conf")
348 self.modulesconf = os.path.join(self.ldapdir, "modules.conf")
349 self.memberofconf = os.path.join(self.ldapdir, "memberof.conf")
350 self.olmmrserveridsconf = os.path.join(self.ldapdir, "mmr_serverids.conf")
351 self.olmmrsyncreplconf = os.path.join(self.ldapdir, "mmr_syncrepl.conf")
352 self.olcdir = os.path.join(self.ldapdir, "slapd.d")
353 self.olcseedldif = os.path.join(self.ldapdir, "olc_seed.ldif")
355 self.schema = Schema(self.domainsid,
356 schemadn=self.names.schemadn, files=[
357 setup_path("schema_samba4.ldif")])
359 def setup_db_config(self, dbdir):
360 """Setup a Berkeley database.
362 :param dbdir: Database directory.
364 from samba.provision import setup_path
365 if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
366 os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
367 if not os.path.isdir(os.path.join(dbdir, "tmp")):
368 os.makedirs(os.path.join(dbdir, "tmp"), 0700)
370 setup_file(setup_path("DB_CONFIG"),
371 os.path.join(dbdir, "DB_CONFIG"), {"LDAPDBDIR": dbdir})
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_user_config = ""
417 if self.ol_mmr_urls is not None:
418 # For now, make these equal
419 mmr_pass = self.ldapadminpass
421 url_list = filter(None,self.ol_mmr_urls.split(','))
422 for url in url_list:
423 self.logger.info("Using LDAP-URL: "+url)
424 if len(url_list) == 1:
425 raise ProvisioningError("At least 2 LDAP-URLs needed for MMR!")
427 mmr_on_config = "MirrorMode On"
428 mmr_replicator_acl = " by dn=cn=replicator,cn=samba read"
429 serverid = 0
430 for url in url_list:
431 serverid = serverid + 1
432 mmr_serverids_config += read_and_sub_file(
433 setup_path("mmr_serverids.conf"), {
434 "SERVERID": str(serverid),
435 "LDAPSERVER": url })
436 rid = serverid * 10
437 rid = rid + 1
438 mmr_syncrepl_schema_config += read_and_sub_file(
439 setup_path("mmr_syncrepl.conf"), {
440 "RID" : str(rid),
441 "MMRDN": self.names.schemadn,
442 "LDAPSERVER" : url,
443 "MMR_PASSWORD": mmr_pass})
445 rid = rid + 1
446 mmr_syncrepl_config_config += read_and_sub_file(
447 setup_path("mmr_syncrepl.conf"), {
448 "RID" : str(rid),
449 "MMRDN": self.names.configdn,
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_USER_CONFIG": mmr_syncrepl_user_config,
507 "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
508 "OLC_MMR_CONFIG": olc_mmr_config,
509 "REFINT_CONFIG": refint_config,
510 "INDEX_CONFIG": index_config,
511 "NOSYNC": nosync_config})
513 self.setup_db_config(os.path.join(self.ldapdir, "db", "user"))
514 self.setup_db_config(os.path.join(self.ldapdir, "db", "config"))
515 self.setup_db_config(os.path.join(self.ldapdir, "db", "schema"))
517 if not os.path.exists(os.path.join(self.ldapdir, "db", "samba", "cn=samba")):
518 os.makedirs(os.path.join(self.ldapdir, "db", "samba", "cn=samba"), 0700)
520 setup_file(setup_path("cn=samba.ldif"),
521 os.path.join(self.ldapdir, "db", "samba", "cn=samba.ldif"),
522 { "UUID": str(uuid.uuid4()),
523 "LDAPTIME": timestring(int(time.time()))} )
524 setup_file(setup_path("cn=samba-admin.ldif"),
525 os.path.join(self.ldapdir, "db", "samba", "cn=samba", "cn=samba-admin.ldif"),
526 {"LDAPADMINPASS_B64": b64encode(self.ldapadminpass),
527 "UUID": str(uuid.uuid4()),
528 "LDAPTIME": timestring(int(time.time()))} )
530 if self.ol_mmr_urls is not None:
531 setup_file(setup_path("cn=replicator.ldif"),
532 os.path.join(self.ldapdir, "db", "samba", "cn=samba", "cn=replicator.ldif"),
533 {"MMR_PASSWORD_B64": b64encode(mmr_pass),
534 "UUID": str(uuid.uuid4()),
535 "LDAPTIME": timestring(int(time.time()))} )
537 mapping = "schema-map-openldap-2.3"
538 backend_schema = "backend-schema.schema"
540 f = open(setup_path(mapping), 'r')
541 try:
542 backend_schema_data = self.schema.convert_to_openldap(
543 "openldap", f.read())
544 finally:
545 f.close()
546 assert backend_schema_data is not None
547 f = open(os.path.join(self.ldapdir, backend_schema), 'w')
548 try:
549 f.write(backend_schema_data)
550 finally:
551 f.close()
553 # now we generate the needed strings to start slapd automatically,
554 if self.ldap_backend_extra_port is not None:
555 # When we use MMR, we can't use 0.0.0.0 as it uses the name
556 # specified there as part of it's clue as to it's own name,
557 # and not to replicate to itself
558 if self.ol_mmr_urls is None:
559 server_port_string = "ldap://0.0.0.0:%d" % self.ldap_backend_extra_port
560 else:
561 server_port_string = "ldap://%s.%s:%d" (self.names.hostname,
562 self.names.dnsdomain, self.ldap_backend_extra_port)
563 else:
564 server_port_string = ""
566 # Prepare the 'result' information - the commands to return in
567 # particular
568 self.slapd_provision_command = [self.slapd_path, "-F" + self.olcdir,
569 "-h"]
571 # copy this command so we have two version, one with -d0 and only
572 # ldapi (or the forced ldap_uri), and one with all the listen commands
573 self.slapd_command = list(self.slapd_provision_command)
575 self.slapd_provision_command.extend([self.ldap_uri, "-d0"])
577 uris = self.ldap_uri
578 if server_port_string is not "":
579 uris = uris + " " + server_port_string
581 self.slapd_command.append(uris)
583 # Set the username - done here because Fedora DS still uses the admin
584 # DN and simple bind
585 self.credentials.set_username("samba-admin")
587 # Wipe the old sam.ldb databases away
588 shutil.rmtree(self.olcdir, True)
589 os.makedirs(self.olcdir, 0770)
591 # If we were just looking for crashes up to this point, it's a
592 # good time to exit before we realise we don't have OpenLDAP on
593 # this system
594 if self.ldap_dryrun_mode:
595 sys.exit(0)
597 slapd_cmd = [self.slapd_path, "-Ttest", "-n", "0", "-f",
598 self.slapdconf, "-F", self.olcdir]
599 retcode = subprocess.call(slapd_cmd, close_fds=True, shell=False)
601 if retcode != 0:
602 self.logger.error("conversion from slapd.conf to cn=config failed slapd started with: %s" % "\'" + "\' \'".join(slapd_cmd) + "\'")
603 raise ProvisioningError("conversion from slapd.conf to cn=config failed")
605 if not os.path.exists(os.path.join(self.olcdir, "cn=config.ldif")):
606 raise ProvisioningError("conversion from slapd.conf to cn=config failed")
608 # Don't confuse the admin by leaving the slapd.conf around
609 os.remove(self.slapdconf)
612 class FDSBackend(LDAPBackend):
614 def __init__(self, backend_type, paths=None, lp=None,
615 credentials=None, names=None, logger=None, domainsid=None,
616 schema=None, hostname=None, ldapadminpass=None, slapd_path=None,
617 ldap_backend_extra_port=None, ldap_dryrun_mode=True, root=None,
618 setup_ds_path=None):
620 from samba.provision import setup_path
622 super(FDSBackend, self).__init__(backend_type=backend_type,
623 paths=paths, lp=lp,
624 credentials=credentials, names=names, logger=logger,
625 domainsid=domainsid, schema=schema, hostname=hostname,
626 ldapadminpass=ldapadminpass, slapd_path=slapd_path,
627 ldap_backend_extra_port=ldap_backend_extra_port,
628 ldap_backend_forced_uri=ldap_backend_forced_uri,
629 ldap_dryrun_mode=ldap_dryrun_mode)
631 self.root = root
632 self.setup_ds_path = setup_ds_path
633 self.ldap_instance = self.names.netbiosname.lower()
635 self.sambadn = "CN=Samba"
637 self.fedoradsinf = os.path.join(self.ldapdir, "fedorads.inf")
638 self.partitions_ldif = os.path.join(self.ldapdir,
639 "fedorads-partitions.ldif")
640 self.sasl_ldif = os.path.join(self.ldapdir, "fedorads-sasl.ldif")
641 self.dna_ldif = os.path.join(self.ldapdir, "fedorads-dna.ldif")
642 self.pam_ldif = os.path.join(self.ldapdir, "fedorads-pam.ldif")
643 self.refint_ldif = os.path.join(self.ldapdir, "fedorads-refint.ldif")
644 self.linked_attrs_ldif = os.path.join(self.ldapdir,
645 "fedorads-linked-attributes.ldif")
646 self.index_ldif = os.path.join(self.ldapdir, "fedorads-index.ldif")
647 self.samba_ldif = os.path.join(self.ldapdir, "fedorads-samba.ldif")
649 self.samba3_schema = setup_path(
650 "../../examples/LDAP/samba.schema")
651 self.samba3_ldif = os.path.join(self.ldapdir, "samba3.ldif")
653 self.retcode = subprocess.call(["bin/oLschema2ldif",
654 "-I", self.samba3_schema,
655 "-O", self.samba3_ldif,
656 "-b", self.names.domaindn],
657 close_fds=True, shell=False)
659 if self.retcode != 0:
660 raise Exception("Unable to convert Samba 3 schema.")
662 self.schema = Schema(
663 self.domainsid,
664 schemadn=self.names.schemadn,
665 files=[setup_path("schema_samba4.ldif"), self.samba3_ldif],
666 additional_prefixmap=["1000:1.3.6.1.4.1.7165.2.1",
667 "1001:1.3.6.1.4.1.7165.2.2"])
669 def provision(self):
670 from samba.provision import ProvisioningError, setup_path
671 if self.ldap_backend_extra_port is not None:
672 serverport = "ServerPort=%d" % self.ldap_backend_extra_port
673 else:
674 serverport = ""
676 setup_file(setup_path("fedorads.inf"), self.fedoradsinf,
677 {"ROOT": self.root,
678 "HOSTNAME": self.hostname,
679 "DNSDOMAIN": self.names.dnsdomain,
680 "LDAPDIR": self.ldapdir,
681 "DOMAINDN": self.names.domaindn,
682 "LDAP_INSTANCE": self.ldap_instance,
683 "LDAPMANAGERDN": self.names.ldapmanagerdn,
684 "LDAPMANAGERPASS": self.ldapadminpass,
685 "SERVERPORT": serverport})
687 setup_file(setup_path("fedorads-partitions.ldif"),
688 self.partitions_ldif,
689 {"CONFIGDN": self.names.configdn,
690 "SCHEMADN": self.names.schemadn,
691 "SAMBADN": self.sambadn,
694 setup_file(setup_path("fedorads-sasl.ldif"), self.sasl_ldif,
695 {"SAMBADN": self.sambadn,
698 setup_file(setup_path("fedorads-dna.ldif"), self.dna_ldif,
699 {"DOMAINDN": self.names.domaindn,
700 "SAMBADN": self.sambadn,
701 "DOMAINSID": str(self.domainsid),
704 setup_file(setup_path("fedorads-pam.ldif"), self.pam_ldif)
706 lnkattr = self.schema.linked_attributes()
708 f = open(setup_path("fedorads-refint-delete.ldif"), 'r')
709 try:
710 refint_config = f.read()
711 finally:
712 f.close()
713 memberof_config = ""
714 index_config = ""
715 argnum = 3
717 for attr in lnkattr.keys():
718 if lnkattr[attr] is not None:
719 refint_config += read_and_sub_file(
720 setup_path("fedorads-refint-add.ldif"),
721 { "ARG_NUMBER" : str(argnum),
722 "LINK_ATTR" : attr })
723 memberof_config += read_and_sub_file(
724 setup_path("fedorads-linked-attributes.ldif"),
725 { "MEMBER_ATTR" : attr,
726 "MEMBEROF_ATTR" : lnkattr[attr] })
727 index_config += read_and_sub_file(
728 setup_path("fedorads-index.ldif"), { "ATTR" : attr })
729 argnum += 1
731 f = open(self.refint_ldif, 'w')
732 try:
733 f.write(refint_config)
734 finally:
735 f.close()
736 f = open(self.linked_attrs_ldif, 'w')
737 try:
738 f.write(memberof_config)
739 finally:
740 f.close()
742 attrs = ["lDAPDisplayName"]
743 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)
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)