af7f07f752526465b69eb24c7940197e68937550
[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, 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         self.credentials.set_forced_sasl_mech("EXTERNAL")
259
260         self.secrets_credentials = Credentials()
261         self.secrets_credentials.guess(self.lp)
262         # Kerberos to an ldapi:// backend makes no sense
263         self.secrets_credentials.set_kerberos_state(DONT_USE_KERBEROS)
264         self.secrets_credentials.set_username("samba-admin")
265         self.secrets_credentials.set_password(self.ldapadminpass)
266         self.secrets_credentials.set_forced_sasl_mech("EXTERNAL")
267
268         self.provision()
269
270     def provision(self):
271         pass
272
273     def start(self):
274         from samba.provision import ProvisioningError
275         self.slapd_command_escaped = "\'" + "\' \'".join(self.slapd_command) + "\'"
276         ldap_backend_script = os.path.join(self.ldapdir, "ldap_backend_startup.sh")
277         f = open(ldap_backend_script, 'w')
278         try:
279             f.write("#!/bin/sh\n" + self.slapd_command_escaped + " $@\n")
280         finally:
281             f.close()
282
283         os.chmod(ldap_backend_script, 0755)
284
285         # Now start the slapd, so we can provision onto it.  We keep the
286         # subprocess context around, to kill this off at the successful
287         # end of the script
288         self.slapd = subprocess.Popen(self.slapd_provision_command,
289             close_fds=True, shell=False)
290
291         count = 0
292         while self.slapd.poll() is None:
293             # Wait until the socket appears
294             try:
295                 time.sleep(1)
296                 ldapi_db = Ldb(self.ldap_uri, lp=self.lp, credentials=self.credentials)
297                 ldapi_db.search(base="", scope=SCOPE_BASE,
298                     expression="(objectClass=OpenLDAProotDSE)")
299                 # If we have got here, then we must have a valid connection to
300                 # the LDAP server!
301                 return
302             except LdbError:
303                 count = count + 1
304
305                 if count > 15:
306                     self.logger.error("Could not connect to slapd started with: %s" %  "\'" + "\' \'".join(self.slapd_provision_command) + "\'")
307                     raise ProvisioningError("slapd never accepted a connection within 15 seconds of starting")
308
309         self.logger.error("Could not start slapd with: %s" % "\'" + "\' \'".join(self.slapd_provision_command) + "\'")
310         raise ProvisioningError("slapd died before we could make a connection to it")
311
312     def shutdown(self):
313         # if an LDAP backend is in use, terminate slapd after final provision
314         # and check its proper termination
315         if self.slapd.poll() is None:
316             # Kill the slapd
317             if getattr(self.slapd, "terminate", None) is not None:
318                 self.slapd.terminate()
319             else:
320                 # Older python versions don't have .terminate()
321                 import signal
322                 os.kill(self.slapd.pid, signal.SIGTERM)
323
324             # and now wait for it to die
325             self.slapd.communicate()
326
327     def post_setup(self):
328         return LDAPBackendResult(self.credentials, self.slapd_command_escaped,
329                     self.ldapdir)
330
331
332 class OpenLDAPBackend(LDAPBackend):
333
334     def __init__(self, backend_type, paths=None, lp=None,
335             credentials=None, names=None, logger=None, domainsid=None,
336             schema=None, hostname=None, ldapadminpass=None, slapd_path=None,
337             ldap_backend_extra_port=None, ldap_dryrun_mode=True,
338             ol_mmr_urls=None, nosync=False, ldap_backend_forced_uri=None):
339         from samba.provision import setup_path
340         super(OpenLDAPBackend, self).__init__( backend_type=backend_type,
341                 paths=paths, lp=lp,
342                 credentials=credentials, names=names, logger=logger,
343                 domainsid=domainsid, schema=schema, hostname=hostname,
344                 ldapadminpass=ldapadminpass, slapd_path=slapd_path,
345                 ldap_backend_extra_port=ldap_backend_extra_port,
346                 ldap_backend_forced_uri=ldap_backend_forced_uri,
347                 ldap_dryrun_mode=ldap_dryrun_mode)
348
349         self.ol_mmr_urls = ol_mmr_urls
350         self.nosync = nosync
351
352         self.slapdconf          = os.path.join(self.ldapdir, "slapd.conf")
353         self.modulesconf        = os.path.join(self.ldapdir, "modules.conf")
354         self.memberofconf       = os.path.join(self.ldapdir, "memberof.conf")
355         self.olmmrserveridsconf = os.path.join(self.ldapdir, "mmr_serverids.conf")
356         self.olmmrsyncreplconf  = os.path.join(self.ldapdir, "mmr_syncrepl.conf")
357         self.olcdir             = os.path.join(self.ldapdir, "slapd.d")
358         self.olcseedldif        = os.path.join(self.ldapdir, "olc_seed.ldif")
359
360         self.schema = Schema(self.domainsid,
361                              schemadn=self.names.schemadn, files=[
362                 setup_path("schema_samba4.ldif")])
363
364     def setup_db_dir(self, dbdir):
365         """Create a database directory.
366
367         :param dbdir: Database directory.
368         """
369         if not os.path.exists(dbdir):
370             os.makedirs(dbdir, 0700)
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_domaindns_config = ""
415         mmr_syncrepl_forestdns_config = ""
416         mmr_syncrepl_user_config = ""
417         mmr_pass = ""
418
419         if self.ol_mmr_urls is not None:
420             # For now, make these equal
421             mmr_pass = self.ldapadminpass
422
423             url_list = filter(None,self.ol_mmr_urls.split(','))
424             for url in url_list:
425                 self.logger.info("Using LDAP-URL: "+url)
426             if len(url_list) == 1:
427                 raise ProvisioningError("At least 2 LDAP-URLs needed for MMR!")
428
429             mmr_on_config = "MirrorMode On"
430             mmr_replicator_acl = "  by dn=cn=replicator,cn=samba read"
431             serverid = 0
432             for url in url_list:
433                 serverid = serverid + 1
434                 mmr_serverids_config += read_and_sub_file(
435                     setup_path("mmr_serverids.conf"), {
436                         "SERVERID": str(serverid),
437                         "LDAPSERVER": url })
438                 rid = serverid * 10
439                 rid = rid + 1
440                 mmr_syncrepl_schema_config += read_and_sub_file(
441                         setup_path("mmr_syncrepl.conf"), {
442                             "RID" : str(rid),
443                            "MMRDN": self.names.schemadn,
444                            "LDAPSERVER" : url,
445                            "MMR_PASSWORD": mmr_pass})
446
447                 rid = rid + 1
448                 mmr_syncrepl_config_config += read_and_sub_file(
449                     setup_path("mmr_syncrepl.conf"), {
450                         "RID" : str(rid),
451                         "MMRDN": self.names.configdn,
452                         "LDAPSERVER" : url,
453                         "MMR_PASSWORD": mmr_pass})
454
455                 rid = rid + 1
456                 mmr_syncrepl_domaindns_config += read_and_sub_file(
457                     setup_path("mmr_syncrepl.conf"), {
458                         "RID" : str(rid),
459                         "MMRDN": "dc=DomainDNSZones," + self.names.domaindn,
460                         "LDAPSERVER" : url,
461                         "MMR_PASSWORD": mmr_pass})
462
463                 rid = rid + 1
464                 mmr_syncrepl_forestdns_config += read_and_sub_file(
465                     setup_path("mmr_syncrepl.conf"), {
466                         "RID" : str(rid),
467                         "MMRDN": "dc=ForestDNSZones," + self.names.domaindn,
468                         "LDAPSERVER" : url,
469                         "MMR_PASSWORD": mmr_pass})
470
471                 rid = rid + 1
472                 mmr_syncrepl_user_config += read_and_sub_file(
473                     setup_path("mmr_syncrepl.conf"), {
474                         "RID" : str(rid),
475                         "MMRDN": self.names.domaindn,
476                         "LDAPSERVER" : url,
477                         "MMR_PASSWORD": mmr_pass })
478         # OpenLDAP cn=config initialisation
479         olc_syncrepl_config = ""
480         olc_mmr_config = ""
481         # if mmr = yes, generate cn=config-replication directives
482         # and olc_seed.lif for the other mmr-servers
483         if self.ol_mmr_urls is not None:
484             serverid = 0
485             olc_serverids_config = ""
486             olc_syncrepl_seed_config = ""
487             olc_mmr_config += read_and_sub_file(
488                 setup_path("olc_mmr.conf"), {})
489             rid = 500
490             for url in url_list:
491                 serverid = serverid + 1
492                 olc_serverids_config += read_and_sub_file(
493                     setup_path("olc_serverid.conf"), {
494                         "SERVERID" : str(serverid), "LDAPSERVER" : url })
495
496                 rid = rid + 1
497                 olc_syncrepl_config += read_and_sub_file(
498                     setup_path("olc_syncrepl.conf"), {
499                         "RID" : str(rid), "LDAPSERVER" : url,
500                         "MMR_PASSWORD": mmr_pass})
501
502                 olc_syncrepl_seed_config += read_and_sub_file(
503                     setup_path("olc_syncrepl_seed.conf"), {
504                         "RID" : str(rid), "LDAPSERVER" : url})
505
506             setup_file(setup_path("olc_seed.ldif"), self.olcseedldif,
507                        {"OLC_SERVER_ID_CONF": olc_serverids_config,
508                         "OLC_PW": self.ldapadminpass,
509                         "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
510         # end olc
511
512         setup_file(setup_path("slapd.conf"), self.slapdconf,
513                    {"DNSDOMAIN": self.names.dnsdomain,
514                     "LDAPDIR": self.ldapdir,
515                     "DOMAINDN": self.names.domaindn,
516                     "CONFIGDN": self.names.configdn,
517                     "SCHEMADN": self.names.schemadn,
518                     "MEMBEROF_CONFIG": memberof_config,
519                     "MIRRORMODE": mmr_on_config,
520                     "REPLICATOR_ACL": mmr_replicator_acl,
521                     "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
522                     "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
523                     "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
524                     "MMR_SYNCREPL_DOMAINDNS_CONFIG": mmr_syncrepl_domaindns_config,
525                     "MMR_SYNCREPL_FORESTDNS_CONFIG": mmr_syncrepl_forestdns_config,
526                     "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
527                     "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
528                     "OLC_MMR_CONFIG": olc_mmr_config,
529                     "REFINT_CONFIG": refint_config,
530                     "INDEX_CONFIG": index_config,
531                     "ADMIN_UID": str(os.getuid()),
532                     "NOSYNC": nosync_config,})
533
534         self.setup_db_dir(os.path.join(self.ldapdir, "db", "forestdns"))
535         self.setup_db_dir(os.path.join(self.ldapdir, "db", "domaindns"))
536         self.setup_db_dir(os.path.join(self.ldapdir, "db", "user"))
537         self.setup_db_dir(os.path.join(self.ldapdir, "db", "config"))
538         self.setup_db_dir(os.path.join(self.ldapdir, "db", "schema"))
539         self.setup_db_dir(os.path.join(self.ldapdir, "db", "samba"))
540
541         if self.ol_mmr_urls is not None:
542             mmr = ""
543         else:
544             mmr = "#"
545
546         cn_samba = read_and_sub_file(
547                     setup_path("cn=samba.ldif"),
548                             { "LDAPADMINPASS": self.ldapadminpass,
549                            "MMR_PASSWORD": mmr_pass,
550                            "MMR": mmr })
551
552         mapping = "schema-map-openldap-2.3"
553         backend_schema = "backend-schema.schema"
554
555         f = open(setup_path(mapping), 'r')
556         try:
557             backend_schema_data = self.schema.convert_to_openldap(
558                     "openldap", f.read())
559         finally:
560             f.close()
561         assert backend_schema_data is not None
562         f = open(os.path.join(self.ldapdir, backend_schema), 'w')
563         try:
564             f.write(backend_schema_data)
565         finally:
566             f.close()
567
568         # now we generate the needed strings to start slapd automatically,
569         if self.ldap_backend_extra_port is not None:
570             # When we use MMR, we can't use 0.0.0.0 as it uses the name
571             # specified there as part of it's clue as to it's own name,
572             # and not to replicate to itself
573             if self.ol_mmr_urls is None:
574                 server_port_string = "ldap://0.0.0.0:%d" % self.ldap_backend_extra_port
575             else:
576                 server_port_string = "ldap://%s.%s:%d" (self.names.hostname,
577                     self.names.dnsdomain, self.ldap_backend_extra_port)
578         else:
579             server_port_string = ""
580
581         # Prepare the 'result' information - the commands to return in
582         # particular
583         self.slapd_provision_command = [self.slapd_path, "-F" + self.olcdir,
584             "-h"]
585
586         # copy this command so we have two version, one with -d0 and only
587         # ldapi (or the forced ldap_uri), and one with all the listen commands
588         self.slapd_command = list(self.slapd_provision_command)
589
590         self.slapd_provision_command.extend([self.ldap_uri, "-d0"])
591
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=True, 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)