~ubuntu-branches/ubuntu/wily/nss-pam-ldapd/wily

« back to all changes in this revision

Viewing changes to pynslcd/cache.py

  • Committer: Package Import Robot
  • Author(s): Arthur de Jong
  • Date: 2013-05-05 20:00:00 UTC
  • mfrom: (16.1.6) (14.1.6 experimental)
  • Revision ID: package-import@ubuntu.com-20130505200000-nhg39y204kr141mz
Tags: 0.8.13-1
* New upstream release
  - include an extra sanity check to ensure not too many file
    descriptors are open
  - fix handling of gid configuration option if it listed before the uid
    option
  - return NSS_STATUS_TRYAGAIN on zero-length (but not-NULL) buffer (thanks
    Jakub Hrozek)
  - provide an _nss_ldap_version symbol in the NSS module to help debug
    problems with a newer nslcd
  - retry updating the lastChange attribute with the normal nslcd LDAP
    connection if the update with the user's connection failed
  - avoid processing passwd_byuid requests for uids below nss_min_uid
  - fix a few minor or very unlikely to occur memory leaks
  - miscellaneous minor changes, fixes and compatibility improvements
* drop 01-fix-set-usec-instead-of-sec.patch which is part of 0.8.13
* remove compatibility code that converted nss-ldapd.conf to nslcd.conf
  for upgrading from pre-0.7 versions of nss-ldapd (thanks Dominik George)
* remove code for fixing permissions when upgrading from a pre-0.6.7.1
  version
