samba-tool domain provision: Make ldap_backend_startup.sh +x and take optional arguments
[mat/samba.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         ldap_backend_script = os.path.join(self.ldapdir, "ldap_backend_startup.sh")
275         f = open(ldap_backend_script, 'w')
276         try:
277             f.write("#!/bin/sh\n" + self.slapd_command_escaped + " $@\n")
278         finally:
279             f.close()
280
281         os.chmod(ldap_backend_script, 0755)
282
283         # Now start the slapd, so we can provision onto it.  We keep the
284         # subprocess context around, to kill this off at the successful
285         # end of the script
286         self.slapd = subprocess.Popen(self.slapd_provision_command,
287             close_fds=True, shell=False)
288
289         count = 0
290         while self.slapd.poll() is None:
291             # Wait until the socket appears
292             try:
293                 ldapi_db = Ldb(self.ldap_uri, lp=self.lp, credentials=self.credentials)
294                 ldapi_db.search(base="", scope=SCOPE_BASE,
295                     expression="(objectClass=OpenLDAProotDSE)")
296                 # If we have got here, then we must have a valid connection to
297                 # the LDAP server!
298                 return
299             except LdbError:
300                 time.sleep(1)
301                 count = count + 1
302
303                 if count > 15:
304                     self.logger.error("Could not connect to slapd started with: %s" %  "\'" + "\' \'".join(self.slapd_provision_command) + "\'")
305                     raise ProvisioningError("slapd never accepted a connection within 15 seconds of starting")
306
307         self.logger.error("Could not start slapd with: %s" % "\'" + "\' \'".join(self.slapd_provision_command) + "\'")
308         raise ProvisioningError("slapd died before we could make a connection to it")
309
310     def shutdown(self):
311         # if an LDAP backend is in use, terminate slapd after final provision
312         # and check its proper termination
313         if self.slapd.poll() is None:
314             # Kill the slapd
315             if getattr(self.slapd, "terminate", None) is not None:
316                 self.slapd.terminate()
317             else:
318                 # Older python versions don't have .terminate()
319                 import signal
320                 os.kill(self.slapd.pid, signal.SIGTERM)
321
322             # and now wait for it to die
323             self.slapd.communicate()
324
325     def post_setup(self):
326         return LDAPBackendResult(self.credentials, self.slapd_command_escaped,
327                     self.ldapdir)
328
329
330 class OpenLDAPBackend(LDAPBackend):
331
332     def __init__(self, backend_type, paths=None, lp=None,
333             credentials=None, names=None, logger=None, domainsid=None,
334             schema=None, hostname=None, ldapadminpass=None, slapd_path=None,
335             ldap_backend_extra_port=None, ldap_dryrun_mode=True,
336             ol_mmr_urls=None, nosync=False, ldap_backend_forced_uri=None):
337         from samba.provision import setup_path
338         super(OpenLDAPBackend, self).__init__( backend_type=backend_type,
339                 paths=paths, lp=lp,
340                 credentials=credentials, names=names, logger=logger,
341                 domainsid=domainsid, schema=schema, hostname=hostname,
342                 ldapadminpass=ldapadminpass, slapd_path=slapd_path,
343                 ldap_backend_extra_port=ldap_backend_extra_port,
344                 ldap_backend_forced_uri=ldap_backend_forced_uri,
345                 ldap_dryrun_mode=ldap_dryrun_mode)
346
347         self.ol_mmr_urls = ol_mmr_urls
348         self.nosync = nosync
349
350         self.slapdconf          = os.path.join(self.ldapdir, "slapd.conf")
351         self.modulesconf        = os.path.join(self.ldapdir, "modules.conf")
352         self.memberofconf       = os.path.join(self.ldapdir, "memberof.conf")
353         self.olmmrserveridsconf = os.path.join(self.ldapdir, "mmr_serverids.conf")
354         self.olmmrsyncreplconf  = os.path.join(self.ldapdir, "mmr_syncrepl.conf")
355         self.olcdir             = os.path.join(self.ldapdir, "slapd.d")
356         self.olcseedldif        = os.path.join(self.ldapdir, "olc_seed.ldif")
357
358         self.schema = Schema(self.domainsid,
359                              schemadn=self.names.schemadn, files=[
360                 setup_path("schema_samba4.ldif")])
361
362     def setup_db_config(self, dbdir):
363         """Setup a Berkeley database.
364
365         :param dbdir: Database directory.
366         """
367         from samba.provision import setup_path
368         if not os.path.isdir(os.path.join(dbdir, "bdb-logs")):
369             os.makedirs(os.path.join(dbdir, "bdb-logs"), 0700)
370             if not os.path.isdir(os.path.join(dbdir, "tmp")):
371                 os.makedirs(os.path.join(dbdir, "tmp"), 0700)
372
373         setup_file(setup_path("DB_CONFIG"),
374             os.path.join(dbdir, "DB_CONFIG"), {"LDAPDBDIR": dbdir})
375
376     def provision(self):
377         from samba.provision import ProvisioningError, setup_path
378         # Wipe the directories so we can start
379         shutil.rmtree(os.path.join(self.ldapdir, "db"), True)
380
381         # Allow the test scripts to turn off fsync() for OpenLDAP as for TDB
382         # and LDB
383         nosync_config = ""
384         if self.nosync:
385             nosync_config = "dbnosync"
386
387         lnkattr = self.schema.linked_attributes()
388         refint_attributes = ""
389         memberof_config = "# Generated from Samba4 schema\n"
390         for att in lnkattr.keys():
391             if lnkattr[att] is not None:
392                 refint_attributes = refint_attributes + " " + att
393
394                 memberof_config += read_and_sub_file(
395                     setup_path("memberof.conf"), {
396                         "MEMBER_ATTR": att,
397                         "MEMBEROF_ATTR" : lnkattr[att] })
398
399         refint_config = read_and_sub_file(setup_path("refint.conf"),
400                                       { "LINK_ATTRS" : refint_attributes})
401
402         attrs = ["linkID", "lDAPDisplayName"]
403         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)
404         index_config = ""
405         for i in range (0, len(res)):
406             index_attr = res[i]["lDAPDisplayName"][0]
407             if index_attr == "objectGUID":
408                 index_attr = "entryUUID"
409
410             index_config += "index " + index_attr + " eq\n"
411
412         # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
413         mmr_on_config = ""
414         mmr_replicator_acl = ""
415         mmr_serverids_config = ""
416         mmr_syncrepl_schema_config = ""
417         mmr_syncrepl_config_config = ""
418         mmr_syncrepl_user_config = ""
419
420         if self.ol_mmr_urls is not None:
421             # For now, make these equal
422             mmr_pass = self.ldapadminpass
423
424             url_list = filter(None,self.ol_mmr_urls.split(','))
425             for url in url_list:
426                 self.logger.info("Using LDAP-URL: "+url)
427             if len(url_list) == 1:
428                 raise ProvisioningError("At least 2 LDAP-URLs needed for MMR!")
429
430             mmr_on_config = "MirrorMode On"
431             mmr_replicator_acl = "  by dn=cn=replicator,cn=samba read"
432             serverid = 0
433             for url in url_list:
434                 serverid = serverid + 1
435                 mmr_serverids_config += read_and_sub_file(
436                     setup_path("mmr_serverids.conf"), {
437                         "SERVERID": str(serverid),
438                         "LDAPSERVER": url })
439                 rid = serverid * 10
440                 rid = rid + 1
441                 mmr_syncrepl_schema_config += read_and_sub_file(
442                         setup_path("mmr_syncrepl.conf"), {
443                             "RID" : str(rid),
444                            "MMRDN": self.names.schemadn,
445                            "LDAPSERVER" : url,
446                            "MMR_PASSWORD": mmr_pass})
447
448                 rid = rid + 1
449                 mmr_syncrepl_config_config += read_and_sub_file(
450                     setup_path("mmr_syncrepl.conf"), {
451                         "RID" : str(rid),
452                         "MMRDN": self.names.configdn,
453                         "LDAPSERVER" : url,
454                         "MMR_PASSWORD": mmr_pass})
455
456                 rid = rid + 1
457                 mmr_syncrepl_user_config += read_and_sub_file(
458                     setup_path("mmr_syncrepl.conf"), {
459                         "RID" : str(rid),
460                         "MMRDN": self.names.domaindn,
461                         "LDAPSERVER" : url,
462                         "MMR_PASSWORD": mmr_pass })
463         # OpenLDAP cn=config initialisation
464         olc_syncrepl_config = ""
465         olc_mmr_config = ""
466         # if mmr = yes, generate cn=config-replication directives
467         # and olc_seed.lif for the other mmr-servers
468         if self.ol_mmr_urls is not None:
469             serverid = 0
470             olc_serverids_config = ""
471             olc_syncrepl_seed_config = ""
472             olc_mmr_config += read_and_sub_file(
473                 setup_path("olc_mmr.conf"), {})
474             rid = 500
475             for url in url_list:
476                 serverid = serverid + 1
477                 olc_serverids_config += read_and_sub_file(
478                     setup_path("olc_serverid.conf"), {
479                         "SERVERID" : str(serverid), "LDAPSERVER" : url })
480
481                 rid = rid + 1
482                 olc_syncrepl_config += read_and_sub_file(
483                     setup_path("olc_syncrepl.conf"), {
484                         "RID" : str(rid), "LDAPSERVER" : url,
485                         "MMR_PASSWORD": mmr_pass})
486
487                 olc_syncrepl_seed_config += read_and_sub_file(
488                     setup_path("olc_syncrepl_seed.conf"), {
489                         "RID" : str(rid), "LDAPSERVER" : url})
490
491             setup_file(setup_path("olc_seed.ldif"), self.olcseedldif,
492                        {"OLC_SERVER_ID_CONF": olc_serverids_config,
493                         "OLC_PW": self.ldapadminpass,
494                         "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
495         # end olc
496
497         setup_file(setup_path("slapd.conf"), self.slapdconf,
498                    {"DNSDOMAIN": self.names.dnsdomain,
499                     "LDAPDIR": self.ldapdir,
500                     "DOMAINDN": self.names.domaindn,
501                     "CONFIGDN": self.names.configdn,
502                     "SCHEMADN": self.names.schemadn,
503                     "MEMBEROF_CONFIG": memberof_config,
504                     "MIRRORMODE": mmr_on_config,
505                     "REPLICATOR_ACL": mmr_replicator_acl,
506                     "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
507                     "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
508                     "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
509                     "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
510                     "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
511                     "OLC_MMR_CONFIG": olc_mmr_config,
512                     "REFINT_CONFIG": refint_config,
513                     "INDEX_CONFIG": index_config,
514                     "NOSYNC": nosync_config})
515
516         self.setup_db_config(os.path.join(self.ldapdir, "db", "user"))
517         self.setup_db_config(os.path.join(self.ldapdir, "db", "config"))
518         self.setup_db_config(os.path.join(self.ldapdir, "db", "schema"))
519
520         if not os.path.exists(os.path.join(self.ldapdir, "db", "samba", "cn=samba")):
521             os.makedirs(os.path.join(self.ldapdir, "db", "samba", "cn=samba"), 0700)
522
523         setup_file(setup_path("cn=samba.ldif"),
524                    os.path.join(self.ldapdir, "db", "samba", "cn=samba.ldif"),
525                    { "UUID": str(uuid.uuid4()),
526                      "LDAPTIME": timestring(int(time.time()))} )
527         setup_file(setup_path("cn=samba-admin.ldif"),
528                    os.path.join(self.ldapdir, "db", "samba", "cn=samba", "cn=samba-admin.ldif"),
529                    {"LDAPADMINPASS_B64": b64encode(self.ldapadminpass),
530                     "UUID": str(uuid.uuid4()),
531                     "LDAPTIME": timestring(int(time.time()))} )
532
533         if self.ol_mmr_urls is not None:
534             setup_file(setup_path("cn=replicator.ldif"),
535                        os.path.join(self.ldapdir, "db", "samba", "cn=samba", "cn=replicator.ldif"),
536                        {"MMR_PASSWORD_B64": b64encode(mmr_pass),
537                         "UUID": str(uuid.uuid4()),
538                         "LDAPTIME": timestring(int(time.time()))} )
539
540         mapping = "schema-map-openldap-2.3"
541         backend_schema = "backend-schema.schema"
542
543         f = open(setup_path(mapping), 'r')
544         try:
545             backend_schema_data = self.schema.convert_to_openldap(
546                     "openldap", f.read())
547         finally:
548             f.close()
549         assert backend_schema_data is not None
550         f = open(os.path.join(self.ldapdir, backend_schema), 'w')
551         try:
552             f.write(backend_schema_data)
553         finally:
554             f.close()
555
556         # now we generate the needed strings to start slapd automatically,
557         if self.ldap_backend_extra_port is not None:
558             # When we use MMR, we can't use 0.0.0.0 as it uses the name
559             # specified there as part of it's clue as to it's own name,
560             # and not to replicate to itself
561             if self.ol_mmr_urls is None:
562                 server_port_string = "ldap://0.0.0.0:%d" % self.ldap_backend_extra_port
563             else:
564                 server_port_string = "ldap://%s.%s:%d" (self.names.hostname,
565                     self.names.dnsdomain, self.ldap_backend_extra_port)
566         else:
567             server_port_string = ""
568
569         # Prepare the 'result' information - the commands to return in
570         # particular
571         self.slapd_provision_command = [self.slapd_path, "-F" + self.olcdir,
572             "-h"]
573
574         # copy this command so we have two version, one with -d0 and only
575         # ldapi (or the forced ldap_uri), and one with all the listen commands
576         self.slapd_command = list(self.slapd_provision_command)
577
578         self.slapd_provision_command.extend([self.ldap_uri, "-d0"])
579
580         uris = self.ldap_uri
581         if server_port_string is not "":
582             uris = uris + " " + server_port_string
583
584         self.slapd_command.append(uris)
585
586         # Set the username - done here because Fedora DS still uses the admin
587         # DN and simple bind
588         self.credentials.set_username("samba-admin")
589
590         # Wipe the old sam.ldb databases away
591         shutil.rmtree(self.olcdir, True)
592         os.makedirs(self.olcdir, 0770)
593
594         # If we were just looking for crashes up to this point, it's a
595         # good time to exit before we realise we don't have OpenLDAP on
596         # this system
597         if self.ldap_dryrun_mode:
598             sys.exit(0)
599
600         slapd_cmd = [self.slapd_path, "-Ttest", "-n", "0", "-f",
601                          self.slapdconf, "-F", self.olcdir]
602         retcode = subprocess.call(slapd_cmd, close_fds=True, shell=False)
603
604         if retcode != 0:
605             self.logger.error("conversion from slapd.conf to cn=config failed slapd started with: %s" %  "\'" + "\' \'".join(slapd_cmd) + "\'")
606             raise ProvisioningError("conversion from slapd.conf to cn=config failed")
607
608         if not os.path.exists(os.path.join(self.olcdir, "cn=config.ldif")):
609             raise ProvisioningError("conversion from slapd.conf to cn=config failed")
610
611         # Don't confuse the admin by leaving the slapd.conf around
612         os.remove(self.slapdconf)
613
614
615 class FDSBackend(LDAPBackend):
616
617     def __init__(self, backend_type, paths=None, lp=None,
618             credentials=None, names=None, logger=None, domainsid=None,
619             schema=None, hostname=None, ldapadminpass=None, slapd_path=None,
620             ldap_backend_extra_port=None, ldap_dryrun_mode=True, root=None,
621             setup_ds_path=None):
622
623         from samba.provision import setup_path
624
625         super(FDSBackend, self).__init__(backend_type=backend_type,
626                 paths=paths, lp=lp,
627                 credentials=credentials, names=names, logger=logger,
628                 domainsid=domainsid, schema=schema, hostname=hostname,
629                 ldapadminpass=ldapadminpass, slapd_path=slapd_path,
630                 ldap_backend_extra_port=ldap_backend_extra_port,
631                 ldap_backend_forced_uri=ldap_backend_forced_uri,
632                 ldap_dryrun_mode=ldap_dryrun_mode)
633
634         self.root = root
635         self.setup_ds_path = setup_ds_path
636         self.ldap_instance = self.names.netbiosname.lower()
637
638         self.sambadn = "CN=Samba"
639
640         self.fedoradsinf = os.path.join(self.ldapdir, "fedorads.inf")
641         self.partitions_ldif = os.path.join(self.ldapdir,
642             "fedorads-partitions.ldif")
643         self.sasl_ldif = os.path.join(self.ldapdir, "fedorads-sasl.ldif")
644         self.dna_ldif = os.path.join(self.ldapdir, "fedorads-dna.ldif")
645         self.pam_ldif = os.path.join(self.ldapdir, "fedorads-pam.ldif")
646         self.refint_ldif = os.path.join(self.ldapdir, "fedorads-refint.ldif")
647         self.linked_attrs_ldif = os.path.join(self.ldapdir,
648             "fedorads-linked-attributes.ldif")
649         self.index_ldif = os.path.join(self.ldapdir, "fedorads-index.ldif")
650         self.samba_ldif = os.path.join(self.ldapdir, "fedorads-samba.ldif")
651
652         self.samba3_schema = setup_path(
653             "../../examples/LDAP/samba.schema")
654         self.samba3_ldif = os.path.join(self.ldapdir, "samba3.ldif")
655
656         self.retcode = subprocess.call(["bin/oLschema2ldif",
657                 "-I", self.samba3_schema,
658                 "-O", self.samba3_ldif,
659                 "-b", self.names.domaindn],
660                 close_fds=True, shell=False)
661
662         if self.retcode != 0:
663             raise Exception("Unable to convert Samba 3 schema.")
664
665         self.schema = Schema(
666                 self.domainsid,
667                 schemadn=self.names.schemadn,
668                 files=[setup_path("schema_samba4.ldif"), self.samba3_ldif],
669                 additional_prefixmap=["1000:1.3.6.1.4.1.7165.2.1",
670                                       "1001:1.3.6.1.4.1.7165.2.2"])
671
672     def provision(self):
673         from samba.provision import ProvisioningError, setup_path
674         if self.ldap_backend_extra_port is not None:
675             serverport = "ServerPort=%d" % self.ldap_backend_extra_port
676         else:
677             serverport = ""
678
679         setup_file(setup_path("fedorads.inf"), self.fedoradsinf,
680                    {"ROOT": self.root,
681                     "HOSTNAME": self.hostname,
682                     "DNSDOMAIN": self.names.dnsdomain,
683                     "LDAPDIR": self.ldapdir,
684                     "DOMAINDN": self.names.domaindn,
685                     "LDAP_INSTANCE": self.ldap_instance,
686                     "LDAPMANAGERDN": self.names.ldapmanagerdn,
687                     "LDAPMANAGERPASS": self.ldapadminpass,
688                     "SERVERPORT": serverport})
689
690         setup_file(setup_path("fedorads-partitions.ldif"),
691             self.partitions_ldif,
692                    {"CONFIGDN": self.names.configdn,
693                     "SCHEMADN": self.names.schemadn,
694                     "SAMBADN": self.sambadn,
695                     })
696
697         setup_file(setup_path("fedorads-sasl.ldif"), self.sasl_ldif,
698                    {"SAMBADN": self.sambadn,
699                     })
700
701         setup_file(setup_path("fedorads-dna.ldif"), self.dna_ldif,
702                    {"DOMAINDN": self.names.domaindn,
703                     "SAMBADN": self.sambadn,
704                     "DOMAINSID": str(self.domainsid),
705                     })
706
707         setup_file(setup_path("fedorads-pam.ldif"), self.pam_ldif)
708
709         lnkattr = self.schema.linked_attributes()
710
711         f = open(setup_path("fedorads-refint-delete.ldif"), 'r')
712         try:
713             refint_config = f.read()
714         finally:
715             f.close()
716         memberof_config = ""
717         index_config = ""
718         argnum = 3
719
720         for attr in lnkattr.keys():
721             if lnkattr[attr] is not None:
722                 refint_config += read_and_sub_file(
723                     setup_path("fedorads-refint-add.ldif"),
724                          { "ARG_NUMBER" : str(argnum),
725                            "LINK_ATTR" : attr })
726                 memberof_config += read_and_sub_file(
727                     setup_path("fedorads-linked-attributes.ldif"),
728                          { "MEMBER_ATTR" : attr,
729                            "MEMBEROF_ATTR" : lnkattr[attr] })
730                 index_config += read_and_sub_file(
731                     setup_path("fedorads-index.ldif"), { "ATTR" : attr })
732                 argnum += 1
733
734         f = open(self.refint_ldif, 'w')
735         try:
736             f.write(refint_config)
737         finally:
738             f.close()
739         f = open(self.linked_attrs_ldif, 'w')
740         try:
741             f.write(memberof_config)
742         finally:
743             f.close()
744
745         attrs = ["lDAPDisplayName"]
746         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)
747
748         for i in range (0, len(res)):
749             attr = res[i]["lDAPDisplayName"][0]
750
751             if attr == "objectGUID":
752                 attr = "nsUniqueId"
753
754             index_config += read_and_sub_file(
755                 setup_path("fedorads-index.ldif"), { "ATTR" : attr })
756
757         f = open(self.index_ldif, 'w')
758         try:
759             f.write(index_config)
760         finally:
761             f.close()
762
763         setup_file(setup_path("fedorads-samba.ldif"), self.samba_ldif, {
764             "SAMBADN": self.sambadn,
765             "LDAPADMINPASS": self.ldapadminpass
766             })
767
768         mapping = "schema-map-fedora-ds-1.0"
769         backend_schema = "99_ad.ldif"
770
771         # Build a schema file in Fedora DS format
772         f = open(setup_path(mapping), 'r')
773         try:
774             backend_schema_data = self.schema.convert_to_openldap("fedora-ds",
775                 f.read())
776         finally:
777             f.close()
778         assert backend_schema_data is not None
779         f = open(os.path.join(self.ldapdir, backend_schema), 'w')
780         try:
781             f.write(backend_schema_data)
782         finally:
783             f.close()
784
785         self.credentials.set_bind_dn(self.names.ldapmanagerdn)
786
787         # Destory the target directory, or else setup-ds.pl will complain
788         fedora_ds_dir = os.path.join(self.ldapdir,
789             "slapd-" + self.ldap_instance)
790         shutil.rmtree(fedora_ds_dir, True)
791
792         self.slapd_provision_command = [self.slapd_path, "-D", fedora_ds_dir,
793                 "-i", self.slapd_pid]
794         # In the 'provision' command line, stay in the foreground so we can
795         # easily kill it
796         self.slapd_provision_command.append("-d0")
797
798         #the command for the final run is the normal script
799         self.slapd_command = [os.path.join(self.ldapdir,
800             "slapd-" + self.ldap_instance, "start-slapd")]
801
802         # If we were just looking for crashes up to this point, it's a
803         # good time to exit before we realise we don't have Fedora DS on
804         if self.ldap_dryrun_mode:
805             sys.exit(0)
806
807         # Try to print helpful messages when the user has not specified the
808         # path to the setup-ds tool
809         if self.setup_ds_path is None:
810             raise ProvisioningError("Fedora DS LDAP-Backend must be setup with path to setup-ds, e.g. --setup-ds-path=\"/usr/sbin/setup-ds.pl\"!")
811         if not os.path.exists(self.setup_ds_path):
812             self.logger.warning("Path (%s) to slapd does not exist!",
813                 self.setup_ds_path)
814
815         # Run the Fedora DS setup utility
816         retcode = subprocess.call([self.setup_ds_path, "--silent", "--file",
817             self.fedoradsinf], close_fds=True, shell=False)
818         if retcode != 0:
819             raise ProvisioningError("setup-ds failed")
820
821         # Load samba-admin
822         retcode = subprocess.call([
823             os.path.join(self.ldapdir, "slapd-" + self.ldap_instance, "ldif2db"), "-s", self.sambadn, "-i", self.samba_ldif],
824             close_fds=True, shell=False)
825         if retcode != 0:
826             raise ProvisioningError("ldif2db failed")
827
828     def post_setup(self):
829         ldapi_db = Ldb(self.ldap_uri, credentials=self.credentials)
830
831         # configure in-directory access control on Fedora DS via the aci
832         # attribute (over a direct ldapi:// socket)
833         aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % self.sambadn
834
835         m = ldb.Message()
836         m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci")
837
838         for dnstring in (self.names.domaindn, self.names.configdn,
839                          self.names.schemadn):
840             m.dn = ldb.Dn(ldapi_db, dnstring)
841             ldapi_db.modify(m)
842         return LDAPBackendResult(self.credentials, self.slapd_command_escaped,
843             self.ldapdir)