s4-openldap: Restored openldap-related options to the provision script
[nivanova/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)
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
167          # For now, assume existing backends at least emulate OpenLDAP
168         self.ldap_backend_type = "openldap"
169
170
171 class LDAPBackend(ProvisionBackend):
172
173     def __init__(self, backend_type, paths=None, lp=None,
174                  credentials=None, names=None, logger=None, domainsid=None,
175                  schema=None, hostname=None, ldapadminpass=None,
176                  slapd_path=None, ldap_backend_extra_port=None,
177                  ldap_backend_forced_uri=None, ldap_dryrun_mode=False):
178
179         super(LDAPBackend, self).__init__(backend_type=backend_type,
180                 paths=paths, lp=lp,
181                 credentials=credentials, names=names, logger=logger)
182
183         self.domainsid = domainsid
184         self.schema = schema
185         self.hostname = hostname
186
187         self.ldapdir = os.path.join(paths.private_dir, "ldap")
188         self.ldapadminpass = ldapadminpass
189
190         self.slapd_path = slapd_path
191         self.slapd_command = None
192         self.slapd_command_escaped = None
193         self.slapd_pid = os.path.join(self.ldapdir, "slapd.pid")
194
195         self.ldap_backend_extra_port = ldap_backend_extra_port
196         self.ldap_dryrun_mode = ldap_dryrun_mode
197
198         if ldap_backend_forced_uri is not None:
199             self.ldap_uri = ldap_backend_forced_uri
200         else:
201             self.ldap_uri = "ldapi://%s" % urllib.quote(
202                 os.path.join(self.ldapdir, "ldapi"), safe="")
203
204         if not os.path.exists(self.ldapdir):
205             os.mkdir(self.ldapdir)
206
207     def init(self):
208         from samba.provision import ProvisioningError
209         # we will shortly start slapd with ldapi for final provisioning. first
210         # check with ldapsearch -> rootDSE via self.ldap_uri if another
211         # instance of slapd is already running
212         try:
213             ldapi_db = Ldb(self.ldap_uri)
214             ldapi_db.search(base="", scope=SCOPE_BASE,
215                 expression="(objectClass=OpenLDAProotDSE)")
216             try:
217                 f = open(self.slapd_pid, "r")
218             except IOError, err:
219                 if err != errno.ENOENT:
220                     raise
221             else:
222                 try:
223                     p = f.read()
224                 finally:
225                     f.close()
226                 self.logger.info("Check for slapd process with PID: %s and terminate it manually." % p)
227             raise SlapdAlreadyRunning(self.ldap_uri)
228         except LdbError:
229             # XXX: We should never be catching all Ldb errors
230             pass
231
232         # Try to print helpful messages when the user has not specified the
233         # path to slapd
234         if self.slapd_path is None:
235             raise ProvisioningError("Warning: LDAP-Backend must be setup with path to slapd, e.g. --slapd-path=\"/usr/local/libexec/slapd\"!")
236         if not os.path.exists(self.slapd_path):
237             self.logger.warning("Path (%s) to slapd does not exist!",
238                 self.slapd_path)
239
240         if not os.path.isdir(self.ldapdir):
241             os.makedirs(self.ldapdir, 0700)
242
243         # Put the LDIF of the schema into a database so we can search on
244         # it to generate schema-dependent configurations in Fedora DS and
245         # OpenLDAP
246         schemadb_path = os.path.join(self.ldapdir, "schema-tmp.ldb")
247         try:
248             os.unlink(schemadb_path)
249         except OSError:
250             pass
251
252         self.schema.write_to_tmp_ldb(schemadb_path)
253
254         self.credentials = Credentials()
255         self.credentials.guess(self.lp)
256         # Kerberos to an ldapi:// backend makes no sense
257         self.credentials.set_kerberos_state(DONT_USE_KERBEROS)
258         self.credentials.set_password(self.ldapadminpass)
259         self.credentials.set_forced_sasl_mech("EXTERNAL")
260
261         self.secrets_credentials = Credentials()
262         self.secrets_credentials.guess(self.lp)
263         # Kerberos to an ldapi:// backend makes no sense
264         self.secrets_credentials.set_kerberos_state(DONT_USE_KERBEROS)
265         self.secrets_credentials.set_username("samba-admin")
266         self.secrets_credentials.set_password(self.ldapadminpass)
267         self.secrets_credentials.set_forced_sasl_mech("EXTERNAL")
268
269         self.provision()
270
271     def provision(self):
272         pass
273
274     def start(self):
275         from samba.provision import ProvisioningError
276         self.slapd_command_escaped = "\'" + "\' \'".join(self.slapd_command) + "\'"
277         ldap_backend_script = os.path.join(self.ldapdir, "ldap_backend_startup.sh")
278         f = open(ldap_backend_script, 'w')
279         try:
280             f.write("#!/bin/sh\n" + self.slapd_command_escaped + " $@\n")
281         finally:
282             f.close()
283
284         os.chmod(ldap_backend_script, 0755)
285
286         # Now start the slapd, so we can provision onto it.  We keep the
287         # subprocess context around, to kill this off at the successful
288         # end of the script
289         self.slapd = subprocess.Popen(self.slapd_provision_command,
290             close_fds=True, shell=False)
291
292         count = 0
293         while self.slapd.poll() is None:
294             # Wait until the socket appears
295             try:
296                 time.sleep(1)
297                 ldapi_db = Ldb(self.ldap_uri, lp=self.lp, credentials=self.credentials)
298                 ldapi_db.search(base="", scope=SCOPE_BASE,
299                     expression="(objectClass=OpenLDAProotDSE)")
300                 # If we have got here, then we must have a valid connection to
301                 # the LDAP server!
302                 return
303             except LdbError:
304                 count = count + 1
305
306                 if count > 15:
307                     self.logger.error("Could not connect to slapd started with: %s" %  "\'" + "\' \'".join(self.slapd_provision_command) + "\'")
308                     raise ProvisioningError("slapd never accepted a connection within 15 seconds of starting")
309
310         self.logger.error("Could not start slapd with: %s" % "\'" + "\' \'".join(self.slapd_provision_command) + "\'")
311         raise ProvisioningError("slapd died before we could make a connection to it")
312
313     def shutdown(self):
314         # if an LDAP backend is in use, terminate slapd after final provision
315         # and check its proper termination
316         if self.slapd.poll() is None:
317             # Kill the slapd
318             if getattr(self.slapd, "terminate", None) is not None:
319                 self.slapd.terminate()
320             else:
321                 # Older python versions don't have .terminate()
322                 import signal
323                 os.kill(self.slapd.pid, signal.SIGTERM)
324
325             # and now wait for it to die
326             self.slapd.communicate()
327
328     def post_setup(self):
329         return LDAPBackendResult(self.credentials, self.slapd_command_escaped,
330                     self.ldapdir)
331
332
333 class OpenLDAPBackend(LDAPBackend):
334
335     def __init__(self, backend_type, paths=None, lp=None,
336             credentials=None, names=None, logger=None, domainsid=None,
337             schema=None, hostname=None, ldapadminpass=None, slapd_path=None,
338             ldap_backend_extra_port=None, ldap_dryrun_mode=False,
339             ol_mmr_urls=None, nosync=False, ldap_backend_forced_uri=None):
340         from samba.provision import setup_path
341         super(OpenLDAPBackend, self).__init__( backend_type=backend_type,
342                 paths=paths, lp=lp,
343                 credentials=credentials, names=names, logger=logger,
344                 domainsid=domainsid, schema=schema, hostname=hostname,
345                 ldapadminpass=ldapadminpass, slapd_path=slapd_path,
346                 ldap_backend_extra_port=ldap_backend_extra_port,
347                 ldap_backend_forced_uri=ldap_backend_forced_uri,
348                 ldap_dryrun_mode=ldap_dryrun_mode)
349
350         self.ol_mmr_urls = ol_mmr_urls
351         self.nosync = nosync
352
353         self.slapdconf          = os.path.join(self.ldapdir, "slapd.conf")
354         self.modulesconf        = os.path.join(self.ldapdir, "modules.conf")
355         self.memberofconf       = os.path.join(self.ldapdir, "memberof.conf")
356         self.olmmrserveridsconf = os.path.join(self.ldapdir, "mmr_serverids.conf")
357         self.olmmrsyncreplconf  = os.path.join(self.ldapdir, "mmr_syncrepl.conf")
358         self.olcdir             = os.path.join(self.ldapdir, "slapd.d")
359         self.olcseedldif        = os.path.join(self.ldapdir, "olc_seed.ldif")
360
361         self.schema = Schema(self.domainsid,
362                              schemadn=self.names.schemadn, files=[
363                 setup_path("schema_samba4.ldif")])
364
365     def setup_db_dir(self, dbdir):
366         """Create a database directory.
367
368         :param dbdir: Database directory.
369         """
370         if not os.path.exists(dbdir):
371             os.makedirs(dbdir, 0700)
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_domaindns_config = ""
416         mmr_syncrepl_forestdns_config = ""
417         mmr_syncrepl_user_config = ""
418         mmr_pass = ""
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_domaindns_config += read_and_sub_file(
458                     setup_path("mmr_syncrepl.conf"), {
459                         "RID" : str(rid),
460                         "MMRDN": "dc=DomainDNSZones," + self.names.domaindn,
461                         "LDAPSERVER" : url,
462                         "MMR_PASSWORD": mmr_pass})
463
464                 rid = rid + 1
465                 mmr_syncrepl_forestdns_config += read_and_sub_file(
466                     setup_path("mmr_syncrepl.conf"), {
467                         "RID" : str(rid),
468                         "MMRDN": "dc=ForestDNSZones," + self.names.domaindn,
469                         "LDAPSERVER" : url,
470                         "MMR_PASSWORD": mmr_pass})
471
472                 rid = rid + 1
473                 mmr_syncrepl_user_config += read_and_sub_file(
474                     setup_path("mmr_syncrepl.conf"), {
475                         "RID" : str(rid),
476                         "MMRDN": self.names.domaindn,
477                         "LDAPSERVER" : url,
478                         "MMR_PASSWORD": mmr_pass })
479         # OpenLDAP cn=config initialisation
480         olc_syncrepl_config = ""
481         olc_mmr_config = ""
482         # if mmr = yes, generate cn=config-replication directives
483         # and olc_seed.lif for the other mmr-servers
484         if self.ol_mmr_urls is not None:
485             serverid = 0
486             olc_serverids_config = ""
487             olc_syncrepl_seed_config = ""
488             olc_mmr_config += read_and_sub_file(
489                 setup_path("olc_mmr.conf"), {})
490             rid = 500
491             for url in url_list:
492                 serverid = serverid + 1
493                 olc_serverids_config += read_and_sub_file(
494                     setup_path("olc_serverid.conf"), {
495                         "SERVERID" : str(serverid), "LDAPSERVER" : url })
496
497                 rid = rid + 1
498                 olc_syncrepl_config += read_and_sub_file(
499                     setup_path("olc_syncrepl.conf"), {
500                         "RID" : str(rid), "LDAPSERVER" : url,
501                         "MMR_PASSWORD": mmr_pass})
502
503                 olc_syncrepl_seed_config += read_and_sub_file(
504                     setup_path("olc_syncrepl_seed.conf"), {
505                         "RID" : str(rid), "LDAPSERVER" : url})
506
507             setup_file(setup_path("olc_seed.ldif"), self.olcseedldif,
508                        {"OLC_SERVER_ID_CONF": olc_serverids_config,
509                         "OLC_PW": self.ldapadminpass,
510                         "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
511         # end olc
512
513         setup_file(setup_path("slapd.conf"), self.slapdconf,
514                    {"DNSDOMAIN": self.names.dnsdomain,
515                     "LDAPDIR": self.ldapdir,
516                     "DOMAINDN": self.names.domaindn,
517                     "CONFIGDN": self.names.configdn,
518                     "SCHEMADN": self.names.schemadn,
519                     "MEMBEROF_CONFIG": memberof_config,
520                     "MIRRORMODE": mmr_on_config,
521                     "REPLICATOR_ACL": mmr_replicator_acl,
522                     "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
523                     "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
524                     "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
525                     "MMR_SYNCREPL_DOMAINDNS_CONFIG": mmr_syncrepl_domaindns_config,
526                     "MMR_SYNCREPL_FORESTDNS_CONFIG": mmr_syncrepl_forestdns_config,
527                     "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
528                     "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
529                     "OLC_MMR_CONFIG": olc_mmr_config,
530                     "REFINT_CONFIG": refint_config,
531                     "INDEX_CONFIG": index_config,
532                     "ADMIN_UID": str(os.getuid()),
533                     "NOSYNC": nosync_config,})
534
535         self.setup_db_dir(os.path.join(self.ldapdir, "db", "forestdns"))
536         self.setup_db_dir(os.path.join(self.ldapdir, "db", "domaindns"))
537         self.setup_db_dir(os.path.join(self.ldapdir, "db", "user"))
538         self.setup_db_dir(os.path.join(self.ldapdir, "db", "config"))
539         self.setup_db_dir(os.path.join(self.ldapdir, "db", "schema"))
540         self.setup_db_dir(os.path.join(self.ldapdir, "db", "samba"))
541
542         if self.ol_mmr_urls is not None:
543             mmr = ""
544         else:
545             mmr = "#"
546
547         cn_samba = read_and_sub_file(
548                     setup_path("cn=samba.ldif"),
549                             { "LDAPADMINPASS": self.ldapadminpass,
550                            "MMR_PASSWORD": mmr_pass,
551                            "MMR": mmr })
552
553         mapping = "schema-map-openldap-2.3"
554         backend_schema = "backend-schema.schema"
555
556         f = open(setup_path(mapping), 'r')
557         try:
558             backend_schema_data = self.schema.convert_to_openldap(
559                     "openldap", f.read())
560         finally:
561             f.close()
562         assert backend_schema_data is not None
563         f = open(os.path.join(self.ldapdir, backend_schema), 'w')
564         try:
565             f.write(backend_schema_data)
566         finally:
567             f.close()
568
569         # now we generate the needed strings to start slapd automatically,
570         if self.ldap_backend_extra_port is not None:
571             # When we use MMR, we can't use 0.0.0.0 as it uses the name
572             # specified there as part of it's clue as to it's own name,
573             # and not to replicate to itself
574             if self.ol_mmr_urls is None:
575                 server_port_string = "ldap://0.0.0.0:%d" % self.ldap_backend_extra_port
576             else:
577                 server_port_string = "ldap://%s.%s:%d" (self.names.hostname,
578                     self.names.dnsdomain, self.ldap_backend_extra_port)
579         else:
580             server_port_string = ""
581
582         # Prepare the 'result' information - the commands to return in
583         # particular
584         self.slapd_provision_command = [self.slapd_path, "-F" + self.olcdir,
585             "-h"]
586
587         # copy this command so we have two version, one with -d0 and only
588         # ldapi (or the forced ldap_uri), and one with all the listen commands
589         self.slapd_command = list(self.slapd_provision_command)
590
591         self.slapd_provision_command.extend([self.ldap_uri, "-d0"])
592         uris = self.ldap_uri
593         if server_port_string is not "":
594             uris = uris + " " + server_port_string
595
596         self.slapd_command.append(uris)
597
598         # Set the username - done here because Fedora DS still uses the admin
599         # DN and simple bind
600         self.credentials.set_username("samba-admin")
601
602         # Wipe the old sam.ldb databases away
603         shutil.rmtree(self.olcdir, True)
604         os.makedirs(self.olcdir, 0770)
605
606         # If we were just looking for crashes up to this point, it's a
607         # good time to exit before we realise we don't have OpenLDAP on
608         # this system
609         if self.ldap_dryrun_mode:
610             sys.exit(0)
611
612         slapd_cmd = [self.slapd_path, "-Ttest", "-n", "0", "-f",
613                          self.slapdconf, "-F", self.olcdir]
614         retcode = subprocess.call(slapd_cmd, close_fds=True, shell=False)
615
616         if retcode != 0:
617             self.logger.error("conversion from slapd.conf to cn=config failed slapd started with: %s" %  "\'" + "\' \'".join(slapd_cmd) + "\'")
618             raise ProvisioningError("conversion from slapd.conf to cn=config failed")
619
620         if not os.path.exists(os.path.join(self.olcdir, "cn=config.ldif")):
621             raise ProvisioningError("conversion from slapd.conf to cn=config failed")
622
623         # Don't confuse the admin by leaving the slapd.conf around
624         os.remove(self.slapdconf)
625
626         cn_samba_cmd = [self.slapd_path, "-Tadd", "-b", "cn=samba", "-F", self.olcdir]
627         p = subprocess.Popen(cn_samba_cmd, stdin=subprocess.PIPE, shell=False)
628         p.stdin.write(cn_samba)
629         p.communicate()
630
631
632 class FDSBackend(LDAPBackend):
633
634     def __init__(self, backend_type, paths=None, lp=None,
635             credentials=None, names=None, logger=None, domainsid=None,
636             schema=None, hostname=None, ldapadminpass=None, slapd_path=None,
637             ldap_backend_extra_port=None, ldap_dryrun_mode=False, root=None,
638             setup_ds_path=None):
639
640         from samba.provision import setup_path
641
642         super(FDSBackend, self).__init__(backend_type=backend_type,
643                 paths=paths, lp=lp,
644                 credentials=credentials, names=names, logger=logger,
645                 domainsid=domainsid, schema=schema, hostname=hostname,
646                 ldapadminpass=ldapadminpass, slapd_path=slapd_path,
647                 ldap_backend_extra_port=ldap_backend_extra_port,
648                 ldap_backend_forced_uri=ldap_backend_forced_uri,
649                 ldap_dryrun_mode=ldap_dryrun_mode)
650
651         self.root = root
652         self.setup_ds_path = setup_ds_path
653         self.ldap_instance = self.names.netbiosname.lower()
654
655         self.sambadn = "CN=Samba"
656
657         self.fedoradsinf = os.path.join(self.ldapdir, "fedorads.inf")
658         self.partitions_ldif = os.path.join(self.ldapdir,
659             "fedorads-partitions.ldif")
660         self.sasl_ldif = os.path.join(self.ldapdir, "fedorads-sasl.ldif")
661         self.dna_ldif = os.path.join(self.ldapdir, "fedorads-dna.ldif")
662         self.pam_ldif = os.path.join(self.ldapdir, "fedorads-pam.ldif")
663         self.refint_ldif = os.path.join(self.ldapdir, "fedorads-refint.ldif")
664         self.linked_attrs_ldif = os.path.join(self.ldapdir,
665             "fedorads-linked-attributes.ldif")
666         self.index_ldif = os.path.join(self.ldapdir, "fedorads-index.ldif")
667         self.samba_ldif = os.path.join(self.ldapdir, "fedorads-samba.ldif")
668
669         self.samba3_schema = setup_path(
670             "../../examples/LDAP/samba.schema")
671         self.samba3_ldif = os.path.join(self.ldapdir, "samba3.ldif")
672
673         self.retcode = subprocess.call(["bin/oLschema2ldif",
674                 "-I", self.samba3_schema,
675                 "-O", self.samba3_ldif,
676                 "-b", self.names.domaindn],
677                 close_fds=True, shell=False)
678
679         if self.retcode != 0:
680             raise Exception("Unable to convert Samba 3 schema.")
681
682         self.schema = Schema(
683                 self.domainsid,
684                 schemadn=self.names.schemadn,
685                 files=[setup_path("schema_samba4.ldif"), self.samba3_ldif],
686                 additional_prefixmap=["1000:1.3.6.1.4.1.7165.2.1",
687                                       "1001:1.3.6.1.4.1.7165.2.2"])
688
689     def provision(self):
690         from samba.provision import ProvisioningError, setup_path
691         if self.ldap_backend_extra_port is not None:
692             serverport = "ServerPort=%d" % self.ldap_backend_extra_port
693         else:
694             serverport = ""
695
696         setup_file(setup_path("fedorads.inf"), self.fedoradsinf,
697                    {"ROOT": self.root,
698                     "HOSTNAME": self.hostname,
699                     "DNSDOMAIN": self.names.dnsdomain,
700                     "LDAPDIR": self.ldapdir,
701                     "DOMAINDN": self.names.domaindn,
702                     "LDAP_INSTANCE": self.ldap_instance,
703                     "LDAPMANAGERDN": self.names.ldapmanagerdn,
704                     "LDAPMANAGERPASS": self.ldapadminpass,
705                     "SERVERPORT": serverport})
706
707         setup_file(setup_path("fedorads-partitions.ldif"),
708             self.partitions_ldif,
709                    {"CONFIGDN": self.names.configdn,
710                     "SCHEMADN": self.names.schemadn,
711                     "SAMBADN": self.sambadn,
712                     })
713
714         setup_file(setup_path("fedorads-sasl.ldif"), self.sasl_ldif,
715                    {"SAMBADN": self.sambadn,
716                     })
717
718         setup_file(setup_path("fedorads-dna.ldif"), self.dna_ldif,
719                    {"DOMAINDN": self.names.domaindn,
720                     "SAMBADN": self.sambadn,
721                     "DOMAINSID": str(self.domainsid),
722                     })
723
724         setup_file(setup_path("fedorads-pam.ldif"), self.pam_ldif)
725
726         lnkattr = self.schema.linked_attributes()
727
728         f = open(setup_path("fedorads-refint-delete.ldif"), 'r')
729         try:
730             refint_config = f.read()
731         finally:
732             f.close()
733         memberof_config = ""
734         index_config = ""
735         argnum = 3
736
737         for attr in lnkattr.keys():
738             if lnkattr[attr] is not None:
739                 refint_config += read_and_sub_file(
740                     setup_path("fedorads-refint-add.ldif"),
741                          { "ARG_NUMBER" : str(argnum),
742                            "LINK_ATTR" : attr })
743                 memberof_config += read_and_sub_file(
744                     setup_path("fedorads-linked-attributes.ldif"),
745                          { "MEMBER_ATTR" : attr,
746                            "MEMBEROF_ATTR" : lnkattr[attr] })
747                 index_config += read_and_sub_file(
748                     setup_path("fedorads-index.ldif"), { "ATTR" : attr })
749                 argnum += 1
750
751         f = open(self.refint_ldif, 'w')
752         try:
753             f.write(refint_config)
754         finally:
755             f.close()
756         f = open(self.linked_attrs_ldif, 'w')
757         try:
758             f.write(memberof_config)
759         finally:
760             f.close()
761
762         attrs = ["lDAPDisplayName"]
763         res = self.schema.ldb.search(expression="(&(objectclass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=1))", base=self.names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
764
765         for i in range (0, len(res)):
766             attr = res[i]["lDAPDisplayName"][0]
767
768             if attr == "objectGUID":
769                 attr = "nsUniqueId"
770
771             index_config += read_and_sub_file(
772                 setup_path("fedorads-index.ldif"), { "ATTR" : attr })
773
774         f = open(self.index_ldif, 'w')
775         try:
776             f.write(index_config)
777         finally:
778             f.close()
779
780         setup_file(setup_path("fedorads-samba.ldif"), self.samba_ldif, {
781             "SAMBADN": self.sambadn,
782             "LDAPADMINPASS": self.ldapadminpass
783             })
784
785         mapping = "schema-map-fedora-ds-1.0"
786         backend_schema = "99_ad.ldif"
787
788         # Build a schema file in Fedora DS format
789         f = open(setup_path(mapping), 'r')
790         try:
791             backend_schema_data = self.schema.convert_to_openldap("fedora-ds",
792                 f.read())
793         finally:
794             f.close()
795         assert backend_schema_data is not None
796         f = open(os.path.join(self.ldapdir, backend_schema), 'w')
797         try:
798             f.write(backend_schema_data)
799         finally:
800             f.close()
801
802         self.credentials.set_bind_dn(self.names.ldapmanagerdn)
803
804         # Destory the target directory, or else setup-ds.pl will complain
805         fedora_ds_dir = os.path.join(self.ldapdir,
806             "slapd-" + self.ldap_instance)
807         shutil.rmtree(fedora_ds_dir, True)
808
809         self.slapd_provision_command = [self.slapd_path, "-D", fedora_ds_dir,
810                 "-i", self.slapd_pid]
811         # In the 'provision' command line, stay in the foreground so we can
812         # easily kill it
813         self.slapd_provision_command.append("-d0")
814
815         #the command for the final run is the normal script
816         self.slapd_command = [os.path.join(self.ldapdir,
817             "slapd-" + self.ldap_instance, "start-slapd")]
818
819         # If we were just looking for crashes up to this point, it's a
820         # good time to exit before we realise we don't have Fedora DS on
821         if self.ldap_dryrun_mode:
822             sys.exit(0)
823
824         # Try to print helpful messages when the user has not specified the
825         # path to the setup-ds tool
826         if self.setup_ds_path is None:
827             raise ProvisioningError("Fedora DS LDAP-Backend must be setup with path to setup-ds, e.g. --setup-ds-path=\"/usr/sbin/setup-ds.pl\"!")
828         if not os.path.exists(self.setup_ds_path):
829             self.logger.warning("Path (%s) to slapd does not exist!",
830                 self.setup_ds_path)
831
832         # Run the Fedora DS setup utility
833         retcode = subprocess.call([self.setup_ds_path, "--silent", "--file",
834             self.fedoradsinf], close_fds=True, shell=False)
835         if retcode != 0:
836             raise ProvisioningError("setup-ds failed")
837
838         # Load samba-admin
839         retcode = subprocess.call([
840             os.path.join(self.ldapdir, "slapd-" + self.ldap_instance, "ldif2db"), "-s", self.sambadn, "-i", self.samba_ldif],
841             close_fds=True, shell=False)
842         if retcode != 0:
843             raise ProvisioningError("ldif2db failed")
844
845     def post_setup(self):
846         ldapi_db = Ldb(self.ldap_uri, credentials=self.credentials)
847
848         # configure in-directory access control on Fedora DS via the aci
849         # attribute (over a direct ldapi:// socket)
850         aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % self.sambadn
851
852         m = ldb.Message()
853         m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci")
854
855         for dnstring in (self.names.domaindn, self.names.configdn,
856                          self.names.schemadn):
857             m.dn = ldb.Dn(ldapi_db, dnstring)
858             ldapi_db.modify(m)
859         return LDAPBackendResult(self.credentials, self.slapd_command_escaped,
860             self.ldapdir)