build: Change bin/default/python -> bin/python symlink to bin/default/python_modules
[sfrench/samba-autobuild/.git] / python / samba / provision / backend.py
1 #
2 # Unix SMB/CIFS implementation.
3 # backend code for provisioning a Samba4 server
4
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
8 #
9 # Based on the original in EJS:
10 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
11 #
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.
16 #
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.
21 #
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/>.
24 #
25
26 """Functions for setting up a Samba configuration (LDB and LDAP backends)."""
27
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
38
39 from ldb import SCOPE_BASE, SCOPE_ONELEVEL, LdbError, timestring
40
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
44
45
46 class SlapdAlreadyRunning(Exception):
47
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)
53
54
55 class BackendResult(object):
56
57     def report_logger(self, logger):
58         """Rerport this result to a particular logger.
59
60         """
61         raise NotImplementedError(self.report_logger)
62
63
64 class LDAPBackendResult(BackendResult):
65
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
70
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())
78
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)
88
89
90 class ProvisionBackend(object):
91
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
100
101         self.type = backend_type
102
103         # Set a default - the code for "existing" below replaces this
104         self.ldap_backend_type = backend_type
105
106     def init(self):
107         """Initialize the backend."""
108         raise NotImplementedError(self.init)
109
110     def start(self):
111         """Start the backend."""
112         raise NotImplementedError(self.start)
113
114     def shutdown(self):
115         """Shutdown the backend."""
116         raise NotImplementedError(self.shutdown)
117
118     def post_setup(self):
119         """Post setup.
120
121         :return: A BackendResult or None
122         """
123         raise NotImplementedError(self.post_setup)
124
125
126 class LDBBackend(ProvisionBackend):
127
128     def init(self):
129         self.credentials = None
130         self.secrets_credentials = None
131
132         # Wipe the old sam.ldb databases away
133         shutil.rmtree(self.paths.samdb + ".d", True)
134
135     def start(self):
136         pass
137
138     def shutdown(self):
139         pass
140
141     def post_setup(self):
142         pass
143
144
145 class ExistingBackend(ProvisionBackend):
146
147     def __init__(self, backend_type, paths=None, lp=None,
148             credentials=None, names=None, logger=None, ldapi_uri=None):
149
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)
154
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)")
160
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
165
166          # For now, assume existing backends at least emulate OpenLDAP
167         self.ldap_backend_type = "openldap"
168
169
170 class LDAPBackend(ProvisionBackend):
171
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):
177
178         super(LDAPBackend, self).__init__(backend_type=backend_type,
179                 paths=paths, lp=lp,
180                 credentials=credentials, names=names, logger=logger)
181
182         self.domainsid = domainsid
183         self.schema = schema
184         self.hostname = hostname
185
186         self.ldapdir = os.path.join(paths.private_dir, "ldap")
187         self.ldapadminpass = ldapadminpass
188
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")
193
194         self.ldap_backend_extra_port = ldap_backend_extra_port
195         self.ldap_dryrun_mode = ldap_dryrun_mode
196
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="")
202
203         if not os.path.exists(self.ldapdir):
204             os.mkdir(self.ldapdir)
205
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
230
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)
238
239         if not os.path.isdir(self.ldapdir):
240             os.makedirs(self.ldapdir, 0700)
241
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
250
251         self.schema.write_to_tmp_ldb(schemadb_path)
252
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)
258
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)
265
266         self.provision()
267
268     def provision(self):
269         pass
270
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()
279
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)
285
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
299
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")
303
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")
306
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)
318
319             # and now wait for it to die
320             self.slapd.communicate()
321
322     def post_setup(self):
323         return LDAPBackendResult(self.credentials, self.slapd_command_escaped,
324                     self.ldapdir)
325
326
327 class OpenLDAPBackend(LDAPBackend):
328
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)
343
344         self.ol_mmr_urls = ol_mmr_urls
345         self.nosync = nosync
346
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")
354
355         self.schema = Schema(self.domainsid,
356                              schemadn=self.names.schemadn, files=[
357                 setup_path("schema_samba4.ldif")])
358
359     def setup_db_config(self, dbdir):
360         """Setup a Berkeley database.
361
362         :param dbdir: Database directory.
363         """
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)
369
370         setup_file(setup_path("DB_CONFIG"),
371             os.path.join(dbdir, "DB_CONFIG"), {"LDAPDBDIR": dbdir})
372
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)
377
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"
383
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
390
391                 memberof_config += read_and_sub_file(
392                     setup_path("memberof.conf"), {
393                         "MEMBER_ATTR": att,
394                         "MEMBEROF_ATTR" : lnkattr[att] })
395
396         refint_config = read_and_sub_file(setup_path("refint.conf"),
397                                       { "LINK_ATTRS" : refint_attributes})
398
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"
406
407             index_config += "index " + index_attr + " eq\n"
408
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 = ""
416
417         if self.ol_mmr_urls is not None:
418             # For now, make these equal
419             mmr_pass = self.ldapadminpass
420
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!")
426
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})
444
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})
452
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 })
477
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})
483
484                 olc_syncrepl_seed_config += read_and_sub_file(
485                     setup_path("olc_syncrepl_seed.conf"), {
486                         "RID" : str(rid), "LDAPSERVER" : url})
487
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
493
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})
512
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"))
516
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)
519
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()))} )
529
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()))} )
536
537         mapping = "schema-map-openldap-2.3"
538         backend_schema = "backend-schema.schema"
539
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()
552
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 = ""
565
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"]
570
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)
574
575         self.slapd_provision_command.extend([self.ldap_uri, "-d0"])
576
577         uris = self.ldap_uri
578         if server_port_string is not "":
579             uris = uris + " " + server_port_string
580
581         self.slapd_command.append(uris)
582
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")
586
587         # Wipe the old sam.ldb databases away
588         shutil.rmtree(self.olcdir, True)
589         os.makedirs(self.olcdir, 0770)
590
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)
596
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)
600
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")
604
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")
607
608         # Don't confuse the admin by leaving the slapd.conf around
609         os.remove(self.slapdconf)
610
611
612 class FDSBackend(LDAPBackend):
613
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):
619
620         from samba.provision import setup_path
621
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)
630
631         self.root = root
632         self.setup_ds_path = setup_ds_path
633         self.ldap_instance = self.names.netbiosname.lower()
634
635         self.sambadn = "CN=Samba"
636
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")
648
649         self.samba3_schema = setup_path(
650             "../../examples/LDAP/samba.schema")
651         self.samba3_ldif = os.path.join(self.ldapdir, "samba3.ldif")
652
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)
658
659         if self.retcode != 0:
660             raise Exception("Unable to convert Samba 3 schema.")
661
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"])
668
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 = ""
675
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})
686
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,
692                     })
693
694         setup_file(setup_path("fedorads-sasl.ldif"), self.sasl_ldif,
695                    {"SAMBADN": self.sambadn,
696                     })
697
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),
702                     })
703
704         setup_file(setup_path("fedorads-pam.ldif"), self.pam_ldif)
705
706         lnkattr = self.schema.linked_attributes()
707
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
716
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
730
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()
741
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)
744
745         for i in range (0, len(res)):
746             attr = res[i]["lDAPDisplayName"][0]
747
748             if attr == "objectGUID":
749                 attr = "nsUniqueId"
750
751             index_config += read_and_sub_file(
752                 setup_path("fedorads-index.ldif"), { "ATTR" : attr })
753
754         f = open(self.index_ldif, 'w')
755         try:
756             f.write(index_config)
757         finally:
758             f.close()
759
760         setup_file(setup_path("fedorads-samba.ldif"), self.samba_ldif, {
761             "SAMBADN": self.sambadn,
762             "LDAPADMINPASS": self.ldapadminpass
763             })
764
765         mapping = "schema-map-fedora-ds-1.0"
766         backend_schema = "99_ad.ldif"
767
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()
781
782         self.credentials.set_bind_dn(self.names.ldapmanagerdn)
783
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)
788
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")
794
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")]
798
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)
803
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)
811
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")
817
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")
824
825     def post_setup(self):
826         ldapi_db = Ldb(self.ldap_uri, credentials=self.credentials)
827
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
831
832         m = ldb.Message()
833         m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci")
834
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)