* updated Turkish debconf translation by Atila KOÇ (closes: #701067)
* drop Richard A Nelson from uploaders
* add build dependency on autotools-dev to ensure config.sub and
  config.guess are automatically updated during build

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
 
 
2
# cache.py - caching layer for pynslcd
 
3
#
 
4
# Copyright (C) 2012 Arthur de Jong
 
5
#
 
6
# This library is free software; you can redistribute it and/or
 
7
# modify it under the terms of the GNU Lesser General Public
 
8
# License as published by the Free Software Foundation; either
 
9
# version 2.1 of the License, or (at your option) any later version.
 
10
#
 
11
# This library is distributed in the hope that it will be useful,
 
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
14
# Lesser General Public License for more details.
 
15
#
 
16
# You should have received a copy of the GNU Lesser General Public
 
17
# License along with this library; if not, write to the Free Software
 
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 
19
# 02110-1301 USA
 
20
 
 
21
import datetime
 
22
import itertools
 
23
import os
 
24
import sys
 
25
 
 
26
import sqlite3
 
27
 
 
28
 
 
29
# TODO: probably create a config table
 
30
 
 
31
# FIXME: store the cache in the right place and make it configurable
 
32
filename = '/var/run/nslcd/cache.sqlite'
 
33
dirname = os.path.dirname(filename)
 
34
if not os.path.isdir(dirname):
 
35
    os.mkdir(dirname)
 
36
con = sqlite3.connect(filename,
 
37
                detect_types=sqlite3.PARSE_DECLTYPES, check_same_thread=False)
 
38
con.row_factory = sqlite3.Row
 
39
 
 
40
# FIXME: have some way to remove stale entries from the cache if all items from LDAP are queried (perhas use TTL from all request)
 
41
 
 
42
# set up the database
 
43
con.executescript('''
 
44
 
 
45
    -- store temporary tables in memory
 
46
    PRAGMA temp_store = MEMORY;
 
47
 
 
48
    -- disable sync() on database (corruption on disk failure)
 
49
    PRAGMA synchronous = OFF;
 
50
 
 
51
    -- put journal in memory (corruption if crash during transaction)
 
52
    PRAGMA journal_mode = MEMORY;
 
53
 
 
54
    -- tables for alias cache
 
55
    CREATE TABLE IF NOT EXISTS `alias_cache`
 
56
      ( `cn` TEXT PRIMARY KEY COLLATE NOCASE,
 
57
        `mtime` TIMESTAMP NOT NULL );
 
58
    CREATE TABLE IF NOT EXISTS `alias_1_cache`
 
59
      ( `alias` TEXT NOT NULL COLLATE NOCASE,
 
60
        `rfc822MailMember` TEXT NOT NULL,
 
61
        FOREIGN KEY(`alias`) REFERENCES `alias_cache`(`cn`)
 
62
        ON DELETE CASCADE ON UPDATE CASCADE );
 
63
    CREATE INDEX IF NOT EXISTS `alias_1_idx` ON `alias_1_cache`(`alias`);
 
64
 
 
65
    -- table for ethernet cache
 
66
    CREATE TABLE IF NOT EXISTS `ether_cache`
 
67
      ( `cn` TEXT NOT NULL COLLATE NOCASE,
 
68
        `macAddress` TEXT NOT NULL COLLATE NOCASE,
 
69
        `mtime` TIMESTAMP NOT NULL,
 
70
        UNIQUE (`cn`, `macAddress`) );
 
71
 
 
72
    -- table for group cache
 
73
    CREATE TABLE IF NOT EXISTS `group_cache`
 
74
      ( `cn` TEXT PRIMARY KEY,
 
75
        `userPassword` TEXT,
 
76
        `gidNumber` INTEGER NOT NULL UNIQUE,
 
77
        `mtime` TIMESTAMP NOT NULL );
 
78
    CREATE TABLE IF NOT EXISTS `group_3_cache`
 
79
      ( `group` TEXT NOT NULL,
 
80
        `memberUid` TEXT NOT NULL,
 
81
        FOREIGN KEY(`group`) REFERENCES `group_cache`(`cn`)
 
82
        ON DELETE CASCADE ON UPDATE CASCADE );
 
83
    CREATE INDEX IF NOT EXISTS `group_3_idx` ON `group_3_cache`(`group`);
 
84
 
 
85
    -- tables for host cache
 
86
    CREATE TABLE IF NOT EXISTS `host_cache`
 
87
      ( `cn` TEXT PRIMARY KEY COLLATE NOCASE,
 
88
        `mtime` TIMESTAMP NOT NULL );
 
89
    CREATE TABLE IF NOT EXISTS `host_1_cache`
 
90
      ( `host` TEXT NOT NULL COLLATE NOCASE,
 
91
        `cn` TEXT NOT NULL COLLATE NOCASE,
 
92
        FOREIGN KEY(`host`) REFERENCES `host_cache`(`cn`)
 
93
        ON DELETE CASCADE ON UPDATE CASCADE );
 
94
    CREATE INDEX IF NOT EXISTS `host_1_idx` ON `host_1_cache`(`host`);
 
95
    CREATE TABLE IF NOT EXISTS `host_2_cache`
 
96
      ( `host` TEXT NOT NULL COLLATE NOCASE,
 
97
        `ipHostNumber` TEXT NOT NULL,
 
98
        FOREIGN KEY(`host`) REFERENCES `host_cache`(`cn`)
 
99
        ON DELETE CASCADE ON UPDATE CASCADE );
 
100
    CREATE INDEX IF NOT EXISTS `host_2_idx` ON `host_2_cache`(`host`);
 
101
 
 
102
    -- FIXME: this does not work as entries are never removed from the cache
 
103
    CREATE TABLE IF NOT EXISTS `netgroup_cache`
 
104
      ( `cn` TEXT NOT NULL,
 
105
        `member` TEXT NOT NULL,
 
106
        `mtime` TIMESTAMP NOT NULL,
 
107
        UNIQUE (`cn`, `member`) );
 
108
 
 
109
    -- tables for network cache
 
110
    CREATE TABLE IF NOT EXISTS `network_cache`
 
111
      ( `cn` TEXT PRIMARY KEY COLLATE NOCASE,
 
112
        `mtime` TIMESTAMP NOT NULL );
 
113
    CREATE TABLE IF NOT EXISTS `network_1_cache`
 
114
      ( `network` TEXT NOT NULL COLLATE NOCASE,
 
115
        `cn` TEXT NOT NULL COLLATE NOCASE,
 
116
        FOREIGN KEY(`network`) REFERENCES `network_cache`(`cn`)
 
117
        ON DELETE CASCADE ON UPDATE CASCADE );
 
118
    CREATE INDEX IF NOT EXISTS `network_1_idx` ON `network_1_cache`(`network`);
 
119
    CREATE TABLE IF NOT EXISTS `network_2_cache`
 
120
      ( `network` TEXT NOT NULL,
 
121
        `ipNetworkNumber` TEXT NOT NULL,
 
122
        FOREIGN KEY(`network`) REFERENCES `network_cache`(`cn`)
 
123
        ON DELETE CASCADE ON UPDATE CASCADE );
 
124
    CREATE INDEX IF NOT EXISTS `network_2_idx` ON `network_2_cache`(`network`);
 
125
 
 
126
    -- table for passwd cache
 
127
    CREATE TABLE IF NOT EXISTS `passwd_cache`
 
128
      ( `uid` TEXT PRIMARY KEY,
 
129
        `userPassword` TEXT,
 
130
        `uidNumber` INTEGER NOT NULL UNIQUE,
 
131
        `gidNumber` INTEGER NOT NULL,
 
132
        `gecos` TEXT,
 
133
        `homeDirectory` TEXT,
 
134
        `loginShell` TEXT,
 
135
        `mtime` TIMESTAMP NOT NULL );
 
136
 
 
137
    -- table for protocol cache
 
138
    CREATE TABLE IF NOT EXISTS `protocol_cache`
 
139
      ( `cn` TEXT PRIMARY KEY,
 
140
        `ipProtocolNumber` INTEGER NOT NULL,
 
141
        `mtime` TIMESTAMP NOT NULL );
 
142
    CREATE TABLE IF NOT EXISTS `protocol_1_cache`
 
143
      ( `protocol` TEXT NOT NULL,
 
144
        `cn` TEXT NOT NULL,
 
145
        FOREIGN KEY(`protocol`) REFERENCES `protocol_cache`(`cn`)
 
146
        ON DELETE CASCADE ON UPDATE CASCADE );
 
147
    CREATE INDEX IF NOT EXISTS `protocol_1_idx` ON `protocol_1_cache`(`protocol`);
 
148
 
 
149
    -- table for rpc cache
 
150
    CREATE TABLE IF NOT EXISTS `rpc_cache`
 
151
      ( `cn` TEXT PRIMARY KEY,
 
152
        `oncRpcNumber` INTEGER NOT NULL,
 
153
        `mtime` TIMESTAMP NOT NULL );
 
154
    CREATE TABLE IF NOT EXISTS `rpc_1_cache`
 
155
      ( `rpc` TEXT NOT NULL,
 
156
        `cn` TEXT NOT NULL,
 
157
        FOREIGN KEY(`rpc`) REFERENCES `rpc_cache`(`cn`)
 
158
        ON DELETE CASCADE ON UPDATE CASCADE );
 
159
    CREATE INDEX IF NOT EXISTS `rpc_1_idx` ON `rpc_1_cache`(`rpc`);
 
160
 
 
161
    -- tables for service cache
 
162
    CREATE TABLE IF NOT EXISTS `service_cache`
 
163
      ( `cn` TEXT NOT NULL,
 
164
        `ipServicePort` INTEGER NOT NULL,
 
165
        `ipServiceProtocol` TEXT NOT NULL,
 
166
        `mtime` TIMESTAMP NOT NULL,
 
167
        UNIQUE (`ipServicePort`, `ipServiceProtocol`) );
 
168
    CREATE TABLE IF NOT EXISTS `service_1_cache`
 
169
      ( `ipServicePort` INTEGER NOT NULL,
 
170
        `ipServiceProtocol` TEXT NOT NULL,
 
171
        `cn` TEXT NOT NULL,
 
172
        FOREIGN KEY(`ipServicePort`) REFERENCES `service_cache`(`ipServicePort`)
 
173
        ON DELETE CASCADE ON UPDATE CASCADE,
 
174
        FOREIGN KEY(`ipServiceProtocol`) REFERENCES `service_cache`(`ipServiceProtocol`)
 
175
        ON DELETE CASCADE ON UPDATE CASCADE );
 
176
    CREATE INDEX IF NOT EXISTS `service_1_idx1` ON `service_1_cache`(`ipServicePort`);
 
177
    CREATE INDEX IF NOT EXISTS `service_1_idx2` ON `service_1_cache`(`ipServiceProtocol`);
 
178
 
 
179
    -- table for shadow cache
 
180
    CREATE TABLE IF NOT EXISTS `shadow_cache`
 
181
      ( `uid` TEXT PRIMARY KEY,
 
182
        `userPassword` TEXT,
 
183
        `shadowLastChange` INTEGER,
 
184
        `shadowMin` INTEGER,
 
185
        `shadowMax` INTEGER,
 
186
        `shadowWarning` INTEGER,
 
187
        `shadowInactive` INTEGER,
 
188
        `shadowExpire` INTEGER,
 
189
        `shadowFlag` INTEGER,
 
190
        `mtime` TIMESTAMP NOT NULL );
 
191
 
 
192
    ''')
 
193
 
 
194
 
 
195
class Query(object):
 
196
 
 
197
    def __init__(self, query, parameters=None):
 
198
        self.query = query
 
199
        self.wheres = []
 
200
        self.parameters = []
 
201
        if parameters:
 
202
            for k, v in parameters.items():
 
203
                self.add_where('`%s` = ?' % k, [v])
 
204
 
 
205
    def add_query(self, query):
 
206
        self.query += ' ' + query
 
207
 
 
208
    def add_where(self, where, parameters):
 
209
        self.wheres.append(where)
 
210
        self.parameters += parameters
 
211
 
 
212
    def execute(self, con):
 
213
        query = self.query
 
214
        if self.wheres:
 
215
            query += ' WHERE ' + ' AND '.join(self.wheres)
 
216
        c = con.cursor()
 
217
        return c.execute(query, self.parameters)
 
218
 
 
219
 
 
220
class CnAliasedQuery(Query):
 
221
 
 
222
    sql = '''
 
223
        SELECT `%(table)s_cache`.*,
 
224
               `%(table)s_1_cache`.`cn` AS `alias`
 
225
        FROM `%(table)s_cache`
 
226
        LEFT JOIN `%(table)s_1_cache`
 
227
          ON `%(table)s_1_cache`.`%(table)s` = `%(table)s_cache`.`cn`
 
228
        '''
 
229
 
 
230
    cn_join = '''
 
231
        LEFT JOIN `%(table)s_1_cache` `cn_alias`
 
232
          ON `cn_alias`.`%(table)s` = `%(table)s_cache`.`cn`
 
233
        '''
 
234
 
 
235
    def __init__(self, table, parameters):
 
236
        args = dict(table=table)
 
237
        super(CnAliasedQuery, self).__init__(self.sql % args)
 
238
        for k, v in parameters.items():
 
239
            if k == 'cn':
 
240
                self.add_query(self.cn_join % args)
 
241
                self.add_where('(`%(table)s_cache`.`cn` = ? OR `cn_alias`.`cn` = ?)' % args, [v, v])
 
242
            else:
 
243
                self.add_where('`%s` = ?' % k, [v])
 
244
 
 
245
 
 
246
class RowGrouper(object):
 
247
    """Pass in query results and group the results by a certain specified
 
248
    list of columns."""
 
249
 
 
250
    def __init__(self, results, groupby, columns):
 
251
        self.groupby = groupby
 
252
        self.columns = columns
 
253
        self.results = itertools.groupby(results, key=self.keyfunc)
 
254
 
 
255
    def __iter__(self):
 
256
        return self
 
257
 
 
258
    def keyfunc(self, row):
 
259
        return tuple(row[x] for x in self.groupby)
 
260
 
 
261
    def next(self):
 
262
        groupcols, rows = self.results.next()
 
263
        tmp = dict((x, list()) for x in self.columns)
 
264
        for row in rows:
 
265
            for col in self.columns:
 
266
                if row[col] is not None:
 
267
                    tmp[col].append(row[col])
 
268
        result = dict(row)
 
269
        result.update(tmp)
 
270
        return result
 
271
 
 
272
 
 
273
class Cache(object):
 
274
 
 
275
    def __init__(self):
 
276
        self.con = con
 
277
        self.table = sys.modules[self.__module__].__name__
 
278
 
 
279
    def store(self, *values):
 
280
        """Store the values in the cache for the specified table."""
 
281
        simple_values = []
 
282
        multi_values = {}
 
283
        for n, v in enumerate(values):
 
284
            if isinstance(v, (list, tuple, set)):
 
285
                multi_values[n] = v
 
286
            else:
 
287
                simple_values.append(v)
 
288
        simple_values.append(datetime.datetime.now())
 
289
        args = ', '.join(len(simple_values) * ('?', ))
 
290
        con.execute('''
 
291
            INSERT OR REPLACE INTO %s_cache
 
292
            VALUES
 
293
              (%s)
 
294
            ''' % (self.table, args), simple_values)
 
295
        for n, vlist in multi_values.items():
 
296
            con.execute('''
 
297
                DELETE FROM %s_%d_cache
 
298
                WHERE `%s` = ?
 
299
                ''' % (self.table, n, self.table), (values[0], ))
 
300
            con.executemany('''
 
301
                INSERT INTO %s_%d_cache
 
302
                VALUES
 
303
                  (?, ?)
 
304
                ''' % (self.table, n), ((values[0], x) for x in vlist))
 
305
 
 
306
    def retrieve(self, parameters):
 
307
        """Retrieve all items from the cache based on the parameters supplied."""
 
308
        query = Query('''
 
309
            SELECT *
 
310
            FROM %s_cache
 
311
            ''' % self.table, parameters)
 
312
        return (list(x)[:-1] for x in query.execute(self.con))