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