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