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