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