python provision: FDSBackend takes forced uri
[samba.git] / python / samba / provision / backend.py
1 #
2 # Unix SMB/CIFS implementation.
3 # backend code for provisioning a Samba4 server
4
5 # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007-2008
6 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2008-2009
7 # Copyright (C) Oliver Liebel <oliver@itc.li> 2008-2009
8 #
9 # Based on the original in EJS:
10 # Copyright (C) Andrew Tridgell <tridge@samba.org> 2005
11 #
12 # This program is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 3 of the License, or
15 # (at your option) any later version.
16 #
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 # GNU General Public License for more details.
21 #
22 # You should have received a copy of the GNU General Public License
23 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
24 #
25
26 """Functions for setting up a Samba configuration (LDB and LDAP backends)."""
27
28 from base64 import b64encode
29 import errno
30 import ldb
31 import os
32 import sys
33 import uuid
34 import time
35 import shutil
36 import subprocess
37 import urllib
38
39 from ldb import SCOPE_BASE, SCOPE_ONELEVEL, LdbError, timestring
40
41 from samba import Ldb, read_and_sub_file, setup_file
42 from samba.credentials import Credentials, DONT_USE_KERBEROS
43 from samba.schema import Schema
44
45
46 class SlapdAlreadyRunning(Exception):
47
48     def __init__(self, uri):
49         self.ldapi_uri = uri
50         super(SlapdAlreadyRunning, self).__init__("Another slapd Instance "
51             "seems already running on this host, listening to %s." %
52             self.ldapi_uri)
53
54
55 class BackendResult(object):
56
57     def report_logger(self, logger):
58         """Rerport this result to a particular logger.
59
60         """
61         raise NotImplementedError(self.report_logger)
62
63
64 class LDAPBackendResult(BackendResult):
65
66     def __init__(self, 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(
375             setup_path("refint.conf"), {"LINK_ATTRS": refint_attributes})
376
377         attrs = ["linkID", "lDAPDisplayName"]
378         res = self.schema.ldb.search(
379             expression="(&(objectclass=attributeSchema)"
380                        "(searchFlags:1.2.840.113556.1.4.803:=1))",
381             base=self.names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
382         index_config = ""
383         for i in range(0, len(res)):
384             index_attr = res[i]["lDAPDisplayName"][0]
385             if index_attr == "objectGUID":
386                 index_attr = "entryUUID"
387
388             index_config += "index " + index_attr + " eq\n"
389
390         # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
391         mmr_on_config = ""
392         mmr_replicator_acl = ""
393         mmr_serverids_config = ""
394         mmr_syncrepl_schema_config = ""
395         mmr_syncrepl_config_config = ""
396         mmr_syncrepl_domaindns_config = ""
397         mmr_syncrepl_forestdns_config = ""
398         mmr_syncrepl_user_config = ""
399         mmr_pass = ""
400
401         if self.ol_mmr_urls is not None:
402             # For now, make these equal
403             mmr_pass = self.ldapadminpass
404
405             url_list = filter(None,self.ol_mmr_urls.split(','))
406             for url in url_list:
407                 self.logger.info("Using LDAP-URL: "+url)
408             if len(url_list) == 1:
409                 raise ProvisioningError("At least 2 LDAP-URLs needed for MMR!")
410
411             mmr_on_config = "MirrorMode On"
412             mmr_replicator_acl = "  by dn=cn=replicator,cn=samba read"
413             serverid = 0
414             for url in url_list:
415                 serverid = serverid + 1
416                 mmr_serverids_config += read_and_sub_file(
417                     setup_path("mmr_serverids.conf"), {
418                         "SERVERID": str(serverid),
419                         "LDAPSERVER": url })
420                 rid = serverid * 10
421                 rid = rid + 1
422                 mmr_syncrepl_schema_config += read_and_sub_file(
423                         setup_path("mmr_syncrepl.conf"), {
424                             "RID" : str(rid),
425                            "MMRDN": self.names.schemadn,
426                            "LDAPSERVER" : url,
427                            "MMR_PASSWORD": mmr_pass})
428
429                 rid = rid + 1
430                 mmr_syncrepl_config_config += read_and_sub_file(
431                     setup_path("mmr_syncrepl.conf"), {
432                         "RID" : str(rid),
433                         "MMRDN": self.names.configdn,
434                         "LDAPSERVER" : url,
435                         "MMR_PASSWORD": mmr_pass})
436
437                 rid = rid + 1
438                 mmr_syncrepl_domaindns_config += read_and_sub_file(
439                     setup_path("mmr_syncrepl.conf"), {
440                         "RID" : str(rid),
441                         "MMRDN": "dc=DomainDNSZones," + self.names.domaindn,
442                         "LDAPSERVER" : url,
443                         "MMR_PASSWORD": mmr_pass})
444
445                 rid = rid + 1
446                 mmr_syncrepl_forestdns_config += read_and_sub_file(
447                     setup_path("mmr_syncrepl.conf"), {
448                         "RID" : str(rid),
449                         "MMRDN": "dc=ForestDNSZones," + self.names.domaindn,
450                         "LDAPSERVER" : url,
451                         "MMR_PASSWORD": mmr_pass})
452
453                 rid = rid + 1
454                 mmr_syncrepl_user_config += read_and_sub_file(
455                     setup_path("mmr_syncrepl.conf"), {
456                         "RID" : str(rid),
457                         "MMRDN": self.names.domaindn,
458                         "LDAPSERVER" : url,
459                         "MMR_PASSWORD": mmr_pass })
460         # OpenLDAP cn=config initialisation
461         olc_syncrepl_config = ""
462         olc_mmr_config = ""
463         # if mmr = yes, generate cn=config-replication directives
464         # and olc_seed.lif for the other mmr-servers
465         if self.ol_mmr_urls is not None:
466             serverid = 0
467             olc_serverids_config = ""
468             olc_syncrepl_seed_config = ""
469             olc_mmr_config += read_and_sub_file(
470                 setup_path("olc_mmr.conf"), {})
471             rid = 500
472             for url in url_list:
473                 serverid = serverid + 1
474                 olc_serverids_config += read_and_sub_file(
475                     setup_path("olc_serverid.conf"), {
476                         "SERVERID" : str(serverid), "LDAPSERVER" : url })
477
478                 rid = rid + 1
479                 olc_syncrepl_config += read_and_sub_file(
480                     setup_path("olc_syncrepl.conf"), {
481                         "RID" : str(rid), "LDAPSERVER" : url,
482                         "MMR_PASSWORD": mmr_pass})
483
484                 olc_syncrepl_seed_config += read_and_sub_file(
485                     setup_path("olc_syncrepl_seed.conf"), {
486                         "RID" : str(rid), "LDAPSERVER" : url})
487
488             setup_file(setup_path("olc_seed.ldif"), self.olcseedldif,
489                        {"OLC_SERVER_ID_CONF": olc_serverids_config,
490                         "OLC_PW": self.ldapadminpass,
491                         "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
492         # end olc
493
494         setup_file(setup_path("slapd.conf"), self.slapdconf,
495                    {"DNSDOMAIN": self.names.dnsdomain,
496                     "LDAPDIR": self.ldapdir,
497                     "DOMAINDN": self.names.domaindn,
498                     "CONFIGDN": self.names.configdn,
499                     "SCHEMADN": self.names.schemadn,
500                     "MEMBEROF_CONFIG": memberof_config,
501                     "MIRRORMODE": mmr_on_config,
502                     "REPLICATOR_ACL": mmr_replicator_acl,
503                     "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
504                     "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
505                     "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
506                     "MMR_SYNCREPL_DOMAINDNS_CONFIG": mmr_syncrepl_domaindns_config,
507                     "MMR_SYNCREPL_FORESTDNS_CONFIG": mmr_syncrepl_forestdns_config,
508                     "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
509                     "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
510                     "OLC_MMR_CONFIG": olc_mmr_config,
511                     "REFINT_CONFIG": refint_config,
512                     "INDEX_CONFIG": index_config,
513                     "ADMIN_UID": str(os.getuid()),
514                     "NOSYNC": nosync_config,})
515
516         self.setup_db_dir(os.path.join(self.ldapdir, "db", "forestdns"))
517         self.setup_db_dir(os.path.join(self.ldapdir, "db", "domaindns"))
518         self.setup_db_dir(os.path.join(self.ldapdir, "db", "user"))
519         self.setup_db_dir(os.path.join(self.ldapdir, "db", "config"))
520         self.setup_db_dir(os.path.join(self.ldapdir, "db", "schema"))
521         self.setup_db_dir(os.path.join(self.ldapdir, "db", "samba"))
522
523         if self.ol_mmr_urls is not None:
524             mmr = ""
525         else:
526             mmr = "#"
527
528         cn_samba = read_and_sub_file(
529                     setup_path("cn=samba.ldif"),
530                             { "LDAPADMINPASS": self.ldapadminpass,
531                            "MMR_PASSWORD": mmr_pass,
532                            "MMR": mmr })
533
534         mapping = "schema-map-openldap-2.3"
535         backend_schema = "backend-schema.schema"
536
537         f = open(setup_path(mapping), 'r')
538         try:
539             backend_schema_data = self.schema.convert_to_openldap(
540                     "openldap", f.read())
541         finally:
542             f.close()
543         assert backend_schema_data is not None
544         f = open(os.path.join(self.ldapdir, backend_schema), 'w')
545         try:
546             f.write(backend_schema_data)
547         finally:
548             f.close()
549
550         # now we generate the needed strings to start slapd automatically,
551         if self.ldap_backend_extra_port is not None:
552             # When we use MMR, we can't use 0.0.0.0 as it uses the name
553             # specified there as part of it's clue as to it's own name,
554             # and not to replicate to itself
555             if self.ol_mmr_urls is None:
556                 server_port_string = "ldap://0.0.0.0:%d" % self.ldap_backend_extra_port
557             else:
558                 server_port_string = "ldap://%s.%s:%d" (self.names.hostname,
559                     self.names.dnsdomain, self.ldap_backend_extra_port)
560         else:
561             server_port_string = ""
562
563         # Prepare the 'result' information - the commands to return in
564         # particular
565         self.slapd_provision_command = [self.slapd_path, "-F" + self.olcdir,
566             "-h"]
567
568         # copy this command so we have two version, one with -d0 and only
569         # ldapi (or the forced ldap_uri), and one with all the listen commands
570         self.slapd_command = list(self.slapd_provision_command)
571
572         self.slapd_provision_command.extend([self.ldap_uri, "-d0"])
573         uris = self.ldap_uri
574         if server_port_string is not "":
575             uris = uris + " " + server_port_string
576
577         self.slapd_command.append(uris)
578
579         # Wipe the old sam.ldb databases away
580         shutil.rmtree(self.olcdir, True)
581         os.makedirs(self.olcdir, 0770)
582
583         # If we were just looking for crashes up to this point, it's a
584         # good time to exit before we realise we don't have OpenLDAP on
585         # this system
586         if self.ldap_dryrun_mode:
587             sys.exit(0)
588
589         slapd_cmd = [self.slapd_path, "-Ttest", "-n", "0", "-f",
590                          self.slapdconf, "-F", self.olcdir]
591         retcode = subprocess.call(slapd_cmd, close_fds=True, shell=False)
592
593         if retcode != 0:
594             self.logger.error("conversion from slapd.conf to cn=config failed slapd started with: %s" %  "\'" + "\' \'".join(slapd_cmd) + "\'")
595             raise ProvisioningError("conversion from slapd.conf to cn=config failed")
596
597         if not os.path.exists(os.path.join(self.olcdir, "cn=config.ldif")):
598             raise ProvisioningError("conversion from slapd.conf to cn=config failed")
599
600         # Don't confuse the admin by leaving the slapd.conf around
601         os.remove(self.slapdconf)
602
603         cn_samba_cmd = [self.slapd_path, "-Tadd", "-b", "cn=samba", "-F", self.olcdir]
604         p = subprocess.Popen(cn_samba_cmd, stdin=subprocess.PIPE, shell=False)
605         p.stdin.write(cn_samba)
606         p.communicate()
607
608
609 class FDSBackend(LDAPBackend):
610
611     def __init__(self, backend_type, paths=None, lp=None,
612                  names=None, logger=None, domainsid=None,
613                  schema=None, hostname=None, ldapadminpass=None,
614                  slapd_path=None, ldap_backend_extra_port=None,
615                  ldap_backend_forced_uri=None, ldap_dryrun_mode=False,
616                  root=None, setup_ds_path=None):
617
618         from samba.provision import setup_path
619
620         super(FDSBackend, self).__init__(backend_type=backend_type,
621                 paths=paths, lp=lp,
622                 names=names, logger=logger,
623                 domainsid=domainsid, schema=schema, hostname=hostname,
624                 ldapadminpass=ldapadminpass, slapd_path=slapd_path,
625                 ldap_backend_extra_port=ldap_backend_extra_port,
626                 ldap_backend_forced_uri=ldap_backend_forced_uri,
627                 ldap_dryrun_mode=ldap_dryrun_mode)
628
629         self.root = root
630         self.setup_ds_path = setup_ds_path
631         self.ldap_instance = self.names.netbiosname.lower()
632
633         self.sambadn = "CN=Samba"
634
635         self.fedoradsinf = os.path.join(self.ldapdir, "fedorads.inf")
636         self.partitions_ldif = os.path.join(self.ldapdir,
637             "fedorads-partitions.ldif")
638         self.sasl_ldif = os.path.join(self.ldapdir, "fedorads-sasl.ldif")
639         self.dna_ldif = os.path.join(self.ldapdir, "fedorads-dna.ldif")
640         self.pam_ldif = os.path.join(self.ldapdir, "fedorads-pam.ldif")
641         self.refint_ldif = os.path.join(self.ldapdir, "fedorads-refint.ldif")
642         self.linked_attrs_ldif = os.path.join(self.ldapdir,
643             "fedorads-linked-attributes.ldif")
644         self.index_ldif = os.path.join(self.ldapdir, "fedorads-index.ldif")
645         self.samba_ldif = os.path.join(self.ldapdir, "fedorads-samba.ldif")
646
647         self.samba3_schema = setup_path(
648             "../../examples/LDAP/samba.schema")
649         self.samba3_ldif = os.path.join(self.ldapdir, "samba3.ldif")
650
651         self.retcode = subprocess.call(["bin/oLschema2ldif",
652                 "-I", self.samba3_schema,
653                 "-O", self.samba3_ldif,
654                 "-b", self.names.domaindn],
655                 close_fds=True, shell=False)
656
657         if self.retcode != 0:
658             raise Exception("Unable to convert Samba 3 schema.")
659
660         self.schema = Schema(
661                 self.domainsid,
662                 schemadn=self.names.schemadn,
663                 files=[setup_path("schema_samba4.ldif"), self.samba3_ldif],
664                 additional_prefixmap=["1000:1.3.6.1.4.1.7165.2.1",
665                                       "1001:1.3.6.1.4.1.7165.2.2"])
666
667     def provision(self):
668         from samba.provision import ProvisioningError, setup_path
669         if self.ldap_backend_extra_port is not None:
670             serverport = "ServerPort=%d" % self.ldap_backend_extra_port
671         else:
672             serverport = ""
673
674         setup_file(setup_path("fedorads.inf"), self.fedoradsinf,
675                    {"ROOT": self.root,
676                     "HOSTNAME": self.hostname,
677                     "DNSDOMAIN": self.names.dnsdomain,
678                     "LDAPDIR": self.ldapdir,
679                     "DOMAINDN": self.names.domaindn,
680                     "LDAP_INSTANCE": self.ldap_instance,
681                     "LDAPMANAGERDN": self.names.ldapmanagerdn,
682                     "LDAPMANAGERPASS": self.ldapadminpass,
683                     "SERVERPORT": serverport})
684
685         setup_file(setup_path("fedorads-partitions.ldif"),
686             self.partitions_ldif,
687                    {"CONFIGDN": self.names.configdn,
688                     "SCHEMADN": self.names.schemadn,
689                     "SAMBADN": self.sambadn,
690                     })
691
692         setup_file(setup_path("fedorads-sasl.ldif"), self.sasl_ldif,
693                    {"SAMBADN": self.sambadn,
694                     })
695
696         setup_file(setup_path("fedorads-dna.ldif"), self.dna_ldif,
697                    {"DOMAINDN": self.names.domaindn,
698                     "SAMBADN": self.sambadn,
699                     "DOMAINSID": str(self.domainsid),
700                     })
701
702         setup_file(setup_path("fedorads-pam.ldif"), self.pam_ldif)
703
704         lnkattr = self.schema.linked_attributes()
705
706         f = open(setup_path("fedorads-refint-delete.ldif"), 'r')
707         try:
708             refint_config = f.read()
709         finally:
710             f.close()
711         memberof_config = ""
712         index_config = ""
713         argnum = 3
714
715         for attr in lnkattr.keys():
716             if lnkattr[attr] is not None:
717                 refint_config += read_and_sub_file(
718                     setup_path("fedorads-refint-add.ldif"),
719                          { "ARG_NUMBER" : str(argnum),
720                            "LINK_ATTR" : attr })
721                 memberof_config += read_and_sub_file(
722                     setup_path("fedorads-linked-attributes.ldif"),
723                          { "MEMBER_ATTR" : attr,
724                            "MEMBEROF_ATTR" : lnkattr[attr] })
725                 index_config += read_and_sub_file(
726                     setup_path("fedorads-index.ldif"), { "ATTR" : attr })
727                 argnum += 1
728
729         f = open(self.refint_ldif, 'w')
730         try:
731             f.write(refint_config)
732         finally:
733             f.close()
734         f = open(self.linked_attrs_ldif, 'w')
735         try:
736             f.write(memberof_config)
737         finally:
738             f.close()
739
740         attrs = ["lDAPDisplayName"]
741         res = self.schema.ldb.search(
742             expression="(&(objectclass=attributeSchema)"
743                        "(searchFlags:1.2.840.113556.1.4.803:=1))",
744             base=self.names.schemadn, scope=SCOPE_ONELEVEL, attrs=attrs)
745
746         for i in range(0, len(res)):
747             attr = res[i]["lDAPDisplayName"][0]
748
749             if attr == "objectGUID":
750                 attr = "nsUniqueId"
751
752             index_config += read_and_sub_file(
753                 setup_path("fedorads-index.ldif"), { "ATTR" : attr })
754
755         f = open(self.index_ldif, 'w')
756         try:
757             f.write(index_config)
758         finally:
759             f.close()
760
761         setup_file(setup_path("fedorads-samba.ldif"), self.samba_ldif, {
762             "SAMBADN": self.sambadn,
763             "LDAPADMINPASS": self.ldapadminpass
764             })
765
766         mapping = "schema-map-fedora-ds-1.0"
767         backend_schema = "99_ad.ldif"
768
769         # Build a schema file in Fedora DS format
770         f = open(setup_path(mapping), 'r')
771         try:
772             backend_schema_data = self.schema.convert_to_openldap("fedora-ds",
773                 f.read())
774         finally:
775             f.close()
776         assert backend_schema_data is not None
777         f = open(os.path.join(self.ldapdir, backend_schema), 'w')
778         try:
779             f.write(backend_schema_data)
780         finally:
781             f.close()
782
783         self.credentials.set_bind_dn(self.names.ldapmanagerdn)
784
785         # Destory the target directory, or else setup-ds.pl will complain
786         fedora_ds_dir = os.path.join(self.ldapdir,
787             "slapd-" + self.ldap_instance)
788         shutil.rmtree(fedora_ds_dir, True)
789
790         self.slapd_provision_command = [self.slapd_path, "-D", fedora_ds_dir,
791                 "-i", self.slapd_pid]
792         # In the 'provision' command line, stay in the foreground so we can
793         # easily kill it
794         self.slapd_provision_command.append("-d0")
795
796         #the command for the final run is the normal script
797         self.slapd_command = [os.path.join(self.ldapdir,
798             "slapd-" + self.ldap_instance, "start-slapd")]
799
800         # If we were just looking for crashes up to this point, it's a
801         # good time to exit before we realise we don't have Fedora DS on
802         if self.ldap_dryrun_mode:
803             sys.exit(0)
804
805         # Try to print helpful messages when the user has not specified the
806         # path to the setup-ds tool
807         if self.setup_ds_path is None:
808             raise ProvisioningError("Fedora DS LDAP-Backend must be setup with path to setup-ds, e.g. --setup-ds-path=\"/usr/sbin/setup-ds.pl\"!")
809         if not os.path.exists(self.setup_ds_path):
810             self.logger.warning("Path (%s) to slapd does not exist!",
811                 self.setup_ds_path)
812
813         # Run the Fedora DS setup utility
814         retcode = subprocess.call([self.setup_ds_path, "--silent", "--file",
815             self.fedoradsinf], close_fds=True, shell=False)
816         if retcode != 0:
817             raise ProvisioningError("setup-ds failed")
818
819         # Load samba-admin
820         retcode = subprocess.call([
821             os.path.join(self.ldapdir, "slapd-" + self.ldap_instance, "ldif2db"), "-s", self.sambadn, "-i", self.samba_ldif],
822             close_fds=True, shell=False)
823         if retcode != 0:
824             raise ProvisioningError("ldif2db failed")
825
826     def post_setup(self):
827         ldapi_db = Ldb(self.ldap_uri, credentials=self.credentials)
828
829         # configure in-directory access control on Fedora DS via the aci
830         # attribute (over a direct ldapi:// socket)
831         aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % self.sambadn
832
833         m = ldb.Message()
834         m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci")
835
836         for dnstring in (self.names.domaindn, self.names.configdn,
837                          self.names.schemadn):
838             m.dn = ldb.Dn(ldapi_db, dnstring)
839             ldapi_db.modify(m)
840         return LDAPBackendResult(self.credentials, self.slapd_command_escaped,
841             self.ldapdir)