f9dbba85f6f3fc539a0c526b6f0646b533ed0327
[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         from samba.provision import setup_path
575
576         super(FDSBackend, self).__init__(backend_type=backend_type,
577                 paths=paths, lp=lp,
578                 credentials=credentials, names=names, logger=logger,
579                 domainsid=domainsid, schema=schema, hostname=hostname,
580                 ldapadminpass=ldapadminpass, slapd_path=slapd_path,
581                 ldap_backend_extra_port=ldap_backend_extra_port,
582                 ldap_backend_forced_uri=ldap_backend_forced_uri,
583                 ldap_dryrun_mode=ldap_dryrun_mode)
584
585         self.root = root
586         self.setup_ds_path = setup_ds_path
587         self.ldap_instance = self.names.netbiosname.lower()
588
589         self.sambadn = "CN=Samba"
590
591         self.fedoradsinf = os.path.join(self.ldapdir, "fedorads.inf")
592         self.partitions_ldif = os.path.join(self.ldapdir,
593             "fedorads-partitions.ldif")
594         self.sasl_ldif = os.path.join(self.ldapdir, "fedorads-sasl.ldif")
595         self.dna_ldif = os.path.join(self.ldapdir, "fedorads-dna.ldif")
596         self.pam_ldif = os.path.join(self.ldapdir, "fedorads-pam.ldif")
597         self.refint_ldif = os.path.join(self.ldapdir, "fedorads-refint.ldif")
598         self.linked_attrs_ldif = os.path.join(self.ldapdir,
599             "fedorads-linked-attributes.ldif")
600         self.index_ldif = os.path.join(self.ldapdir, "fedorads-index.ldif")
601         self.samba_ldif = os.path.join(self.ldapdir, "fedorads-samba.ldif")
602
603         self.samba3_schema = setup_path(
604             "../../examples/LDAP/samba.schema")
605         self.samba3_ldif = os.path.join(self.ldapdir, "samba3.ldif")
606
607         self.retcode = subprocess.call(["bin/oLschema2ldif",
608                 "-I", self.samba3_schema,
609                 "-O", self.samba3_ldif,
610                 "-b", self.names.domaindn],
611                 close_fds=True, shell=False)
612
613         if self.retcode != 0:
614             raise Exception("Unable to convert Samba 3 schema.")
615
616         self.schema = Schema(
617                 self.domainsid,
618                 schemadn=self.names.schemadn,
619                 files=[setup_path("schema_samba4.ldif"), self.samba3_ldif],
620                 additional_prefixmap=["1000:1.3.6.1.4.1.7165.2.1",
621                                       "1001:1.3.6.1.4.1.7165.2.2"])
622
623     def provision(self):
624         from samba.provision import ProvisioningError, setup_path
625         if self.ldap_backend_extra_port is not None:
626             serverport = "ServerPort=%d" % self.ldap_backend_extra_port
627         else:
628             serverport = ""
629
630         setup_file(setup_path("fedorads.inf"), self.fedoradsinf,
631                    {"ROOT": self.root,
632                     "HOSTNAME": self.hostname,
633                     "DNSDOMAIN": self.names.dnsdomain,
634                     "LDAPDIR": self.ldapdir,
635                     "DOMAINDN": self.names.domaindn,
636                     "LDAP_INSTANCE": self.ldap_instance,
637                     "LDAPMANAGERDN": self.names.ldapmanagerdn,
638                     "LDAPMANAGERPASS": self.ldapadminpass,
639                     "SERVERPORT": serverport})
640
641         setup_file(setup_path("fedorads-partitions.ldif"),
642             self.partitions_ldif,
643                    {"CONFIGDN": self.names.configdn,
644                     "SCHEMADN": self.names.schemadn,
645                     "SAMBADN": self.sambadn,
646                     })
647
648         setup_file(setup_path("fedorads-sasl.ldif"), self.sasl_ldif,
649                    {"SAMBADN": self.sambadn,
650                     })
651
652         setup_file(setup_path("fedorads-dna.ldif"), self.dna_ldif,
653                    {"DOMAINDN": self.names.domaindn,
654                     "SAMBADN": self.sambadn,
655                     "DOMAINSID": str(self.domainsid),
656                     })
657
658         setup_file(setup_path("fedorads-pam.ldif"), self.pam_ldif)
659
660         lnkattr = self.schema.linked_attributes()
661
662         refint_config = open(setup_path("fedorads-refint-delete.ldif"), 'r').read()
663         memberof_config = ""
664         index_config = ""
665         argnum = 3
666
667         for attr in lnkattr.keys():
668             if lnkattr[attr] is not None:
669                 refint_config += read_and_sub_file(
670                     setup_path("fedorads-refint-add.ldif"),
671                          { "ARG_NUMBER" : str(argnum),
672                            "LINK_ATTR" : attr })
673                 memberof_config += read_and_sub_file(
674                     setup_path("fedorads-linked-attributes.ldif"),
675                          { "MEMBER_ATTR" : attr,
676                            "MEMBEROF_ATTR" : lnkattr[attr] })
677                 index_config += read_and_sub_file(
678                     setup_path("fedorads-index.ldif"), { "ATTR" : attr })
679                 argnum += 1
680
681         open(self.refint_ldif, 'w').write(refint_config)
682         open(self.linked_attrs_ldif, 'w').write(memberof_config)
683
684         attrs = ["lDAPDisplayName"]
685         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)
686
687         for i in range (0, len(res)):
688             attr = res[i]["lDAPDisplayName"][0]
689
690             if attr == "objectGUID":
691                 attr = "nsUniqueId"
692
693             index_config += read_and_sub_file(
694                 setup_path("fedorads-index.ldif"), { "ATTR" : attr })
695
696         open(self.index_ldif, 'w').write(index_config)
697
698         setup_file(setup_path("fedorads-samba.ldif"), self.samba_ldif, {
699             "SAMBADN": self.sambadn,
700             "LDAPADMINPASS": self.ldapadminpass
701             })
702
703         mapping = "schema-map-fedora-ds-1.0"
704         backend_schema = "99_ad.ldif"
705
706         # Build a schema file in Fedora DS format
707         backend_schema_data = self.schema.convert_to_openldap("fedora-ds",
708             open(setup_path(mapping), 'r').read())
709         assert backend_schema_data is not None
710         f = open(os.path.join(self.ldapdir, backend_schema), 'w')
711         try:
712             f.write(backend_schema_data)
713         finally:
714             f.close()
715
716         self.credentials.set_bind_dn(self.names.ldapmanagerdn)
717
718         # Destory the target directory, or else setup-ds.pl will complain
719         fedora_ds_dir = os.path.join(self.ldapdir,
720             "slapd-" + self.ldap_instance)
721         shutil.rmtree(fedora_ds_dir, True)
722
723         self.slapd_provision_command = [self.slapd_path, "-D", fedora_ds_dir,
724                 "-i", self.slapd_pid]
725         # In the 'provision' command line, stay in the foreground so we can
726         # easily kill it
727         self.slapd_provision_command.append("-d0")
728
729         #the command for the final run is the normal script
730         self.slapd_command = [os.path.join(self.ldapdir,
731             "slapd-" + self.ldap_instance, "start-slapd")]
732
733         # If we were just looking for crashes up to this point, it's a
734         # good time to exit before we realise we don't have Fedora DS on
735         if self.ldap_dryrun_mode:
736             sys.exit(0)
737
738         # Try to print helpful messages when the user has not specified the
739         # path to the setup-ds tool
740         if self.setup_ds_path is None:
741             raise ProvisioningError("Fedora DS LDAP-Backend must be setup with path to setup-ds, e.g. --setup-ds-path=\"/usr/sbin/setup-ds.pl\"!")
742         if not os.path.exists(self.setup_ds_path):
743             self.logger.warning("Path (%s) to slapd does not exist!",
744                 self.setup_ds_path)
745
746         # Run the Fedora DS setup utility
747         retcode = subprocess.call([self.setup_ds_path, "--silent", "--file",
748             self.fedoradsinf], close_fds=True, shell=False)
749         if retcode != 0:
750             raise ProvisioningError("setup-ds failed")
751
752         # Load samba-admin
753         retcode = subprocess.call([
754             os.path.join(self.ldapdir, "slapd-" + self.ldap_instance, "ldif2db"), "-s", self.sambadn, "-i", self.samba_ldif],
755             close_fds=True, shell=False)
756         if retcode != 0:
757             raise ProvisioningError("ldif2db failed")
758
759     def post_setup(self):
760         ldapi_db = Ldb(self.ldap_uri, credentials=self.credentials)
761
762         # configure in-directory access control on Fedora DS via the aci
763         # attribute (over a direct ldapi:// socket)
764         aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % self.sambadn
765
766         m = ldb.Message()
767         m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci")
768
769         for dnstring in (self.names.domaindn, self.names.configdn,
770                          self.names.schemadn):
771             m.dn = ldb.Dn(ldapi_db, dnstring)
772             ldapi_db.modify(m)