~lefteris-nikoltsios/+junk/samba-lp1016895

« back to all changes in this revision

Viewing changes to source4/scripting/python/samba/provision/backend.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short
  • Date: 2011-12-21 13:18:04 UTC
  • mfrom: (0.39.21 sid)
  • Revision ID: package-import@ubuntu.com-20111221131804-xtlr39wx6njehxxr
Tags: 2:3.6.1-3ubuntu1
* Merge from Debian testing.  Remaining changes:
  + debian/patches/VERSION.patch:
    - set SAMBA_VERSION_SUFFIX to Ubuntu.
  + debian/patches/error-trans.fix-276472:
    - Add the translation of Unix Error code -ENOTSUP to NT Error Code
    - NT_STATUS_NOT_SUPPORTED to prevent the Permission denied error.
  + debian/smb.conf:
    - add "(Samba, Ubuntu)" to server string.
    - comment out the default [homes] share, and add a comment about
      "valid users = %S" to show users how to restrict access to
      \\server\username to only username.
    - Set 'usershare allow guests', so that usershare admins are 
      allowed to create public shares in addition to authenticated
      ones.
    - add map to guest = Bad user, maps bad username to guest access.
  + debian/samba-common.config:
    - Do not change priority to high if dhclient3 is installed.
    - Use priority medium instead of high for the workgroup question.
  + debian/control:
    - Don't build against or suggest ctdb.
    - Add dependency on samba-common-bin to samba.
  + Add ufw integration:
    - Created debian/samba.ufw.profile
    - debian/rules, debian/samba.dirs, debian/samba.files: install
      profile
    - debian/control: have samba suggest ufw
  + Add apport hook:
    - Created debian/source_samba.py.
    - debian/rules, debian/samba.dirs, debian/samba-common-bin.files: install
  + Switch to upstart:
    - Add debian/samba.{nmbd,smbd}.upstart.
  + debian/samba.logrotate, debian/samba-common.dhcp, debian/samba.if-up:
    - Make them upstart compatible
  + debian/samba.postinst: 
    - Avoid scary pdbedit warnings on first import.
  + debian/samba-common.postinst: Add more informative error message for
    the case where smb.conf was manually deleted
  + debian/patches/fix-debuglevel-name-conflict.patch: don't use 'debug_level'
    as a global variable name in an NSS module 
  + Dropped:
    - debian/patches/error-trans.fix-276472
    - debian/patches/fix-debuglevel-name-conflict.patch

Show diffs side-by-side

added added

removed removed

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