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