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