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