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