s4-python: Fix formatting.
[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 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         search_ol_rootdse = 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             search_ol_rootdse = 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, e:
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                 search_ol_rootdse = 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, e:
245                 time.sleep(1)
246                 pass
247         
248         raise ProvisioningError("slapd died before we could make a connection to it")
249
250     def shutdown(self):
251         # if an LDAP backend is in use, terminate slapd after final provision and check its proper termination
252         if self.slapd.poll() is None:
253             #Kill the slapd
254             if hasattr(self.slapd, "terminate"):
255                 self.slapd.terminate()
256             else:
257                 # Older python versions don't have .terminate()
258                 import signal
259                 os.kill(self.slapd.pid, signal.SIGTERM)
260     
261             #and now wait for it to die
262             self.slapd.communicate()
263
264
265 class OpenLDAPBackend(LDAPBackend):
266     def __init__(self, backend_type, paths=None, setup_path=None, lp=None, credentials=None,
267                  names=None, message=None,
268                  domainsid=None,
269                  schema=None,
270                  hostname=None,
271                  ldapadminpass=None,
272                  slapd_path=None,
273                  ldap_backend_extra_port=None,
274                  ldap_dryrun_mode=False,
275                  ol_mmr_urls=None,
276                  nosync=False):
277
278         super(OpenLDAPBackend, self).__init__(
279                 backend_type=backend_type,
280                 paths=paths, setup_path=setup_path,
281                 lp=lp, credentials=credentials,
282                 names=names,
283                 message=message,
284                 domainsid=domainsid,
285                 schema=schema,
286                 hostname=hostname,
287                 ldapadminpass=ldapadminpass,
288                 slapd_path=slapd_path,
289                 ldap_backend_extra_port=ldap_backend_extra_port,
290                 ldap_dryrun_mode=ldap_dryrun_mode)
291
292         self.ol_mmr_urls = ol_mmr_urls
293         self.nosync = nosync
294
295         self.schema = Schema(
296                 self.setup_path,
297                 self.domainsid,
298                 schemadn=self.names.schemadn,
299                 serverdn=self.names.serverdn,
300                 files=[setup_path("schema_samba4.ldif")]);
301
302     def provision(self):
303         # Wipe the directories so we can start
304         shutil.rmtree(os.path.join(self.paths.ldapdir, "db"), True)
305
306         #Allow the test scripts to turn off fsync() for OpenLDAP as for TDB and LDB
307         nosync_config = ""
308         if self.nosync:
309             nosync_config = "dbnosync"
310         
311         lnkattr = self.schema.linked_attributes()
312         refint_attributes = ""
313         memberof_config = "# Generated from Samba4 schema\n"
314         for att in  lnkattr.keys():
315             if lnkattr[att] is not None:
316                 refint_attributes = refint_attributes + " " + att 
317             
318                 memberof_config += read_and_sub_file(self.setup_path("memberof.conf"),
319                                                  { "MEMBER_ATTR" : att ,
320                                                    "MEMBEROF_ATTR" : lnkattr[att] })
321             
322         refint_config = read_and_sub_file(self.setup_path("refint.conf"),
323                                       { "LINK_ATTRS" : refint_attributes})
324     
325         attrs = ["linkID", "lDAPDisplayName"]
326         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)
327         index_config = ""
328         for i in range (0, len(res)):
329             index_attr = res[i]["lDAPDisplayName"][0]
330             if index_attr == "objectGUID":
331                 index_attr = "entryUUID"
332             
333             index_config += "index " + index_attr + " eq\n"
334
335         # generate serverids, ldap-urls and syncrepl-blocks for mmr hosts
336         mmr_on_config = ""
337         mmr_replicator_acl = ""
338         mmr_serverids_config = ""
339         mmr_syncrepl_schema_config = "" 
340         mmr_syncrepl_config_config = "" 
341         mmr_syncrepl_user_config = "" 
342        
343     
344         if self.ol_mmr_urls is not None:
345             # For now, make these equal
346             mmr_pass = self.ldapadminpass
347         
348             url_list=filter(None,self.ol_mmr_urls.split(' ')) 
349             if (len(url_list) == 1):
350                 url_list=filter(None,self.ol_mmr_urls.split(',')) 
351                      
352             
353                 mmr_on_config = "MirrorMode On"
354                 mmr_replicator_acl = "  by dn=cn=replicator,cn=samba read"
355                 serverid=0
356                 for url in url_list:
357                     serverid=serverid+1
358                     mmr_serverids_config += read_and_sub_file(self.setup_path("mmr_serverids.conf"),
359                                                           { "SERVERID" : str(serverid),
360                                                             "LDAPSERVER" : url })
361                     rid=serverid*10
362                     rid=rid+1
363                     mmr_syncrepl_schema_config += read_and_sub_file(self.setup_path("mmr_syncrepl.conf"),
364                                                                 {  "RID" : str(rid),
365                                                                    "MMRDN": self.names.schemadn,
366                                                                    "LDAPSERVER" : url,
367                                                                    "MMR_PASSWORD": mmr_pass})
368                 
369                     rid=rid+1
370                     mmr_syncrepl_config_config += read_and_sub_file(self.setup_path("mmr_syncrepl.conf"),
371                                                                 {  "RID" : str(rid),
372                                                                    "MMRDN": self.names.configdn,
373                                                                    "LDAPSERVER" : url,
374                                                                    "MMR_PASSWORD": mmr_pass})
375                 
376                     rid=rid+1
377                     mmr_syncrepl_user_config += read_and_sub_file(self.setup_path("mmr_syncrepl.conf"),
378                                                               {  "RID" : str(rid),
379                                                                  "MMRDN": self.names.domaindn,
380                                                                  "LDAPSERVER" : url,
381                                                                  "MMR_PASSWORD": mmr_pass })
382         # OpenLDAP cn=config initialisation
383         olc_syncrepl_config = ""
384         olc_mmr_config = "" 
385         # if mmr = yes, generate cn=config-replication directives
386         # and olc_seed.lif for the other mmr-servers
387         if self.ol_mmr_urls is not None:
388             serverid=0
389             olc_serverids_config = ""
390             olc_syncrepl_seed_config = ""
391             olc_mmr_config += read_and_sub_file(self.setup_path("olc_mmr.conf"),{})
392             rid=1000
393             for url in url_list:
394                 serverid=serverid+1
395                 olc_serverids_config += read_and_sub_file(self.setup_path("olc_serverid.conf"),
396                                                       { "SERVERID" : str(serverid),
397                                                         "LDAPSERVER" : url })
398             
399                 rid=rid+1
400                 olc_syncrepl_config += read_and_sub_file(self.setup_path("olc_syncrepl.conf"),
401                                                      {  "RID" : str(rid),
402                                                         "LDAPSERVER" : url,
403                                                         "MMR_PASSWORD": mmr_pass})
404             
405                 olc_syncrepl_seed_config += read_and_sub_file(self.setup_path("olc_syncrepl_seed.conf"),
406                                                           {  "RID" : str(rid),
407                                                              "LDAPSERVER" : url})
408                 
409             setup_file(self.setup_path("olc_seed.ldif"), self.paths.olcseedldif,
410                        {"OLC_SERVER_ID_CONF": olc_serverids_config,
411                         "OLC_PW": self.ldapadminpass,
412                         "OLC_SYNCREPL_CONF": olc_syncrepl_seed_config})
413         # end olc
414                 
415         setup_file(self.setup_path("slapd.conf"), self.paths.slapdconf,
416                    {"DNSDOMAIN": self.names.dnsdomain,
417                     "LDAPDIR": self.paths.ldapdir,
418                     "DOMAINDN": self.names.domaindn,
419                     "CONFIGDN": self.names.configdn,
420                     "SCHEMADN": self.names.schemadn,
421                     "MEMBEROF_CONFIG": memberof_config,
422                     "MIRRORMODE": mmr_on_config,
423                     "REPLICATOR_ACL": mmr_replicator_acl,
424                     "MMR_SERVERIDS_CONFIG": mmr_serverids_config,
425                     "MMR_SYNCREPL_SCHEMA_CONFIG": mmr_syncrepl_schema_config,
426                     "MMR_SYNCREPL_CONFIG_CONFIG": mmr_syncrepl_config_config,
427                     "MMR_SYNCREPL_USER_CONFIG": mmr_syncrepl_user_config,
428                     "OLC_SYNCREPL_CONFIG": olc_syncrepl_config,
429                     "OLC_MMR_CONFIG": olc_mmr_config,
430                     "REFINT_CONFIG": refint_config,
431                     "INDEX_CONFIG": index_config,
432                     "NOSYNC": nosync_config})
433         
434         setup_db_config(self.setup_path, os.path.join(self.paths.ldapdir, "db", "user"))
435         setup_db_config(self.setup_path, os.path.join(self.paths.ldapdir, "db", "config"))
436         setup_db_config(self.setup_path, os.path.join(self.paths.ldapdir, "db", "schema"))
437     
438         if not os.path.exists(os.path.join(self.paths.ldapdir, "db", "samba",  "cn=samba")):
439             os.makedirs(os.path.join(self.paths.ldapdir, "db", "samba",  "cn=samba"), 0700)
440         
441         setup_file(self.setup_path("cn=samba.ldif"), 
442                    os.path.join(self.paths.ldapdir, "db", "samba",  "cn=samba.ldif"),
443                    { "UUID": str(uuid.uuid4()), 
444                      "LDAPTIME": timestring(int(time.time()))} )
445         setup_file(self.setup_path("cn=samba-admin.ldif"), 
446                    os.path.join(self.paths.ldapdir, "db", "samba",  "cn=samba", "cn=samba-admin.ldif"),
447                    {"LDAPADMINPASS_B64": b64encode(self.ldapadminpass),
448                     "UUID": str(uuid.uuid4()), 
449                     "LDAPTIME": timestring(int(time.time()))} )
450     
451         if self.ol_mmr_urls is not None:
452             setup_file(self.setup_path("cn=replicator.ldif"),
453                        os.path.join(self.paths.ldapdir, "db", "samba",  "cn=samba", "cn=replicator.ldif"),
454                        {"MMR_PASSWORD_B64": b64encode(mmr_pass),
455                         "UUID": str(uuid.uuid4()),
456                         "LDAPTIME": timestring(int(time.time()))} )
457         
458
459         mapping = "schema-map-openldap-2.3"
460         backend_schema = "backend-schema.schema"
461
462         backend_schema_data = self.schema.ldb.convert_schema_to_openldap("openldap", open(self.setup_path(mapping), 'r').read())
463         assert backend_schema_data is not None
464         open(os.path.join(self.paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
465
466         # now we generate the needed strings to start slapd automatically,
467         # first ldapi_uri...
468         if self.ldap_backend_extra_port is not None:
469             # When we use MMR, we can't use 0.0.0.0 as it uses the name
470             # specified there as part of it's clue as to it's own name,
471             # and not to replicate to itself
472             if self.ol_mmr_urls is None:
473                 server_port_string = "ldap://0.0.0.0:%d" % self.ldap_backend_extra_port
474             else:
475                 server_port_string = "ldap://" + self.names.hostname + "." + self.names.dnsdomain +":%d" % self.ldap_backend_extra_port
476         else:
477             server_port_string = ""
478
479         # Prepare the 'result' information - the commands to return in particular
480         self.slapd_provision_command = [self.slapd_path]
481
482         self.slapd_provision_command.append("-F" + self.paths.olcdir)
483
484         self.slapd_provision_command.append("-h")
485
486         # copy this command so we have two version, one with -d0 and only ldapi, and one with all the listen commands
487         self.slapd_command = list(self.slapd_provision_command)
488     
489         self.slapd_provision_command.append(self.ldapi_uri)
490         self.slapd_provision_command.append("-d0")
491
492         uris = self.ldapi_uri
493         if server_port_string is not "":
494             uris = uris + " " + server_port_string
495
496         self.slapd_command.append(uris)
497
498         # Set the username - done here because Fedora DS still uses the admin DN and simple bind
499         self.credentials.set_username("samba-admin")
500     
501         # If we were just looking for crashes up to this point, it's a
502         # good time to exit before we realise we don't have OpenLDAP on
503         # this system
504         if self.ldap_dryrun_mode:
505             sys.exit(0)
506
507         # Finally, convert the configuration into cn=config style!
508         if not os.path.isdir(self.paths.olcdir):
509             os.makedirs(self.paths.olcdir, 0770)
510
511             retcode = subprocess.call([self.slapd_path, "-Ttest", "-f", self.paths.slapdconf, "-F", self.paths.olcdir], close_fds=True, shell=False)
512
513 #            We can't do this, as OpenLDAP is strange.  It gives an error
514 #            output to the above, but does the conversion sucessfully...
515 #
516 #            if retcode != 0:
517 #                raise ProvisioningError("conversion from slapd.conf to cn=config failed")
518
519             if not os.path.exists(os.path.join(self.paths.olcdir, "cn=config.ldif")):
520                 raise ProvisioningError("conversion from slapd.conf to cn=config failed")
521
522             # Don't confuse the admin by leaving the slapd.conf around
523             os.remove(self.paths.slapdconf)        
524
525
526 class FDSBackend(LDAPBackend):
527     def __init__(self, backend_type, paths=None, setup_path=None, lp=None, credentials=None,
528                  names=None, message=None,
529                  domainsid=None,
530                  schema=None,
531                  hostname=None,
532                  ldapadminpass=None,
533                  slapd_path=None,
534                  ldap_backend_extra_port=None,
535                  ldap_dryrun_mode=False,
536                  root=None,
537                  setup_ds_path=None):
538
539         super(FDSBackend, self).__init__(
540                 backend_type=backend_type,
541                 paths=paths, setup_path=setup_path,
542                 lp=lp, credentials=credentials,
543                 names=names,
544                 message=message,
545                 domainsid=domainsid,
546                 schema=schema,
547                 hostname=hostname,
548                 ldapadminpass=ldapadminpass,
549                 slapd_path=slapd_path,
550                 ldap_backend_extra_port=ldap_backend_extra_port,
551                 ldap_dryrun_mode=ldap_dryrun_mode)
552
553         self.root = root
554         self.setup_ds_path = setup_ds_path
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                     "LDAPMANAGERDN": self.names.ldapmanagerdn,
601                     "LDAPMANAGERPASS": self.ldapadminpass, 
602                     "SERVERPORT": serverport})
603
604         setup_file(self.setup_path("fedorads-partitions.ldif"), self.partitions_ldif, 
605                    {"CONFIGDN": self.names.configdn,
606                     "SCHEMADN": self.names.schemadn,
607                     "SAMBADN": self.sambadn,
608                     })
609
610         setup_file(self.setup_path("fedorads-sasl.ldif"), self.sasl_ldif, 
611                    {"SAMBADN": self.sambadn,
612                     })
613
614         setup_file(self.setup_path("fedorads-dna.ldif"), self.dna_ldif, 
615                    {"DOMAINDN": self.names.domaindn,
616                     "SAMBADN": self.sambadn,
617                     "DOMAINSID": str(self.domainsid),
618                     })
619
620         setup_file(self.setup_path("fedorads-pam.ldif"), self.pam_ldif)
621
622         lnkattr = self.schema.linked_attributes()
623
624         refint_config = data = open(self.setup_path("fedorads-refint-delete.ldif"), 'r').read()
625         memberof_config = ""
626         index_config = ""
627         argnum = 3
628
629         for attr in lnkattr.keys():
630             if lnkattr[attr] is not None:
631                 refint_config += read_and_sub_file(self.setup_path("fedorads-refint-add.ldif"),
632                                                  { "ARG_NUMBER" : str(argnum) ,
633                                                    "LINK_ATTR" : attr })
634                 memberof_config += read_and_sub_file(self.setup_path("fedorads-linked-attributes.ldif"),
635                                                  { "MEMBER_ATTR" : attr ,
636                                                    "MEMBEROF_ATTR" : lnkattr[attr] })
637                 index_config += read_and_sub_file(self.setup_path("fedorads-index.ldif"),
638                                                  { "ATTR" : attr })
639                 argnum += 1
640
641         open(self.refint_ldif, 'w').write(refint_config)
642         open(self.linked_attrs_ldif, 'w').write(memberof_config)
643
644         attrs = ["lDAPDisplayName"]
645         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)
646
647         for i in range (0, len(res)):
648             attr = res[i]["lDAPDisplayName"][0]
649
650             if attr == "objectGUID":
651                 attr = "nsUniqueId"
652
653             index_config += read_and_sub_file(self.setup_path("fedorads-index.ldif"),
654                                              { "ATTR" : attr })
655
656         open(self.index_ldif, 'w').write(index_config)
657
658         setup_file(self.setup_path("fedorads-samba.ldif"), self.samba_ldif,
659                     {"SAMBADN": self.sambadn, 
660                      "LDAPADMINPASS": self.ldapadminpass
661                     })
662
663         mapping = "schema-map-fedora-ds-1.0"
664         backend_schema = "99_ad.ldif"
665     
666         # Build a schema file in Fedora DS format
667         backend_schema_data = self.schema.ldb.convert_schema_to_openldap("fedora-ds", open(self.setup_path(mapping), 'r').read())
668         assert backend_schema_data is not None
669         open(os.path.join(self.paths.ldapdir, backend_schema), 'w').write(backend_schema_data)
670
671         self.credentials.set_bind_dn(self.names.ldapmanagerdn)
672
673         # Destory the target directory, or else setup-ds.pl will complain
674         fedora_ds_dir = os.path.join(self.paths.ldapdir, "slapd-samba4")
675         shutil.rmtree(fedora_ds_dir, True)
676
677         self.slapd_provision_command = [self.slapd_path, "-D", fedora_ds_dir, "-i", self.paths.slapdpid];
678         #In the 'provision' command line, stay in the foreground so we can easily kill it
679         self.slapd_provision_command.append("-d0")
680
681         #the command for the final run is the normal script
682         self.slapd_command = [os.path.join(self.paths.ldapdir, "slapd-samba4", "start-slapd")]
683
684         # If we were just looking for crashes up to this point, it's a
685         # good time to exit before we realise we don't have Fedora DS on
686         if self.ldap_dryrun_mode:
687             sys.exit(0)
688
689         # Try to print helpful messages when the user has not specified the path to the setup-ds tool
690         if self.setup_ds_path is None:
691             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\"!")
692         if not os.path.exists(self.setup_ds_path):
693             self.message (self.setup_ds_path)
694             raise ProvisioningError("Warning: Given Path to slapd does not exist!")
695
696         # Run the Fedora DS setup utility
697         retcode = subprocess.call([self.setup_ds_path, "--silent", "--file", self.fedoradsinf], close_fds=True, shell=False)
698         if retcode != 0:
699             raise ProvisioningError("setup-ds failed")
700
701         # Load samba-admin
702         retcode = subprocess.call([
703             os.path.join(self.paths.ldapdir, "slapd-samba4", "ldif2db"), "-s", self.sambadn, "-i", self.samba_ldif],
704             close_fds=True, shell=False)
705         if retcode != 0:
706             raise ProvisioningError("ldif2db failed")
707
708     def post_setup(self):
709         ldapi_db = Ldb(self.ldapi_uri, credentials=self.credentials)
710
711         # delete default SASL mappings
712         res = ldapi_db.search(expression="(!(cn=samba-admin mapping))", base="cn=mapping,cn=sasl,cn=config", scope=SCOPE_ONELEVEL, attrs=["dn"])
713     
714         # configure in-directory access control on Fedora DS via the aci attribute (over a direct ldapi:// socket)
715         for i in range (0, len(res)):
716             dn = str(res[i]["dn"])
717             ldapi_db.delete(dn)
718             
719         aci = """(targetattr = "*") (version 3.0;acl "full access to all by samba-admin";allow (all)(userdn = "ldap:///CN=samba-admin,%s");)""" % self.sambadn
720         
721         m = ldb.Message()
722         m["aci"] = ldb.MessageElement([aci], ldb.FLAG_MOD_REPLACE, "aci")
723
724         m.dn = ldb.Dn(ldapi_db, self.names.domaindn)
725         ldapi_db.modify(m)
726             
727         m.dn = ldb.Dn(ldapi_db, self.names.configdn)
728         ldapi_db.modify(m)
729             
730         m.dn = ldb.Dn(ldapi_db, self.names.schemadn)
731         ldapi_db.modify(m)