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