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